Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions scripts/pm/check-skill-line-ratchet.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -25,6 +25,39 @@
* see check-skill-id-lint.mjs). Without a gate that intent erodes one
* well-meaning paragraph at a time.
*
* ## What is covered — and the published catalog, which deliberately is not
*
* "The whole surface" above means the CEILINGS map below, which is an
* ENUMERATION, never a root glob: the pm-dispatch surface (SKILL.md, its
* references/, the per-lane job descriptions), the four other
* `.claude/skills/` playbook SKILL.md files, the dev-agent definition
* `.claude/agents/os-dev.md`, and the root `AGENTS.md`. Read a file's absence
* from the map as a fact to check, not an oversight to infer — `.claude/hooks/`,
* `.claude/settings.json` and `CLAUDE.md` carry no ceiling either.
*
* The **published** `skills/` catalog — the one that ships to customer projects
* — is deliberately OUTSIDE the ceiling. It is the omission worth stating
* because it is by far the larger surface: eleven published SKILL.md totalling
* ~10,400 lines, against ~3,700 covered here. Two reasons, both about the cost
* curve the ratchet prices rather than about size (#9923):
*
* - What the ratchet prices is a full-file token read paid PER SEAT SESSION
* and PER ROUTINE FIRE, which is what every covered file above costs. The
* published catalog is read by customer projects, not by this repo's seats
* — a different cost curve, and not the one this gate was built against.
* - The published catalog is already a governed, human-merge-only surface
* (Prime Directive #14), so growth there passes a human eye by
* construction — a control a ratchet would merely duplicate. Note this
* reason does not separate the two roots by itself: the covered files are
* governed at merge too. What no reviewer prices THERE is the recurring
* per-session read of reason one, which is why they still carry ceilings
* and the published catalog does not.
*
* Extending coverage to the published root is therefore a POLICY CHANGE, not a
* maintenance edit: it needs its own card and a maintainer's ruling, not a
* CEILINGS row added in passing. The self-test pins this boundary, because
* enforcement cannot — a published-root entry would run perfectly green.
*
* ## The ratchet discipline (shrink-only, per file)
*
* - A ceiling may be LOWERED by any PR that shrinks its file — lowering is
Expand DownExpand Up@@ -142,6 +175,12 @@ function selfTest() {
['all six lane job descriptions are covered', ['engine', 'services', 'cli', 'devx', 'skills', 'spec'].every((n) => CEILINGS.has(`.claude/skills/pm-dispatch/references/lanes/${n}.md`)), true],
['the other four skills are covered (#9473)', ['checklist-test', 'checklist-author', 'dogfood-verification', 'spec-property-retirement'].every((n) => CEILINGS.has(`.claude/skills/${n}/SKILL.md`)), true],
['root AGENTS.md is covered (#9792)', CEILINGS.has('AGENTS.md'), true],
// The boundary the header states, pinned (#9923). Enforcement cannot hold
// it: a ceiling on a real published SKILL.md runs green like any other row,
// so without this case the header paragraph could drift from the map
// silently. Extending coverage to the published catalog is a policy change
// — it lands with a maintainer ruling that also deletes this case.
['the published skills/ catalog is deliberately uncovered', [...CEILINGS.keys()].some((k) => k.startsWith('skills/')), false],
];
let failed = 0;
for (const [name, actual, expected] of cases) {
Expand Down
Loading