import * as fs from "node:fs"; import * as path from "node:path"; import * as os from "node:os"; import { DecisionEngine, DecisionInput } from "../core/decision-engine.js"; import { DEFAULT_CI_CONFIG } from "../types/config.js"; describe("DecisionEngine", () => { let tempDir: string; let engine: DecisionEngine; beforeEach(() => { tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "ci-decision-test-")); engine = new DecisionEngine(DEFAULT_CI_CONFIG, tempDir); }); afterEach(() => { fs.rmSync(tempDir, { recursive: true, force: true }); }); const baseInput: DecisionInput = { decision: "Use PostgreSQL for storage", rationale: "Strong ecosystem, ACID compliance needed", confidence: 0.95, category: "technology_choice", alternatives_considered: [ { option: "MongoDB", rejected_reason: "No ACID transactions" }, { option: "SQLite", rejected_reason: "No concurrent writes" }, ], }; describe("makeDecision", () => { it("auto-decides with high confidence (above threshold)", () => { const result = engine.makeDecision(baseInput); expect(result.escalated).toBe(false); expect(result.decision.id).toMatch(/^D-\d{3}$/); expect(result.decision.confidence).toBe(0.95); expect(result.decision.category).toBe("technology_choice"); }); it("escalates with low confidence (below threshold)", () => { const result = engine.makeDecision({ ...baseInput, confidence: 0.4, }); expect(result.escalated).toBe(true); expect(result.reason).toContain("below threshold"); }); it("auto-decides at exactly threshold confidence", () => { const result = engine.makeDecision({ ...baseInput, confidence: 0.6, }); expect(result.escalated).toBe(false); }); it("increments decision IDs sequentially", () => { const result1 = engine.makeDecision(baseInput); const result2 = engine.makeDecision(baseInput); expect(result1.decision.id).toBe("D-001"); expect(result2.decision.id).toBe("D-002"); }); it("generates commit message for git-native audit trail", () => { const result = engine.makeDecision(baseInput); expect(result.commitMessage).toBeDefined(); expect(result.commitMessage).toContain("---ci---"); expect(result.commitMessage).toContain("D-001"); expect(result.commitMessage).toContain("Use PostgreSQL for storage"); }); it("preserves alternatives in the decision", () => { const result = engine.makeDecision(baseInput); expect(result.decision.alternatives_considered).toHaveLength(2); expect(result.decision.alternatives_considered![0].option).toBe("MongoDB"); }); it("sets human_override to null by default", () => { const result = engine.makeDecision(baseInput); expect(result.decision.human_override).toBeNull(); }); }); describe("makeHighConfidenceDecision", () => { it("creates a decision with 0.95 confidence", () => { const result = engine.makeHighConfidenceDecision( "Use REST API", "REST is well-understood and has wide tooling support", "architecture" ); expect(result.escalated).toBe(false); expect(result.decision.confidence).toBe(0.95); }); }); describe("makeMediumConfidenceDecision", () => { it("creates a decision with 0.7 confidence", () => { const result = engine.makeMediumConfidenceDecision( "Use JWT for auth", "JWT is standard for stateless APIs", "implementation_approach" ); expect(result.escalated).toBe(false); expect(result.decision.confidence).toBe(0.7); }); it("escalates if threshold is raised above 0.7", () => { const strictConfig = { ...DEFAULT_CI_CONFIG, autonomy: { ...DEFAULT_CI_CONFIG.autonomy, decision_confidence_threshold: 0.8 }, }; const strictEngine = new DecisionEngine(strictConfig, tempDir); const result = strictEngine.makeMediumConfidenceDecision( "Use JWT for auth", "JWT is standard", "implementation_approach" ); expect(result.escalated).toBe(true); }); }); describe("shouldAutoDecide", () => { it("returns true when confidence meets threshold", () => { expect(engine.shouldAutoDecide(0.6)).toBe(true); expect(engine.shouldAutoDecide(0.8)).toBe(true); expect(engine.shouldAutoDecide(1.0)).toBe(true); }); it("returns false when confidence is below threshold", () => { expect(engine.shouldAutoDecide(0.59)).toBe(false); expect(engine.shouldAutoDecide(0.3)).toBe(false); expect(engine.shouldAutoDecide(0.0)).toBe(false); }); }); describe("isIrreversibleAction", () => { it("detects irreversible actions from escalation_hooks", () => { expect(engine.isIrreversibleAction("deploy to production")).toBe(true); expect(engine.isIrreversibleAction("delete_data in database")).toBe(true); expect(engine.isIrreversibleAction("merge_to_main branch")).toBe(true); }); it("returns false for non-irreversible actions", () => { expect(engine.isIrreversibleAction("create file")).toBe(false); expect(engine.isIrreversibleAction("run tests")).toBe(false); expect(engine.isIrreversibleAction("refactor code")).toBe(false); }); }); describe("setPhase", () => { it("updates the current phase", () => { engine.setPhase(3); expect(engine.setPhase).toBeDefined(); }); }); describe("setMilestone", () => { it("updates the current milestone", () => { engine.setMilestone("v2.0"); expect(engine.setMilestone).toBeDefined(); }); }); });