Development record
Every lint warning rule shares one baseline file that only shrinks, so a new warning fails the change that introduces it
Typed links: related_issues iss-46 (the lint scope holes record, whose one remaining hole is this baseline).
Press Release
Every lint warning rule shares one baseline file that only shrinks, so a new warning fails the change that introduces it. The warnings record-lint and docs-lint report today are frozen in one committed baseline file; a change that adds a warning the baseline does not hold fails, a change that fixes one takes it out of the baseline in the same change, and the file only ever loses entries.
"We had forty warnings nobody read, and every new one hid among them," said Bob, a staff engineer on a platform team. "Now the old ones sit in one file that only gets shorter, and the one I just introduced fails my own change."
Why This Matters
A warn rule reports and never fails, so a warning added today reads exactly like the ones that were already there, and a rule can only be armed once its whole backlog is cleared. iss-46 names this as the last of its five lint scope holes: the ratchets that exist (the prose-citation baseline, the site's unresolved-reference baseline) are each one rule's own file, and there is no general one. The product thinker ruled on 2026-09-29 (ruling BT3) for one shared baseline file for all warn rules, shrink-only, in which a new warning fails; a baseline per rule and arming rules only when clean were the alternatives not taken.
Mechanism
We expect warnings to stop accumulating because the change that introduces one is the change that fails, while the existing backlog stays visible in one file a reviewer can read; shown wrong if the baseline file grows between releases, or if warnings are silenced by moving rules out of warn rather than by fixing them.
What's In Scope
- One committed baseline file holding every current finding of every warn rule in record-lint and docs-lint, keyed so an unrelated edit to the same file does not re-key a finding.
- The gate: a finding the baseline does not hold fails the lint; a baseline entry whose finding is gone must leave the baseline in the same change; the file only ever loses entries.
- Coexistence with the per-rule baselines that exist (the prose-citation and site baselines): the planning interview decides whether they fold into the shared file or stay beside it.
What's Out of Scope
- Blocker rules, which fail already.
- The scope matrix iss-46 also asks for (which rule covers which tree).
Scope Conditions
- Holds for the warn rules of record-lint and docs-lint in a repository that commits the baseline; a repository without the file runs the rules as warn, unchanged.
Acceptance Criteria
- Given a tree whose warn findings all appear in the baseline, when the lint runs, then it passes and reports the baseline's size.
- Given a change that introduces a warn finding the baseline does not hold, when the lint runs, then it fails naming the finding and the rule.
- Given a change that fixes a baselined finding but leaves its entry, when the lint runs, then it fails naming the stale entry.
- Given a change that adds an entry to the baseline, when the lint runs, then it fails: the file only shrinks.
SOTA
Surveyed on 2026-10-23; sources, evidence tiers and the local fit risks are in the forge, CI and merge SOTA note.
- The most recent general design keys by file, rule and count, not by
line: ESLint bulk suppressions (April 2025) hold
{ "<file>": { "<rule>": { "count": n } } }in one committed file for all rules; a count exceeded reports every violation of that rule in that file; a count too high exits non-zero ("There are suppressions left that do not occur anymore") until--prune-suppressionsruns; counts were chosen because there is "no reliable way to determine whether the new violations were introduced recently or already existed" (ESLint docs and blog). ESLint applies the file to error-severity rules only ("If a rule is enabled as"warn", ESLint will not suppress the violations"), so the borrowed part is the file shape and the count semantics; a shrink-only ratchet over a warn tier has no direct precedent in these tools, and PHPStan's baseline is the precedent that is not tier-restricted. - PHPStan is the same shape (
message,count,path), reports an entry whose error is gone, warns that regenerating to admit new errors grows the file, and says "The life goal of a baseline file is to not exist." Psalm's--update-baselineremoves fixed issues and "will not add new issues". - detekt keys by signature (
<RuleID>:<FileName>:<Signature>, an id that carries no line number, an inference from the format), and "auto formatting cannot be combined with the baseline" because its signatures would be too ambiguous: the finer key, at the cost of a signature per rule. - Ruff has no baseline (inline
--add-noqaplusRUF100); Betterer is the "only gets better" idea, with no stable release since August 2022 (5.4.0 on npm; one alpha, 6.0.0-alpha.1, in December 2024). - The alternative family is new-code-only gating (SonarQube: "do not
recommend adding conditions for overall code"; golangci-lint's
new-from-merge-base, already in the lint-subset note for blocking rules). No conflict: that note gated the error tier; this intent is the warn tier, where a committed shrinking file is the debt record the ratchet principle asks for, and where ESLint's precedent, built for the error tier, is extended rather than copied.
Path 2: a native shared baseline shaped to ESLint's file: one committed
JSON keyed by path, rule id and count; a new warn rule's existing findings
enter through an explicit --suppress-rule <id>-style admission in the
change that adds the rule, the one named exception to shrink-only; the
per-rule baselines fold in, as every precedent keeps one file for all rules;
the count key is why an unrelated edit cannot re-key a finding, and detekt's
signature key is the seam for a rule whose findings are many per file. No
dependency. The independent fit-challenge runs at plan time.
Open Questions
- How a new warn rule's existing findings enter the file: shrink-only refuses additions, so arming a new rule over a tree that already breaks it needs a declared path (the rule's first freeze in the change that adds the rule, or no freeze at all).
- Whether the per-rule baselines fold in (see scope).
- (SOTA 2026-10-23) Whether a count key is fine enough for rules with many findings per file (prose citations), where a fix and a new finding in one file cancel out in the count, or such a rule takes a signature key.
- (SOTA 2026-10-23) Whether each entry carries the issue id that retires it, which none of the precedents do and the ratchet principle asks for.
- The key a finding carries in the file, so an unrelated edit does not read as a fixed finding plus a new one.
Audit Notes
Empty. Populated by intent-auditor when intent moves to shipped/.
Frontmatter
| id | itd-2609291924469783 |
|---|---|
| type | intent |
| lifecycle | drafts |
| severity | minor |
.abcd/development/intents/drafts/itd-2609291924469783-every-lint-warning-rule-shares-one.md
open on GitHub ↗ · commit history ↗
Referenced by1
- iss-46lint scope holes and gate parity: link-lint does not cover all committed markdown; the persona rule is absent from docs-lint; record-lint blocking semantics are inconsistent between local and CI and there is no warn-baseline ratchet; gofmt is missing from make preflight though attributed to it; repo-local hooks activation and its provisioning dependency are undocumented. Detector (per ratchet-not-big-bang): a lint scope matrix (which rule covers which tree, checked into the record) plus baseline-ratchet support in record-lint so new rules arm immediately against frozen violations. Acceptance corpus: the five holes above.related
mentions1
- dcn-26092900000000072026-09-29 — The product thinker answered twenty-six owed rulings and the published-history question in one interview, asked one at a time by orchestrator abcd-23 of autonomous run A (recorded by lane recRulings for orchestrator abcd-cb). The answers are kept verbatim in the local-tier file `.abcd/.work.local/scratch/reports/rulings-answered-2609-29.md`, dated 2026-09-29, and are restated here because that file is not committed. Published history is left as it is, with no rewrite. The build loop's fidelity audit runs once, on the lane that closes the spec, over the whole delivery (AI). On the vendor denylist the product thinker answered, verbatim: "I think we need an allow list rather than trying to catch a deny list." The interviewer's reading of that answer, recorded as a reading and not as the product thinker's words, is that the per-provider allowlist is the control and the vendor denylist is not extended to chase spellings, with whether the bundled `anthropic/*` entry stays as a backstop left to the technical facilitator (AA(a)). Only a route the person set up on their own machine may use their paid key, and a repository's own route to it is refused, which replaces the route half of itd-2609081951381895 decision 8 (AA(b)). The live site setup, the API adapter and the decision adapter may each close on tests against a simulated service, with the live paid check recorded as owed to a person (iss-2609291925387011, iss-2609291925393913, iss-2609291925398978). itd-111's last criterion is met by an agent's timing check on macOS plus the Linux CI run, so the intent may be finished. Automated dependency updates are switched on, pushed with a personal or app token the person creates and stores as a repository secret (itd-2609221842494980; creating the token is the person's act, and the lane builds everything else and names the secret it expects). itd-6 adds a Go MCP module, proposed for the person's explicit sign-off before it is fetched; closing itd-6 on a fake server was not ruled in this interview. (A later answer the same day, H1b in the local-tier file `.abcd/.work.local/scratch/reports/rulings-answered-2609-29-b.md`, dated 2026-09-29, rules that it is not: itd-6 is finished only after running once against the real RepoPrompt app, a check owed to a person.) For itd-148: the store under the home folder wins (adr-2609091248200336); the store draft itd-2609091014076309 owns add, list and clean-up and itd-148 uses them; a session that declares itself the coordinator is exempt from the write block in the primary checkout and every other session is blocked; and the store draft is planned first, with nothing from the store draft, the merged-worktree clean-up included, built before its planning interview (Q4). itd-148's own wait is not part of that ruling: it follows from Q2, because itd-148 uses the store's verbs, and is carried by the `blocked_by` below. itd-148 and spc-42 are rewritten to match in this change, and itd-148 is now `blocked_by` the store draft. A release retrospective is seeded from a release, the intents a tag shipped (AD); itd-24 and its spec are rewritten to the release in this change. An unpointed route is refused naming the one setting to add, as built in 35100512e, so AC11 of itd-2609170822093401 is complete (BR1). A missed target becomes `next`, whatever the following release is numbered (BS1). Spec pieces keep "step" and the lane stages are renamed, a breaking change to the JSON fields `step`, `performed` and `refusal.step` that resolves iss-2609291313276243 and corrects the glossary entry `core/step.md` (BU1), and the old command spellings are removed in that same breaking release (BT1). The text board shows Later as a count only while the site and `--json` keep the full list (BV1), and an intent in progress appears only under Now (BV2). A pause of zero is allowed (BW1); drain stays in the agents' help list until it runs end to end (BX1); a managed repository must hold the drain eligibility decision in its own record, and drain refuses there until it does (BX2); a capture without a remedy is refused (BX3), which tightens the one-line capture friction `commands/capture.md` promises, so the lane that builds it reconciles that page, the principle wording and every caller that files a capture; a run's pick reason keeps the shared `pursued:` kind with its marker (BZ1); and an intent whose blocker was superseded waits on the successor, transitively (BZ2). The five itd-48 follow-ups are planned as one intent, drafted as itd-2609291923559186 (BT2), and all warn rules share one shrink-only baseline file in which a new warning fails, drafted as itd-2609291924469783 (BT3); both drafts wait on their planning interviews. Each ruling that contradicts a shipped behaviour or another record is amended in the change that builds it, not here. itd-7's planning interview stays owed.