From ea7343c59d3074aa49df46c798188a42e2522727 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 12 Sep 2026 15:38:04 +0000 Subject: [PATCH 1/2] docs(agents): cite by content, not by line address (#8875) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Claude-Session: https://claude.ai/code/session_01FhBNJcLRZLe8M87VcUgpKr --- .../issue-8875-cite-by-content-convention.md | 14 ++++++++++++++ AGENTS.md | 7 +++++++ 2 files changed, 21 insertions(+) create mode 100644 .changeset/issue-8875-cite-by-content-convention.md diff --git a/.changeset/issue-8875-cite-by-content-convention.md b/.changeset/issue-8875-cite-by-content-convention.md new file mode 100644 index 0000000000..a3c66732a4 --- /dev/null +++ b/.changeset/issue-8875-cite-by-content-convention.md @@ -0,0 +1,14 @@ +--- +--- + +`AGENTS.md` only — no released package source is touched, so this declares **no release** with +an empty frontmatter rather than a bump. A `patch` here would version-bump the whole 39-package +fixed group for a change no consumer can observe. + +objectui#8875 clause 1: adds Coding Standard **#10 — cite by CONTENT, not by line address**, the +human-facing convention behind the differential gate that landed in objectui#8974. objectui#7853 +ruled the class; objectui#8047 mechanized it for test names only, exempting comments and failure +messages because "a human reads them beside the code they annotate". #10 records the point that +justification is positional: it holds for a same-file citation and does not survive the citation +crossing a file boundary, where nothing puts the cited line in front of the reader and nothing +tells them it moved. diff --git a/AGENTS.md b/AGENTS.md index c53e51422a..4dcbbf32e3 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -107,6 +107,13 @@ interface BaseSchema { - **#7 — No-Touch zones (Shadcn purity).** `packages/components/src/ui/**/*.tsx` are upstream 3rd-party files overwritten by sync scripts — **never edit their logic/styles**. To change `Button`/`Dialog` behavior: create/edit a wrapper in `packages/components/src/custom/`, import the primitive from `@/ui/...`, and wrap it. - **#8 — UI state lives where it can survive (objectui#2269, ADR-0054 C3).** Classify every piece of UI state before writing it: **addressable** (user would share it / expect it back after refresh / Back should respect it) → the **URL** (`?recordId=`, `?form=`, `?tab=` — constants in `app-shell/src/urlParams.ts`, never string literals); **preference** (stable across records/sessions) → localStorage or server prefs; **truly ephemeral** (hover, open dropdown — nobody cares if it's lost) → component state. The rule: **state that must survive a data refresh may never live only in an uncontrolled component** (that's how the detail-tab reset happened — objectui#2257). Corollary: **refresh data, don't rebuild UI** — after a save/action, invalidate the affected data (`notifyDataChanged` from `@object-ui/react`) so consumers refetch in place; never bump a `key=` to remount a subtree (it destroys scroll, collapsed sections, tab state, in-progress inline edits, and triggers a refetch storm). - **#9 — A "verified" claim is only as good as the check that re-verifies it (objectui#7833).** Prose that declares something checked, counted, aligned or covered — in a doc, a code comment, a script or workflow header, or this file — states a fact that was derived once, when it was written, and is derived never again; no later reading can tell such an assertion from a live one. So when you declare something verified, **point at the instrument that re-derives it — the script, the gate, the test — and never write down its answer**: "the count this gate prints", not the count; "the list this script enumerates", not the list. A figure that is still correct is the dangerous case, not the safe one — a reader who spot-checks it confirms it and is still wrong once the population it described has moved out from under it. Where nothing re-derives the claim, say that in the text rather than let it read as live. This file is not exempt: it has carried such a claim and needed a repair (objectui#7800). The measured instances behind this rule sit on objectui#7833 — pointed at, not copied here, which is this rule applied to itself. +- **#10 — Cite by CONTENT, not by line address; a CROSS-FILE `path:line` is banned outright (objectui#7853, objectui#8875).** When a comment, an assertion or failure message, a doc, a changeset or a script header cites an assertion, a call, or a line of code, cite it by something that travels with the thing itself — a quoted distinctive string, a symbol name, a test name, a heading anchor — and ⛔ never by a `path:line` address. A line address is read by nothing: no gate parses it and the file it points at is never opened, so it cannot fail — it rots the first time a line is inserted above the thing it cites, and stays wrong until a reader believes it. objectui#7853 ruled the class and landed as `fa7d66c45`; objectui#6548, #6998, #7289, #7913 and #8045 are the one-at-a-time repairs that followed it, and the class kept recurring — which is why it is now mechanized rather than remembered. + - **The same-file carve-out stands; crossing a file boundary is what makes the citation indefensible.** objectui#8047 mechanized the ruling as the `no-line-address-in-test-name` ESLint rule, over test names only, deliberately exempting comments and failure messages on the ground stated in 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 it 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, nothing tells them it moved, and the person who moves it never sees the citation. Measured once, inside a single pull request's blast radius (objectui#8875 — a historical reading, ⛔ not re-derived here and ⛔ not a live count): 73 cross-file hits across four syntaxes, 14 of them moved by that one branch's line shifts, and **10 of those 14 were already false before the branch existed**. ⇒ same-file line addresses keep the objectui#8047 exemption; cross-file ones are the banned form, in every carrier the ruling names. + - Maintainer's ruling, 2026-09-10 — **原文照录、不翻译**: + > 跨文件的「某文件第几行」引用, 这种完全没必要吧,是否应该避免 + - **The gate is DIFFERENTIAL — it reads what your branch ADDS, never a tree-wide total.** `pnpm check:new-line-citations` (`scripts/check-new-cross-file-line-citations.mjs`, workflow `Line Citation Gate`, landed by objectui#8974) compares your branch against its base and reports the cross-file line-address citations the branch introduced; read that script's `ENFORCEMENT` constant for whether findings block today, and its file header for the syntaxes it scans. ⛔ An absolute count was explicitly refused, and not on taste: a tree-wide number also moves when unrelated line shifts flip a stale citation into a true one by accident, scoring as progress an unearned green that belongs to nobody. The existing population is therefore ⛔ not the gate's denominator; `pnpm census:cross-file-line-citations` prints the tree-wide picture and is report-only by design. ⛔ Never copy either instrument's answer into prose — that is #9 applied to this rule. + - **Existing citations: repair opportunistically, ⛔ never as a sweep.** When you touch a file anyway, fix the cross-file line addresses inside it; ⛔ do not open a pull request whose purpose is sweeping them. ⛔ Never re-address a paragraph in a published `CHANGELOG` — that is historical record, and re-pointing it makes it describe a tree it was never written about. A citation into a regenerated artifact (`dist/*.d.ts`) is *unresolvable*, not a finding. + - **⛔ Don't store a line number as a key, either.** The dividing line is **stored vs computed**: a `path:line` string a script *computes at runtime to print a diagnostic* cannot expire, while one that is *stored and compared* — a ledger key, a baseline entry — is this same defect with a gate wired to it, so it goes red instead of going quiet. Key those by symbol name or anchor text. --- From a9a7143da2b9bb9613fd8cfe1d13dc935248094c Mon Sep 17 00:00:00 2001 From: "claude[bot]" Date: Mon, 14 Sep 2026 08:06:00 +0000 Subject: [PATCH 2/2] docs(changeset): point the objectui#8875 changeset at commandment #11, not #10 The merge that reconciled this branch with main renumbered this branch's commandment from #10 to #11, because bde07805d (#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 Claude-Session: https://claude.ai/code/session_013VGeMu3p6qEFWR6K6GGLaW --- .changeset/issue-8875-cite-by-content-convention.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.changeset/issue-8875-cite-by-content-convention.md b/.changeset/issue-8875-cite-by-content-convention.md index a3c66732a4..2a1265edba 100644 --- a/.changeset/issue-8875-cite-by-content-convention.md +++ b/.changeset/issue-8875-cite-by-content-convention.md @@ -5,10 +5,10 @@ an empty frontmatter rather than a bump. A `patch` here would version-bump the whole 39-package fixed group for a change no consumer can observe. -objectui#8875 clause 1: adds Coding Standard **#10 — cite by CONTENT, not by line address**, the +objectui#8875 clause 1: adds Coding Standard **#11 — cite by CONTENT, not by line address**, the human-facing convention behind the differential gate that landed in objectui#8974. objectui#7853 ruled the class; objectui#8047 mechanized it for test names only, exempting comments and failure -messages because "a human reads them beside the code they annotate". #10 records the point that +messages because "a human reads them beside the code they annotate". #11 records the point that justification is positional: it holds for a same-file citation and does not survive the citation crossing a file boundary, where nothing puts the cited line in front of the reader and nothing tells them it moved.