UNPKG

aiwg

Version:

Cognitive architecture for AI-augmented software development with structured memory, ensemble validation, and closed-loop correction. FAIR-aligned artifacts, 84% cost reduction via human-in-the-loop, standards adopted by 100+ organizations.

417 lines (366 loc) â€ĸ 12.5 kB
/** * Ralph Command Handlers * * CLI command handlers for Ralph iterative execution loop. * Uses external Ralph supervisor for crash-resilient background execution. * Falls back to internal scripts when --internal flag is used. * * @implements @.aiwg/architecture/decisions/ADR-001-unified-extension-system.md * @implements @.aiwg/working/issue-ralph-external-completion.md * @tests @test/unit/cli/handlers/ralph.test.ts * @issue #33, #275 */ import { CommandHandler, HandlerContext, HandlerResult } from './types.js'; import { createScriptRunner } from './script-runner.js'; import { launchExternalRalph, getLoopStatuses, abortLoop, resumeLoop, RalphLaunchOptions, } from './ralph-launcher.js'; /** * Parse Ralph command arguments */ function parseRalphArgs(args: string[]): { objective?: string; completionCriteria?: string; maxIterations?: number; model?: string; budget?: number; timeout?: number; mcpConfig?: string; giteaIssue?: boolean; memory?: number | string; crossTask?: boolean; enableAnalytics?: boolean; enableBestOutput?: boolean; enableEarlyStopping?: boolean; internal?: boolean; help?: boolean; loopId?: string; provider?: string; } { const result: ReturnType<typeof parseRalphArgs> = {}; let i = 0; while (i < args.length) { const arg = args[i]; if (arg === '--help' || arg === '-h') { result.help = true; } else if (arg === '--internal') { result.internal = true; } else if (arg === '--completion' || arg === '-c') { result.completionCriteria = args[++i]; } else if (arg === '--max-iterations') { result.maxIterations = parseInt(args[++i], 10); } else if (arg === '--model') { result.model = args[++i]; } else if (arg === '--budget') { result.budget = parseFloat(args[++i]); } else if (arg === '--timeout') { result.timeout = parseInt(args[++i], 10); } else if (arg === '--mcp-config') { result.mcpConfig = args[++i]; } else if (arg === '--gitea-issue') { result.giteaIssue = true; } else if (arg === '--memory' || arg === '-m') { const memArg = args[++i]; result.memory = isNaN(parseInt(memArg)) ? memArg : parseInt(memArg, 10); } else if (arg === '--cross-task') { result.crossTask = true; } else if (arg === '--no-cross-task') { result.crossTask = false; } else if (arg === '--no-analytics') { result.enableAnalytics = false; } else if (arg === '--no-best-output') { result.enableBestOutput = false; } else if (arg === '--no-early-stopping') { result.enableEarlyStopping = false; } else if (arg === '--loop-id') { result.loopId = args[++i]; } else if (arg === '--provider') { result.provider = args[++i]; } else if (!arg.startsWith('-') && !result.objective) { result.objective = arg; } i++; } return result; } /** * Ralph Loop Handler * * Executes iterative task loops with automatic completion detection. * By default, launches as a detached background process that survives terminal closure. * Use --internal flag for the legacy foreground execution. */ export class RalphHandler implements CommandHandler { id = 'ralph'; name = 'Ralph Loop'; description = 'Execute iterative task loop with automatic completion detection'; category = 'ralph' as const; aliases = ['ralph', '-ralph', '--ralph']; async execute(ctx: HandlerContext): Promise<HandlerResult> { const parsed = parseRalphArgs(ctx.args); // Show help if (parsed.help) { return { exitCode: 0, message: this.getHelpText(), }; } // Use internal (foreground) mode if requested if (parsed.internal) { const runner = createScriptRunner(ctx.frameworkRoot); return runner.run('tools/ralph/ralph-cli.mjs', ctx.args.filter((a) => a !== '--internal')); } // Validate required arguments if (!parsed.objective) { return { exitCode: 1, message: 'Error: Objective is required.\n\nUsage: aiwg ralph "<objective>" --completion "<criteria>"', }; } if (!parsed.completionCriteria) { return { exitCode: 1, message: 'Error: Completion criteria is required.\n\nUsage: aiwg ralph "<objective>" --completion "<criteria>"', }; } // Launch external Ralph as detached background process try { const options: RalphLaunchOptions = { objective: parsed.objective, completionCriteria: parsed.completionCriteria, maxIterations: parsed.maxIterations, model: parsed.model, budget: parsed.budget, timeout: parsed.timeout, mcpConfig: parsed.mcpConfig, giteaIssue: parsed.giteaIssue, memory: parsed.memory, crossTask: parsed.crossTask, enableAnalytics: parsed.enableAnalytics, enableBestOutput: parsed.enableBestOutput, enableEarlyStopping: parsed.enableEarlyStopping, loopId: parsed.loopId, provider: parsed.provider, }; const result = await launchExternalRalph(ctx.frameworkRoot, process.cwd(), options); return { exitCode: 0, message: `✓ ${result.message}\n PID: ${result.pid}\n Loop ID: ${result.loopId}`, }; } catch (error) { return { exitCode: 1, message: `Failed to launch Ralph: ${error instanceof Error ? error.message : String(error)}`, error: error instanceof Error ? error : new Error(String(error)), }; } } private getHelpText(): string { return ` Ralph Loop - Crash-resilient iterative task execution USAGE: aiwg ralph "<objective>" --completion "<criteria>" [options] aiwg ralph-status [--all] aiwg ralph-abort [--loop-id <id>] aiwg ralph-resume [--loop-id <id>] ARGUMENTS: <objective> Task objective (required) OPTIONS: -c, --completion <str> Completion criteria (required) --max-iterations <n> Maximum iterations (default: 5) --model <model> Claude model (default: opus) --budget <usd> Budget per iteration in USD (default: 2.0) --timeout <min> Timeout per iteration in minutes (default: 60) --mcp-config <json> MCP server configuration JSON --gitea-issue Create/link Gitea issue for tracking --loop-id <id> Use specific loop ID --provider <name> CLI provider: claude (default), codex RESEARCH-BACKED OPTIONS (REF-015, REF-021): -m, --memory <n> Memory capacity Ί (default: 3) --cross-task Enable cross-task learning (default: true) --no-cross-task Disable cross-task learning --no-analytics Disable iteration analytics --no-best-output Disable best output selection --no-early-stopping Disable early stopping on high confidence FLAGS: --internal Run in foreground (legacy mode) -h, --help Show this help message EXAMPLES: # Start background loop aiwg ralph "Fix all failing tests" --completion "npm test passes" # Check status aiwg ralph-status # Abort running loop aiwg ralph-abort `; } } /** * Ralph Status Handler * * Shows current Ralph loop status and iteration history. * Works across terminal sessions by reading from the loop registry. */ export class RalphStatusHandler implements CommandHandler { id = 'ralph-status'; name = 'Ralph Status'; description = 'Show Ralph loop status and iteration history'; category = 'ralph' as const; aliases = ['ralph-status']; async execute(ctx: HandlerContext): Promise<HandlerResult> { const showAll = ctx.args.includes('--all') || ctx.args.includes('-a'); const statuses = getLoopStatuses(process.cwd()); if (statuses.length === 0) { return { exitCode: 0, message: 'No Ralph loops found.', }; } // Sort by most recent first statuses.sort((a, b) => new Date(b.lastUpdate).getTime() - new Date(a.lastUpdate).getTime()); // Filter to running only unless --all const displayed = showAll ? statuses : statuses.filter((s) => s.status === 'running'); if (displayed.length === 0) { return { exitCode: 0, message: 'No running Ralph loops. Use --all to see completed/failed loops.', }; } const lines: string[] = ['Ralph Loop Status', '=================', '']; for (const loop of displayed) { const statusIcon = loop.status === 'running' ? '🔄' : loop.status === 'completed' ? '✅' : loop.status === 'aborted' ? 'âšī¸' : '❌'; lines.push(`${statusIcon} ${loop.loopId}`); lines.push(` Status: ${loop.status.toUpperCase()}`); lines.push(` Progress: ${loop.iteration}/${loop.maxIterations} iterations`); lines.push(` Objective: ${loop.objective.slice(0, 60)}${loop.objective.length > 60 ? '...' : ''}`); lines.push(` Started: ${loop.startedAt}`); if (loop.status === 'running') { lines.push(` PID: ${loop.pid}`); } lines.push(''); } if (!showAll && statuses.length > displayed.length) { lines.push(`(${statuses.length - displayed.length} completed/failed loops hidden. Use --all to show.)`); } return { exitCode: 0, message: lines.join('\n'), }; } } /** * Ralph Abort Handler * * Aborts currently running Ralph loop by killing the background process. * Works across terminal sessions. */ export class RalphAbortHandler implements CommandHandler { id = 'ralph-abort'; name = 'Ralph Abort'; description = 'Abort currently running Ralph loop'; category = 'ralph' as const; aliases = ['ralph-abort']; async execute(ctx: HandlerContext): Promise<HandlerResult> { // Parse --loop-id if provided let loopId: string | undefined; const loopIdIndex = ctx.args.indexOf('--loop-id'); if (loopIdIndex !== -1 && ctx.args[loopIdIndex + 1]) { loopId = ctx.args[loopIdIndex + 1]; } const result = abortLoop(process.cwd(), loopId); return { exitCode: result.success ? 0 : 1, message: result.message, }; } } /** * Ralph Resume Handler * * Resumes previously interrupted Ralph loop as a new background process. * Works across terminal sessions. */ export class RalphResumeHandler implements CommandHandler { id = 'ralph-resume'; name = 'Ralph Resume'; description = 'Resume previously aborted Ralph loop'; category = 'ralph' as const; aliases = ['ralph-resume']; async execute(ctx: HandlerContext): Promise<HandlerResult> { // Parse arguments let loopId: string | undefined; let maxIterations: number | undefined; for (let i = 0; i < ctx.args.length; i++) { const arg = ctx.args[i]; if (arg === '--loop-id' && ctx.args[i + 1]) { loopId = ctx.args[++i]; } else if (arg === '--max-iterations' && ctx.args[i + 1]) { maxIterations = parseInt(ctx.args[++i], 10); } } try { const result = await resumeLoop(ctx.frameworkRoot, process.cwd(), loopId, { maxIterations }); return { exitCode: 0, message: `✓ ${result.message}\n PID: ${result.pid}\n Loop ID: ${result.loopId}`, }; } catch (error) { return { exitCode: 1, message: `Failed to resume: ${error instanceof Error ? error.message : String(error)}`, error: error instanceof Error ? error : new Error(String(error)), }; } } } /** * Create Ralph handler instance */ export function createRalphHandler(): CommandHandler { return new RalphHandler(); } /** * Create Ralph Status handler instance */ export function createRalphStatusHandler(): CommandHandler { return new RalphStatusHandler(); } /** * Create Ralph Abort handler instance */ export function createRalphAbortHandler(): CommandHandler { return new RalphAbortHandler(); } /** * Create Ralph Resume handler instance */ export function createRalphResumeHandler(): CommandHandler { return new RalphResumeHandler(); } /** * Export handler instances */ export const ralphHandler = new RalphHandler(); export const ralphStatusHandler = new RalphStatusHandler(); export const ralphAbortHandler = new RalphAbortHandler(); export const ralphResumeHandler = new RalphResumeHandler(); /** * Export all Ralph handlers as array for easy registration */ export const ralphHandlers: CommandHandler[] = [ ralphHandler, ralphStatusHandler, ralphAbortHandler, ralphResumeHandler, ];