feat: implement CI (Continuous Intelligence) autonomous engineering harness
Implements the full PRD for CI - a fully autonomous AI-driven software engineering harness derived from Learnship's architecture. Core components: - CI Orchestrator agent with autonomous pipeline (SPECIFY → CLARIFY → RESEARCH → PLAN → EXECUTE → VERIFY → COMPLETE) - Decision Engine with confidence thresholds (high/medium/low) - Clarify Phase with question budget and default acceptance - Escalation Protocol with timeout auto-proceed - Audit Trail system (.ci/audit/) for post-hoc review - Error Recovery with retry, plan revision, and rollback 18 agents (all Learnship agents + Orchestrator): - Autonomous behavioral modifications per PRD §7.1 - Agent registry with factory pattern 11 CLI commands: - ci init, ci run, ci quick, ci debug, ci verify - ci review, ci status, ci audit, ci clarify - ci rollback, ci ship 4-layer verification system: - Structural, Behavioral, Security, Code Quality 3 autonomy levels: full, supervised, guided Compatible with Learnship artifact schemas (.planning/)
This commit is contained in:
@@ -0,0 +1,162 @@
|
||||
import * as fs from "node:fs";
|
||||
import * as path from "node:path";
|
||||
import { writeFile, readFile, ensureDir } from "../utils/file.js";
|
||||
|
||||
const PLANNING_DIR = ".planning";
|
||||
|
||||
export interface ProjectManifest {
|
||||
name: string;
|
||||
objective: string;
|
||||
created_at: string;
|
||||
phases: PhaseInfo[];
|
||||
current_phase: number;
|
||||
status: "initializing" | "researching" | "planning" | "executing" | "verifying" | "complete" | "error";
|
||||
}
|
||||
|
||||
export interface PhaseInfo {
|
||||
id: number;
|
||||
name: string;
|
||||
status: "pending" | "active" | "complete" | "failed";
|
||||
started_at?: string;
|
||||
completed_at?: string;
|
||||
}
|
||||
|
||||
export interface DecisionsManifest {
|
||||
decisions: Array<{
|
||||
id: string;
|
||||
decision: string;
|
||||
rationale: string;
|
||||
confidence: number;
|
||||
category: string;
|
||||
timestamp: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
export interface StateManifest {
|
||||
current_phase: number;
|
||||
current_stage: string;
|
||||
last_agent: string;
|
||||
last_action: string;
|
||||
updated_at: string;
|
||||
pipeline_progress: Record<string, boolean>;
|
||||
}
|
||||
|
||||
export class ArtifactManager {
|
||||
private projectPath: string;
|
||||
|
||||
constructor(projectPath: string) {
|
||||
this.projectPath = projectPath;
|
||||
}
|
||||
|
||||
private get planningDir(): string {
|
||||
return path.join(this.projectPath, PLANNING_DIR);
|
||||
}
|
||||
|
||||
ensureStructure(): void {
|
||||
ensureDir(this.planningDir);
|
||||
ensureDir(path.join(this.planningDir, "phases"));
|
||||
ensureDir(path.join(this.projectPath, ".ci", "audit"));
|
||||
}
|
||||
|
||||
isInitialized(): boolean {
|
||||
return fs.existsSync(path.join(this.planningDir, "PROJECT.md"));
|
||||
}
|
||||
|
||||
writeProject(manifest: ProjectManifest): void {
|
||||
const lines = [
|
||||
`# ${manifest.name}`,
|
||||
"",
|
||||
`**Objective**: ${manifest.objective}`,
|
||||
`**Created**: ${manifest.created_at}`,
|
||||
`**Status**: ${manifest.status}`,
|
||||
`**Current Phase**: ${manifest.current_phase}`,
|
||||
"",
|
||||
"## Phases",
|
||||
"",
|
||||
];
|
||||
for (const phase of manifest.phases) {
|
||||
lines.push(
|
||||
`- Phase ${phase.id}: ${phase.name} [${phase.status}]`
|
||||
);
|
||||
}
|
||||
lines.push("");
|
||||
|
||||
writeFile(path.join(this.planningDir, "PROJECT.md"), lines.join("\n"));
|
||||
}
|
||||
|
||||
writeDecisions(decisions: DecisionsManifest): void {
|
||||
const lines = [
|
||||
"# Decisions Log",
|
||||
"",
|
||||
`Total decisions: ${decisions.decisions.length}`,
|
||||
"",
|
||||
];
|
||||
for (const d of decisions.decisions) {
|
||||
lines.push(`## ${d.id}: ${d.decision}`);
|
||||
lines.push(`- **Category**: ${d.category}`);
|
||||
lines.push(`- **Confidence**: ${(d.confidence * 100).toFixed(0)}%`);
|
||||
lines.push(`- **Rationale**: ${d.rationale}`);
|
||||
lines.push(`- **Timestamp**: ${d.timestamp}`);
|
||||
lines.push("");
|
||||
}
|
||||
writeFile(path.join(this.planningDir, "DECISIONS.md"), lines.join("\n"));
|
||||
}
|
||||
|
||||
writeState(state: StateManifest): void {
|
||||
writeJSON(path.join(this.planningDir, "STATE.md.json"), state);
|
||||
|
||||
const lines = [
|
||||
"# Project State",
|
||||
"",
|
||||
`**Current Phase**: ${state.current_phase}`,
|
||||
`**Current Stage**: ${state.current_stage}`,
|
||||
`**Last Agent**: ${state.last_agent}`,
|
||||
`**Last Action**: ${state.last_action}`,
|
||||
`**Updated**: ${state.updated_at}`,
|
||||
"",
|
||||
"## Pipeline Progress",
|
||||
"",
|
||||
];
|
||||
for (const [stage, complete] of Object.entries(
|
||||
state.pipeline_progress
|
||||
)) {
|
||||
lines.push(`- ${stage}: ${complete ? "✓" : "○"}`);
|
||||
}
|
||||
lines.push("");
|
||||
|
||||
writeFile(path.join(this.planningDir, "STATE.md"), lines.join("\n"));
|
||||
}
|
||||
|
||||
readState(): StateManifest | null {
|
||||
const filePath = path.join(this.planningDir, "STATE.md.json");
|
||||
if (!fs.existsSync(filePath)) return null;
|
||||
return JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
||||
}
|
||||
|
||||
writePhaseArtifact(
|
||||
phase: number,
|
||||
artifactName: string,
|
||||
content: string
|
||||
): void {
|
||||
const phaseDir = path.join(this.planningDir, "phases", `phase-${phase}`);
|
||||
ensureDir(phaseDir);
|
||||
writeFile(path.join(phaseDir, artifactName), content);
|
||||
}
|
||||
|
||||
readPhaseArtifact(
|
||||
phase: number,
|
||||
artifactName: string
|
||||
): string | null {
|
||||
const filePath = path.join(
|
||||
this.planningDir,
|
||||
"phases",
|
||||
`phase-${phase}`,
|
||||
artifactName
|
||||
);
|
||||
return readFile(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
function writeJSON(filePath: string, data: unknown): void {
|
||||
writeFile(filePath, JSON.stringify(data, null, 2));
|
||||
}
|
||||
Reference in New Issue
Block a user