From d7faf8a4e5f37375728a7e27ddcbaf97f07a180e Mon Sep 17 00:00:00 2001 From: Lex Christopherson Date: Sat, 14 Mar 2026 14:14:40 -0600 Subject: [PATCH] fix(tests): invalidate path cache between deriveState calls that expect fresh disk state Tests that write files and immediately call deriveState() got stale results because the path resolution cache (dirEntryCache/dirListCache) returned cached directory listings that didn't include newly written files. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../extensions/gsd/tests/complete-milestone.test.ts | 2 ++ src/resources/extensions/gsd/tests/draft-promotion.test.ts | 4 +++- .../gsd/tests/integration-mixed-milestones.test.ts | 3 +++ src/resources/extensions/gsd/tests/unit-runtime.test.ts | 6 ++++++ 4 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/resources/extensions/gsd/tests/complete-milestone.test.ts b/src/resources/extensions/gsd/tests/complete-milestone.test.ts index cdb286792..0055cb862 100644 --- a/src/resources/extensions/gsd/tests/complete-milestone.test.ts +++ b/src/resources/extensions/gsd/tests/complete-milestone.test.ts @@ -3,6 +3,7 @@ import { join, dirname } from "node:path"; import { tmpdir } from "node:os"; import { fileURLToPath } from "node:url"; import { createTestContext } from './test-helpers.ts'; +import { clearPathCache } from '../paths.ts'; // loadPrompt reads from ~/.gsd/agent/extensions/gsd/prompts/ (main checkout). // In a worktree the file may not exist there yet, so we resolve prompts @@ -179,6 +180,7 @@ async function main(): Promise { // Now add the summary and verify it transitions to complete writeMilestoneSummary(base, "M001", "# M001 Summary\n\nDone."); + clearPathCache(); const stateAfter = await deriveState(base); assertEq(stateAfter.phase, "complete", "deriveState returns complete after summary exists"); assertEq(stateAfter.registry[0]?.status, "complete", "registry shows complete status"); diff --git a/src/resources/extensions/gsd/tests/draft-promotion.test.ts b/src/resources/extensions/gsd/tests/draft-promotion.test.ts index 0fb7160cd..1037d9809 100644 --- a/src/resources/extensions/gsd/tests/draft-promotion.test.ts +++ b/src/resources/extensions/gsd/tests/draft-promotion.test.ts @@ -3,7 +3,7 @@ import { join } from "node:path"; import { tmpdir } from "node:os"; import { deriveState } from "../state.js"; -import { resolveMilestoneFile } from "../paths.js"; +import { resolveMilestoneFile, clearPathCache } from "../paths.js"; let passed = 0; let failed = 0; @@ -40,6 +40,7 @@ assert( const contextPath = join(gsd, "milestones", "M001", "M001-CONTEXT.md"); writeFileSync(contextPath, "# M001: Full Context\n\nDeep discussion output.\n"); +clearPathCache(); const state2 = await deriveState(tmpBase); assert( state2.phase === "pre-planning", @@ -65,6 +66,7 @@ assert( ); // Step 4: After cleanup, state is still pre-planning (CONTEXT.md exists) +clearPathCache(); const state3 = await deriveState(tmpBase); assert( state3.phase === "pre-planning", diff --git a/src/resources/extensions/gsd/tests/integration-mixed-milestones.test.ts b/src/resources/extensions/gsd/tests/integration-mixed-milestones.test.ts index d5b761075..7e16581eb 100644 --- a/src/resources/extensions/gsd/tests/integration-mixed-milestones.test.ts +++ b/src/resources/extensions/gsd/tests/integration-mixed-milestones.test.ts @@ -23,6 +23,7 @@ import { parseSliceBranch, switchToMain, } from '../worktree.ts'; +import { clearPathCache } from '../paths.ts'; import { createTestContext } from './test-helpers.ts'; // ─── Assertion Helpers ──────────────────────────────────────────────────── @@ -437,6 +438,7 @@ Built the legacy feature successfully. `); run('git add .', base); run('git commit -m add-m001', base); + clearPathCache(); // M001 (seq=1) < M001-abc123 (seq=1) — but M001 has incomplete S02 // Since M001 seq=1 and M002-abc123 seq=2, blocker should reference M001/S02 @@ -459,6 +461,7 @@ Built the legacy feature successfully. `); run('git add .', base); run('git commit -m complete-m001', base); + clearPathCache(); assertEq( getPriorSliceCompletionBlocker(base, 'main', 'plan-slice', 'M002-abc123/S01'), diff --git a/src/resources/extensions/gsd/tests/unit-runtime.test.ts b/src/resources/extensions/gsd/tests/unit-runtime.test.ts index b96439ede..64c7ee49a 100644 --- a/src/resources/extensions/gsd/tests/unit-runtime.test.ts +++ b/src/resources/extensions/gsd/tests/unit-runtime.test.ts @@ -8,6 +8,7 @@ import { readUnitRuntimeRecord, writeUnitRuntimeRecord, } from "../unit-runtime.ts"; +import { clearPathCache } from '../paths.ts'; import { createTestContext } from './test-helpers.ts'; const { assertEq, assertTrue, report } = createTestContext(); @@ -48,6 +49,7 @@ console.log("\n=== execute-task durability inspection ==="); "utf-8", ); writeFileSync(join(base, ".gsd", "STATE.md"), "## Next Action\nExecute T10 for S02: next thing\n", "utf-8"); + clearPathCache(); status = await inspectExecuteTaskDurability(base, "M100/S02/T09"); assertEq(status!.summaryExists, true, "summary found after write"); @@ -128,6 +130,7 @@ console.log("\n=== must-haves: partially mentioned in summary ==="); ); writeFileSync(join(mhBase, ".gsd", "STATE.md"), "## Next Action\nExecute T02 for S02: next thing\n", "utf-8"); + clearPathCache(); const status = await inspectExecuteTaskDurability(mhBase, "M200/S02/T01"); assertTrue(status !== null, "mh-partial: status exists"); assertEq(status!.mustHaveCount, 3, "mh-partial: mustHaveCount is 3"); @@ -155,6 +158,7 @@ console.log("\n=== must-haves: no task plan file ==="); ); writeFileSync(join(mhBase, ".gsd", "STATE.md"), "## Next Action\nExecute T02 for S03: next thing\n", "utf-8"); + clearPathCache(); const status = await inspectExecuteTaskDurability(mhBase, "M200/S03/T01"); assertTrue(status !== null, "mh-noplan: status exists"); assertEq(status!.mustHaveCount, 0, "mh-noplan: mustHaveCount is 0 when no task plan"); @@ -179,6 +183,7 @@ console.log("\n=== must-haves: present but no summary file ==="); ); writeFileSync(join(mhBase, ".gsd", "STATE.md"), "## Next Action\nExecute T01 for S04: build parser\n", "utf-8"); + clearPathCache(); const status = await inspectExecuteTaskDurability(mhBase, "M200/S04/T01"); assertTrue(status !== null, "mh-nosummary: status exists"); assertEq(status!.mustHaveCount, 2, "mh-nosummary: mustHaveCount is 2"); @@ -210,6 +215,7 @@ console.log("\n=== must-haves: substring matching (no backtick tokens) ==="); ); writeFileSync(join(mhBase, ".gsd", "STATE.md"), "## Next Action\nExecute T02 for S05: next thing\n", "utf-8"); + clearPathCache(); const status = await inspectExecuteTaskDurability(mhBase, "M200/S05/T01"); assertTrue(status !== null, "mh-substr: status exists"); assertEq(status!.mustHaveCount, 3, "mh-substr: mustHaveCount is 3");