docs(pm): runbook step that pastes the decision frame into every dispatch prompt - #15057
Conversation
…atch prompt The internal dispatch runbook constructed the dispatch prompt without ever naming the four-axis decision frame. After the batch-3 item-4 ruling (option B, 2026-09-03) the dev definition expects the frame to arrive in the prompt from the PM's own copy and stops and asks when it does not, so the runbook was an instruction surface promising a behaviour it no longer produced -- one round-trip per dispatch until the step is written down. Adds one bullet to the clause list of the dispatch-prompt construction section, beside the "two readings handed down at dispatch" clause it belongs with: the internal-pm frame section is taken fresh and pasted verbatim per card, from `.claude/skills/pm-dispatch/SKILL.md`, delimited by its two anchor sentences; never retyped; the brief records the copy's commit and md5; the os-dev definition stops and asks when the frame is missing. The frame text itself is NOT copied -- the bullet points at the existing copy by its anchors, so `check:skill-frame-sync` keeps watching exactly two copies. Line ratchet: 276 -> 280 of the 280 ceiling; no ceiling edit, no re-wrap, no cross-file move needed. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019RfFHiRCSs3JXLK4cwcfox
|
Why this PR keeps falling out of the merge queue — skills seat (session The approvals are not the blocker: the governed guard passed on every queue build. What reds is Fix in flight (patch round to the card's dev, same branch): merge Generated by Claude Code |
…nbook-frame-paste-step
…hrases `check-skill-frame-sync` stopped skipping `references/` subtrees, so its anti-dormancy scan now reads this file. The bullet's paste anchors quoted the frame's opening and binding sentences verbatim, and the opening one carries a phrase the scan's FINGERPRINTS match -- so the merged tree read the runbook as an undeclared copy and the queue ejected the PR on every build. Re-anchors the paste span structurally instead of by quotation: the internal PM copy is named by file, by its enclosing section heading, and by the block's shape (bold lead sentence through the closing recommendation clause), with the dispatching seat recording that copy's commit and md5 in the brief. The instruction is unchanged -- paste the whole block verbatim, never retype, never excerpt, never a second in-repo copy, and the os-dev definition stops and asks when a brief arrives without it. Verified against all four FINGERPRINTS and both MENTION_PATTERNS: no hit anywhere in the file. Still four lines, 108/118/119/119 bytes, so the ratchet stays at 280 of its 280 ceiling. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019RfFHiRCSs3JXLK4cwcfox
|
Patch verified at the merged head — one fresh approval lands it — skills seat (session Head The existing approvals are pinned to the old head Generated by Claude Code |
Fixes #14869
The internal dispatch runbook constructed the dispatch prompt without ever naming the four-axis decision frame. After the batch-3 item-4 ruling (option B, recorded 2026-09-03 on #14685, comment 5520452691) the frame keeps two copies, both PM-side, and the dev definition expects it to arrive in the prompt from the PM's own copy —
.claude/agents/os-dev.md:398-401, verbatim and untranslated:That clause is unchanged by this PR — it is quoted here only because the new bullet is the PM-side half it was waiting for. Until now the runbook was an instruction surface promising a behaviour it no longer produced: the failure is loud rather than silent, but it costs one round-trip per dispatch.
The change — one bullet, one file, +4 lines, nothing else
.claude/skills/pm-dispatch/references/dispatch-runbook.md, § 派发词构造细则, inserted into the clause list immediately after the 两份读数 clause (the other "what every brief carries at dispatch time" entry) and before 文件面两句原文.Conventions matched against the file it lands in: Chinese prose, English machine identifiers in backticks, ASCII
(/)/:/,/;(the file uses ASCII 115/115/96/158/101 times against 9/9/0/0/0 full-width), the ideographic、and。, the bare U+26D4 no-entry glyph with no variation selector (44 occurrences, 0 VS16), and the(维护者 2026-0x-xx 裁)date-citation form its neighbour at:246uses. No issue numbers —check:pm-skill-id-lintscans this directory with/#[0-9]{3,}/.The bullet's current text, and the reason it was re-anchored after the first round, are in the Patch round section at the bottom.
The frame text is NOT copied — the bullet points at it
Anchor measurement, re-taken on this branch rather than trusted from the brief:
.claude/skills/pm-dispatch/SKILL.md:868-885, inside its## 升级与决策sectionsed -n 868,885p .claude/skills/pm-dispatch/SKILL.md | md5sum=b9a0de17880c39e412055e656a953222(matches the brief's number; re-measured again on the merged tree, unchanged)That blind spot is larger than this PR and out of scope here — filed separately and unassigned as #15056 (32 of 72 hand-written markdown files under the two scan roots were invisible, including all 12 published skill reference pages). PR #15060 has since acted on it, which is what forced this PR's patch round — see the bottom section.
Line ratchet — paid from existing headroom, no ceiling edit
origin/maind5b330dc(git show origin/main:… | wc -l)Gate's own verdict line:
✓ check-skill-line-ratchet: .claude/skills/pm-dispatch/references/dispatch-runbook.md is 280 lines (ceiling 280; headroom 0).No ceiling was edited, no other bullet was re-wrapped, and no cross-file move was declared or needed — the four lines fit the headroom exactly, and the patch round kept the bullet at exactly four lines.
Governed surface
.claude/**— governed, so a human merge is the review record. No seat flips it ready, enqueues it, or arms auto-merge.skip-changesetapplies: nothing here publishes from any released package.Patch round — re-anchored after the scanner's population widened
Why the queue kept ejecting an approved PR.
check-skill-frame-syncreds on the merged tree, never on this PR's own build. PR #15060 (merged 2026-09-03T20:09Z, after headcce4a1ebwas built) removedreferences/from that scanner's skip set — acting on #15056, the finding this PR's first round filed. The scan therefore began readingdispatch-runbook.md, and the bullet's paste anchor quoted the frame's opening sentence, which carries a phrase in the scanner's FINGERPRINTS. Result:dispatch-runbook.md ... looks like ANOTHER copy of the decision frame (matched /固定评估轴/) but is not declared in COPIES. The PR's own checks were green because they ran before #15060 landed.Merged
origin/mainc351a84dinto the branch as merge commitba86720d(no rebase, no force-push); the fix isea53bae5.The re-anchoring. The instruction is unchanged — paste the whole block verbatim from the internal PM copy, never retype, never a second in-repo copy, record the copy's commit and md5 in the brief, and the os-dev definition stops and asks when a brief arrives without it. What changed is only how the paste span is identified: by quotation before, structurally now.
Before (
cce4a1eb):After (
ea53bae5):The span is now named by file, by its enclosing section heading, and by the block's shape (bold lead sentence through the closing recommendation clause) — no quoted sentence at all, so nothing to fingerprint. The md5 the brief records stays the integrity check, which is what makes a structural anchor safe: a heading that moves is caught by the digest rather than by a stale quotation. The bullet also gained the rule the widened scan now enforces repo-wide — no second in-repo copy anywhere, not merely no third copy of the whole section — and dropped its excerpt allowance (
⛔ 不摘句), because after #15060 an excerpt of the marker sentences is exactly what reds.Checked against the detector itself, not by eye. All four FINGERPRINTS (
/fixed axes/i,/固定评估轴/,/条评估轴/,/\b[A-Za-z]+-axis\s+(?:analysis|decision\s+frame|frame)/) and both MENTION_PATTERNS were run over the new bullet and over the whole file: zero hits in either.Verdicts on the merged tree, at head
ea53bae5— exit codes captured by redirect before any pipe:node scripts/check-skill-frame-sync.mjs✓ check-skill-frame-sync: 2 copies of the decision frame are structurally isomorphic across 2 files·binding sentence present in all 2; 5 count mention(s) agree; 72 markdown files scanned for undeclared copies.pnpm check:pm-skill-ratchet✓ check-skill-line-ratchet: … dispatch-runbook.md is 280 lines (ceiling 280; headroom 0).node scripts/pm/check-skill-id-lint.mjs✓ check-skill-id-lint: 23 file(s) clean (pattern /#[0-9]{3,}/g).node scripts/pm/check-governed-merges.mjs --test PATHS⛔ GOVERNED — a human merge is the review record for this PR·.claude/** ×1pnpm check:nul-bytescheck-nul-bytes: OK (scanned 8259 text file(s) … no raw ASCII control bytes).The scan population is the headline number: 72 markdown files now, against 40 before #15060 — the 32 files that finding named are being read, and the runbook is one of them, green.
Then the full re-derivation at the merged head,
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands(no paths — the script takes the change set from the merge base, reporting1 path(s) vs merge base c351a84da): 13 commands, one more than the previous round (check-closing-keyword-parity.mjs --self-testjoined). All 13 run, all exit 0, includingcheck:doc-formula-expressionsafter building its two named prerequisites (@objectstack/formula,@objectstack/lint) — that gate's exit 3 is PREREQUISITE NOT MET, which it states is not a measurement.Two corrections to the patch brief, neither affecting the outcome:
pnpm check:control-bytesdoes not exist in this tree — the control-byte gate ischeck:nul-bytes, which is in the derived list and ran green; andcheck-governed-merges.mjs --testrequires the changed paths as arguments (it refuses an empty list rather than answering "not governed"), so they were derived withgit diff --name-only $(git merge-base origin/main HEAD) HEAD.Draft/ready, auto-merge and queue membership were not touched by this round — that state belongs to the seat that set it. That is also why the push arrived when it did: a branch with a PR in the merge queue is refused by the protected-branch hook (
GH006 ... Branches that are queued for merging cannot be updated), and the only reliable way to release it is to dequeue the PR, which this seat does not do. The fix commit was therefore held locally and pushed the moment the 05:16:30Z queue entry ejected on the same frame-sync red, at 05:56:55Z — the enqueue at the old head is what the queue was still building, so no queue build was interrupted.Generated by Claude Code — session
session_019RfFHiRCSs3JXLK4cwcfox, recorded in prose because a body edit is measured to normalise the footer link below.