docs(pm): a spec narrowing may declare Clause-2 yes — the conservative direction is never an error - #18135
Conversation
…e direction is never an error The spec lane's rule says a narrowing does not TRIP clause 2, and nothing said whether declaring it anyway is allowed, so an author reading a per-card ruling in a PR body inferred the opposite rule. One line states both halves and clamps the per-card ruling out of rulebook standing. Net 0 lines under the pm skill ratchet: the generated-artifact hand-edit clause is dropped from the same section — that rule survives in AGENTS.md (the Documentation Guardrails table and the "Touched packages/spec? Regenerate its artifacts BEFORE pushing" section) and, for the landing half, in this skill's core-rules and landing-operations. Claude-Session: https://claude.ai/code/session_01DAcomhvR9kKizeYgg89Vo8 Co-authored-by: Claude <noreply@anthropic.com>
Contract reviewHead: ① derived judgments (seat-measured on the fetched head
② semver: unchanged — nothing published. ③ boundary flags: the PR body stores two footers (the create channel appended its own after a footer sent without a leading rule — platform-readings' fourth shape); left as is, a PATCH would add a third. The dev's out-of-scope note that Implemented-by: Verdict: PASS — references tier ⇒ this seat lands: ready through 维护者速读(终稿)
Generated by Claude Code |
Fixes #17963
Why
references/lanes/spec.mdanswered one question — what trips clause ② — and was silent on the other: whether declaringClause-②: yeson a narrowing anyway is allowed. With the rulebook silent, a PR body became the authority. The measured cause, verbatim from PR #17498's body:That is a per-card director ruling, not rulebook text, and nothing in that body says so. Both statements are true at once — the rulebook says what trips clause ② mechanically, the ruling said what one card should declare — but nothing said so in one place, so the next author either argues about which text wins or quietly stops declaring on narrowings because the rulebook says they do not trip.
The conservative direction was already sanctioned in this same skill, just never joined to the narrowing rule.
references/contract-review.md(unchanged by this PR) carries both:The edit
One rule line, inserted directly under the rule it reconciles (
:19→ new:20):93 bytes. It carries all three ruled halves: a narrowing does not TRIP clause ②, declaring
yeson one is never an error, and a per-card ruling does not change this line. No issue number in operational text, so the provenance above lives in this PR rather than in the rule.Line budget — net 0, ceiling untouched
lanes/spec.mdis pinned at 43 lines (scripts/pm/check-skill-line-ratchet.mjs), zero headroom, every line ≤ 120 bytes.:19in place was measured first and does not fit.:19is 108 bytes; the budget leaves 12 bytes — four CJK characters — and the shortest wording carrying the new substance needs 30 or more.- 生成物门禁重生成提交,⛔ 手改。leaves the same section. Its rule survives elsewhere, in files that outrank this one:AGENTS.md→ the Documentation Guardrails table (AUTO-GEN ❌ Never hand-edit. Regenerated by …), § Touchedpackages/spec? Regenerate its artifacts BEFORE pushing (match the change to the gate and regenerate up front; nocheck:script regenerates anything), and Multi-agent discipline §11 (pre-commitrefuses a commit that still owes a regeneration).references/core-rules.md「入队资格是每一个检查全绿 ⛔ 不是必查子集;碰生成物的 PR 入队前先同步再重生成。」 andreferences/landing-operations.md§A.LC_ALL=C awk 'length($0)>120'over the file prints nothing, before and after.Acceptance greps (both directions)
git grep -n '恒不是错误' -- .claude/skills/pm-dispatch/references/lanes/spec.md→ 0 hits onorigin/main, 1 hit (:20) heregit grep -n '收窄' -- …/lanes/spec.md→:19still hits, plus the new:20git diff --stat origin/main→1 file changed, 1 insertion(+), 1 deletion(-)git diff --stat a90a9f267 -- …/contract-review.md …/core-rules.md …/SKILL.md→ empty for eachLC_ALL=C awk)Gates
Derived with
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack(no paths — the tool takes its own change set), all run in the foreground with$?captured before any pipe, then reconciled with--ran:All 15 exit 0, plus
pnpm check:pm-governed-prose(exit 0, outside the derivation, named by the dispatch). The named ones:check:pm-skill-ratchet,check:pm-skill-id-lint,check:pm-governed-prose,check:nul-bytes— the ratchet's own verdict line reads✓ check-skill-line-ratchet: declared cross-file moves: 1, total ceilings down 9 lines.with no ceiling raised.Seven of the sixteen first exited 3 (PREREQUISITE NOT MET) in the fresh worktree — not a verdict. Six cleared after
pnpm install;check:doc-formula-expressionsalso neededpnpm exec turbo run build --filter=@objectstack/formula --filter=@objectstack/lint, run underscripts/pm/os-verify-lock.sh(VERDICT command-exit 0 · held the lock 172s · waited 0s), and then exited 0.Local verification scope
The diff touches no package, so there is no dependency-closure build and no package test or typecheck to owe. The repo-wide lint is CI's run, and the narrowing here is an empty intersection measured by eslint itself, not asserted:
pnpm exec eslint --no-inline-config --format jsonon the edited path reports one file, 0 errors, and the warningFile ignored because no matching configuration was supplied— everyfiles:selector ineslint.config.mjsnames only.ts/.tsx/.mts/.cts/.js/.jsx/.mjs/.cjs, so a Markdown file is outside the linted population and cannot move any untouched file's verdict.Changeset
skip-changesetlabel, applied through the additive labels endpoint and read back..claude/**ships in no package'sfiles[]— it publishes nothing.Acceptance notes (out of scope, not filed)
contract-review.md:9for 「拿不准 ⇒ 按yes」, which ona90a9f267is:14(:9is the 保守方向 line), andcore-rules.md:113for the review rule, which ona90a9f267is:112(:113is the dispatch-word rule). Nothing in the tree is wrong; this is why the new line cites by content and not by number. Successor: whoever reads this card next — no repo change owed.lanes/spec.md:25points at 「SKILL.md 模型分档」, which is not a heading inSKILL.md; the rules it means are under### 派发and the keyword does occur there (:501), so the pointer resolves by grep, not by section. Polish, not a defect. Successor: the next edit to either file.维护者速读(草稿)
改了什么 —— PM 技能包 spec 车道说明加一行规则:收窄不触发条款②,但按
yes申报恒不是错误,个案裁决不改这条。行数配额是零余量,这行由同一节里删掉「生成物门禁重生成提交,⛔ 手改」买单 —— 那条规则在 AGENTS.md 与本技能包的 core-rules / landing-operations 里都还在,不是丢掉。为什么改 —— 规则只写了「什么触发条款②」,没写「不触发的能不能照样申报」。空白处被一份 PR 正文顶上去当了权威:它引一张个案裁决说收窄要申报
yes。两句话其实都对(一个讲机制,一个讲那张卡),但没有一处把它们放在一起,下一个作者就得在两份文本之间二选一,或者干脆不再申报。一行话把口子合上,保守方向照旧许可,成本是零 —— 语义面卡本来就按契约复审档施工。风险与代价(含回滚) —— 代价是删掉的那条生成物提醒不再出现在 spec 车道页,读者要去 AGENTS.md 看(那份文件本来就要求全文读,且冲突时它为准)。风险低:纯说明文字,无代码、无发布面、无生成物。回滚 = revert 这一个 commit,文件回到 43 行原样。
席位意见 ——
你要做的 —— 受管面(
.claude/**),按 Prime Directive #14 由维护者人工合并:本 PR 保持 draft,未挂 ready、未入队、未开 auto-merge。确认那一行读起来就是你要的裁决,以及「删这条买那条」的取舍你接受,然后人工合并。Generated by Claude Code
Generated by Claude Code