fix(pm): the claim HANDOVER protocol — one comment, four items, provenance instead of a liveness test; the reader accepts it and C9 keeps one red - #19502
Merged
Conversation
…ems, provenance instead of a liveness test SKILL.md: the dead-claim reclaim heuristics (>24h suspicion, PR search, closes-list, mtime threshold, WIP commit + push before reclaim) are replaced by the handover-in-one-comment rule; identity and early-push lines aligned with the reader. core-rules.md twins follow. os-dev.md: every compilable step is pushed — the remote branch is the only thing a handover can pick up. Claude-Session: https://claude.ai/code/session_017ETYWqMQD4qMtZzAGovWNi Co-authored-by: Claude <noreply@anthropic.com>
… keeps one red `claimRetractions` gains the HANDOVER arm: a `Release:` by a DIFFERENT login retracts an earlier claim when, and only when, its line names that claim's comment id AND session id and the comment carries the three provenance fields of SKILL.md's 出处三件 line (谁的指令 / 原话 / 在哪说), each non-empty — fail closed on any missing piece, and no liveness test. Same-login retractions unchanged. C9 keeps exactly one red (a cross-login `Claim:` with no `Release:` at all for the earlier claim); its remedy is SKILL.md's handover sentence verbatim, and a refused cross-login release is listed with its reason. Self-tests pin both sides (1075 → 1097 cases). Claude-Session: https://claude.ai/code/session_017ETYWqMQD4qMtZzAGovWNi Co-authored-by: Claude <noreply@anthropic.com>
…aim-handover-protocol
…f SKILL.md A self-test that reads SKILL.md makes check:pm-clause2-carriers a derived family of that file (check:pm-dispatch-gates' governed-read census reds on the unclassified read). The byte identity with SKILL.md is the twin rule's, checked at review; the self-test keeps the shape pin. Claude-Session: https://claude.ai/code/session_017ETYWqMQD4qMtZzAGovWNi Co-authored-by: Claude <noreply@anthropic.com>
…im's +120 line budget No behaviour change: the same accept shape, the same refusals, the same printed rule and remedy; docblocks and fixtures tightened (1091 self-test cases, 1075 before). Claude-Session: https://claude.ai/code/session_017ETYWqMQD4qMtZzAGovWNi Co-authored-by: Claude <noreply@anthropic.com>
Collaborator
Author
Contract reviewServed-tier: ① Derived judgments
② Semver levelNone — nothing published; ③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
os-steve
marked this pull request as ready for review
September 21, 2026 04:23
os-steve
enabled auto-merge
September 21, 2026 04:23
This was referenced Sep 21, 2026
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 free
to 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.
Fixes #19240
Clause-②: yes
Clause-②: yes— the claim reader's accept set widens (a cross-loginRelease:carrying provenance now retracts) and C9's judged set narrows to a bare cross-loginClaim:; a.claude/**surface ⇒ Tier S, the seat lands it on its## Contract reviewPASS +--pair0. This PR stays draft.What lands — ruling 5754797404, shape A, executed as ruled
The claim HANDOVER protocol. A card whose claimant is unreachable (token exhausted, session ended, identity retired) is taken over by a new session in ONE comment, and the claim reader accepts that comment — no liveness heuristic anywhere: the human's word, copied with provenance, is the permission.
scripts/pm/check-clause2-carriers.mjsclaimRetractionsgains the HANDOVER arm;CLAIM_RETRACTION_RULE,CLAIM_HANDOVER_RULE,CLAIM_HANDOVER_REMEDYrewritten; C9 keeps one red and lists refused handover attempts; self-tests both sides (1075 → 1091 cases).claude/skills/pm-dispatch/SKILL.md.claude/skills/pm-dispatch/references/core-rules.md.claude/agents/os-dev.mdscripts/pm/check-half-states.mjsgrep -n 'author !== '→ 0 hits; itsRelease:readers (H47latestMarkedComment/releaseAnswersClaim) compare comment ORDER, never authors, so it carries no copy of the retraction rule and imports nothing from the clause-② readerBase
32b5831,origin/mainmerged once atd00692f(PR #19462 had not landed at 2026-09-21T04:1xZ — SKILL.md ceiling stays 813, no region overlap). Every line ≤ 120 bytes;check:pm-skill-ratchet,check:pm-skill-id-lint,check:pm-governed-prose,check:agent-model-declared,check:nul-bytesall exit 0 on the edited files.1. The reader
The HANDOVER arm of
claimRetractions(the one accept-set widening)A
Release:comment by a different login retracts an earlier claim when, and only when:Release:line (the first line of the body thatmarkerMatches(RELEASE_COMMENT_MARKER, line)reads — the sibling's ONE reading, applied per line, so**Release:**and`Release:`read and- Release:does not) names the retracted claim's comment id (digit-bounded) and its session id (token-bounded; the claim'sSession:line first, else the firstsession_…token in the claim body — a claim with none cannot be named, fail closed);Missing any one piece ⇒ NOT a retraction, state unchanged. ⛔ No liveness test: the earlier claimant's later comments are irrelevant (pinned). Same-login retractions: byte-for-byte the old behaviour (no id, no session, no provenance needed).
Pinned key spellings (
HANDOVER_PROVENANCE_KEYS = ['谁的指令', '原话', '在哪说'], exactly the :149 vocabulary — ⛔ no fourth key, ⛔ no synonym). A field is: the key · optional decoration (*,_, backticks) · an optional parenthetical(…)/(…)· a colon (ASCII:or fullwidth:— indistinguishable on the page, pinned equal) · the value = the rest of that line up to the next key, or, when that is blank, the blockquote (>lines) under the key. Whitespace,>, decoration and separator punctuation alone are an EMPTY value (pinned per key).The two live specimens, both replayed verbatim in the self-test:
**出处三件** — **谁的指令**:维护者,在本席(…)会话内的三个真实用户轮次。**在哪说**:本席会话聊天,在评论 5754717208(2026-09-21T02:44Z)之后、本条之前的连续三轮。**原话**(逐字,⛔ 未翻译、未润色):followed by the blockquoted turns.**出处三件**——/**谁的指令**:维护者(本仓 maintainer,…)。/**在哪说**:本会话聊天内,…。/**原话**(逐字,⛔ 未翻译、未润色):followed by the blockquoted turns.C9 keeps exactly one red
claimHandoversis unchanged in its walk: it reads the LIVE claims through the sameclaimRetractionsmap, so a handover comment (① provenanceRelease:naming the holder's claim + ③ newClaim:withBranch:/Clause-②:in the SAME comment) leaves one author holding ⇒ no row, no note, and the newClaim:is the governing claim on the--pairpath (a comment is not later than itself, so it cannot retract its own claim — pinned). The one red left: a cross-loginClaim:with NORelease:at all for the earlier claim — a real claim-jump.CROSS_AUTHOR_CLAIM_ROW_EFFECTIVE_ATstays; its gating now applies to that narrowed red only (it is read at the same place as before).Loud refusal, not silent red: a cross-login
Release:that TRIED to hand over a live claim (names its id or session id, or carries a provenance field) and did not is listed in the C9 sentence with its reason —missing 在哪说,the comment id is not on itsRelease:line,the session id is not on itsRelease:line,the claim carries no session id to name. A bareRelease:by another login (a seat releasing its own claim) is not an attempt and is not listed — the first draft listed those and the--pair 19373row named os-steve's own two releases as "refused handovers" of os-bill's claim, which was noise; narrowed.The remedy sentence (
CLAIM_HANDOVER_REMEDY) prescribes the four-item handover comment and prints SKILL.md's handover sentence verbatim (CLAIM_HANDOVER_SENTENCE_LINES= the five 认领 bullets, byte for byte), citing the 出处三件 line as its source. The old remedy words 「the HOLDER postsRelease:… the TAKER posts nothing until then … ⛔ never aRelease:on the holder's behalf」 are gone; ② (assignee swap) and ④ (the sha record) are stated as the seat's acts, unread by the reader.Self-tests (beside the existing retraction and C9 cases, ⛔ not at
selfTest()'s tail; floor unchanged)Retraction battery: ⭐ a cross-login provenance
Release:naming id + session is accepted — statedeclared, the handover's ownClaim:governs, the record says "a DIFFERENT login … HANDOVER" · ⛔ missing any one field, or a key with an empty value ⇒ refused, one case per key each way, the missing key named · ⛔ id without session / session without id / both in prose under a bareRelease:line / the three fields with noRelease:line at all ⇒ refused · ⭐ NO liveness test: the earlier claimant commenting after the handover changes nothing · ⭐ both live specimens' spellings read, and a fullwidth colon reads as the ASCII one · ⛔ same-loginRelease:still needs nothing (arm untouched); a claim with no session id cannot be handed over · ⛔ item ④ absent still retracts (the seat's act, not the reader's gate) · the printed rule names both arms, the three keys, the source line and the absent liveness test.C9 battery: ⭐ the handover comment clears C9 (no row, no note) · the handover's
Claim:is the governing claim on--pair(branch, declaration) and is not self-retracted · ⛔ the same comment missing any one field ⇒ still C9 JUDGED, the row names the refused release and the missing key · ⛔ the ONE red kept: a cross-loginClaim:with noRelease:at all · ⛔ a handover naming only one of two live claims leaves the other standing · the remedy is SKILL.md's handover sentence verbatim, with the 出处三件 source line and 让先到者 for a yield · each sentence line is one SKILL.md bullet by shape (≤ 120 bytes, no bullet, no issue id).2. Before / after — every changed instruction line
.claude/skills/pm-dispatch/SKILL.md- dev 自己死了不等于维护者中止:子代理消失是正常死法,走死认领回收。- dev 自己死了不等于维护者中止:子代理消失是正常死法,走接管(见认领节)。- 共享身份下 assignee 只答有无认领;身份只认正文 session ID,⛔ 不认作者字段。- 共享身份下 assignee 只答有无认领;身份只认正文 session ID,⛔ 不认作者字段,接管同此。- 释放是显式动作:让卡离手者同笔清 assignee +Release:行(会话/因/去向);下一任重新认领。RELEASE_ACT_RULEincheck-half-states.mjs(outside this claim's surface); the handover reuses the act's two halves (② assignee + ①Release:line, by the taker), stated in the new bullets- dev 侧早推分支,远程分支是在飞工作最硬的证据。- dev 每个可编译小步即 push:容器随会话回收,未 push 的树救不回,可交接的只有远程分支。- 认领人不可达(token 耗尽/会话结束/身份退役)⇒ 接管:一条评论四件齐,⛔ 不判死活。/- ① 跨账号Release:点名被撤认领的 id 与 session ID,带出处三件(谁的指令/原话/在哪说)。/- ② assignee 同笔换人(--unassign 旧 --assign 新);③ 新Claim::新 session、续用分支与远程 sha。/- ④ 交接记录:旧分支最后已 push 的 sha + 一句状态;读者只验①③形状,缺一件即非撤销。/- C9 只剩一种红:无任何Release:的跨账号Claim:(真抢卡);线程上每条活认领都要点名。- 误伤活席位 ⇒ 令其追加式更正,落 PR 正文不落分支历史。.claude/skills/pm-dispatch/references/core-rules.md- 更早的他会话认领即让行并交出已诊断的一切;认领逾一天且无合并证据即疑死。- 更早的他会话认领即让行并交出已诊断的一切;认领人不可达即接管,⛔ 不判死活。- dev 自死不等于维护者中止,需显式信号;回收前先救工作树,有提交的活分支 ⛔ 永不回收。- dev 自死不等于维护者中止,需显式信号;接管一条评论四件齐,只救已 push 的分支。.claude/agents/os-dev.md- 有可展示内容即 commit、push 并开 draft PR,不等验证结束;验证结果到达即写进报告。- 每个可编译小步即 commit + push;有可展示内容即开 draft PR;接管只认远程分支最后 sha。The dropped tail 「验证结果到达即写进报告」 survives at os-dev.md :95 (「未读到的判决写 NOT MEASURED」) and :311 (「报告在本地验证走完时交付」).
3. SKILL.md deletion list — each retired line's surviving home
死认领回收:认领 >~24h ⇒ 疑死;判死主腿 = 搜引用本卡的 PR、读其 merged/merged_at。⛔ 判死不读 closes-list;承诺分支缺席与提交扫描失效只能支持判死、永不单独确立。零引用 PR ⇒ 停下发问,⛔ 不判什么都没落地。回收前先救工作树:向任何派发 worktree 提交前先过存活/所有权检查。或对树最新 mtime 过明确年龄阈值;⛔ 不凭 GitHub 侧静默动手。过栏后,派发 worktree 的未提交改动先 WIP commit 到派发分支并 push,sha 记进回收评论。WIP commit 标 INCOMPLETE AND UNREVIEWED;续派者 diff 它,⛔ 不无审续建。WIP 信息只写观察到的(脏路径/行数/sha),⛔ 不写席位行为的现在时断言。再评论询问,静默一窗后释放回队(Release:行载因);有带提交活分支的认领永不回收。Release:line, now with provenance instead of a silence window) + :497 (every live claim named) — 「有带提交活分支的认领永不回收」 is retired: a pushed branch is precisely what the handover continues (:495 ③)4. PM mechanism assumptions — verified, one refuted
5e7d83c=32b5831(no diff on the surface between them):CLAIM_RETRACTION_RULE:1731 stated "⛔ never a DIFFERENT author's line",claimRetractionsskipped every candidate whose author differs (:1778candidate.author === null || candidate.author !== claim.author), andclaimHandoversjudged cross-login claims afterCROSS_AUTHOR_CLAIM_ROW_EFFECTIVE_AT(:2073,2026-09-19T03:45Z).PM_SWEEP_REPO=objectstack-ai/objectstack):--pair 19373→ exit 4,✗ C9 — card #17518 (delivering open PR #19373) — 2 authors hold LIVE claim comments …os-bill's 5646971772 at 2026-09-12T15:54:12Z is the claim that stood;os-litant's 5749581295 at 2026-09-20T11:43:41Z took the card fromos-bill(dated AFTER the effective instant 2026-09-19T03:45Z — JUDGED);--pair 19335→ exit 4,✗ C9 — card #18670 (delivering open PR #19335) — 3 authors …os-litant's 5717305863 … stood;os-steve's 5736537462 … (listed, informational);os-bill's 5749165780 at 2026-09-20T10:14:08Z took the card fromos-steve(… JUDGED). After the change both STILL exit 4 (same rows, the remedy now printing the four-item comment) — as predicted, until the seats post the handover comments below.HANDOVER_PROVENANCE_SOURCE).node scripts/pm/check-governed-merges.mjs --pr Nis run once the PR number exists; the result is in the report. The PR stays draft.label-write --clear-assignees --assign NEWis refused by the tool:--clear-assignees cannot be combined with --assign/--unassign(scripts/pm/label-write.mjs:417–:419). The one-write assignee swap isnode scripts/pm/label-write.mjs --repo objectstack-ai/objectstack --issue N --unassign OLD_LOGIN --assign NEW_LOGIN(computeAssigneeTarget: target = current − unassign + assign, one write, read back). SKILL.md :495 and the handover comments below use that spelling.5. The handover comments the seats post (verbatim — ⛔ not posted by this PR, ⛔ nothing written on #17518 / #18670 / PR #19373 / PR #19335 here)
Both were simulated offline against the live threads (the REST rows of each card plus the drafted comment appended): C9 state
null(clear), pool = the handover comment, governing branch = the continued branch, declarationdeclared/yes, C8 = 0; controls — the same comment without 在哪说 ⇒ C9 judgedtrue; the same comment with the session id blanked on theRelease:line ⇒ C9 judgedtrue. Placeholders in CAPITALS are the poster's to fill (its own session id / login, the UTC stamp). The 原话 / 在哪说 values copy the maintainer's words that adopted this protocol for exactly these two PRs (5754717208 § the maintainer's turns; 5754797404 「同意」, which names PR #19373 and PR #19335 as the two the ruling unblocks); a fresher instruction naming the card directly is a better value, if the seat has one.#17518 (PR #19373) — posted by the
domain:spec#1seat (os-litant, the taker already holding claim 5749581295)Why the seat's own 5749581295 is named too: the reader would otherwise carry TWO live
Claim:comments byos-litant(C8). Named on the sameRelease:line it is retracted by the same-login arm, and the freshClaim:in this comment is the only one standing. If the posting session differs fromsession_01LvwGppdonww4zGLWZo5rho, theSession:line carries the new one.#18670 (PR #19335) — posted by the live
domain:specseat (POSTER_LOGIN / POSTER_SESSION_ID; the taker of record,session_01JbZnqu8bt6YqfJsr9vaFb3, was retired at 2026-09-20T23:34Z)Why all three claims are named: C9 walks every LIVE claim; naming only 5749165780 would leave
os-litant→os-steve→ NEW as two hand-overs, the last dated after the instant — still red. The row prints exactly the ids to name (:497 「线程上每条活认领都要点名」).6. Four-axis analysis
「No liveness test」 (ruling; 5754717208 §4's 「点名的是活认领 ⇒ 拒」 not kept)
.refine()carries the rule — an author validating againstpackages/spec/json-schema/**gets a green for metadata the runtime refuses #18670 / PR feat(spec)!: publish the $-prefix key ban the normalized filter enforces, and make the ratchet able to see it #19335; objectui#9370) are all cases where the human already knew the claimant was gone and the machine could not: a subagent session that ended 2026-09-12, a seat session retired at 23:34Z, a retired identity. In every one the holder's silence was total, so a liveness heuristic (>24h, later comments, PR search, mtime) would have said "dead" only by luck of thresholds, and a holder that posts one late comment would have flipped a correct takeover into a refusal. The maintainer's words: 「这种情况通常都是人类口头交代的」 — the decision is already taken by a human; the reader's job is to verify the copy, not to re-decide.「C9 keeps one red」 (a bare cross-login
Claim:with noRelease:at all)__proto__key from its parse OUTPUT while reporting success — ObjectSchema accepts the document and hands back a different one #17852's two seats eight hours apart, spec: the evaluated-slot rule of #15430 reaches only the flow-node ledger — every otherExpressionInputSchemaslot an engine evaluates (formulaexpression, validation / hook / sharingcondition,visibleWhen…) still accepts anast-only or blank-sourceenvelope #15811's silent assignee move); those are exactly the shape left red. The two finished PRs it blocked were handovers, not jumps — they had no channel to say so; now they have one comment.Claim:silently govern (the pre-[finding] the #18828 triage's p1 escalation condition is MET — 12 open cards across both boards carry LIVEClaim:comments from two or more DIFFERENT authors with no retraction between them, and every reader prints the newer one green as a supersession; a cross-session ownership transfer has no reader #18862 SUPERSEDED exit-0 reading); keeping the red but printing the four-item comment as the remedy makes the correct act the shortest path. A refused attempt is listed with its reason instead of a bare "2 authors hold live claims".7. Tests and gates (head
7a66ffe; every exit captured before any pipe)node scripts/pm/check-clause2-carriers.mjs --self-test→ exit 0, 1091 cases pass (1075 atorigin/main, run from a temp copy in the same tree; the roster floor unchanged; both new case groups sit inside their existing batteries).--pair 19373/--pair 19335→ exit 4 before AND after (rows quoted in §4.2); after the change each row ends with the four-item remedy (grep -c 认领人不可达= 1 per log).declared/yes; controls red.node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, 48 commands, derived from the tree at693afd7after theorigin/mainmerge and re-run at7a66ffe): all 48 of 48 commands exit 0 (run 2026-09-21T03:56Z–04:15Z, sequential, each exit captured before any pipe; the list reconciled with--ran); the slowest,pnpm check:pm-dispatch-gates, ran its full 1883-case battery green at this head.pnpm --filter @objectstack/lint run check:doc-formula-expressionsfirst answered exit 3 (PREREQUISITE NOT MET:@objectstack/formula/@objectstack/lintnot built — NOT a finding); afterpnpm exec turbo run build --filter=@objectstack/formula --filter=@objectstack/lintunderos-verify-lock.sh(VERDICT command-exit 0, 203 s) it answers exit 0.pnpm check:pm-dispatch-gates(845 s on this box) red once on an EARLIER draft: its governed-read census found areadFileSyncof SKILL.md in this reader's self-test (my "same words" pin). Removed — see Deviations — and re-run green at the final head.Deviations (declared)
check:pm-clause2-carriersa derived family of SKILL.md and needs aGOVERNED_READ_FLOORrow inscripts/pm/dispatch-gates.mjs(outside this claim's surface; a gate-derivation change). Kept instead:CLAIM_HANDOVER_SENTENCE_LINES(the remedy prints the five lines verbatim) + the twin rule at review + a shape pin (each line ≤ 120 bytes, no bullet, no issue id). Open question for the seat: register the read so a SKILL.md edit that breaks the sentence reds the reader (recommended; a two-line floor row).check-skill-line-ratchet.mjsis outside the surface; headroom 4 is reported, the seat lowers it if wanted.--unassign OLD --assign NEW), see §4.5.Acceptance notes (off-path; noted, not filed — ⛔ no card filed by this dev)
scripts/pm/check-half-states.mjsH47 leg (b) sentence still quotes 「释放回队(Release:行载因)」 as "the dead-claim route" — that SKILL.md line is retired here, so the quotation is stale prose in a remedy sentence (a doc nit, not a defect; carrier: thedomain:skillsseat on its next half-states touch).references/platform-readings.md:391 「处置 = 死认领回收加 worktree 抢救,⛔ 不重核前提、不升级」 names the retired route (a host-signal disposition line; outside this claim's surface — the seat's twin-rule follow-up, one line: 「处置 = 接管(认领节),⛔ 不重核前提、不升级」).check-clause2-carriers.mjs's C9 docblock still carries the [finding] the #18828 triage's p1 escalation condition is MET — 12 open cards across both boards carry LIVEClaim:comments from two or more DIFFERENT authors with no retraction between them, and every reader prints the newer one green as a supersession; a cross-session ownership transfer has no reader #18862 ruling history verbatim (「the holder postsRelease:; the taker posts nothing until then」 as the ruling's quoted words) — kept as history, the new paragraph below it states the change; no action..claude/skills/pm-dispatch/SKILL.md:272 「维护者强制接管令 … ⛔ 不取在飞卡,由原认领者跟完」 is the seat-level forced takeover (a blanket order) and is not contradicted by a per-card handover on a named instruction; left as is.维护者速读(草稿)
改了什么:把「死认领回收」换成「接管协议」。一个 agent 做到一半没 token 了,新会话在一条评论里接管:① 跨账号
Release:点名旧认领的评论 id 与 session ID,并带出处三件(谁的指令 / 原话 / 在哪说);② assignee 同笔换人;③ 新Claim:(续用远程分支与 sha);④ 一句交接状态。认领读者(check-clause2-carriers.mjs)按形状接受①③,不再判死活;C9 只剩「没有任何Release:的跨账号抢卡」一种红。SKILL.md 删掉九行判死启发式,换成五行接管规则;os-dev.md 把「早推分支」提为硬要求(每个可编译小步即 push)。为什么改:两张已复核完毕的成品 PR(#19373、#19335)今天落不了地,只因为旧认领人已经不在、没人能替它写
Release:;而「代执行他人指令要带出处三件」这条规矩早就在 SKILL.md 里,只是读者不读。您的原话:「这种情况通常都是人类口头交代的……我们系统开发了太多无用的门禁」。风险与代价(含回滚):风险是一条编造出处的接管评论会被读者接受——但出处三件留在卡上可审,误伤活席位按既有规则追加更正。代价是读者多一条判形状的分支(+120 行,含自测)。回滚 = revert 本 PR,一次 revert 即回到判死启发式与旧 C9。
席位意见:(席位填写)
你要做的:本 PR 是受管面(
.claude/**),由席位达档复核后落地,不需要您动手;落地后 spec 席按正文第 5 节的两条评论接管 #17518 与 #18670,两张 PR 即可入队。若您希望读者对「SKILL.md 与读者同句」做机械钉死(而非复核时人工核对),点一下头,席位在dispatch-gates.mjs登记一条 governed read 即可。Generated by Claude Code