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'), + ); + } + }); +});