skills(pm-dispatch): needs:contract-review rides a reviewable increment — the carrier discipline restated where triage reads it - #16698
Conversation
…s it `needs:contract-review` was being hung at TRIAGE time as a routing state, on cards with no `pm:*` state and no claim — a convention inherited from neighbouring triage comments rather than from the rule it claimed to implement. The carrier discipline already says the label rides a reviewable increment (a draft PR, or the card in the same stroke as a `Clause-②: yes` claim) and never gets pre-hung; it was just not written where triage reads. Two sentences, both fold-paid, both files at their pinned line counts: - `references/lanes/triage.md` (7/7): the new rule is paid by the blank separator between the H1 and the pointer paragraph. Proved render-neutral with micromark — an ATX heading is a leaf block, so the paragraph that follows it needs no blank line, and the rendered h1/p/ul structure is byte-identical to before. Merging two of the three existing rule bullets was measured first and would have cost a clause: the three carry 324 bytes of unique content over a 120-byte per-line cap, and nothing in them is restated anywhere else in the corpus. - `SKILL.md` (811/811): paid by deleting one restating line whose every clause survives in fuller form in `references/dispatch-runbook.md`, which the same section already delegates to on the adjacent line. Claude-Session: https://claude.ai/code/session_018dxq7YqsLDMeZDZ5AzsgJX Co-authored-by: Claude <noreply@anthropic.com>
…rrier-discipline-triage
|
ACCEPT — flight N, PR #16698 at head Own readings (review worktree detached at
Review checklist:
Implemented-by: os-dev subagent 维护者速读改了什么: 两个文件各加一句、各折一处,行数不动( 为什么改: 你 14:48Z 裁的 A 只剩文本这一半——十一张卡曾因这个惯例停在没有队列的地方,规则本来写着「不前瞻预挂」,只是不在分诊席读的那份文件里。选项 B(第七个停车态)按裁定不做。 风险与代价(含回滚): 两行规则、两处折叠,零新文件、零 CI 成本、棘轮不抬。回滚 = revert 一个提交。唯一可见的形式变化:triage.md 的标题后没有空行,与另外五份车道说明略有不同;有人加回去时棘轮当场变红,不会静默。 席位意见: 复核通过;派发词给的两种付账办法经量都办不到,空行付账是席位认可的替代,建议按现状合并。 你要做的: 手动合并本 PR(受管面,⛔ 不走队列、不挂 auto-merge);合并即关 #16625。是否合并? Generated by Claude Code |
Fixes #16625
needs:contract-reviewwas being hung at triage time as a routing state — on cards with nopm:*state and no claim — so eleven cards sat in no queue at all (not dispatchable, notdecidable, not blocked), and of twenty open card-side carriers eleven had nothing to review. The
carrier discipline already forbids that: the label rides a reviewable increment and is never
pre-hung. The convention was inherited from neighbouring triage comments instead of from the rule
it claimed to implement, so this PR writes the rule where triage actually reads.
The ruling this PR executes
Director seat, summon #17, decision batch #3 (comment 5572349104); maintainer's reply, verbatim
and untranslated: 「同意」. The ruling, quoted verbatim:
Option B is not implemented here in any form: no state was added, no label was written on any
card, and the sixteen carriers triage already answered were not touched.
The two sentences added
Each is worded in its own file's register, and each is self-sufficient — a reader landing on one
never has to fetch the other.
.claude/skills/pm-dispatch/references/lanes/triage.md, appended as the fourth rule bullet —115 bytes:
The charter names its seat in its other bullets (「分诊每 fire 只取一个仓…」), so this one names it
too and lets 分诊 carry as the subject through the second half.
.claude/skills/pm-dispatch/SKILL.md, inserted in the 分诊座位职责 section immediately afterL339 「分类动作每张三选一,外加一个修复通道。」 — 115 bytes:
The section supplies the seat, so this one drops the subject the way its neighbours do
(L378 「⛔ 写入前对每张卡现读当前状态…」) and spends the bytes on 裁定 instead — naming the act
that owes the direction and the state.
Both widths measured, not estimated:
printf '%s' LINE | wc -c= 115 for each, against theratchet's 120-byte per-line cap. Placement is the verdict block, not the sweep enumeration
(L323–338), because what the ruling constrains is what a triage verdict does: the line sits
directly under the head of the three-way classification rule it qualifies.
The two folds
Fold 1 —
triage.md, and the mechanism the dispatch proposed does not exist.The dispatch named two ways to pay inside the seven lines: extend an existing line, or merge two
of the three rule bullets into one. Both were measured first and both fail, so a third
mechanism was used and is declared here rather than taken silently.
origin/mainare 119 / 113 / 110 / 101 bytes, so the whole file's headroom under the 120-byte cap is 37 bytes spread across four lines, and the largest single-line headroom is 19. The sentence is 115.git grepfor each bullet's distinctive terms (轮替,饥饿,最旧优先,第二分诊席,fire 频率) over the whole pm-dispatch corpus returns zero hits outside this file, so nothing in them is restated anywhere and no clause can be dropped to make a merge fit.li.The ratchet ceiling was not raised, and no clause left the file. The whole diff on this file is
one deleted blank line and one added rule.
Fold 2 —
SKILL.md, one restating line deleted, quoted byte-verbatim fromorigin/main:555:origin/mainat.claude/skills/pm-dispatch/references/dispatch-runbook.md:226— 「条件性标准条款命中判据才抄,⛔ 不命中就省掉别硬套。」 — the same clause verbatim, plus what to do when the trigger does not fire.claude/skills/pm-dispatch/references/dispatch-runbook.md:227–228— 「多实现面组件(同一契约 ≥2 个实现面)⇒ 测试放在未来的分叉会被抓住的地方。」 / 「即进共享一致性覆盖,不是独立测试文件。」 — the same rule with the definition of 多实现面 and the negative half the deleted line lackedTwo properties the dispatch required of a fold candidate, both checked rather than assumed:
references/core-rules.md. A similarity scan of every SKILL.md bulletagainst the whole corpus, scoring each line's best twin outside core-rules.md against its best
match inside it, puts this line at twin 0.43 / core-rules 0.05 —
git grepfor条件性and多实现面overcore-rules.mdexits 1. The two stronger twins found (SKILL.md:586, whosetwin sits at
core-rules.md:120, and SKILL.md:600, which shares a clause withcore-rules.md:122) were rejected on exactly this rule.SKILL.md:554, ends 「原文见 runbook」, andSKILL.md:564points atreferences/dispatch-runbook.mdby name. Deleting it leaves L554 and L556 — two rules of thesame class, about what a dispatch word must copy from the runbook — reading continuously.
Twins whose survival lives in
.claude/agents/os-dev.md(SKILL.md:346–350) were also rejected:that file is the dev's copy and SKILL.md is the PM's, so deleting one leaves its reader
without the rule. Survival has to be reachable by the same reader.
No untouched line was re-wrapped. The diff is exactly
+2 / -2across both files, and thewidest line in SKILL.md is unchanged (its widest table row is still 342 bytes, pin 342).
The five dispatched premises, each measured against the tree
lanes/triage.mdis 7 lines onorigin/main, ratchet row['.claude/skills/pm-dispatch/references/lanes/triage.md', 7]with widest-row pin 0, last touch4a85d978f, and it contains noneeds:contract-reviewand noClause-②git show origin/main:PATH | wc -l= 7; ratchet map L798 = 7 and L1160 = 0;git log origin/main -1 --format=%h -- PATH=4a85d978f2;grep -cE 'needs:contract-review|Clause-②'over that blob = 0SKILL.mdis 811/811 (widest-row pin 342); 分诊座位职责 runs L307–417 with the Backlog-sweep line at L323; the carrier rule already lives at L647–649 and inreferences/contract-review.md〈载体纪律〉; the decision-frame block L734–752 has md522f2339f0acb64cdb50c7adc9db681c3## 分诊座位职责and L323 is 「Backlog sweep 是常设职责,每 fire 扫任一析取命中的卡。」; L647–649 carry 「…由 PR 创建者随可复审契约增量同笔挂…」 / 「Clause-②: yes认领同笔在卡上挂标…」 / 「挂标后复核完成前短暂停靠;⛔ 不前瞻预挂。」;sed -n '734,752p' | md5sum=22f2339f0acb64cdb50c7adc9db681c3before the edit and unchanged afterreferences/core-rules.md,references/contract-review.md,.claude/agents/os-dev.md,AGENTS.mdandcontent/docs/releases/**untouchedgit diff --name-only origin/main...HEADlists exactly the two intended paths and nothing else; zero label writes were issued by this session on any card in either repoSKILL.md's last touch is4fe00b80e; no open PR names either file; take a merge-tree reading before opening the PRgit log origin/main -1 --format=%h -- SKILL.md=4fe00b80e7, as dispatched.mainhad already advanced past the dispatch tip when work started (4fe00b80e→0f07b2c86) and advanced again during the run (→7797102139), so the open-PR check was re-run against the current set: all 16 open PRs fetched byrefs/pull/N/head, and for each the blob at both paths compared toorigin/main. Every triage.md blob is identical toorigin/main. Ten SKILL.md blobs differ, and each differing value was resolved to a historicalorigin/mainblob —fc5032c4=4fe00b80e^,4a8302c7=905a1d2f5,7d2c8d56=4a85d978f— i.e. an unmodified older copy carried by a stale base, not a modification.git diff --name-only MERGE_BASE origin/main -- .claude/skills/pm-dispatch/is empty:mainnever touched either fileGates — derived, never hand-listed
Derived at final HEAD
f305c7c9e5withnode scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack. The firstderivation, taken before merging
main, printed a STALE TREE refusal — 15 commits behind, 14of the files the families are derived from changed in that range — so
origin/mainwas merged(clean, zero conflicts) and the list was derived again from the merged tree. The second run's own
header:
Every command ran with its exit captured to a file before any pipe (
cmd > FILE 2>&1; ex=$?).node 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-testpnpm --filter @objectstack/lint run check:doc-formula-expressions(first run exited 3 =PREREQUISITE NOT MET, which is not a measurement; prerequisites built under the lock, then re-run)pnpm check:agent-test-spellingpnpm check:doc-authoringpnpm check:driver-memory-censuspnpm check:nul-bytespnpm check:pm-governed-mergespnpm check:pm-governed-prosepnpm check:pm-skill-id-lintpnpm check:pm-skill-ratchetpnpm check:refd-timer-probepnpm check:skill-frame-syncpnpm check:watch-hint-literalnode scripts/pm/check-governed-merges.mjs --teston both paths — expected: 3 = GOVERNEDReconciliation, quoted from the tool's own verdict line:
Gate verdict lines that judge this diff, quoted as each gate printed them:
check-governed-merges --test, exit 3:The prerequisite build ran through the shared lock (
OS_VERIFY_LOCK_SLOT=issue-16625), whichprinted:
os-verify-lock: VERDICT command-exit 0 · held the lock 166s (2m46s) · waited 0s.No changeset: this diff publishes nothing from any released package, so
skip-changesetis thecorrect disposition and is applied as a label.
dispatch-gatesnames 13 further families that「apply once this card's changeset exists」 — they stay out of scope for exactly that reason.
pnpm lint— a measured narrowing, with all three evidencespnpm lintiseslint . --no-inline-configover the whole repo, a CI-owned run. It was narrowedto the changed files, and the narrowing is a measurement, not a skip:
files:selector ineslint.config.mjsis a JS/TS extension glob — L971**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs},L1015, L1054, L1103, L1172, L1212
**/*.{ts,tsx,mts,cts}, plusCOMMENT_SWALLOW_FILESat L785(
['**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}']). A grep formarkdown|processor|**/*.mdover theconfig exits 1: no markdown plugin, no processor, no
.mdselector exists.--format json.pnpm exec eslinton both changed paths with--no-inline-config --format jsonreturned 2 results, 0 errors, 2 warnings, and eachwarning is eslint's own
"File ignored because no matching configuration was supplied."⇒ 0 linted files in this diff, stated by the instrument rather than inferred.
block's
parserOptionsis{ ecmaVersion: 'latest', sourceType: 'module' }, and a grep forparserOptions.project|projectService|project:returns exactly one hit — L328, inside theconfig's own comment saying so: 「this repo runs one
eslint.config.mjs, which never enablestype-aware linting (no
parserOptions.project, no typed@typescript-eslintrules) for ANYfile, test or not」. With no cross-file type program, a markdown-only diff cannot move any
verdict on any untouched file.
Merge-tree reading
Taken immediately before this PR was opened, at final HEAD
f305c7c9e5:origin/mainis also this branch's merge base —mainwas merged into the branch during the runafter the stale-tree refusal — so the reading is a clean fast-forward with no conflicted path.
验收备注
triage.mdare botharithmetically impossible against the file as it stands; the measurements are in the Fold 1
table. This is a fact about the dispatch word, not a defect in the tree, so no card. It is
surfaced here because the seat reviewing this PR is the one that wrote those mechanisms.
承接者: the PM seat, at this PR's ACCEPT.
triage.mdis now the only lane charter whose H1 is not followed by ablank line. A future author who "tidies" that blank line back in pushes the file to 8 lines and
reds
check:pm-skill-ratchet— the failure is loud and immediate, not silent, so no guard isowed. 承接者:
scripts/pm/check-skill-line-ratchet.mjs, mechanically.line folded here —
SKILL.md:600againstreferences/review-checklist.md:40is the closest(they share every clause), and
SKILL.md:349–350against.claude/agents/os-dev.md:45–46arenear-verbatim across the PM/dev split. Neither is a defect and neither was touched; they are
headroom for a future compression round, not work. 承接者:无.
mcp__github__list_issuesunder-returning a single-label enumeration isrecorded on this card's own thread (comment 5571816579) as a bound, not a total. Nothing in this
PR depends on that reading. 承接者:无 — it is already stated where it was measured.
维护者速读(草稿)
改了什么
两个文件各加一句规则,各自用一次折叠付账,行数一动不动:
references/lanes/triage.md7/7,SKILL.md811/811,棘轮 headroom 都是 0。新规则说的是同一件事:needs:contract-review这个标签只跟着能审的东西走 —— 一个 draft PR,或者认领时同笔写下的
Clause-②: yes;分诊席自己不挂它,分诊的裁定要做的是把条款②的方向写进散文,并且把卡放进六个状态之一。
两句各 115 字节,都在棘轮的每行 120 字节上限之内。付账方式:
SKILL.md删掉一句纯重复的规则(它的每个子句在
references/dispatch-runbook.md里都活着,而且说得更全,上面逐条给了行号);triage.md花掉的是标题和指路段之间的那个空行 —— Markdown 的标题本来就不需要后面跟空行,我用本仓自己装的 CommonMark 实现渲染前后对比过,渲染结果逐字节相同。
为什么改
不是补功能,是止住一个已经发生的损失。今天实测:十一张卡因为这个惯例卡在没有任何队列里 ——
不可派、不可裁、不阻塞 —— 其中一张是 p2 缺陷卡,后果是托管租户上传附件 404。规则本来就写着
「⛔ 不前瞻预挂」,只是没写在分诊席读的那份文件里,于是它被下一个分诊席从邻座的评论里
「继承」了八次。分诊席已经按现行文本把十六张卡全部放回队列/决策箱了,所以这张卡剩下的
只有文本这一半。第七个停车态(选项 B)按裁定不做:档位这道门按契约在复核,不在派发。
风险与代价(含回滚)
代价:两行规则,两行折叠,零新文件,零 CI 成本,棘轮上限一格没抬。风险低但不是零 ——
SKILL.md少了一句「条件性标准条款命中判据才抄」,读者要看全文得翻到 runbook;它的邻居 L554 本来就以「原文见 runbook」结尾,所以这一跳是这一节原有的读法,不是新加的。
另一个可见变化是
triage.md的标题后面没有空行了,这会让它和另外五份车道岗位说明看起来略有出入 —— 渲染没有差别,而且真有人把空行加回去,棘轮会当场红,不会静默。
回滚就是 revert 这一个 commit:两个文件各回到原来的行数,被折的两处内容在别处完整存活,
revert 只会造成一次重复,不会造成缺失。没有生成物、没有基线、没有别的文件依赖它。
席位意见
(留空,待席位定稿)
你要做的
裁定已经做完了(A,您回的「同意」),这张 PR 只是执行它,没有留下任何要您再拍的岔路。
唯一值得您看一眼的是
triage.md那个空行:裁定要求「行数不动」,而派发词提的两条付账办法(拉长某一行、把三条规则合并成两条)我都量过,在每行 120 字节的上限下都办不到 ——
三条规则一共 324 字节,合并任意两条都要删掉一个子句,而它们的内容在整个语料里没有第二处。
所以我花掉了那个空行,并且证明了渲染没有变化。请回一个字:是(合并)或 否(要我换个付法)。
Generated by Claude Code