import { test } from "node:test"; import assert from "node:assert/strict"; import { CONTEXT_DRAFT_MARKER, checkSubstep, closureGateBlocks, closureModelGuardApplies, flowGuardApplies, implementWriteGuardApplies, implementExemptDirs, nextGauntletEntered, phaseLabel, parseGitCommit, resolveRepoDir, findMarkerFile, markerBlockReason, transitionPhaseState, markerGuardApplies, recoverableEdge, type RecoveryPhaseMap, type RecoveryPhaseStatus, } from "./phase-tracker-helpers.ts"; const recoveryPhases = ( overrides: Partial> = {}, ): RecoveryPhaseMap => Object.fromEntries( (["brainstorm", "plan", "implement", "verify", "ship"] as const).map((phase) => [ phase, { status: overrides[phase] ?? "pending" }, ]), ) as RecoveryPhaseMap; test("recoverableEdge: recognizes only the two authorized edges", () => { assert.equal(recoverableEdge(recoveryPhases({ plan: "complete" })), "plan-implement"); assert.equal(recoverableEdge(recoveryPhases({ verify: "complete" })), "verify-ship"); }); test("recoverableEdge: rejects any active phase", () => { for (const phase of ["brainstorm", "plan", "implement", "verify", "ship"] as const) { const recoverable: Partial> = phase === "verify" || phase === "ship" ? { plan: "complete" } : { verify: "complete" }; assert.equal(recoverableEdge(recoveryPhases(recoverable)), phase === "verify" || phase === "ship" ? "plan-implement" : "verify-ship"); assert.equal(recoverableEdge(recoveryPhases({ ...recoverable, [phase]: "in_progress" })), undefined); } }); test("recoverableEdge: rejects invalid source and destination states", () => { for (const status of ["pending", "in_progress", "skipped"] as const) { assert.equal(recoverableEdge(recoveryPhases({ plan: status })), undefined); assert.equal(recoverableEdge(recoveryPhases({ verify: status })), undefined); } for (const status of ["in_progress", "complete", "skipped"] as const) { assert.equal(recoverableEdge(recoveryPhases({ plan: "complete", implement: status })), undefined); assert.equal(recoverableEdge(recoveryPhases({ verify: "complete", ship: status })), undefined); } }); test("recoverableEdge: rejects unrelated and ambiguous phase maps", () => { assert.equal(recoverableEdge(recoveryPhases()), undefined); assert.equal(recoverableEdge(recoveryPhases({ brainstorm: "complete", implement: "complete" })), undefined); assert.equal(recoverableEdge(recoveryPhases({ plan: "complete", verify: "complete" })), undefined); }); test("checkSubstep: in_progress -> ok", () => { assert.deepEqual(checkSubstep("in_progress"), { ok: true }); }); test("checkSubstep: non-in_progress statuses -> error naming actual status", () => { for (const s of ["pending", "complete", "skipped"]) { const r = checkSubstep(s); assert.equal(r.ok, false); if (!r.ok) assert.match(r.error, new RegExp(s)); } }); test("phaseLabel: with and without substep", () => { assert.equal(phaseLabel("brainstorm", "gather"), "brainstorm(gather)"); assert.equal(phaseLabel("brainstorm", undefined), "brainstorm"); }); test("parseGitCommit: plain, -am, chained after &&", () => { assert.ok(parseGitCommit('git commit -m "x"')); assert.ok(parseGitCommit("git commit -am 'x'")); assert.ok(parseGitCommit('git add -A && git commit -m "x"')); }); test("parseGitCommit: -C path and cd prefix are captured", () => { assert.deepEqual(parseGitCommit('git -C /wt commit -m "x"'), { cPath: "/wt", cdPath: undefined }); assert.deepEqual(parseGitCommit('cd /wt && git commit -m "x"'), { cPath: undefined, cdPath: "/wt" }); }); test("parseGitCommit: non-commit git and non-git -> undefined", () => { assert.equal(parseGitCommit("git log --oneline"), undefined); assert.equal(parseGitCommit("git commitish"), undefined); assert.equal(parseGitCommit('echo "git commit"'), undefined); // statement-start anchor: a quote is not a statement boundary assert.equal(parseGitCommit("npm test"), undefined); }); test("parseGitCommit: -c config flags between git and commit don't bypass the guard", () => { assert.deepEqual(parseGitCommit('git -c user.email=x commit -m "y"'), { cPath: undefined, cdPath: undefined }); assert.deepEqual(parseGitCommit("git -C /wt -c user.email=x commit"), { cPath: "/wt", cdPath: undefined }); }); test("parseGitCommit: commit-graph / commit-tree are not commit", () => { assert.equal(parseGitCommit("git commit-graph write"), undefined); assert.equal(parseGitCommit("git commit-tree HEAD^{tree}"), undefined); }); test("parseGitCommit: cd path in subshell (cd /a) captures /a without trailing paren", () => { assert.deepEqual(parseGitCommit("(cd /a) && git commit -m x"), { cPath: undefined, cdPath: "/a" }); }); test("parseGitCommit: last cd before the commit wins over an earlier one", () => { assert.deepEqual(parseGitCommit("cd /a && cd /b && git commit -m x"), { cPath: undefined, cdPath: "/b" }); }); test("parseGitCommit: cd recognized before non-&& statement separators", () => { assert.deepEqual(parseGitCommit("cd /wt; git commit -m x"), { cPath: undefined, cdPath: "/wt" }); assert.deepEqual(parseGitCommit("cd /wt\ngit commit -m x"), { cPath: undefined, cdPath: "/wt" }); assert.deepEqual(parseGitCommit("cd /wt || git commit -m x"), { cPath: undefined, cdPath: "/wt" }); }); test("resolveRepoDir: -C wins over cd, cd wins over session cwd, relative -C resolves against cd", () => { assert.equal(resolveRepoDir({ cPath: "/b", cdPath: "/a" }, "/s"), "/b"); assert.equal(resolveRepoDir({ cPath: undefined, cdPath: "/a" }, "/s"), "/a"); assert.equal(resolveRepoDir({ cPath: undefined, cdPath: undefined }, "/s"), "/s"); assert.equal(resolveRepoDir({ cPath: "wt", cdPath: "/a" }, "/s"), "/a/wt"); }); test("findMarkerFile: line-1 hit found, quoted-in-body miss, missing dir -> undefined", () => { const files: Record = { "/r/doc/specs/a.md": CONTEXT_DRAFT_MARKER + "\n\nbody", "/r/doc/specs/b.md": "# Real spec\n\n`" + CONTEXT_DRAFT_MARKER + "` quoted in body", }; const listFiles = (dir: string) => dir === "/r/doc/specs" ? Object.keys(files) : []; const readFirstLine = (f: string) => files[f]?.split("\n", 1)[0]; assert.equal(findMarkerFile("/r", ["doc/specs"], listFiles, readFirstLine), "/r/doc/specs/a.md"); delete files["/r/doc/specs/a.md"]; assert.equal( findMarkerFile("/r", ["doc/specs"], (d) => (d === "/r/doc/specs" ? Object.keys(files) : []), readFirstLine), undefined, ); assert.equal(findMarkerFile("/r", ["nope"], () => [], readFirstLine), undefined); }); test("markerBlockReason names the file and the enforce escape hatch", () => { const r = markerBlockReason("/r/doc/specs/a.md"); assert.match(r, /\/r\/doc\/specs\/a\.md/); assert.match(r, /flowGuards\.enforce/); }); test("transitionPhaseState: complete/skipped/pending drop any prior substep", () => { for (const status of ["complete", "skipped", "pending"]) { const r = transitionPhaseState(status); assert.equal(r.status, status); assert.ok(!("substep" in r)); } }); test("transitionPhaseState: reason propagates when provided", () => { assert.deepEqual(transitionPhaseState("skipped", "why"), { status: "skipped", reason: "why" }); }); test("markerGuardApplies: gated by flowGuards.enforce and brainstorm in_progress", () => { assert.equal(markerGuardApplies(false, "in_progress"), false); assert.equal(markerGuardApplies(true, "in_progress"), true); assert.equal(markerGuardApplies(true, "pending"), false); }); test("nextGauntletEntered: arms only when a start makes brainstorm in_progress", () => { assert.equal(nextGauntletEntered(false, "start", "in_progress"), true); assert.equal(nextGauntletEntered(true, "start", "in_progress"), true); // re-arm idempotent }); test("nextGauntletEntered: reset always disarms", () => { assert.equal(nextGauntletEntered(true, "reset", "pending"), false); assert.equal(nextGauntletEntered(true, "reset", "in_progress"), false); assert.equal(nextGauntletEntered(false, "reset", "pending"), false); }); test("nextGauntletEntered: marker survives downstream actions", () => { assert.equal(nextGauntletEntered(true, "start", "complete"), true); // start plan/implement (brainstorm already complete) assert.equal(nextGauntletEntered(true, "complete", "complete"), true); assert.equal(nextGauntletEntered(true, "substep", "in_progress"), true); }); test("nextGauntletEntered: a non-brainstorm start never arms a dormant flow", () => { assert.equal(nextGauntletEntered(false, "start", "complete"), false); // cold start verify/implement assert.equal(nextGauntletEntered(false, "start", "skipped"), false); assert.equal(nextGauntletEntered(false, "complete", "pending"), false); }); test("closureGateBlocks: blocks only for verify + entered + enforce + not-yet-dispatched", () => { assert.equal(closureGateBlocks("verify", true, true, false), true); assert.equal(closureGateBlocks("verify", false, true, false), false); // incident: not entered -> no block assert.equal(closureGateBlocks("verify", true, true, true), false); // already dispatched assert.equal(closureGateBlocks("verify", true, false, false), false); // enforce off assert.equal(closureGateBlocks("plan", true, true, false), false); // wrong phase }); test("flowGuardApplies: requires both an active guard phase and an entered flow", () => { assert.equal(flowGuardApplies(true, true), true); assert.equal(flowGuardApplies(true, false), false); // not entered assert.equal(flowGuardApplies(false, true), false); // phase not active assert.equal(flowGuardApplies(false, false), false); }); test("closureModelGuardApplies: requires both an entered flow and closure enforcement", () => { assert.equal(closureModelGuardApplies(true, true), true); assert.equal(closureModelGuardApplies(false, true), false); // not entered -> dormant assert.equal(closureModelGuardApplies(true, false), false); // enforce off assert.equal(closureModelGuardApplies(false, false), false); }); test("implementWriteGuardApplies: window is implement in_progress or plan-complete/implement-pending", () => { assert.equal(implementWriteGuardApplies("complete", "in_progress", true, false), true); assert.equal(implementWriteGuardApplies("complete", "pending", true, false), true); // armed post-plan gap (incident window) assert.equal(implementWriteGuardApplies("skipped", "in_progress", true, false), true); // window is implement itself assert.equal(implementWriteGuardApplies("in_progress", "pending", true, false), false); // still planning assert.equal(implementWriteGuardApplies("skipped", "pending", true, false), false); // plan skipped: accepted residual gap (spec Edge cases) assert.equal(implementWriteGuardApplies("complete", "complete", true, false), false); // implement done }); test("implementWriteGuardApplies: requires an armed flow and a parent process", () => { assert.equal(implementWriteGuardApplies("complete", "in_progress", false, false), false); // unarmed assert.equal(implementWriteGuardApplies("complete", "in_progress", true, true), false); // subagent child }); test("implementExemptDirs: adds each spec dir's sibling plans dir, deduped", () => { assert.deepEqual(implementExemptDirs(["doc/specs"]), ["doc/specs", "doc/plans"]); assert.deepEqual(implementExemptDirs(["specs"]), ["specs", "plans"]); assert.deepEqual(implementExemptDirs(["doc/specs/"]), ["doc/specs", "doc/plans"]); assert.deepEqual(implementExemptDirs(["a/plans"]), ["a/plans"]); // already a plans dir: Set dedups assert.deepEqual(implementExemptDirs(["doc/specs", "svc/doc/specs"]), ["doc/specs", "doc/plans", "svc/doc/specs", "svc/doc/plans"]); }); test("nextGauntletEntered: skip preserves the marker (resume gesture)", () => { assert.equal(nextGauntletEntered(true, "skip", "skipped"), true); });