chore(lint): count-based lint ratchet — new violations block, existing debt baselined; CI lint now blocking (#3962)#3989
Conversation
Eliminate the ~468 ESLint config false-positives (346 parser errors +
122 no-undef fires on TS ambient globals) so the baseline captures only
genuine rule violations:
• Extend top-level ignores: docs/** (VitePress cache), examples/**,
protobufs/** (submodule), public/** → kills 346 parser errors
• Add no-undef:'off' for all TS/JS files — standard typescript-eslint
recommendation; tsc reports undefined identifiers, not ESLint
• Add project:false override for scripts/**/*.{js,mjs,cjs,ts},
*.{js,mjs,cjs}, tests/**/*.{js,mjs,cjs} — utility files outside
tsconfig.json now lint without the type-aware parser
After: 0 parser errors, 0 no-undef errors.
Part of remediation epic #3962 Task 1.4.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AphLfgUJWaFNAgU5UXdJ4n
Promote three rules from warn→error (existing violations are frozen by the count-based ratchet introduced in WP3; new occurrences are blocked): • @typescript-eslint/no-explicit-any → 'error' (2,024 sites baselined) • react-hooks/exhaustive-deps → 'error' (110 sites / 43 files baselined; do NOT auto-fix — missing deps in effect arrays can cause render loops) • prefer-const → 'error' (34 sites — auto-fixed in the next commit) Add a no-restricted-syntax block scoped to src/components/** and src/pages/** banning bare fetch(), window.fetch(), and globalThis.fetch(). 64 existing sites (25 component files, 3 page files) are frozen by the ratchet baseline; they migrate to ApiService in Phase 5. Note: flat-config array replacement means the SQL-ban selectors are not in this block — components/pages never access the DB directly. Part of remediation epic #3962 Task 1.4. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AphLfgUJWaFNAgU5UXdJ4n
Run `eslint --fix --rule '{"prefer-const":"error"}' 'src/**/*.{ts,tsx}'`.
Every changed line is a bare let→const rename; no behavioral change.
3 residual sites (destructuring in RebootModal.tsx and apiTokenRoutes.ts)
could not be auto-fixed and are frozen by the lint ratchet baseline.
Part of remediation epic #3962 Task 1.4.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AphLfgUJWaFNAgU5UXdJ4n
Add scripts/lint-ratchet.mjs: a homegrown count-based per-file ESLint
ratchet. Semantics: for each file×rule, current > baseline → FAIL;
current < baseline → advisory only. Zero new npm dependencies.
Add scripts/lint-ratchet.test.mjs: 10 Vitest unit tests covering the
pure compare(), tally(), and sortObj() functions.
Add package.json scripts:
lint:ci → node scripts/lint-ratchet.mjs (CI gate, blocking)
lint:baseline → node scripts/lint-ratchet.mjs --update (regenerate)
Add eslint-baseline.json (generated, NOT hand-edited):
410 files · 2,515 violations frozen
Top buckets: no-explicit-any 2,026 · no-unused-vars 175 ·
exhaustive-deps 110 · no-restricted-syntax (fetch ban + SQL) 79 ·
react-refresh 37 · prefer-const 3 (residual after auto-fix)
Keys alphabetically sorted, 2-space indent, trailing newline.
npm run lint:ci exits 0 on HEAD (baseline matches current tree).
Part of remediation epic #3962 Task 1.4.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AphLfgUJWaFNAgU5UXdJ4n
ci.yml: replace non-blocking 'npm run lint' (continue-on-error: true) with blocking 'npm run lint:ci'. Existing violations are frozen by the count-based baseline; only new regressions cause CI failures. pr-tests.yml: replace 'npm run lint || true' with 'npm run lint:ci'. Same semantics — blocking gate on new violations only. CLAUDE.md: add ESLint ratchet subsection documenting: - lint / lint:ci / lint:baseline commands and when to use each - rules that are now errors (no-explicit-any, exhaustive-deps, prefer-const) - raw fetch() ban in src/components/** and src/pages/** - how to handle a legit new violation - warning against baseline growth Part of remediation epic #3962 Task 1.4. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AphLfgUJWaFNAgU5UXdJ4n
Add a second pass in compare() that scans baseline entries missing from the current lint output (file became fully clean). Without this pass, advisory messages only fire when a file still has *some* violation but fewer than baseline — completely-fixed files were silently ignored. Also add a Vitest unit test covering the fully-fixed-file advisory case. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AphLfgUJWaFNAgU5UXdJ4n
|
Claude finished @Yeraze's task —— View job PR Review: chore(lint): count-based lint ratchet
Overall this is a well-executed implementation. The ratchet mechanism is clean, has good unit test coverage of the pure functions, and the CI wiring is correct. A few findings below: Bugs / Correctness Issues1.
When a file already exists in the baseline, the outer loop only iterates over rules that are currently firing. A brand-new rule violation on a file that already has a baseline entry is correctly caught (the 2.
This is low risk today but worth a comment in 3.
Code Quality4.
path.join(cwd, 'node_modules', '.bin', 'eslint'),On Windows, the 5. CI lint runs on every Node matrix version needlessly
ESLint Config6.
7. Test files opt out of
Test Coverage8. No test for the The 9. No negative test for The "missing baseline" branch at SummaryThe implementation is solid. The three actionable items worth addressing before merge:
Everything else is advisory. The core ratchet logic, test coverage of the pure functions, and CI wiring are all correct and well-structured. |
Summary
Task 1.4 of the remediation plan (#3962, Phase 1). Lint goes from decorative to enforced.
The problem was bigger than the plan knew: CI lint has been
continue-on-errorsince forever, masking 727 errors (not the ~367 previously estimated) — 468 of them config false-positives (VitePress build cache parsed with type-aware rules,no-undeffiring onNodeJS/React/viin TS files), 259 genuine.ignoresfor docs/examples/protobufs/public,no-undef: offfor TS (TypeScript owns that check),project: falsefor standalone scripts — the 468 false-positives die at the config level, NOT in the baseline.no-explicit-any→ error (2,026 baselined), rawfetch(banned insrc/components/**+src/pages/**viano-restricted-syntax(64 sites baselined; migrate in Phase 5),react-hooks/exhaustive-deps→ error (110 baselined — dep-array auto-edits are behavior-risky),prefer-const→ error (31 of 34 auto-fixed in this PR, purelet→const; 3 destructuring sites baselined).scripts/lint-ratchet.mjs(no new dependencies) compares per-file/per-rule counts against the checked-in, alphabetically-sortedeslint-baseline.json(410 files / 2,515 frozen violations). Any file exceeding its baseline count fails; improvements print an advisory suggestingnpm run lint:baseline. 11 unit tests in the standard suite.ci.ymlandpr-tests.ymlrunnpm run lint:ciwith no escape hatch — closing the deviation left by Task 0.5. CLAUDE.md documents the workflow.Verification
any/ new raw fetch / new exhaustive-deps violation / newleteach fail the ratchet at the exact file; existing debt passes; improvement advisory fires; parser + no-undef false-positives gone.npm run lint:ciexit 0, typecheck clean, test-typecheck baseline unchanged (283), full suite 8169 passed, 0 failed, 0 suite failures.Refs #3962
🤖 Generated with Claude Code
https://claude.ai/code/session_01AphLfgUJWaFNAgU5UXdJ4n