@every-env/cli
Version:
Multi-agent orchestrator for AI-powered development workflows
163 lines • 7.52 kB
JavaScript
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