UNPKG

@every-env/cli

Version:

Multi-agent orchestrator for AI-powered development workflows

163 lines 7.52 kB
import chalk from 'chalk'; import { join } from 'path'; import { LiquidEngine } from '../templates/liquid-engine.js'; import { execCommand, execCapture } from '../utils/exec.js'; import { logger } from '../utils/logger.js'; import { updateLastUsedAgent } from '../utils/command-state.js'; import { existsSync, statSync } from 'fs'; import { mkdir } from 'fs/promises'; export async function parseReviewTarget(args) { const reviewTarget = args.join(' '); // If no target provided, the workflow will review the latest PR return reviewTarget; } export async function renderReviewTemplate(baseDir, reviewTarget) { const liquidEngine = new LiquidEngine(baseDir); const reviewTemplatePath = '.claude/commands/workflows/review.md'; const renderedPrompt = await liquidEngine.renderFile(reviewTemplatePath, { ARGUMENTS: reviewTarget }); return renderedPrompt; } export async function executeReviewCommand(cli, renderedPrompt, args) { // Build command: cli "prompt" args... const commandArgs = [renderedPrompt, ...args]; // Execute command - this will take over the terminal await execCommand(cli, commandArgs); } async function maybeSetupWorktree(baseDir, reviewTarget) { try { if (!reviewTarget) { // Only operate when explicit PR target is provided to avoid surprises 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); // YYYYMMDDHHMMSS 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) { // If branch exists locally already, continue logger.debug?.(`git fetch for PR ${prIdentifier} may have failed or already exists: ${e instanceof Error ? e.message : String(e)}`); } // Create the worktree pointing at pr-<id> try { await execCapture('git', ['worktree', 'add', worktreePath, `pr-${prIdentifier}`], { cwd: gitRoot }); } catch (e) { // If worktree already exists, we still proceed if directory is present if (!existsSync(worktreePath) || !statSync(worktreePath).isDirectory()) { throw e; } } console.log(chalk.green(`✓ Worktree ready: ${worktreePath}`)); // Install dependencies if applicable 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; } } export async function runReviewCommand(args, options = {}) { const baseDir = options.baseDir || process.cwd(); const configPath = options.configPath || join(baseDir, '.every-env', 'config.json'); try { // Step 1: Parse review target const reviewTarget = await parseReviewTarget(args); // Optional: prepare isolated worktree for PR review when requested if (options.setupWorktree) { console.log(chalk.cyan('📁 Preparing isolated git worktree for review...')); await maybeSetupWorktree(baseDir, reviewTarget); } // Step 2: Load runtime config to get agent configurations const { readRuntimeConfigIfExists } = await import('../utils/runtime-config.js'); const runtimeConfig = await readRuntimeConfigIfExists(configPath); let cli = 'claude'; let configArgs = []; if (options.agentcli) { // Use the specified agent from config const agentConfig = runtimeConfig?.agents?.[options.agentcli]; if (agentConfig && typeof agentConfig.cli === 'string') { cli = agentConfig.cli; configArgs = Array.isArray(agentConfig.args) ? agentConfig.args : []; } else { // Fallback to using the agentcli as the command itself cli = options.agentcli; } // Save the last used agent updateLastUsedAgent('review', options.agentcli); } else { // Use default agent const defaultAgent = runtimeConfig?.defaultAgent || 'claude'; const defaultConfig = runtimeConfig?.agents?.[defaultAgent]; if (defaultConfig && typeof defaultConfig.cli === 'string') { cli = defaultConfig.cli; configArgs = Array.isArray(defaultConfig.args) ? defaultConfig.args : []; } } // Step 3: Render review template const renderedPrompt = await renderReviewTemplate(baseDir, reviewTarget); console.log(chalk.cyan(`\n🔍 Starting review${reviewTarget ? ` of: "${reviewTarget}"` : ' of latest PR'}\n`)); // Step 4: Execute command with pass-through args const allArgs = [...configArgs, ...(options.passThroughArgs || [])]; await executeReviewCommand(cli, renderedPrompt, allArgs); } catch (error) { logger.error('Review command failed:', error); console.error(chalk.red(`\n✖ Error: ${error instanceof Error ? error.message : 'Unknown error'}`)); throw error; } } //# sourceMappingURL=review.js.map