import { execSync } from "node:child_process"; import { Decision, DecisionCategory, Alternative, confidenceToLevel } from "../types/decisions.js"; import { CIAgentConfig } from "../types/config.js"; import { CommitBuilder, DecisionCommitInput } from "./commit-builder.js"; import { CommitDecision } from "../types/commit-meta.js"; export interface DecisionInput { decision: string; rationale: string; confidence: number; category: DecisionCategory; alternatives_considered: Alternative[]; phase?: string; task?: string; } export interface DecisionResult { decision: Decision; escalated: boolean; reason?: string; commitMessage?: string; } export class DecisionEngine { private config: CIAgentConfig; private projectPath: string; private currentPhase: number; private currentMilestone: string; private decisionCounter: number; constructor(config: CIAgentConfig, projectPath: string, milestone: string = "v1.0") { this.config = config; this.projectPath = projectPath; this.currentPhase = 0; this.currentMilestone = milestone; this.decisionCounter = 0; } setPhase(phase: number): void { this.currentPhase = phase; } setMilestone(milestone: string): void { this.currentMilestone = milestone; } makeDecision(input: DecisionInput): DecisionResult { const id = `D-${String(++this.decisionCounter).padStart(3, "0")}`; const threshold = this.config.autonomy.decision_confidence_threshold; const decision: Decision = { id, timestamp: new Date().toISOString(), decision: input.decision, rationale: input.rationale, confidence: input.confidence, category: input.category, alternatives_considered: input.alternatives_considered, human_override: null, phase: input.phase, task: input.task, }; const commitDecision: CommitDecision = { id, decision: input.decision, rationale: input.rationale, confidence: input.confidence, alternatives: input.alternatives_considered.map((a) => a.option), }; const confidenceLevel = confidenceToLevel(input.confidence); const escalated = input.confidence < threshold; let commitMessage: string | undefined; if (this.config.git.auto_commit) { commitMessage = CommitBuilder.buildDecisionCommit({ phase: this.currentPhase, milestone: this.currentMilestone, subject: input.decision, decisions: [commitDecision], }); } if (escalated) { return { decision, escalated: true, reason: `Confidence ${input.confidence.toFixed(2)} below threshold ${threshold} (${confidenceLevel})`, commitMessage, }; } return { decision, escalated: false, commitMessage }; } makeHighConfidenceDecision( decision: string, rationale: string, category: DecisionCategory, alternatives: Alternative[] = [] ): DecisionResult { return this.makeDecision({ decision, rationale, confidence: 0.95, category, alternatives_considered: alternatives, }); } makeMediumConfidenceDecision( decision: string, rationale: string, category: DecisionCategory, alternatives: Alternative[] = [] ): DecisionResult { return this.makeDecision({ decision, rationale, confidence: 0.7, category, alternatives_considered: alternatives, }); } shouldAutoDecide(confidence: number): boolean { return confidence >= this.config.autonomy.decision_confidence_threshold; } isIrreversibleAction(action: string): boolean { return this.config.autonomy.escalation_hooks.some((hook) => action.toLowerCase().includes(hook.toLowerCase()) ); } commitDecision(commitMessage: string): boolean { if (!this.config.git.auto_commit) return false; try { execSync(`git add -A && git commit -m "${commitMessage.replace(/"/g, '\\"')}" --allow-empty`, { cwd: this.projectPath, stdio: "pipe", }); return true; } catch { return false; } } }