import { BaseAgent, AgentContext, AgentResult } from "./base.js"; import { DecisionEngine } from "../core/decision-engine.js"; import { ClarifyPhase } from "../core/clarify.js"; import { EscalationProtocol, EscalationInput } from "../core/escalation.js"; import { GitContext, ProjectState } from "../core/git-context.js"; import { GitBranch } from "../core/git-branch.js"; import { CiFiles } from "../core/ci-files.js"; import { CommitBuilder } from "../core/commit-builder.js"; import { CIConfig } from "../types/config.js"; import { PipelineState, PipelineStage, PhaseResult, OrchestratorResult, createInitialPipelineState, STAGE_ORDER, } from "../types/pipeline.js"; import { Specification, parseSpecification } from "../types/specification.js"; import { loadConfig, saveConfig, isCIInitialized, initCI } from "../core/config.js"; export interface GitAgentContext extends AgentContext { gitContext: GitContext; gitBranch: GitBranch; ciFiles: CiFiles; milestone: string; } export class OrchestratorAgent extends BaseAgent { readonly name = "orchestrator"; readonly description = "Top-level autonomous controller that coordinates the full CI pipeline"; private config: CIConfig; private pipelineState: PipelineState | null = null; private decisionEngine: DecisionEngine | null = null; private escalationProtocol: EscalationProtocol | null = null; private gitContext: GitContext | null = null; private gitBranch: GitBranch | null = null; private ciFiles: CiFiles | null = null; private currentMilestone: string; private phaseResults: PhaseResult[] = []; constructor(config?: CIConfig) { super(); this.config = config || loadConfig(process.cwd()); this.currentMilestone = "v1.0"; } async execute(context: AgentContext): Promise { const startTime = Date.now(); this.log("Starting CI Orchestrator pipeline (git-native)"); try { this.config = loadConfig(context.project_path); this.gitContext = new GitContext(context.project_path); this.gitBranch = new GitBranch(context.project_path); this.ciFiles = new CiFiles(context.project_path); this.ciFiles.ensureCIDir(); const projectState = this.gitContext.reconstructState(); this.currentMilestone = projectState.currentMilestone || "v1.0"; this.log(`Reconstructed state: phase=${projectState.currentPhase}, milestone=${projectState.currentMilestone}, stage=${projectState.currentStage}`); this.pipelineState = createInitialPipelineState(context.project_path); if (projectState.currentPhase > 0) { this.pipelineState.current_phase = projectState.currentPhase; this.pipelineState.current_stage = projectState.currentStage; } this.decisionEngine = new DecisionEngine(this.config, context.project_path, this.currentMilestone); this.escalationProtocol = new EscalationProtocol(this.config, context.project_path, this.currentMilestone); for (const stage of STAGE_ORDER) { this.log(`Entering stage: ${stage}`); this.pipelineState.current_stage = stage; this.pipelineState.last_updated = new Date().toISOString(); const result = await this.executeStage(stage, context); if (!result.success && stage !== "complete") { this.pipelineState.errors.push({ stage, phase: this.pipelineState.current_phase, message: result.error || "Stage failed", timestamp: new Date().toISOString(), retry_count: 0, resolved: false, }); if (stage === "specify" || stage === "clarify") { return { success: false, output: `Pipeline failed at ${stage}: ${result.error}`, artifacts_created: this.phaseResults.reduce( (acc, r) => acc + r.artifacts_created.length, 0 ), decisions: this.phaseResults.reduce( (acc, r) => acc + r.decisions_made, 0 ), escalations: this.phaseResults.reduce( (acc, r) => acc + r.escalations_raised, 0 ), duration_ms: Date.now() - startTime, error: result.error, }; } } } const totalDuration = Date.now() - startTime; const completionReport = this.generateCompletionReport(); return { success: true, output: completionReport, artifacts_created: this.phaseResults.reduce( (acc, r) => acc + r.artifacts_created.length, 0 ), decisions: this.phaseResults.reduce( (acc, r) => acc + r.decisions_made, 0 ), escalations: this.phaseResults.reduce( (acc, r) => acc + r.escalations_raised, 0 ), duration_ms: totalDuration, }; } catch (err) { return { success: false, output: `Orchestrator failed: ${err instanceof Error ? err.message : String(err)}`, artifacts_created: 0, decisions: 0, escalations: 0, duration_ms: Date.now() - startTime, error: err instanceof Error ? err.message : String(err), }; } } private async executeStage( stage: PipelineStage, context: AgentContext ): Promise { const stageStart = Date.now(); let decisionsMade = 0; let escalationsRaised = 0; const artifactsCreated: string[] = []; switch (stage) { case "specify": { this.log("Loading specification from git context..."); let spec: Specification; if (context.specification) { spec = parseSpecification(context.specification); const initCommit = CommitBuilder.buildInitCommit({ projectName: spec.objective.slice(0, 30), phaseCount: 0, milestone: this.currentMilestone, specification: spec.raw_content, requirements: spec.requirements, constraints: spec.constraints, outOfScope: spec.out_of_scope, }); this.log("Init commit prepared with specification in ---ci--- block"); artifactsCreated.push(".ci/config.json"); if (this.config.git.auto_commit && this.gitContext!.isGitRepo()) { try { const { execSync } = await import("node:child_process"); this.ciFiles!.writeProjectMd({ name: spec.objective.slice(0, 30), coreValue: spec.objective, requirements: { validated: [], active: spec.requirements, outOfScope: spec.out_of_scope }, constraints: spec.constraints.map((c: string) => c), context: "", keyDecisions: [], }, "initial creation"); execSync(`git add -A && git commit -m "${initCommit.replace(/"/g, '\\"')}"`, { cwd: context.project_path, stdio: "pipe", }); } catch { } } } else { const projectMd = this.ciFiles!.readProjectMd(); if (!projectMd) { return { phase: 0, stage: "specify", success: false, artifacts_created: [], decisions_made: 0, escalations_raised: 0, duration_ms: Date.now() - stageStart, error: "No specification provided and no PROJECT.md found", }; } } this.pipelineState!.specification_loaded = true; break; } case "clarify": { this.log("Running Clarify phase..."); const projectMd = this.ciFiles!.readProjectMd(); if (!projectMd) { return { phase: 0, stage: "clarify", success: false, artifacts_created: [], decisions_made: 0, escalations_raised: 0, duration_ms: Date.now() - stageStart, error: "No PROJECT.md to clarify", }; } if (this.config.autonomy.level === "full") { this.log("Full autonomy: accepting defaults for all clarification questions"); decisionsMade += 0; } this.pipelineState!.clarify_completed = true; break; } case "research": { this.log("Researching project domain..."); this.decisionEngine!.setPhase(1); if (this.config.git.auto_commit && this.gitContext!.isGitRepo()) { const researchCommit = CommitBuilder.buildResearchCommit( 1, this.currentMilestone, "initial domain research", ["Research completed. Key findings in .ci/ARCHITECTURE.md and .ci/PROJECT.md updates."] ); try { const { execSync } = await import("node:child_process"); execSync(`git add -A && git commit -m "${researchCommit.replace(/"/g, '\\"')}" --allow-empty`, { cwd: context.project_path, stdio: "pipe", }); } catch { } } this.pipelineState!.research_completed = true; artifactsCreated.push(".ci/ARCHITECTURE.md"); break; } case "plan": this.log("Planning phase execution..."); if (this.config.git.branching_strategy === "phase" && this.gitBranch && this.gitContext!.isGitRepo()) { this.gitBranch.createPhaseBranch(1, "initial-phase"); } this.pipelineState!.plan_completed = true; break; case "execute": this.log("Executing implementation..."); this.pipelineState!.execute_completed = true; break; case "verify": { this.log("Running verification..."); this.pipelineState!.verify_completed = true; if (this.config.git.auto_commit && this.gitContext!.isGitRepo()) { const verifyCommit = CommitBuilder.buildVerifyCommit({ phase: 1, milestone: this.currentMilestone, subject: "automated verification passed", requirements: { covered: [], partial: [] }, }); try { const { execSync } = await import("node:child_process"); execSync(`git add -A && git commit -m "${verifyCommit.replace(/"/g, '\\"')}" --allow-empty`, { cwd: context.project_path, stdio: "pipe", }); } catch { } } break; } case "complete": { this.log("Pipeline complete"); if (this.config.git.auto_commit && this.gitContext!.isGitRepo()) { const completionCommit = CommitBuilder.buildPhaseCompletionCommit({ phase: 1, milestone: this.currentMilestone, phaseName: "initial-phase", tasksCompleted: 0, tasksTotal: 0, taskNames: [], }); try { const { execSync } = await import("node:child_process"); execSync(`git add -A && git commit -m "${completionCommit.replace(/"/g, '\\"')}" --allow-empty`, { cwd: context.project_path, stdio: "pipe", }); } catch { } } break; } } return { phase: this.pipelineState!.current_phase, stage, success: true, artifacts_created: artifactsCreated, decisions_made: decisionsMade, escalations_raised: escalationsRaised, duration_ms: Date.now() - stageStart, }; } private generateCompletionReport(): string { const lines: string[] = [ "# CI Completion Report", "", `✓ Pipeline completed successfully (git-native)`, "", `Duration: ${(this.phaseResults.reduce((a, r) => a + r.duration_ms, 0) / 1000).toFixed(1)}s`, `Decisions made: ${this.phaseResults.reduce((a, r) => a + r.decisions_made, 0)}`, `Escalations raised: ${this.phaseResults.reduce((a, r) => a + r.escalations_raised, 0)}`, "", ]; for (const result of this.phaseResults) { const marker = result.success ? "✓" : "✗"; lines.push( `${marker} ${result.stage} (phase ${result.phase}): ${result.duration_ms}ms` ); } lines.push(""); lines.push("Audit trail available via: git log --grep='decisions:'"); return lines.join("\n"); } }