import { execSync } from "node:child_process"; import { Decision } from "../types/decisions.js"; import { Escalation } from "../types/escalation.js"; import { confidenceToLevel } from "../types/decisions.js"; export interface AuditEntry { phase: number; decisions: Decision[]; escalations: Escalation[]; } export function logDecision( projectPath: string, phase: number, decision: Decision ): void { console.warn( `[DEPRECATED] logDecision() is a no-op. Decisions are now committed to git via ---ci--- blocks. ` + `Read audit data with readAudit() or getAuditSummary() which derive from git log.` ); } export function logEscalation( projectPath: string, phase: number, escalation: Escalation ): void { console.warn( `[DEPRECATED] logEscalation() is a no-op. Escalations are now committed to git via ---ci--- blocks. ` + `Read audit data with readAudit() or getAuditSummary() which derive from git log.` ); } export function readAudit( projectPath: string, phase?: number ): AuditEntry[] { const entries = readAuditFromGit(projectPath); if (phase !== undefined) { return entries.filter((e) => e.phase === phase); } return entries; } export function getAuditSummary(projectPath: string): { total_decisions: number; total_escalations: number; phases: number[]; decisions_by_confidence: Record; escalations_by_type: Record; } { const entries = readAuditFromGit(projectPath); let total_decisions = 0; let total_escalations = 0; const phases = new Set(); const decisions_by_confidence: Record = { high: 0, medium: 0, low: 0, }; const escalations_by_type: Record = {}; for (const entry of entries) { phases.add(entry.phase); total_decisions += entry.decisions.length; total_escalations += entry.escalations.length; for (const d of entry.decisions) { const level = confidenceToLevel(d.confidence); decisions_by_confidence[level]++; } for (const e of entry.escalations) { escalations_by_type[e.type] = (escalations_by_type[e.type] || 0) + 1; } } return { total_decisions, total_escalations, phases: [...phases], decisions_by_confidence, escalations_by_type, }; } function readAuditFromGit(projectPath: string): AuditEntry[] { try { const raw = execSync( `git log --all --max-count=200 --format="%B%x01"`, { cwd: projectPath, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], timeout: 10000 } ); const phaseMap = new Map(); const entries = raw.split("\x01").filter(Boolean); for (const entry of entries) { const ciBlockMatch = entry.match(/---ci---[\s\S]*?---\/ci---/); if (!ciBlockMatch) continue; const phaseMatch = ciBlockMatch[0].match(/phase:\s*(\d+)/); if (!phaseMatch) continue; const phase = parseInt(phaseMatch[1]); if (!phaseMap.has(phase)) { phaseMap.set(phase, { phase, decisions: [], escalations: [] }); } const auditEntry = phaseMap.get(phase)!; const decisionsMatch = ciBlockMatch[0].match(/decisions:\s*\n([\s\S]*?)(?=\n[a-z]|---\/ci---)/); if (decisionsMatch) { const idMatches = [...decisionsMatch[1].matchAll(/id:\s*(D-\d+)/g)]; const decMatches = [...decisionsMatch[1].matchAll(/decision:\s*(.+)/g)]; const ratMatches = [...decisionsMatch[1].matchAll(/rationale:\s*(.+)/g)]; const confMatches = [...decisionsMatch[1].matchAll(/confidence:\s*([0-9.]+)/g)]; const catMatches = [...decisionsMatch[1].matchAll(/category:\s*(.+)/g)]; for (let i = 0; i < idMatches.length; i++) { auditEntry.decisions.push({ id: idMatches[i]?.[1] || "D-000", decision: decMatches[i]?.[1]?.trim() || "", rationale: ratMatches[i]?.[1]?.trim() || "", confidence: parseFloat(confMatches[i]?.[1] || "0.5"), category: (catMatches[i]?.[1]?.trim() as Decision["category"]) || "general", timestamp: new Date().toISOString(), alternatives_considered: [], human_override: null, }); } } const escMatch = ciBlockMatch[0].match(/escalations:\s*\n([\s\S]*?)(?=\n[a-z]|---\/ci---)/); if (escMatch) { const escEntries = escMatch[1].split(/-\s*/).filter(Boolean); for (const escLine of escEntries) { const typeMatch = escLine.match(/type:\s*(\S+)/); const descMatch = escLine.match(/description:\s*(.+)/); if (typeMatch) { auditEntry.escalations.push({ id: "E-000", timestamp: new Date().toISOString(), type: typeMatch[1] as Escalation["type"], phase: String(phase), description: descMatch?.[1]?.trim() || "", context: "", options: [], default_option_id: "", resolution: "pending", commit_hash: "", }); } } } } return [...phaseMap.values()]; } catch { return []; } }