Skip to content

fix(pm): H4 prose and the pm:blocked readings release a PR target on merge, never on close - #28031

Merged
objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/project-thread-vo8914
Oct 30, 2026
Merged

objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/project-thread-vo8914

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Elon · project thread

Fixes #27989

Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么:Blocked-by: 目标是 PR 时,三处读器从「关闭即放」改为「合并才放」:H4 散文半边把「关而未合并」单独成桶、渲染 CLOSED UNMERGED、不再说「due back in pm:queue」;SKILL.md 的 pm:blocked 行改为「关单(PR 则合并,关而未合并不放)时放回」;platform-readings.md 改为 PR 判合并走 pull_request_read/REST,不再点名 issue_read。
  • 为什么改:解析器已给废弃 PR 打 unmerged: true,但 H4 散文子句仍按 state === 'closed' 分桶,把卡在废弃 PR 上的卡报成「条件已满足、该回队」;MCP issue_read 实测分不出合并与废弃(记录 6516593752)。
  • 风险与回滚:只有 report-only 行文与规则文本,无新门禁、无标签写入;两处规则行各自行数持平(319/319、458/458),字节在天花板内。回滚 = revert 本 PR 的一个 commit。
  • 先看:scripts/pm/check-half-states.mjs proseBlockerClause(新 docblock 段落与 abandoned 桶)→ 9 个 H4 prose PR: 钉子 → 两行规则文本 diff。
  • 两分钟验证:node scripts/pm/check-half-states.mjs --self-test && pnpm check:pm-skill-ratchet && pnpm check:pm-skill-id-lint,预期全绿。
  • 需要你做的:无。本 PR 欠契约复核(席位以 CONTRACT_REVIEW_TIER 出记录),落地档以 GOVERNED_SURFACES 登记为准(--branch 预读:Tier S,.claude/** ×2),席位落地。

What changes

After the unlock sweep learned that a Blocked-by: line can name a PR, and that a PR target releases on merged, three readers still spoke of a PR target as "closed". Card items 1–3; items 4 and 5 are read below.

Item 1 — proseBlockerClause (H4 prose half), scripts/pm/check-half-states.mjs. The resolver blockerResolutionFromRow already keeps state: 'closed' for an abandoned PR and marks it unmerged: true, but the clause bucketed every closed row as a satisfied condition and told the seat the card was "due back in pm:queue" — for a prose-named PR that closed without merging, a release that never happened. Now:

  • namedProseCandidates renders such a row as `#N` CLOSED UNMERGED (closed_at).
  • The verdict has an abandoned bucket. A merged PR (closed, no mark) still reads "ALREADY CLOSED … due back in pm:queue" — the released direction is unchanged. All-abandoned reads "closed UNMERGED — an abandoned attempt releases nothing … the block still stands; the target wants re-pointing". Mixed readings count only merges/issue closes as expired, and an abandoned close beside an open target no longer claims "They all still read OPEN".
  • Nine H4 prose PR: pins drive the measured GET /issues/N shapes (fix(rest): a refused xlsx export answers the 400 envelope, not a 200 workbook stream #27888 merged, pm:mailbox — sunhaolin #27296 closed unmerged, the same rows H19's battery pins) through the sweep's own projection into the clause. Report-only: H4 writes no label, and the clause still ends with "fix it by writing the Blocked-by: #N line".

Item 2 — .claude/skills/pm-dispatch/SKILL.md, the pm:blocked status row. 「#N 关闭时由解锁扫描或 scheduled-full-run-card.yml 刷新放回」 → 「#N 关单(PR 则合并,关而未合并不放)时由解锁扫描或 scheduled-full-run-card.yml 放回」, agreeing with state-machine.md 「合并才放,关而未合并 ⛔ 不放」. In-row, 319 lines kept. The row sits at the file's MAX_TABLE_ROW_BYTES ceiling (341), so three words were tightened in the same row to pay for the new clause: 「刷新放回」→「放回」, 「工已完、PR 被外部门禁卡住的」→「工已完而被门禁卡住的」, 「正文或线程行首」→「双通道行首」 (the term the hold/blocked line below already uses), 「挂本态直至其」→「直至其」. No rule was dropped.

Item 3 — .claude/skills/pm-dispatch/references/platform-readings.md. The unlock-scan line named issue_read + merged_at; measurement 6516593752 shows MCP issue_read on a PR number answers state: "closed" with no pull_request key, so it cannot tell a merge from an abandonment. The line now reads 「解锁扫描 list_issues 列卡、逐目标直读,⛔ 不加常设评论读;PR 合并走 pull_request_read/REST。」 — exactly 120 bytes, the MAX_LINE_BYTES ceiling, which is why the REST read is named without its merged_at field and issue_read is excluded by omission rather than by a ⛔ clause. 458 lines kept.

Item 4 — read, not edited. Triage 6516946612 rules: no new gate; triage's unlock scan already lists a closed-unmerged PR target as not released; at most the pm:blocked row says so. The row now says 「关而未合并不放」; state-machine.md is untouched and H26 is not extended.

Item 5 — H28 wording. Not touched: the diff does not reach H28.

Verification

  • node scripts/pm/check-half-states.mjs --self-test → 5098 cases pass (was 5089).
  • Ablation, three legs, each restored to the committed file afterwards (git diff empty): (A) the closed bucket counts unmerged rows again → 6 of the new pins red; (B) the render drops the UNMERGED word → 1 red; (C) the all-abandoned branch removed → 1 red. The two merged-PR pins pin the direction that was already right and are not reachable by an ablation of this diff.
  • pnpm check:pm-skill-id-lint → 105 files clean. pnpm check:pm-skill-ratchet → green (SKILL.md row 341/341, platform-readings line 120/120). node scripts/check-skills-token-ratchet.mjs → green.
  • Gate families from node scripts/pm/dispatch-gates.mjs --commands --changed: 58 commands, all run serially on the committed head 45763c7 in a fresh worktree after pnpm install --frozen-lockfile --ignore-scripts and the @objectstack/lint... build, each exit code captured before any pipe; all 58 exit 0; dispatch-gates --ran reconciliation: 58 derived, 58 run, 0 NOT-MEASURED, 0 UNRUN (exit 0); the derivation recomputed after the commit is the same 58.
  • node scripts/pm/check-governed-merges.mjs --branch claude/project-thread-vo8914 → governed .claude/** ×2, Tier S; scripts/pm/check-half-states.mjs not on the register.
  • The project-context step "assign and request a review from the requester at PR creation" was not performed: SKILL.md 〈路径分叉〉 says 「pr_create 不请不派」; the hand-off is the seat's after ACCEPT.

Human Minimum

  • 1. Schemas start as Zod; packages/spec holds no runtime logic. (not touched)
  • 2. Every name I added uses the casing for its kind.
  • 3. No formatter ran; pnpm lint passes on the touched script.
  • 4. One issue, one branch off an up-to-date main, one pull request.
  • 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset. (nothing publishes; skip-changeset)
  • 6. packages/spec touched: its generated artifacts are regenerated and committed. (not touched)
  • 7. No hand edits to content/docs/references/, packages/*/CHANGELOG.md or content/docs/releases/.
  • 8. Title, body and comments are in English.
  • The Before Submitting list in CONTRIBUTING.md is done.

🤖 Generated with Claude Code

https://claude.ai/code/session_016WV4d17R6AtQwLrxguoYs1

…merge, never on close

After the unlock sweep learned that a `Blocked-by:` line can name a PR and
that the PR releases on merged, three readers still spoke of a PR target as
"closed":

- `proseBlockerClause` (the H4 prose half) bucketed every `closed` candidate
  as a satisfied condition and told the seat a card parked on an abandoned
  (closed, unmerged) PR was due back in `pm:queue`. The resolver already marks
  such a row `unmerged: true`; the clause now keeps it in its own bucket,
  renders it `CLOSED UNMERGED`, never counts it among the expired, and says
  the target wants re-pointing. Nine pins cover a merged PR (released), an
  abandoned PR (withholds, no "due back"), and the mixed readings.
- The pm-dispatch SKILL.md `pm:blocked` row said a target releases when #N
  closes; it now says a PR target releases on merge and a closed-unmerged one
  does not release (line-neutral, row at its byte ceiling).
- platform-readings.md named `issue_read` + `merged_at` for the merge
  reading; the MCP issue read cannot tell a merge from an abandonment, so the
  line now names `pull_request_read` or the REST read (line-neutral).

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WV4d17R6AtQwLrxguoYs1
@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Oct 30, 2026
@github-actions github-actions Bot added size/s documentation Improvements or additions to documentation labels Oct 30, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:skills#2 · os-elon-musk · session_016WV4d1QkPVVzNz3FJqb2iS

Contract-review-claim: session_016WV4d1QkPVVzNz3FJqb2iS · head 45763c7 · 2026-10-30T09:21Z

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:skills#2 · os-elon-musk · session_016WV4d1QkPVVzNz3FJqb2iS

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 45763c74fa470b9e277be741ddf87d0366ca78c1
Local-runs: probe — check-half-states.mjs --self-test on the head plus two ablation legs (restored, git diff empty) and a read-only clause probe over eight candidate mixes, because the card's acceptance item 1 is a behavioural claim about the H4 prose clause that no check-run prints; the skill ratchet, skill-id lint and token ratchet were run read-only beside it
Scope: .claude/skills/pm-dispatch/SKILL.md (:110, the pm:blocked status row), .claude/skills/pm-dispatch/references/platform-readings.md (:252, the unlock-scan line) + scripts/pm/check-half-states.mjs (namedProseCandidates :2356, proseBlockerClause :2395-2450, blockerResolutionFromRow :5152, h19BlockOutlivedBlocker :5434-5515, h28StaleBodyBlockerLine :7640-7660, H4 prose PR pins :25023-25037); read but not reviewed: .claude/skills/pm-dispatch/references/state-machine.md :22-:27, .claude/skills/pm-dispatch/references/dispatch-runbook.md :85, scripts/ci/scheduled-full-run.mjs (staleBlockVerdict :571, targetReading :641, renderBlockNote :660)

① Derived judgments

Base is origin/main at 3f02da10f (merge base; the branch is the single commit 45763c74f). node scripts/pm/check-governed-merges.mjs --pr 28031 on the head: 2 of 3 paths hit the register, both .claude/**, Tier S; scripts/pm/check-half-states.mjs is not on the register; size 55 lines. The register and the PR body agree. Every claim the diff makes, judged against the tree and by running it:

  1. H4 prose half, the resolver contract. blockerResolutionFromRow (:5152) keeps state: 'closed' for a PR row whose pull_request.merged_at is null and adds unmerged: true; an issue row (no pull_request key) and a merged PR carry no mark. Confirmed by running it on the three row shapes (the probe prints the rows). The clause's new buckets are built on exactly that field: closed is now state === 'closed' && unmerged !== true, abandoned is state === 'closed' && unmerged === true, and open is read explicitly instead of as the remainder. TRUE as the PR body states.
  2. The three directions the card asks for, driven at the head through the sweep's own projection (blockerResolutionFromRow) and read off the rendered clause:
    • a prose-named MERGED PR (fix(rest): a refused xlsx export answers the 400 envelope, not a 200 workbook stream #27888 shape): list line #27888 CLOSED (closed_at), verdict Every one of them is ALREADY CLOSED … due back in pm:queue. Released, as before.
    • a prose-named CLOSED-UNMERGED PR (pm:mailbox — sunhaolin #27296 shape): list line #27296 CLOSED UNMERGED (closed_at), verdict Every one of them is a PR that closed UNMERGED — an abandoned attempt releases nothing … the block still stands; the target wants re-pointing. due back absent, ALREADY CLOSED absent. NOT released.
    • a closed ISSUE: identical to the merged-PR reading, due back in pm:queue present. Issues still release on closed as before.
    • abandoned beside open: None of them has released — 1 still reads OPEN, 1 closed UNMERGED (which withholds); abandoned beside unresolved; abandoned beside unresolved beside open: same shape, counts right, no ALREADY CLOSED, no due back.
    • merged beside abandoned, and closed-issue beside abandoned: 1 of 2 is ALREADY CLOSED — the block is at least partly expired; only the merge or issue close is counted, the abandoned one is named CLOSED UNMERGED on the list line.
      Branch order: the all-closed and partly-closed branches come first and now see only true releases, so the all-abandoned branch can only fire when no row released; the all-open branch is now explicit, so an abandoned row beside an open one no longer reads They all still read OPEN (the base's remainder bucket would have said exactly that). Every direction the card's item 1 names holds.
  3. The pins pin it. node scripts/pm/check-half-states.mjs --self-test on the committed head: exit 0, 5098 cases (was 5089; nine new H4 prose PR: pins, matching the PR body). Ablation at the head, each leg restored with git checkout and git diff --stat empty afterwards: (A) the closed filter back to state === 'closed' → exit 1, 6 of 5098 red, all six the new pins (NOT released, never says due back, re-point not release, neither an expiry nor all-open, never counts the unmerged close as expired, only the merge as expired); (C) the all-abandoned branch deleted → exit 1, 1 red (re-point, not release). The seat's reported counts (A: 6, C: 1) reproduce exactly. The two merged-PR pins and the closed-issue pins are the control direction and stay green under both legs, as the seat said. Leg B (dropping the UNMERGED word) was not re-run here; the seat's 1-red reading is consistent with pin names the candidate with its unmerged mark, which is the only pin that reads the list line.
  4. Report-only holds. proseBlockerClause returns a string that H4 appends to its finding; no label write, no new exit code, and the clause still ends with fix it by writing the Blocked-by: #N line whatever the states above say. H19 (:5434-5435) already excluded unmerged from closed and renders closed UNMERGED before this PR; H26 (:7264) still asks its question of open targets only; H28 (:7652) is untouched. No new gate anywhere in the diff.
  5. SKILL.md :110, the pm:blocked row. Old: 「#N 关闭时由解锁扫描或 scheduled-full-run-card.yml 刷新放回」. New: 「#N 关单(PR 则合并,关而未合并不放)时由解锁扫描或 scheduled-full-run-card.yml 放回」. True against the code: the unlock scan's reader H19 founds nothing on an unmerged PR and the refresh's staleBlockVerdict (scheduled-full-run.mjs :571-583) answers held on any unmerged row and expired only when every target merged or closed as an issue. True against state-machine.md :24 「合并才放,关而未合并 ⛔ 不放」 and consistent with :27 「上游已关 ⇒ 先重新推导是否有新阻塞并改写该行,⛔ 不反射式放行」, which the old row did not contradict either (the row names who puts the card back; :27 names what the seat does first). The four in-row payments: 「刷新放回」→「放回」 (the yml still refreshes; the verb that matters is kept); 「工已完、PR 被外部门禁卡住的」→「工已完而被门禁卡住的」 (now the same words as state-machine.md :23 「工已完而被门禁缺陷卡住者同用本态」, nothing dropped); 「正文或线程行首」→「双通道行首」 (the term SKILL.md :126 and platform-readings.md :251 already use for body-or-comment); 「挂本态直至其」→「直至其」 (the row IS the state, so the subject is implied). No rule dropped. Line count 319 → 319 (wc -l on head and on 3f02da10f); the row is 341 bytes at the head (336 on base), and check-skill-line-ratchet.mjs reads SKILL.md: widest table row is 341 bytes (pin 341; headroom 0) and 319 lines (ceiling 319), exit 0.
  6. platform-readings.md :252. Old: 「解锁扫描 list_issues 列卡、issue_read 直读目标,⛔ 不加常设评论读;PR 判合并读 merged_at。」 New: 「解锁扫描 list_issues 列卡、逐目标直读,⛔ 不加常设评论读;PR 合并走 pull_request_read/REST。」 True against measurement 6516593752: MCP issue_read on a PR number answers state: closed with no pull_request key and no merged_at on both pm:mailbox — sunhaolin #27296 (abandoned) and fix(rest): a refused xlsx export answers the 400 envelope, not a 200 workbook stream #27888 (merged), so the old line named a read that cannot make the distinction it claimed; pull_request_read get answers merged, and REST GET /issues/N carries pull_request.merged_at, which is the field blockerResolutionFromRow and scheduled-full-run.mjs :623 read. The line is 120 bytes at the head (MAX_LINE_BYTES 120, the ratchet reads platform-readings.md at 458 lines, ceiling 458, exit 0); line count 458 → 458. The seat's declared deviation (the line cannot also say never issue_read within 120 bytes) is accepted: issue_read is excluded by omission and pull_request_read is a term the same file already uses (:61, :230).
  7. Skill text hygiene. node scripts/pm/check-skill-id-lint.mjs --self-test then the lint: 105 files clean against /#[0-9]{3,}/g, exit 0; a grep of the two edited files for # followed by three or more digits finds nothing. node scripts/check-skills-token-ratchet.mjs: 56 authored bundle files within ceilings, exit 0. Both pnpm check:pm-skill-ratchet and pnpm check:pm-skill-id-lint map (package.json :75-76) to the two scripts run above with --self-test first, and both run without an install.
  8. Agreement with triage 6516946612. The ruling: no new gate; an abandoned PR target is flagged by the patrol and re-pointed by triage, not held silently; at most the pm:blocked row says so. The diff does exactly that: the row says 「关而未合并不放」, the H4 clause names the abandoned target and says 「the target wants re-pointing (to the PR's card, or to the queue with a note)」, state-machine.md and H26 are untouched, H28 is untouched (card item 5 conditions it on touching H28). Items 4 and 5 are read, not edited, as the PR body states.
  9. Scope. The diff touches the three files the claim's file surface names and nothing else; state-machine.md, named as conditional in the claim, is untouched. PR body: Fixes #27989 on the first content line, Clause-②: no, Metadata-protocol: none; label skip-changeset is right for PM tooling and skill text with no published package.
  10. Not measured here: ESLint on the touched script (no node_modules in the worktree or the checkout); the Lint · ESLint check-run on the head is success. The seat's 58-command gate reconciliation is read, not re-run.

② Semver level

No published package is touched (.claude/** and scripts/pm/** only); skip-changeset carried. Clause-②: no is right; no narrowing declared or present.

③ Boundary flags

  • Served tier versus face: the register answers Tier S on two .claude/** paths, so the governed rules-text face applies and this record is served at CONTRACT_REVIEW_TIER, the tier the queue guard will read.
  • Author: PR user.login is objectstack-fleet[bot] (attribution comment names os-elon-musk); no non-bot login to record.
  • Contract-review-claim: comment 6519369559 names the seat session and this head; no competing claim, no hold, no hand-off.
  • CI at review time (second poll, 45 check runs on 45763c74): success on Lint & Repo Gates, Lint · ESLint, Lint · repo & tooling gates, Lint · PM dispatch-gates, Lint · docs & code guards, Lint · CI release & engine gates, the three Lint · scoped jobs, TypeScript Type Check and its four sub-jobs, Governed Surface Queue Guard, Dogfood Regression Gate, Test UI, Test Core (2/8), Check Changeset, Check PR Size, PR-body guards, Live required-set sweep, Platform checklist gate sweep, Release section-coverage sweep, board snapshot, Auto Label, filter, Plan Test Core shards; still in_progress: Test Core (1/8), Live half-state sweep; the rest skipped by path filter. No failure. The final all-green reading is the landing gate's.
  • Seat report deviations: (a) the 120-byte line names pull_request_read/REST and not never issue_read, accepted in ①.6; (b) assignment and review request skipped at PR creation per SKILL.md 「pr_create 不请不派」, accepted; (c) state-machine.md untouched per triage, accepted in ①.8.
  • Premise: premise_still_valid: true; base 3f02da10f still carried 「#N 关闭时」 at SKILL.md :110 and issue_read + merged_at at platform-readings.md :252 (read off git show).
  • Open edge, not blocking: with only abandoned and unresolved rows (no open one) the mixed branch prints 0 still read OPEN, …; counts right, verdict right, wording slightly odd (should-fix 4).
  • Draft PR, mergeable_state: blocked; Tier S landing by the owning seat after this record and all-green.

④ Design judgment

Rules face, per hits file:

  • SKILL.md :110. The row is the one place the status table states when a pm:blocked card comes back, and it was false for a PR target the moment PR fix(pm): a Blocked-by PR target is released on merge in every unlock path, and H19 holds an abandoned PR #27983 let a Blocked-by: line name a PR. The edit puts the merged-not-closed rule where a dispatching seat reads it first, in six characters of parenthesis, and pays for them inside the same row rather than by widening the ceiling. Against the code it cites (the unlock scan and the full-run refresh) the sentence is now exactly what both readers do, and against state-machine.md :24 it is the same rule in the short form. The payments are the right kind: two are the state-machine's own phrasing, one is a term the file already defines by reference, one drops a subject the row itself supplies. Holds.
  • platform-readings.md :252. The old line taught a read that the measurement shows cannot answer the question (MCP issue_read flattens a merged and an abandoned PR to closed), which is the worse failure for a rules line: a seat following it would judge release on a field the tool does not return. The new line names the two reads that do return it and stops naming the tool, which is the right fix at this byte budget: the ⛔ against issue_read would be a rule about one tool's shape, and the file's convention (:61, :228-:230) is to record such shapes as measured legs, not as prohibitions. Holds; a later line stating the issue_read leg as a measured reading (the way :228-:230 do for labels) would make the omission explicit (should-fix 3).
  • The code half (not a hits file, read as the gate the rule names): the clause's bucket change is minimal and fail-closed in the one direction that matters, an abandoned PR never reads as released, and it is pinned from the API row shape rather than from a hand-built row, so a change to the resolver's unmerged contract would break these pins too. No new gate, no label write, consistent with triage's ruling that the patrol flags and triage re-points.

Implemented-by: claude/project-thread-vo8914
Reviewed-by: session_016WV4d1QkPVVzNz3FJqb2iS

VERDICT: PASS

Must-fix: none.

Should-fix:

  1. .claude/skills/pm-dispatch/references/dispatch-runbook.md :85 「旗二:pm:blocked 卡的 Blocked-by: 目标已关闭。」 still describes the patrol's flag as closed for every target; for a PR target the flag fires on merged (H19 excludes an unmerged close). One in-line word, e.g. 「目标已关单(PR 则已合并)」, governed path, outside this card's file surface. Skills seat.
  2. scripts/pm/check-half-states.mjs h28StaleBodyBlockerLine :7652 and :7659 count an abandoned PR among spent and say CLOSED (card item 5). The verdict and remedy are right (the body line is stale either way, and namedTargets renders closed UNMERGED), so wording only; the card conditions it on a PR that touches H28, and this one does not. Rides later.
  3. platform-readings.md: a measured-leg line for issue_read on a PR number (answers state: closed, no pull_request, no merged_at; measurement 6516593752) in the style of :228-:230, when the file has a line of headroom. Makes the :252 omission explicit instead of implicit.
  4. proseBlockerClause mixed branch (:2435-2440): when open.length is 0 (abandoned beside unresolved only) the sentence opens 0 still read OPEN. Drop the open clause when the count is zero. Cosmetic; counts and verdict are right.
  5. scripts/ci/scheduled-full-run.mjs renderBlockNote :661-663 renders every resolution as (closed …) in the body note; only reachable at verdict expired or assigned, where staleBlockVerdict has already excluded any unmerged row, so a merged PR is rendered closed: true, imprecise. Outside scripts/pm, noted for the same follow-up as 2.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:skills#2 · os-elon-musk · session_016WV4d1QkPVVzNz3FJqb2iS

维护者速读

  • 改了什么:Blocked-by: 指向的是 PR 时,三处读器改成「合并才放行」,PR 关闭但没合并不放行。三处是:

    • H4 散文部分:把「关闭但未合并」单列一类,显示 CLOSED UNMERGED,也不再提示「due back in pm:queue」;
    • SKILL.md:110 的 pm:blocked 行;
    • platform-readings.md:252:判断 PR 是否合并改用 pull_request_read 或 REST,不再用 issue_read。

    共 3 个文件。规则文本两处行数不变(319、458)。

  • 为什么改:PR fix(pm): a Blocked-by PR target is released on merge in every unlock path, and H19 holds an abandoned PR #27983 之后,Blocked-by: 可以写 PR,规则是合并才放行。但这三处仍按「关闭」来判,会把挂在废弃 PR 上的卡报成「该回队」。实测 MCP issue_read 分不出合并和废弃(6516593752)。

  • 风险与回滚:只改 report-only 的行文和规则文本,不加门禁,也不写标签。issue 类目标仍是关闭即放行,行为不变。回滚就是 revert。

  • 先看:check-half-states.mjs 的 proseBlockerClause,它新增了 abandoned 一类,并加了 9 个钉子。

  • 两分钟验证:node scripts/pm/check-half-states.mjs --self-test,5098 例,exit 0。

  • 需要你做的:无。登记册判定为 Tier S(.claude/** ×2)。契约复核 PASS,记录在本评论之前,Head-sha: 45763c74fa470b9e277be741ddf87d0366ca78c1。本席转 ready 并开自动合并。

席位意见:ACCEPT。契约复核 PASS,没有 must-fix。复核亲自做了两条消融,都按预期变红,复原后 diff 为空:

  • 让「已关闭」一类也算未合并的 PR:6 个新钉子变红;
  • 去掉「全部废弃」的分支:1 个变红。

should-fix 1、2、4、5 都只是措辞,不影响判定,转到跟进卡 #28033。这四处还把 PR 目标说成「关闭」:

  • dispatch-runbook.md:85 旗二;
  • H28;
  • H4 混合行;
  • 定时全量运行的备注。

should-fix 3(给 issue_read 补一条实测行)作为该卡的可选项。

席位核对

  • 形态: one PR, Fixes #27989, three files: scripts/pm/check-half-states.mjs, .claude/skills/pm-dispatch/SKILL.md, .claude/skills/pm-dispatch/references/platform-readings.md. skip-changeset. check-governed-merges.mjs --pr 28031: Tier S (.claude/** ×2; check-half-states.mjs is not on the register).
  • 前提实测: blockerResolutionFromRow marks unmerged: true only on a closed PR with a null merged_at. H19 already excludes unmerged, and staleBlockVerdict holds on any unmerged row, so the new row text is true against the code and matches state-machine.md:24. The review probed eight candidate mixes at the head: a merged PR is released, a closed-unmerged PR reads CLOSED UNMERGED with no "due back", and a closed issue is released as before.
  • 门禁 check-runs: head 45763c74fa470b9e277be741ddf87d0366ca78c1: 51 check-runs, 30 success, 20 skipped, 0 failed, 1 still running (Live half-state sweep or Test Core 1/8); auto-merge waits on it. Locally: skill line ratchet, token ratchet and skill-id lint (105 files) all exit 0; 58 of 58 derived gate families exit 0 (dev report).
  • 接受的偏差: none. state-machine.md and H28 are untouched, as triage 6516946612 ruled (no new gate; item 4 is wording at most).
  • 范围外去向: pm(half-states, pm-dispatch): four readers still say closed for a Blocked-by PR target that releases on merged (dispatch-runbook flag two, H28, the H4 mixed line, the scheduled run note) #28033 (pm:blocked on this PR) takes review should-fixes 1–5.
  • 落地条款: Tier S: on the contract review PASS on the current head, the seat posts the record and this ACCEPT, flips it ready and arms auto-merge, under the maintainer's landing grant for this term (seat thread, 2026-10-29 22:04Z).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/s skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants