Skip to content

skills(pm-dispatch): the round-open harness reading names the load surface .claude/**, not three paths - #18656

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-18636-skill-harness-load-surface
Sep 17, 2026
Merged

os-zhuang merged 1 commit into
mainfrom
claude/issue-18636-skill-harness-load-surface

Conversation

@os-justin

Copy link
Copy Markdown
Collaborator

Fixes #18636

Clause-②: no

The defect

The round-open step-2 reading at .claude/skills/pm-dispatch/SKILL.md:91 named three paths — .claude/{settings.json,agents/*.md,hooks/*} — while the harness loads the whole .claude/** tree from the primary checkout at session start and never reloads it, .claude/skills/** in full for every seat session (the load moment scripts/pm/check-skill-line-ratchet.mjs's header states). So the charter told the seat to measure a set SMALLER than the set that rots it. The tool half — scripts/pm/check-harness-current.mjs's PATHS widened to .claude/skills/**, plus a second CONTENT reading per path — landed in PR #18610; this PR is the charter half that #18544 comment 5706565860 said stays on this file, line-neutral. The card's own true positive: SKILL.md moved ab1d359 → 7f4aa9e5c at a round open and the step-2 reading said nothing, because the seat's copy of the tool was the old one too. The charter has to name the right set on its own, whichever copy of the tool the seat holds.

The line, before and after

:91 only; nothing else in the file, no reference file, no script. Before (120 B, on origin/main 99fcb4ac1, byte-identical to the cf39b83c0 the card was dispatched against):

  • 同读 harness 载入面 .claude/{settings.json,agents/*.md,hooks/*} 的最新触碰是否已在共享检出 HEAD。

After (96 B):

  • 同读 harness 载入面 .claude/** 的最新触碰与内容是否已在共享检出 HEAD。

Payment: in place, line-neutral — 812 / 812, 1 insertion / 1 deletion (git diff HEAD~1 HEAD --stat), no second line, no re-wrap, no other line moved, the widest table row unchanged (342 B, pin 342). It reads as one block with :92, which is untouched: 「否 ⇒ 记座位贴,自然收班时换会话,⛔ 不中断批次;读数走 scripts/pm/check-harness-current.mjs。」 — the 「否」 still answers :91's yes/no question, and :92 still names the tool that takes the reading.

Two things moved on the line:

  • .claude/{settings.json,agents/*.md,hooks/*} → .claude/** — the SET, the card's fix.
  • 「最新触碰」 → 「最新触碰与内容」 — the READINGS. The tool on main takes two per watched path (PLACEMENT: is the commit that last touched the path on origin/main an ancestor of the shared HEAD; CONTENT: are the shared HEAD's bytes of the path equal to origin/main's), and a path is STALE on either. The charter now names both, so a seat holding an old copy of the tool — the card's reading 3 — still knows what it is supposed to measure and can see when the tool's report is short of it.

Why the set is the directory and not a longer list — measured, then the four axes

Measured on this tree at 14f751515 (git ls-files per pathspec): .claude/** selects 43 tracked files; the tool's four pathspecs select 1 (settings.json) + 1 (agents/*.md) + 10 (hooks/*) + 29 (skills/**) = 41. The two files outside the four pathspecs are .claude/launch.json and .claude/workflows/docs-accuracy-audit.js (both last touched 5de93728a; their load moment is unmeasured, as the tool's header says of hooks and agents). Spelling the four pathspecs on the line would cost 131 B — over the 120-byte max-line budget — and would be the file-list spelling the tool's own PATHS comment names as the failure shape: 「naming the charters one by one is how a predicate ends up guarding less than it claims (the population it covers must be the population that is loaded)」.

四维分析(新记录,按章程用中文):

  • 实际业务需求 — 场景已实测,不是推断:[finding] check-harness-current's PATHS omits .claude/skills/**, so a seat seating on a stale checkout is never told its charter is stale #18544 评论 5706565860 记录了一个席位整班执行已离开 origin/main 的旧 :92 处方(车道停摆约 7 小时);本卡读数 2 是同一机制的第二次真阳性(SKILL.md ab1d359 → 7f4aa9e5c,step-2 读数一字未提)。harness 侧的载入面本来就是目录——check-skill-line-ratchet.mjs 头部「read in full by every seat session」说的是 .claude/skills/**,不是三个文件;写 .claude/** 就是写实测的载入面。
  • 项目长远合理性 — 契约优先:章程是契约,工具是读数的实现,章程应当说清「量哪个集合、量什么」,由工具追章程而不是章程追工具的常量。工具头部自己写明「the population it covers must be the population that is loaded」。逐个列 pathspec 的写法今天恰好等于 PATHS,.claude/ 明天多一个子目录就再次落后——正是本卡的缺陷形态,只是换了个数字;目录写法不随 PATHS 腐烂。
  • 防 AI 写代码犯错,尤其是防 AI 写元数据 app 犯错 — 席位是 AI,它量什么集合只取决于章程这一行字。读数 3 已证工具副本也会旧:章程点名 .claude/** 与两种读数之后,旧工具只报两条路径时席位能在结构上看出集合不齐;现在的三路径写法让它无从察觉。不写「与上轮」:那是一条形态未定的席位侧账本义务,120 B 里写不清的义务恰是 AI 会写错的那种。
  • 创业阶段不扩散需求 — 零新机制:一行、两个词,两种读数都已在 main 的工具里落地;不加参考文件、不加脚本、不加座位贴字段。目录写法多覆盖的两个文件(launch.json、workflows/*.js)载入时刻未测,按从紧原则不据此扩 PATHS,记入验收备注交工具所在车道。

The directory-diff suggestion — taken in one half, left in the other

The card proposes writing 「目录 diff 与上轮逐字节比对」 into step 2. Split along the same axes:

  • Taken — the byte-level half, as the tool's CONTENT reading against origin/main, which PR fix(pm): check-harness-current watches the skills tree, and compares content as well as placement #18610 landed and which :91 now names (「与内容」). It is strictly more informative than the seat's byte-count habit the card describes: it names the differing files per path, needs no history, and fires on a checkout that was ALREADY stale at seating.
  • Left — the 「与上轮」 half (a number compared with the previous round's). (i) For the three charters, :89–:90 already compare the latest touch sha with the previous round-open marker and prescribe the re-read, so a second round-over-round comparison on :91 would duplicate them. (ii) For the harness-loaded rest (settings.json, hooks, agents) a re-read is not a remedy — the harness never reloads — so :92 is the whole prescription, and "moved this round" versus "already stale" changes nothing the seat does. (iii) A round-over-round ledger is a new seat-side obligation whose form (what to record, where, compared how) does not fit in the 24 bytes the line has left, and an under-specified obligation is the kind an AI seat gets wrong. Where it would go if the seat wants it: the round-open marker's contents on :90 (record the tool's named stale paths beside the touch sha), or a since-marker flag on the tool — not :91.

Ratchet reading

pnpm check:pm-skill-ratchet (self-test then gate) at 14f751515, exit 0:

✓ check-skill-line-ratchet: .claude/skills/pm-dispatch/SKILL.md: widest table row is 342 bytes (pin 342; headroom 0).
✓ check-skill-line-ratchet: .claude/skills/pm-dispatch/SKILL.md is 812 lines (ceiling 812; headroom 0).

The edited line went 120 B → 96 B, under the 120-byte max-line budget; .claude/skills/pm-dispatch/SKILL.md is the only path in the diff; grep -naP for control bytes over the file: 0 hits.

Gates

Derived with node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack from the worktree with no hand-fed paths (change set from git: 1 path vs merge base 99fcb4ac1, three-dot): 18 commands. Each run with the tool's own capture idiom (redirect, then $?, the printed command recorded byte for byte), then reconciled with --ran: 18 derived, 18 run, 0 NOT-MEASURED, 0 UNRUN (exit 0, 「a DERIVED zero — all 18 recorded an exit code and none of them is 3」).

# command exit
1 node scripts/check-closing-keyword-parity.mjs 0
2 node scripts/check-closing-keyword-parity.mjs --self-test 0
3 node scripts/check-comment-mask-corpus.mjs 0
4 node scripts/pm/check-governed-queue-guard.mjs --self-test 0
5 node scripts/pm/check-harness-current.mjs --self-test 0 (26 cases)
6 pnpm --filter @objectstack/lint run check:doc-formula-expressions 0 — first run exit 3 PREREQUISITE NOT MET (unbuilt closure), not a measurement; pnpm exec turbo run build --filter=@objectstack/lint under scripts/pm/os-verify-lock.sh (VERDICT command-exit 0, held 141 s, waited 0 s), rerun exit 0
7 pnpm check:agent-test-spelling 0
8 pnpm check:doc-authoring 0
9 pnpm check:driver-memory-census 0
10 pnpm check:nul-bytes 0
11 pnpm check:pm-governed-merges 0
12 pnpm check:pm-governed-prose 0 (2 surfaces name all 5 governed surfaces)
13 pnpm check:pm-half-states 0
14 pnpm check:pm-skill-id-lint 0 (27 files clean)
15 pnpm check:pm-skill-ratchet 0
16 pnpm check:refd-timer-probe 0
17 pnpm check:skill-frame-sync 0 (one declared copy, 4 axes, 74 markdown files scanned)
18 pnpm check:watch-hint-literal 0

Not derived, run anyway:

  • node scripts/pm/check-governed-merges.mjs --test .claude/skills/pm-dispatch/SKILL.md → exit 3, GOVERNED (1 of 1 path hits the register) — the expected answer for this path, recorded as such and not as a failure.
  • pnpm check:pm-settings-deny-roster (the derivation flags its roster as living under .claude, so its silence is evidence in neither direction) → exit 0, 17 declared = 17 enforced.
  • node scripts/check-skill-frame-freshness.mjs --no-fetch → exit 0.
  • Tier: dispatch-gates.mjs --tier --repo objectstack-ai/objectstack .claude/skills/pm-dispatch/SKILL.md → the MANDATORY tier the claim comment's Container & model line carries, derived from the file surface; this run was at that tier.

pnpm lint (the repo-wide union) is CI's run; locally it narrows to zero files by measurement, not by skipping: (1) population read from eslint.config.mjs itself — every files: glob is {ts,tsx,mts,cts,js,jsx,mjs,cjs}, no markdown; (2) npx eslint --no-inline-config --format json on the one changed path → 1 file, 「File ignored because no matching configuration was supplied」, 0 errors, exit 0; (3) invariance: the diff touches no file eslint lints, so no untouched file's verdict can move. Not measured locally: the 52 artifact-roster families, the 11 wide-population families and the 14 pending-changeset families the derivation lists outside its runnable total — CI's, as the derivation says.

Changeset

skip-changeset, measured on the fast track: .claude/** is repo-internal agent tooling, in no package's files[]; nothing published moves.

Acceptance notes

  • noted, not filed: on 14f751515 the tool's four PATHS pathspecs select 41 of the 43 tracked files under .claude/; the two outside are .claude/launch.json and .claude/workflows/docs-accuracy-audit.js. Whether the harness loads either at session start is unmeasured (the same status the tool's header gives hooks and agents), so this is an observation, not a contract violation; 承接者: the tool's lane (domain:devx, scripts/pm/check-harness-current.mjs), if a load-moment measurement ever lands.
  • noted, not filed: check-doc-formula-expressions refuses with exit 3 on a fresh worktree until @objectstack/lint's closure is built; its refusal text names the exact fix and the derivation classifies 3 as NOT-MEASURED, so nothing is hidden — 承接者:无.

维护者速读(草稿)

  • 改了什么:.claude/skills/pm-dispatch/SKILL.md 第 91 行一行。开轮 step-2 的 harness 读数原来只点名三个路径 .claude/{settings.json,agents/*.md,hooks/*},现在点名整个载入面 .claude/**,并把工具在 main 上已落地的两种读数(最新触碰、内容)一起写进去。812 / 812 行不变,该行 120 B → 96 B,别处一字未动。
  • 为什么改:harness 在会话开始时整份读入 .claude/**(.claude/skills/** 每个席位都整份读),章程却让席位只量三个文件——量的集合比会腐烂的集合小。[finding] check-harness-current's PATHS omits .claude/skills/**, so a seat seating on a stale checkout is never told its charter is stale #18544 评论 5706565860 记录过一个席位整班执行旧规则、车道停摆约 7 小时;本卡又撞上一次(SKILL.md 动了,读数一字未提,因为席位手里的工具也是旧的)。工具那一半 PR fix(pm): check-harness-current watches the skills tree, and compares content as well as placement #18610 已合;这是章程那一半。
  • 风险与代价(含回滚):风险极低——一行文字,不加机制、不加文件、不动棘轮上限;18 个派生门禁全绿,棘轮 812 / 812、最宽行 342 不变。回滚就是 revert 这一个 commit(14f751515),不牵连任何脚本。
  • 席位意见:(留给席位)
  • 你要做的(一个动作):批准或合并这个 draft PR(受管面 .claude/**,须维护者亲手或授权批准后由席位落地)。

Generated by Claude Code

…rface `.claude/**`, not three paths

The step-2 round-open reading at SKILL.md:91 named three paths
(`.claude/{settings.json,agents/*.md,hooks/*}`) while the harness loads
the whole `.claude/**` tree, `.claude/skills/**` in full for every seat
session -- the charter told the seat to measure a set smaller than the
one that rots it. The line now names the directory surface and both
readings of `scripts/pm/check-harness-current.mjs` (latest touch and
content), so the set the charter prescribes is the set that is loaded
and the reading does not depend on which copy of the tool the seat holds.

Line-neutral: 812 / 812, the edited line 120 B -> 96 B, no other line
moved, widest table row unchanged (342).

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gqi43smmqjJ5sUrhfoPeKu

Copy link
Copy Markdown
Collaborator Author

维护者速读(终稿)

skills 席 session_01Gqi43smmqjJ5sUrhfoPeKu,2026-09-17T12:56Z。对照本席自己读的 diff 校正 dev 草稿;席位意见已填。

改了什么 — pm-dispatch/SKILL.md 开轮第 2 步的 harness 读数一行(:91)。原文点名三个路径「.claude/{settings.json,agents/*.md,hooks/*} 的最新触碰」,改为「.claude/** 的最新触碰与内容」。只此一行,812 / 812 行不变,120 B → 96 B,其余文件一字未动。

为什么改 — harness 实际载入的是整个 .claude/** 目录,.claude/skills/** 每个席位会话整份读;而章程教席位去量的集合比真正会让它腐烂的集合小。#18544 已经撞上真阳性:本章程自己从 ab1d359 动到 7f4aa9e5c,而按旧读数跑的工具一个字没说。工具那一半(check-harness-current.mjs 的 PATHS 纳入 .claude/skills/**)已由 PR #18610 落地;本 PR 是章程这一半,把集合写成与工具一致的载入面本身。「与内容」点的是工具的第二个读数(内容对照 origin/main),正是抓到那次真阳性的读数。

风险与代价(含回滚) — 规则层受管面,只改一行定义;不加标签、不加义务、不动工具。卡上的第二条建议(逐轮目录字节比对)没有采纳::89–:90 已经对照上一开轮标记比对三章程的触碰,而 harness 载入的其余部分重读无用(:92 是全部处方),逐轮账本是一项塞不进这一行的新义务;若要,落点在 :90 的标记内容或工具的 since-marker 开关。回滚 = revert 一个提交,零副作用。

席位意见 — 荐合。它把章程与已落地的工具对齐,消除的是一个已经发生过的漏读;不新增规则。

你要做的(一个动作) — 认可「集合 = 载入面本身」即 Approve 本 PR(或直接人工合并);席位随后按裁定 C 落地。若你想要逐轮目录比对也进章程,Request changes,本席另起。


Generated by Claude Code

@os-zhuang
os-zhuang marked this pull request as ready for review September 17, 2026 14:38
@os-zhuang
os-zhuang added this pull request to the merge queue Sep 17, 2026
Merged via the queue into main with commit 9cbd227 Sep 17, 2026
36 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-18636-skill-harness-load-surface branch September 17, 2026 14:59
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 28, 2026
…for (objectstack-ai#18666)

Fixes objectstack-ai#18454
Clause-②: no

`skip-changeset` — text under `.claude/skills/pm-dispatch/**` only;
nothing published moves. Governed `.claude/**`: draft, four-piece, the
maintainer's approval lands it. PR objectstack-ai#18656 (SKILL.md :91) is ahead in the
SKILL.md serial queue; :89–:92 are untouched here.

## What changed — one rule, three citations rewritten in place, one tell
added

The card's own correction stands: this is a SKILL-text gap, not a
tooling defect. No script changed. Each citation of a repo-scoped
instrument now says which repository the instrument answers for, in the
spelling that makes the answer checkable.

| site | before (bytes) | after (bytes) |
|:--|:--|:--|
| `SKILL.md` :201 | `- 落点不明留分诊首触,⛔ 不猜。` (44) | deleted — restated
verbatim at `references/lanes/ui.md:15` and by 〈域车道〉 :242
「说不出修复碰哪个文件就还不可标」 |
| `SKILL.md` :206 (new, 〈多仓协调〉, directly under the `scripts/pm/**` 住
objectstack line) | — | `- 工装只答被告知的仓,缺 `--repo`/`PM_SWEEP_REPO` 即答
objectstack;⛔ 不答手上那张卡的仓。` (112) |
| `SKILL.md` :539 〈派发〉 | `- 门禁清单取 `dispatch-gates.mjs --commands`
逐条跑,退出码先落盘,`--ran` 对账;⛔ 不抓人读输出。` (119) | `- 门禁清单取 `dispatch-gates
--commands --repo 仓` 逐条跑,退出码先落盘,`--ran` 对账;被拒即手推。` (119) |
| `SKILL.md` :628 〈复核〉 | `- 入队资格 = 每个 check 为 success 或预期
skip(名单:check-expected-skips.mjs),⛔ 不是 required 子集。` (119) | `- 入队资格:每
check 绿或预期 skip,⛔ 非必查子集;名单 check-expected-skips.mjs 只判 objectstack。`
(116) |
| `SKILL.md` :645 〈入队与落地〉 | `- `Clause-②: yes` 认领同笔卡上挂标;PR 开出读
`check-clause2-carriers --pair PR-NUMBER` 为 0 再请审。` (118) | `-
`Clause-②: yes` 认领同笔卡上挂标;开 PR `PM_SWEEP_REPO=仓 check-clause2-carriers
--pair N` 0 才请审。` (118) |
| `references/contract-review.md` :41 | `- ② 双载体已清,逐对机读 `node
scripts/pm/check-clause2-carriers.mjs --pair PR-NUMBER`。` (99) | `- ②
双载体已清,逐对机读 `PM_SWEEP_REPO=仓 node scripts/pm/check-clause2-carriers.mjs
--pair N`。` (109) |
| `references/platform-readings.md` :212 | `- 读 `total_count` 得 None、打印成
total: None,与真空集只差一个字符,而请求根本没跑。` (108) | `- 读 `total_count` 得 None、打印成
total: None,与空集只差一字符;缺键即拒绝,判别式是状态码。` (118) |

Fragments dropped to pay the byte cap, disclosed: :539 loses 「⛔ 不抓人读输出」
— the machine modes the line names (`--commands`, `--ran`) are that
prohibition's mechanism, and the harvest-by-eye history lives in the
script header; :628 spells 「⛔ 非必查子集」 as `core-rules.md` :124 already
does and spells success as 绿 (the same twin's word) so the literal
`check-expected-skips.mjs` that the gate's self-test pins stays on the
line; :645 compresses 「PR 开出读 … 为 0 再请审」 to 「开 PR … 0 才请审」; :212 trades
「而请求根本没跑」 for the tell. Every line ≤ 120 B; every touched file
line-neutral.

## Measured behaviour — from an objectstack checkout, 2026-09-17
12:55–13:05Z, tree `d6137fd6c` (re-derived at `4bbddc5a9`)

| instrument | told the repo by | run about `objectstack-ai/objectui`
from this checkout | tell on the output |
|:--|:--|:--|:--|
| `dispatch-gates.mjs --commands` / `--tier` | `--repo OWNER/NAME`
asserts the checkout | **REFUSES**, exit 2: `REFUSING — asked for
'objectstack-ai/objectui', but this checkout is
'objectstack-ai/objectstack'`, and names the remedy (hand-derive from
the sibling's own manifest and workflows). Without `--repo`: exit 0, 18
objectstack commands on stdout, the provenance banner on **stderr only**
— a `--commands > gates.list` harvest is a well-formed list about the
wrong repo | the refusal; or the stderr banner `gate list derived from
the tree of 'objectstack-ai/objectstack'` |
| `check-expected-skips.mjs --pr N` | `PM_SWEEP_REPO` retargets the
**board only**; the roster is always this checkout's workflows (cannot
be told) | **ANSWERS**, exit 4 on objectui PR objectstack-ai#9604: `Test (coverage)`,
`Test (coverage shard …)`, `dependabot` reported as 3 skips outside the
roster — a verdict against the wrong roster (here a false red; a shared
job name would make it a false green). Without the env: default board
objectstack, PR objectstack-ai#9604 → HTTP 404 → exit 3 NOT MEASURED | first line
`board: objectstack-ai/objectui (PM_SWEEP_REPO); roster: this checkout's
workflows.` — board ≠ checkout ⇒ verdict void |
| `check-clause2-carriers.mjs --pair N` | `PM_SWEEP_REPO` (reads no tree
file) | **WORKS**: `every row below is read from objectstack-ai/objectui
(source: PM_SWEEP_REPO)`; objectstack-ai#9604 is closed so the pair is not formed
(exit 2, explicitly not a clearance). Without the env: `(source: default
— set PM_SWEEP_REPO to target another repo)` | the provenance line names
board and source |
| container proxy, `GET
/search/issues?q=repo:objectstack-ai/objectui+is:issue+kanban` | not
tellable — session-bound | **refusal in a success-looking body**: HTTP
**403**; body is valid JSON with keys `message`, `documentation_url` and
**no** `total_count`; a `.get('total_count')` read prints None/0.
Repo-scoped control `GET
/repos/objectstack-ai/objectui/issues?per_page=1` → 200, one row | the
status is 403 (never 200); the key is **absent**, not zero;
`documentation_url` points at docs.anthropic.com (the 415 discriminator
at platform-readings :118 generalises) |

One rule reaches all four: an instrument answers for the repository it
is told (`--repo`, `PM_SWEEP_REPO`), defaults to objectstack, and never
for the card in your hand; a citation without the repo argument is a
clearance for objectstack only. Written once at :206; each citation
carries its own half.

### Triage's ask — which repo do the round-open step-1/2 readings answer
when the seat works objectui from an objectstack checkout

- Step 1, `git-history.mjs touch
--path=.claude/skills/pm-dispatch/SKILL.md --ref=origin/main`: from the
objectstack worktree → `7f4aa9e5c`, proven, exit 0. From cwd
`/home/user/objectui` → exit 1, `no commit on 'origin/main' touches
.claude/skills/pm-dispatch/SKILL.md` (the tool also auto-deepened that
clone to `--deepen=4096`; object store only, no tree edit). It answers
for the cwd's repo; the charter exists only in objectstack, so run from
objectstack it answers the one repo that has it, and a sibling cwd
answers not-found, never a wrong sha.
- Step 2, `check-harness-current.mjs`: default `--shared` is the cwd's
git common dir → `CURRENT -- every harness-loaded path on origin/main is
in /home/user/objectstack HEAD d6137fd`, exit 0; with `--shared
/home/user/objectui` → UNDECIDED, exit 2 (graft boundary at the time).
It answers for the checkout it is pointed at and names it in the verdict
line.
- No SKILL line added: both readings are self-labelling (the verdict
names the checkout; the charter path has one home), and the lines that
own them (:89–:92) are the serial-queue block of PR objectstack-ai#18656.

## Four-axis reasons for the placement

- **实际业务需求** — measured, not recalled: the one instrument that ANSWERS a
sibling board is `check-expected-skips` (exit 4 against a foreign
roster), and `--commands` without `--repo` prints a well-formed list at
exit 0 with the banner on stderr. Both failures are read AT the
citation, so the repair sits at :539 / :628 / :645 / contract-review
:41; the principle sits once in 〈多仓协调〉 beside the line that says the
toolchain lives in objectstack.
- **项目长远合理性** — no script change (the card's own correction); the
citation becomes the checkable spelling, contract-first: the argument is
the contract, the provenance line is the receipt.
- **防 AI 写代码犯错** — SKILL.md :168's order: delete the error-permitting
construct, make the correct form the only spelling, only then add a
check. The argument-less spellings were the construct; the cited
spellings now carry `--repo 仓` / `PM_SWEEP_REPO=仓`, the non-retargetable
one says 只判 objectstack, and the proxy row says 缺键即拒绝 — an absent key
can no longer be read as a zero.
- **创业阶段不扩散需求** — no per-repo rosters, no new gate, no ceiling raise;
line-neutral, paid by deleting one line restated twice elsewhere.

## Gates

- `pnpm check:pm-expected-skips` exit 0 on `4bbddc5a9` (99 cases; the
round-1 head `7e78dc6ad` failed its pin `the SKILL.md enqueue bar names
this file` because :628 lacked `.mjs`). Run explicitly: the derivation
does not name it for a SKILL.md change, and `--ran` files it as 'Outside
this card's derivation (1)' — filed as objectstack-ai#18673.
- `pnpm check:pm-skill-ratchet` exit 0 — `SKILL.md` 812 lines (ceiling
812; headroom 0), widest table row 342 (pin 342); `contract-review.md`
60/60; `platform-readings.md` 466/466. `pnpm check:skill-frame-sync`
exit 0 (one coherent copy; 74 markdown files scanned). `pnpm
check:pm-skill-id-lint` exit 0 (27 files clean). `pnpm check:nul-bytes`
exit 0.
- Derived with `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` from the worktree (no hand-fed paths): 19
commands, each captured redirect-then-`$?`: Re-run on `4bbddc5a9`: 19/19
exit 0 (doc-formula-expressions after the same prerequisite build
through the lock, waited 0s).
`check-closing-keyword-parity.mjs` 0 · `check-closing-keyword-parity.mjs
--self-test` 0 · `check-comment-mask-corpus.mjs` 0 ·
`check-governed-queue-guard.mjs --self-test` 0 ·
`check-harness-current.mjs --self-test` 0 · `@objectstack/lint
check:doc-formula-expressions` 0 (first run exit 3 PREREQUISITE NOT MET
— unbuilt `@objectstack/formula`/`@objectstack/lint`; built through
`os-verify-lock.sh` after one queue-timeout (exit 99, holder: a
sibling's `check:pm-dispatch-gates`), then 0) ·
`check:agent-test-spelling` 0 · `check:doc-authoring` 0 ·
`check:driver-memory-census` 0 · `check:nul-bytes` 0 ·
`check:pm-governed-merges` 0 · `check:pm-governed-prose` 0 ·
`check:pm-half-states` 0 · `check:pm-skill-id-lint` 0 ·
`check:pm-skill-ratchet` 0 · `check:refd-timer-probe` 0 ·
`check:required-contexts` 0 · `check:skill-frame-sync` 0 ·
`check:watch-hint-literal` 0.
- `--ran` reconciliation: 19 derived, 19 run, 0 NOT-MEASURED, 0 UNRUN,
exit 0.
- `check-governed-merges.mjs --test` on the three paths: exit 3
GOVERNED, as expected.
- Tier: `--tier --repo objectstack-ai/objectstack` on the touched files
→ MANDATORY, clause ① from `SKILL.md`.

## Acceptance notes

- noted, not filed: `git-history.mjs touch` run from a sibling cwd
fetches `--deepen` up to 4096 into that clone before answering not-found
— an operator-visible side effect on a checkout the tool does not own
(its header says deepening is an operator action). Carrier: none;
recorded here for the skills seat.
- noted, not filed: `true-green.md` :15–:16 and `platform-readings.md`
:392 already state the `dispatch-gates.mjs` cross-repo behaviour for the
dev side; this PR adds the PM-side citation and the rule, and leaves
those readings in place.

## 维护者速读(草稿)

**改了什么**:pm-dispatch 技能文本在四处引用住在 objectstack 的工装(门禁派生
`dispatch-gates`、预期 skip 名单 `check-expected-skips`、条款②配对检查
`check-clause2-carriers`)时,现在每处都写明它答的是哪个仓、拼写里带上仓参数;〈多仓协调〉加一条总则「工装只答被告知的仓,缺参数即答
objectstack,⛔
不答手上那张卡的仓」;平台读数表里代理拒绝那一行补上判别式(缺键即拒绝,看状态码)。零脚本改动,三个文件行数全部中性。

**为什么改**:一位在 objectui 上工作的席位实测:三件工装三种行为,其中预期 skip 名单会拿 objectstack
的花名册去判 objectui 的板,交出一份它判不了的放行;技能文本里没有任何一行提醒。本 PR 逐件复测并把结论写在被引用的那一行上。

**风险与代价(含回滚)**:纯文本改动。付账方式是删掉 SKILL.md 一行在 `lanes/ui.md`
与〈域车道〉均已重述的规则;:539 为容纳 `--repo` 去掉了「⛔ 不抓人读输出」的措辞(机读模式已由
`--commands`/`--ran` 拼写承载)。回滚 = revert 单个 commit。

**席位意见**:(留给席位)

**你要做的(一个动作)**:批准本 PR,或点名要改的那一行。


---
_Generated by [Claude Code](https://claude.ai/code)_

---------

Co-authored-by: os-dev <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 28, 2026
…rries its reading or is marked 未验证 — the 派发令 evidentiary duty extended to 裁决 (objectstack-ai#18679)

Fixes objectstack-ai#18506

Clause-②: no

`skip-changeset` — the only touched path is
`.claude/skills/pm-dispatch/SKILL.md` (governed `.claude/**`, internal
agent tooling; it ships in no package's `files[]`).

## The defect, in one paragraph

A ruling is consumed exactly like a dispatch order — quoted verbatim
into the brief, ⛔ not re-litigable, treated by the dev as settled — yet
the charter's evidentiary discipline binds dispatch orders only:
「派发令里关于代码的危害断言必须有读数(点名 call site / 路径 / 迁移)」,
「搬自任何工件(含己文)本轮未亲测计数/零命中/文件面:重测或注「未验证」点名来源」, 「PM 测不了的危害 ⇒ 写成给 dev 的问题,⛔
不写成栅栏」. Nothing required a ruling's code-level assertions to carry a
reading, and nothing marked them unverified when they did not. The
artefact with more authority had fewer evidentiary requirements than the
one that merely relays it — the asymmetry is the defect. This PR extends
the same two options (carry the reading, or mark that clause 「未验证」) to
裁决, so the dev reading a ruling knows which clause to falsify first. ⛔
It is not 「measure before ruling」, adds no gate or script, and re-grades
no ruling.

## The evidence: six for six on one split (card comment 5701534399)

| ruling | kind of claim | outcome |
|:--|:--|:--|
| objectui#8938 | asserted a code fact (「every one folds in the
restrictive direction」) | broke — `_packageId` presence admits an
affordance at 14 of 38 read sites |
| objectui#8826 | asserted a code location (`ObjectKanban.tsx`'s
`handleDragEnd`) | broke — it lives in `KanbanImpl.tsx` |
| objectui#8209 | asserted a call shape (`field.format ?? 'compact'`) |
broke — `temporalText(type, value, locale)` takes no `field`; no
`format` key on the published type |
| objectui#8167 | asserted a mount topology (「each host mount supplies
it」) | broke — zero mount sites; registry-resolved |
| objectui#7771 | cited standing rules (「一个标签存在当且仅当有具名读者」) | held |
| objectui#9537 | cited standing rules (「门禁内部参数与扫描器盲区修复(加强,非削弱)」;
ADR-0049) | held — every code fact it named had been READ; its one slip
(`:866-870` for `:878`) was inherited from the card body |

The rulings that reasoned from rules held; the four that asserted
implementation facts broke. The exposure is confined to the moment a
ruling names a file, a symbol, a line or a call shape — the same
boundary the charter already draws for dispatch orders, which is why the
line below carries a rule-citing exemption rather than a measurement
duty.

## The change — one line in, one line out, SKILL.md 812 / 812

**In** — `.claude/skills/pm-dispatch/SKILL.md` :731 (work tree), section
升级与决策, between 「卡上裁决评论是详注,仍是执行与复核的第一落点。」 and 「出决策箱须引同趟取回过的裁决 id …」:

```text
- 裁决点名文件/符号/行/调用形状的条款,逐条带读数或注「未验证」;只引规则的条款免。
```

118 B (cap 120). Reads: a ruling clause that names a file, a symbol, a
line or a call shape carries, per clause, the reading behind it or is
marked 「未验证」; a clause that only cites rules owes nothing. 读数 is the
corpus's defined term (:166–:167: UTC 取数时刻 + ref/tip; AGENTS.md: a count
plus the tree it was taken against), and 「未验证」 is the dispatch-order
rule's own mark, so the mark travels verbatim with the clause when the
ruling is quoted into a 派发令.

**Out** — the 分诊座位职责 line at origin/main :309, 95 B:

```text
- 代裁只由维护者召唤的总监席产出,⛔ 永不凭自述、⛔ 无子代理裁决。
```

Every clause of it is already stated in the ruling section this PR adds
to: 「总监席是唯一裁决者:决策箱、代裁与一类自裁只出自本席,⛔ 无子代理无自述。」 (:769) covers 代裁 ⊂
{决策箱、代裁、一类自裁}, 只出自本席, 无自述, 无子代理; 「项目总监席:维护者在所在仓召唤 …」 (:765) covers
维护者召唤; :75 「裁决不在本席,归维护者召唤的总监席」 and the red line :30 「唯一例外是已裁的代裁通道」 still
tell the triage seat the same thing in its own sections. Deleting the
second spelling is the corpus discipline (one rule per line, no rule
stated twice), not a weakening — a control grep for the deleted spelling
returns 0 and each restating line returns its line.

Why no cheaper payment existed: measured on this tree, of SKILL.md's 598
adjacent bullet pairs ZERO merge under the 120-byte cap (smallest 124
B), and in the 升级与决策 section ZERO of 40 (smallest 158 B); re-wrap
funding is refused by the ratchet rule. The whole-file diff vs
`origin/main` is exactly these two hunks (`1 insertion(+), 1
deletion(-)`).

## Placement — SKILL.md, not core-rules.md, along the four axes

- **实际业务需求** — the consumers are the seat writing a ruling and the dev
reading it quoted into a brief; both read SKILL.md in full every
session. core-rules.md :3 says 「本文不新增规则」 and :43 makes it 「本文的子集」, so it
cannot be the sole home; a twin there would cost a second line in a file
at 151 / 151 where ZERO of 122 adjacent pairs merge under the cap
(smallest 175 B, measured on this tree) and no line restates another —
an unrelated compression to fund it, which the ratchet's own precedent
refuses.
- **项目长远合理性** — the line sits in the block that defines the ruling
record (:729 权威载体, :730 卡上裁决评论, :732 出决策箱须引裁决 id), where 裁决 is the
generic term — maintainer ruling, 代裁, 一类自裁, PM 裁量, retriage 改判 — so it
binds every seat that rules without naming one. :44
(一条规则在本文与核心条款一处改动,另一处同 PR 同改) is not triggered: the new line is new, and
the three dispatch-order lines whose twin is core-rules :116 are
byte-untouched.
- **防 AI 写错** — the mark is mechanical (a clause either has a 读数 or the
literal 「未验证」), per clause, and rides verbatim into the 派发令 under the
existing 「裁决不可重裁 / 机制假设须实测」 partition; no seat has to judge whether it
is a nice-to-have, and a 「不 expected」 built on an unread function is no
longer a legal spelling.
- **创业阶段不扩散** — one line, zero net lines, no gate, no script, no new
label, no re-grade; the rule-citing exemption keeps the cheap reading
and forecloses the expensive one.

## Measured: the neighbours do not contradict it, and the quoted lines
are untouched

Neighbouring text (work tree): :715 「② 把裁决挂在具名、可证伪的前提上,派发令要求 dev
先验前提再动手。」 (a premise-bearing ruling names its premise — compatible: the
new line says which clauses of any ruling are the premises); :180
「裁决明令的动作实施中测出对向事实 ⇒ 照字面执行 …」 (what happens when a measurement
contradicts a ruled action — unchanged); :427–:432 (the PM's
pre-dispatch staleness re-read of a ruling's 动作面/卡引用面 — about the
re-read, not about what the ruling carries); :401 「①
既有裁定/在案类规则/成文纪律机械决定方向,裁决评论点名所用权威」 (the class-1 gate is rule-citing by
construction — the exemption agrees with it);
`references/lanes/director.md` :11 (the ruling comment's fixed first
line — unaffected).

Byte-identity against `origin/main` `7f7b8557d` (diff of the extracted
lines, empty): the three quoted dispatch-order lines (:541–:543 there,
:540–:542 here); PR objectstack-ai#18656's :91; PR objectstack-ai#18666's :198–:209, :536–:542 (now
:535–:541), :625–:631 (now :624–:630), :642–:648 (now :641–:647). The
set of over-120 B lines is unchanged (23, all table rows plus the
claim-template line); widest row 342 B, pin 342. Line numbers shift by
−1 between the deleted line and the inserted one; no hunk overlaps
either PR's hunks.

## Readings

Head `cb1a949d0` (`origin/main` `f6c2eb7c8` merged — one unrelated
driver-sql commit; the SKILL.md delta unchanged). Derived with `node
scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` from the worktree (change set from the merge
base, no hand-fed paths): 18 commands, list identical before and after
the merge. Exit codes captured redirect-then-`$?`, reconciled with
`--ran`: 「18 derived, 18 run, 0 NOT-MEASURED (a DERIVED zero — all 18
recorded an exit code and none of them is 3)」.

| command | exit |
|:--|--:|
| `node scripts/check-closing-keyword-parity.mjs` | 0 |
| `node scripts/check-closing-keyword-parity.mjs --self-test` | 0 |
| `node scripts/check-comment-mask-corpus.mjs` | 0 |
| `node scripts/pm/check-governed-queue-guard.mjs --self-test` | 0 |
| `node scripts/pm/check-harness-current.mjs --self-test` | 0 |
| `pnpm --filter @objectstack/lint run check:doc-formula-expressions` |
0 — first run exit 3 = PREREQUISITE NOT MET (unbuilt
`@objectstack/formula` / `@objectstack/lint`, 「Nothing was measured」);
built both under `os-verify-lock.sh` (VERDICT command-exit 0, held 165
s, waited 0 s), then rerun |
| `pnpm check:agent-test-spelling` | 0 |
| `pnpm check:doc-authoring` | 0 |
| `pnpm check:driver-memory-census` | 0 |
| `pnpm check:nul-bytes` | 0 — 「OK (scanned 8798 text file(s) … no raw
ASCII control bytes)」 |
| `pnpm check:pm-governed-merges` | 0 |
| `pnpm check:pm-governed-prose` | 0 |
| `pnpm check:pm-half-states` | 0 |
| `pnpm check:pm-skill-id-lint` | 0 |
| `pnpm check:pm-skill-ratchet` | 0 —
「.claude/skills/pm-dispatch/SKILL.md is 812 lines (ceiling 812; headroom
0)」 · 「widest table row is 342 bytes (pin 342; headroom 0)」;
core-rules.md untouched at 151 / 151 |
| `pnpm check:refd-timer-probe` | 0 |
| `pnpm check:skill-frame-sync` | 0 — 「the one declared copy of the
decision frame is internally coherent
(.claude/skills/pm-dispatch/SKILL.md …)」 |
| `pnpm check:watch-hint-literal` | 0 |
| `pnpm check:pm-expected-skips` (explicit, not derived; its self-test
pins the `check-expected-skips.mjs` literal on the enqueue-bar line,
untouched) | 0 — 「99 cases pass」 |
| `pnpm check:skill-frame-freshness` (explicit, outside the derivation)
| 0 — 「the decision frame in this tree is current with origin/main
(fetched just now)」 |

`node scripts/pm/check-governed-merges.mjs --test
.claude/skills/pm-dispatch/SKILL.md` → exit 3, 「GOVERNED — a human merge
is the review record for this PR」 — recorded as the expected answer, not
a failure. CI-owned and not run locally: the 52 artifact-roster
families, the 11 wide-population families and `pnpm lint` (the
derivation names them as outside the 18; none reads this file's
content). No package build is owed by the diff (no package touched); the
one build above was that gate's own prerequisite.

## Acceptance notes

- noted, not filed: `references/lanes/director.md` :9
「裁决只存在于本卡:聊天原话、台账行、他卡引述 ⛔ 都不是裁决」 and SKILL.md :729 「一条裁决已记录的权威载体 =
总监席每场收工的摘要台账」 name different carriers as authoritative for a ruling.
Observed only; not a defect class (a)–(c), and outside this card. 承接者:无.
- noted, not filed: core-rules.md carries no twin of the new line
(reasons above). If the maintainer wants one, it is one core-rules line
at 151 / 151 with no duplicate to pay — a ruled raise or a compression
of another rule, both the maintainer's call. 承接者:the `domain:skills`
seat, at the maintainer's word.

## 维护者速读(草稿)

**改了什么** — `pm-dispatch` 技能文件加一行、删一行,行数不变(812 /
812):裁决里凡点名文件、符号、行号或调用形状的条款,要么带上得出它的读数,要么该条注「未验证」;只引规则的条款不欠。删掉的是分诊节里一条与总监席节完全重复的「代裁只出自总监席」拼写。

**为什么改** — 派发令早有这条纪律(代码危害断言必须有读数,未亲测就注「未验证」),但裁决没有。裁决被 dev
当作已定事实照抄进派发令,不可重裁。一轮六张卡实测:引规则的两条裁决全对,断言实现事实的四条全错(文件位置、调用形状、挂载拓扑、折叠方向),每次都靠
dev 自己重新测量才发现。权威更高的工件反而举证要求更低,这是结构性缺口,不是三次失误。

**风险与代价(含回滚)** —
零新增门禁、零脚本、零标签;只约束裁决的写法,不要求先测再裁(引规则免费,只有点名代码事实的条款才欠读数或标记)。代价是出裁决的席位多写几个字。回滚
= revert 这一个 commit,两行原样回来。

**席位意见** — (留空,由席位填写)

**你要做的(一个动作)** — 批准这个 PR(受管面 `.claude/**`,需要你的 approve 或亲手合并)。

---
_Generated by [Claude
Code](https://claude.ai/code/session_01Gqi43smmqjJ5sUrhfoPeKu)_

Co-authored-by: os-dev <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/xs skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants