Files
ci/src/core/escalation.ts
T
CI 9cf5c000d9 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/)
2026-05-28 23:24:42 +00:00

148 lines
4.4 KiB
TypeScript

import * as fs from "node:fs";
import * as path from "node:path";
import {
Escalation,
EscalationType,
EscalationOption,
EscalationResolution,
ESCALATION_TYPES,
} from "../types/escalation.js";
import { CIConfig } from "../types/config.js";
import { logEscalation } from "./audit.js";
export interface EscalationInput {
type: EscalationType;
phase: string;
description: string;
context: string;
options: EscalationOption[];
default_option_id: string;
plan?: string;
task?: string;
}
export class EscalationProtocol {
private config: CIConfig;
private projectPath: string;
private counter: number;
private pendingEscalations: Map<string, Escalation>;
private timeoutCallback: (escalation: Escalation, chosenOption: string) => void;
constructor(
config: CIConfig,
projectPath: string,
timeoutCallback: (escalation: Escalation, chosenOption: string) => void = () => {}
) {
this.config = config;
this.projectPath = projectPath;
this.counter = 0;
this.pendingEscalations = new Map();
this.timeoutCallback = timeoutCallback;
}
escalate(input: EscalationInput): Escalation {
const id = `E-${String(++this.counter).padStart(3, "0")}`;
const date = new Date().toISOString().split("T")[0];
const escalation: Escalation = {
id,
timestamp: new Date().toISOString(),
type: input.type,
phase: input.phase,
plan: input.plan,
task: input.task,
description: input.description,
context: input.context,
options: input.options,
default_option_id: input.default_option_id,
resolution: "pending",
audit_file: `.ci/audit/${date}-phase${input.phase}-decisions.json`,
};
this.pendingEscalations.set(id, escalation);
logEscalation(this.projectPath, parseInt(input.phase) || 0, escalation);
if (this.config.autonomy.escalation_timeout_ms > 0) {
this.scheduleTimeout(escalation);
}
return escalation;
}
resolveEscalation(
escalationId: string,
chosenOptionId: string,
resolution: EscalationResolution = "approved"
): Escalation | null {
const escalation = this.pendingEscalations.get(escalationId);
if (!escalation) return null;
escalation.resolution = resolution;
escalation.resolved_at = new Date().toISOString();
escalation.resolution_detail = `Chose option: ${chosenOptionId}`;
this.pendingEscalations.delete(escalationId);
return escalation;
}
getPendingEscalations(): Escalation[] {
return [...this.pendingEscalations.values()];
}
hasPending(): boolean {
return this.pendingEscalations.size > 0;
}
formatEscalation(escalation: Escalation): string {
const lines: string[] = [
`⚠️ ESCALATION [${escalation.id}]`,
"",
`Type: ${ESCALATION_TYPES[escalation.type]}`,
`Phase: ${escalation.phase}${escalation.plan ? `, Plan: ${escalation.plan}` : ""}${escalation.task ? `, Task: ${escalation.task}` : ""}`,
`Decision Required: ${escalation.description}`,
"",
`Context: ${escalation.context}`,
"",
"Options:",
];
for (const opt of escalation.options) {
const marker = opt.recommended ? " (recommended)" : "";
lines.push(` ${opt.id}) ${opt.label}${marker} - ${opt.description}`);
}
const defaultOpt = escalation.options.find(
(o) => o.id === escalation.default_option_id
);
lines.push("");
lines.push(
`Default: ${defaultOpt?.label || escalation.default_option_id}`
);
if (this.config.autonomy.escalation_timeout_ms > 0) {
const seconds = Math.floor(this.config.autonomy.escalation_timeout_ms / 1000);
lines.push(
`(auto-proceed in ${seconds}s if no response)`
);
}
lines.push(`\nAudit: ${escalation.audit_file}`);
return lines.join("\n");
}
private scheduleTimeout(escalation: Escalation): void {
const timeout = this.config.autonomy.escalation_timeout_ms;
if (timeout <= 0) return;
setTimeout(() => {
if (this.pendingEscalations.has(escalation.id)) {
escalation.resolution = "timeout_auto_proceed";
escalation.resolved_at = new Date().toISOString();
escalation.resolution_detail = `Auto-proceeded with default: ${escalation.default_option_id}`;
this.pendingEscalations.delete(escalation.id);
this.timeoutCallback(escalation, escalation.default_option_id);
}
}, timeout);
}
}