Skip to content

Commit d34533c

Browse files
pm-dispatch(sign-off): a seat going off shift hands every unlanded claimed card back to pm:queue in one stroke; a needs_decision card goes to the decision box; stay-behind narrows to ACCEPTed PRs waiting on the queue or a human (#27371)
Fixes #27359 Clause-②: no Metadata-protocol: none ## 维护者速读 - **改了什么**:席位下班(收班)时,手上每张还没落地的认领卡一笔交回 `pm:queue`(`Release:` + `Resume-from:` + 清 assignee);dev 报了 `needs_decision` 的卡改进决策箱;留守只剩已 ACCEPT、正在等合并队列或等人批的 PR。`check-handover.mjs` 同步收窄:声明留守的卡必须有已 ACCEPT 且在等的 PR,dev 报 `needs_decision` 却还挂 `pm:dispatched` 的卡点名拒收。 - **为什么改**:下班的席位不再把卡锁在自己账号下。之前留守是默认安置,结果两张卡停在 `pm:dispatched` 没人跑,其中一张的待裁问题根本没到你手里,别的席位只能强制接管。 - **风险与回滚**:只改两页席位协议文字和一个本地只读检查脚本(不进 CI、不写 GitHub)。风险是检查比以前严:以前能过的「随手留守」现在被拒,要么交回、要么先把 PR 送进队列或送审。回滚 = revert 本 PR。 - **先看**:`scripts/pm/check-handover.mjs` 的 `handoverVerdict` 与 `acceptedPullWait`(收窄逻辑);`seat-post-protocol.md`〈活性、写侧刷新与收班〉新第 35–36 行与〈交接收尾清单〉第 2 步;`seat-lifecycle.md` 第 40、43 行。 - **两分钟验证**:`node scripts/pm/check-handover.mjs --self-test`,应看到 `✓ check-handover self-test: 66 cases pass`。 - **需要你做的**:无,席位落地(`.claude/**` 为 Tier S,席位达档复核后落地)。 ## What changes The maintainer's rule, verbatim: 「下班时卡片应该交回队列,这个应该立卡更新 skills」. The card's items 1–4 are the spec. **Protocol text** (both files stay at their line ceilings, every line at most 120 bytes): | file · line | before | bytes | after | bytes | |---|---|---|---|---| | seat-post-protocol.md L35 | 安置默认离任留守;留不了才热移交:在飞卡落收单注记 + 点名看护者。 | 94 | 收班默认交回(〈交接收尾清单〉第 2 步);留守只剩已 ACCEPT 待队列或待人批的 PR。 | 109 | | seat-post-protocol.md L36 (new) | — | — | 留守写面 = 简报点名的这些 PR,残余定时器同界;留不了才热移交:收单注记 + 点名看护者。 | 119 | | seat-post-protocol.md L38 → L39 | 收班序列:贴正文与标题刷新在先,简报评论是最终一笔;继任的释放判据 = 简报在。 | 110 | 收班序列不看维护者措辞:`check-handover.mjs` 退出 0 → 刷正文与标题 → 简报最终一笔。 | 114 | | seat-post-protocol.md L40 (deleted) | 留守写面恰为简报点名的项:已 ACCEPT 待队列 PR、落地中的评审链载体;残余定时器同界。 | 117 | — (content moved to L35/L36; 评审链载体 leaves stay-behind by item 3) | — | | seat-post-protocol.md L47 (checklist step 2) | 在手工件逐件处置:本席认领的卡落地,或 `Release:` 与清 assignee 同笔释放,转成真实状态。 | 118 | 认领卡落地,或同笔 `Release:`+`Resume-from:`+清 assignee 交回 `pm:queue`;`needs_decision` 进决策箱。 | 118 | | seat-lifecycle.md L40 | 收班 = 状态 flush + 在飞显式安置,不是在飞归零;派发口径失效,简报 ⛔ 不列作继承裁决。 | 118 | 收班 = 状态 flush + 在飞默认交回队列、不等落地;派发口径失效,简报 ⛔ 不列作继承裁决。 | 120 | | seat-lifecycle.md L43 | 离任留守只写简报点名的项,⛔ 不读队列、不新派发、不碰未点名卡。 | 94 | 离任留守只写点名的已 ACCEPT 待队列或待人批 PR,⛔ 不读队列、不新派发、不碰未点名卡。 | 120 | Bytes are the full source line, list marker included (the gate's measure); the table shows the text after the marker. Line counts: seat-post-protocol.md 88 → 88 (ceiling 88), seat-lifecycle.md 96 → 96 (ceiling 96). The new L36 is paid by deleting old L40, whose content moved into L35/L36. One clause leaves old L38: 「继任的释放判据 = 简报在」. Its home is seat-lifecycle.md 〈开轮互斥〉: 「收班简报是前任不再写的显式声明,是释放标记不是锁:简报即最新事件 ⇒ 立即坐席。」 - Item 1 (hand-back is the 收班 default): L35 + checklist step 2. The stroke is `Release:`, `Resume-from:`, the assignee cleared, and the card back to `pm:queue`. - Item 2 (a `needs_decision` card goes to the decision box, ⛔ not `pm:queue`): checklist step 2, and the new check below. - Item 3 (stay-behind narrows to ACCEPTed PRs waiting on the queue or a human): L35/L36, seat-lifecycle L43, and the check. - Item 4 (the brief and the `check-handover.mjs` run are the last acts, whatever words end the shift): L39. **`scripts/pm/check-handover.mjs`** (a local refusal tool; no workflow runs it): - **(a′) `[needs-decision-unreleased]`.** A held card is refused by this name when its newest `os-dev-report` is newer than the claim and declares `"status": "needs_decision"`, and the card is not `needs-user-decision`. The remedy names the decision box, ⛔ not `pm:queue`. It is refused even when the card is declared as a stay-behind. - **Stay-behind narrowed.** A card listed in `--stay-behind` and declared on the vacant body is reported as `[stay-behind]` only when an open pull that delivers it meets two conditions. First, the pull's newest `## 席位核对` record is an ACCEPT. Second, the pull waits: it is in the merge queue (newest queue event on its timeline is the add), auto-merge is armed, or it awaits a human approval (`needs-user-decision` on the pull). Otherwise the card stays `[claim-unreleased]`, and the refusal names the failed condition for each pull. - **No reader is restated.** `latestDevReport` / `devReportPayloadCandidates` / `deliveryEvidence` come from `check-half-states.mjs`, `SEAT_RECORD_MARKER` / `seatRecordAccepts` from `seat-record.mjs`, and `enqueuerFromTimeline` from `check-governed-queue-guard.mjs`. The only new reading is the report's `status` field. - **Reads are bought only for a declared tail.** That is one paged listing of open pulls, then each delivering pull's thread, and its timeline only when the label and `auto_merge` did not answer. Every request is a GET. ## Tests `node scripts/pm/check-handover.mjs --self-test`: **46 → 66 cases**, all pass (handshake intact). Changed rows: ARM 1 now requires a waiting ACCEPTed pull, and the decision-not-excused row uses a waiting tail so it still isolates the decision refusal. New rows cover the narrowing (not-waiting, unread), (a′) (measured shape, older-than-claim, newest-decides, already-in-inbox, refused-by-name), `acceptedPullWait` (queue, approval label, armed, needs-timeline, dequeued, no-accept, REWORK-after-ACCEPT, ACCEPT-after-REWORK), the shortfall text, delivery evidence, and two structural pins. <details><summary>Ablation, four legs through scripts/ablation-replace.mjs, each restored and hash-proven (HEAD d457c56, blob 2557438)</summary> - leg 1: `else if (twice && h.wait)` changed to `else if (twice)`. 2 of 66 failed: `SIGN-OFF NARROWING`, `absence never reads as waiting`. - leg 2: the `needs_decision` status test changed to `return null`. 1 of 66 failed: `(a′) THE MEASURED SHAPE`. - leg 3: the ACCEPT requirement changed to record existence. 1 of 66 failed: `a REWORK newer than the ACCEPT, is no ACCEPT`. - leg 4: the queue reading changed to `if (true)`. 1 of 66 failed: `the newest queue event is a removal, or there is none`. - After each leg: blob after restore == blob at HEAD (2557438), and `git diff HEAD` is empty. The unmutated control run: 66 cases pass. </details> <details><summary>Live reads (GET only, this container)</summary> - `--probe`: transport prerequisite met. - Departed `domain:server` seat (`session_01R1q2xdtYgU6FKrZ7LgjPCC`, `os-sales`, since 2026-10-27): exit 0, clear. The measured instance has moved on. #25805 is now `needs-user-decision`, and #23181's newest claim names another session, so it is listed, not refused. - This seat with `--stay-behind=#27359` and a vacant probe body: exit 2. #27359 was refused with "declared a stay-behind on both, but no open pull request delivers it". Coverage: `(c) 1 declared stay-behind card(s): 14 open pull(s) listed`. </details> ## Acceptance notes - `落地中的评审链载体` leaves the stay-behind write surface. Card item 3 narrows stay-behind to ACCEPTed PRs only. - Checklist step 2 loses 「转成真实状态」. The ruled targets replace it: `pm:queue`, or the decision box for `needs_decision`. Step 4 still carries `pm:blocked` + `Blocked-by:` / `Restart-when:` for ledger obligations. - The stay-behind reading asks whether the pull's newest seat record is an ACCEPT. It does not ask whether that record was made for the current head; H69 patrols that half on Tier H. - (a′) reads held claims only. A card released to `pm:queue` while its dev's question is open leaves that population, and H52 (open questions unrouted) still reports it. - Queue membership goes through `enqueuerFromTimeline`. An add event whose actor has no login reads as not queued, which is the refusing direction. - The coverage line prints `(c)` between `(a)` and `(b)`, because the stay-behind reads follow the held-card reads. Cosmetic only. - The file's self-test keeps its pre-existing shape: a handshake and no battery floor. Adding a floor is outside this card. - This PR carries no assignee. That follows the seat's ruling for seat-driven drafts, while `os-dev.md` on main still says to send one. ## Verification Final head `d457c56d`. Derivation: `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` gives 36 commands. All 36 exited 0 locally. `check:doc-formula-expressions` first exited 3 (PREREQUISITE NOT MET: `@objectstack/lint` was not built). It exited 0 after `turbo run build --filter=@objectstack/formula --filter=@objectstack/lint` under the verify lock. `--ran` reconciliation: `36 derived famil(ies) accounted for — 36 run, 0 NOT-MEASURED (a DERIVED zero — all 36 recorded an exit code and none of them is 3)`. <details><summary>Gate readings</summary> - `pnpm check:pm-skill-ratchet`: seat-post-protocol.md is 88 lines (ceiling 88; headroom 0). seat-lifecycle.md is 96 lines (ceiling 96; headroom 0). Self-test: 164 cases pass. - `pnpm check:pm-skill-id-lint`: 105 file(s) clean. - `pnpm check:pm-post-stamped`: 735 cases pass across 26 batteries. Its self-test reads `check-handover.mjs`'s `carriesFacetMarker`, which is unchanged. - `pnpm check:doc-authoring`, `pnpm check:nul-bytes`, `node scripts/check-self-test-wired.mjs` (+ `--self-test`), `pnpm check:pm-governed-merges`, `pnpm check:skill-frame-sync`, and the remaining 26 derived commands: exit 0. - `pnpm exec eslint --no-inline-config scripts/pm/check-handover.mjs`: exit 0. - `check:pm-governed-prose` and `check:pm-half-states` are not derived for this diff, so they did not run locally; CI runs the lint farm. </details> --- _Generated by [Claude Code](https://claude.ai/code/session_01QSeKYmhU5KEj1nukYhp1Tg)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 6e0e7d7 commit d34533c

3 files changed

Lines changed: 236 additions & 33 deletions

File tree

‎.claude/skills/pm-dispatch/references/seat-lifecycle.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,10 @@
3737
- 敲门后满 `RECLAIM_AFTER_HOURS` 无答复即由总监席一笔中继释放,细则 `triage-duties.md`。
3838
- ⛔ 永不从贴的 `updated_at` 或标题推死活;活性只认在任者自己的产出。
3939
- 竞态三招:动手前重 fetch 正文、审计评论时间戳先到先得、写后回读。
40-
- 收班 = 状态 flush + 在飞显式安置,不是在飞归零;派发口径失效,简报 ⛔ 不列作继承裁决。
40+
- 收班 = 状态 flush + 在飞默认交回队列、不等落地;派发口径失效,简报 ⛔ 不列作继承裁决。
4141
- 坐席发 `seat-online`、收班简报同一动作发 `seat-offline`,键见 `project-forms.md` 〈信箱 PR〉。
4242
- 强制接管的新账号代撞墙账号发 `seat-online`;Routine 席唤不醒,读信箱靠巡查。
43-
- 离任留守只写简报点名的项,⛔ 不读队列、不新派发、不碰未点名卡。
43+
- 离任留守只写点名的已 ACCEPT 待队列或待人批 PR,⛔ 不读队列、不新派发、不碰未点名卡。
4444
- 换班报告默认零建议,只收三类:原则错/缺、可机械化项、平台事实变化。
4545
- 原则错/缺 → 只进简报,维护者点名才立卡;可机械化项 → 门禁/脚本卡,⛔ 不是散文。
4646
- 平台事实变化 → references 事实表改一行。

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,19 +32,19 @@
3232
- 耐久状态只住正文或以 App 写入;正文守中继上限 60,000 字节,超大贴由半状态巡查浮出。
3333
- 协议升级迁移状态、或巡检发现正文不符时,当场改正文 + 审计评论。
3434
- Routine 座位的收尾简报也落自己的座位贴,它是下一轮自退守卫的读数。
35-
- 安置默认离任留守;留不了才热移交:在飞卡落收单注记 + 点名看护者。
35+
- 收班默认交回(〈交接收尾清单〉第 2 步);留守只剩已 ACCEPT 待队列或待人批的 PR。
36+
- 留守写面 = 简报点名的这些 PR,残余定时器同界;留不了才热移交:收单注记 + 点名看护者。
3637
- 收单注记写评审判据、落地步骤与当前状态;看护只守落地,⛔ 评审判断不可外包。
3738
- 看护者默认下任座位;在跑云会话经 poke 触发器投递,⛔ 非 `SendMessage` 直投。
38-
- 收班序列:贴正文与标题刷新在先,简报评论是最终一笔;继任的释放判据 = 简报在。
39+
- 收班序列不看维护者措辞:`check-handover.mjs` 退出 0 → 刷正文与标题 → 简报最终一笔。
3940
- 标题滞后是可修半状态,坐席时顺手修,⛔ 不是阻塞。
40-
- 留守写面恰为简报点名的项:已 ACCEPT 待队列 PR、落地中的评审链载体;残余定时器同界。
4141
- 末尾巴合并 ⇒ 贴上一条尾清注记;继任立即坐席,点名集作围栏。
4242
- 标题词表不扩,收班座位恒 `⏳ vacant`;留守登记(谁、哪些尾巴)= 说明段一行。
4343

4444
## 交接收尾清单(漏一步就是留残缺现场)
4545

4646
1. 立即停止新派发。
47-
2. 在手工件逐件处置:本席认领的卡落地,或 `Release:` 与清 assignee 同笔释放,转成真实状态。
47+
2. 认领卡落地,或同笔 `Release:`+`Resume-from:`+清 assignee 交回 `pm:queue`;`needs_decision` 进决策箱。
4848
3. 全量枚举本车道队列、决策箱与 findings,形成接任台账;等裁决的卡只挂 `needs-user-decision`。
4949
4. 台账义务逐条带卡号(条件型开 `pm:blocked`+`Blocked-by:`,定时型 `Restart-when:`),正文只放指针。
5050
5. 第 2、3 步由 `node scripts/pm/check-handover.mjs --session=ID --account=LOGIN` 核,退出 0 才写空座正文。

0 commit comments

Comments
 (0)