Split from #17400, which keeps the wiring. This leaf is the measurement itself: two axes over a commit's added lines, plus the bars they are judged against.
Nothing in the repo measured comment density in shipped source — check-ticket-archaeology governs decay-prone refs, check-substrate-size governs turn-loaded bytes — so the only gate was the author's own judgement. Measured over 21 days of agent-authored ai/ + src/ commits, prose share of added lines by family: opus5 39.8%, fable 30.5%, kimi 28.6%, gpt 10.7%, against an operator bar of ~30%.
The Problem
A share alone has an author-controlled denominator.a17ade4264 measures 21% — under the bar — while carrying a 36-line contiguous prose run on one function, and the operator change-requested it for bloat. ~200 lines of fixture in the same commit produced that dilution, so adding tests buys room for narrative: gameable in the direction of good behaviour, which nobody audits.
The cost is paid twice. A maintainer pays on every visit, and the text is ingested into Chroma — verified: a query for a phrase existing only inside comments returns TextEmbeddingService.mjs (2313) and TenantRepoSyncService.mjs (543). A long derivation dilutes the invariant it wraps and then competes with it for retrieval, at full weight, forever. Unlike a ticket, a docblock never closes.
The Architectural Reality
buildScripts/util/check-ticket-archaeology.mjs:44 — extractComment(line, state), string-aware with block state carried across lines. Reused rather than re-regexed.
buildScripts/util/stagedDiff.mjs:57 — getStagedAddedLines(file, gitRoot), the added-line-number reader.
buildScripts/util/check-substrate-size.mjs — budget-shaped guard precedent, scoped to turn-loaded substrate.
package.jsonlint-staged — where a pre-commit entry would live; .husky/pre-push runs its checks serially and is the likelier home.
The Fix
Two axes over added lines, warned independently: prose share, and longest contiguous prose run — the axis with no denominator to dilute.
Tag lines are contract, not prose.@param / @returns / @summary are excluded and break a run, so a densely-typed signature does not read as bloat.
Bars as inputs. The run default sits inside the measured p90–p99 band (p90 32, p99 64 across ai/services/**), so a warning means unusual.
Warn, never block, and print both raw numbers rather than a verdict.
Contract Ledger Matrix
Target Surface
Source of Authority
Proposed Behavior
Fallback
Docs
Evidence
measureProseDensity
the file's lines + the added-line set
per-file share and longest run; only added lines count
non-comment lines break a run
pre-commit docs
pre-existing prose is not this commit's
summarizeDensity
per-file measurements
commit-level share (summed) and run (max); warns on either
empty set never warns
pre-commit docs
21% share / 36 run must warn
bars
measured p90–p99 band + the ~30% observation
operator-tunable inputs
partial bounds normalize onto defaults
pre-commit docs
a changed bound changes the outcome
Decision Record impact
none — adds a measurement, changes no authority.
⛔ IMPLEMENTATION PRESCRIPTION SUPERSEDED 2026-08-20 — read before building
PR #17407 was closed unmerged under a Drop+Supersede from @neo-gpt-emmy (review). The problem and the consumer are unchanged; the inference substrate is refuted. Do not restart from that diff's approach.
Why the line-oriented substrate cannot govern this
Raw-prefix comment inference. Matching /^\s*(\/\*|\*\/|\*|\/\/)/ against a raw line classifies a continued multiplication as a comment:
const area = width
* height;
A checker that manufactures both false warnings and false silence on valid JavaScript has no authority over repository prose.
Unit mismatch, third instance on this lane. The shipped code measured a whole pushed range (remoteSha..localSha) as one diff while both bars were calibrated per commit. Earlier instances: a whole-FILE run distribution applied to per-commit added lines, and a chunk count divided into a file-count rate. A threshold inherits the unit of the distribution that produced it. The successor names its unit once, explicitly, and calibrates in that unit.
Lexical concession detection is a dead end, and this is measured. Two independent candidate vocabularies for the operator's "I know duplicating code is bad, but…" specimen scored 0 true across 13 hits over 2564 in-scope files: the lexical form fired only on this repository's own epistemic idiom ("the case we know"), the structural <norm>…but form only on contrastive "not X but Y" technical prose. Do not re-attempt it; beat the measurement first if you disagree.
Required substrate for the successor
Parser-owned comment ranges.buildScripts/util/check-aiconfig-test-mutation.mjs:160-200 is the sanctioned Acorn precedent in this repository. Comment classification comes from the parser, never from line prefixes.
One named measurement unit, calibrated in that unit, applied in that unit.
not-measured is a distinct state from clean. A file that could not be parsed, a range that could not be resolved, and a range with no findings must be three different outputs. The refuted implementation collapsed the first two into the third — twice in one file, the second time after the first was fixed.
Salvage — retained, do not rebuild
The pre-push payload capture and its advisory || true (with the set -e reasoning that makes non-blocking a stated property rather than an assumption); the pendingRanges tuple / branch-deletion / new-remote-branch shapes; the warning output format carrying raw numbers rather than a verdict; injectable bounds with the fixture proving a changed bound changes the outcome; the scan roots including buildScripts/ so the checker is subject to its own rule; and the temp-git-repo fixture, which is how a git-reading arm stays honest under a depth-1 CI checkout.
Acceptance Criteria
Red-proof, run axis: the TOOL, run over a17ade4264, reports 19% share and a 45-line block — under the share bar, over the run bar. Asserted by measuring the commit, not by feeding the numbers in: an earlier version of this AC said 36 while the tool computed 9 on the same commit, because the run broke on tag lines.
Silent arm: the tool reports 24% share and an 11-line block on 087114e8ab and warns on neither axis, so a guard that warns on everything fails here.
Share axis warns high and is silent at the median, and it catches what the run axis misses: 027125dcf7 cuts the run 45 → 5 while 27 of its 32 added lines are comment.
Tag lines are excluded from prose and do NOT break the run. The axes measure different objects: a typed signature is contract rather than narration, and a 45-line docblock is 45 lines to a reader whether or not @param sits in the middle. Breaking the run on tags is what made the axis unable to fire at all.
A comment marker inside a string literal is not prose.
An unchanged line breaks a run; pre-existing prose is not counted as this commit's.
Each bar is the p90 of the seats whose behaviour did not change — two independent flat controls agreeing (block run 34 and 35; share 31.9% and 28.5%) — and a changed bound changes the outcome. A trailing median would drift along with what it measures.
Both raw numbers are printed, never only a verdict.
Scope includes specs and buildScripts/ — a checker exempt from its own rule is not a rule.
Out of Scope
Wiring it into a hook.#17400 keeps it. Adding the lint-staged entry reproduced Task killed on unrelated checks, and a single-file commit reproduced the same kill, so the cause is host memory pressure rather than the entry. .husky/pre-push needs a commit-range mode, which is the follow-up rather than a rushed addition.
Blocking on density, and any rule a marker can silence.
Artifact-body density (issue / PR / review prose) — #16217 owns that surface.
Ticket-ref hygiene in comments — check-ticket-archaeology owns it.
Avoided Traps
A ratio as the only axis. Caught by a peer's counterexample rather than by reasoning, which is why the run axis is an AC.
A control that is merely smaller. A 30-line run six below the 36 that must warn would pin the bar into (30, 36] by construction, and 30 sits between median and p90 — ordinary. The silent arm is at the median instead.
Mirroring the archaeology check's escape hatch along with its shape. Free-text escapes defeat prose guards specifically, because prose is what the escape is made of.
Counting markers instead of text.extractComment('/**') returns *; counting it inflated every docblock by one, so prose requires a word character.
Attributing the hook kills to this change. One trial each way is not a cause, and a single-file commit reproduced the kill.
Context
Split from #17400, which keeps the wiring. This leaf is the measurement itself: two axes over a commit's added lines, plus the bars they are judged against.
Nothing in the repo measured comment density in shipped source —
check-ticket-archaeologygoverns decay-prone refs,check-substrate-sizegoverns turn-loaded bytes — so the only gate was the author's own judgement. Measured over 21 days of agent-authoredai/+src/commits, prose share of added lines by family: opus5 39.8%, fable 30.5%, kimi 28.6%, gpt 10.7%, against an operator bar of ~30%.The Problem
A share alone has an author-controlled denominator.
a17ade4264measures 21% — under the bar — while carrying a 36-line contiguous prose run on one function, and the operator change-requested it for bloat. ~200 lines of fixture in the same commit produced that dilution, so adding tests buys room for narrative: gameable in the direction of good behaviour, which nobody audits.The cost is paid twice. A maintainer pays on every visit, and the text is ingested into Chroma — verified: a query for a phrase existing only inside comments returns
TextEmbeddingService.mjs(2313) andTenantRepoSyncService.mjs(543). A long derivation dilutes the invariant it wraps and then competes with it for retrieval, at full weight, forever. Unlike a ticket, a docblock never closes.The Architectural Reality
buildScripts/util/check-ticket-archaeology.mjs:44—extractComment(line, state), string-aware with block state carried across lines. Reused rather than re-regexed.buildScripts/util/stagedDiff.mjs:57—getStagedAddedLines(file, gitRoot), the added-line-number reader.buildScripts/util/check-substrate-size.mjs— budget-shaped guard precedent, scoped to turn-loaded substrate.package.jsonlint-staged— where a pre-commit entry would live;.husky/pre-pushruns its checks serially and is the likelier home.The Fix
@param/@returns/@summaryare excluded and break a run, so a densely-typed signature does not read as bloat.ai/services/**), so a warning means unusual.Contract Ledger Matrix
measureProseDensitysummarizeDensityDecision Record impact
none— adds a measurement, changes no authority.⛔ IMPLEMENTATION PRESCRIPTION SUPERSEDED 2026-08-20 — read before building
PR #17407 was closed unmerged under a Drop+Supersede from @neo-gpt-emmy (review). The problem and the consumer are unchanged; the inference substrate is refuted. Do not restart from that diff's approach.
Why the line-oriented substrate cannot govern this
/^\s*(\/\*|\*\/|\*|\/\/)/against a raw line classifies a continued multiplication as a comment:const area = width * height;A checker that manufactures both false warnings and false silence on valid JavaScript has no authority over repository prose.remoteSha..localSha) as one diff while both bars were calibrated per commit. Earlier instances: a whole-FILE run distribution applied to per-commit added lines, and a chunk count divided into a file-count rate. A threshold inherits the unit of the distribution that produced it. The successor names its unit once, explicitly, and calibrates in that unit.<norm>…butform only on contrastive "not X but Y" technical prose. Do not re-attempt it; beat the measurement first if you disagree.Required substrate for the successor
buildScripts/util/check-aiconfig-test-mutation.mjs:160-200is the sanctioned Acorn precedent in this repository. Comment classification comes from the parser, never from line prefixes.not-measuredis a distinct state fromclean. A file that could not be parsed, a range that could not be resolved, and a range with no findings must be three different outputs. The refuted implementation collapsed the first two into the third — twice in one file, the second time after the first was fixed.Salvage — retained, do not rebuild
The pre-push payload capture and its advisory
|| true(with theset -ereasoning that makes non-blocking a stated property rather than an assumption); thependingRangestuple / branch-deletion / new-remote-branch shapes; the warning output format carrying raw numbers rather than a verdict; injectable bounds with the fixture proving a changed bound changes the outcome; the scan roots includingbuildScripts/so the checker is subject to its own rule; and the temp-git-repo fixture, which is how a git-reading arm stays honest under a depth-1 CI checkout.Acceptance Criteria
a17ade4264, reports 19% share and a 45-line block — under the share bar, over the run bar. Asserted by measuring the commit, not by feeding the numbers in: an earlier version of this AC said 36 while the tool computed 9 on the same commit, because the run broke on tag lines.087114e8aband warns on neither axis, so a guard that warns on everything fails here.027125dcf7cuts the run 45 → 5 while 27 of its 32 added lines are comment.@paramsits in the middle. Breaking the run on tags is what made the axis unable to fire at all.buildScripts/— a checker exempt from its own rule is not a rule.Out of Scope
Task killedon unrelated checks, and a single-file commit reproduced the same kill, so the cause is host memory pressure rather than the entry..husky/pre-pushneeds a commit-range mode, which is the follow-up rather than a rushed addition.check-ticket-archaeologyowns it.Avoided Traps
(30, 36]by construction, and 30 sits between median and p90 — ordinary. The silent arm is at the median instead.extractComment('/**')returns*; counting it inflated every docblock by one, so prose requires a word character.Related
Origin Session ID: 8cbd588b-be06-4a56-9997-1058f2a3a07b
Retrieval Hint:
query_raw_memories("comment density two axes share run author-controlled denominator prose bar p90 band warn only")