Skip to content

Commit 369da91

Browse files
os-steveclaude
andauthored
fix(pm): correct stale four/two-copy decision-frame comments to two-copy truth (#15105)
check:skill-frame-sync's COPIES table dropped from four entries to two (internal-pm, published-pm) when the dev-side frame copies were retired — the dev prompt now receives the frame pasted from the PM's own copy at dispatch time instead of embedding one. Six comments across dispatch-gates.mjs, check-skill-line-ratchet.mjs and check-skills-token-ratchet.mjs still described the old four/two-copy state as present-tense fact; this rewrites them to the current truth without touching any behaviour, verdict line, or ceiling value (every affected self-test prints the identical case count before and after). Also moves dispatch-gates.mjs's `--changed` flag from a whole-invocation usage-line alternative (implying it excludes --commands/--json) to the position `<path> ...` occupies, matching how #15036 placed --residue — `--changed --commands` is legal and answers, so the old line advertised a refusal the argv chain does not make. New self-test pins lock the fix, including a live CONTROL that the combination really works. Fixes #14870 Claude-Session: https://claude.ai/code/session_019RfFHiRCSs3JXLK4cwcfox Co-authored-by: Claude <noreply@anthropic.com>
1 parent 4a4a35d commit 369da91

3 files changed

Lines changed: 42 additions & 11 deletions

File tree

scripts/check-skills-token-ratchet.mjs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,7 @@ export const CEILINGS = new Map([
331331
['skills/objectstack-platform/SKILL.md', 12984],
332332
// 14239 -> 14391: the pull-directed split-resolution order joined the decision
333333
// frame (maintainer ruling 2026-08-27, verbatim and untranslated: 「tong y 4」 —
334-
// accepting the four-rule set), and this file carries TWO enforced frame copies
334+
// accepting the four-rule set), and this file carries one enforced frame copy
335335
// (check:skill-frame-sync COPIES), so the rule ships to third-party installers
336336
// with the frame it amends — the #5130 drift is exactly a frame-semantics change
337337
// that skipped this mirror. +152 tokens across both copies, compressed to the
@@ -341,8 +341,8 @@ export const CEILINGS = new Map([
341341
// 2026-09-01, verbatim and untranslated (kept on ONE line, #11106: a governed
342342
// quotation that soft-breaks stops being findable by the things that grep it):
343343
// 「四维分析中,长期合理应该权重最高,至少50%」
344-
// Same shape and same reason as the +152 row above it: this file carries TWO
345-
// enforced frame copies (check:skill-frame-sync COPIES), and a rule that
344+
// Same shape and same reason as the +152 row above it: this file carries one
345+
// enforced frame copy (check:skill-frame-sync COPIES), and a rule that
346346
// changes WHICH RECOMMENDATION the frame yields is exactly the #5130 drift
347347
// class if it ships to third-party installers with only the old tie-break —
348348
// the customer's agent would weigh the axes co-equally while this repo weighs
@@ -356,7 +356,7 @@ export const CEILINGS = new Map([
356356
// existing sentence redundant, and a re-wrap moves no tokens and pays nothing.
357357
// 14549 -> 9708: re-locked at the landed count after the #14296 item-4 split —
358358
// the developer-agent operating template moved to `rules/dev-template.md`
359-
// (its own row below); the two gate-pinned copies of the decision frame stay
359+
// (its own row below); the one gate-pinned copy of the decision frame stays
360360
// in this file (check:skill-frame-sync reads its copies by path). Lowered,
361361
// not raised: shrink-only, no ruling needed for this direction.
362362
['skills/objectstack-pm-dispatch/SKILL.md', 9708],

scripts/pm/check-skill-line-ratchet.mjs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -568,9 +568,11 @@ export const CEILINGS = new Map([
568568
// 4 lines are re-flow slack, never a cut. Headroom 0 again, same convention.
569569
// Raised 466 → 469 by the axis-weighting ruling (maintainer 2026-09-01, PM
570570
// chat, verbatim and untranslated): 「四维分析中,长期合理应该权重最高,至少50%」.
571-
// This file is one of the four `check:skill-frame-sync` COPIES, so a rule that
572-
// changes which recommendation the frame yields has to reach it — the #5130
573-
// drift is exactly a frame-semantics change that skipped a mirror. +3 lines,
571+
// This file is not a `check:skill-frame-sync` COPIES entry, but it names the
572+
// decision frame's mechanism (the dev reads it from the PM's pasted copy at
573+
// dispatch time), and a rule that changes which recommendation the frame
574+
// yields still has to reach that description — the #5130 drift is exactly a
575+
// frame-semantics change that skipped a mirror. +3 lines,
574576
// folded into the existing binding sentence rather than added as a new
575577
// paragraph. ⚠️ Re-wrap funding was AVAILABLE here and was REFUSED: three
576578
// paragraphs nearby carry wrap artifacts (two orphan lines of 6 and 8 bytes)

scripts/pm/dispatch-gates.mjs

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8564,12 +8564,12 @@ export const MANDATORY_TIER_GLOBS = [
85648564
{
85658565
glob: '.claude/agents/os-dev.md',
85668566
tier: CONTRACT_REVIEW_TIER,
8567-
why: 'clause ① (2026-08-20 narrowing): the dev-agent definition is protocol semantics — every dispatched dev runs under it, and it carries an enforced copy of the decision frame',
8567+
why: 'clause ① (2026-08-20 narrowing): the dev-agent definition is protocol semantics — every dispatched dev runs under it, and receives the decision frame the PM pastes into its prompt at dispatch time rather than carrying a copy of its own',
85688568
},
85698569
{
85708570
glob: 'skills/objectstack-pm-dispatch/SKILL.md',
85718571
tier: CONTRACT_REVIEW_TIER,
8572-
why: 'clause ① (2026-08-20 narrowing): the published PM skill carries two enforced copies of the decision frame (check:skill-frame-sync COPIES) and ships verbatim to third-party projects',
8572+
why: 'clause ① (2026-08-20 narrowing): the published PM skill carries one enforced copy of the decision frame (check:skill-frame-sync COPIES) and ships verbatim to third-party projects',
85738573
},
85748574
];
85758575

@@ -16397,7 +16397,7 @@ function selfTest() {
1639716397
const fableOf = (paths) => deriveTier(paths);
1639816398
t('the pm-dispatch SKILL.md MAIN file is fable-mandatory', fableOf(['.claude/skills/pm-dispatch/SKILL.md']).tier === CONTRACT_REVIEW_TIER);
1639916399
t('the dev-agent definition is fable-mandatory', fableOf(['.claude/agents/os-dev.md']).tier === CONTRACT_REVIEW_TIER);
16400-
t('the published PM skill (two enforced frame copies) is fable-mandatory', fableOf(['skills/objectstack-pm-dispatch/SKILL.md']).tier === CONTRACT_REVIEW_TIER);
16400+
t('the published PM skill (one enforced frame copy) is fable-mandatory', fableOf(['skills/objectstack-pm-dispatch/SKILL.md']).tier === CONTRACT_REVIEW_TIER);
1640116401
t('a pm-dispatch REFERENCES path carries NO path mandate — the 2026-08-20 narrowing, inverted from the pre-narrowing pin', fableOf(['.claude/skills/pm-dispatch/references/review-checklist.md']).mandatory === false);
1640216402
const mixed = fableOf(['packages/spec/src/data/filter.zod.ts', '.claude/agents/os-dev.md']);
1640316403
t('a MIXED surface is mandatory — one mandatory path decides, ordinary paths do not dilute it', mixed.mandatory && mixed.tier === CONTRACT_REVIEW_TIER);
@@ -17935,6 +17935,27 @@ function selfTest() {
1793517935
['--tier', '--residue', '--commands', '--json', '--ran', '--repo', '--changed', '--self-test']
1793617936
.every((flag) => USAGE_LINE.includes(flag)),
1793717937
);
17938+
// ⭐ #14870 — the mirror-image fix: `--changed` sat OUTSIDE the alternation
17939+
// as a whole-invocation alternative, which reads as excluding every mode
17940+
// beside it, though `--changed --commands` is legal and answers (CONTROL
17941+
// below). Moved to the position `<path> ...` occupies, the other path
17942+
// source it stands in for.
17943+
t(
17944+
'⭐ the usage line no longer presents --changed as a whole-invocation alternative that takes no other flag (#14870)',
17945+
!USAGE_LINE.includes('] | --changed | --self-test'),
17946+
);
17947+
t(
17948+
'…and it still offers --changed where <path> ... sits, combining with the modes before it',
17949+
USAGE_LINE.includes('[<path> ... | --changed]'),
17950+
);
17951+
// CONTROL: --changed really does combine with a stdout-shape flag — the
17952+
// usage-line fix above would otherwise be cosmetic on a refusal that does
17953+
// not exist.
17954+
const changedCommandsRun = runCli(['--changed', '--commands']);
17955+
t(
17956+
'CONTROL: --changed --commands is legal and answers, so the moved usage line describes a real combination',
17957+
changedCommandsRun.status === 0 && (changedCommandsRun.stdout ?? '').length > 0,
17958+
);
1793817959
}
1793917960

1794017961
// ── END TO END: the CI-measured family, on the card it was measured on (#14004)
@@ -18304,6 +18325,14 @@ const invokedDirectly = isEntrypoint(import.meta.url);
1830418325
* ⛔ Deleting `[--residue]` instead would understate it — the flag really is
1830518326
* legal with the other three, and with the plain human rendering.
1830618327
*
18328+
* `--changed` had the mirror-image problem: it sat OUTSIDE the alternation as
18329+
* a whole-invocation alternative, which reads as excluding every member next
18330+
* to it — `--commands`/`--json` included, though `--changed --commands` is
18331+
* legal and answers (it derives the path list `<path> ...` would otherwise
18332+
* supply, and nothing more). Moved to the position `<path> ...` occupies, the
18333+
* other path source it stands in for, so the line no longer implies a refusal
18334+
* the argv chain does not make (#14870).
18335+
*
1830718336
* A CONSTANT rather than a literal at the print site, because the pin belongs
1830818337
* beside the refusals it mirrors: reaching the print site needs a checkout
1830918338
* where `changedPathsFromGit()` refuses, and a pin that cannot be run in the
@@ -18312,7 +18341,7 @@ const invokedDirectly = isEntrypoint(import.meta.url);
1831218341
const USAGE_LINE =
1831318342
'usage: node scripts/pm/dispatch-gates.mjs'
1831418343
+ ' [--tier | [--residue] [--commands | --json | --ran <file>]]'
18315-
+ ' [--repo owner/name] [<path> ...] | --changed | --self-test';
18344+
+ ' [--repo owner/name] [<path> ... | --changed] | --self-test';
1831618345

1831718346
/**
1831818347
* Executed only as a CLI. Importing this module must have NO side effect.

0 commit comments

Comments
 (0)