From 63f8829578ff6595e4e68fb484ac01d21292b6c6 Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Sat, 29 Aug 2026 04:45:59 +0000 Subject: [PATCH] fix(test): e2e-harness-audit derives its skill census from disk The hand-maintained 39-name SKILL_GLOBS list had drifted to 39 of 54 SKILL.md.tmpl on disk. No live gap today (none of the 15 unlisted skills is interactive), but the next interactive skill would have landed unguarded with zero signal. The audit now walks top-level dirs for SKILL.md.tmpl (statSync so symlinked dirs like connect-chrome count), so new skills are in scope the commit they appear. Co-Authored-By: Claude Fable 5 --- test/e2e-harness-audit.test.ts | 68 +++++++++++++--------------------- 1 file changed, 26 insertions(+), 42 deletions(-) diff --git a/test/e2e-harness-audit.test.ts b/test/e2e-harness-audit.test.ts index bd3ecf46c..537845ab6 100644 --- a/test/e2e-harness-audit.test.ts +++ b/test/e2e-harness-audit.test.ts @@ -15,47 +15,31 @@ import * as fs from 'fs'; import * as path from 'path'; const ROOT = path.resolve(import.meta.dir, '..'); -const SKILL_GLOBS = [ - 'plan-ceo-review', - 'plan-eng-review', - 'plan-design-review', - 'plan-devex-review', - 'office-hours', - 'codex', - 'investigate', - 'qa', - 'retro', - 'cso', - 'review', - 'ship', - 'design-review', - 'devex-review', - 'qa-only', - 'design-consultation', - 'design-shotgun', - 'autoplan', - 'land-and-deploy', - 'plan-tune', - 'document-release', - 'context-save', - 'context-restore', - 'health', - 'setup-deploy', - 'setup-browser-cookies', - 'canary', - 'learn', - 'benchmark', - 'benchmark-models', - 'make-pdf', - 'open-gstack-browser', - 'gstack-upgrade', - 'pair-agent', - 'design-html', - 'freeze', - 'unfreeze', - 'careful', - 'guard', -]; + +/** + * Every top-level skill directory with a SKILL.md.tmpl, discovered from + * disk. This replaced a hand-maintained 39-name list that had drifted to + * 39-of-54 templates on disk — none of the unlisted 15 happened to be + * interactive, so there was no LIVE gap, but the next interactive skill + * would have landed unguarded with no signal. + */ +function skillTemplateDirs(): string[] { + return fs.readdirSync(ROOT, { withFileTypes: true }) + .filter((entry) => { + // Directory symlinks (connect-chrome → open-gstack-browser) count too: + // existsSync below follows them, and duplicates only re-check the same + // template. isDirectory() is false for symlinked dirs, hence statSync. + if (entry.name.startsWith('.') || entry.name === 'node_modules') return false; + try { + return fs.statSync(path.join(ROOT, entry.name)).isDirectory() + && fs.existsSync(path.join(ROOT, entry.name, 'SKILL.md.tmpl')); + } catch { + return false; + } + }) + .map((entry) => entry.name) + .sort(); +} /** * Load .tmpl files for each skill and return the names of those that have @@ -63,7 +47,7 @@ const SKILL_GLOBS = [ */ function findInteractiveSkills(): string[] { const interactive: string[] = []; - for (const skill of SKILL_GLOBS) { + for (const skill of skillTemplateDirs()) { const tmplPath = path.join(ROOT, skill, 'SKILL.md.tmpl'); if (!fs.existsSync(tmplPath)) continue; const content = fs.readFileSync(tmplPath, 'utf-8');