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
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