feat(pm): a PR over 5,000 changed lines lands only by a human merge — size predicate in check-governed-merges --test, the same reading in dispatch-gates, one rule line in SKILL.md and landing-operations - #19033
Conversation
…itions + deletions over 5000 lands only by a human merge Maintainer ruling 2026-09-18, verbatim: 「还有应该完善skills,修改代码量超过某个行数(比如5000)就应该人工审核。」 The threshold is declared once as HUMAN_MERGE_LINE_THRESHOLD = 5000. --pr reads the pair off the same GET that gives changed_files (its absence is a refusal, never a size of zero); --branch counts the merge-base range with --numstat (binary files at zero, as on GitHub); --test takes --additions/--deletions as a pair or says NOT MEASURED on stdout. Either limb exits on the GOVERNED code, so every caller that routes 3 to the human terminal routes an oversized PR there. Generated files are included — a certified regeneration lifts the path off the register and lifts nothing from the size. Self-test: a new battery pinned on both sides of the threshold and on the PR that prompted the ruling. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BTeBejoPUvRHN8WdAJC6oF
…ier verdict The same 2026-09-18 human-merge line threshold the landing gate enforces, read at dispatch time off the worktree's own diff (numstat off the merge base against the working tree, untracked files counted from disk, binary files at zero) so a seat knows before ACCEPT that the PR needs a human. An explicit path list carries no diff and prints NOT MEASURED, never a silent under. The threshold is imported from check-governed-merges.mjs — one declaration, no second copy. --json carries the reading as changedLines; --commands keeps it on stderr with the rest of the provenance. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BTeBejoPUvRHN8WdAJC6oF
…md line, the landing-operations pre-check row, the ceiling 812 → 813 Maintainer ruling 2026-09-18, verbatim: 「还有应该完善skills,修改代码量超过某个行数(比如5000)就应该人工审核。」 SKILL.md gains one 111-byte rule line beside the four-piece-terminal trigger; landing-operations.md folds the size limb into the pre-check row (now spelled --pr N, which reads paths and size in one call) at 117 bytes, ceiling unchanged. The SKILL.md ceiling rises 812 → 813 under the ratchet's own maintainer exit: measured, the rule could not be paid in place — 0 of 598 adjacent bullet pairs merge under the 120-byte cap (smallest 123 B) and the trigger line stands at 118 B. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BTeBejoPUvRHN8WdAJC6oF
…ze-threshold-human-merge
…lock The threshold import moved this file's inherited-population marker from line 702 to 705; the census row that names it by file and line moves with it — the row is named, never counted, so the move is recorded rather than absorbed. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BTeBejoPUvRHN8WdAJC6oF
Contract reviewServed-tier: ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
维护者速读(终稿)· skills 席 · 2026-09-18T12:49Z这个 PR 做什么:您 12:33Z 的字「修改代码量超过某个行数(比如5000)就应该人工审核」落成机械门禁。落地前置检查 在真 PR 上的读数:PR #18971(+238,310 / −119)—— 旧版本判「不受管,照常入队」;这个版本判「HUMAN MERGE,23.8 万行 > 5000」。今天其他 PR(最大 +692)全部「under」。 它还没盖住的两处,已立卡:合并队列的守卫仍只看路径(#:席位若跳过前置检查直接入队,超大 PR 仍会合进去 —— 更急);AGENTS.md §7 还写着「两类 PR 不能凭绿灯合入」,现在是三类(#)。 验收:复核记录 5730219787(PASS)、ACCEPT 5730220167;门禁自测 328 → 369、派发工具自测 1849 → 1862;派生门禁 42 / 42 绿;CI 在跑,截至 12:47Z 无红。 请您做的一件事:Approve(受管文本),或直接 ready + squash 合并。要问的只有一个字:5000 是不是您要的数 —— 是就照批,不是回个数我改常量。 Generated by Claude Code |
|
Correction (skills seat) · 2026-09-18T12:51Z — the review of record 5730219787 and the 维护者速读 5730220889 above name the two follow-up cards with EMPTY numbers (a variable that did not expand when the seat's filing step failed before the post). The cards, filed at 2026-09-18T12:50Z: #19036 (the merge-queue guard's Generated by Claude Code |
One conflicted file, scripts/pm/check-governed-merges.mjs: the tiered register from main meets the SIZE predicate on this branch. Both sides kept: GOVERNED_TIER_* beside HUMAN_MERGE_LINE_THRESHOLD; `tier` beside `humanMerge` on the post-lift verdict; the Tier H block prints its landing-tier line and then the size line; the Tier S block gains the same size line; the self-test summary carries both suffixes; the roster floor is recounted to the 28 batteries the merged file declares (27 and 26 on the two sides, none lowered). The size line's "same terminal" now names Tier H, the terminal the rules layer keeps under the tiers. SKILL.md, the ratchet ceiling and landing-operations.md merged clean: 813 lines, ceiling 813. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BTeBejoPUvRHN8WdAJC6oF
…lists one — the SIZE limb reaches the queue The 2026-09-18 ruling landed seat-side (PR #19033) while the queue leg of check-governed-queue-guard.mjs kept handing testVerdict no size, so a PR over HUMAN_MERGE_LINE_THRESHOLD changed lines that a seat enqueued still merged. Queue guard: a fourth leg reads every queued pull request's additions / deletions off the same pull object the head read uses (makePullReader grew a `size`, no second endpoint), judges it through the sibling's IMPORTED predicate (testVerdict([], { size }) + landsByHumanMerge — this file declares no threshold and spells no comparison, pinned against its own source), and REFUSES on exit 8 with the two numbers, the threshold and the human-merge remedy printed; an unreadable size fails CLOSED on exit 9; the pull_request leg stays silent and byte-identical; exit precedence governed > size > carrier is a pure function, pinned on every combination. Post-merge sweep: classifyCommit is now landsByHumanMerge on testVerdict, the size read LOCALLY off the landed diff by one `git diff-tree --numstat` per mainline commit (commitChanges, replacing commitPaths' --name-only with a byte-identical path list), so an oversized landing with no governed path is an entry on the size limb alone — counted apart in the head, listed with a ⛔ SIZE row and the same attribution column. Self-tests: guard 261 → 296, governed-merges 410 → 435. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W5y9kRg1YtYaMQYExVLRc2
Fixes #19012
Clause-②: no
Maintainer ruling (verbatim, 2026-09-18)
Read as: a pull request whose changed line count — GitHub's
additions + deletionson the PR, generated files INCLUDED — exceeds 5,000 lands only by a human merge, at the same terminal as governed text (ACCEPT on the card,needs-user-decisionon the PR, a final 维护者速读, review requested fromGOVERNED_APPROVERS); no seat flips it ready or arms auto-merge. 5,000 is the ruled default (「比如」), declared once asHUMAN_MERGE_LINE_THRESHOLDinscripts/pm/check-governed-merges.mjs, so it moves by one word from the maintainer and one edit. The case that prompted it, PR #18971 (+238,310 / −119, of which 237,706 lines were regenerated artefacts), is the first PR the rule governs — an exemption for generated files would exempt exactly it, so there is none.What changed
scripts/pm/check-governed-merges.mjs— the SIZE predicate.--pr Nreadsadditions/deletionsoff the sameGET /repos/OWNER/REPO/pulls/Nthat giveschanged_files(a PR object missing the pair is a refusal on exit 1 — never a size of zero, never a "not governed" answer);--branch REFcounts the same merge-base range withgit diff --numstat --no-renames(a binary file is 0 lines, as GitHub counts it);--test PATHStakes--additions N --deletions Nas a pair, or printssize: NOT MEASUREDon stdout naming the modes that read it. Either limb exits on the GOVERNED code 3, so every caller that already routes 3 to the human terminal routes an oversized PR there without a new code;--jsoncarriessizeandhumanMerge(governedstays the path limb). A certified generated-artifact regeneration lifts the PATH off the register and lifts nothing from the size. The queue guard'stestVerdict(paths)reading is unchanged (no size handed in ⇒ the path answer as before).scripts/pm/dispatch-gates.mjs— the same reading at dispatch time. With no paths (the derived run) it printsChanged lines — N (+a / -d; generated files INCLUDED) vs the human-merge threshold 5000: underor⛔ OVER — this PR lands only by a HUMAN MERGE …beside the tier verdict (human and--tiermodes), the count on stderr with the rest of the provenance, andchangedLinesin--json. The count is--numstatoff the merge base against the working tree plus untracked files counted from disk (under-derivation refused, like the path list). An explicit path list carries no diff and printsNOT MEASURED, never a silent under. The threshold is imported from the gate — one declaration, no second copy..claude/skills/pm-dispatch/SKILL.mdgains one line beside the four-piece-terminal trigger (line 608, 111 B): 「改动 >5000 行(含生成物)同换终局四件套,⛔ 无事实层例外;读数 = PR additions+deletions。」references/landing-operations.mdline 26 folds the size limb into the pre-check row, now spelled--pr N(which reads paths and size in one call), 117 B, ceiling unchanged at 69. The SKILL.md ceiling rises 812 → 813 inscripts/pm/check-skill-line-ratchet.mjsunder the ratchet's own maintainer exit, the ruling quoted in the entry (the 811 → 812 precedent's form).Readings — before / after, measured
43f476688)check-governed-merges.mjs --pr 18971(live API through the proxy)✅ NOT governed — ordinary queue landing applies⛔ HUMAN MERGE — 238429 changed line(s) (+238310 / -119) > 5000--pr 18994(2 files, +15 / −1)size: 16 changed line(s) (+15 / -1) ≤ 5000 — under the human-merge threshold--pr 18921(SKILL.md, +6 / −6)size: 12 changed line(s) … undercheck-governed-merges.mjs --self-testdispatch-gates.mjs --tier(no paths, this worktree)Changed lines — 722 (+691 / -31; generated files INCLUDED) vs the human-merge threshold 5000: under.dispatch-gates.mjs --tier packages/spec/src/index.tsChanged lines — NOT MEASURED: a path list carries no diff to count …check:pm-dispatch-gates(detached,tail --pid)43f476688)check:pm-skill-ratchetSelf-test pins on the threshold: exactly 5,000 changed lines is under; 5,001 is over; the +238,310 / −119 pair reads 238,429 and is over; a certified pure regeneration over the threshold still lands by a human merge; the verdict is byte-identical through
--branchand through--testonce the same list and numbers are handed in.Line budget (measured)
SKILL.md: 812 → 813 lines; ceiling 812 → 813 (maintainer exit). A fold was not available: 0 of 598 adjacent bullet pairs merge under the 120-byte cap (smallest 123 B); the trigger line (607) stands at 118 B; the rule's shortest self-contained form is 111 B; deleting a ruled clause is refused on the state-machine precedent.references/landing-operations.md: 69 → 69 lines (line 26: 118 B → 117 B).check:pm-skill-id-lint: 27 files clean (no issue-ID citation in either line).Gates (this head; exit codes captured before any pipe)
Derived with
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackfrom the worktree at7fdd61ca0(42 commands; change set 5 paths, 724 changed lines by its own reading), every one run withcmd > log 2>&1; status=$?and reconciled with--ran:dispatch-gates --ran: 42 derived, 42 run, 0 NOT-MEASURED, 0 UNRUN (verdict line:✓ dispatch-gates --ran: 42 derived famil(ies) accounted for — 42 run, 0 NOT-MEASURED).check:pm-dispatch-gatesran detached (nohup+tail --pid, 795.6 s on this box):✓ dispatch-gates self-test: 1862 cases pass.check:pm-governed-merges:✓ check-governed-merges --self-test: 369 assertions.check:doc-formula-expressionsexited 3 (PREREQUISITE NOT MET:@objectstack/formula/@objectstack/lintnot built) on the first pass; both were built underscripts/pm/os-verify-lock.sh(VERDICT command-exit 0, 152 s held) and the gate reran green — the exit 3 was never a measurement.NOT MEASURED locally, by the derivation itself (CI-only, value-bearing argv):
scripts/check-shard-attestation.mjs --emit …,scripts/check-test-completeness.mjs …,scripts/pm/check-half-states.mjs --format=markdown --provenance=…; plus the 11 wide-population families and the 50 artifact-roster families CI runs on every PR, outside the derived total by design.pnpm lint(repo-wide eslint) is CI-owned and was not run here. No package build/test is owed: the diff touches nopackages/**file (no ①/② in the local verification scope), so the only lock-wrapped run was the formula/lint build above.Line-budget after the final commit (
7fdd61ca0):check:pm-skill-ratchet—.claude/skills/pm-dispatch/SKILL.md is 813 lines (ceiling 813; headroom 0),references/landing-operations.md is 69 lines (ceiling 69; headroom 0);check:pm-skill-id-lint— 27 file(s) clean.Deviations from the dispatch brief
--pr, 3 already means GOVERNED — the file's own rule is that no invocation carries both meanings — so a PR object without the pair is a REFUSAL on the derivation code 1 (a stated refusal, never 0, never a size of zero). The ruling's intent (never read as "not governed") is kept.--branchderives the size itself (--numstaton the range it lists) rather than taking passed-in numbers; the flags beside a deriving mode (--pr,--branch) are refused as two readings of one number, the way two mode flags are.dispatch-gates.mjs's self-test pins a NAMED census of live population markers by file and line; the import block moved this file's owninherited-populationmarker from :702 to :705, so that one row is updated — the census exists to be updated exactly this way.Acceptance notes
merge_groupleg reads the PATH register only; a seat that skips the landing pre-check can still enqueue an oversized PR. Dedupe words:queue guard size threshold,merge_group additions deletions,check-governed-queue-guard 5000,human merge line count.AGENTS.md green alone third class,5000 lines human merge AGENTS.check-governed-merges.mjs) lists governed-surface merges only; an oversized PR that landed through the queue is not listed. 承接者: the skills seat, together with the queue-guard follow-up above.check:doc-formula-expressionsexits 3 (PREREQUISITE NOT MET) on a fresh worktree until@objectstack/formulaand@objectstack/lintare built — by design of that gate; built under the verify lock here and rerun. 承接者: none.维护者速读(草稿)
改了什么:落地前检
check-governed-merges.mjs新增「体量」判据:PR 的 additions + deletions 超过 5000 行(含生成物)⇒ 只能人合,与受管面走同一终点;dispatch-gates在派发/认领时就把同一读数印在 tier 行旁;SKILL.md 与 landing-operations.md 各落一行规则。阈值只声明一次(HUMAN_MERGE_LINE_THRESHOLD = 5000),改它是一个词。为什么改:您 2026-09-18 的裁决。触发案例是 PR #18971(+238,310 / −119,其中 237,706 行是生成物)只凭 AI 审查就经队列合入;生成物不豁免,否则恰好豁免它。
风险与代价(含回滚):大 PR 的落地从「席位挂 auto-merge」变成「等您点一下」,每张超 5000 行的 PR 多一次人工动作;回滚 = revert 本 PR(纯脚本 + 两行规则文本,无发布物)。已知缺口:队列守卫的 merge_group 腿尚未读体量,眼下靠席位跑落地前检;已列为后续单。
席位意见:(留空)
你要做的:确认 5000 这个默认值(「比如」)是否就是您要的;是 ⇒ 人合本 PR;要改数字 ⇒ 说一个数即可。
Generated by Claude Code