Uh oh!
There was an error while loading. Please reload this page.
feat(pm): report the closing-keyword contradiction on the squash-commit surface (H23) - #11117
Conversation
…it surface (H23) Every closing-keyword reader in this repo is handed a PR body: H7 and H21 in check-half-states.mjs, and the blocking gate check-partof-closing-keyword.mjs. GitHub's parser also acts on commit messages landing on the default branch, and this repo squash-merges, so each merged PR writes one such message that nothing read. H23 is a REPORT-ONLY patrol row over that surface. It reuses the existing extractors at a new `markdown: false` surface option, because a commit message is not markdown: quoting a keyword does not neutralise it there, so the finding sentence prescribes rewording and never the body-surface remedy. The row states its own coverage numbers in the summary line, and the bounded window is read over REST because the patrol runner checks out shallow. Measured over 1,546 first-parent messages on main (pinned window 2026-08-11T00:00Z to 2026-08-22T18:00Z): 270 keyword bindings across 234 messages, 6 carrying the contradiction, all 6 assembled by squash from a branch that mixed the two trailer spellings. Card: #10942
os-zhuang
commented
Aug 22, 2026
PM 验收 — ACCEPT复核:席位 session_01MsbKEG4LtERSLaDrbehM3e 全 diff 审毕(单文件 +436/−5)。定级边界逐项吻合:report-only(无 blocking、无 workflow 变更)、复用既有提取器( 发现卡:#11118(H8 窗口论据 ~18/天 vs 实测 ~132/天)、#11119(squash 拼接生成器——作者侧惯例半)均合规立卡,进下轮集中定级。 落地类:scripts/pm 单文件,非治理面——本席 ready + auto-merge 布防,队列落地后剥 #10942 状态标。 Generated by Claude Code |
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 32591905896 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 失败的 job(日志抽取,best effort):
跨 PR 相同签名(24h,按失败测试文件聚合):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
Uh oh!
There was an error while loading. Please reload this page.
Fixes#10942
Every closing-keyword reader in this repo is handed a PR body:
h7PartOfWithClosingKeywordandh21NegatedClosingKeywordinscripts/pm/check-half-states.mjs, and the blocking gatescripts/check-partof-closing-keyword.mjs. GitHub's closing-keyword parser also acts on commit messages that land on the default branch, and this repo squash-merges — so every merged PR writes exactly one such message that nothing reads.H23is a report-only patrol row over that surface: a squash commit message carryingPart of #Nand a closing keyword bound to that same#N— theH7contradiction shape, on a surfaceH7cannot see.H23's own finding sentence prescribes, and applying it here is the cheapest available demonstration.The mechanism — why no body-side guard could have caught it
The squash message is composed at merge time from the branch's own commit messages, not from the PR body. All six measured specimens are multi-commit branches where one commit's trailer closed the card and another's said it was only part of it; the squash concatenated them and the contradiction was manufactured by the assembly.
Measured on the clearest one, PR #9478: its body carries a closing keyword bound to card 9320 and no
Part ofanywhere, so the body is clean underH7and under the blocking gate — correctly, because the contradictory text existed in no body at all. This is why the row cannot be "H7with a wider input": there is a text onmainthat no body ever held.Measured — stage 1, this change's own pass
Corpus: all 1,546 first-parent commit messages on
mainin the pinned window2026-08-11T00:00:00Z … 2026-08-22T18:00:00Z(first/last message2026-08-11T01:21:16Z … 2026-08-22T17:59:02Z), read with the repo's own extractors.(#PR)mainlinearityH21's negation window ported to this surfaceThe six, columns deliberately apart:
0c24898c0fixformd7283250dfixformaf2a989befixform3db37957cfixform7e06f51eefixform30536e37cfixformThese reproduce the six the card body recorded, exactly. They are evidence that the shape reaches
mainunguarded, not six adjudicated wrong closes — in every one the lead commit's trailer looks deliberate — so the finding sentence reports the contradiction and explicitly declines to adjudicate it.git log --since=2026-08-11is an approxidate — git fills the unspecified time-of-day from now, so a bare date slides the corpus as the clock moves. Two runs twelve minutes apart returned 1,443 and 1,441 messages for what read as one window. Every number above is from ISO-pinned bounds, and the docblock says so.The asymmetry (the card's point 2), and what it costs today
A commit message is not markdown: nothing renders it, so backticks and fences are ordinary characters and a quoted keyword binds exactly like one in prose. The extractors therefore gained one option —
partOfTargets(text, { markdown: false })/closingKeywordTargets(text, { markdown: false })— the same shapestripMarkdownCode's own{ inline }option takes. The default is unchanged, so every pre-existing caller keeps byte-identical output (pinned).Honest accounting of what that buys today: on this corpus the two readings agree exactly — 270 bindings and 6 findings either way. It is not clean for lack of opportunity, which is the failure mode a zero-difference number invites:
#Ninside the code regionThe population is everywhere; no author has yet landed the two adjacent inside a fence. The first who does is most likely the author following the body-surface remedy, which is the case the surface-correct reading catches and a stripped reading would silently drop.
The remedy text differs, and the self-test pins the difference both ways
H7andH21both end with "or put the keyword in backticks", which is correct for a body and false here.H23's sentence says so out loud and prescribes the only remedy that works on this surface — reword, so no closing keyword sits next to a card number. Because the realistic regression is someone copyingH7's tail across, the self-test assertsH7's sentence carries that clause whileH23's never does; a one-sided assertion would pass against a sentence that lost both.Decisions taken, and why
H21's negation window is not ported. It flags 0 of the 270 bindings on this surface: commit messages carry no## Out of scoperegister, because that register belongs to a PR body's prose sections. A port is a later card with its own numbers. (sentenceStartOffsetwas already written with this surface in mind — its docblock declines to treat a plain newline as a boundary precisely because commit messages hard-wrap — so the port stays available at no design cost.)git log, for the live window. The patrol runner (.github/workflows/half-state-patrol.yml) usesactions/checkout@v7with nofetch-depth, whose default is 1 —git logthere would read one message and report a clean surface four times a day, forever. Not hypothetical about someone else's container: the checkout this change was authored in arrived shallow at 375 commits, and the corpus above only became readable after an explicitgit fetch --deepen.sha=parameter, so the endpoint uses the repository's own default branch and the reader stays repo-agnostic like every other listing in the file.Tests
node scripts/pm/check-half-states.mjs --self-test— 879 cases pass (was 838; +41), at827971d1b.The shipped predicate was then driven over the real corpus in the REST row shape the sweep passes in: it fires on exactly the six specimens, correlates each to the right PR, and reports
270 binding(s) across 234 message(s)— the coverage numbers the summary line now carries.Reverse-verification — two legs, mutation proved on disk by marker counts, restore under an
EXIT INT TERMtrap, both directions predicted before running:H7's body-surface remedy tail intoH23's sentenceLeg A's prediction is the interesting one and it held: the six specimens do not distinguish the two readings (the corpus says so — 6 either way), so the asymmetry cases are the only thing pinning the surface distinction. That is why they exist as separate cases rather than being folded into the specimens.
Restore proof: 0 ablation markers on disk,
git status --porcelainempty on the file, self-test back to 879.Gates
Re-derived from the actual diff with
node scripts/pm/dispatch-gates.mjsand no hand-fed paths (its own no-path mode, off the merge base), at827971d1b— 10 families, all green, each quoted from the gate's own verdict line:check:pm-half-states✓ check-half-states self-test: 879 cases pass.check:partof-closing-keyword✓ check-partof-closing-keyword self-test: 28 cases pass.check-closing-keyword-paritycheck-closing-keyword-parity: OK (3 parsers agree on all 9 keywords and both measured separators; sweep found 5 file(s) carrying the grammar across 6438 tracked file(s), all registered).check:entry-guard✓ check:entry-guard: 136 scripts/ file(s) — every entry guard goes through invoked-as.mjs; 94 export bindings, 84 of them inert on importcheck:parse-guard✓ check:parse-guard: 135 scripts/ file(s) — every TypeScript parse goes through ts-parse.mjs.check:cross-package-test-inputsOK: 13 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob.check-ci-filter-parityOK: all 86 declared cross-package glob(s) (74 unique) are covered by core or crosspkg…check-cross-package-test-inputscheck:family above)check-partof-closing-keywordcheck:family above)check-half-states(patrol entry)check:family above)Exit codes were captured before any pipe (
cmd > file 2>&1; EXIT=$?), never off atail.check-closing-keyword-parityis the one worth naming: it is the gate that would have caught a grammar drift between the three closing-keyword parsers, and this change deliberately adds no new grammar — one extractor read two ways, so the parser count and the registered-file sweep are unchanged.Lint — a declared narrowing, not a skipped run. Repo-wide
pnpm lintis CI's run; this pass linted the one changed file and proves the narrowing excluded nothing:new ESLint({}).isPathIgnored('scripts/pm/check-half-states.mjs')→false, so the file is in the linted population.--format json: 1 file linted, 0 errors, 0 warnings, exit 0.calculateConfigForFile(...)reportsparserOptions.project === null, andeslint.config.mjsstates in its own header that this repo "never enables type-aware linting … for ANY file". With no cross-file type program, a one-file diff cannot move any untouched file's verdict.No changeset:
scripts/pm/**only — nothing published.skip-changesetapplied additively at PR creation.Control-byte scan clean (
grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]'→ no match, exit 1).Generated by Claude Code