fix(gen): preamble tiers are explicit; silent ?? 4 default becomes an error; spec stops rendering its preamble twice

Eight skills (scrape, diagram, spec, skillify, pair-agent, landing-report,
open-gstack-browser + its connect-chrome symlink) silently received the
HEAVIEST tier-4 preamble because a missing frontmatter field defaulted to 4.
Tiers are now declared in every {{PREAMBLE}} template's frontmatter and a
missing declaration throws at generation time with the template path (the 5
templates without {{PREAMBLE}} never invoke the resolver). The stale
hand-written tier-map comment (wrong in 3 of 4 rows) is gone.

Bonus bug fixed: spec/SKILL.md.tmpl mentioned {{PREAMBLE}} in prose, so the
generator inlined the ENTIRE preamble a second time — spec/SKILL.md shrinks
127,462 -> 80,924 bytes (-46,538) from de-duplication alone. skill-size-budget
gains a reasoned INTENTIONAL_SHRINKS entry (its frozen baseline had measured
the doubled-preamble bug). New tests: missing-tier throw carries the path;
every {{PREAMBLE}} template declares a tier. (Carries chunk-23 edits in the
shared test/gen-skill-docs.test.ts.)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-08-14 21:12:22 -07:00
co-authored by Claude Fable 5
parent 9124559810
commit 85388c66a5
18 changed files with 81 additions and 814 deletions
+5 -3
View File
@@ -53,11 +53,13 @@ function discoverTier2PlusSkillMds(): Array<{ skillName: string; mdPath: string
const mdPath = path.join(ROOT, e.name, 'SKILL.md');
const tmplPath = path.join(ROOT, e.name, 'SKILL.md.tmpl');
if (!fs.existsSync(mdPath) || !fs.existsSync(tmplPath)) continue;
// Check tier via frontmatter
// Check tier via frontmatter. Every template that resolves {{PREAMBLE}}
// must declare preamble-tier (the generator throws otherwise), so a
// missing declaration means the template has no preamble at all — scan it
// anyway (the vocabulary check is content-wide and cheap).
const tmpl = fs.readFileSync(tmplPath, 'utf-8');
const tierMatch = tmpl.match(/preamble-tier:\s*(\d+)/);
const tier = tierMatch ? parseInt(tierMatch[1], 10) : 4;
if (tier < 2) continue;
if (tierMatch && parseInt(tierMatch[1], 10) < 2) continue;
results.push({ skillName: e.name, mdPath });
}
return results;