Uh oh!
There was an error while loading. Please reload this page.
docs(pm-dispatch): keep two copies of the decision frame, not four - #14862
Merged
Conversation
Batch-3 ruling, item 4, option B: the four-axis decision frame keeps two
copies (internal-pm and published-pm); the dev-side copies are replaced by
the {decision_frame} placeholder the dispatching PM fills at dispatch time.
- skills/objectstack-pm-dispatch/SKILL.md: the embedded dev-agent template
loses the frame copy (declaring sentence through binding sentence). The
stop trigger stays -- it is the escalation TRIGGER, not the frame, and
rules/dev-template.md carries it nowhere else. The paste instruction now
names the surviving section by heading.
- .claude/agents/os-dev.md: the frame block collapses to one sentence saying
the frame arrives in the dispatch prompt, that every option is analyzed on
its axes and the recommendation justified on them, and that a prompt
without it is a stop-and-ask rather than an invitation to invent axes.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1…copies check-skill-frame-sync watched four copies across three files; the batch-3 ruling (item 4, option B) leaves two, both PM-side. The dev-side entries are removed and every count that was spelled out is now derived from the table. - COPIES loses internal-dev and published-dev; the header, the COPIES docblock and the count-disagreement message stop spelling "four". - The same-file overlap guard stays. It is vacuous today (no two copies share a file) but re-declaring such a pair is one edit away, and it is the check that makes such a pair prove it compares two different sections. - Self-test: the three cases driven through the removed ids are retargeted at the surviving copies, and a new case pins the reduction itself -- the dev-side text reappearing in the now-undeclared agent definition is refused by the anti-dormancy scan. Its measured limit is written next to it: the scan's population is per FILE, so a second copy pasted back into a file that is already declared is not refused, and was not before this change either. - check-skill-frame-freshness imported COPIES but spelled one frame file's path in five fixtures; those now derive it from the table, which is what the file's own #8024 rule asks for. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1
…cision-frame-two-copies
… budget check:pm-skill-ratchet reads a per-line byte budget as well as a line ceiling; the first line of the replacement landed at 121B. Rewrapped at legal CJK break points, content byte-identical. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1
This was referenced Sep 3, 2026
os-zhuang
approved these changes
Sep 3, 2026
os-zhuang
marked this pull request as ready for review
September 3, 2026 08:57
os-zhuang
enabled auto-merge
September 3, 2026 08:57
Uh oh!
There was an error while loading. Please reload this page.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of #14685
Part of #13597
Item 4 of decision batch 3, ruled B. From the ruling comment (5520452691), verbatim:
1. The confidence gap, discharged BEFORE any edit
The ruling made removal conditional on a reader census. Run at the merge base
5ff5f9576, over.claude/ skills/ scripts/ .github/ packages/ docs/, for four things: the published-dev start marker (Analyze every option on ... fixed axes:), the literalpublished-dev,{decision_frame}, and the internal-dev start marker. Every hit, and what it is:skills/objectstack-pm-dispatch/SKILL.md:633-662skills/objectstack-pm-dispatch/SKILL.md:623{decision_frame}line"skills/objectstack-pm-dispatch/rules/dev-template.md:99{decision_frame}line the instruction above fillsscripts/check-skill-frame-sync.mjs:136-143published-devCOPIES entryscripts/check-skill-frame-sync.mjs:714,719,723,724,727published-devidscripts/check-skill-frame-freshness.mjsCOPIES/AXIS_MAPwholesale; five fixtures spelled.claude/agents/os-dev.mdscripts/pm/dispatch-gates.mjs:15382-15397COPIESfor the tier mandatescripts/pm/check-dispatch-gates.mjs:202FRAME_MODULEwatch-hint constantscripts/check-skills-token-ratchet.mjs:334,345,359scripts/pm/check-skill-line-ratchet.mjs:455No test, generator, workflow, doc or fixture quotes the block by fingerprint. The gap resolves in favour of removal: the placeholder path and the two frame gates are the whole reader set, and both gates read the COPIES table rather than the prose.
The same census over the internal-dev start marker found exactly one non-gate hit — the block itself at
.claude/agents/os-dev.md:400.2. What changed, site by site
skills/objectstack-pm-dispatch/SKILL.md— the embedded dev template loses the frame copy: the declaring sentence through the binding sentence (old:633-662), which is precisely the span the gate'sstartandbindinganchors delimit.The stop trigger above it (old
:627-632, "When to STOP instead of coding …") stays, and that is a judgement call worth naming. It is not part of the frame — the gate's anchors do not reach it — and it is the escalation TRIGGER rather than the analysis.rules/dev-template.mdcarries it nowhere else (grepped: the template mentionsneeds_decisiononly at:34inside the contract-first rule and at:112in the report schema), so deleting it with the frame would have removed the dev's instruction to stop at all, which the ruling did not ask for.The paste instruction now names the surviving copy by its heading:
.claude/agents/os-dev.md— the frame block (old:400-421, declaring sentence through the binding sentence, which ran into the same paragraph as the unrelatedblockedrule) collapses to one sentence. Theblockedrule is preserved byte-for-byte. Neither:232nor:303is touched (PR #14808 holds them); the diff is one hunk at:400.Three judgement calls in that one sentence:
rules/dev-template.md, because that template is the PUBLISHED skill's artifact. The internal PM composes dispatch prompts per.claude/skills/pm-dispatch/references/dispatch-runbook.mdand has no{decision_frame}slot (grep: the token exists only underskills/objectstack-pm-dispatch/). The published slot is named parenthetically, as the brief asked, without claiming the internal lane uses it. See finding F5.os-dev.md's own doctrine is that standard clauses live in that file and prompt silence is normal — so moving the frame OUT of it opens a hole that a silent prompt would otherwise walk through.fixed axes,固定评估轴,条评估轴and anyWORD-axis decision frameform. Those areFINGERPRINTSin the sync gate's anti-dormancy scan, whose population is per file:os-dev.mdstops being a declared file with this change, so any of those phrasings would have turned the gate red. Verified after the edit —grep -nPfor all four patterns overos-dev.mdreturns nothing.scripts/check-skill-frame-sync.mjs—COPIES4 → 2.Every spelled count is now derived: the header ("FOUR hand-written copies across three files"), the
COPIESdocblock, theaxisEntryStartsdocblock, the freshness-gate cross-reference, and the count-disagreement message (the four copies do not agreebecomesthe ${results.length} copies do not agree).report()and the success line already derived theirs fromCOPIES.length.The same-file overlap guard is kept even though it is vacuous today — no two copies share a file any more. Re-declaring such a pair is one edit away, and that guard is what would make such a pair prove it compares two different sections rather than the same one twice.
scripts/check-skill-frame-freshness.mjs— it importedCOPIESbut its self-test SPELLED.claude/agents/os-dev.mdin five fixtures (cases 1, 2, 6, 7 and the twowithUnreadableCopycalls in 8 and 9). Those broke on the reduction, exactly as that file's own #8024 rule predicts. They now deriveSAMPLE_COPY_ID/SAMPLE_FRAME_FILEfromCOPIES[0], so the next change to the table re-points the fixtures instead of stranding them.3. The anti-dormancy scan still refuses a returning copy — measured, both directions
Committed first, then mutated, with an
EXIT INT TERMtrap restoring absolute paths; each mutation proved on disk by counting the injected marker before reading any verdict; each restore proved bygit diff HEADempty AND by comparinggit hash-objectagainst the path's HEAD blob.Direction A — the removed text returns to the now-undeclared
.claude/agents/os-dev.md. REFUSED. Marker occurrences after mutation: 1 (was 0).node scripts/check-skill-frame-sync.mjsexit 1:Restore:
git diff HEADempty; worktree blob968d4fd10a653f013635a8a48bdee25a25efc648equals the HEAD blob.Direction B — the same text returns to the still-declared
skills/objectstack-pm-dispatch/SKILL.md. NOT REFUSED (exit 0). This is a real, measured limit and it is not new: the scan skips any file present indeclaredFiles, so a second undeclared copy inside an already-declared file has always been invisible to it. Restore proved the same way (blob3cbbed0f501736a0db3a5ecdd9e6cda43d44b434).What that limit means for this PR, stated rather than implied: what keeps a second published copy from being written is the paste instruction naming the surviving section by heading, plus review — not the scan. Closing it mechanically needs a per-occurrence criterion the gate does not have (the published file legitimately carries two more
fixed axesfingerprints, at:534and in the:539heading). A comment next to the new self-test case says exactly this, so the next reader does not mistake direction B for a bug introduced here.A self-test case pins direction A against the REAL path this change undeclared, rather than a synthetic one.
4. Counts (merge base
5ff5f9576→ head)skills/objectstack-pm-dispatch/SKILL.md.claude/agents/os-dev.mdscripts/check-skill-frame-sync.mjsscripts/check-skill-frame-freshness.mjsWhole published package
skills/objectstack-pm-dispatch/**(all markdown): 11540 → 11063 tokens, −477.Two ratcheted files, both shrink; no ceiling row is edited, in either ratchet. The measured token saving is −477, not the −630 the card estimated — the difference is the stop trigger, kept for the reason in section 2. The script growth (+50 lines across two gates) is entirely the derivation constants, the new regression case and the comments recording the measured limit; neither script is under a ratchet.
5. Gates
Union derived at the final head with
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands:Every exit code captured by redirect before any pipe.
All 32 re-run at the final head
ae616fbc91, after the last commit. 31 green, 1 NOT MEASURED, 0 red. The load-bearing ones, quoted from their own verdict lines:pnpm check:skill-frame-sync✓ check-skill-frame-sync self-test: 13 cases pass, plus 5 dispatch-gates declaration cases./✓ check-skill-frame-sync: 2 copies of the decision frame are structurally isomorphic across 2 filesnode scripts/check-skill-frame-freshness.mjs✓ check-skill-frame-freshness: the decision frame in this tree is current with origin/main (fetched just now).(12 self-test cases pass)node scripts/check-skills-token-ratchet.mjs✓ check-skills-token-ratchet: skills/objectstack-pm-dispatch/SKILL.md is 9225 tokens (ceiling 9708; headroom 483).pnpm check:pm-skill-ratchet✓ check-skill-line-ratchet: .claude/agents/os-dev.md is 450 lines (ceiling 469; headroom 19).node scripts/check-self-test-wired.mjs✓ check-self-test-wired: every one of the 164 script(s) CI runs that ship a --self-test has that self-test run by CI.pnpm check:pm-dispatch-gates✓ dispatch-gates self-test: 1241 cases pass.pnpm --filter @objectstack/spec run check:skill-docs✓ skills/README.md/✓ content/docs/ai/skills-reference.mdx— nothing to regeneratepnpm check:ratchet-remedy-authoritypnpm check:pm-skill-id-lintpnpm check:nul-bytesgrep -naPfor control bytes over both edited markdown files: no match)pnpm check:doc-authoring,check:role-word,check:agent-model-declared,check:agent-test-spelling,check:corpus-claim-drift,check:skill-compatibility,check:skill-identifier-liveness,check:watch-hint-literal,check:entry-guard,check:parse-guard,check:cli-command-ids,check:bash32-floor,check:pnpm-filter-targets,check:pm-governed-merges,check:cross-package-test-inputsnode scripts/check-ci-filter-parity.mjs,check-cross-package-test-inputs.mjs,check-shard-attestation.mjs,check-whole-set-label-write.mjs,scripts/pm/bare-root-worklist.mjs --self-testpnpm --filter @objectstack/lint run check:doc-formula-expressions@objectstack/formulathen@objectstack/lintunbuilt). Prerequisite supplied via the shared verify lock (turbo run build --filter=@objectstack/formula, then--filter=@objectstack/lint) and re-run to a real verdict:✓ check:doc-formula-expressions: 22 record-scoped formula example(s) across 426 files / 1365 TS blocks judged cleannode scripts/check-test-completeness.mjsOne red was found and fixed rather than reported: on the first pass
check:pm-skill-ratchetsaid✗ check-skill-line-ratchet: .claude/agents/os-dev.md has 1 line(s) over the 120-byte budget: L400 (121B). The ratchet prices a per-line byte budget as well as a line ceiling. The replacement sentence was rewrapped at legal CJK break points, content byte-identical, and the whole union was re-derived and re-run at the resulting head — which is the head every number above comes from.NOT MEASURED, with reasons:
node scripts/check-test-completeness.mjs— exit 3, its own text: "PREREQUISITE NOT MET — this gate grades a savedturbo run testlog, and no log was named … the local reading for this gate is NOT MEASURED. ⛔ It is not a red." CI tees the log and passes it; unreachable there.skip-changeset—.claude/**,skills/**andscripts/**publish nothing from any package.6. Out-of-scope findings — NOT fixed here, for the seat to triage
Each is a claim that this change makes false, in a file this flight does not hold.
scripts/pm/dispatch-gates.mjs:8090—why: '… the dev-agent definition is protocol semantics — every dispatched dev runs under it, and it carries an enforced copy of the decision frame'. The last clause is now false; the mandate itself still holds on the first clause, and the self-test still passes (it asserts the glob exists, not the reason).scripts/pm/dispatch-gates.mjs:8095—why: '… the published PM skill carries two enforced copies of the decision frame (check:skill-frame-sync COPIES) …'. One copy now.scripts/pm/check-skill-line-ratchet.mjs:455—// This file is one of the four check:skill-frame-sync COPIES, so a rule that …, in theos-dev.mdceiling ledger. That file is no longer a COPIES entry, which also weakens the stated reason for the +3 raise recorded there. Item 5 holds this file.scripts/check-skills-token-ratchet.mjs:334, 345, 359— three ledger comments stating the published SKILL.md "carries TWO enforced frame copies" and that "the two gate-pinned copies of the decision frame stay in this file". Historically true; now stale as a present-tense claim.{decision_frame}slot.os-dev.mdnow expects the frame to arrive in the dispatch prompt, but.claude/skills/pm-dispatch/references/dispatch-runbook.md(派发词构造细则) does not tell the internal PM to paste it, and.claude/skills/pm-dispatch/SKILL.mdis explicitly outside this flight. Until that lands, the internal dev's only protection is the stop-and-ask clause added in section 2. This is the one finding with live consequences.os-dev.mdblock carried a verbatim maintainer quotation — the 2026-09-01 axis-weighting ruling — so this PR deletes a quotation site that item 2's quotation-policy flight may have surveyed. The ruling text survives at.claude/skills/pm-dispatch/SKILL.md:904and.claude/skills/pm-dispatch/references/decision-analysis.md:50, and the RULE itself survives in both published and internal PM copies of the frame.7. Posture
Draft, governed surface. No reviewers requested, no ready flip, no enqueue, no auto-merge. Labels
skip-changesetandneeds:contract-review.🤖 Generated with Claude Code
https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1
Generated by Claude Code