@every-env/cli
Version:
Multi-agent orchestrator for AI-powered development workflows
279 lines • 12.1 kB
JavaScript
import chalk from 'chalk';
import { join } from 'path';
import ora from 'ora';
import { execCapture } from '../utils/exec.js';
import { logger } from '../utils/logger.js';
import { existsSync, statSync } from 'fs';
import { mkdir } from 'fs/promises';
import { ConfigLoader } from '../core/config-loader.js';
import { AgentManager } from '../core/agent-manager.js';
import { PatternRegistry } from '../patterns/registry/pattern-registry.js';
import { PatternLoader } from '../patterns/registry/pattern-loader.js';
// Re-export the existing review functions for backward compatibility
export { parseReviewTarget, renderReviewTemplate, executeReviewCommand } from './review.js';
/**
* Set up worktree for PR review
*/
async function setupWorktreeForReview(baseDir, reviewTarget) {
try {
if (!reviewTarget) {
return undefined;
}
// Determine if target looks like a PR number or GitHub PR URL
let prIdentifier = null;
const numericMatch = reviewTarget.match(/^\d+$/);
const urlMatch = reviewTarget.match(/pull\/(\d+)/);
if (numericMatch) {
prIdentifier = numericMatch[0];
}
else if (urlMatch) {
prIdentifier = urlMatch[1];
}
else {
// If target refers to a local file, skip worktree setup
const absPath = join(baseDir, reviewTarget);
if (existsSync(absPath))
return undefined;
return undefined;
}
// Resolve git root
const { stdout: gitRootOut } = await execCapture('git', ['rev-parse', '--show-toplevel'], { cwd: baseDir });
const gitRoot = gitRootOut.trim();
if (!gitRoot)
return undefined;
const timestamp = new Date()
.toISOString()
.replace(/[-:T]/g, '')
.slice(0, 15);
const worktreeBase = join(gitRoot, '.worktrees', 'reviews');
const worktreePath = join(worktreeBase, `pr-${prIdentifier}-${timestamp}`);
// Ensure base directory exists
await mkdir(worktreeBase, { recursive: true });
// Fetch PR ref into local branch
try {
await execCapture('git', ['fetch', 'origin', `pull/${prIdentifier}/head:pr-${prIdentifier}`], { cwd: gitRoot });
}
catch (e) {
logger.debug?.(`git fetch for PR ${prIdentifier} may have failed or already exists: ${e instanceof Error ? e.message : String(e)}`);
}
// Create the worktree
try {
await execCapture('git', ['worktree', 'add', worktreePath, `pr-${prIdentifier}`], { cwd: gitRoot });
}
catch (e) {
if (!existsSync(worktreePath) || !statSync(worktreePath).isDirectory()) {
throw e;
}
}
console.log(chalk.green(`✓ Worktree ready: ${worktreePath}`));
// Install dependencies
try {
const pkgJson = join(worktreePath, 'package.json');
const reqs = join(worktreePath, 'requirements.txt');
const gemfile = join(worktreePath, 'Gemfile');
if (existsSync(pkgJson)) {
console.log(chalk.cyan('⬇ Installing npm dependencies in worktree...'));
await execCapture('npm', ['install', '--silent'], { cwd: worktreePath });
}
else if (existsSync(reqs)) {
console.log(chalk.cyan('⬇ Installing Python requirements in worktree...'));
await execCapture('pip', ['install', '-r', 'requirements.txt'], { cwd: worktreePath });
}
else if (existsSync(gemfile)) {
console.log(chalk.cyan('⬇ Installing Ruby gems in worktree...'));
await execCapture('bundle', ['install'], { cwd: worktreePath });
}
}
catch (e) {
logger.warn?.(`Dependency installation in worktree encountered an issue: ${e instanceof Error ? e.message : String(e)}`);
}
return worktreePath;
}
catch (e) {
logger.warn?.(`Worktree setup skipped due to error: ${e instanceof Error ? e.message : String(e)}`);
return undefined;
}
}
/**
* Load review patterns from the pattern registry
*/
async function loadReviewPatterns(baseDir, patternNames) {
const loader = new PatternLoader();
const registry = new PatternRegistry();
// Load built-in patterns
const builtInPatternsDir = join(dirname(fileURLToPath(import.meta.url)), '../../resources/patterns/review');
if (existsSync(builtInPatternsDir)) {
await loader.loadPatterns(builtInPatternsDir, registry, 'review');
}
// Load user patterns
const userPatternsDir = join(baseDir, '.every-env/patterns/review');
if (existsSync(userPatternsDir)) {
await loader.loadPatterns(userPatternsDir, registry, 'review');
}
// Get requested patterns or all patterns
if (patternNames && patternNames.length > 0) {
return patternNames.map(name => {
const pattern = registry.getPattern(name, 'review');
if (!pattern) {
throw new Error(`Review pattern '${name}' not found`);
}
return pattern;
});
}
// Return all review patterns
return registry.getPatternsByCommand('review');
}
/**
* Execute review patterns using the agent manager
*/
async function executeReviewPatterns(config, patterns, context, options) {
const spinner = ora('Executing review agents...').start();
try {
// Prepare variables for pattern execution
const variables = {
...context,
projectName: config.projectName || 'Project',
date: new Date().toISOString().split('T')[0],
timestamp: new Date().toISOString(),
};
// Create agent manager with parallel execution
const agentManager = new AgentManager(config, {
concurrency: options.parallel || 5,
});
// Create tasks for all agents across all patterns
const tasks = [];
for (const pattern of patterns) {
for (const agent of pattern.agents) {
tasks.push({
pattern: pattern.name,
agent,
matches: [], // Review agents don't need file matches
variables: {
...variables,
...pattern.variables,
},
workingDir: context.worktreePath,
});
}
}
spinner.text = `Executing ${tasks.length} review agents across ${patterns.length} patterns...`;
// Execute all agents
const results = await agentManager.executeTasks(tasks);
spinner.succeed(`Completed ${results.length} review agents`);
// Generate final synthesis if ultra-synthesis agent was included
const synthesisTasks = tasks.filter(t => t.agent.id === 'ultra-synthesis');
if (synthesisTasks.length > 0) {
console.log(chalk.cyan('\n📊 Review synthesis complete!'));
console.log(chalk.gray(`Review artifacts available in: ${context.outputDir}`));
}
}
catch (error) {
spinner.fail('Review execution failed');
throw error;
}
}
/**
* Enhanced review command with multi-agent support
*/
export async function runEnhancedReviewCommand(args, options = {}) {
const baseDir = options.baseDir || process.cwd();
const configPath = options.configPath || join(baseDir, '.every-env', 'config.json');
try {
// Parse review target
const reviewTarget = args.join(' ');
// Determine review type
let reviewType = 'branch';
let prNumber;
if (reviewTarget.match(/^\d+$/) || reviewTarget.match(/pull\/(\d+)/)) {
reviewType = 'pr';
prNumber = reviewTarget.match(/^\d+$/)?.[0] || reviewTarget.match(/pull\/(\d+)/)?.[1];
}
else if (reviewTarget && existsSync(join(baseDir, reviewTarget))) {
reviewType = 'document';
}
// Set up output directory
const outputDir = options.outputDir || join(baseDir, '.every-env/reviews', `review-${reviewType}-${Date.now()}`);
await mkdir(outputDir, { recursive: true });
// Set up worktree for PR reviews
let worktreePath = baseDir;
if (options.setupWorktree !== false && reviewType === 'pr') {
console.log(chalk.cyan('📁 Preparing isolated git worktree for review...'));
const newWorktreePath = await setupWorktreeForReview(baseDir, reviewTarget);
if (newWorktreePath) {
worktreePath = newWorktreePath;
}
}
// Create review context
const context = {
worktreePath,
prNumber,
reviewType,
reviewTarget,
outputDir,
};
// Check if multi-agent mode is requested
if (options.multiAgent) {
console.log(chalk.cyan(`\n🤖 Starting multi-agent review${reviewTarget ? ` of: "${reviewTarget}"` : ''}\n`));
// Load configuration
const configLoader = new ConfigLoader();
const config = await configLoader.loadConfig(configPath);
// Load review patterns
const patterns = await loadReviewPatterns(baseDir, options.patterns);
if (patterns.length === 0) {
throw new Error('No review patterns found. Please check your configuration.');
}
console.log(chalk.gray(`Found ${patterns.length} review patterns with ${patterns.reduce((sum, p) => sum + p.agents.length, 0)} agents total`));
// Execute review patterns
await executeReviewPatterns(config, patterns, context, options);
// Post-process results
await postProcessReviewResults(context);
}
else {
// Fall back to simple Claude pass-through mode
console.log(chalk.cyan(`\n🔍 Starting review${reviewTarget ? ` of: "${reviewTarget}"` : ' of latest PR'}\n`));
const { runReviewCommand } = await import('./review.js');
await runReviewCommand(args, options);
}
}
catch (error) {
logger.error('Enhanced review command failed:', error);
console.error(chalk.red(`\n✖ Error: ${error instanceof Error ? error.message : 'Unknown error'}`));
throw error;
}
}
/**
* Post-process review results and generate GitHub integration
*/
async function postProcessReviewResults(context) {
const finalReviewPath = join(context.outputDir, 'final-review.md');
if (existsSync(finalReviewPath) && context.reviewType === 'pr' && context.prNumber) {
console.log(chalk.cyan('\n📤 Posting review to GitHub...'));
try {
// Post review as PR comment
await execCapture('gh', [
'pr', 'comment', context.prNumber,
'--body-file', finalReviewPath
], { cwd: context.worktreePath });
console.log(chalk.green(`✓ Review posted to PR #${context.prNumber}`));
}
catch (error) {
logger.warn?.(`Failed to post review to GitHub: ${error instanceof Error ? error.message : String(error)}`);
console.log(chalk.yellow('⚠ Could not post review to GitHub. Review saved locally.'));
}
}
// Display summary
console.log(chalk.green('\n✨ Review complete!'));
console.log(chalk.gray(`Review artifacts saved to: ${context.outputDir}`));
// List key files
const keyFiles = ['final-review.md', 'executive-summary.md', 'action-plan.md'];
for (const file of keyFiles) {
const filePath = join(context.outputDir, file);
if (existsSync(filePath)) {
console.log(chalk.gray(` • ${file}`));
}
}
}
// Import necessary modules for pattern loading
import { dirname } from 'path';
import { fileURLToPath } from 'url';
//# sourceMappingURL=review-enhanced.js.map