Skip to content

Commit 5997b41

Browse files
Yerazeclaude
andauthored
chore(lint): count-based lint ratchet — new violations block, existing debt baselined; CI lint now blocking (#3962) (#3989)
* chore(lint): WP1 config hygiene — kill 468 false-positive errors 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 * chore(lint): WP2 severity promotions + raw-fetch ban (#3962) 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 * chore(lint): WP2 prefer-const auto-fix (let→const, 31 of 34 sites) 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 * chore(lint): WP3 count-based ratchet + baseline (#3962) 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(lint): WP4 make lint blocking + document ratchet (#3962) 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 * chore(lint): detect fully-fixed files in ratchet advisory (#3962) 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 --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent c273230 commit 5997b41

44 files changed

Lines changed: 1731 additions & 70 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,11 @@ jobs:
6969
- name: Install dependencies
7070
run: npm install --legacy-peer-deps
7171

72-
- name: Run linter
73-
run: npm run lint
74-
continue-on-error: true # Pre-existing errors need cleanup before enforcing; tracked separately
72+
- name: Run lint ratchet
73+
# BLOCKING: fails only on new violations above the checked-in baseline.
74+
# Existing debt is frozen in eslint-baseline.json and burns down over time.
75+
# Regenerate baseline: npm run lint:baseline. Tracking: #3962 Task 1.4.
76+
run: npm run lint:ci
7577

7678
- name: Run type checking
7779
run: npm run typecheck

.github/workflows/pr-tests.yml

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,10 +80,13 @@ jobs:
8080
# Add prettier check when configured
8181
# npm run format:check
8282
83-
- name: Run linter
83+
- name: Run lint ratchet
8484
run: |
85-
echo "Running ESLint..."
86-
npm run lint || true # Don't fail for now
85+
echo "Running ESLint ratchet..."
86+
# BLOCKING: fails only on new violations above the checked-in baseline.
87+
# Existing debt is frozen in eslint-baseline.json and burns down over time.
88+
# Regenerate baseline: npm run lint:baseline. Tracking: #3962 Task 1.4.
89+
npm run lint:ci
8790
8891
- name: Type check
8992
run: |

CLAUDE.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,21 @@ afterEach(() => harness.cleanup());
112112
- ESLint rule (`no-restricted-syntax` in `eslint.config.mjs`) forbids `this.db.prepare`, `this.db.exec`, `postgresPool.query`, `mysqlPool.query` outside `src/server/migrations/**` and test files.
113113
- Intentional exceptions must carry `// eslint-disable-next-line no-restricted-syntax -- <reason>`.
114114

115+
### ESLint Ratchet (remediation #3962 Task 1.4)
116+
Three lint commands:
117+
- `npm run lint` — full ESLint report; noisy during burn-down (thousands of existing violations). For local exploration only.
118+
- `npm run lint:ci` — the **CI gate**. Runs `scripts/lint-ratchet.mjs`: fails only when a file's per-rule violation count exceeds the checked-in baseline (`eslint-baseline.json`). **This is what CI checks — it must exit 0.**
119+
- `npm run lint:baseline` — regenerate `eslint-baseline.json` from the current tree. Run **after intentionally fixing violations** (baseline shrinks). Never run it to paper over new ones — reviewers will flag a baseline that grows rule counts.
120+
121+
**Rules now errors (existing violations frozen by baseline; burn them down, never up):**
122+
- `@typescript-eslint/no-explicit-any` — 2,026 sites baselined. Burn down in Phase 6.
123+
- `react-hooks/exhaustive-deps` — 110 sites. Do NOT auto-fix; missing deps can cause render loops. Fix per-site with behavior verification or a targeted `eslint-disable-next-line` with an issue-ref reason.
124+
- `prefer-const` — 3 residual sites (destructuring edge cases).
125+
126+
**Raw `fetch()` banned in `src/components/**` and `src/pages/**`** — 64 existing sites frozen by baseline; they migrate to `ApiService` (`src/services/api.ts`) or a TanStack query hook in Phase 5. New components/pages must not use raw `fetch()`.
127+
128+
**Adding a violation you can't avoid:** fix it, or add a targeted `// eslint-disable-next-line <rule> -- #<issue> <reason>`, or (last resort, with reviewer sign-off) `npm run lint:baseline`. Treat any PR that *grows* a rule count in the baseline as a red flag.
129+
115130
### Migration Registry
116131
Migrations use a centralized registry in `src/db/migrations.ts`. Each migration has functions for all three backends.
117132

0 commit comments

Comments
 (0)