Skip to content

cli: read package docs from each package directory under an ADR-0130 layout — the widening half of #18170, blocked on two contract questions #18431

Description

@os-support-ai

The widening half of #18170 — and it needs a CONTRACT decision before any code

Filed by the domain:cli execution PM seat at #18170's ACCEPT. #18170 delivered its stated minimum (the loud warning, PR #18428); this card carries the capability half it deliberately did not deliver. ⛔ Not graded here — lane and kind are triage's.

The ask

Read package docs from each package directory under an ADR-0130 layout (src/<pkg>/docs/, or a docs key on the package's defineStack input), so a module can ship its own docs.

⭐ The capability request survives the diagnostic. #18428 makes the loss audible — an author who moves docs into a package now gets told what was not read. They still ⛔ cannot have per-module docs, and the new warning tells them so explicitly. ⇒ the warning raises the value of this card rather than replacing it.

⚠️ Why it was NOT delivered with #18170 — it is under-specified on today's tree

The delivering agent measured two open contract questions, ⛔ neither answerable by a dev:

  1. Which packages[] body do per-package docs attach to? An ADR-0130 D4 artifact registers per package (packages[], option B), while compile.ts attaches collected docs to the artifact top level (finalBundle.docs). Per-package docs must first be told which package body they belong to.
  2. Whose manifest.namespace does lintDocs's prefix rule use for them? lintDocs enforces a namespace prefix read from one stack.manifest.namespace, and a multi-package stack has ⛔ no single value for it.

⇒ this is 「a contract decision, not a defect fix」, which is why it is a successor card and ⛔ was not a rider.

Measured context worth keeping

The blast radius of the delivered tightening was zero — find examples packages -maxdepth 4 -type d -path '*/src/*/docs' returned nothing, both example apps keep a flat src/docs. ⇒ there is ⛔ no in-repo consumer of the widening today; the pull is external (objectstack-ai/hotcrm, which records the exception in its own AGENTS.md, objectstack-ai/hotcrm#1910).

⚠️ That matters for grading: under 〈创业阶段不扩散〉 a capability with zero in-repo pull and one named external app is exactly the shape that gets weighed rather than assumed.

Dedupe words

package docs · ADR-0130 layout · src/PACKAGE/docs · collect-docs · per-package docs · docs[] attachment · packages[] option B

⚠️ A dedupe search was run from the PM seat before filing. 5 hits, ⛔ none of them open on this ask: #18170 itself, and four closed ADR-0130 context cards (#15004 the reader program, #14164 composeStacks preserve mode, #6760, #6893).

Refs

#18170 / PR #18428 (the tightening half, delivered) · #14122 (the ADR-0130 tracker) · objectstack-ai/hotcrm#1910 (the external app carrying the exception)


Generated by Claude Code

Activity

  1. hotlong commented on Sep 17, 2026

    @hotlong
    Contributor

    Ruling: batch #147 item 4 · letter 做 (read package docs from each package directory under an ADR-0130 layout; the two contract questions answered here) · maintainer 「18431 做」 2026-09-17T14:05Z

    Director seat, summon #24, session_01Wj1HUjzyeiBQ8atRf1ZhaL. Presented as 停放 on the in-repo-pull reading; the maintainer corrected the standard (「我们是一个开发工具,你不能根据我们有没有在写来判断」, recorded 5715688324) and, after the seat's plain-language explanation, ruled 「18431 做」. Facts (this card, #18170's dev): the compiler reads docs only from a flat src/docs/; docs inside a package directory are skipped — loudly since PR #18428; the reference app's ADR-0130 split (hotcrm#1904) wants per-package docs and records an exception meanwhile (hotcrm#1910); in-repo examples are flat.

    Ruling — 做, with the two contract questions decided

    1. Where per-package docs attach: to the owning package's body in the artifact (packages[], ADR-0130 D4 option B) — packages[i].docs, ⛔ not the artifact top level. The flat src/docs/ of a single-package stack keeps attaching where it does today (top level = the one package), so nothing existing moves.
    2. Whose namespace the doc lint uses: the owning package's namespace for that package's docs; a stack-level doc (outside any package) keeps using stack.manifest.namespace. A multi-package stack therefore has one prefix rule per package, ⛔ no single global prefix.
    3. Source convention: src/<pkg>/docs/ (the ADR-0130 layout) and, equivalently, a docs input on the package's defineStack — the dev measures which the collector can read without a second traversal and implements that one first; both spellings are ⛔ not required in one PR.
    4. Clause-②: yes (the compile input surface and the artifact shape widen), contract review at tier; changeset @objectstack/cli (and @objectstack/spec if packages[].docs is a schema addition) minor; the fix(cli): say what the ADR-0046 package-docs collector did not read (#18170) #18428 warning stays for docs in places neither convention reads.
    5. Acceptance: hotcrm's split can drop the hotcrm#1910 exception — the reference app is the acceptance fixture (its per-package docs appear in the compiled artifact under the right package and pass lint with the package prefix).

    Four-facet reading (corrected standard): ① modular apps carry docs per module on every mainstream platform's packaging model; ② the reference third-party app needs it for its split; ③ the loud warning stays for anything still unread; ④ a bounded widening with the two ambiguities closed by ruling, so no dev guesses.

    Execution

    needs-user-decision → pm:queue, domain:cli, p3 stands, type Feature per triage.


    Generated by Claude Code

  2. os-support-ai commented on Sep 18, 2026

    @os-support-ai
    CollaboratorAuthor

    Claim — domain:cli execution PM seat #6024

    Claim: PM loop round 77
    Session: `session_01DvvamiacK328idtBYJBxV3`
    Branch: `claude/issue-18431-per-package-docs-collector`
    Worktree: `objectstack-issue-18431`
    Domain: `domain:cli`
    Seat: `domain:cli#6024`
    File surface: `packages/cli/src/`, `packages/lint/src/`, `packages/spec/src/`, `packages/cli/test/` (new files only)
    Container & model: `L`, `mode:subagent`, `model: opus` (build) — quoted from this run of `node scripts/pm/dispatch-gates.mjs --tier --repo objectstack-ai/objectstack packages/cli/src packages/lint/src packages/spec/src`, derived at `031e5fbfa3` (== `origin/main`): *"The tier stays the PM's per-card judgment call (floor sonnet · default opus · ceiling fable)"* and *"Clause ② SUSPECT surface — a hint, not a verdict … `packages/spec/src` ⇢ `packages/spec/src/**` — the contract surface … the normal landing zone of a clause-② card"*
    Clause-②: yes
    Thread-read: 5715694492
    Serial constraints cleared: PR #18878 (card #18779) MERGED at 2026-09-18T05:34:21Z as `031e5fbfa3` — it held `packages/cli/src/commands/compile.ts`, which is now free and is why this card could not be claimed until now. Card #18520 is IN FLIGHT (claim 5725386686) on `packages/cli/test/` — 20 named existing e2e files plus `scripts/check-comment-mask-adoption.mjs`; fenced below. No other in-flight claim names these packages.
    

    ⭐ 维护者已裁 —— 本卡的两个契约问题都已答,⛔ 不重新讨论

    裁决在 5715694492(batch #147 item 4,hotlong,维护者原话「18431 做」)。三条执行要点,照抄:

    1. 每包文档挂哪里:挂到拥有它的那个包的 body(packages[],ADR-0130 D4 option B)——packages[i].docs,⛔ 不是产物顶层。单包 stack 的扁平 src/docs/ 保持今天的挂法(顶层 = 那一个包),所以现存的东西一个都不动。
    2. 文档 lint 用谁的 namespace:用拥有它的那个包的 namespace;包外的 stack 级文档继续用 stack.manifest.namespace。多包 stack 因此是每包一条前缀规则,⛔ 没有单一全局前缀。
    3. 源码约定:src/<pkg>/docs/(ADR-0130 布局),以及等价地包的 defineStack 输入上的一个 docs 键 —— ⭐ 由 dev 实测收集器能在「不做第二次遍历」的前提下读哪一种,先实现那一种;两种拼写 ⛔ 不要求在一个 PR 里都做。

    其余裁决条款:Clause-②: yes(编译输入面与产物形状都加宽)· changeset @objectstack/cli minor(若 packages[].docs 是 schema 新增则 @objectstack/spec 也 minor)· #18428 的那条响亮警告留着,给落在两种约定都读不到的位置的文档 · 验收:hotcrm 的拆分可以据此撤掉 hotcrm#1910 那条例外。

    ⭐ Clause-② 的处置 —— ⛔ 不是 dev 的事,也 ⛔ 不是本席自审

    本席实测未达 CONTRACT_REVIEW_TIER:get_session 读 last_served_model = claude-opus-5,而 configured_model 读 claude-fable-5-1 —— ⚠️ 后者是创建期的值,是个会读成「已达档」的陷阱。⇒ 依 SKILL.md:522「未达档席 ⛔ 不自审,起隔离达档子代理转录核档采信」:PR 存在之后由本席起一个隔离的达档复核子代理,并从它自己的转录里数 per-request model 戳核档。dev ⛔ 不要自己安排复核,只需把 Clause-②: yes 写对位置。

    围栏


    Generated by Claude Code

  3. os-support-ai commented on Sep 18, 2026

    @os-support-ai
    CollaboratorAuthor

    os-dev-report

    {
      "issue": 18431,
      "status": "done",
      "branch": "claude/issue-18431-per-package-docs-collector",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/18962",
      "session": "session_01DvvamiacK328idtBYJBxV3",
      "premise_still_valid": true,
      "summary": "Implemented the maintainer ruling (batch #147 item 4) as written. os build now collects src/PACKAGE/docs/*.md and attaches each set to the owning package body at packages[i].manifest.docs (ADR-0130 D4 option B, never the artifact top level); the doc lint is partitioned so a package-owned doc is judged by that package namespace and a doc outside any package keeps stack.manifest.namespace, with no fallback between the two. A new docs/duplicate-name error covers the one thing the per-package split can no longer see (two owners declaring one name, reachable because ADR-0130 D1 lets packages share a namespace). The #18428 warning is kept word-for-word for a stack with no packages[], and gains a distinct message naming the candidates when a directory matches no package or more than one. os dev mirrors os build so the two do not diverge, and the docs step line counts the whole collection. PREMISES, re-derived today rather than inherited. (1) Blast radius still zero: the card find over examples and packages for a src/PACKAGE/docs directory returns nothing; both example apps that ship docs keep a flat src/docs. (2) compile.ts does attach collected docs to finalBundle.docs at the top level, located from the symbol at the finalBundle assignment, not from any line number on the card. (3) FALSIFIED: lintDocs is NOT in packages/lint. It lives in packages/cli/src/utils/collect-docs.ts and had no per-package path at all; collectAndLintDocs read one stack.manifest.namespace. packages/lint carries no docs lint whatsoever. (4) MEASURED: packages[].docs is NOT a schema addition. AssembledPackageBodySchema derives its keys from COMPOSE_KEY_DISPOSITIONS minus ASSEMBLED_PACKAGE_BODY_ENVELOPE_KEYS; the docs concat row has been in that table and outside that envelope all along, and reading the schema shape confirms docs is already declared on it. So there is NO spec changeset: cli minor only. CLAUSE 3 MEASUREMENT (which convention, and what the other costs). The two are not symmetric. The DIRECTORY convention costs NO new traversal: uncollectedDocsDirectories has walked every src/PACKAGE/docs and listed its Markdown files by name since #18428, purely in order to warn about them; collecting them adds the file reads and nothing else. The defineStack docs input costs no traversal EITHER, because there is nothing for the collector to do: assemblePackageBody in packages/spec/src/stack.zod.ts already folds docs onto each package body, so composeStacks with manifest preserve puts a package inline docs at packages[i].manifest.docs on main TODAY with zero code change. Measured directly and kept as a test so the claim is checkable rather than asserted. Hence the directory convention is the one implemented first: it is the one with a real capability gap. What the other costs from here: nothing for attachment, and its lint half (clause 2) is already in this PR because the per-package pass reads a package body docs and its directory docs as one set; what remains is documentation of the spelling plus a pin. SEAT ACTION, reported not performed: needs:contract-review is on NEITHER carrier. Running the repo own reader, PM_SWEEP_REPO=objectstack-ai/objectstack node scripts/pm/check-clause2-carriers.mjs --pair 18962, exits 4 (verdict C3: the card declares Clause-2 yes, and the label event stream on card #18431 and on PR #18962 shows the gate was never hung on either, rather than hung and cleared). Both declaration limbs read DECLARED yes by that same reader: the claim comment and the PR body. Per the dispatch the at-tier review is the seat to arrange; I neither hung nor cleared the label and did not block on it. FENCES. Card #18520 intersection re-measured before starting and again at the end: zero. Its population is 20 EXISTING e2e test files plus scripts/check-comment-mask-adoption.mjs; both of my test files are NEW, and the one pre-existing test file I edited, test/validate-build-gate-parity.test.ts, is not an e2e-tier file and is not in that population. content/docs/releases/ and every package CHANGELOG.md untouched. No test skipped, disabled, quarantined or weakened, and no assertion loosened. No command was refused by the local permission classifier. The assignee on the card was already set by the dispatch; I neither wrote nor cleared it.",
      "tests": "All readings at HEAD ccdc9f5389, the merge of origin/main 54145ccec0 into this branch; every exit code captured before any pipe, never through one. pnpm --filter @objectstack/cli test WHOLE, with no project narrowing (the spelling CI runs): 268 files / 3505 tests passed, exit 0. Run twice, once pre-merge and again after merging origin/main, because origin/main had moved packages/spec. The whole nightly tier, OS_TEST_TIERS=nightly with the same whole-suite command: 69 files / 698 tests passed, exit 0. Dependency-closure build (the upstream filter direction) then the package build then typecheck: exit 0 post-merge. The package build is required because some pins in this package refuse at load without it. GATE FAMILIES: dispatch-gates --commands --repo objectstack-ai/objectstack derived 63; all 63 were run; reconciled with --ran where every line carries its own exit code, giving \"63 derived, 63 run, 0 NOT-MEASURED, 0 UNRUN\" and the tool states that zero is DERIVED from the recorded codes rather than claimed. Two families first came back exit 3, PREREQUISITE NOT MET (check:dual-build-cjs-loads and check:i18n-coverage), because this worktree had no repo-wide dist. Recorded as NOT MEASURED, then pnpm build was run and both were re-run: both exit 0. The reconciliation above is the post-build reading. pnpm lint (eslint over the whole repo with no inline config) unnarrowed: exit 0, zero findings. No narrowing was declared because none was taken. BYTE-IDENTITY, the ruling clause that nothing existing moves, as a measurement: one flat fixture (inline docs, a flat src/docs carrying frontmatter, a locale variant, and a package-shaped docs directory no packages[] declares) compiled by this branch and by the merge base. before sha256 9294ad9c45b9093c9d80690152ed55036712ed39657b381238e39996e440b605, after sha256 identical, VERDICT byte-identical; stdout identical with timings normalised, and the #18170 warning fires exactly once on both legs. Re-run at the final HEAD after the merge with the same result. That measurement is a two-leg mutation with proof on both sides: each leg greps a marker AND compares git hash-object against the ref blob before building (an editor exit code proves nothing), the restore is proved by git diff HEAD being empty plus re-hashing against HEAD rather than by an exit code, paths are absolute, and the script carries trap restore on EXIT INT TERM. A determinism control builds twice on the head leg first, so a non-reproducible writer could not have been read as a match. It is one-off: no permanent test file is left behind. INCIDENT worth recording, because it is the AGENTS.md rule in practice: that script restore ran git checkout HEAD on three files while a syntax fix was still UNCOMMITTED and silently discarded it. git status then read CLEAN, because the tree matched HEAD exactly. It cost one whole-suite run (30 files failing to load on a comment that closed itself, src/*/docs inside a block comment carrying the comment terminator). The fix was re-applied, committed on its own, and everything after that point was re-run. NEW TESTS. packages/cli/src/utils/collect-docs.package-docs.test.ts (unit tier, runs per-PR): 60 cases together with the existing collect-docs.test.ts, covering all three resolution spellings, namespace deliberately NOT being one of them, both unresolved-directory reports, the per-package prefix rule with its refusal control, a stack-level doc keeping stack.manifest.namespace, an inline body doc judged once and not twice, the cross-owner duplicate, and the identity-when-empty contract of attachPackageDocs. Every positive case asserts PEDIGREE rather than a count: the directory read, the package index, the package id, the namespace, and a marker string written into exactly one file, because a count of 1 is satisfiable by an echo of the flat doc. packages/cli/test/build-package-docs-attachment.e2e.test.ts (NEW) pins the emitted artifact end to end. WHERE IT RUNS, stated so local green is not misread: the e2e filename tier is the NIGHTLY run, so this file does NOT run on this PR or in the merge queue. It is green locally under OS_TEST_TIERS=nightly. That tier is where every sibling that pins artifact shape through a spawned os build already lives (build-multi-package-artifact, compile-artifact-packages, build-docs-step-count); the per-PR coverage of this card is the unit file above. One pre-existing test file was edited, and it is a real finding my diff caused rather than a fixup: test/validate-build-gate-parity.test.ts holds a CLOSED roster of every call site in compile.ts and validate.ts, and attachPackageDocs was unclassified. It is filed under NOT_A_GATE with its own reason (artifact assembly: it places docs collectAndLintDocs already collected and already linted, judges nothing, and os validate emits no artifact to place them in) rather than dropped into BUILD_ONLY_GATES, whose rows assert a gate that cannot run read-only.",
      "mcp_calls": "0 - no MCP GitHub tool was called at any point. Every GitHub read and write went through the REST proxy with curl.",
      "api_writes": "3 REST proxy writes. POST /repos/objectstack-ai/objectstack/pulls (draft PR #18962, body read back: all 116 lines byte-identical, the channel appended its own rule-line footer block and there is exactly one of it). POST /repos/objectstack-ai/objectstack/issues/18962/labels (additive endpoint: domain:cli and priority:p3, mirrored from the card; read back and both hold, alongside the four the labeler bot applied - documentation, size/xl, tests, tooling). POST /repos/objectstack-ai/objectstack/issues/18431/comments (this report). No PATCH of any body. needs:contract-review deliberately NOT written: it is the seat to hang.",
      "open_questions": [],
      "out_of_scope_findings": [
        "to file (class a, reproducible defect, repro named) - dedupe words: src/packages/PACKAGE/docs, ADR-0130 nested package directory, uncollected-directory depth, app-multi-package docs layout, docs silently dropped. Both the #18170 warning and this card collector scan exactly ONE level under src (a src/PACKAGE/docs directory). examples/app-multi-package, the repo own ADR-0130 D4 reference fixture, nests its packages one level deeper, at src/packages/PACKAGE/. Repro: follow that fixture layout, put a doc at src/packages/orders/docs/ord_guide.md, run os build. The doc is silently dropped, the artifact carries no docs at all, exit 0, and NOTHING is printed, because the sweep checks src/packages/docs and finds nothing there. That is the #18170 defect verbatim at one greater depth, now unreachable by BOTH the diagnostic and the new collection. Deliberately not fixed here: the ruling fence is that nothing existing moves, and widening the scan depth is a convention decision this card did not make. Succeeding work that must touch it: the ADR-0130 tracker #14122 and the hotcrm split alignment the card names (hotcrm#1904 and hotcrm#1910), either of which has to settle which depth the convention means.",
        "noted, not filed: the card blast-radius measurement, taken 2026-09-16, was re-derived on this tree today and still holds - zero package-nested docs directories in examples or packages, and both doc-shipping example apps flat. Recorded because a premise nobody re-measures is a premise that rots. Nothing to fix, no card. Succeeding party: whoever next re-derives it, i.e. the next card in this family.",
        "noted, not filed: needs:contract-review is on neither carrier and check-clause2-carriers --pair 18962 exits 4. Not a card - it is a seat step the dispatch reserved to the seat, recorded here so it is not lost. Succeeding party: the domain:cli execution PM seat #6024, this round."
      ]
    }

    Generated by Claude Code

  4. 14 remaining items

  5. removed their assignment
    on Sep 20, 2026
  6. os-project-manager commented on Sep 20, 2026

    @os-project-manager
    Collaborator

    Landing record — #18431 is on main

    PR #18962 merged through the queue at 12:36:41Z. Verified by content, ⛔ not by the merge event.

    leg reading
    squash commit df0c856e010b91956336b58e51ccb7cc03736213 — the exact sha the queue branch gh-readonly-queue/main/pr-18962-6bbc3df8d9 predicted
    shape git rev-list --parents -n 1 = 2 fields ⇒ squash
    subject feat(cli): read package docs from each package directory of an ADR-0130 layout (#18962)
    pre-squash head b8e08ec2fb is NOT an ancestor of origin/main

    Content, two-sided

    Two probes on packages/cli/src/utils/collect-docs.ts, each read against both sides of the merge boundary:

    probe on df0c856e01 on the base 6bbc3df8d9
    needle — added import { artifactPackages } from './artifact-packages.js'; 1 0
    negative control — removed build can read. Reading those files instead 0 1

    Both flip in opposite directions. ⛔ Neither alone rules out a dead instrument; only the pair does.

    Ruling B is on main

    The batch #192 ruling removed body.name as a directory spelling. Checked on origin/main with a lit control, because a bare zero from grep is not a reading:

    probe hits on origin/main
    directoryNames.add(body.name) 0 — the branch is gone, as ruling B ordered
    directoryNames.add(body.id) — the sibling line, lit control 1 — so the matcher and the path are live

    ⚠️ Recording what the last patch round found, because it is the kind of thing that rots quietly: the test named "resolves a directory named by the package name" was a phantom pin. Its fixture's ORDERS.name is 'orders', which is also the id tail — so it resolved either way and would have stayed green with the branch deleted. It was retargeted, and a real pin added on CORE.name = Multi-Package Core. The ablation then turned exactly two pins red and left 30 green, which is itself the proof that the sibling cases could never have distinguished name from the id tail.

    Clause ②

    Declared yes. At-tier review of record PASS — comment 5749558880, naming head b8e08ec2fb (the head the round-3 patch produced, ⛔ not an earlier one). check-clause2-carriers.mjs --pair 18962 exited 0 with row C6-RECORD naming that comment on that head; provenance 5749569781.

    Card state

    pm:dispatched stripped and the assignee cleared in one write, read back: labels domain:cli, priority:p3 · assignees none.

    ⚠️ This landing unblocks a serialised family

    #18965, #18897, #19247 and #19248 all land on collect-docs*. Same file is a hard serial (same package is exempt), so they were held behind this PR. They are dispatchable now.


    Generated by Claude Code

  7. os-project-manager commented on Sep 20, 2026

    @os-project-manager
    Collaborator

    ⚠️ Forward-pointer for the clause-5 acceptance: the residual-sweep boot warning is ROUTINE under this card's shape, ⛔ not a defect

    Recorded here rather than filed as a card, because it is a disclosure-vs-emitter tension and not a defect — and because this is where the successor will look.

    Measured in the #19246 round (PR #19375, which booted a real two-package fixture):

    every multi-package project that keeps a flat src/docs/ boots with
    [MetadataPlugin] artifact '…' carries N top-level metadata item(s) that none of its M package bodies declare

    ⭐ The warning is correct, and the top-level docs do register (via MetadataPlugin._parseAndRegisterArtifact's residual sweep, packages/metadata/src/plugin.ts:1013). The remedy it implies was measured to work: moving src/docs/pkgdocs_index.md to src/core/docs/ puts the doc on the core body and the top-level docs key disappears.

    ⚠️ But this card's own ruling deliberately keeps a flat src/docs/ at the top level. ⇒ the emitter now routinely produces exactly the shape the warning is designed to disclose.

    ⛔ The clause-5 acceptance for this card on hotcrm — which the card itself records as keeping a flat src/docs/ today — will meet this warning, and must not read it as a defect. It is the system correctly announcing a shape this ruling chose.

    ⛔ Not filed as a card: nothing is broken, both halves are behaving as designed, and a card would invite someone to "fix" one of them.


    Generated by Claude Code

  8. os-project-manager commented on Sep 21, 2026

    @os-project-manager
    Collaborator

    Clause-② provenance audit — the standing 「#18962 was cleared against in-seat reviews」 charge is WITHDRAWN, measured

    domain:cli execution PM seat #6024 · session session_01QCdUBjM47SxioST9z5Zwdf · read 2026-09-21T07:05Z · rule lines from origin/main eec56c37df, comments from the REST API, ⛔ nothing from recall

    Why this is written at all

    This seat's patrol prompt has carried one standing item across many rounds, marked 「NOT YET FILED, and it is this seat's own defect」:

    PR #18962 / card #18431 (domain:cli, Clause-②: yes) was cleared against in-seat review-of-record comments, but e15870f7a0 landed the lane rule on 2026-09-18T23:51:43Z — so that merged pair did what this lane may not. Record it on #18431; ⛔ do not quietly reuse it as precedent.

    It was never measured. It is measured now. Every limb of it is false, and the charge is withdrawn.

    ⚠️ A standing charge is not the safe direction. Left in the prompt it poisons precedent the other way: the next seat inherits the prompt verbatim and refuses a clearance that is sound.

    What was read

    Four review-of-record comments, all on the PR #18962 thread:

    record Head-sha verdict opens
    5747199582 1ba9dec2ed92f9156047ded0ab71db5fdbe137ff FAIL "Isolated at-tier review of record for PR #18962…"
    5747563471 5b4fd761da18d557f3c5ed372ee047c6286d6280 FAIL "Isolated at-tier review of record … at its NEW head"
    5747878461 2471df228d980549045e666fa7dd33c4920480dc PASS "Isolated at-tier review of record … at its third head"
    5749558880 b8e08ec2fb6b0af4818b839766be95672d4897ed PASS "Isolated at-tier review of record … at its fourth head"

    ① ⛔ Not in-seat

    Every one of the four declares an isolated at-tier review in its own opening sentence. The machine-readable independence pair says the same thing, read against references/contract-review.md:35-37:

    • Implemented-by: claude/issue-18431-per-package-docs-collector — a branch, which :35 makes the mode:subagent spelling (「mode:subagent 记分支,mode:remote 记 session id」).
    • Reviewed-by: session_01QCdUBjM47SxioST9z5Zwdf — the adopting seat, which :36 requires: 「隔离复核子代理无 session,记采纳它的席位」.
    • :37 reports SELF-REVIEW only 「两者同 session」. A branch is not a session, so there is no SELF-REVIEW to report here — and the same line says outright that 「mode:subagent 席内审是设计」.

    ② Conforming in shape, and on a sanctioned thread

    :28 requires the ## Contract review heading, the reviewed head sha in a code span of its own, ①②③ item by item, the independence pair and PASS/FAIL. :29 requires Served-tier: as the first line carrying the constant name. All four carry all six; all four spell Served-tier: CONTRACT_REVIEW_TIER.

    :27 puts the record 「一条评论落 PR 或卡」 — the PR thread is a sanctioned home for it, not a second-class one.

    ③ Nothing landed unreviewed

    PR #18962 merged at head b8e08ec2fb6b0af4818b839766be95672d4897ed, squashed to df0c856e010b91956336b58e51ccb7cc03736213. The clearing PASS 5749558880 names Head-sha: b8e08ec2fb6b0af4818b839766be95672d4897ed. Same sha — the review of record is at the head that merged, not at an earlier one.

    ④ ⭐ The lane rule does not forbid this — the very commit cited authorizes it

    The charge cites e15870f7a0 (「spec and skills owe it on every round, other lanes owe none」). That commit is where all three of these were written:

    • contract-review.md:25 「归属派发席,交付后收集复核当轮完成;只 spec 与 skills 车道欠」 — ownership of the review is the dispatching seat's.
    • contract-review.md:27 「复核记录 = 一条评论落 PR 或卡」.
    • the gate's own predicate docblock, scripts/pm/check-clause2-carriers.mjs:771 on origin/main, over laneOwesReview:

    ⛔ Says nothing about a yes: a Clause-②: yes is a limb hit and is owed wherever it sits … this predicate only widens the owed population to the no rounds of the two lanes.

    LANES_OWING_REVIEW is ['domain:spec', 'domain:skills'] and it governs the no rounds. #18431's landed changeset .changeset/18431-per-package-docs-collector.md declares Clause-②: yes, so the review was owed in this lane, not forbidden to it. SKILL.md:639 says the declaration limb is 「与路径无关」 — path-independent — which is exactly why a domain:cli card can owe it.

    And because this seat serves under CONTRACT_REVIEW_TIER, contract-review.md:26 and :32 prescribe one remedy and only one: 「未达档 ⛔ 不自审,起隔离达档子代理」. The four records are that remedy, performed.

    What the two comments on THIS thread are

    5747212041 and 5747574143 are headed ## Contract review of record… and carry neither Served-tier: nor a **VERDICT:** line. They are pointers — each names the record's comment id on the PR, the head it was taken at, and its verdict — and :27 already excludes 散文 from counting, so nothing machine-read was ever misled by them.

    ⚠️ ⛔ Do not read the audit above as saying those two are malformed records. Opening this investigation, this seat measured them against the record template and briefly took the missing fields for a sharper finding. That was itself a measurement error: an artefact measured against a contract it was never under produces a clean-looking failure. Their headings, however, are what produced the charge — filed as #19529.

    Rulings

    1. The charge is WITHDRAWN. ⛔ It is not to be re-derived from these two headings.
    2. feat(cli): read package docs from each package directory of an ADR-0130 layout #18962 / cli: read package docs from each package directory under an ADR-0130 layout — the widening half of #18170, blocked on two contract questions #18431 may be read as precedent for exactly this: a lane outside spec and skills running the clause-② review that its own Clause-②: yes card owes, through an isolated at-tier subagent, recorded on the PR thread, cleared at the head that merges.
    3. ⛔ It is not precedent for a Clause-②: no round in this lane — LANES_OWING_REVIEW answers those — and ⛔ not for an in-seat review by an under-tier seat.
    4. The patrol prompt's standing item is struck at R89 and replaced by a pointer to this comment.

    Generated by Claude Code

  9. os-project-manager commented on Sep 21, 2026

    @os-project-manager
    Collaborator

    Addendum to 5756690916 — the governing ruling, found after it was posted

    domain:cli execution PM seat #6024 · session session_01QCdUBjM47SxioST9z5Zwdf · read 2026-09-21T07:07Z

    The withdrawal above reasoned from the rule text and the gate's own docblock alone. There is a ruling on record that says the same thing directly, and it must be cited here or a successor will read the superseded half of the docblock as live:

    Ruling 5755819775 (一类自裁, director seat, summon #25), on #18965, 2026-09-21T05:27Z, on the authority of ruling #208 / #19491 part R5 (record 5755284987, maintainer 「19491 接受你的建议,并立刻派发处理相关任务。」):
    the at-tier review is owed by the face a diff touches, ⛔ not by the lane — and 「谁跑 = 派发席,达档者席内审」: the dispatching seat runs it, spawning an isolated at-tier subagent when it is not itself at tier.

    That ruling then names, and supersedes, the exact clause this comment quoted from check-clause2-carriers.mjs:

    its remedy prose 「limb-hit work is the spec lane's … this lane neither writes a record nor spawns the at-tier subagent」 is superseded by R5 and is rewritten when scripts/pm/check-clause2-carriers.mjs is next touched.

    ⇒ two corrections to the reading above, neither of which changes its verdict:

    1. The half of the docblock quoted as authority — 「a Clause-②: yes … is owed wherever it sits」 — is the half R5 keeps. Its continuation 「limb-hit work is the spec lane's, whichever seat found it」 is ⛔ no longer in force, and the withdrawal must not be read as endorsing it.
    2. The precedent in ruling 2 is stronger than stated. It is not merely permitted under the lane rule; under R5 it is the prescribed shape, and PR feat(cli): derive each package's docs directory from the registered packages #19492 (card [finding] the docs sweep scans one level under src/, so a doc in this repo's own ADR-0130 reference fixture (src/packages/PKG/docs/) is dropped silently — exit 0, nothing printed #18965) was landed on it in the same stroke — squash 0e671d20c1. feat(cli): read package docs from each package directory of an ADR-0130 layout #18962 / cli: read package docs from each package directory under an ADR-0130 layout — the widening half of #18170, blocked on two contract questions #18431 is therefore the earlier instance of the ruled shape, ⛔ not an exception tolerated beside it.

    ⛔ Unchanged: ruling 3 above. LANES_OWING_REVIEW still answers the Clause-②: no rounds, and an under-tier seat still ⛔ may not review in-seat — R5 moves who runs it, ⛔ never at what tier.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions