Skip to content

Commit 77df0f6

Browse files
os-steveclaude
andauthored
pm(tooling): move the contract-review tier off the retired ceiling, and give the fuse the case it never had (#19573)
Fixes #19544 Clause-②: no Fable is gone. The contract-review tier moves to the opus tier the harness actually serves, and the downgrade fuse gains the case it never had: a tier that is GONE is not a tier that is EXHAUSTED, and the difference is **who may act**. ## The four parts **1 — the constant.** `CONTRACT_REVIEW_TIER` in `scripts/pm/dispatch-gates.mjs` now holds the opus model id. ⛔ Not recalled and ⛔ not invented: read off the harness itself — the `claude-code-remote` `get_session` tool reports it for this session as `external_metadata.last_served_model`, and `session_context.model` and `configured_model` agree with it. The id is still spelled as a VALUE on exactly one line in the tree, which the battery proves rather than promises (a landed case scans both roots and reds on a second site — exercised below). **2 — the ladder.** 「ceiling fable」 was a WORD written beside the constant, and that is exactly how the pair drifted: the harness stopped serving the tier, the constant kept naming it, and the ladder went on printing its family word as a live rule. The ceiling is DERIVED now — `tierWordOf()` reads the family out of the model id, `TIER_CEILING` is that derivation — so `--tier` renders ``` The tier stays the PM's per-card judgment call (floor sonnet · default opus · ceiling opus). ``` and the next retirement moves ONE line. The rendered EXITS text and its docblock stop naming the retired tier too: the quota exemption now reads "the mandated tier EXHAUSTED ⇒ opus, never lower — a tier that is RETIRED is a maintainer ruling instead, ⛔ never a seat's reading". `tierWordOf` hands an id it cannot read back VERBATIM rather than throwing: `--tier` runs on every dispatch, and a ladder printing the whole id is louder than one printing a family word nobody ruled. **3 — the case the rule never had.** `.claude/skills/pm-dispatch/references/contract-review.md` line 60 now reads: ``` - 额度耗尽豁免只及派发 ⛔ 不及复核;档位退役 ≠ 耗尽,恒维护者裁决 ⛔ 非席位读数。 ``` Paid IN PLACE — ⛔ no ceiling raised, ⛔ no ruled clause deleted. The file is still **60 lines against a ceiling of 60 at headroom 0**, and the line is **115 bytes** against the 120-byte cap. The old line's two clauses (「豁免对象是派发」 and 「复核 ⛔ 不随派发档位免除」) survive compressed into 「只及派发 ⛔ 不及复核」; 额度耗尽's existing treatment is untouched, because it was never on this line — 「队列外等档」 is line 53's and 「⛔ 不自审」 is line 26's, and neither moved. **4 — the pins.** Ten new self-test cases hold the constant and the rule text together: the ladder line is compared against the constant (not against a remembered word), a retired-spelling guard (`RETIRED_TIER_WORDS`) is asserted over every live rendering, the rulebook root is asserted to spell no model id at all, and the fuse's two arms are read out of the skill file itself. ## What deliberately did NOT change The issue is explicit that a record of what was served AT THE TIME stays true, so every occurrence naming the retired tier as **history** was left alone and judged one by one — verbatim maintainer rulings in the mandate docblocks and the `skills/**` glob's `why` row, the #8640 incident note in the battery, and the test LABELS that call the mandate "fable-mandatory" (they already assert against `CONTRACT_REVIEW_TIER`, so they are tier-agnostic in behaviour). The retired-word guard therefore covers the RULE lines and excludes the `- path ⇢ glob — why` provenance rows, with a non-vacuity case proving the filtered text still carries rules to search. `check-clause2-carriers --template`'s `Served-tier:` contract is **unchanged**: the key still admits the constant's NAME, ⛔ never a model id. It was already pinned (the battery refuses a family prefix, a suffixed constant, and the constant's own VALUE, and its refusal never quotes an identifier back), so nothing was added there — only re-run. ## Governance — Tier S, ALL-not-ANY ``` node scripts/pm/check-governed-merges.mjs --test scripts/pm/dispatch-gates.mjs \ .claude/skills/pm-dispatch/references/contract-review.md ``` > governed-surface predicate: 1 of 2 path(s) hit the register (6 surfaces, repo-agnostic). > ⛔ GOVERNED — Tier S(席内达档复核落地) … `.claude/** ×1` — > `.claude/skills/pm-dispatch/references/contract-review.md`; > paths not on the register: `scripts/pm/dispatch-gates.mjs` No Tier H path is touched: `AGENTS.md`, `CLAUDE.md`, `docs/adr/**`, `docs/NORTH-STAR.md` and the published `skills/` root are all outside this diff. This PR lands on the owning seat's at-tier review of record; ⛔ no maintainer click is waited for and ⛔ no seat approves it. ## Evidence **`node scripts/pm/dispatch-gates.mjs --self-test`** — `✓ dispatch-gates self-test: 1893 cases pass.`, exit 0, zero reds, run to completion on the final commit (the battery re-spawns the tool's CLI many times, so it is run detached per its own header and the log read at the end). **`node scripts/pm/check-clause2-carriers.mjs --self-test`** — `1115 cases pass`, exit 0 (it imports the constant, so this change moves its verdict even though the path derivation does not name it; CI runs it in `Lint & Repo Gates`). **Derived gate families** — `node scripts/pm/dispatch-gates.mjs --commands` derived 37 commands off the merge base; all were run and all are green, with one NOT MEASURED: `check:doc-formula-expressions` exits 3 `PREREQUISITE NOT MET` (two workspace packages are unbuilt in this worktree) — ⛔ read as not measured, never as a pass. `check:pm-skill-ratchet` and its self-test are green (157 cases), as are `check:pm-governed-merges`, `check:skill-frame-sync`, `check:watch-hint-literal`, `check:nul-bytes` and `check:pm-skill-id-lint`. **Lint, narrowed and declared.** `eslint scripts/pm/dispatch-gates.mjs --no-inline-config` → 1 file, 0 errors, 0 warnings. The narrowing is measured, not assumed: ① eslint's own population is `**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}`, so the `.md` file is outside it entirely; ② the file count is read from `--format json` (1); ③ `eslint.config.mjs` enables no type-aware linting anywhere (no `parserOptions.project`, no `projectService`), so this diff cannot move the verdict on any file it does not touch. The repo-wide `pnpm lint` sweep is CI's run. **Control bytes.** `grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]'` over both changed files prints nothing (exit 1); the control — the same `-P` class engine asked for printable ASCII on the same files — fires on 28,205 and 53 lines. ### The retired id is gone from every live rule, with a control The pattern is spelled as the id's SHAPE so this body carries no model identifier: ``` git grep -nE 'claude-[a-z]+-5-1' HEAD -- . scripts/check-commit-card-trailers.mjs:848 git grep -nE 'claude-[a-z]+-5-1' origin/main -- . # CONTROL: same pattern, base tree scripts/check-commit-card-trailers.mjs:848 scripts/pm/dispatch-gates.mjs:12124 ``` The control proves the grep fires: on `origin/main` it finds the constant's old declaration; on this branch that line is gone. The one survivor is a **rejection vocabulary** — the list of ids `check-commit-card-trailers` must refuse in a commit trailer — not a tier rule, and deleting the retired entry would weaken the gate for old history. ### Ablation — the pins are load-bearing Each mutation was landed with `scripts/ablation-replace.mjs` (anchor count checked, blob hash before/after recorded) and restored with `git checkout HEAD --`, proven by `blob == HEAD blob` and an empty `git diff HEAD`. **A — move the code side without the rule.** The ladder's `ceiling ${TIER_CEILING})` was replaced by the retired family word (anchor 1 → 0, blob `e3a804cfc19f` → `d19c861ac7ee`): ``` ✗ the ladder prints a ceiling DERIVED from the contract-review constant, so the two cannot drift apart ✗ ⛔ no RETIRED tier word survives in any live RULE … (dirty: 0/fable 3/fable) ``` 2 reds out of 1,506 cases decided; every other new pin stayed green, which is the point — they bind different halves. **B — a second spelling of the tier's VALUE** (a fixture argument in the battery rewritten to the constant's own value; anchor 1 → 0, blob `e3a804cfc19f` → `517886d4a0f9`): ``` ✗ the tier constant's VALUE is spelled in exactly ONE site under .claude/skills/pm-dispatch + scripts/pm — 25+30 files read … (found: scripts/pm/dispatch-gates.mjs:12142, scripts/pm/dispatch-gates.mjs:25317) ``` This run is why two of the cases first written here were **deleted before review**: the promise was already pinned, and better, by a landed case that derives its roots from the mandate globs and finds the definition line instead of remembering it. One assertion, one owner. **C — move the rule side without the code.** Line 60 of the rulebook was reverted to its pre-card wording with a model id appended (anchor 1 → 0, blob `aebe3ca8b11d` → `906f7ea1e9d5`): ``` ✓ the ladder prints a ceiling DERIVED from the contract-review constant … ✓ …in the ladder's own vocabulary — the constant's FAMILY word … ✗ .claude/skills/pm-dispatch spells NO model id at all — 25 file(s) read … (found: …/contract-review.md:60) ✗ …and its fuse keeps the quota exemption pointed at DISPATCH, never at the review ✗ …and carries the case it never had: a RETIRED tier is not an exhausted one … ✗ the tier constant's VALUE is spelled in exactly ONE site … (found: …/contract-review.md:60, …/dispatch-gates.mjs:12142) ``` 4 reds out of 1,422 cases decided — the three rulebook pins plus the landed value-site one — while **both ladder pins stayed green**. That asymmetry is the demonstration the card asks for: move the rule text without the constant and the pins red; the halves are bound, not merely adjacent. ## Acceptance notes Reported to the PM rather than fixed here — all outside the declared file surface (`scripts/pm/dispatch-gates.mjs` · `.claude/skills/pm-dispatch/`): - `.claude/skills/checklist-test/SKILL.md:103` states 「floor sonnet · default opus · ceiling fable」 as a **live rule**, not as history. It is Tier S like this diff, so it could have ridden along, but it is out of surface. - `scripts/pm/check-dispatch-gates.mjs:263` defines part of the mandated surface as "the fable-mandatory surface" in a live docblock — stale vocabulary, no behaviour. - `.claude/skills/pm-dispatch/SKILL.md:534` reads 「额度耗尽豁免仅当契约复审档实测不可用才落默认 判断档」. With the ceiling now equal to the default, that exemption has no room left to exempt; it is in surface but it is a rule about the DISPATCH ladder, not the review fuse the card names, so it was left for a ruling rather than reinterpreted here. - `scripts/pm/check-clause2-carriers.mjs` consumes `CONTRACT_REVIEW_TIER` but is not derived by `dispatch-gates --commands` for a diff that changes it — a watch-hint blind spot of exactly the shape that file's own docblocks describe. Run by hand here; green. --- _Generated by [Claude Code](https://claude.ai/code/session_01AmH9bKvGoLjiY86Q4Z3og2)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 2aac821 commit 77df0f6

2 files changed

Lines changed: 124 additions & 7 deletions

File tree

‎.claude/skills/pm-dispatch/references/contract-review.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,4 +57,4 @@
5757
- 产出裁决的每轮都须读到契约复审档位,见回退证据 ⇒ 裁决整体作废。
5858
- 父会话只可逐字采纳或整体作废(核验失败、越范围、格式不完整),⛔ 永不改写润色。
5959
- PASS、FAIL 与作废都落 PR 或卡;同 head 再起子代理须引前次作废因,⛔ 不重起求 PASS。
60-
- 契约复核 ⛔ 不适用额度耗尽豁免降档:豁免对象是派发,复核 ⛔ 不随派发档位免除。
60+
- 额度耗尽豁免只及派发 ⛔ 不及复核;档位退役 ≠ 耗尽,恒维护者裁决 ⛔ 非席位读数。

‎scripts/pm/dispatch-gates.mjs‎

Lines changed: 123 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12119,9 +12119,27 @@ export function residueLines(
1211912119
* gate's accept set is the maintainer's decision, not a refresh-time
1212012120
* convenience. The review label deliberately names WHAT is reviewed, never a
1212112121
* model (maintainer, 2026-08-16: 「needs:fable-review 这个标签不好,下次模型升级怎么办」).
12122+
*
12123+
* ## A tier that is GONE is not a tier that is EXHAUSTED (#19544)
12124+
*
12125+
* The exits below carry a QUOTA exemption: a tier that is exhausted comes
12126+
* back, so the card waits out of the queue and the review is never downgraded
12127+
* and never self-reviewed. A tier that has been RETIRED never comes back, and
12128+
* the two cases differ on WHO MAY ACT: a seat reading 「⛔ 不许降档」 onto a
12129+
* vanished tier holds its whole lane forever, and a seat picking the
12130+
* replacement itself is the silent downgrade the fuse exists to stop. So a
12131+
* retirement is a maintainer ruling and ⛔ never a seat's reading — and when
12132+
* the ruling lands, this VALUE is the one line that moves. The ceiling of the
12133+
* ladder `tierLines` prints is DERIVED from it ({@link TIER_CEILING}) so the
12134+
* two cannot drift apart. This value moved off the retired ceiling on the
12135+
* maintainer's 2026-09-21 ruling — verbatim: 「fable 没有了」, answered with
12136+
* 「改成 opus」 — which is why the docblocks below still name the retired tier
12137+
* where they record what was ruled AT THE TIME; a record of what was served
12138+
* then stays true. Rule text: `references/contract-review.md` 「降档保险丝」.
12139+
*
1212212140
* Rulebook: `.claude/skills/pm-dispatch/SKILL.md` 「入队与落地」 — the clause-② gate and the `needs:contract-review` review-chain bullets.
1212312141
*/
12124-
export const CONTRACT_REVIEW_TIER = 'claude-fable-5-1';
12142+
export const CONTRACT_REVIEW_TIER = 'claude-opus-5';
1212512143

1212612144
/**
1212712145
* The globs that MANDATE a model tier for any card whose file surface touches
@@ -12201,9 +12219,10 @@ export const CONTRACT_REVIEW_TIER = 'claude-fable-5-1';
1220112219
*
1220212220
* The sanctioned exits from a mandate — the one-line-class mechanical-edit
1220312221
* downgrade (a card CONTENT judgment, like clause ②), the measured quota
12204-
* exemption (fable unavailable ⇒ opus, never lower) and the proactive
12205-
* low-headroom downgrade — are claim-time judgments, not properties of the
12206-
* file surface. This tool states the mandate; the seat records any exit and
12222+
* exemption (the mandated tier EXHAUSTED ⇒ the default tier, never lower — a
12223+
* tier that is RETIRED is not exhausted and is a maintainer ruling instead,
12224+
* ⛔ never a seat's reading) and the proactive low-headroom downgrade — are
12225+
* claim-time judgments, not properties of the file surface. This tool states the mandate; the seat records any exit and
1220712226
* its reason in the claim comment. ONE exit is path-shaped, and so it IS
1220812227
* encoded: the one-line-class downgrade does not exist for a surface under
1220912228
* `skills/**` — the 2026-09-10 ruling's 必须, because a closed enumeration
@@ -12331,6 +12350,49 @@ export const TIER_FLOOR = 'sonnet';
1233112350
/** The default judgment tier for a card with no mandate — the ruling's 默认判断档. */
1233212351
export const TIER_DEFAULT = 'opus';
1233312352

12353+
/**
12354+
* The FAMILY word inside a model id — `claude-<family>-<version>` ⇒ `family`.
12355+
*
12356+
* The ladder the claim comment quotes is written in family words (`sonnet`,
12357+
* `opus`), while {@link CONTRACT_REVIEW_TIER} is a full model id, so the
12358+
* ceiling has to cross between the two vocabularies somewhere. It crosses
12359+
* HERE, by derivation, because the alternative — writing the ceiling's word
12360+
* down beside the constant — is the second value site the constant's own
12361+
* docblock refuses, and it is exactly how the ladder came to read 「ceiling
12362+
* fable」 for a tier the harness had stopped serving.
12363+
*
12364+
* An id this cannot read returns VERBATIM rather than throwing or guessing: a
12365+
* ladder printing the whole model id is louder than one printing a family word
12366+
* nobody ruled, and `--tier` runs on every dispatch, so a model id in an
12367+
* unfamiliar shape must not take the tool down. Both directions are pinned.
12368+
*/
12369+
export function tierWordOf(modelId) {
12370+
const m = /^claude-([a-z]+)-/.exec(String(modelId ?? ''));
12371+
return m === null ? String(modelId ?? '') : m[1];
12372+
}
12373+
12374+
/**
12375+
* The ladder's CEILING — the contract-review tier, in the ladder's vocabulary.
12376+
*
12377+
* ⛔ Not a constant of its own: derived from {@link CONTRACT_REVIEW_TIER} so a
12378+
* retirement ruling moves ONE line and the ladder follows. The day the ceiling
12379+
* and the default read the same word is not a bug — it is what a retired
12380+
* ceiling falling to the default LOOKS like, and the ladder says so rather
12381+
* than keeping a tier nobody can be dispatched at.
12382+
*/
12383+
export const TIER_CEILING = tierWordOf(CONTRACT_REVIEW_TIER);
12384+
12385+
/**
12386+
* Tier family words this ladder once printed and must never print again.
12387+
*
12388+
* ⛔ Not a tier table and ⛔ not an ordering — a RETIRED-SPELLING guard, the
12389+
* same shape as the retired clause-② keys pinned further down. The self-test
12390+
* asserts no rendering contains one, so the day a ceiling is written down by
12391+
* hand again it reds instead of quietly outliving the harness that served it.
12392+
* A word leaves this list only when a maintainer ruling brings the tier back.
12393+
*/
12394+
export const RETIRED_TIER_WORDS = Object.freeze(['fable']);
12395+
1233412396
/**
1233512397
* Place a card's file surface against the mandatory globs. Pure over its
1233612398
* inputs, so the self-test can drive every branch offline.
@@ -12394,7 +12456,7 @@ export function tierLines(result) {
1239412456
if (!mandatory) {
1239512457
return [
1239612458
`Model tier — no path-derived mandate: the surface hits none of the ${declared} declared glob(s), derived here, not recalled.`,
12397-
` The tier stays the PM's per-card judgment call (floor ${TIER_FLOOR} · default ${TIER_DEFAULT} · ceiling fable).`,
12459+
` The tier stays the PM's per-card judgment call (floor ${TIER_FLOOR} · default ${TIER_DEFAULT} · ceiling ${TIER_CEILING}).`,
1239812460
clause2,
1239912461
...suspicion,
1240012462
];
@@ -12415,7 +12477,8 @@ export function tierLines(result) {
1241512477
`Model tier — MANDATORY: ${tier} (derived from the file surface, not recalled).`,
1241612478
...hits.map((h) => ` - ${h.path} ⇢ '${h.glob}' — ${h.why}`),
1241712479
` Exits, each recorded with its reason in the claim comment's \`Container & model\` line: ${oneLineExit};` +
12418-
' the measured quota exemption (fable unavailable ⇒ opus, never lower); the proactive low-headroom downgrade.',
12480+
` the measured quota exemption (the mandated tier EXHAUSTED ⇒ ${TIER_DEFAULT}, never lower — a tier that is` +
12481+
" RETIRED is a maintainer ruling instead, ⛔ never a seat's reading); the proactive low-headroom downgrade.",
1241912482
clause2,
1242012483
...suspicion,
1242112484
];
@@ -25232,6 +25295,60 @@ function selfTest() {
2523225295
t(`every frame-sync-enforced copy is fable-mandated (unmandated: ${frameFiles.filter((f) => !deriveTier([f]).mandatory).join(', ') || 'none'})`, frameFiles.length > 0 && frameFiles.every((f) => deriveTier([f]).tier === CONTRACT_REVIEW_TIER));
2523325296
t('the SKILL.md main file and the dev-agent definition are declared in their own right, not only via the frame table', MANDATORY_TIER_GLOBS.some((g) => g.glob === '.claude/skills/pm-dispatch/SKILL.md') && MANDATORY_TIER_GLOBS.some((g) => g.glob === '.claude/agents/os-dev.md'));
2523425297

25298+
// ── The ladder's CEILING and the constant, pinned TOGETHER (#19544) ───────
25299+
//
25300+
// The ceiling used to be a WORD written beside the constant, and the pair
25301+
// drifted in the one direction nothing could see: the harness stopped
25302+
// serving the tier, the constant kept naming it, and the ladder went on
25303+
// printing its family word as a LIVE rule for a tier no dispatch could
25304+
// reach. The ceiling is derived now, so these cases hold the RENDERING
25305+
// against the constant rather than against a remembered word — write the
25306+
// ceiling by hand again and they red, whichever half was edited.
25307+
// The provenance ROWS (` - <path> ⇢ '<glob>' — <why>`) are excluded from the
25308+
// retired-word guard below, and deliberately: several of them quote a
25309+
// maintainer ruling verbatim, and a record of what was ruled AT THE TIME
25310+
// stays true however the tier moves afterwards. What the guard covers is the
25311+
// rules — the ladder, the exits, the clause-② note, the suspicion line.
25312+
const liveRuleText = (rendered) => rendered.split('\n').filter((l) => !/^\s+- /.test(l)).join('\n');
25313+
const ladderRenderings = [plainLines, mandLines, catalogLines, tierLines(fableOf(['packages/spec/src/api/error-code-ledger.zod.ts'])).join('\n')].map(liveRuleText);
25314+
const ladderLine = plainLines.split('\n').find((l) => l.includes('The tier stays')) ?? '';
25315+
t('the ladder prints a ceiling DERIVED from the contract-review constant, so the two cannot drift apart', ladderLine.includes(`ceiling ${tierWordOf(CONTRACT_REVIEW_TIER)})`), ladderLine || 'no ladder line was rendered at all');
25316+
t("…in the ladder's own vocabulary — the constant's FAMILY word, so a parseable id NEVER reaches the ladder verbatim", /^claude-[a-z]+-/.test(CONTRACT_REVIEW_TIER) && TIER_CEILING === tierWordOf(CONTRACT_REVIEW_TIER) && !ladderLine.includes(CONTRACT_REVIEW_TIER), ladderLine);
25317+
t('tierWordOf reads the family out of an id, and hands an unreadable one back VERBATIM rather than guessing a word', tierWordOf('claude-example-9-9') === 'example' && tierWordOf('an-unfamiliar-shape') === 'an-unfamiliar-shape' && tierWordOf(null) === '');
25318+
t(`⛔ no RETIRED tier word survives in any live RULE — ladder, exits, clause-② note and suspicion line all read the constant (dirty: ${ladderRenderings.map((l, i) => RETIRED_TIER_WORDS.filter((w) => l.toLowerCase().includes(w)).map((w) => `${i}/${w}`).join(' ')).filter(Boolean).join(' ') || 'none'})`, ladderRenderings.every((l) => RETIRED_TIER_WORDS.every((w) => !l.toLowerCase().includes(w))));
25319+
t('…and the guard is not reading an empty string — every rendering it clears still carries its own rule text', ladderRenderings.every((l) => l.includes('Model tier')) && ladderRenderings.some((l) => l.includes('Exits,')));
25320+
t('the retired-spelling guard is not vacuous — it names at least one word, and none of them is a tier still in the ladder', RETIRED_TIER_WORDS.length > 0 && !RETIRED_TIER_WORDS.includes(TIER_FLOOR) && !RETIRED_TIER_WORDS.includes(TIER_DEFAULT) && !RETIRED_TIER_WORDS.includes(TIER_CEILING));
25321+
// A sibling case further down pins the constant's CURRENT value to exactly
25322+
// one site under these roots. What that case cannot see is a RETIRED id left
25323+
// behind — it is not the current value, so nothing compares it to anything,
25324+
// and it reads to a grepping seat as a live tier rule. So this one asks the
25325+
// rulebook root a different question: does it spell a model id AT ALL? The
25326+
// skill's own rule is 「本文不写模型名」, and a retirement is the moment that
25327+
// promise pays for itself — the tree needs no edit, so it cannot go stale.
25328+
const walkFilesUnder = (dir, out = []) => {
25329+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
25330+
const abs = nodePath.join(dir, entry.name);
25331+
if (entry.isDirectory()) walkFilesUnder(abs, out);
25332+
else if (entry.isFile()) out.push(abs);
25333+
}
25334+
return out;
25335+
};
25336+
const SKILL_RULEBOOK_ROOT = '.claude/skills/pm-dispatch';
25337+
const skillRulebookFiles = walkFilesUnder(nodePath.join(ROOT, SKILL_RULEBOOK_ROOT));
25338+
const skillIdSpellings = [];
25339+
for (const abs of skillRulebookFiles) {
25340+
readFileSync(abs, 'utf8').split('\n').forEach((line, i) => {
25341+
if (/\bclaude-[a-z]+-\d[\w.-]*/.test(line)) skillIdSpellings.push(`${nodePath.relative(ROOT, abs)}:${i + 1}`);
25342+
});
25343+
}
25344+
t(`${SKILL_RULEBOOK_ROOT} spells NO model id at all — ${skillRulebookFiles.length} file(s) read, and a RETIRED id left there is caught HERE, where a current-value pin cannot see it (found: ${skillIdSpellings.join(', ') || 'none'})`, skillRulebookFiles.length > 0 && skillIdSpellings.length === 0);
25345+
// The rulebook half of the same coupling: the skill names the constant, and
25346+
// its downgrade fuse carries the case the quota exemption never had.
25347+
const fuseRules = readFileSync(nodePath.join(ROOT, '.claude/skills/pm-dispatch/references/contract-review.md'), 'utf8');
25348+
t('the rulebook names the contract-review tier by its CONSTANT, so a retirement moves the value and the prose follows', fuseRules.includes('`CONTRACT_REVIEW_TIER`'));
25349+
t('…and its fuse keeps the quota exemption pointed at DISPATCH, never at the review', /额度耗尽豁免[^\n]*⛔[^\n]*不及复核/.test(fuseRules));
25350+
t('…and carries the case it never had: a RETIRED tier is not an exhausted one, and it is a maintainer ruling, ⛔ never a seat\'s reading', /档位退役[^\n]*≠[^\n]*耗尽[^\n]*维护者裁决[^\n]*⛔[^\n]*非席位读数/.test(fuseRules));
25351+
2523525352
// ── Clause-② suspicion (the enqueue-gate card): hit / no hit / wording ────
2523625353
//
2523725354
// The wording cases are not decoration — the suspicion line is quoted into

0 commit comments

Comments
 (0)