From a646a32b41ba311d46a879d90b0a24efc95d5516 Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Fri, 14 Aug 2026 15:58:47 -0700 Subject: [PATCH] feat(test): validate touchfile dependency paths exist on disk MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New guard in touchfiles.test.ts: every non-glob dep path must exist, and every glob's anchor directory must exist. This is the axis the 181-key two-map sync discipline never covered — an entry can point at a long-deleted file and diff-based selection then silently never triggers those tests (the sidebar trio sat rotted for 48 versions). First run immediately caught a fourth rotted entry: 'spec authored quality' referenced test/fixtures/spec/** (directory does not exist) and selected for a judge test that exists nowhere in the repo. Removed. Co-Authored-By: Claude Fable 5 --- test/helpers/touchfiles.ts | 1 - test/touchfiles.test.ts | 54 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/test/helpers/touchfiles.ts b/test/helpers/touchfiles.ts index 562bbf5f3..889c6e414 100644 --- a/test/helpers/touchfiles.ts +++ b/test/helpers/touchfiles.ts @@ -775,7 +775,6 @@ export const LLM_JUDGE_TOUCHFILES: Record = { 'plan-eng-review/SKILL.md sections': ['plan-eng-review/SKILL.md', 'plan-eng-review/SKILL.md.tmpl'], // /spec authored-spec quality (paid LLM-judge — periodic-tier). - 'spec authored quality': ['spec/SKILL.md', 'spec/SKILL.md.tmpl', 'test/fixtures/spec/**'], 'plan-design-review/SKILL.md passes': ['plan-design-review/SKILL.md', 'plan-design-review/SKILL.md.tmpl'], // Design skills diff --git a/test/touchfiles.test.ts b/test/touchfiles.test.ts index f7ced6971..12f6c7453 100644 --- a/test/touchfiles.test.ts +++ b/test/touchfiles.test.ts @@ -330,3 +330,57 @@ describe('TOUCHFILES completeness', () => { } }); }); + +// --- dependency paths exist on disk --- +// +// The axis nobody guarded: a dep-list entry can point at a file that was +// deleted long ago (browse/src/sidebar-agent.ts sat in three entries for 48 +// versions), and diff-based selection then silently never triggers those +// tests. Globs are skipped (they describe patterns, not files); every literal +// path must exist. + +describe('touchfile dependency paths exist', () => { + const allEntries: Array<[string, string]> = []; + for (const [name, deps] of Object.entries(E2E_TOUCHFILES)) { + for (const dep of deps) allEntries.push([name, dep]); + } + for (const [name, deps] of Object.entries(LLM_JUDGE_TOUCHFILES)) { + for (const dep of deps) allEntries.push([name, dep]); + } + for (const dep of GLOBAL_TOUCHFILES) allEntries.push(['(global)', dep]); + + test('every non-glob dependency path exists', () => { + const stale = allEntries + .filter(([, dep]) => !dep.includes('*')) + .filter(([, dep]) => !fs.existsSync(path.join(ROOT, dep))); + if (stale.length > 0) { + throw new Error( + `Touchfile dep lists reference files that do not exist:\n` + + stale.map(([name, dep]) => ` ${name} -> ${dep}`).join('\n') + + `\nDelete or update these entries in test/helpers/touchfiles.ts — ` + + `diff-based selection silently skips tests whose deps are gone.`, + ); + } + }); + + test('every glob dependency anchors to a directory that exists', () => { + // Cheap sanity for globs, two shapes: 'dir/**' (prefix ends with '/') + // must have the directory itself; 'dir/file-prefix*.ext' must have the + // containing directory. Catches 'deleted-dir/**' rot without a full + // filesystem walk; deliberately does not chase file-prefix staleness. + const stale = allEntries + .filter(([, dep]) => dep.includes('*')) + .map(([name, dep]) => { + const prefix = dep.split('*')[0]; + const anchor = prefix.endsWith('/') ? prefix.slice(0, -1) : path.dirname(prefix); + return [name, dep, anchor] as const; + }) + .filter(([, , anchor]) => anchor.length > 0 && anchor !== '.' && !fs.existsSync(path.join(ROOT, anchor))); + if (stale.length > 0) { + throw new Error( + `Touchfile glob deps whose anchor directory does not exist:\n` + + stale.map(([name, dep]) => ` ${name} -> ${dep}`).join('\n'), + ); + } + }); +});