Commit e15870f
skills(pm-dispatch): key the clause-② contract review by lane — spec and skills owe it on every round, other lanes owe none (#18903)
Fixes #18536
Clause-②: no
## What this lands
The maintainer's lane rule, restated by the director record on the card
(comment 5717169811), quoted verbatim and untranslated:
> 「曾经要求只有 spec 和 skills 需要 fable,其他 opus 就够了,理论上其他车道不需要契约复审」「即使项目经理跑在
opus 上,也可以支持起 fable 子 agent 复审,这样最快。除非子 agent 用 fable 启动失败,也就是没有 fable」
carried into the governed text and the two machine readers:
1. **Spec and skills lanes** — every delivered round gets the contract
review at `CONTRACT_REVIEW_TIER`: in-seat when the seat's served tier is
that tier, otherwise by the at-tier review subagent the seat spawns (the
09-17 route stays). The record is the same-shape comment with
`Served-tier:`, on the PR or the card.
2. **Every other lane** — no contract review. The clause-② limbs (path
`packages/spec/src/**`, declaration `Clause-②: yes`) survive unchanged
and now answer the LANE question: a limb hit is spec-lane work and moves
there; a `Clause-②: no` PR outside the contract surface lands on the
three pre-checks and the gates. No default-tier self-review record is
demanded, and no other lane spawns the at-tier subagent.
3. **Tier unavailable** (the at-tier subagent cannot start) — the review
cannot be produced: the PR stays draft and out of the queue, and the
maintainer's own review is the only bypass, by their word each time.
The lane key is the 2026-09-10 key restored (PR #17294) with the
2026-09-17 subagent route kept (PR #18511); the 2026-09-16 tier key (PR
#18363) is superseded. Not a byte-revert: both spec AND skills owe the
review, and 「余席默认档自审加门禁」 is gone — other lanes owe nothing.
## Charter (commit 1) — net 0 lines per file
| file | before → after | ratchet ceiling |
|:--|:--|:--|
| `.claude/skills/pm-dispatch/SKILL.md` | 812 → 812 | 812 |
| `references/contract-review.md` | 60 → 60 | 60 |
| `references/core-rules.md` | 151 → 151 | 151 |
Every edited line is at most 120 bytes; `check:pm-skill-ratchet` is
green on `bc0c2ec41`. Rule text carries no issue numbers
(`check:pm-skill-id-lint` green); the four-axis frame block is untouched
(`check:skill-frame-sync` / `-freshness` green).
**SKILL.md** — five in-place rewrites, one added line, one retired line:
- :512 强制条款② — 「达档复核归派发席席内」 → 「命中即 spec 车道的活」.
- :522 席位档策略 — 「按实测档:达档席内审;未达档席 ⛔ 不自审,起隔离达档子代理转录核档采信」 → 「按车道:spec 与
skills 席达档席内审;未达档 ⛔ 不自审,起隔离达档子代理」 (the transcript-verified tier of the
subagent stays stated in contract-review.md :54).
- :636 入队闸门 — 「无席内条款②复核 PASS 在案」 → 「无达档条款②复核 PASS 在案」; the limbs at :637
/ :638 are untouched.
- :639 交付后复核 — 「归派发席:达档席内审,未达档循保险丝起子代理;记录 = 同形评论落 PR 或卡」 → 「只 spec 与
skills 车道欠,每轮达档:席内审或起子代理;双肢命中即 spec 车道」 (the record's shape lives in
contract-review.md :27–:29).
- :640 ADDED (rule 3, where the enqueue gate lives) — 「子代理起不来 ⇒ 复核缺席,PR
留 draft 队列外等档;唯一旁路是维护者亲审,逐次为准。」
- :646 pointer — 「席内复核的适用面」 → 「契约复核的适用面」.
- RETIRED, paying for :640 — the former :672 「报告席记条款②默认档 FAIL
率入复审清单;超改制前达档史值 ⇒ 决策卡交维护者定回退。」 Reason: it meters the FAIL rate of
DEFAULT-TIER clause-② reviews, and under rules 1–2 no lane performs one
(spec and skills review at tier; other lanes review nothing), so the
metric's population is empty. It sits outside the claim's declared
SKILL.md bands; it is the one tier-keyed line in the file the ruling
empties, and paying in-file required it. Re-wrap is not currency: no
line was re-flowed.
**contract-review.md** — thirteen in-place rewrites:
- Title and :23 — 「(席内)」 → 「(按车道)」.
- :9, :11, :12, :14 — 「席内契约复核 / 席内复核」 → 「达档契约复核 / 达档复核」: the review is
at tier wherever it happens, and "in-seat" was the 09-16 key.
- :25 — 「交付后收集复核当轮席内完成;借复核不移卡,新 spec 工作恒归 spec 席」 → 「交付后收集复核当轮完成;只 spec
与 skills 车道欠,新 spec 工作恒归 spec 席」. The "borrow the isolated review" route
is what rule 2 closes for other lanes.
- :26 — 「按档位:达档席内审契约增量;未达档 ⛔ 不自审,走保险丝路;豁免仅独立性件与保险丝」 → 「按车道:spec 与 skills
席审契约增量;达档席内审,未达档 ⛔ 不自审,起达档子代理」.
- :27 — 「达档与默认档同形」 → 「席内与子代理同形」: there is no default-tier record any
more; one shape holds between in-seat and subagent (the two scripts'
quotations of this line are updated in the same PR).
- :32 — the independence case → 「独立性件(契约真分叉、dev
挂旗)与保险丝只免席内审,不免复核:起隔离达档子代理」. This is the 「豁免仅独立性件与保险丝」 rewrite the
ruling asked for: the independence case and the fuse exempt the seat
from IN-SEAT review only, never from the review's existence. The former
third trigger 「派发后的跨车道面(含 spec)」 is lane routing now (:25).
- :40 landing pre-check ① — 「席内条款②复核 PASS 在案 …(档位按实测)」 → 「达档条款②复核 PASS
在案 …(spec、skills 每轮)」.
- :50 the fuse — 「管每个出条款②裁决的席 … 未达档 ⛔ 不自审」 → 「只管 spec 与 skills 席的条款②复核」
(the 不自审 clause stays at :26).
- :52 — 「至席内复核完成」 → 「至达档复核完成」.
- :53 — 「标签原样留置,队列外等待是安全态」 → 「起不来即无复核,标签原样、队列外等档」 (rule 3 on the fuse
side).
**core-rules.md** — one rewrite: :112 「契约卡达档复核归派发席」 → 「契约复核只 spec、skills
欠」. Trace by REST `GET /pulls/N/files`: PR #18363 (09-16) touched
SKILL.md, contract-review.md and platform-readings.md only, so the 09-16
re-key never landed in core-rules.md; :112's tier key came with PR
#18511 (09-17), replacing PR #17294's 「归 spec 席」. :122 「`references/`
席内达档复核后入队」 is left as the mirror of SKILL.md :625 (see acceptance
notes).
## Machine side
**commit 2 — `scripts/pm/dispatch-gates.mjs`.** The clause-② note and
the suspect tail that `--tier` prints (quoted into claim comments) said
「spec seat; default-tier build」 and 「in the spec seat」 — the 09-10 seat
key, which PR #18363 never re-keyed. They now name the spec and skills
lanes, the in-seat-or-subagent route, and 「a hit outside those lanes is
spec-lane work and moves there」; the docblock above
`MANDATORY_TIER_GLOBS` carries the rule. Four self-test pins hold both
renderings to the lane key and refuse the two retired spellings.
`--self-test`: 1852 cases pass (baseline on `0b31d90`: 1848).
**commit 3 — `scripts/pm/check-clause2-carriers.mjs`.**
`needsRecordRead` — C6's population gate and the sweep's read budget —
owed a record only in the completed state (a cleared `yes`), so a
spec-lane `Clause-②: no` round read 0 with no record on its head:
exactly PRs #18530 / #18529, the card's measured pair. Now:
- `LANES_OWING_REVIEW` (`domain:spec`, `domain:skills`, frozen) and
`laneOwesReview(pair)`, read off the CARD's `domain:*` labels;
unreadable labels stay UNJUDGED through the existing labels gap.
- Population: the completed state (unchanged) OR a declared `no` on a
spec/skills card (new). A `no` anywhere else still owes nothing —
pinned.
- C6's row on a spec/skills `no` round with no record: exit 4; remedy =
the lane's review at tier (in-seat or by the at-tier subagent), with the
unavailable-tier state and its one bypass named.
- A cleared `yes` OUTSIDE the two lanes keeps its row and its exit 4
(the `yes` is a limb hit, and limb-hit work is owed), but its remedy is
lane ROUTING: re-lane the item to `domain:spec` (`pm:retriage`, or split
the contract work to a spec-lane card or PR per 「新 `packages/spec` 工作恒由
`domain:spec` 席收口」), or correct a false `yes` with a
`Clause-②-correction:` comment — never a default-tier self-review, never
an at-tier subagent from that lane.
- The C6-RECORD note prescribes the clear-citation only where a clear
exists; on a spec/skills `no` round with its record it says the lane
owes the record and it exists.
- New floored battery, 34 cases (floor 30); roster floor 32 → 33.
`--self-test`: 977 cases pass (baseline 941).
Deviation from the dispatch's suggested case (c) — a `yes` pair on a
`domain:cli` card as 「not owed as a record, plus a note」 at exit 0 —
implemented instead as owed at exit 4 with the routing remedy. Reasons:
contract-review.md :42 promises 「0 = … head 上有记录」; a cli seat clearing a
`yes` pair and landing it from that lane is never a legal workflow under
rule 2 (the work 「moves there」), so an exit-0 note would be the
0-with-a-message the file's own header bans; and the row's exit is
unchanged from today (only the remedy text moves), so no legal workflow
is re-blocked. The four-axis reading is in the report's
`open_questions`; the PR is draft for the maintainer's word either way.
## Gates (run on `bc0c2ec41`, the final commit)
`node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` derived 46 families. 45 ran with exit 0
(each captured redirect-then-`$?`), among them `check:pm-skill-ratchet`,
`check:pm-skill-id-lint`, `check:skill-frame-sync`,
`check:pm-governed-prose`, `check:pm-clause2-carriers`,
`check:declared-population-live`, `check:pm-half-states`,
`check:nul-bytes`. `check:doc-formula-expressions` first exited 3 —
PREREQUISITE NOT MET (`@objectstack/formula` / `@objectstack/lint` not
built; nothing measured) — and reruns green after `turbo run build
--filter=@objectstack/formula --filter=@objectstack/lint` under the
verify lock (22 + 9 + 14 examples judged clean).
`check:pm-dispatch-gates` (430–450 s) was still running detached when
this body was written; its verdict and the `--ran` reconciliation are in
the `os-dev-report` comment on #18536. Both edited scripts' own
`--self-test` pass (above). The four package tests that mention
`dispatch-gates.mjs` do so in docblocks only and read nothing from it,
so they are not owed. The derivation printed a STALE TREE warning (the
tree is behind an `origin/main` that moved during the run); the PM's
re-derivation after the report reads the true list.
`skip-changeset`: nothing published moves — `.claude/**` and
`scripts/pm/**` ship in no package's `files[]`.
## Acceptance notes
Lines outside this card's declared file surface that carry seat or tier
wording — reported, not edited (承接者: the skills seat's next SKILL.md
round; the hot-file queue behind this card holds #18743 · #18755 ·
#18665 · #18469 PR-B · #18489):
- SKILL.md :231 「派发后发现的跨车道面(含 spec)不移卡,认领席借契约复审档隔离复核」 and core-rules.md
:62 (its mirror). The "borrow the isolated at-tier review" route is what
rule 2 closes for other lanes (no other lane spawns the at-tier
subagent). The ruling's 「moves there」 and SKILL.md :234 「已派发卡 ⛔ 不因触 spec
转席」 reconcile as "the contract WORK moves to the spec lane; the card is
not re-seated", which is how contract-review.md :25 now reads. Suggested
rewrite of :231, same byte budget: 「认领即跟到 MERGED:派发后发现的跨车道面(含
spec)不移卡,契约面工作交 spec 车道达档复核。」
- SKILL.md :608 「技能面 hunk 须由契约复审档的席复核,档外席先交 skills 席」, :623 「由本席按达档自审」,
:625 「经席内达档复核后 ready → 入队」 and core-rules.md :122 「`references/`
席内达档复核后入队」: consistent with rule 1 read as "the skills lane's review at
tier, in-seat or by subagent"; the word 席内 there is the 09-16 spelling
and could be read as in-seat-only.
- `references/lanes/spec.md` :19–:33 and `references/lanes/skills.md`:
no contradiction found; `lanes/skills.md` :13 「契约复核归派发席席内」 is ownership
wording (the seat owns the record it adopts) and stays true.
- The follow-up the ruling assigns to the lane seats after this lands,
not to this card: re-read the `needs:contract-review` carriers hung
outside the spec/skills lanes (the director's ledger ⑫) and re-lane or
strip each.
- Sweep-mode cost: `check-clause2-carriers.mjs` without `--pair` now
buys the PR thread for every spec/skills-lane `no` pair too (one read
per PR, cached per PR); the budget paragraph in the file's header is
unchanged in shape.
## 维护者速读(草稿)
**改了什么**:把契约复核的归属重新按「车道」写回章程:只有 spec 与 skills
两条车道的每一轮交付都要过契约复审档的复核——席位达档就席内审,不达档就起达档子代理;其余车道零契约复核,条款②命中即 spec
车道的活;达档子代理起不来时 PR 留 draft、队列外等档,唯一旁路是您亲审。三份章程文本行数不变(812 / 60 /
151),两个机读脚本(`--tier` 的提示行、`--pair` 的复核记录人口)同步改成按车道判。
**为什么改**:9-16 那次把「只在 spec 席」改成了「按席位实测档」,席位名单没了,于是同一张 `Clause-②: no` 的
spec 车道 PR 在两条细则下答案相反(#18536 的两种读法),而账号级 429 让「等档位」在两种读法里含义完全不同。您 9-17
的裁决把车道规则说回来了,本 PR 只是把它落到文本和脚本上。
**风险与代价(含回滚)**:`--pair` 从此对 spec/skills 车道的 `no` 轮也要求 head 上有复核记录,没有就退
4——这正是 #18530 / #18529 该有的读数,但意味着这两条车道的 `no` 轮在记录落下前都不能入队;其它车道不受影响,`yes`
挂在别的车道上的旧读数(退 4)不变,只是补救措施从「自审」改成「改道 spec」。回滚 = revert 这三个
commit,文本与脚本一起回到 `0b31d90`。
**席位意见**:(留空)
**你要做的**:确认本 PR 是否如实落了您的车道规则,是则合并;合并后各车道席按裁决去清理挂在 spec/skills 之外的
`needs:contract-review` 载体。
---
_Generated by [Claude
Code](https://claude.ai/code/session_01BTeBejoPUvRHN8WdAJC6oF)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 554e928 commit e15870f
5 files changed
Lines changed: 280 additions & 76 deletions
File tree
- .claude/skills/pm-dispatch
- references
- scripts/pm
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
509 | 509 | | |
510 | 510 | | |
511 | 511 | | |
512 | | - | |
| 512 | + | |
513 | 513 | | |
514 | 514 | | |
515 | 515 | | |
| |||
519 | 519 | | |
520 | 520 | | |
521 | 521 | | |
522 | | - | |
| 522 | + | |
523 | 523 | | |
524 | 524 | | |
525 | 525 | | |
| |||
633 | 633 | | |
634 | 634 | | |
635 | 635 | | |
636 | | - | |
| 636 | + | |
637 | 637 | | |
638 | 638 | | |
639 | | - | |
| 639 | + | |
| 640 | + | |
640 | 641 | | |
641 | 642 | | |
642 | 643 | | |
643 | 644 | | |
644 | 645 | | |
645 | 646 | | |
646 | | - | |
| 647 | + | |
647 | 648 | | |
648 | 649 | | |
649 | 650 | | |
| |||
669 | 670 | | |
670 | 671 | | |
671 | 672 | | |
672 | | - | |
673 | 673 | | |
674 | 674 | | |
675 | 675 | | |
| |||
Lines changed: 14 additions & 14 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | | - | |
| 1 | + | |
2 | 2 | | |
3 | 3 | | |
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
7 | 7 | | |
8 | 8 | | |
9 | | - | |
| 9 | + | |
10 | 10 | | |
11 | | - | |
12 | | - | |
| 11 | + | |
| 12 | + | |
13 | 13 | | |
14 | | - | |
| 14 | + | |
15 | 15 | | |
16 | 16 | | |
17 | 17 | | |
| |||
20 | 20 | | |
21 | 21 | | |
22 | 22 | | |
23 | | - | |
| 23 | + | |
24 | 24 | | |
25 | | - | |
26 | | - | |
27 | | - | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
28 | 28 | | |
29 | 29 | | |
30 | 30 | | |
31 | 31 | | |
32 | | - | |
| 32 | + | |
33 | 33 | | |
34 | 34 | | |
35 | 35 | | |
36 | 36 | | |
37 | 37 | | |
38 | 38 | | |
39 | 39 | | |
40 | | - | |
| 40 | + | |
41 | 41 | | |
42 | 42 | | |
43 | 43 | | |
| |||
47 | 47 | | |
48 | 48 | | |
49 | 49 | | |
50 | | - | |
| 50 | + | |
51 | 51 | | |
52 | | - | |
53 | | - | |
| 52 | + | |
| 53 | + | |
54 | 54 | | |
55 | 55 | | |
56 | 56 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
109 | 109 | | |
110 | 110 | | |
111 | 111 | | |
112 | | - | |
| 112 | + | |
113 | 113 | | |
114 | 114 | | |
115 | 115 | | |
| |||
0 commit comments