Repository navigation
Commit fa494d0
feat(pm): a project seat's patrol is part of seating, recorded on the stub, and its absence is a half-state reading (#28687)
<!-- claude-projects-attribution: {"github_login":"marchtian"} -->
_Requested by **田騏** · [project
thread](https://claude.ai/code/project/chan_01VmuUhXU9SSUzqJYyXSmYSi?thread=cmsg_01VmuUhXU9SSUzqJYyXSmYSiHXKqxUqDPJKZyNHP7BoqTb)_
Fixes #28642
Clause-②: no
Metadata-protocol: none
## 维护者速读
- **改了什么**:项目席坐席时同一动作自建自绑的巡查 routine,座位贴存根记 `Patrol: TRIGGER_ID · CRON`
行,收班同笔删掉;队列空是待命而非停查,「连查几次没变化就停」的习惯不适用;半状态巡查新增两行只读读数:H70(在任项目席的座位贴没有
`Patrol:` 行)、H71(有 🟢 席位的车道上,无人认领且超过 2 小时无动静的 `pm:queue` 卡)。
- **为什么改**:#28560(p1)在 spec 车道席位在任、手上 0 张卡的情况下排队 4.5
小时无人认领——该席从未建巡查,队列清空后停了定时查;规则原本写着,但建巡查不在坐席步骤里,座位贴也看不出缺巡查,也没有读数会报警。
- **风险与回滚**:规则文本只改写既有行,三个文件净行数均为 0,SKILL.md
未动;两行读数只报告、不挡落地、不写标签。首跑实测:H70 报 16 个座位贴(规则刚立,现有存根只有 #19287 写了巡查行),H71 报
3 张卡。回滚 = revert 本 PR。
- **先看**:①
`.claude/skills/pm-dispatch/references/seat-lifecycle.md`〈座位贴协议〉首两行与〈停止条件与待命〉新两行;②
`seat-post-protocol.md` 存根行与〈座位 Routine 创建〉末行;③
`scripts/pm/check-half-states.mjs` 的 `h70HeldSeatWithoutPatrol` /
`h71UnclaimedOnDutyLane`。
- **两分钟验证**:`node scripts/pm/check-half-states.mjs --self-test`,预期 exit
0,末行电池清单含 `H70/H71 seat patrol 27/26`。
- **需要你做的**:批准本 PR。分诊裁定这是规则变更、以你的批准为裁决;卡上引的原话「为什么会停了定时巡查,skills
是否需要优化」是提问而非明令加巡查行,所以你的批准同时作为 H70/H71 两行新读数的授权(仪器纪律:新增巡查行须有维护者原话)。
## What changes
**Rules text** (`.claude/skills/pm-dispatch/references/`, every line
rewritten in place; no ceiling moved):
- `seat-lifecycle.md` 〈座位贴协议〉 — the rule's one home: seating creates the
self-bound patrol routine, records `Patrol:` on the stub and sends
`seat-online` in the same act; the sign-off deletes it in the same act
as `seat-offline`. The key pointer moved to the next line.
- `seat-lifecycle.md` 〈停止条件与待命〉 — an empty queue is standby: the patrol
runs until sign-off, only the maintainer's order stops it, and the "stop
after N quiet checks" habit does not apply. The patrol prompt claims up
to the seat's `batch` limit first, then runs the five standby items
listed right below it (`pm:blocked` / `pm:on-hold` unlock, in-flight PRs
to MERGED, and the rest).
- `seat-lifecycle.md` 〈断粮与跨墙恢复〉 — the old line 「项目席巡查 routine
默认每小时跑各车道待命项…」 is deleted; each part has a home already: hourly is
`seat-post-protocol.md` 〈座位 Routine 创建〉, the standby items are the new
prompt line, the wall note is the stub's line, no cron chain is SKILL.md
〈唤醒〉.
- `seat-post-protocol.md` — the stub adds a `Patrol:` line; 〈座位 Routine
创建〉 names its spelling `Patrol: TRIGGER_ID · CRON` as where the current
value lives (the director seat has none).
- `project-forms.md` 〈席位是读出来的〉 — 「首次坐席自建」 becomes 「坐席即建巡查 routine」 with
a pointer to `seat-lifecycle.md`.
**Half-state rows** (`scripts/pm/check-half-states.mjs`, report-only,
zero extra requests — both read listings the sweep already holds):
- **H70** (`state`) — a held `pm:seat` post (🟢 a login; not `🟢 Routine`,
not the director seat, not vacant or paused) whose body has no `Patrol:`
line. The shared directive reader is used, so `- **Patrol:** …` counts
as a line. The value is not judged.
- **H71** (`stall`) — an open, unassigned `pm:queue` card (not
`pm:epic`) on a lane where some seat post reads 🟢, quiet for more than
`QUEUE_ON_DUTY_UNCLAIMED_HOURS` = 2. The clock is `updated_at`
(H10/H30's clock), so the age is a lower bound on time in the queue.
- Self-test battery `H70/H71 seat patrol`, 27 cases, floor 26: firing
cases, the live decorated spelling, out-of-scope arms (Routine,
director, vacant/paused, assigned, epic, closed, PR rows), the 2 h
boundary, an unreadable stamp, the lane alias, and the band and push
registry.
## Tests
- `node scripts/pm/check-half-states.mjs --self-test` → exit 0, `5158
cases pass`, battery `H70/H71 seat patrol 27/26`.
- Live sweep (read-only), tree `f4027cfe`, 2026-11-01: H70 fired on 16
seat posts (#6026 #6367 #7623 #9831 #12224 #18883 #18917 #19357 #20163
#20966 #24145 #25966 #26620 #26761 #27737 #28677). #19287 was clean
because it carries a decorated `Patrol:` line. The triage Routine post
and the vacant director post were not judged. H71 fired on 3 cards:
#25918 (~28h, `domain:ui`), #25921 (~7h, `domain:ui`), #27890 (~58h,
`domain:skills`).
## Acceptance notes
- **The `Notify: seat-online` key is not extended.** The card says the
`seat-online` record carries the patrol line. The mailbox key template
is parsed by `record-recognisers.mjs`, so adding a field there changes
the recogniser grammar, which is outside this claim's file surface and
belongs to the mailbox-keys card serial behind this one. The patrol line
lives on the stub only.
- **H71 drops "spare concurrency".** No seat post states its cap in
machine-readable form, and a cap can span several lane seats of one
account. The row says so in its own text: a full seat reads the same as
an idle one.
- **`dispatch-runbook.md` is not edited.** Item ④ lives in
`seat-lifecycle.md` 〈停止条件与待命〉, next to the standby list it points at.
- **"含前提过时" is gone with the deleted 〈断粮〉 line.** No standby item
carried it before either.
- **No director exemption on the seating line itself (no room in the
120-byte budget).** `project-forms.md` (总监席不建), `seat-post-protocol.md`
(总监席无) and SKILL.md 〈唤醒〉 each carry the exemption.
- **H70 reuses `seatIsHeld`.** So a 🟢 title whose first token is not a
login (`🟢 held …`, `🟢 session_…`) reads as held, as it does for H32.
- **Tier.** The governed register puts `.claude/**` at Tier S. Triage's
binding note makes the maintainer's approval the ruling for this rule
change, so this PR waits for that approval either way.
## Verification
<details><summary>Rules ratchets — head 58de368</summary>
- `pnpm check:pm-skill-ratchet` → exit 0 (project-forms 54/54,
seat-lifecycle 96/96, seat-post-protocol 88/88; every edited line ≤ 120
bytes)
- `pnpm check:pm-skill-id-lint` → exit 0
- `pnpm check:skill-frame-sync` → exit 0
- `pnpm check:pm-governed-prose` → exit 0
</details>
<details><summary>Derived gate list — head 58de368</summary>
`node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` gave 55 commands. All 55 ran and exited 0.
`check:doc-formula-expressions` first exited 3 (PREREQUISITE NOT MET:
formula/lint not built). It exited 0 after `turbo run build
--filter=@objectstack/formula --filter=@objectstack/lint`. `--ran`
reconciliation: `55 derived famil(ies) accounted for — 55 run, 0
NOT-MEASURED (a DERIVED zero — all 55 recorded an exit code and none of
them is 3)`.
Roster families under the touched directories, run as readings:
`check-published-list-mirrors`, `check:dts-closure`,
`check:engine-double-contract`, `check:i18n-stale-fill`,
`check:pm-label-desc-cap` and `check:pm-settings-deny-roster` all exited
0. `check:published-readme-exports` is NOT MEASURED: exit 3,
PREREQUISITE NOT MET, 63 packages not built. CI runs it.
</details>
No published package is touched (`.claude/**` and `scripts/pm/**` only),
so this PR carries `skip-changeset` and no changeset.
Session: `session_017ihL2g7RyXa8ErM6GAkHyc` (os-dev for
`domain:skills#2`).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent e1fede6 commit fa494d0
4 files changed
Lines changed: 196 additions & 8 deletions
File tree
- .claude/skills/pm-dispatch/references
- scripts/pm
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
23 | 23 | | |
24 | 24 | | |
25 | 25 | | |
26 | | - | |
| 26 | + | |
27 | 27 | | |
28 | 28 | | |
29 | 29 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
38 | 38 | | |
39 | 39 | | |
40 | 40 | | |
41 | | - | |
42 | | - | |
| 41 | + | |
| 42 | + | |
43 | 43 | | |
44 | 44 | | |
45 | 45 | | |
| |||
85 | 85 | | |
86 | 86 | | |
87 | 87 | | |
88 | | - | |
| 88 | + | |
| 89 | + | |
89 | 90 | | |
90 | 91 | | |
91 | 92 | | |
92 | 93 | | |
93 | 94 | | |
94 | 95 | | |
95 | 96 | | |
96 | | - | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
15 | 15 | | |
16 | 16 | | |
17 | 17 | | |
18 | | - | |
| 18 | + | |
19 | 19 | | |
20 | 20 | | |
21 | 21 | | |
| |||
63 | 63 | | |
64 | 64 | | |
65 | 65 | | |
66 | | - | |
| 66 | + | |
67 | 67 | | |
68 | 68 | | |
69 | 69 | | |
| |||
0 commit comments