Skip to content

docs(agents): cite by content, not by line address (#8875 clause 1) - #9301

Merged
os-zhuang merged 4 commits into
mainfrom
claude/issue-8875-cite-by-content-convention
Sep 14, 2026
Merged

docs(agents): cite by content, not by line address (#8875 clause 1)#9301
os-zhuang merged 4 commits into
mainfrom
claude/issue-8875-cite-by-content-convention

Conversation

@baozhoutao

Copy link
Copy Markdown
Contributor

Part of #8875PR 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:

node scripts/check-governed-queue-guard.mjs --test AGENTS.md .changeset/issue-8875-cite-by-content-convention.md
⛔ GOVERNED — 1 of 2 path(s) are on a governed surface:
   AGENTS.md x1 — the repo-root agent instruction file

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.md on 2c208d5bb (2026-09-10). That reading is two days old, so it was re-taken on 243fc8317e (2026-09-12T14:56:42Z) with the control in the same run:

pattern in AGENTS.md hits
cite (case-insensitive) 0
按内容 0
行号 0
line address 0
line-address 0
path:line 0
8047 / 7853 0
\.(ts|tsx|mjs|md|js):[0-9]+ 0
firing controlobjectui# 26

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 are session 引用 and issue 引用 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-FILE path:line is banned outright, placed after #9 (the closest neighbour in kind — "point at the instrument, never write down its answer"). Five sub-points:

  1. The rule and its history. Cite by a quoted distinctive string, a symbol name, a test name, an anchor — never a 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.mjs cites 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.
  2. ⭐ The point this card adds. gate(tests): a file:line citation 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 of packages/types/src/crud.ts is not looking at packages/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 #9 applied to the new text.
  3. The maintainer's ruling, 原文照录、不翻译.
  4. The gate is DIFFERENTIAL. Points at pnpm check:new-line-citations and its ENFORCEMENT constant rather than restating today's mode, and says why an absolute count was refused. Existing citations are ⛔ not the denominator.
  5. Existing citations — repair when you touch the file, ⛔ never as a sweep; ⛔ never re-address a published CHANGELOG; dist/*.d.ts is unresolvable, not a finding. Plus the stored vs computed dividing line for line-number ledger keys.

Fences observed

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". A patch would version-bump the 39-package fixed group for a change no consumer can observe. git status showed A (new file), not M: the pre-existing .changeset/8875-cross-file-line-citation-census.md was not clobbered.

Gates — exit codes captured before any pipe (cmd > log 2>&1; EXIT=$?)

Run on the merged tree (origin/main merged in immediately before opening, AGENTS.md being hot; merge was clean and brought no AGENTS.md change).

gate exit verdict line
check-changeset-presence 0 ✅ No source or published contract of a released package changed in this range, so no changeset is owed.
check-changeset-no-major 0 ✅ No changeset declares a major bump.
check-control-bytes 0 ✅ OK (scanned 7494 tracked text file(s); skipped 85 binary)
check-doc-links 0 Links are valid across 17 scan roots.
check-shell-escape-residue 0 ✅ OK (5/5 root(s) resolved — AGENTS.md: 1 file(s), 15 fence(s) …)
check-doc-fence-languages 0 ✅ every TypeScript block in 227 document(s) is fenced ts/tsx/typescript …
check-doc-expression-carriage 0 ✅ Controls pass … ✅ Blind spot: none
check-new-cross-file-line-citations 0 VERDICT new-cross-file-line-citations: 0 new citation(s), enforcement report-only -> exit 0 — all four synthetic controls PASS
check-governed-queue-guard --test 3 ⛔ GOVERNED — the classification this PR wants, not a failure

⚠️ One declared narrowing, not a silent skip: check:doc-examples / check:doc-snippets exit 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 条在分支存在之前就已经是错的

风险与代价(含回滚) — 风险极低:纯规程文本,不影响任何运行时、不发版、不改门禁行为。代价是 AGENTS.md 又长了 7 行。回滚就是 revert 这一个 commit,没有任何下游依赖它。⚠️ 一个真实但已知的副作用:这条约定把差分门(目前 report-only)的规范依据写死了,以后有人想把门翻成 blocking 时,不需要再重新裁一次。

席位意见(留空,等席位定稿成评论)

你要做的 — 读一遍 §5 新增的 #10,确认措辞符合你 2026-09-10 那句裁决的本意(那句原话已逐字照录在条文里,未翻译),然后由你来合并——这是受管面,agent 不翻 ready、不入队、不自批。

🤖 Generated with Claude Code

https://claude.ai/code/session_01FhBNJcLRZLe8M87VcUgpKr


Generated by Claude Code

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
@github-actions github-actions Bot added the documentation Improvements or additions to documentation label Sep 12, 2026
@claude

claude Bot commented Sep 12, 2026

Copy link
Copy Markdown
Contributor

ACCEPT — reviewed against the DIFF. ⛔ Terminal here: governed surface, draft, awaiting a human merge.

domain:devx @ objectui PM seat (session_01FhBNJcLRZLe8M87VcUgpKr), R55, 2026-09-12T15:4xZ. Head ad4a3c65c7, base 345e24a0c8. Two files, +21 / −0AGENTS.md +7, changeset +14.

Verified, ⛔ not taken from the report

  • Scope held. AGENTS.md + its changeset only. scripts/** untouched — PR objectui#8974 / efead6c60 already landed that half.
  • Self-consistency, the check this particular PR had to survive: a rule banning cross-file path:line must not contain one. Added lines scanned for <name>.<ext>:<digits>zero hits. The two file paths it does name (crud.ts, ActionRunner.ts) carry no line numbers, which is the rule demonstrating itself.
  • Changeset correct: git diff --name-status reports A, not M — the pre-existing .changeset/8875-cross-file-line-citation-census.md was not clobbered. Frontmatter is genuinely empty (--- immediately followed by ---), the AGENTS.md §9 first-class 「no release」 declaration. ⛔ Not a patch bump, which would have version-bumped the 39-package fixed group.
  • Every existence claim in the new text re-derived on the head, because a standard that names instruments is only as good as their existing:
claim in the text verified
pnpm check:new-line-citations ✅ in package.json
pnpm census:cross-file-line-citations ✅ in package.json
workflow Line Citation Gate .github/workflows/line-citation-gate.yml
the script's ENFORCEMENT constant export const ENFORCEMENT = 'report-only';
placed after #9 #9 at :109, #10 at :110

⭐ Three things this round got right that are worth naming

  1. It labels its own numbers as historical. 「73 cross-file hits … 10 of those 14 were already false」 is written as 「a historical reading, ⛔ not re-derived here and ⛔ not a live count」. This lane spent today undoing the opposite habit — a dev read a figure out of a brief and reported it as a fact about its own card, and a refutation nobody re-measured blocked a correct card for ~13h. A standard that dates its own measurements cannot do that to the next reader.
  2. It points at ENFORCEMENT instead of restating 「report-only」 — and says so: 「⛔ Never copy either instrument's answer into prose — that is 完善设计器的每一个细节 #9 applied to this rule.」 The new commandment obeys the one above it. That is the difference between a standard and a snapshot.
  3. The stored-vs-computed dividing line is a real addition, not restatement: a path:line a script computes at runtime to print a diagnostic cannot expire, while one stored and compared — a ledger key, a baseline entry — is the same defect with a gate wired to it, so it fails loudly instead of rotting quietly.

⭐ 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: objectui# = 26 hits today against the dispatch's 22 — the file grew, the control HITS, so the zero is a reading and not a broken grep. ⇒ the clause's 「若已有 cite-by-content 句则引之」 branch correctly did not apply.

⛔ Terminal shape — and this is the end of this seat's path

AGENTS.md is a governed surface. Read back from the API: draft: true, requested_reviewers: [hotlong, os-zhuang], auto_merge: null, merged: false. ⛔ This seat will not flip it ready, enqueue it, arm auto-merge, approve it, or merge it, at any point and under any framing. The remaining step is a human merge — 「落地需要人工合并,但判断不需要维护者」.

objectui#8875 stays open on this PR, as it has since PR 1: Refs, ⛔ not a closing keyword. ⚠️ It is now the head of this lane's AGENTS.md serial queue — objectui#7800 (PR objectui#8952) merged 2026-09-10T12:55Z and objectui#7833 closed 2026-09-11T06:53Z, so nothing is ahead of it. objectui#9213 is next behind and ⛔ must not be dispatched until this merges.

⚠️ Declared narrowing, recorded rather than buried: check:doc-examples and check:doc-snippets exited 2 = PREREQUISITE NOT MET locally (they need all 39 packages built). That reads NOT MEASURED, ⛔ not green. The diff adds zero code fences, so they have nothing new to judge, and CI runs them on a full build.


Generated by Claude Code

@os-zhuang
os-zhuang marked this pull request as ready for review September 13, 2026 06:12
@os-zhuang
os-zhuang enabled auto-merge September 13, 2026 06:12
claude Bot and others added 2 commits September 14, 2026 07:53
…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
@os-zhuang
os-zhuang added this pull request to the merge queue Sep 14, 2026
Merged via the queue into main with commit c0dab26 Sep 14, 2026
35 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-8875-cite-by-content-convention branch September 14, 2026 08:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation

Projects

None yet

3 participants