v0.2.0: Git-native architecture (#1)
This commit was merged in pull request #1.
This commit is contained in:
@@ -0,0 +1,164 @@
|
||||
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" },
|
||||
],
|
||||
learnship_equivalent: "discuss-phase would ask: What database? Options: A) PostgreSQL B) MongoDB",
|
||||
};
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user