You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
finding: CLAUDE.md carries no line-ratchet ceiling, while AGENTS.md does — same read path, same governance #9965
Filed unassigned while implementing #9923 (ratchet header boundary honesty). Out of that card's scope, which was ruled to be header wording about the published skills/ root and explicitly not a coverage extension.
Measured
scripts/pm/check-skill-line-ratchet.mjsCEILINGS on origin/main at merge base 5989b0de9 covers 18 files. Root AGENTS.md is covered at 958 lines. Root CLAUDE.md is not covered, at 86 lines.
Why the two look alike
Both are repo-root agent instruction files, both are read by every seat session, both are governed surfaces under Prime Directive #14. The reasoning that added the AGENTS.md ceiling — largest, most-read, most-binding instruction file with no ceiling — applies to CLAUDE.md in kind, differing only in degree.
CLAUDE.md is also structurally growth-prone in a way the ratchet is built for: it exists to inline the rules that must never be missed, so every new must-never-miss rule is an argument for appending to it. It currently inlines four.
The honest counter-argument
At 86 lines it is small, and its own framing caps it: it is an excerpt of AGENTS.md, not an independent surface. A ceiling on it may be a control with nothing to control.
The card this was found on now makes the fact visible either way — the ratchet's header states that CLAUDE.md, .claude/hooks/ and .claude/settings.json carry no ceiling, so absence reads as a fact to check rather than an oversight to infer. This card is only about whether the fact should change.
Decision needed, not an implementation
Adding a CEILINGS row is a one-line change; whether it should exist is a policy question of exactly the kind #9923 was ruled on. Note the sibling repo objectui has its own CLAUDE.md and AGENTS.md, neither reachable from this ratchet at all, so a maintainer may prefer to answer the root-instruction-file question once rather than per file.
Maintainer ruling recorded (2026-08-20, live decision-inbox session with the triage seat, session session_01PjAP6vbcsg2yMtvySPv1Qo)
Ruled: add a CEILINGS row for root CLAUDE.md. Provenance: maintainer accepted the batch, verbatim: 「其他接受你的建议。」Rationale on record: CLAUDE.md is structurally growth-prone (every must-never-miss rule argues for appending), and a one-line ceiling now prevents compound growth cheaply. The sibling-repo question (objectui/cloud root instruction files unreachable from this ratchet) is noted for the skills seat to carve separately if wanted — not part of this landing.
Governed surface (scripts/pm/ ratchet + the covered file) — draft-only PR, human merge, contract-review tier per the skills-face rule. State transition in the same stroke: needs-user-decision → pm:queue.
Claim: PM loop round 1
Session: session_01RMTpSRF5CjMmQBFfPtPCwJ
Branch: claude/issue-9965-claude-md-ceiling
Worktree: objectstack-issue-9965
Domain: domain:skills
File surface: scripts/pm/check-skill-line-ratchet.mjs (stop on breach; explain in the report)
Container & model: S mechanical, mode:subagent, model: opus — per this dispatch's node scripts/pm/dispatch-gates.mjs --tier run: "no path-derived mandate"; governance-face mechanical change dispatched at opus construction per the 2026-08-20 tiering ruling, with contract-review-tier review as the compensating control at ACCEPT (this seat session is machine-verified at that tier)
Clause-②: no
Serial constraints cleared: none in flight — scanned all 9 open PRs, none touches scripts/pm/**; last churn on this file is merged PR #11126.
{
"issue": 9965,
"status": "done",
"branch": "claude/issue-9965-claude-md-ceiling",
"pr": "https://github.com/objectstack-ai/objectstack/pull/11274",
"premise_still_valid": true,
"summary": "Added ['CLAUDE.md', 86] to CEILINGS in scripts/pm/check-skill-line-ratchet.mjs per the 2026-08-20 ruling, quoted in place. All three PM mechanism assumptions held on measurement: CLAUDE.md is 86 lines on origin/main under the gate's OWN countLines convention (wc -l agrees), the header sentence naming CLAUDE.md as uncovered exists and was corrected in the same commit, and the file surface held — CLAUDE.md itself is untouched. One coupling the brief did not name was forced by the gate's existing self-test: the case 'every separator-less ceiling declares a root-file watch hint' goes red for any separator-less CEILINGS key lacking a hint, so ROOT_FILE_WATCH_HINTS gained 'CLAUDE.md/**' — the same spelling scripts/pm/check-governed-merges.mjs already carries for these two root files. Its docblock prose and the AGENTS.md-exclusivity self-test case were corrected with it. Everything stayed inside the one named file.",
"files_changed": [
"scripts/pm/check-skill-line-ratchet.mjs"
],
"gates_run": [
"pnpm check:pm-skill-ratchet — '✓ check-skill-line-ratchet self-test: 20 cases pass.' (was 19) and '✓ check-skill-line-ratchet: CLAUDE.md is 86 lines (ceiling 86; headroom 0).'; 21 ceiling rows green (was 20)",
"pnpm check:cross-package-test-inputs — 'OK: 14 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob.'",
"pnpm check:entry-guard — '✓ check:entry-guard: 137 scripts/ file(s) — every entry guard goes through invoked-as.mjs; 96 export bindings, 86 of them inert on import'",
"pnpm check:parse-guard — 'All 104 self-test cases passed.'",
"pnpm check:pnpm-filter-targets — '✓ check:pnpm-filter-targets: 121/149 `--filter` occurrence(s) across 25 file(s) resolve against 78 workspace package(s)'",
"node scripts/check-ci-filter-parity.mjs — 'OK: all 88 declared cross-package glob(s) (76 unique) are covered by `core` or `crosspkg`'",
"node scripts/check-cross-package-test-inputs.mjs — 'OK: 14 package(s) read outside themselves, all declared'",
"pnpm check:nul-bytes — 'check-nul-bytes: OK (scanned 6330 text file(s) -- 6330 tracked, 0 untracked-not-ignored; skipped 5 binary; no raw ASCII control bytes).'"
],
"tests": "Families derived by `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack` with NO path arguments, against the actual change set (it read '1 path(s) vs merge base 6a4e929f5', working tree 1) — 7 families, all run, all green, plus check:nul-bytes; see gates_run for each gate's own verdict line (exit codes captured before any pipe, via redirect-then-capture, never `| tail`). UNION RUN AT FINAL COMMIT 411b950 (`git rev-parse --short HEAD` from that run). ABLATION (throwaway, never committed, trap-guarded `trap ... EXIT INT TERM` so a foreground-cap SIGTERM mid-mutation could not leave the tree mutated): no build/dist leg applies — the covered input is a .md file the gate reads directly, so there is no rebuild to prove; the mutation was instead confirmed ON DISK rather than trusted to an editor exit code — marker `grep -c` 0 -> 1, gate-convention line count 86 -> 87, sha256 03749199..c57b91d2 -> a59e9ffe..cec82e5c. RED leg (process exit 1), gate's own verdict line: '✗ check-skill-line-ratchet: CLAUDE.md is 87 lines; the ratchet ceiling is 86.' RESTORE proved byte-identical: sha256 back to 03749199..c57b91d2, marker count back to 0, `git status --porcelain CLAUDE.md` empty. GREEN leg (exit 0): '✓ check-skill-line-ratchet: CLAUDE.md is 86 lines (ceiling 86; headroom 0).' Direction was as predicted (red on growth). WATCH-HINT DELTA measured, not asserted: a hypothetical CLAUDE.md card derived 1 family before (check:pm-governed-merges) and 2 after (+ check:pm-skill-ratchet, 'matched via CLAUDE.md ⇢ gate source CLAUDE.md/**') — exactly the one (gate, file) pair the docblock claims per root file. NOTE, not a flake: check-ci-filter-parity.mjs first exited 1 with ERR_MODULE_NOT_FOUND for `yaml` because I had not yet run `pnpm install` in the fresh worktree; after install (via `bash scripts/pm/os-verify-lock.sh -c`, VERDICT command-exit 0 · held the lock 8s · waited 196s) it is green as quoted. My own missed step, corrected — not a repo condition.",
"deviations": [
"check-governed-merges DISAGREES with the ruling comment's prose, and the PR is kept draft anyway per the card's recorded constraint. `node scripts/pm/check-governed-merges.mjs --test scripts/pm/check-skill-line-ratchet.mjs`, verdict verbatim: 'governed-surface predicate: 0 of 1 path(s) hit the register (5 surfaces, repo-agnostic).' / '✅ NOT governed — ordinary queue landing applies to a PR with exactly this file list.' The register governs docs/adr/**, .claude/**, skills/**, AGENTS.md and CLAUDE.md — NOT scripts/pm/**. The ruling comment said 'Governed surface (`scripts/pm/` ratchet + the covered file)': the COVERED FILE is governed, the RATCHET SCRIPT is not, and this PR touches only the script. PR left draft, auto_merge null, never flipped ready — for the PM to reconcile at ACCEPT.",
"Self-test surface changed beyond the brief, all inside the named file and mechanically forced: ROOT_FILE_WATCH_HINTS + its docblock, and the two self-test cases described in summary. No other file touched."
],
"open_questions": [],
"out_of_scope_findings": []
}
Checklist conclusions, verified against GitHub not the report: draft ✓ · base main ✓ · Fixes #9965 first line ✓ · changed files = exactly the claimed surface (scripts/pm/check-skill-line-ratchet.mjs, 1 file) ✓ · skip-changeset on a scripts-only diff ✓. Diff read line by line: the CEILINGS row quotes the ruling verbatim in place; the header boundary sentence is corrected in the same stroke (the contradiction this lane files findings about); the ROOT_FILE_WATCH_HINTS addition is mechanically forced by the pre-existing self-test case and matches the sibling gate's spelling; the exclusivity self-test case correctly moves to the two-file join form. Ablation evidence is two-directional with the mutation proven on disk and the restore proven byte-identical. Watch-hint delta measured at exactly one (gate, file) pair — the discoverability claim is backed, not asserted.
Contract-review-tier compensating control (per the claim): this review ran on the seat session machine-verified at tier — get_session → external_metadata.last_served_model: claude-fable-5 = CONTRACT_REVIEW_TIER, read this fire, not self-described.
Deviation reconciliation: check-governed-merges --test says NOT governed (register covers the COVERED file, not the ratchet script) — the dev is right about the register. The card's recorded ruling constraint (draft-only, human merge) is honored anyway: PR stays draft, review requested from os-zhuang (author is claude[bot], so the #11083 author-identity failure does not apply). If the maintainer prefers ordinary queue landing for it, that is their call at merge; the discrepancy is listed in the round report.
Activity
os-warren commentedon Aug 19, 2026
首触定级(skills 座,session
ae0d8bd1-5667-5f58-8ff8-fa22ec154325):转needs-user-decision—— 卡自陈「Decision needed, not an implementation」,且与 #9923 裁过的政策形状相同(棘轮覆盖范围=政策),照 #9962 落地的头部升级句走维护者。座位推荐:暂不加 ceiling。反方论证成立 —— 86 行、自我定位是 AGENTS.md 的节选、每条内联规则都经治理面人合;且 #9962 之后「CLAUDE.md 无 ceiling」已是头部明示的事实而非盲区,可见性问题已解决,剩下的纯粹是要不要多一道控制。若维护者想一次回答「根指令文件是否都该有 ceiling」(含 objectui 的一对,本棘轮够不着),那是跨仓政策卡,另立。
Generated by Claude Code
os-warren commentedon Aug 19, 2026
指派 os-zhuang = 决策通知机制(维护者 2026-08-19 裁定,推送已实测):待裁 = CLAUDE.md 是否加 line-ratchet ceiling(座位推荐:暂不加,理由见上方定级评论)。裁定后摘指派。
Generated by Claude Code
os-zhuang commentedon Aug 20, 2026
Maintainer ruling recorded (2026-08-20, live decision-inbox session with the triage seat, session
session_01PjAP6vbcsg2yMtvySPv1Qo)Ruled: add a
CEILINGSrow for rootCLAUDE.md. Provenance: maintainer accepted the batch, verbatim: 「其他接受你的建议。」Rationale on record:CLAUDE.mdis structurally growth-prone (every must-never-miss rule argues for appending), and a one-line ceiling now prevents compound growth cheaply. The sibling-repo question (objectui/cloud root instruction files unreachable from this ratchet) is noted for the skills seat to carve separately if wanted — not part of this landing.Governed surface (
scripts/pm/ratchet + the covered file) — draft-only PR, human merge, contract-review tier per the skills-face rule. State transition in the same stroke:needs-user-decision→pm:queue.Generated by Claude Code
os-zhuang commentedon Aug 23, 2026
Claim: PM loop round 1
Session:
session_01RMTpSRF5CjMmQBFfPtPCwJBranch:
claude/issue-9965-claude-md-ceilingWorktree:
objectstack-issue-9965Domain:
domain:skillsFile surface:
scripts/pm/check-skill-line-ratchet.mjs(stop on breach; explain in the report)Container & model: S mechanical,
mode:subagent,model: opus— per this dispatch'snode scripts/pm/dispatch-gates.mjs --tierrun: "no path-derived mandate"; governance-face mechanical change dispatched at opus construction per the 2026-08-20 tiering ruling, with contract-review-tier review as the compensating control at ACCEPT (this seat session is machine-verified at that tier)Clause-②: no
Serial constraints cleared: none in flight — scanned all 9 open PRs, none touches
scripts/pm/**; last churn on this file is merged PR #11126.Generated by Claude Code
claude commentedon Aug 23, 2026
{ "issue": 9965, "status": "done", "branch": "claude/issue-9965-claude-md-ceiling", "pr": "https://github.com/objectstack-ai/objectstack/pull/11274", "premise_still_valid": true, "summary": "Added ['CLAUDE.md', 86] to CEILINGS in scripts/pm/check-skill-line-ratchet.mjs per the 2026-08-20 ruling, quoted in place. All three PM mechanism assumptions held on measurement: CLAUDE.md is 86 lines on origin/main under the gate's OWN countLines convention (wc -l agrees), the header sentence naming CLAUDE.md as uncovered exists and was corrected in the same commit, and the file surface held — CLAUDE.md itself is untouched. One coupling the brief did not name was forced by the gate's existing self-test: the case 'every separator-less ceiling declares a root-file watch hint' goes red for any separator-less CEILINGS key lacking a hint, so ROOT_FILE_WATCH_HINTS gained 'CLAUDE.md/**' — the same spelling scripts/pm/check-governed-merges.mjs already carries for these two root files. Its docblock prose and the AGENTS.md-exclusivity self-test case were corrected with it. Everything stayed inside the one named file.", "files_changed": [ "scripts/pm/check-skill-line-ratchet.mjs" ], "gates_run": [ "pnpm check:pm-skill-ratchet — '✓ check-skill-line-ratchet self-test: 20 cases pass.' (was 19) and '✓ check-skill-line-ratchet: CLAUDE.md is 86 lines (ceiling 86; headroom 0).'; 21 ceiling rows green (was 20)", "pnpm check:cross-package-test-inputs — 'OK: 14 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob.'", "pnpm check:entry-guard — '✓ check:entry-guard: 137 scripts/ file(s) — every entry guard goes through invoked-as.mjs; 96 export bindings, 86 of them inert on import'", "pnpm check:parse-guard — 'All 104 self-test cases passed.'", "pnpm check:pnpm-filter-targets — '✓ check:pnpm-filter-targets: 121/149 `--filter` occurrence(s) across 25 file(s) resolve against 78 workspace package(s)'", "node scripts/check-ci-filter-parity.mjs — 'OK: all 88 declared cross-package glob(s) (76 unique) are covered by `core` or `crosspkg`'", "node scripts/check-cross-package-test-inputs.mjs — 'OK: 14 package(s) read outside themselves, all declared'", "pnpm check:nul-bytes — 'check-nul-bytes: OK (scanned 6330 text file(s) -- 6330 tracked, 0 untracked-not-ignored; skipped 5 binary; no raw ASCII control bytes).'" ], "tests": "Families derived by `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack` with NO path arguments, against the actual change set (it read '1 path(s) vs merge base 6a4e929f5', working tree 1) — 7 families, all run, all green, plus check:nul-bytes; see gates_run for each gate's own verdict line (exit codes captured before any pipe, via redirect-then-capture, never `| tail`). UNION RUN AT FINAL COMMIT 411b950 (`git rev-parse --short HEAD` from that run). ABLATION (throwaway, never committed, trap-guarded `trap ... EXIT INT TERM` so a foreground-cap SIGTERM mid-mutation could not leave the tree mutated): no build/dist leg applies — the covered input is a .md file the gate reads directly, so there is no rebuild to prove; the mutation was instead confirmed ON DISK rather than trusted to an editor exit code — marker `grep -c` 0 -> 1, gate-convention line count 86 -> 87, sha256 03749199..c57b91d2 -> a59e9ffe..cec82e5c. RED leg (process exit 1), gate's own verdict line: '✗ check-skill-line-ratchet: CLAUDE.md is 87 lines; the ratchet ceiling is 86.' RESTORE proved byte-identical: sha256 back to 03749199..c57b91d2, marker count back to 0, `git status --porcelain CLAUDE.md` empty. GREEN leg (exit 0): '✓ check-skill-line-ratchet: CLAUDE.md is 86 lines (ceiling 86; headroom 0).' Direction was as predicted (red on growth). WATCH-HINT DELTA measured, not asserted: a hypothetical CLAUDE.md card derived 1 family before (check:pm-governed-merges) and 2 after (+ check:pm-skill-ratchet, 'matched via CLAUDE.md ⇢ gate source CLAUDE.md/**') — exactly the one (gate, file) pair the docblock claims per root file. NOTE, not a flake: check-ci-filter-parity.mjs first exited 1 with ERR_MODULE_NOT_FOUND for `yaml` because I had not yet run `pnpm install` in the fresh worktree; after install (via `bash scripts/pm/os-verify-lock.sh -c`, VERDICT command-exit 0 · held the lock 8s · waited 196s) it is green as quoted. My own missed step, corrected — not a repo condition.", "deviations": [ "check-governed-merges DISAGREES with the ruling comment's prose, and the PR is kept draft anyway per the card's recorded constraint. `node scripts/pm/check-governed-merges.mjs --test scripts/pm/check-skill-line-ratchet.mjs`, verdict verbatim: 'governed-surface predicate: 0 of 1 path(s) hit the register (5 surfaces, repo-agnostic).' / '✅ NOT governed — ordinary queue landing applies to a PR with exactly this file list.' The register governs docs/adr/**, .claude/**, skills/**, AGENTS.md and CLAUDE.md — NOT scripts/pm/**. The ruling comment said 'Governed surface (`scripts/pm/` ratchet + the covered file)': the COVERED FILE is governed, the RATCHET SCRIPT is not, and this PR touches only the script. PR left draft, auto_merge null, never flipped ready — for the PM to reconcile at ACCEPT.", "Self-test surface changed beyond the brief, all inside the named file and mechanically forced: ROOT_FILE_WATCH_HINTS + its docblock, and the two self-test cases described in summary. No other file touched." ], "open_questions": [], "out_of_scope_findings": [] }Generated by Claude Code
os-zhuang commentedon Aug 23, 2026
ACCEPT (skills seat, session
session_01RMTpSRF5CjMmQBFfPtPCwJ, reviewer of record) — PR #11274.Checklist conclusions, verified against GitHub not the report: draft ✓ · base
main✓ ·Fixes #9965first line ✓ · changed files = exactly the claimed surface (scripts/pm/check-skill-line-ratchet.mjs, 1 file) ✓ ·skip-changeseton a scripts-only diff ✓. Diff read line by line: the CEILINGS row quotes the ruling verbatim in place; the header boundary sentence is corrected in the same stroke (the contradiction this lane files findings about); theROOT_FILE_WATCH_HINTSaddition is mechanically forced by the pre-existing self-test case and matches the sibling gate's spelling; the exclusivity self-test case correctly moves to the two-file join form. Ablation evidence is two-directional with the mutation proven on disk and the restore proven byte-identical. Watch-hint delta measured at exactly one (gate, file) pair — the discoverability claim is backed, not asserted.Contract-review-tier compensating control (per the claim): this review ran on the seat session machine-verified at tier —
get_session→external_metadata.last_served_model: claude-fable-5=CONTRACT_REVIEW_TIER, read this fire, not self-described.Deviation reconciliation:
check-governed-merges --testsays NOT governed (register covers the COVERED file, not the ratchet script) — the dev is right about the register. The card's recorded ruling constraint (draft-only, human merge) is honored anyway: PR stays draft, review requested fromos-zhuang(author isclaude[bot], so the #11083 author-identity failure does not apply). If the maintainer prefers ordinary queue landing for it, that is their call at merge; the discrepancy is listed in the round report.Generated by Claude Code