Repository navigation
Commit e872ef4
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)
Fixes #19012
Clause-②: no
## Maintainer ruling (verbatim, 2026-09-18)
> 「还有应该完善skills,修改代码量超过某个行数(比如5000)就应该人工审核。」
Read as: a pull request whose changed line count — GitHub's `additions +
deletions` on 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-decision` on the PR, a final 维护者速读, review
requested from `GOVERNED_APPROVERS`); no seat flips it ready or arms
auto-merge. 5,000 is the ruled default (「比如」), declared once as
`HUMAN_MERGE_LINE_THRESHOLD` in `scripts/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
1. **`scripts/pm/check-governed-merges.mjs` — the SIZE predicate.**
`--pr N` reads `additions` / `deletions` off the same `GET
/repos/OWNER/REPO/pulls/N` that gives `changed_files` (a PR object
missing the pair is a refusal on exit 1 — never a size of zero, never a
"not governed" answer); `--branch REF` counts the same merge-base range
with `git diff --numstat --no-renames` (a binary file is 0 lines, as
GitHub counts it); `--test PATHS` takes `--additions N --deletions N` as
a pair, or prints `size: NOT MEASURED` on 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; `--json` carries `size` and `humanMerge` (`governed`
stays the path limb). A certified generated-artifact regeneration lifts
the PATH off the register and lifts nothing from the size. The queue
guard's `testVerdict(paths)` reading is unchanged (no size handed in ⇒
the path answer as before).
2. **`scripts/pm/dispatch-gates.mjs` — the same reading at dispatch
time.** With no paths (the derived run) it prints `Changed lines — N (+a
/ -d; generated files INCLUDED) vs the human-merge threshold 5000:
under` or `⛔ OVER — this PR lands only by a HUMAN MERGE …` beside the
tier verdict (human and `--tier` modes), the count on stderr with the
rest of the provenance, and `changedLines` in `--json`. The count is
`--numstat` off 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 prints `NOT MEASURED`, never a
silent under. The threshold is imported from the gate — one declaration,
no second copy.
3. **Rule text.** `.claude/skills/pm-dispatch/SKILL.md` gains one line
beside the four-piece-terminal trigger (line 608, 111 B): 「改动 >5000
行(含生成物)同换终局四件套,⛔ 无事实层例外;读数 = PR additions+deletions。」
`references/landing-operations.md` line 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 in `scripts/pm/check-skill-line-ratchet.mjs` under the ratchet's own
maintainer exit, the ruling quoted in the entry (the 811 → 812
precedent's form).
## Readings — before / after, measured
| reading | before (`43f476688`) | after (this head) |
|---|---|---|
| `check-governed-merges.mjs --pr 18971` (live API through the proxy) |
exit 0 — `✅ NOT governed — ordinary queue landing applies` | exit 3 — `⛔
HUMAN MERGE — 238429 changed line(s) (+238310 / -119) > 5000` |
| `--pr 18994` (2 files, +15 / −1) | exit 0 | exit 0 — `size: 16 changed
line(s) (+15 / -1) ≤ 5000 — under the human-merge threshold` |
| `--pr 18921` (SKILL.md, +6 / −6) | exit 3 GOVERNED | exit 3 GOVERNED,
plus `size: 12 changed line(s) … under` |
| `check-governed-merges.mjs --self-test` | 328 assertions, 26 batteries
| 369 assertions, 27 batteries (new battery: the SIZE predicate, floor
30) |
| `dispatch-gates.mjs --tier` (no paths, this worktree) | no size line |
`Changed lines — 722 (+691 / -31; generated files INCLUDED) vs the
human-merge threshold 5000: under.` |
| `dispatch-gates.mjs --tier packages/spec/src/index.ts` | no size line
| `Changed lines — NOT MEASURED: a path list carries no diff to count …`
|
| `check:pm-dispatch-gates` (detached, `tail --pid`) | 1849 cases (the
dispatch's reading at `43f476688`) | 1862 cases pass (795.6 s, detached;
+13 cases) |
| `check:pm-skill-ratchet` | SKILL.md 812 / 812 · landing-operations.md
69 / 69 | SKILL.md 813 / 813 · landing-operations.md 69 / 69 |
Self-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 `--branch` and through
`--test` once 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/objectstack` from the worktree at `7fdd61ca0` (42
commands; change set 5 paths, 724 changed lines by its own reading),
every one run with `cmd > log 2>&1; status=$?` and reconciled with
`--ran`:
```text
node scripts/check-ci-filter-parity.mjs :: exit 0
node scripts/check-closing-keyword-parity.mjs :: exit 0
node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0
node scripts/check-comment-mask-corpus.mjs :: exit 0
node scripts/check-declaration-mirrors.mjs :: exit 0
node scripts/check-declaration-mirrors.mjs --self-test :: exit 0
node scripts/check-scripts-symbol-anchors.mjs :: exit 0
node scripts/check-scripts-symbol-anchors.mjs --self-test :: exit 0
node scripts/check-self-test-wired.mjs :: exit 0
node scripts/check-self-test-wired.mjs --self-test :: exit 0
node scripts/check-self-test-workflow-commands.mjs :: exit 0
node scripts/check-self-test-workflow-commands.mjs --self-test :: exit 0
node scripts/check-skills-token-ratchet.mjs :: exit 0
node scripts/check-skills-token-ratchet.mjs --self-test :: exit 0
node scripts/check-whole-set-label-write.mjs :: exit 0
node scripts/check-whole-set-label-write.mjs --self-test :: exit 0
node scripts/pm/bare-root-worklist.mjs --self-test :: exit 0
node scripts/pm/check-governed-queue-guard.mjs --self-test :: exit 0
node scripts/pm/check-harness-current.mjs --self-test :: exit 0
pnpm --filter @objectstack/lint run check:doc-formula-expressions :: exit 0
pnpm check:agent-test-spelling :: exit 0
pnpm check:bash32-floor :: exit 0
pnpm check:cli-command-ids :: exit 0
pnpm check:cross-package-test-inputs :: exit 0
pnpm check:declared-population-live :: exit 0
pnpm check:doc-authoring :: exit 0
pnpm check:driver-memory-census :: exit 0
pnpm check:entry-guard :: exit 0
pnpm check:nul-bytes :: exit 0
pnpm check:parse-guard :: exit 0
pnpm check:pm-expected-skips :: exit 0
pnpm check:pm-governed-prose :: exit 0
pnpm check:pm-half-states :: exit 0
pnpm check:pm-skill-id-lint :: exit 0
pnpm check:pm-skill-ratchet :: exit 0
pnpm check:pnpm-filter-targets :: exit 0
pnpm check:ratchet-remedy-authority :: exit 0
pnpm check:refd-timer-probe :: exit 0
pnpm check:skill-frame-sync :: exit 0
pnpm check:watch-hint-literal :: exit 0
pnpm check:pm-governed-merges :: exit 0
pnpm check:pm-dispatch-gates :: exit 0
```
`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-gates` ran 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-expressions` exited 3 (PREREQUISITE NOT MET:
`@objectstack/formula` / `@objectstack/lint` not built) on the first
pass; both were built under `scripts/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 no `packages/**` 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
1. Mechanism assumption 1 said a failing size read exits PREREQUISITE
NOT MET (3). Under `--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.
2. "812 / 812 — fold or pay": measured, neither was available (above),
so the SKILL.md line lands under the ratchet's own maintainer exit (812
→ 813), the form the 811 → 812 entry took. The hunk sits at :608,
disjoint from PR #18903's bands (:509–:525, :633–:675) and from the two
PRs that landed on SKILL.md meanwhile (merged into this branch; the line
is still there once). If the seat prefers the follow-up route, drop
commit 3's SKILL.md hunk and the ratchet entry together.
3. `--branch` derives the size itself (`--numstat` on 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.
4. `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
own `inherited-population` marker from :702 to :705, so that one row is
updated — the census exists to be updated exactly this way.
## Acceptance notes
- to file (class b — a declared contract the queue cannot yet hold): the
queue guard's `merge_group` leg 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`.
- to file (class b): AGENTS.md §7 lists "two classes of PR never enter
this path on green alone" (governed surface; Version Packages) — the
ruled third class is missing from the rules layer. Dedupe words:
`AGENTS.md green alone third class`, `5000 lines human merge AGENTS`.
- noted, not filed: the post-merge sweep (default mode of
`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.
- noted, not filed: `check:doc-formula-expressions` exits 3
(PREREQUISITE NOT MET) on a fresh worktree until `@objectstack/formula`
and `@objectstack/lint` are 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](https://claude.ai/code/session_01BTeBejoPUvRHN8WdAJC6oF)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 6819dcb commit e872ef4
5 files changed
Lines changed: 694 additions & 33 deletions
File tree
- .claude/skills/pm-dispatch
- references
- scripts/pm
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
605 | 605 | | |
606 | 606 | | |
607 | 607 | | |
| 608 | + | |
608 | 609 | | |
609 | 610 | | |
610 | 611 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
23 | 23 | | |
24 | 24 | | |
25 | 25 | | |
26 | | - | |
| 26 | + | |
27 | 27 | | |
28 | 28 | | |
29 | 29 | | |
| |||
0 commit comments