Uh oh!
There was an error while loading. Please reload this page.
docs(agents): guard history dating against a shallow clone - #14808
Conversation
`git log -S`, `git log --follow` and `git blame` answer from the horizon
commit in a shallow clone — exit 0, plausible sha, plausible date, no
warning — so a dev asked to date a line of history can report a wrong
answer with no signal. One standing clause now requires
`git rev-parse --is-shallow-repository` before a date or an ordering is
trusted, deepening when it is true, and declaring it in the report.
Scoped to history reads only. A content read at a ref (`git show
ref:path`, `git grep … ref --`) is depth-safe and is deliberately not
caught by the wording.
Paid same-file at ratchet headroom 0: the Definition-of-done line
"用户可见的改动加 changeset。" is dropped as a strict restatement of basic
rule 4, which states the same obligation more precisely
("用户可见的改动需要 `.changeset/*.md`") at a more binding site.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1hotlong
commented
Sep 3, 2026
Maintainer ruling, 2026-09-03, given in chat to the director seat (session_01WXyGTWPbbreqXow7Z2pZCk) on decision batch 2, item 1 (this PR, option A: merge as-is). Verbatim: 「同意」. Executing on that instruction from the maintainer's own account: marking ready, bringing the branch up to date with Generated by Claude Code |
Uh oh!
There was an error while loading. Please reload this page.
Fixes#14793
One standing clause added to
.claude/agents/os-dev.md, paid same-file at ratchet headroom 0 (469 lines in, 469 lines out). Head sha for every reading below: c6d554a.The line added — verbatim, at line 232
119 UTF-8 bytes, under the corpus cap of 120.
It is phrased for history reads only. A content read at a ref (
git show ref:path,git grep ... ref --) reads the tree at the tip, which a shallow clone does not truncate, so it is depth-safe and is deliberately not caught by the wording: the trigger is the three history verbs plus the two things you can get wrong from them, a date or an ordering.Placement: the bullet immediately after the git family rule in the standing clauses. That neighbour is the file's other clause about a git command whose answer depends on repository state the reader did not check (there, shared refs and the stash; here, depth).
The line paid — verbatim, was line 303, in Definition of done
Why its removal is justified on its own merits
It is a strict restatement. Basic rule 4, lines 71-72, states the same obligation on the same subject, more precisely, at a more binding site:
Same subject phrase,
用户可见的改动; the surviving spelling names the file glob (.changeset/*.md) where the deleted one said only "changeset". The surviving site is one of the six basic rules — the part of the file the contract itself treats as the rules that must never be missed.grep -c '用户可见'over the file was 2 before this PR and is 1 after: the fact is not lost, only its weaker copy. (The phrase用户可见的改动itself now greps to 0 because basic rule 4 wraps it across lines 71-72 — a line-scoped grep is exactly the wrong instrument for judging whether this fact survives, which is worth a reviewer's second look.)The Definition of done also keeps a changeset step of its own: the
skip-changesetbullet still makes the dev decide changeset-versus-label against a stated criterion (publishes nothing from any package), so a change that does not qualify for the label is routed straight back to basic rule 4.Chosen over the two other restatements considered because its omission fails loudly: a missing changeset is a red Check Changeset gate on the PR, caught by CI within the same round.
Candidates rejected, and why
资源纪律rule 6's⛔ 永不把验证挂在后台 watcher 上然后停轮(禁令与两种合法终态见「干净收尾」)。— also a self-admitted narrow copy of a rule whose home is干净收尾clause 4, and legal under the file's own "one rule, one home" convention. Rejected: it is a forward pointer across a long distance, so it carries real navigational value, and its omission fails silently (a stalled round nobody is woken for).拆掉你启动的一切 —— dev server,以及你挂起的每一个后台 monitor(见下节)。— restated as a principle by干净收尾clause 1, which sits directly below it. Rejected for the same asymmetry: a leaked monitor replays a whole report at the PM and nothing goes red.法,期 +,同,任,——,怎么,义),直). Refused outright: the maintainer's 2026-08-17 ruling makes deleting content the only legal currency for the line ratchet, and the file carries that ruling itself. (They remain a legitimate independent density repair under the 2026-08-29 ruling — worth up to about 7 lines of headroom on a file pinned at 469/469 — but that is a separate PR that buys no content, not a payment bundled with this one.)A costing correction the card should carry
The card and the triage both cost this as "one line", from a prose sentence of about 200 bytes. Under the corpus's 120-byte line cap, the ruled content does not fit on one line: naming the probe command alone costs 39 bytes, the three verbs 23, and a complete spelling with the failure evidence measures 185-190 bytes, i.e. two lines and therefore two payments.
Rather than take a second deletion, the wording was compressed to fit one line, and two things were dropped to do it:
git fetch --unshallow, or--deepen=Nuntil the answer stops moving) — the line says加深, which is the same verb the file already uses for this at line 158.Naming that trade rather than silently making the line longer, because it is the maintainer's call whether the evidence is worth a second payment.
Reverse verification of the guard's claim
Throwaway clone, made and deleted inside this run; never one of the shared checkouts. Cloned over
file://from the container's own mirror so--depthis honoured and no network is used.88 days of error, same exit code, no warning, and the wrong answer is a real commit with a real date. The full-history checkout this PR was written in independently reports
43625fbd6c 2026-05-30, matching the card's own measurement.The framing correction, measured again here
The card's reassurance that the shared checkout "is no longer shallow" was a property of one container. Measured in this one, before any edit:
Two mirrors, one container, opposite answers. Neither "the checkout is deep now" nor "every container is shallow" is a durable fact about a given tree, which is the argument for a guard that says check, per repo, at the moment you are about to date something rather than one that says deepen at startup.
The pm-dispatch site, named and not added
Measured rather than assumed, and the answer inverts the card's evidence. The card's "0 occurrences of
shallow" is a grep for the ASCII word; the corpus spells it in Chinese..claude/skills/pm-dispatch/**already carries the rule in two places:references/platform-readings.md, in 读数五坑:浅检出上的历史读数不可信namingmerge-base --is-ancestor,rev-list --countandbranch -r --contains, with the remedy先 --deepen 再判,或走 REST compare.SKILL.md's tool table row forscripts/pm/git-history.mjs:窗口化 commit 计数:回答或 REFUSE —— 浅 clone 对窗口化 git log/rev-list 以 exit 0 无警告答错, plushistoryHorizon()as a read-only predicate for self-answering tools.So the PM half is not a gap and no second ratchet needs paying. What was uncovered was the os-dev half and the specific verb set: neither site names
-S,--followorblame, which are the dating verbs, and os-dev.md's single prior mention of a shallow checkout (line 157) is aboutdispatch-gates.mjsrefusing loudly — the opposite signal from the silent one this clause guards.One consistency note for the reviewer, since it looks like a contradiction and is not.
scripts/pm/git-history.mjsrecords that--is-shallow-repositoryis the wrong predicate on its own: after a legitimate deepen a repo can still reporttruewhile answering the asked window exactly, so a guard that refused on it would refuse correct answers. This clause does not refuse on it — it deepens on it. For a windowed count you can prove coverage (the floor sits below the window) and refusing is avoidable; for a-Sdating read there is no window to prove anything against, sodeepen until the answer stops movingis the only sound rule. The same file's other finding is why the clause says加深and never--shallow-since: that flag deepens or shortens, exit 0, no warning.Gates, all at c6d554a — 11 commands, all green, nothing left unmeasured
Union re-derived after the final commit:
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands(stderr confirms the answer is about this repo at this commit) returns 9 commands. All 9 were run, plus the 2 the dispatch named that the derivation does not carry.pnpm check:pm-skill-ratchet✓ check-skill-line-ratchet: .claude/agents/os-dev.md is 469 lines (ceiling 469; headroom 0).and✓ ... widest table row is 0 bytes (pin 0; headroom 0).and✓ check-skill-line-ratchet self-test: 111 cases pass.— identical verdicts before and after the editpnpm check:pm-governed-prose✓ check-governed-prose: 2 instruction surface(s) name all 5 registered governed surfacespnpm check:pm-skill-id-lint✓ check-skill-id-lint: 23 file(s) clean (pattern /#[0-9]{3,}/g).pnpm check:skill-frame-sync✓ check-skill-frame-sync: 4 copies of the decision frame are structurally isomorphic across 3 filespnpm check:agent-model-declared✓ check-agent-model-declared: 1 agent definition(s) under .claude/agents/ all declare a modelpnpm check:nul-bytescheck-nul-bytes: OK (scanned 8064 text file(s) ... no raw ASCII control bytes).pnpm check:corpus-claim-driftcheck-corpus-claim-drift: OK, no new claim sites beside a pinned spelling.pnpm check:agent-test-spelling✓ check-agent-test-spelling: 0 violations — 430 file(s)pnpm check:doc-authoring✓ doc authoring guard: sibling-package prose ids hold the baseline — 831 pinned site(s) across 231 file(s)pnpm check:pm-governed-merges✓ check-governed-merges --self-test: 243 assertionspnpm --filter @objectstack/lint run check:doc-formula-expressions✓ check:doc-formula-expressions: 22 record-scoped formula example(s) across 426 files / 1365 TS blocks judged clean by @objectstack/formula.— the first two attempts exited 3,PREREQUISITE NOT MET, read as NOT MEASURED rather than red; it became measurable only after building@objectstack/spec,@objectstack/formulaand@objectstack/lintunder the verify lock. This gate reads.claudeas one of its four ROOTS, so it genuinely covers the edited file and was worth paying for.Every exit code was captured by redirecting to a file before any pipe, and each verdict above is the gate's own printed line, never a bare
$?.Edit-landed-on-disk proof, independent of any tool's exit code:
wc -l .claude/agents/os-dev.mdwc -c .claude/agents/os-dev.mdgrep -c 'is-shallow-repository'grep -cof the deleted line, anchoredgit diff --stat1 file changed, 1 insertion(+), 1 deletion(-)Control bytes:
grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]'over the edited file exits 1 (no match), beside the gate.eslint, narrowed — with the three pieces
Not "not run": measured, and the narrowing is proved not to have excluded anything.
files:entry ineslint.config.mjs(lines 785, 971, 1015, 1054, 1103, 1172, 1212) is an extension glob over{ts,tsx,mts,cts,js,jsx,mjs,cjs}. A case-insensitive grep of the config formarkdown,processorand the.mdextension returns 0 hits — there is no markdown processor, so a.mdfile is not in the linted population at all.--format json.npx eslint --no-inline-config --format json .claude/agents/os-dev.mdexits 0 and returns 1 result witherrorCount: 0and one message:File ignored because no matching configuration was supplied.The changed-file count inside the lint population is 0 of 1.grep -c projectService eslint.config.mjsis 0, and the config states it itself at line 328: noparserOptions.project, no typed@typescript-eslintrules. With no type-aware linting there is no cross-file verdict coupling, so a markdown file outside the population cannot move any untouched file's result.Landing
.claude/agents/os-dev.mdline 418 is held by PR #14779 (one line, in the decision-frame region). This hunk is at lines 232 and 301 and is disjoint from it; whichever lands later mergesmainin rather than rebasing, and both survive.No changeset: this publishes nothing from any package, so the
skip-changesetlabel is applied instead. Applied by union write (documentation,size/xs,skip-changeset) after reading the existing set, since this session has no additive REST endpoint; comparative read-back showed all three present, and a delayed re-read is owed because an immediate read-back cannot detect a later strip.Draft, and it stays draft — governed
.claude/**, human merge is the review record. Not flipped ready, not enqueued, no reviewers requested.🤖 Generated with Claude Code
https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1