Skip to content

Commit a7a135d

Browse files
pm: the handover checklist names release, the decision inbox and carded obligations; two local handover checks (#23417)
Fixes #23396 Clause-②: no Implements ruling `6162084315` (letter A) on #23396 under the claim `6162205633`: the handover checklist gains its three missing steps, the filing gate gains the carrier sentence, and two local, read-only script checks land beside them. One PR, as the claim set out: `.claude/**` is in the diff, so the PR is governed whole (Tier S) and stays a draft. ## The ruling's clauses, mapped to the diff | Ruling clause | Where it landed | |:--|:--| | Every ledger obligation carries a card; a conditional one is `pm:blocked` + `Blocked-by:` (time-triggered: `Restart-when:`); the vacant body holds pointers only | `seat-post-protocol.md` step 4 (new) | | Every card this seat claimed is landed or released in one write (`Release:` + assignee cleared) and moved to its real state | `seat-post-protocol.md` step 2 (rewritten) | | Every card awaiting the maintainer carries `needs-user-decision` and nothing else | `seat-post-protocol.md` step 3 (amended) | | The step naming the pre-handover check gives the command and requires exit 0 before the vacant body | `seat-post-protocol.md` step 5 (new) | | "The next PR that touches X" is not a carrier; a follow-up a landed work item owes is filed, outside the noise clause | `filing-gate.md` line 22 (new) | | 2(a) `post-stamped.mjs --body` refuses a vacant body whose ledger holds an obligation line without `#N` | `scripts/pm/post-stamped.mjs` (pure core + 27-case battery) | | 2(b) pre-handover check: unreleased claim / decision outside the inbox | `scripts/pm/check-handover.mjs` (new, 30-case self-test) | | No twin moves | `SKILL.md` has no checklist twin (grep `收班` / `交接` / `释放`: only pointers to this file); `references/core-rules.md` does not exist | ## Charter lines, before → after `seat-post-protocol.md`, 交接收尾清单 (91 → 91 lines, every line ≤ 120 bytes): ```text before 2. 在手工件逐件处置:能收则收,收不完留守或热移交;⛔ 不为清零而等。 3. 全量枚举本车道队列、决策箱与 findings,形成接任台账。 4. 贴正文改 `⏳ vacant` + 台账:前任会话 ID、注销时间、队列快照、串行队、跨车道备忘。 5. 收班简报与审计评论存档;接任指引 = `/pm-dispatch 接手` + 先读座位贴。 6. `list_triggers` 清点本会话定时器:删入座 Routine,一次性的逐个清理或随移交物转交。 7. 移交中 PR 的定时器随 PR 走;留守保留的定时器只触点名项。 8. 本座位派出的 dev 会话由离任 PM 自己逐个 `archive_session`,归档义务 ⛔ 不可移交。 9. 在飞 dev 卡与留跑会话在贴里点名(卡号 + 派发会话 ID);跟完与归档 ⛔ 不随交接转移。 after 2. 在手工件逐件处置:本席认领的卡落地,或 `Release:` 与清 assignee 同笔释放,转成真实状态。 3. 全量枚举本车道队列、决策箱与 findings,形成接任台账;等裁决的卡只挂 `needs-user-decision`。 4. 台账义务逐条带卡号(条件型开 `pm:blocked`+`Blocked-by:`,定时型 `Restart-when:`),正文只放指针。 5. 第 2、3 步由 `node scripts/pm/check-handover.mjs --session=ID --account=LOGIN` 核,退出 0 才写空座正文。 6. 贴正文改 `⏳ vacant` + 台账:前任会话 ID、注销时间、队列快照、串行队、跨车道备忘。 7. 收班简报与审计评论存档;接任指引 = `/pm-dispatch 接手` + 先读座位贴。 8. `list_triggers` 清点本会话定时器:删入座 Routine,一次性的逐个清理或随移交物转交。 9. 在飞卡与留跑会话贴里点名(卡号+派发会话 ID);派出的会话离任 PM 自己 `archive_session`。 ``` Steps 1 and 10 are unchanged. Old 4–6 are new 6–8 verbatim. `filing-gate.md` (58 → 58 lines): ```text added after line 21 - 「搭下个碰 X 的 PR」不算 `carrier:`;已落地工作欠的后续照立卡,⛔ 不按「不是卡」压下。 before (lines 30–31) - 分诊席的存量清理仍走维护者确认的十张一组,⛔ 不因本门改批量。 - 十张一组只备下一组:当前组未经维护者确认 ⛔ 不预备再下一组、不提前展开全部批次。 after (line 31) - 分诊席的存量清理仍走维护者确认的十张一组、只备下一组,⛔ 不因本门改批量。 ``` "防噪条款" appears nowhere in the skill, so the sentence names what it means in this file: the gate's `⛔ 不是卡` exclusions. ### How the net-zero lines were paid No ceiling moved. Each freed line comes from deleted text, never a pure re-wrap: every fold below joins lines whose bytes sum to more than 120, so it could not fit without deleting bytes, and what it deletes is restated elsewhere. - **Old step 7 deleted.** "移交中 PR 的定时器随 PR 走" restates step 6's "随移交物转交"; "留守保留的定时器只触点名项" restates line 38's "残余定时器同界". - **Old steps 8 + 9 fold** (223 B → 117 B). "归档义务 ⛔ 不可移交" restates "由离任 PM 自己". "跟完 … ⛔ 不随交接转移" restates `seat-lifecycle.md:30`: replies, liveness probes and re-dispatch stay only in the dispatching session. - **Old step 2 becomes clause B.** "收不完留守或热移交" restates line 33. "⛔ 不为清零而等" restates `seat-lifecycle.md:41` ("不是在飞归零"). - **Clause C is appended to step 3**, inside its own 120-byte budget. Step 3 already enumerates the decision box. - **filing-gate.md lines 30–31 fold.** Line 31's "当前组未经维护者确认 ⛔ 不预备再下一组、不提前展开全部批次" restates "只备下一组" within a group of ten the maintainer confirms. ⚠️ Line 31 is the text a prior PR (#20108) landed. The fold keeps its rule, but a reviewer who wants that wording verbatim should say so. No other line in the file restates another. - **Bytes, stated:** `seat-post-protocol.md` 7,018 → 7,137 (+119). `filing-gate.md` 5,225 → 5,242 (+17). ## The three predicates, with the exact strings they key on **Vacant body** (`seatPostVacancy`, post-stamped): - The Current-PM section is the first `##` heading matching `CURRENT_PM_HEADING`, down to the next `##`. That pattern matches `Current PM` (any case) or `当前 PM`. - The body is vacant when the **first** seat-status token in that heading or section is `⏳` and its next word is `vacant`, emphasis skipped (`⏳ **vacant**`, `**⏳ vacant**`). The tokens are `SEAT_STATUS_TOKENS`: `🟢`, `⏳`, `⏸`. - Positional, not containment. Measured on the 22 open `pm:seat` bodies: #7623 is seated, yet its Current-PM section says "Seat 2 (#19287) ⏳ vacant" two lines under its own `🟢`. A containment test would have refused a seated post's refresh. **Ledger section and obligation line** (`vacantLedgerRefusals`, post-stamped): - **Ledger** = every line after the Current-PM section, minus fenced code, inline code, table rows, blockquotes and headings. It is not the `Ledger` heading alone: four of the six obligations the card measured on #6023 sit under `## 4. Notes`, as `Carried` lines. - **Obligation line** = a ledger line carrying a key from `LEDGER_OBLIGATION_KEYS`, read where the key means a duty: - `Carried` / `欠` only as the item's first word. - `owe(s/d/ing)` / `欠` only on a group label (a line ending `:`) or a heading. - `Carrier:` / `承接者:` only as a capitalised clause at a clause start. - `next PR|change|edit|push` / `下一个碰` anywhere. - A list member under a keyed group label or keyed heading is an obligation line too. The label itself is never judged. - **Passes** when the line carries `#N` or `owner/repo#N` outside code, or sits under a list item that does. When the line names its carrier (`Carrier:`), the card must be in that clause. - **Pointers and facts are not refused.** A pointer line `→ #N` carries its card. A fact line (`Landed: …`, `Closed this shift:`) carries no key, so it is never judged. - **Why the keys are positional:** read anywhere, `owe` and `carrier` refused five lesson bullets on #6024 (`no objection owed`, `owes a changeset`, `DUAL carrier:`), none of them a duty. - **Measured on the live corpus:** - #6023's four `Carried` lines are refused: three with carrier "the next PR touching X" / "the next checklist-author sweep", one with no carrier. - #22648 is refused on four lines whose carrier is "the next change to …" or none, and #6021 on its group "Stale comments to ride the next edit". - The other vacant bodies, every seated body, and #18883's ledger and notes read as if vacant, all pass. - **Stated limit:** the ruling's grain is the line. #6023's "B′ (an audit card) is owed …" shares a line with `#23177` and passes. Whether a cited card is still open is a live read, which this offline core does not do. **Decision pending outside the inbox** (`decisionOutsideInbox`, check-handover): - An open issue on which the departing session's latest comment authored by `objectstack-fleet[bot]` and carrying `--session` contains the `os-decision-facets` token. - The token is read by `hasDecisionFacetMarker`, a literal containment test, so the plain line and its HTML-comment wrapping both read. - No later comment classifies as a ruling under `check-prior-rulings.mjs`'s `classifyRulingComment` (`Ruling:` / `Ruling addendum` / `Ruling recorded`). - The card's states (`PM_EXCLUSIVE_STATE_LABELS`) are anything other than `needs-user-decision` alone. **Unreleased claim** (`claimHolding`, check-handover): - An open `pm:dispatched` issue whose assignees include `--account` and whose newest `Claim:` comment carries `--session`. - Hit when no `Release:` comment carrying `--session` is newer than that claim. Both markers are read through the patrol's `markerMatches`, so a bolded `**Release:**` reads. ### Two readings past the dispatch's literal spelling - **(a) is scoped by the claim, not the assignee alone.** Accounts are shared across seats, so a card under the departing account may be held by another live session. Such a card is listed, not refused, and so is a card with no claim at all. - **(b) declines a card whose ruling was recorded after the analysis.** The director writes `Ruling:` and moves the card's state, while the departing seat's analysis stays its latest comment there. Read literally, (b) would refuse every handover that followed an ordinary ruling. ## Why a sibling file, not a mode of `check-half-states.mjs` `check-half-states.mjs` is report-only by its header contract ("the workflow never fails a build over findings"). This check exists to exit non-zero on a finding. A mode would put a second contract inside a 35k-line patrol. So `check-handover.mjs` imports the patrol's readers and transport pieces, and `classifyRulingComment` from `check-prior-rulings.mjs`; none is restated, and its self-test pins that: - readers: `labelNames`, `latestMarkedComment`, `markerMatches`, the claim and release markers, `hasDecisionFacetMarker`, `PM_EXCLUSIVE_STATE_LABELS`, `pmLabelListingPath`, `OPEN_LISTING_PAGE_CEILING`; - transport: `proxyRearmPlan` (under its own guard name), `classifyTransportProbe`, `describeTransportRoute`. It is GET-only. Exit codes: 0 clear, 1 usage, 2 refused, 3 prerequisite not met, 4 no verdict (a read failed or a ceiling bound). The optional `post-stamped --body` → `check-handover` call is **not** done. The departing session id is prose in the vacant body ("Previous holder: … `session_…`"), not a field, and reading it would need a parsing rule the ruling did not define. ## Verification (final head `79e0a8838`) - `node scripts/pm/post-stamped.mjs --self-test` → `✓ post-stamped self-test: 657 cases pass across 23 batteries`, exit 0 - `node scripts/pm/check-handover.mjs --self-test` → `✓ check-handover self-test: 30 cases pass`, exit 0 - `node scripts/pm/check-half-states.mjs --self-test` → `✓ check-half-states self-test: 4916 cases pass`, exit 0 - `pnpm -s check:pm-skill-ratchet` exit 0 (`seat-post-protocol.md is 91 lines (ceiling 91; headroom 0)`, `filing-gate.md is 58 lines (ceiling 58; headroom 0)`) - `pnpm -s check:pm-skill-id-lint` exit 0 · `pnpm -s check:pm-governed-prose` exit 0 · `pnpm -s check:nul-bytes` exit 0 · `pnpm -s check:skill-frame-sync` exit 0 - `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derived 38 families for this diff. All 38 were run on `79e0a8838`, each with exit 0, and reconciled with `--ran`: `38 derived, 38 run, 0 NOT-MEASURED (a DERIVED zero — all 38 recorded an exit code and none of them is 3)`. - The battery `pnpm check:pm-dispatch-gates` → `✓ dispatch-gates self-test (fast tier): 1839 cases pass`, exit 0. - `check:doc-formula-expressions` first exited 3, an unmet prerequisite (unbuilt `@objectstack/formula` / `@objectstack/lint`). After building them under the verify lock it exited 0. - `node scripts/pm/check-governed-merges.mjs --test` on the four files → exit 3, `GOVERNED — Tier S` (`.claude/**` ×2). The PR is governed whole. - **End to end:** `post-stamped --dry-run --body=6023` on #6023's live body → exit 2, refused on lines 65–68 (the four `Carried` lines). The same tool on #18883's body with its status flipped to vacant → exit 0. - **Live dry runs of the pre-handover check**: - `--session=session_012zwsLMxQBwHUYJgPyrpEvx --account=zhuangjianguo --since=2026-10-12T13:57Z` (the devx shift the card measured) → exit 0, `✓ check-handover: clear`. - (a) read 20 open `pm:dispatched` cards, 0 under `zhuangjianguo`: the after-the-fact releases `6160919454` and `6160925950` are in. - (b) read 19 pages of the comment stream (1,809 comments, 146 by the fleet carrying the session, on 41 cards). None of the session's latest comments carries the facet token. - Positive control, the dispatching director session `session_01LqNgdPZB2Jnr5HpGbaqLeB` / `os-zhuang` → exit 2, `[claim-unreleased]` on #23396 and #23352. Both are held under its live claims. - **Ablation**, under the verify lock with a restore trap, through `scripts/ablation-replace.mjs`: - Mutation: the anchor `if (!vacancy.vacant) return [];` became `return [];` (anchor 1 → 0; blob `ba1c7468` → `d4d34d28`). - With the refusal removed: `✗ post-stamped self-test: 9 of 657 case(s) failed, 0 floor problem(s)`. The nine are the battery's refusal cases. - Restore: blob after `ba1c7468aba7` equals HEAD's, `git diff HEAD` empty, `git status` clean. - The first run went red by a crash (a case read `measured[0].why` unguarded). `79e0a8838` guards those reads so a missing refusal shows up as named failing cases, and the run above is from that commit. ## 维护者速读(草稿) **改了什么:** 收班交接清单补上三步:认领的卡收班前要么落地、要么同笔释放;等你裁决的卡只挂 `needs-user-decision`;台账里每条欠账都要有卡号,空座位正文只放指针。立卡门补一句:「搭下一个碰 X 的 PR」不算承接者,已落地工作欠下的后续照常立卡。另加两个本地脚本:写空座位正文时,有欠账行没卡号就拒写;收班前跑一个检查,还有没释放的认领、或决策卡不在你的收件箱,就拒绝交接。 **为什么改:** 上一班 devx 席按清单逐条做完,仍留下三类残缺,都要你亲自指出:认领没释放,两张决策卡没进收件箱,六条欠账没卡号。你的裁决选了 A:规则写进清单,再用两个脚本拦住。 **风险与代价(含回滚):** 不进 CI,不加 Actions 时长,两个脚本只在本地跑。留守的在飞卡用 `--stay-behind=#N` 声明,并写在空座正文的 `Stay-behind:` 行上,检查只报告不拒绝(第二轮,总监裁 B)。回滚即 revert 本 PR,无数据迁移。 **席位意见:** **你要做的:** 无。Tier S:席位出同档复审记录后入队落地。 ## Acceptance notes **Gates:** listed above, all on `79e0a8838`. The local self-test of `check-handover.mjs` runs in no workflow, by the ruling ("two local scripts, no CI time"). `check-self-test-wired` does not count it, because no workflow and no root alias names it. **Owed, not written by this PR:** the `skip-changeset` label. Nothing here ships: `.claude/**` and `scripts/pm/**` are in no package's `files[]`. The dispatch's write budget names no label write. **Open questions for the director** (answered in round 2: Q1 → B, implemented below; Q2 → A, the follow-up for C is #23420)**:** 1. **Stay-behind against check (a).** `seat-post-protocol.md` lines 33, 38 and 40 still let a departing seat stay behind (留守) on named tails after its vacant body. New step 2 and check (a) refuse while such a tail is held. - A: keep as ruled. A tail lands or is released before the vacant body, so 留守 means staying seated until then. Lines 33/38/40 then owe an edit. - B: a declared stay-behind list (`--stay-behind=#N,…`) that the check reports instead of refusing, cross-checked against the body's stay-behind line. This narrows the ruled predicate, so it needs the director's word. - C: run the check before the closing brief rather than before the body. - Recommendation: B. It keeps the stay-behind protocol coherent and still refuses the measured half-state, because #23193 was never a declared tail. 2. **Recall of check (b).** Neither measured analysis (`6141962120`, `6147446653`) carried the facet token, so (b) as ruled would not have fired on the measured shift. Its recall is the four-facet filing duty's. - A: keep. - B: also key on an analysis heading. That is a reader built from imagination. - C: make the token mandatory on a comment-anchored analysis and refuse its absence at `post-stamped --comment`. - Recommendation: A now, with C as a follow-up card. **Out of scope, noted not filed:** - `.claude/agents/os-dev.md:53` defines `carrier:` as "将碰该文件的 PR 或人". Read loosely, that is the "next PR touching X" the filing gate now says is not a carrier. A twin wording outside this claim's surface. Carrier: the director seat, `session_01LqNgdPZB2Jnr5HpGbaqLeB`. - `seat-post-protocol.md:29` lists what `post-stamped --body` intercepts (unread knocks) and does not mention the new vacant refusal. Left alone under "nothing else moves". Same carrier. **Deviations:** - The two predicate readings above go past the dispatch's literal spelling. - `79e0a8838` is a test-only fix found by the ablation. - `origin/main` had not moved past the base `5656c5638` when the PR opened, so there was nothing to merge. - The worktree is the sibling layout `objectstack-issue-23396`. --- _Generated by [Claude Code](https://claude.ai/code/session_01LqNgdPZB2Jnr5HpGbaqLeB)_ ## Round 2 (director's answers: Q1 -> B, Q2 -> A) - **Q1 -> B, implemented.** `check-handover.mjs --stay-behind=#N[,#N…] --body-file=PATH`. A card held under this session's claim is reported as `[stay-behind] #N` (exit 0) only when it is listed on the command line AND declared on the vacant body's `Stay-behind:` line. Listed-but-undeclared or declared-but-unlisted stays `[claim-unreleased]` (exit 2). - **Input: `--body-file`, not `--seat`.** Step 5 runs before the vacant body exists, so the live post would still be the seated body. The file also needs no request. - **Charter text unchanged.** Step 5 keeps its 120 bytes because the refusal names both flags. Lines 33/38/40 stand. - **Q2 -> A, kept.** (b) still keys on the `os-decision-facets` token; the follow-up for C is #23420. - **(b) fix.** A live run refused the dispatching session on this card's own round-1 report, which quotes the token inside fenced JSON. The marker is now read with code blanked (`carriesFacetMarker`). Measured over two days of comments: 20 real markers, none in code; 3 quotations, all in code. - **Verification on `9862e23b0`.** Self-tests: check-handover 46/46, post-stamped 657/657, check-half-states 4916. `dispatch-gates --ran`: 38 derived, 38 run, 0 NOT-MEASURED. Live: arm 1 exit 0 with two `[stay-behind]` lines; arm 2 exit 2 on the undeclared card. Ablation of the both-arms condition: 1 of 44 red, restore proven. - **Out-of-scope twins carried by #23423** (`os-dev.md:53`'s `carrier:` wording; `seat-post-protocol.md:29`'s intercept list). Appended by the director seat from the dev's round-2 report 6163597596. --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: objectstack-fleet[bot] <332303061+objectstack-fleet[bot]@users.noreply.github.com>
1 parent be50633 commit a7a135d

4 files changed

Lines changed: 988 additions & 10 deletions

File tree

‎.claude/skills/pm-dispatch/references/filing-gate.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
- ④ 协调节点:跨仓/跨层父单与它的逐层子单。
2020
- ⛔ 不是卡:席位自己的失误、协议观察与「记住这条」,它们进座位贴或收班简报。
2121
- ⛔ 不是卡:三类外或无 `reach:` 的 `finding`,它们进 Acceptance notes 带 `carrier:`。
22+
- 「搭下个碰 X 的 PR」不算 `carrier:`;已落地工作欠的后续照立卡,⛔ 不按「不是卡」压下。
2223
- ⛔ 不是卡:丢卡重建(除非自身即 ①–④)、sweep 每 fire 重导的 pin/bump/卫生提醒。
2324
- ⛔ 不是卡:既有卡一条评论能承载的指针/知会/请求;那条评论即其记录。
2425
- UI 卡写明所需通用块或 spec 契约门;要产品命名 widget/dialog/hook/命名空间的卡退回重立。
@@ -27,8 +28,7 @@
2728
- 「动手的读者」与 Acceptance notes 同一问:说得出具体 PR 或人 ⇒ 写进去,说不出 ⇒ 不立卡。
2829
- 说不出的形态:`.changeset/*`、无人在改的文档页、生成物、已扫完的批次,兜底不成立。
2930
- 配额:一席每 fire 至多立三张;sweep 超出的折进一张带清单的卡,或折进座位贴。
30-
- 分诊席的存量清理仍走维护者确认的十张一组,⛔ 不因本门改批量。
31-
- 十张一组只备下一组:当前组未经维护者确认 ⛔ 不预备再下一组、不提前展开全部批次。
31+
- 分诊席的存量清理仍走维护者确认的十张一组、只备下一组,⛔ 不因本门改批量。
3232
- 读到 `User` ⇒ 记座位贴、本 fire ⛔ 不立新卡;认领仍靠 assignee,署名仍是文本的 session ID。
3333

3434
## 定级判据(北极星「优先级」的执行说明)

‎.claude/skills/pm-dispatch/references/seat-post-protocol.md‎

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -42,14 +42,14 @@
4242
## 交接收尾清单(漏一步就是留残缺现场)
4343

4444
1. 立即停止新派发。
45-
2. 在手工件逐件处置:能收则收,收不完留守或热移交;⛔ 不为清零而等。
46-
3. 全量枚举本车道队列、决策箱与 findings,形成接任台账。
47-
4. 贴正文改 `⏳ vacant` + 台账:前任会话 ID、注销时间、队列快照、串行队、跨车道备忘。
48-
5. 收班简报与审计评论存档;接任指引 = `/pm-dispatch 接手` + 先读座位贴。
49-
6. `list_triggers` 清点本会话定时器:删入座 Routine,一次性的逐个清理或随移交物转交。
50-
7. 移交中 PR 的定时器随 PR 走;留守保留的定时器只触点名项。
51-
8. 本座位派出的 dev 会话由离任 PM 自己逐个 `archive_session`,归档义务 ⛔ 不可移交。
52-
9. 在飞 dev 卡与留跑会话在贴里点名(卡号 + 派发会话 ID);跟完与归档 ⛔ 不随交接转移。
45+
2. 在手工件逐件处置:本席认领的卡落地,或 `Release:` 与清 assignee 同笔释放,转成真实状态。
46+
3. 全量枚举本车道队列、决策箱与 findings,形成接任台账;等裁决的卡只挂 `needs-user-decision`。
47+
4. 台账义务逐条带卡号(条件型开 `pm:blocked`+`Blocked-by:`,定时型 `Restart-when:`),正文只放指针。
48+
5. 第 2、3 步由 `node scripts/pm/check-handover.mjs --session=ID --account=LOGIN` 核,退出 0 才写空座正文。
49+
6. 贴正文改 `⏳ vacant` + 台账:前任会话 ID、注销时间、队列快照、串行队、跨车道备忘。
50+
7. 收班简报与审计评论存档;接任指引 = `/pm-dispatch 接手` + 先读座位贴。
51+
8. `list_triggers` 清点本会话定时器:删入座 Routine,一次性的逐个清理或随移交物转交。
52+
9. 在飞卡与留跑会话贴里点名(卡号+派发会话 ID);派出的会话离任 PM 自己 `archive_session`。
5353
10. 向维护者交最终报告。
5454

5555
## 座位 Routine 创建

0 commit comments

Comments
 (0)