Skip to content
Merged
Show file tree
Hide file tree
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
8 changes: 4 additions & 4 deletions scripts/check-skills-token-ratchet.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,7 @@ export const CEILINGS = new Map([
['skills/objectstack-platform/SKILL.md', 12984],
// 14239 -> 14391: the pull-directed split-resolution order joined the decision
// frame (maintainer ruling 2026-08-27, verbatim and untranslated: 「tong y 4」 —
// accepting the four-rule set), and this file carries TWO enforced frame copies
// accepting the four-rule set), and this file carries one enforced frame copy
// (check:skill-frame-sync COPIES), so the rule ships to third-party installers
// with the frame it amends — the #5130 drift is exactly a frame-semantics change
// that skipped this mirror. +152 tokens across both copies, compressed to the
Expand All @@ -341,8 +341,8 @@ export const CEILINGS = new Map([
// 2026-09-01, verbatim and untranslated (kept on ONE line, #11106: a governed
// quotation that soft-breaks stops being findable by the things that grep it):
// 「四维分析中,长期合理应该权重最高,至少50%」
// Same shape and same reason as the +152 row above it: this file carries TWO
// enforced frame copies (check:skill-frame-sync COPIES), and a rule that
// Same shape and same reason as the +152 row above it: this file carries one
// enforced frame copy (check:skill-frame-sync COPIES), and a rule that
// changes WHICH RECOMMENDATION the frame yields is exactly the #5130 drift
// class if it ships to third-party installers with only the old tie-break —
// the customer's agent would weigh the axes co-equally while this repo weighs
Expand All @@ -356,7 +356,7 @@ export const CEILINGS = new Map([
// existing sentence redundant, and a re-wrap moves no tokens and pays nothing.
// 14549 -> 9708: re-locked at the landed count after the #14296 item-4 split —
// the developer-agent operating template moved to `rules/dev-template.md`
// (its own row below); the two gate-pinned copies of the decision frame stay
// (its own row below); the one gate-pinned copy of the decision frame stays
// in this file (check:skill-frame-sync reads its copies by path). Lowered,
// not raised: shrink-only, no ruling needed for this direction.
['skills/objectstack-pm-dispatch/SKILL.md', 9708],
Expand Down
8 changes: 5 additions & 3 deletions scripts/pm/check-skill-line-ratchet.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -568,9 +568,11 @@ export const CEILINGS = new Map([
// 4 lines are re-flow slack, never a cut. Headroom 0 again, same convention.
// Raised 466 → 469 by the axis-weighting ruling (maintainer 2026-09-01, PM
// chat, verbatim and untranslated): 「四维分析中,长期合理应该权重最高,至少50%」.
// This file is one of the four `check:skill-frame-sync` COPIES, so a rule that
// changes which recommendation the frame yields has to reach it — the #5130
// drift is exactly a frame-semantics change that skipped a mirror. +3 lines,
// This file is not a `check:skill-frame-sync` COPIES entry, but it names the
// decision frame's mechanism (the dev reads it from the PM's pasted copy at
// dispatch time), and a rule that changes which recommendation the frame
// yields still has to reach that description — the #5130 drift is exactly a
// frame-semantics change that skipped a mirror. +3 lines,
// folded into the existing binding sentence rather than added as a new
// paragraph. ⚠️ Re-wrap funding was AVAILABLE here and was REFUSED: three
// paragraphs nearby carry wrap artifacts (two orphan lines of 6 and 8 bytes)
Expand Down
37 changes: 33 additions & 4 deletions scripts/pm/dispatch-gates.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8564,12 +8564,12 @@ export const MANDATORY_TIER_GLOBS = [
{
glob: '.claude/agents/os-dev.md',
tier: CONTRACT_REVIEW_TIER,
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',
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',
},
{
glob: 'skills/objectstack-pm-dispatch/SKILL.md',
tier: CONTRACT_REVIEW_TIER,
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',
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',
},
];

Expand Down Expand Up @@ -16397,7 +16397,7 @@ function selfTest() {
const fableOf = (paths) => deriveTier(paths);
t('the pm-dispatch SKILL.md MAIN file is fable-mandatory', fableOf(['.claude/skills/pm-dispatch/SKILL.md']).tier === CONTRACT_REVIEW_TIER);
t('the dev-agent definition is fable-mandatory', fableOf(['.claude/agents/os-dev.md']).tier === CONTRACT_REVIEW_TIER);
t('the published PM skill (two enforced frame copies) is fable-mandatory', fableOf(['skills/objectstack-pm-dispatch/SKILL.md']).tier === CONTRACT_REVIEW_TIER);
t('the published PM skill (one enforced frame copy) is fable-mandatory', fableOf(['skills/objectstack-pm-dispatch/SKILL.md']).tier === CONTRACT_REVIEW_TIER);
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);
const mixed = fableOf(['packages/spec/src/data/filter.zod.ts', '.claude/agents/os-dev.md']);
t('a MIXED surface is mandatory — one mandatory path decides, ordinary paths do not dilute it', mixed.mandatory && mixed.tier === CONTRACT_REVIEW_TIER);
Expand Down Expand Up @@ -17935,6 +17935,27 @@ function selfTest() {
['--tier', '--residue', '--commands', '--json', '--ran', '--repo', '--changed', '--self-test']
.every((flag) => USAGE_LINE.includes(flag)),
);
// ⭐ #14870 — the mirror-image fix: `--changed` sat OUTSIDE the alternation
// as a whole-invocation alternative, which reads as excluding every mode
// beside it, though `--changed --commands` is legal and answers (CONTROL
// below). Moved to the position `<path> ...` occupies, the other path
// source it stands in for.
t(
'⭐ the usage line no longer presents --changed as a whole-invocation alternative that takes no other flag (#14870)',
!USAGE_LINE.includes('] | --changed | --self-test'),
);
t(
'…and it still offers --changed where <path> ... sits, combining with the modes before it',
USAGE_LINE.includes('[<path> ... | --changed]'),
);
// CONTROL: --changed really does combine with a stdout-shape flag — the
// usage-line fix above would otherwise be cosmetic on a refusal that does
// not exist.
const changedCommandsRun = runCli(['--changed', '--commands']);
t(
'CONTROL: --changed --commands is legal and answers, so the moved usage line describes a real combination',
changedCommandsRun.status === 0 && (changedCommandsRun.stdout ?? '').length > 0,
);
}

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

/**
* Executed only as a CLI. Importing this module must have NO side effect.
Expand Down
Loading