docs(agents): cite by content, not by line address (#8875 clause 1) - #9301
Conversation
Adds Coding Standard #10 to AGENTS.md: when a comment, a failure message, a doc, a changeset or a script header cites an assertion, a call or a line of code, cite it by CONTENT — a quoted distinctive string, a symbol name, a test name, an anchor — never by a `path:line` address. objectui#7853 ruled the class (landed `fa7d66c45`); objectui#8047 mechanized it as the `no-line-address-in-test-name` ESLint rule over TEST NAMES ONLY, deliberately exempting comments and failure messages because "a human reads them beside the code they annotate". #10 records the point this card adds: that justification is POSITIONAL. It holds for a same-file citation and does not survive the citation crossing a file boundary — nothing puts the cited line in front of the reader of the citing file, and nothing tells them it moved. The clause also records the stored-vs-computed dividing line for line-number ledger keys, and that repair is opportunistic, never a sweep. This is the human-facing convention that the differential gate landed by objectui#8974 enforces. No released package source is touched, so the changeset carries empty frontmatter (declares "no release") rather than a patch bump of the 39-package fixed group. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FhBNJcLRZLe8M87VcUgpKr
…e-by-content-convention
ACCEPT — reviewed against the DIFF. ⛔ Terminal here: governed surface, draft, awaiting a human merge.
Verified, ⛔ not taken from the report
⭐ Three things this round got right that are worth naming
⭐ The measurement was re-derived on today's tip rather than inherited from the dispatch's 2026-09-10 reading, with the firing control in the same run: ⛔ Terminal shape — and this is the end of this seat's path
objectui#8875 stays open on this PR, as it has since PR 1:
Generated by Claude Code |
…e-by-content-convention Resolve the AGENTS.md numbering collision: bde0780 (#9257) landed "Never depend on the IDENTITY of a useMemo / useCallback result" as commandment #10 on main after this branch's base, and this branch had already claimed #10 for "Cite by CONTENT, not by line address". Both rules survive. Main's #10 keeps its number byte-identical; this branch's commandment is renumbered #10 -> #11. That single digit is the only byte changed in the approved text — the heading tail and all six sub-bullets are byte-identical to head ad4a3c6. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013VGeMu3p6qEFWR6K6GGLaW
… not #10 The merge that reconciled this branch with main renumbered this branch's commandment from #10 to #11, because bde0780 (#9257) had already taken #10 for "Never depend on the IDENTITY of a useMemo / useCallback result". That renumber left this changeset naming #10 in two places, where #10 now resolves to main's rule instead of this one. Body text only; the frontmatter stays empty and still declares no release. Two bytes change, both a '0' becoming a '1'. Neither occurrence referred to main's rule -- the file mentions no useMemo/useCallback/identity text at all. A stale by-number citation inside the pull request that bans stale by-number citations is the rule failing on its own diff, which is why this is repaired here rather than left to the changeset's deletion at version time. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013VGeMu3p6qEFWR6K6GGLaW
…the contract-review carrier) (objectstack-ai#9466) Fixes objectstack-ai#9213 ## ⛔ This PR is GOVERNED and opens as a DRAFT — the draft state is the FINISHED state `AGENTS.md` is governed surface. Verbatim verdict from this branch: ``` $ node scripts/check-governed-queue-guard.mjs --test AGENTS.md # exit 3 ⛔ GOVERNED — 1 of 1 path(s) are on a governed surface: AGENTS.md x1 — the repo-root agent instruction file - AGENTS.md One governed path governs the WHOLE pull request — proportion is not a question. ⛔ Do not flip it ready, enqueue it, or arm auto-merge. Park it as a DRAFT and leave the merge to the maintainer; a human merge IS the review record for a governed surface. The merge-queue run of "Governed Surface Queue Guard" refuses this diff unless an APPROVED review by an authorized approver (GOVERNED_APPROVERS: os-zhuang, hotlong) is on the pull request — on whichever commit it was left (maintainer ruling 2026-09-04). ```⚠️ **The draft state does not mean the work is unfinished.** The change is complete and the gates are green. A governed PR needs a human `APPROVED` review from `GOVERNED_APPROVERS` (`os-zhuang` / `hotlong`) and a **human merge**. No agent seat will flip this ready, enqueue it, arm auto-merge, or approve it. ## What is wrong `AGENTS.md` § 受管面 states the queue guard's `merge_group` refusal predicate as **one** thing: an authorized latest-decisive `APPROVED` review. Since objectstack-ai#9018 / PR objectstack-ai#9212 that leg carries **two independent predicates**: | leg | predicate | exits | |:--|:--|:--| | governed surface | an authorized latest-decisive `APPROVED` review record | 3 · 4 · 5 | | contract-review carrier | no queued pull request carries `needs:contract-review` | 6 · 7 | ⇒ **a merge group whose diff touches nothing governed at all can now be refused.** The paragraph says the opposite *by omission*, and the error points in the **permissive** direction. The paragraph's existing caveat (「覆盖面以脚本自己的 `GOVERNED_SURFACES` 为准」) does **not** already cover this. It delegates the **surface list**. The carrier leg reads **no surface at all** — it reads a **label on the pull request**. ## Measurements, re-derived on this branch's base (`f5b9c17973`), not inherited from the card **1. The exits exist and mean what the card said.** ``` $ git grep -n 'EXIT_REFUSED_CARRIER' origin/main -- scripts/check-governed-queue-guard.mjs origin/main:scripts/check-governed-queue-guard.mjs:258:export const EXIT_REFUSED_CARRIER = 6; origin/main:scripts/check-governed-queue-guard.mjs:259:export const EXIT_REFUSED_CARRIER_UNREADABLE = 7; ``` 6 = a queued pull request carries the label (`refusalKind: 'gated'`). 7 = a label set could not be read, or the group names no pull request at all (`refusalKind: 'unreadable' | 'no-pull'`). **2. The carrier leg reads a LABEL, not a surface.** Code path, all in `scripts/check-governed-queue-guard.mjs`: `runCarrierGuard({ event, rows, namedPull, fetchLabels })` → `carrierPullsInGroup(rows, namedPull)` enumerates the queued PRs **per commit** → for each, `await fetchLabels(pr)` → the whole predicate is `names.includes(CONTRACT_REVIEW_LABEL)`, where `export const CONTRACT_REVIEW_LABEL = 'needs:contract-review'`. **No path, no diff, no surface set is consulted anywhere on this leg.** The file says so itself: > the carrier is REMOTE state hung by a seat, not a property of the diff. So every merge group pays > one label read per queued pull request. ⚖️ THE ACCEPTED COST … a GitHub outage now refuses a merge > group whose diff touches nothing governed, which the governed leg alone never did. **3. `AGENTS.md` carried no mention of it.**⚠️ Read as **TEXT with whitespace collapsed**, not with a line-oriented grep — the target is one enormous wrapping sentence on a single physical line (1434 chars), and a line grep cannot be trusted to answer a question about a sentence. ``` needs:contract-review 0 | GOVERNED_APPROVERS 3 (control, HIT) contract-review 0 | merge_group 2 (control, HIT) carrier 0 | latest-decisive 2 (control, HIT) 承载 / 合同评审 / EXIT_REFUSED_CARRIER 0 ``` Every zero is carried by three controls that HIT in the same run, in the same file, with the same reader. **4. `content/docs/guide/ci-cd-pipeline.md` (NOT governed) already carries the corrected account** — PR objectstack-ai#9212 updated it. This is the strongest available basis for a minimal correction: the wording below is **propagated from settled prose already in the tree**, not invented here. > **The second leg: the contract-review carrier.** On a **merge-queue build only**, this check > carries a second and completely independent predicate, keyed on a *label* rather than on paths > (objectstack-ai#9018, a re-implementation of the leg objectstack landed as its own `objectstack#17484`). It > enumerates **every** pull request the merge group is landing — per commit, exactly as the governed > leg does, because `merge_group.head_ref` names only the *last* pull request in the group and keying > on it would let an earlier pull request's open carrier ride into `main` behind a clean one — reads > each one's labels from the pull object … and refuses while `needs:contract-review` is on any of > them (**exit 6**) or when a label set cannot be read or the group names no pull request at all > (**exit 7**, split from 6 for the same reason 4 is split from 3). The `pull_request` leg is > untouched and reads no label. Its row in the same file's check table already says it too: > a governed-surface diff with no authorized approval record (on any commit) is refused there, and so > is any merge group whose queued pull requests still carry `needs:contract-review` ## ⭐ The wording is the maintainer's to amend — that is why the diff is this small objectstack-ai#9213 declines to propose wording, verbatim: 「**Not proposing the wording.** What the paragraph should say is a maintainer's call, not a seat's, and the file is governed precisely so that it is.」 This PR therefore makes the **minimal factual correction** and nothing else. **`diff --numstat` → `1 1 AGENTS.md`.** One line changed, because the paragraph is one physical line. Text was **added**; nothing was removed. **What I deliberately did NOT touch:** - ⛔ did not restructure the paragraph into a two-predicate list, though that reads better - ⛔ did not re-translate, re-punctuate, shorten or otherwise "improve" any existing prose - ⛔ did not touch the 「覆盖面以脚本自己的 `GOVERNED_SURFACES` 为准」 caveat, even though it is the sentence a reader is most likely to over-read (see the alternatives below) - ⛔ did not touch any other paragraph, section or file — not the guard script, not the workflow, not `content/docs/guide/ci-cd-pipeline.md` - ⛔ did not rule on anything the card declined to rule on - ⛔ did not touch objectstack-ai#9301's region (lines 107..112); this change is in the 受管面 paragraph near line 522. Verified non-overlapping. Register: the insertion is Chinese prose with inline code spans, half-width punctuation (`,` `;` `:` `(` `)` — measured on the target line: 27 `,`, 13 `()`, 9 `:`, 5 `;`; the only two full-width `,` in that line are inside the verbatim untranslated maintainer quote), and the paragraph's existing⚠️ / ⇒ / `**bold**` markers. No English sentence was introduced. ## ⭐ Alternative phrasings I considered — offered here, NOT put in the file The maintainer should feel free to take any of these instead; amending one line is cheap by construction. 1. **(taken)** One appended sentence-block after the 已接受的代价 caveat and before 「⚠️ 但它**尚未**是 required context」, opening 「⚠️ 而这条拒绝腿上的判据**不止上面那一条**」. Chosen because it lands while 「`merge_group` 腿才是会拒绝的那条 —— 它要求 …」 is still in the reader's eye, and because the following 「但它尚未是 required context ⇒ 那条拒绝腿只报告、不阻止队列」 is true of **both** predicates, so it now correctly scopes over both. 2. **Restructure into 「它有两条相互独立的拒绝判据:① … ② …」.** Clearer, and what I would write from scratch. Rejected: the card forbids restructuring, and it would rewrite bytes the maintainer already ruled on (the 2026-09-04 sha-pin retirement wording). 3. **Insert immediately after 「它要求 [predicate 1] …(维护者 2026-09-04 裁 …)。」, before the 已接受的代价 caveat.** Tightest possible placement. Rejected: 「已接受的代价:批准之后的 push 不再 被这道门重审」 would then have my sentence between it and its antecedent, and 「这道门」 could be misread as the carrier leg — for which that cost is not the accepted cost. 4. **Append at the very end of the paragraph, after the `GOVERNED_SURFACES` caveat**, so it could say 「上面那句托付的是受管面清单,⛔ 不是判据清单」. Rejected: it leaves five sentences of the paragraph reading false before the correction arrives. I got the same protection without the cross-reference by stating plainly that the leg 「不读任何路径」 — a reader who then meets a delegation about 覆盖面 cannot mistake it for covering a leg that reads no surface. 5. **Also correct the `--test` renderer's own advisory text**, which describes the merge-queue refusal with the approval predicate alone (quoted at the top of this PR). Rejected: out of scope for objectstack-ai#9213, and it is a different file. Reported to the PM rather than fixed here. ## Gates — PRE / POST, exit codes captured by redirect-then-capture, never across a pipe PRE = base bytes restored from `HEAD` by explicit ref; POST = this commit. The restore leg was proven by `git hash-object` comparison (base `30f47ec4b5…`, post `eb017a9f30…`), not by an exit code. | gate | PRE | POST | |:--|:--|:--| | `check:control-bytes` | 0 | 0 | | `check:new-line-citations` | 0 | 0 | | `docs:check-links` | 0 | 0 | | `check:shell-escape-residue` | 0 | 0 | | `check:governed-queue-guard` (self-test) | 0 | 0 | | `check:required-check-set` (self-test) | 0 | 0 | | `check-changeset-presence.mjs` | — | 0 | Both sides agree, so each instrument is shown to be non-empty and to have actually read the file: - `check-control-bytes: OK (scanned 7644 tracked text file(s); skipped 85 binary)` - `check-shell-escape-residue: OK (5/5 root(s) resolved -- AGENTS.md: 1 file(s), 15 fence(s); …)` - `Links are valid across 17 scan roots.` - `VERDICT new-cross-file-line-citations: 0 new citation(s), enforcement report-only -> exit 0` - `check-governed-queue-guard self-test: 185 cases pass (… plus the SECOND queue predicate objectui#9018 added on the merge_group leg — the contract-review carrier …)` — the guard's own self-test corroborates the premise on this base. Changeset: none owed. `check-changeset-presence.mjs` → `✅ No source or published contract of a released package changed in this range, so no changeset is owed.`⚠️ The `skip-changeset` label was deliberately **not** applied: in this repository that label object exists but no workflow or script reads it, and a pin test holds that in place. I also self-scanned the file for control bytes outside the gate (`grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]'` → no match, with a positive control that HIT in the same run on a fixture containing `0x0c`). ## 维护者速读(草稿) **改了什么** —— `AGENTS.md` 受管面那段里,关于 `merge_group` 拒绝腿的描述补了一句:那条腿现在有**两条** 相互独立的判据,原文只写了一条(获授权的 APPROVED review),漏掉了 objectstack-ai#9018 加的 contract-review carrier (读 PR 上的 `needs:contract-review` label,exit 6 / 7)。一行 diff,只加不删。 **为什么改** —— 漏写的方向是**放宽**:照原文读,一个 diff 完全没碰受管面的 merge group 不可能被这道 check 拒绝;实际上可以。每一次 dispatch 都读这段。 **风险与代价(含回滚)** —— 风险极低:纯文字,不动脚本、不动 workflow、不动任何判定逻辑,六道门禁 PRE/POST 全绿。回滚 = revert 这一个 commit,恢复一行。措辞是从 `content/docs/guide/ci-cd-pipeline.md` 里已经被接受的说法搬过来的,不是新造的。 **席位意见** —— **你要做的** —— ① 读上面「Alternative phrasings」五条,措辞归你定,改一行很便宜;② 这是受管面 PR, 需要你一条 `APPROVED` 并由你合并 —— 席位不会 ready、不会入队、不会自合、不会批准。 --- 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01FhBNJcLRZLe8M87VcUgpKr --- _Generated by [Claude Code](https://claude.ai/code/session_01FhBNJcLRZLe8M87VcUgpKr)_ Co-authored-by: Claude <noreply@anthropic.com>
Part of #8875 — PR 2 of two, clause 1 only. PR 1 landed as #8974 (
efead6c60) and carried clauses 2, 3 and 5; this one carries the human-facing convention that gate enforces. ⛔ This PR does not close #8875 on its own.⛔ Governed surface — draft only
The diff touches
AGENTS.md, so the whole PR is governed: ⛔ not flipped ready, ⛔ not enqueued, ⛔ no auto-merge, ⛔ no self-approval. It waits for a human merge, and that is the correct terminal state, not a failure. Confirmed mechanically rather than asserted:The premise was re-derived on today's tip, with a firing control
The dispatch that queued this measured zero cite-by-content sentences in
AGENTS.mdon2c208d5bb(2026-09-10). That reading is two days old, so it was re-taken on243fc8317e(2026-09-12T14:56:42Z) with the control in the same run:AGENTS.mdcite(case-insensitive)按内容行号line addressline-addresspath:line8047/7853\.(ts|tsx|mjs|md|js):[0-9]+objectui#The control fires (26, up from the dispatch's 22 — the file grew in between), so the zero is a reading and not a broken grep. The only three hits for
引用in the file aresession 引用andissue 引用in the GitHub-body-rewriting section — a different sense of the word, not a cite-by-content sentence. ⇒ the clause's 「若已有 cite-by-content 句则引之」 branch does not apply; this is new text.What was added
One new commandment in
## 5. Coding Standards,#10 — Cite by CONTENT, not by line address; a CROSS-FILEpath:lineis banned outright, placed after#9(the closest neighbour in kind — "point at the instrument, never write down its answer"). Five sub-points:path:line. A line address is read by nothing, so it cannot fail; it rots the first time a line is inserted above what it cites. finding(examples): component-fixture-declared-keys.test.ts cites safe-validate-corpus-6318.test.ts:98 by LINE — a comment-only edit above that line moves it, and nothing goes red #7853 ruled the class (fa7d66c45); finding(core): one it() test NAME still cites tsconfig.typetests.json — the last stale citation, left as out-of-scope by the comment-only sweep #6548, finding(scripts):js-comment-mask.mjscites two line numbers as evidence and both point at the wrong line (86 and 107 lines off) #6998, finding(gate): the baseline-prose pin generalises to scripts/vite-declared-lazy-views.ts, but only its VALUE half — that file cites five commits with nothing live to check them against #7289, finding(plugin-markdown): toc-anchor-parity cites packages/react/README.md by line address (:224) and the heading has moved to :253 — a citation nothing checks #7913, finding(types): text-value-retired-6951.test.ts names two cases by line address (layout.ts:66) — accurate today, checked by nothing #8045 were the one-at-a-time repairs, and the class kept recurring.file:linecitation inside a test NAME is read by nothing — five recurrences under a standing ruling, and per-instance repair has not closed the class #8047 mechanized the ruling for test names only, deliberately exempting comments and failure messages on the ground quoted from that rule's own header — "A human reads them beside the code they annotate, and the next reader of that code corrects a wrong one." That justification is positional: it holds for a SAME-FILE citation and does not survive the citation crossing a file boundary. The reader ofpackages/types/src/crud.tsis not looking atpackages/core/src/actions/ActionRunner.ts; nothing puts the cited line in front of them and nothing tells them it moved. The blast-radius measurement (73 hits / 4 syntaxes, 14 moved by one branch's line shifts, 10 of the 14 already false before the branch existed) is recorded explicitly as a historical reading, ⛔ not as a live count — §5#9applied to the new text.pnpm check:new-line-citationsand itsENFORCEMENTconstant rather than restating today's mode, and says why an absolute count was refused. Existing citations are ⛔ not the denominator.CHANGELOG;dist/*.d.tsisunresolvable, not a finding. Plus the stored vs computed dividing line for line-number ledger keys.Fences observed
AGENTS.mdonly (+ its changeset).scripts/**untouched — PR feat(ci): differential gate for newly added cross-file line citations, and retire two stored line-number ledgers (objectui#8875) #8974 already landed that half.file:linecitation inside a test NAME is read by nothing — five recurrences under a standing ruling, and per-instance repair has not closed the class #8047 ESLint rule was not widened to comments or message strings (option A, not ruled).CHANGELOGparagraph re-addressed.content/docs/releases/**,.claude/**,docs/adr/**,skills/**,CLAUDE.mduntouched.path:lineforms, and adds zero code fences.Changeset
.changeset/issue-8875-cite-by-content-convention.md, empty frontmatter (---immediately followed by---) — AGENTS.md §9's first-class way to declare "no release". Apatchwould version-bump the 39-package fixed group for a change no consumer can observe.git statusshowedA(new file), notM: the pre-existing.changeset/8875-cross-file-line-citation-census.mdwas not clobbered.Gates — exit codes captured before any pipe (
cmd > log 2>&1; EXIT=$?)Run on the merged tree (
origin/mainmerged in immediately before opening,AGENTS.mdbeing hot; merge was clean and brought noAGENTS.mdchange).check-changeset-presence✅ No source or published contract of a released package changed in this range, so no changeset is owed.check-changeset-no-major✅ No changeset declares a major bump.check-control-bytes✅ OK (scanned 7494 tracked text file(s); skipped 85 binary)check-doc-linksLinks are valid across 17 scan roots.check-shell-escape-residue✅ OK (5/5 root(s) resolved — AGENTS.md: 1 file(s), 15 fence(s) …)check-doc-fence-languages✅ every TypeScript block in 227 document(s) is fenced ts/tsx/typescript …check-doc-expression-carriage✅ Controls pass … ✅ Blind spot: nonecheck-new-cross-file-line-citationsVERDICT new-cross-file-line-citations: 0 new citation(s), enforcement report-only -> exit 0— all four synthetic controls PASScheck-governed-queue-guard --test⛔ GOVERNED— the classification this PR wants, not a failurecheck:doc-examples/check:doc-snippetsexit 2 = PREREQUISITE NOT MET locally — they need all 39 packages built (… declares types at packages/*/dist/index.d.ts and it is not on disk). That is NOT MEASURED, neither green nor red. They type-check TypeScript fences in documents, and this diff adds zero fences (git diff … | grep -c '^+```'→0), so there is nothing new for them to judge. CI runs them on the full build.Also 0 control bytes by an independent scan of the changed files:
grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]'→ exit 1 (no match).维护者速读(草稿)
改了什么 — 给
AGENTS.md的 §5 加一条#10:引用代码时按内容指(引一段有辨识度的字符串、符号名、测试名、锚点),⛔ 不写路径:行号;跨文件的行号引用一律禁止,同文件的保留 #8047 的既有豁免。另加一个空 frontmatter 的 changeset(声明不发版)。⛔ 只动这一个文件,scripts/**一行没碰。为什么改 — 这是维护者 2026-09-10 裁决 C 的第 1 条,#8974 已经把第 2/3/5 条(那道差分门)落地了,门有了但人读的约定还不在规程里。本卡真正新增的那一点是:#8047 之所以豁免注释和消息串,理由是「人读它的时候它就在被注释的代码旁边」——这个理由是位置性的,跨了文件就不成立:读 A 文件的人不会看着 B 文件,没有任何东西告诉他被引的那行已经挪了。一次 PR 范围内实测:73 条跨文件引用,14 条被那一个分支的行位移挪偏,其中 10 条在分支存在之前就已经是错的。
风险与代价(含回滚) — 风险极低:纯规程文本,不影响任何运行时、不发版、不改门禁行为。代价是⚠️ 一个真实但已知的副作用:这条约定把差分门(目前 report-only)的规范依据写死了,以后有人想把门翻成 blocking 时,不需要再重新裁一次。
AGENTS.md又长了 7 行。回滚就是 revert 这一个 commit,没有任何下游依赖它。席位意见 — (留空,等席位定稿成评论)
你要做的 — 读一遍 §5 新增的
#10,确认措辞符合你 2026-09-10 那句裁决的本意(那句原话已逐字照录在条文里,未翻译),然后由你来合并——这是受管面,agent 不翻 ready、不入队、不自批。🤖 Generated with Claude Code
https://claude.ai/code/session_01FhBNJcLRZLe8M87VcUgpKr
Generated by Claude Code