Repository navigation
Commit 3230308
docs(pm-dispatch): a fork PR closes in the same stroke that closes its card (#19895)
Fixes #19883
Clause-②: no
## 维护者速读(草稿)
**改了什么**:三份 PM 协议细则文件,共 +5 / −3 行。① 采纳了 fork PR 的卡,正文恒带一行行首 `Adopts:
#M`(M = 那个 fork PR 的号),立卡那一笔就写;② 落地收口清单(确认
MERGED、给卡收口的那一笔)加一行:卡带这一行,就在同一动作里给 fork PR 贴致谢 + 落地链接并关闭它;需求被拒(卡关
not_planned)时,关卡那一笔同样贴致谢与谢绝理由并关闭;③ 分诊席每次读的职责文件加一行:每次 fire 扫开着的 fork
PR,没卡就立卡,卡已关而 fork PR 还开着就补关。两份有行数上限的文件各加一行、各退一行同文件里已原样写着的重复内容,上限不动。
**为什么改**:维护者原话「这种pr应该写个评论直接关闭。」「"这种 PR"都应该这样处理,开一个 skills 卡片」。外部贡献者的 PR
在需求已由内部 PR 落地后被晾了两天没人关:这条规则只写在一份落地时没有席位会读的文件里,落地收口清单里没有这一步,卡上指向 fork PR
的只是一段散文。另外核实发现,「分诊每次 fire 扫 fork PR」这一步本身也不在分诊席每次读的职责文件里,只在那份没人读的文件里。
**风险与代价(含回滚)**:纯文本改动:不加门禁、不加脚本、不加巡查行、不动行数上限。代价是分诊每次 fire 多一次「列出开着的
PR」的读取(当前 23 个开放 PR,0 个 fork),以及卡正文多一种行首拼写
`Adopts:`。退掉的两行,其每一句在同一文件里都还在(逐条对照见下文表格)。回滚 = revert 这一个 squash commit。
**席位意见**:
**你要做的**:无需点击。本 PR 只碰 `.claude/**`,属 Tier S,由 skills 席在席内契约复核 PASS
后走队列落地。若不希望分诊每次 fire 都扫 fork PR(只保留落地那一笔),在本 PR 评论一句即可,删掉 triage 那一行。
## What this PR does
Three files under `.claude/skills/pm-dispatch/references/`, +5 / −3.
1. **`external-contributions.md`** (the fork-PR playbook)
- Step ①: the card behind a fork PR always carries a line-start `Adopts:
#M` (M = the fork PR's number). It is written in the stroke that files
the card, or that attaches the fork PR to a card that already exists.
The landing closeout and the triage catch-up read only this line.
- Step ③: the line "after the internal PR lands, close the fork PR with
thanks and the landing link" becomes: the stroke that closes the card
also closes the fork PR its `Adopts:` line names. On `completed` the
closing comment carries thanks and the landing link. On `not_planned` it
carries thanks and the reason the need was declined. The declined case
had no text before.
- The sentence that limits a seat's comments on a fork PR is rewritten,
not worked around. It used to say "only these two kinds" after naming
review comments and the close. It now names them: apart from step ①'s
fixed comment, a seat writes review comments and the closing comment,
and nothing else. The declined form is the closing comment's second
form, not a third kind.
2. **`landing-operations.md` §B, the MERGED closeout**, the home of "the
landing stroke": one line directly after the `Part of` / `Fixes`
closeout line. A card closing `completed` with an `Adopts:` line means
the same action posts thanks and the landing link on that fork PR and
closes it.
3. **`triage-duties.md`**: one line at the end of the backlog-sweep
block. Every fire also scans open fork PRs. No card: file one per step
①. Card already closed: close the fork PR per step ③.
## Why this shape — the four axes
The card offers two candidates, A (at the landing step) and B (in the
triage sweep), and leaves the shape to the seat. `SKILL.md`'s order for
a failure fix is: remove the construct that allows the error, make the
right form the only spelling, and only then add a check.
- **Remove the construct.** The miss was allowed because the close was a
separate, later act. It was written only in `external-contributions.md`,
and no seat reads that file at MERGED. The landing seat reads
`landing-operations.md`, whose closeout list had no fork-PR item. The
card named its fork PR only in free prose: an "Adoption of PR" section
and a promise to close it "in the landing stroke". Shape A removes the
construct: the close is now an item of the closeout list itself, in the
same action that closes the card.
- **One spelling.** `Adopts: #M`, line-start and undecorated, the same
form as `Blocked-by:`. It has three named readers: the landing closeout,
the triage catch-up, and step ①'s "does this fork PR already have a
card". Measured on the one case: the fork PR's timeline carries **10**
cross-references from other issues and PRs, one of them its card. The
line is what picks the card out of the ten.
- **Check.** None is added: no gate, no half-state row, no ratchet.
Neither ruling sentence names one, and a new gate defaults to no. B is
one prose duty line on a scan the triage seat already owes by step ①. It
is not a patrol row.
- **Why B at all, with A in place.** The declined case has no other
reader. A `not_planned` close happens in several seats' flows: a triage
first-touch close, a decision-box answer, a falsified premise. The one
file every seat reads is `SKILL.md`, which sits at 319 / 319 and is held
by PR #19890. The triage fire is the one reader that sees every closed
card's fork PR, and it is also the backstop if a landing stroke misses
one.
- **Real business need (measured).** Fork PRs are rare: 1 among the 500
most recent closed PRs (created 2026-09-17T18:37Z .. 2026-09-23T14:41Z).
That one is the fork PR that was missed. 0 of 23 open PRs are forks at
2026-09-23T15:29:57Z. The maintainer's word covers every such PR, and
the one miss cost an outside contributor two days of silence.
- **Startup focus.** +2 lines net across the corpus. Both ceilinged
files stay at their ceilings: `landing-operations.md` 101 / 101,
`triage-duties.md` 120 / 120. `external-contributions.md` goes 16 → 18;
that file is not in the ceiling map.
- **Rejected alternative.** Teach `scripts/pm/close-cards.mjs` to read
`Adopts:` and refuse a card close that does not also close the fork PR.
That is structurally the strongest shape, but it is a new mechanical
check that no maintainer word names, and it is outside this card's
claimed file surface.
## Premise check, on `origin/main` at `dabf8d7`
- **Confirmed.** The rule's only carrier was `external-contributions.md`
(the old line 13), as prose. Fork PR #19342 stayed open from the
internal landing to 2026-09-23T14:42:37Z. Card #19489 is `closed` /
`completed` (2026-09-21T10:32:18Z). PR #19534 merged
2026-09-21T10:32:16Z as `2aac821a8c`.
- **Correction to one reading in the dispatch.** `triage-duties.md` had
**no** fork-PR scan line. `git grep -c -i fork dabf8d7 --
…/triage-duties.md` exits 1 (zero hits). The control on the same tree
and term, `…/external-contributions.md`, hits 8 and exits 0. Step ①'s
scan lived only in `external-contributions.md`, and `SKILL.md`'s
phase-file index names no reader for that file. The pre-relocation
`SKILL.md` (`f151ef2^`) pointed at it only from the enqueue-and-landing
section. So candidate B's premise, "step ① already scans open PRs on
every fire", held on paper only. The new triage line is what gives step
① a reader.
- The dispatch was cut at `e9eb2244d5`; this worktree starts at
`dabf8d7`. `git diff --stat e9eb224 dabf8d7 --
.claude/skills/pm-dispatch/` is empty.
## Retired lines: every clause is still stated in the same file
| retired | where each clause still stands |
|---|---|
| `landing-operations.md` old :19, "follow to MERGED; after enqueue the
watch belongs to the lane PM's landing window: every round read the
queue branch and `origin/main`" | §B heading 「跟到 MERGED 为止;入队后的看护归车道 PM
落地窗口」 and §B 「确认 MERGED 要两个读数:每轮同时读队列分支与 `origin/main`。」 |
| `triage-duties.md` old :8, "the tool-loading discipline binds only the
triage fresh session's opening; execution seats and devs are not bound"
| line 3: the file is the triage seat's alone. Line 6 「fire
开局只按名加载…判定本轮有活才加载其余」 and line 7 「⛔ 分诊 fresh session 开局不做泛关键词 ToolSearch」
are both scoped to the opening already. |
Neither retired line is quoted anywhere else in the tree (`git grep`
over both texts: the only hits are the lines themselves).
## Gates, on `4a3242c`
`node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` derives 18 families from the 3 changed
paths, merge base `dabf8d795`. All 18 were run, each exit captured
before any pipe. `--ran` verdict: `✓ dispatch-gates --ran: 18 derived
famil(ies) accounted for — 18 run, 0 NOT-MEASURED (a DERIVED zero — all
18 recorded an exit code and none of them is 3).`
- `pnpm check:pm-skill-ratchet`: exit 0. `landing-operations.md is 101
lines (ceiling 101; headroom 0)`; `triage-duties.md is 120 lines
(ceiling 120; headroom 0)`. Every changed line is 120 bytes or less.
- `pnpm check:pm-skill-id-lint`: exit 0, `34 file(s) clean (pattern
/#[0-9]{3,}/g)`. No rule line cites a tracker number.
- `pnpm check:pm-governed-prose` (named by the dispatch, outside the
derivation): exit 0.
- `pnpm check:nul-bytes`: exit 0, `no raw ASCII control bytes`.
- `pnpm check:skill-frame-sync`: exit 0.
- `pnpm check:pm-half-states`: exit 0, `4912 cases pass`.
- `pnpm check:pm-governed-merges`: exit 0.
- `pnpm --filter @objectstack/lint run check:doc-formula-expressions`:
the first run exited 3 (PREREQUISITE NOT MET, `@objectstack/formula` and
`@objectstack/lint` not built). After `turbo run build` for those two
packages under the verify lock (VERDICT command-exit 0), the re-run
exited 0.
- The other 10 derived families (closing-keyword parity ×2, comment-mask
corpus, harness-current, agent-test spelling, cross-package test inputs,
doc authoring, driver-memory census, gitlink, refd-timer probe,
watch-hint literal): exit 0.
No package is touched, so there is no build closure and no package test
to run. Nothing here is published (`.claude/**`), so this PR carries
`skip-changeset`.
## Acceptance notes
- `SKILL.md`'s phase-file index names no reader for
`references/external-contributions.md`. After this PR both executing
seats' files point at it (`landing-operations.md` line 7,
`triage-duties.md`), so no index row is owed. Carrier: none.
- `references/external-contributions.md` is not in the line ratchet's
ceiling map, while the other `pm-dispatch` reference files are. Stated
as a fact, not a gap. Carrier: none.
- A fork-PR card that closes as `duplicate` is not covered: its
`Adopts:` line would need to move to the surviving card. There are 0
instances. Carrier: none.
- `check-half-states.mjs`'s H65 message quotes `triage-duties.md` as
「选层按 fire 时刻,⛔ 不用计数器;简报写明本轮跑的层」, but the file says 「选层按 fire 时刻 ⛔
不用计数器,简报写明层」. This is report-only message text. Carrier: none.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01A22sUB3mUWs6M36VgfijBq)_
Co-authored-by: Claude <noreply@anthropic.com>1 parent 029d8a4 commit 3230308
3 files changed
Lines changed: 5 additions & 3 deletions
File tree
- .claude/skills/pm-dispatch/references
Lines changed: 3 additions & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
| 7 | + | |
7 | 8 | | |
8 | 9 | | |
9 | 10 | | |
10 | 11 | | |
11 | 12 | | |
12 | 13 | | |
13 | | - | |
| 14 | + | |
| 15 | + | |
14 | 16 | | |
15 | 17 | | |
16 | 18 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
16 | 16 | | |
17 | 17 | | |
18 | 18 | | |
19 | | - | |
20 | 19 | | |
21 | 20 | | |
22 | 21 | | |
| |||
66 | 65 | | |
67 | 66 | | |
68 | 67 | | |
| 68 | + | |
69 | 69 | | |
70 | 70 | | |
71 | 71 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
5 | 5 | | |
6 | 6 | | |
7 | 7 | | |
8 | | - | |
9 | 8 | | |
10 | 9 | | |
11 | 10 | | |
| |||
21 | 20 | | |
22 | 21 | | |
23 | 22 | | |
| 23 | + | |
24 | 24 | | |
25 | 25 | | |
26 | 26 | | |
| |||
0 commit comments