skills(pm-dispatch): dedupe at grading by file/mechanism over the open board of both repos — same finding ⇒ duplicate_of, never a second dispatch - #18981
Conversation
…n board of both repos before keywords; same finding ⇒ duplicate_of, never a second dispatch The triage band's shadow rows (:366–:368) read as a cross-repo keyword search only. Two cards on one file and one defect were graded two days apart into two lanes by the same seat, both dispatched, and the second PR closed as superseded — the step that was missing is dedupe by FILE / MECHANISM against the OPEN board (own repo and sibling repos, `pm:dispatched` included) before keywords, with the finding — not the card — as the unit: the later card's evidence moves to the earlier card and the later card closes `duplicate_of`. Paid in-band at 812 / 812: the three rows are rewritten, no line added, no re-wrap; every retired clause survives in the same band (see the PR body). Claude-Session: https://claude.ai/code/session_01BTeBejoPUvRHN8WdAJC6oF Co-authored-by: Claude <noreply@anthropic.com>
Contract reviewServed-tier: ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
维护者速读(终稿)— PR #18981 · 席位定稿 2026-09-18T09:36Z(草稿在正文,以本评论为准)改了什么:分诊规则里的三行「shadow 检查」改写成一条真正的查重步骤:定级时先按文件/机制查本仓与姊妹仓的全部 open 卡(含已派发的),再看引用与关键词;同文件同缺陷视为同一个发现、不分车道——证据搬到先卡,后卡以 为什么改:今天早上同一个文件、同一个缺陷的两张卡(#18314 与 #18844)被同一个分诊席隔两天定进两条车道,两边都派了 dev,第二个 PR 作废——浪费一轮 dev。原规则只说「跨仓 + 关键词」,本仓已有的卡根本不在检查范围里。分诊席自己在 #18963 上认了这一步是缺的。 风险与代价(含回滚):纯规则文本,不碰代码与发布物;代价是分诊每张卡多做一次按文件名的板面检索。与在等你批准的 PR #18903、#18921 同文件不同行段,谁后落谁先合 main。回滚 = revert 一个提交。 席位意见:建议批准。三行各在 120 B 之内,退掉的每个子句都有去处;「不分车道」四个字是关键——正是它让两条车道各自的分诊看见同一张卡。席位这边的另一半(认领前扫远端分支)是 #18964,另走 runbook。 你要做的:确认这条查重规则该进分诊纪律,是则 APPROVE( Generated by Claude Code |
|
Landing record — ruling C, on an authorized APPROVED (skills seat, os-zhuang ( Generated by Claude Code |
Fixes #18963
Clause-②: no
Governed rules layer (
.claude/skills/pm-dispatch/SKILL.md) — draft PR, four-piece terminal; the skills seat's contract-tier review, then the maintainer's approval lands it.skip-changeset: nothing published moves (.claude/**is internal agent tooling, in no package'sfiles[]).What changed — three rows of the 分诊 band, 812 / 812 held, no line added, no re-wrap
The triage band's shadow rows read as a cross-repo keyword search only, and the dedupe step that would have caught two cards on one file and one defect — graded two days apart into two lanes by the same seat, both dispatched, the second PR superseded — did not exist in the rules text: a dedupe by FILE / MECHANISM against the OPEN board, own repo included, before keywords, with the finding (not the card) as the unit. Governing triage reading, verbatim: 「缺的不是勤勉,是一条按『文件/机制』而不是按『关键词』去重的步骤」 · 「去重的单位是『发现』,⛔ 不是『卡』」.
pm:dispatched),再跟引用与关键词。」 (120 B)Blocked-by:不派;open 未认领 ⇒ 先收敛成一个派发入口。」 (99 B)duplicate_of,⛔ 不并排派发。」 (115 B)Blocked-by:不派;open 未认领 ⇒ 先并成一个派发入口;已完成 ⇒ 卡可能过期。」 (119 B)Band total 221 B → 354 B (+133 B) at +0 lines; every row is at or under the 120-byte cap (120 / 115 / 119). Lines :223 (「分诊座位唯一生产:定级/路由/type/查重/shadow/
duplicate_of…」) and every line outside :366–:368 are byte-identical toorigin/main— the diff is 3 insertions / 3 deletions on one file. The parallel-draft bands of PR #18903 (:509–:670) and PR #18921 (:162–:183) are untouched.What the new rows say (measured against the card's requirements)
pm:dispatchedincluded — :366: 「先按文件/机制查本仓与姊妹仓 open 卡(含pm:dispatched),再跟引用与关键词」. The population is stated once, in the same row (the PM's mechanism assumption 2, confirmed: 「跨仓」 became 「本仓与姊妹仓」 in place).duplicate_of(「后卡关duplicate_of」, the state the :136 row already binds to theduplicatereason), and it is never dispatched beside the earlier one (「⛔ 不并排派发」).Blocked-by:, not dispatched; open and unclaimed ⇒ fold into ONE dispatch entry first; completed ⇒ the card may be stale. 「其余」 scopes the row to what :367 did not already settle, so a same-finding sibling in flight lands onduplicate_of, not onBlocked-by:.Retired clauses and their survivors (nothing retired without one)
Blocked-by:不派」Blocked-by:不派」 — same mechanism, scoped to hits that are not the same findingThe 「shadow 命中」 subject of the old :367/:368 is now carried by :366 naming the check and :368's 「其余」 reading against :367; no row outside the band was touched to make that binding.
references/core-rules.md(151 / 151): its only shadow mention is :56 「跨仓查重与 shadow 检查恒归中央 ⛔ 不下放」 — a rule about WHO runs the check (central triage), not about its population or order, and it stays true under the new rows (:55 already makes same-repo dedupe central too). No rule moved there, so no twin is owed (the PM's mechanism assumption 3, confirmed bygit grep -i shadowon that file: 1 hit, :56).Reader test — the second grading, replayed under the new rows
At 2026-09-18T00:06Z the triage seat grades #18844 (
check-single-claim-paths.mjs401s because nodefetchignoresHTTPS_PROXY; remedy: the proxy re-exec the sibling scripts already carry). Platform state of #18314 at that instant, from its label events: open,pm:queue+domain:spec+priority:p2since 2026-09-17T10:10Z, unclaimed (pm:dispatchedand the assignee arrived at 05:49Z, five hours later — so at grading it was a queued sibling, not yet an in-flight one; the dispatch text's 「pm:dispatchedwhen #18844 was graded」 describes the later claim-time instant, which is the seat's own half, #18964).check-single-claim-paths.mjs⇒ [finding]scripts/check-single-claim-paths.mjscannot be run from an agent container — it 401s because node'sfetchignoresHTTPS_PROXY, while three sibling scripts already carry the re-exec fix #18314's title carries the file name verbatim; it is on the open board (pm:queue; had it already beenpm:dispatched, 「含pm:dispatched」 keeps it in the population).fetchvsHTTPS_PROXY, the re-exec remedy), a different lane (domain:specvs the skills seat's filing) ⇒ 「同一发现,不分车道」 ⇒ [finding]scripts/check-single-claim-paths.mjsreads GitHub through node's global fetch with no proxy re-exec, so with real PR context it exits 1 onGitHub API 401inside an agent container — the gate can be read only in CI, while its sibling post-stamped already re-execs with--use-env-proxy#18844's evidence (the seven-file census) moves onto [finding]scripts/check-single-claim-paths.mjscannot be run from an agent container — it 401s because node'sfetchignoresHTTPS_PROXY, while three sibling scripts already carry the re-exec fix #18314; [finding]scripts/check-single-claim-paths.mjsreads GitHub through node's global fetch with no proxy re-exec, so with real PR context it exits 1 onGitHub API 401inside an agent container — the gate can be read only in CI, while its sibling post-stamped already re-execs with--use-env-proxy#18844 shuts asduplicate_of[finding]scripts/check-single-claim-paths.mjscannot be run from an agent container — it 401s because node'sfetchignoresHTTPS_PROXY, while three sibling scripts already carry the re-exec fix #18314; ⛔ no second dispatch, no PR fix(scripts): re-exec check:single-claim-paths through the session proxy so its verdict is readable outside CI #18945.Blocked-by:nor 「先并成一个派发入口」 applies.Outcome: one card (#18314, p2,
domain:spec), one dispatch, one PR. The old :366 could not reach this: its population was 「各姊妹仓」 only, and its method was keywords.Gates — all at
ea2521bd9(the final commit; the tree did not move after these runs)Derived from the worktree's own changeset with
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack(stderr: 「gate list derived from the tree of 'objectstack-ai/objectstack' at commit ea2521b」; change set: 1 path vs merge base26c73fb4e): 20 commands, 20 run, every exit code captured by redirect-then-$?, never through a pipe.--ranreconciliation: 「20 derived famil(ies) accounted for — 20 run, 0 NOT-MEASURED, 0 UNRUN」 (exit 0).pnpm check:pm-skill-ratchet.claude/skills/pm-dispatch/SKILL.md is 812 lines (ceiling 812; headroom 0)· ✓references/core-rules.md is 151 lines (ceiling 151; headroom 0)· widest table row 342 (pin 342)pnpm check:pm-skill-id-lint/#[0-9]{3,}/g)pnpm check:skill-frame-sync3327d02c56f8a0eca88569dad2270f32before and afterpnpm check:pm-governed-prosepnpm check:nul-bytespnpm --filter @objectstack/lint run check:doc-formula-expressions*Whenpredicates clean — afterpnpm --filter "@objectstack/lint..." buildunderscripts/pm/os-verify-lock.sh(VERDICT command-exit 0, 30 s held; first attempt without the lint build exited 3 PREREQUISITE NOT MET, which is a refusal, not a measurement)pnpm check:agent-test-spellingpnpm check:cross-package-test-inputspnpm check:doc-authoringpnpm check:driver-memory-censuspnpm check:pm-expected-skipspnpm check:pm-governed-mergespnpm check:pm-half-statespnpm check:refd-timer-probepnpm check:watch-hint-literalnode scripts/check-closing-keyword-parity.mjsnode scripts/check-closing-keyword-parity.mjs --self-testnode scripts/check-comment-mask-corpus.mjsnode scripts/pm/check-governed-queue-guard.mjs --self-testnode scripts/pm/check-harness-current.mjs --self-testBuilds were run with
OS_SKIP_DTS=1(the formula gate imports compiled JS, not declarations); no gate that reads.d.tsis in this derivation. Repo-wide scans (pnpm lint) are CI's run, not owed here — this diff touches no lintable source.Line budget
SKILL.md: 812 before / 812 after / ceiling 812 — paid in-band by rewriting the three rows (:366 75 → 120 B, :367 99 → 115 B, :368 47 → 119 B); the one compression is 「收敛成」 → 「并成」 in the :368 survivor.references/core-rules.md: 151 / 151 / 151 — untouched, no twin owed.Acceptance notes
origin/mainmoved from26c73fb4e(BASE) to873e0e8e2while this ran;git log BASE..origin/main -- .claude/skills/pm-dispatch/is empty, and neither PR skills(pm-dispatch): key the clause-② contract review by lane — spec and skills owe it on every round, other lanes owe none #18903 nor PR skills(pm-dispatch): a passing control certifies the instrument, not the question — a zero-hit reading names the instrument's reach and one known target outside it #18921 has landed, so no merge oforigin/mainwas performed — the branch is one commit onBASEand its three-dot diff againstmainis exactly the table above. Declared here rather than done: a merge commit would carry no content for this file.references/dispatch-runbook.md) is finding(skills): the seat's claim-time serial check reads open PRs' file lists only, so a sibling card already claimed and pushed but not yet opened as a PR is invisible to it — onegit ls-remote --heads origin | grep <file stem>would have shown #18314's branch before #18844 was claimed #18964 and was not touched; the runbook carries no spelling of the shadow check to drift against these rows (git grep -i shadowonreferences/: onlycore-rules.md:56). 承接者: finding(skills): the seat's claim-time serial check reads open PRs' file lists only, so a sibling card already claimed and pushed but not yet opened as a PR is invisible to it — onegit ls-remote --heads origin | grep <file stem>would have shown #18314's branch before #18844 was claimed #18964.check-closing-keyword-parityreports it skippedpackages/spec/CHANGELOG.md(6,080,453 B over its 2 MiB cutoff for UNREGISTERED files) — the gate's declared, printed behaviour, not a defect; 承接者:无.dispatch-gates --ranaccepts an exit code per recorded line and flags a bare record as 「CLAIMED」; the record here was re-run in that form (see the report). 承接者:无.维护者速读(草稿)
改了什么 — 分诊规则里的三行「shadow 检查」被改写为一条真正的去重步骤:定级时先按文件/机制、再按关键词,对本仓与姊妹仓全部 open 卡(含已派发的)查重;同文件同缺陷视为同一个发现,后卡的证据搬到先卡、后卡以
duplicate_of关闭,不再并排派发。行数 812/812 不变,只在原三行内改写,没有折行凑数。为什么改 — 同一个文件、同一个缺陷的两张卡,被同一个分诊席在两天里定进两个车道,两边都派了 dev,第二个 PR 作废:浪费了一轮 dev。原规则只说「跨仓 + 关键词」,本仓已有的卡根本不在检查范围里。
风险与代价(含回滚) — 纯规则文本,不碰代码与发布包;风险是分诊多做一次按文件名的板面检索(成本很小)。回滚 = revert 这一个 commit,三行恢复原文。
席位意见 — (留空,席位定稿成评论)
你要做的 — 一个动作:APPROVE(受管面,需你的批准后由席位落地)。
Generated by Claude Code