Skip to content

Commit a0aa8a1

Browse files
pm-dispatch(contract-review): the lane reviews its own PRs, triage takes only an owed review nobody claims (drafts included); one Contract-review-claim line per head; an in-repo owed-review list (#27295)
Fixes #27062 Clause-②: no Metadata-protocol: none ## 维护者速读 - 改了什么:契约复审归属改成「车道审自己的,分诊只接没人在审的」:欠审的 PR(含 draft)在现 head 上没有复审记录、没有有效认领,且满足 (a) 有交接行或等复核标记、(b) 等满一轮分诊约 60 分钟、(c) 车道席收班或不在总表之一,分诊才接;起审前先贴一行 `Contract-review-claim:`;欠审清单从会话临时脚本改成仓内脚本 `scripts/pm/check-owed-reviews.mjs`。 - 为什么改:维护者原话「应该车车道没有人审查的你才负责契约复审对吧」「如何避免重复审查」「所以你需要更新 skills吧」。之前分诊只认交接行、扫描又跳过 draft,两个 Tier H draft 带着交接行等了约五轮;同时没有任何东西能防两个复审者同时审同一个 head。 - 风险与回滚:只动协议文本、两个 PM 脚本与一个 package.json 脚本串,不碰产品包、门禁判定、workflow、队列守卫;认领行只是协调,⛔ 没有门禁读它。回滚 = revert 本 PR。 - 先看:① `.claude/skills/pm-dispatch/references/contract-review.md` 新增 6 行(L23–L28:触发条件、认领行形状、失效与车道优先)与 L60 改写;② `scripts/pm/record-recognisers.mjs` 的 `readContractReviewClaim` / `contractReviewClaimLapse` / `contractReviewClaims`(风险点:同 head 双认时谁持有);③ `scripts/pm/check-owed-reviews.mjs` 的 `owedReviewRow`(TAKE/WAIT/HELD/DONE 判定)。 - 两分钟验证:`node scripts/pm/check-owed-reviews.mjs`,预期首行 `check-owed-reviews — objectstack-ai/objectstack, …: N open PR(s) read (M draft); K owe a contract review — …`,其后每个欠审 PR 一行,TAKE 在前,退出码 0。 - 需要你做的:无,席位落地(Tier S:席位达档契约复核 PASS 后入队)。 ## What changes The card's Wanted 1–6 as amended by the 12:23Z scope amendment (drafts are in; an in-repo owed-review list). - **Item 1, the lane reviews its own** — unchanged (`contract-review.md` L20 「谁跑 = 派发席」). - **Items 2–3, the gap-only trigger and the claim line** — `references/contract-review.md` 〈复核归属与资格〉, six new lines after the hand-off line: triage takes only an owed PR (draft or ready) with no record and no live claim on the current head, and one of (a) a hand-off line naming the current head or the waiting label, taken the same round; (b) the head has waited one full triage round (about 60 minutes); (c) the lane seat has stood down or is absent from the roster. Every reviewer reads the PR first; a record or a live claim on the current head means do not start; otherwise it posts one claim line per head first: ```text Contract-review-claim: SESSION · head FULL-SHA · UTC ``` (the file spells the placeholders in angle brackets; they are spelled out here because the platform strips tag-shaped fragments). A claim lapses when the head moves or 2 hours after posting with no record; when a lane claim and a triage claim name one head the lane wins and triage replies once to withdraw and discards its subagent's work. Delta re-reviews stay with the earlier reviewer — the existing rule (now L41) 「只落发现的 commit 由同一复核者 delta 复核」 already says it for both sides, so no line was added for it. - **〈等复核标记〉** — L54 「只带标记无交接行者仍归本席,分诊 ⛔ 不碰…」 is deleted: the amendment makes the label condition (a), so that sentence is now false; its second half (a hand-off goes stale when the head moves) is carried by the new (a) line, which counts only a hand-off naming the current head. The ⛔ line (now L60) keeps check, workflow and queue guard off the label and adds the claim line to what they never read; 「巡查脚本」 leaves it because the owed-review list now reads the label as one owed limb (report-only, never a verdict). - **Item 6, the round step** — `references/triage-duties.md` L11–L12: each round runs `check-owed-reviews.mjs` first (open PRs including drafts, hand-off and claim lines read), and an owed review is claimed and taken the same round; L12 keeps the isolated at-tier subagent signed by the seat and the quota rule. The dropped clause 「交接行读法 `record-recognisers.mjs`」 has its home in the script, which reads hand-off lines through that file. - **Item 5, the round record** — `references/triage-duties.md` L16 in place: the review table row names the claimer (`认领者 · 记录 head→现 head→状态`). - **Item 4, the recogniser** — `scripts/pm/record-recognisers.mjs`, beside `readContractReviewHandoff`: `CONTRACT_REVIEW_CLAIM_KEY` / `CONTRACT_REVIEW_CLAIM_TEMPLATE` / `CONTRACT_REVIEW_CLAIM_LAPSE_HOURS`; `readContractReviewClaim(body)` (session in the signature line's session shape, imported; the FULL 40-hex head; the UTC minute, seconds allowed; read through `stripSignatureLine`); `contractReviewClaimLapse` (pure: `live` / `head-moved` / `expired`, timed from the comment's `created_at`); `contractReviewClaims(rows, head, { now })` (the holder, lane first on a tie; a claim is triage's only when its comment is signed `triage#N`). ⛔ No gate reads it. - **Item 6, the owed-review list** — new `scripts/pm/check-owed-reviews.mjs` (named as `scripts/pm/` names its report-only readers, `check-half-states.mjs` the model): read-only, prints one row per open PR (draft or ready) whose face owes a review — the face from `reviewFace`'s path hits (reused, not re-implemented), the PR body's `Clause-②:` line through `readClause2Line`, and the waiting label; the record on the current head through `locateReviewOfRecord` (stale records name their reviewer, the delta owner); the hand-off through `contractReviewHandoff`; the claim through `contractReviewClaims`; the lane seat from the card's `domain:*` label against the open seat posts (`fleet-roster.mjs` `parseSeat`); a status `TAKE` / `WAIT` / `HELD` / `DONE` and the trigger that holds. Exit 0 on every read; a failed read prints `sweep INCOMPLETE`, never clean; usage errors exit 2. `--self-test` carries a battery roster, floor and verdict handshake. - **Wiring (no workflow line)** — root `package.json` `check:pm-record-recognisers` now runs `record-recognisers.mjs --self-test && check-owed-reviews.mjs --self-test`, so the existing `lint.yml` step runs the new self-test; `check:self-test-wired` is green on it. - **Companion registration** — the recogniser's claim battery pins the template and its 2-hour lapse verbatim in `contract-review.md`, a read of a governed file: the existing `self-test-reads` declaration (line 77, unmoved) names both files and `GOVERNED_READ_FLOOR` (`scripts/pm/dispatch-gates.data.mjs`) gains the row. `dispatch-gates.self-test.mjs`'s census is unchanged (same declaration, same line). ## Line budget | file | before | after | ceiling | |:--|--:|--:|--:| | `.claude/skills/pm-dispatch/references/contract-review.md` | 55 | 60 | 60 | | `.claude/skills/pm-dispatch/references/triage-duties.md` | 102 | 102 | 102 | Every edited line is ≤120 bytes (`wc -c`): contract-review.md L23–L28 116, 116, 105, 117, 64 (the code line), 120, L60 113; triage-duties.md L11 115, L12 118, L16 120. contract-review.md spends its 5 free lines plus the one freed by deleting L54 (home above). No re-wrap was used to buy a line. ## Tests <details><summary>Self-tests, counts before → after</summary> - `node scripts/pm/record-recognisers.mjs --self-test`: 276 → 297 cases (batteries 15 → 16; the claim battery 21 cases, floor 21). - `node scripts/pm/check-owed-reviews.mjs --self-test`: new, 49 cases in 8 batteries (floor 8), including a fixture modelled on the two Tier H drafts that waited (a workflow-step draft with a hand-off on its current head since 07:43Z; a narrowing draft whose first hand-off went stale on a merge-forward and was renewed): both list as `TAKE` under `(a) hand-off`, and read `HELD` once the triage claim is posted. - Ablations, each restored with `blob == HEAD` and an empty `git diff HEAD` (`scripts/ablation-replace.mjs`): the tie rule (`holder = live[0]`) reds 「the LANE wins a tie」 (1 of 297); the lapse boundary (`>=` → `>`) reds the lapse case (1 of 297); excluding drafts from the owed list reds the owed-review self-test (exit 1). </details> <details><summary>Live run, read-only (14:39Z)</summary> `node scripts/pm/check-owed-reviews.mjs` exit 0 in 47 s: 22 open PRs read (16 draft); 16 owe a contract review — 3 to take, 8 waiting on the lane, 0 claimed, 5 reviewed on this head. The narrowing draft named on the card reads `DONE` (record on its current head, PASS, by the triage session; hand-off `triage`; claim `lapsed`); the workflow-step draft has since merged and is not listed. </details> ## Acceptance notes - The waiting label is hung by the dispatching seat at ACCEPT whenever a review is owed and no PASS is on file (〈等复核标记〉 L58). With the amendment it is condition (a), taken by triage the same round — so a lane seat that will review itself posts its claim line before or with the label; that is what keeps item 1 true. Noted, not changed: the label rule's text is outside this card's ruled scope. - `dispatch-runbook.md` 〈巡查常设项〉 still tells a cross-account reviewer to post a `Claim:`-shaped comment before a review; that is a second spelling of the review claim. Not touched here (that file is held by another claim this wave); noted for the seat. - The `lint.yml` step comment above `check:pm-record-recognisers` says it runs one file's self-test; it now runs two through the package script. A workflow comment is Tier H, so it is left as is. - Trigger (b) times the head from the later of its commit's committer date and the PR's opening; GitHub's API does not expose push time on the pull row, so a force-push of an old commit can read as waited longer than it has. - The verdict word (PASS / FAIL) is read by the list for display only, from the record's `VERDICT:` line; owed-ness never depends on it. ## Verification <details><summary>Local gates at dca7881 (derived by `dispatch-gates --commands`, reconciled with `--ran`)</summary> `node scripts/pm/dispatch-gates.mjs --ran ran.list --repo objectstack-ai/objectstack` → exit 0: 「50 derived famil(ies) accounted for — 46 run, 4 NOT-MEASURED (4 DERIVED from a recorded exit 3)」. Exit 0 on all 46 run, among them `check:pm-skill-ratchet` (contract-review.md 60/60, triage-duties.md 102/102, headroom 0), `check:pm-skill-id-lint` (104 files clean), `check:pm-record-recognisers` (297 + 49), `check:pm-dispatch-gates` (1935 cases, fast tier), `check:pm-half-states` (5034), `check:pm-governed-merges`, `check:self-test-wired` (244 scripts), `check:ratchet-remedy-authority`, `check:entry-guard`, `check:parse-guard`, `check:nul-bytes`; `check:doc-formula-expressions` after building its closure under the verify lock. Run by hand beyond the derivation: `check:pm-governed-prose` exit 0, `check-governed-queue-guard.mjs --self-test` exit 0 (401 cases). NOT MEASURED (exit 3, PREREQUISITE NOT MET — a full `pnpm build`; the diff touches only a script string in the root `package.json`): `check:dts-closure`, `check:dual-build-cjs-loads`, `check:lean-entry-closure`, `check:sourcemap-no-sources-content` — CI runs them. </details> --- _Generated by [Claude Code](https://claude.ai/code/session_01QSeKYmhU5KEj1nukYhp1Tg)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 65b4920 commit a0aa8a1

6 files changed

Lines changed: 825 additions & 11 deletions

File tree

‎.claude/skills/pm-dispatch/references/contract-review.md‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@
2020
- 四面皆不碰 ⇒ CI 加席位自读 ⛔ 不起第二个 agent;谁跑 = 派发席,达档者席内审。
2121
- 派发席达档调用被配额拒(HTTP 429)⇒ ⛔ 不降档审;PR 落交接行、留等复核标记,交分诊席。
2222
- `Contract-review-handoff: triage — CONTRACT_REVIEW_TIER unavailable (<HTTP status / error>, <UTC time>) · head <sha>`
23+
- 分诊只接无人在审的欠审 PR(draft 亦算):现 head 无记录、无活认领,且 (a)(b)(c) 之一成立。
24+
- (a) 交接行点名现 head 或 PR 带等复核标记,同轮接;(b) 现 head 已等满一轮分诊(约 60 分钟);
25+
- (c) 车道席已收班或不在总表;欠审清单 = `node scripts/pm/check-owed-reviews.mjs`,只读。
26+
- 车道或分诊起审前读 PR:现 head 有记录或活认领 ⇒ ⛔ 不起;否则先落认领行,一 head 一行:
27+
- `Contract-review-claim: <session> · head <full sha> · <UTC>`
28+
- 认领随 head 移动或贴后 2 小时无记录失效;同 head 双认 ⇒ 车道胜,分诊回帖撤回、弃产出。
2329
- 复核记录 = 一条评论落 PR 或卡,席内与子代理同形;散文、dev 自评、`os-dev-report` 恒不算。
2430
- 同形 = `## Contract review` 题头、head sha 独占码段、①②③ 逐项(申报面外三面加 ④)、
2531
独立性对、PASS/FAIL。
@@ -51,5 +57,4 @@
5157
- 只作等复核标记,⛔ 不是闸门;单载体 = PR,卡上副本在规则外、不是要求。
5258
- 挂:ACCEPT 时条款②任一肢命中而现 head 无同形 PASS 在案 ⇒ 派发席同笔挂于 PR。
5359
- 摘:现 head 同形 PASS 在案 ⇒ 写记录的席同笔摘;PR 合并或关闭 ⇒ 派发席摘;FAIL 不摘。
54-
- 只带标记无交接行者仍归本席,分诊 ⛔ 不碰;交接后 head 再动 ⇒ 新交接行或本席自审。
55-
- ⛔ 无 check、workflow、队列守卫或巡查脚本读它;入队只认同形记录,标记有无不改判。
60+
- ⛔ 无 check、workflow 或队列守卫读标记与认领行;入队只认同形记录,标记有无不改判。

‎.claude/skills/pm-dispatch/references/triage-duties.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@
88
- 查重缓存住席内:新会话全量拉,其后 `since` 增量;⛔ 不建分支、不共享、不依赖备份。
99
- 每日一 fire 跑两仓全量对账并归集日频职责;选层按 fire 时刻 ⛔ 不用计数器,简报写明层。
1010
- 每条枚举比对返回数与 `totalCount`,不等 ⇒ 报 `sweep INCOMPLETE` 点名缺口,⛔ 永不报干净。
11-
- 复审交接:每轮列最新交接行点名现 head 的 open PR,隔离子代理达档跑同形记录、署本席。
12-
- 本席也被配额拒 ⇒ PR 写一次、留标,⛔ 永不降档审;交接行读法 `record-recognisers.mjs`。
11+
- 复审:每轮先跑 `check-owed-reviews.mjs`(open PR 含 draft,读交接与认领行),欠审同轮认领后接。
12+
- 隔离子代理达档跑同形记录、署本席;本席也被配额拒 ⇒ PR 写一次留标,⛔ 永不降档审。
1313
- 每轮比对自审记录 `Head-sha` 与现 head,越过即过期,纯重生成/只动散文面例外(判已提交树)。
1414
- 自审 FAIL 过期 ⇒ 同轮达档重审新 head,逐条复查 must-fix,delta commit 当改动审。
1515
- 自审 PASS 过期 ⇒ PR 记一条 PASS 不再管辖、欠 delta 复审,归本席或新交接行。
16-
- 轮次记录逐张自审 PR 写 `记录 head → 现 head → 状态`(现行/过期已重审/过期已记)。
16+
- 轮次记录逐张自审 PR 写 `认领者 · 记录 head→现 head→状态`(现行/过期已重审/过期已记)。
1717
- Backlog sweep 是常设职责,每 fire 扫任一析取命中的卡。
1818
- 析取 ①:全裸(无 `pm:*`、无 `needs-user-decision`、无 `domain:*`)。
1919
- 析取 ②:有 `pm:queue` 无 `domain:*`(仅多车道仓);析取 ③:有 `domain:*` 无 pm-state。

‎package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@
8181
"check:declared-population-live": "node scripts/check-declared-population-live.mjs --self-test && node scripts/check-declared-population-live.mjs",
8282
"check:pm-half-states": "node scripts/pm/check-half-states.mjs --self-test",
8383
"check:issue-citations": "node scripts/check-issue-citations.mjs --self-test",
84-
"check:pm-record-recognisers": "node scripts/pm/record-recognisers.mjs --self-test",
84+
"check:pm-record-recognisers": "node scripts/pm/record-recognisers.mjs --self-test && node scripts/pm/check-owed-reviews.mjs --self-test",
8585
"check:pm-prior-rulings": "node scripts/pm/check-prior-rulings.mjs --self-test",
8686
"check:pm-stale-finding": "node scripts/pm/sweep-stale-finding.mjs --self-test",
8787
"check:pm-write-pace": "node scripts/pm/write-pace.mjs --self-test",

0 commit comments

Comments
 (0)