/lint-fix — Lint, Fix, and Capture
Who Should Call This
Dev agents only — specifically:
-
dev-implement-story (via its verification phase)
-
dev-fix-story (via its fix and verification phases)
-
dev-code-review (via code-review-lint.agent.md )
Never call pnpm lint , pnpm eslint , or pnpm turbo run lint directly. Always go through /lint-fix so fixes are attempted, results are captured in the KB, and suppressions are tracked as debt.
Gate Behavior (CRITICAL)
After auto-fix and manual fix attempts, if any errors remain:
VERDICT: FAIL
-
In code review context: the story receives a LINT FAIL verdict. The orchestrator (dev-code-review ) MUST move the story to failed-code-review and invoke /dev-fix-story . The dev agent MUST properly fix the root cause — eslint-disable suppressions are NOT an acceptable fix.
-
In implementation context: the agent MUST fix all errors before committing. Do not suppress with eslint-disable unless a config-update candidate has been identified and documented in the KB.
eslint-disable comments added during a story = automatic FAIL in code review. The suppression inventory (Phase 4) is diffed against the previous run to detect new suppressions introduced by the current story.
Description
Runs ESLint with auto-fix across the monorepo, then:
-
Captures every error that could not be resolved automatically, grouped by rule
-
Scans the codebase for eslint-disable comments — these are suppressed violations, not fixes — and tracks them as technical debt in the KB
-
Stores the run summary, config candidates, and suppression inventory in the knowledge base for trend analysis
Usage
Full repo (most common)
/lint-fix
Single package
/lint-fix --scope=@repo/gallery
Multiple packages
/lint-fix --scope=@repo/gallery,@repo/api-client
Audit only — no auto-fix, no KB writes, just show what would fail
/lint-fix --dry-run
Parameters
-
--scope - Comma-separated package names. Omit for full repo.
-
--dry-run - Skip --fix and skip all KB writes. Terminal output only.
EXECUTION INSTRUCTIONS
Phase 1: Build the lint command
IF --scope provided: filters = "--filter=@repo/pkg1 --filter=@repo/pkg2" cmd = "pnpm turbo run lint --force --continue {filters} 2>&1" ELSE: cmd = "pnpm turbo run lint --force --continue 2>&1"
Phase 2: Run the linter and capture output
Run via Bash. Capture the full output.
Key output signals:
ERROR LINE: "@repo/pkg:lint: 15:3 error <message> <rule-name>" WARNING LINE: "@repo/pkg:lint: 15:3 warning <message> <rule-name>" SUCCESS: " Tasks: N successful, N total" FAILED PKGS: " Failed: @repo/pkg1#lint, @repo/pkg2#lint"
Rule name = the final whitespace-delimited token on each error/warning line.
Phase 3: Parse and group errors
Build two maps:
By rule (sorted descending by count):
rule_name → { count, severity, files: [up to 5 paths with line numbers] }
By package (sorted descending by error count):
package_name → { errors, warnings }
Also extract: total packages run, packages succeeded, packages failed (names), total errors, total warnings.
Phase 4: Scan for eslint-disable suppressions
Suppressions are errors that were silenced, not fixed. They are technical debt.
Find all suppression comments in source files
grep -rn "eslint-disable" .
--include=".ts" --include=".tsx" --include=".js" --include=".jsx"
| grep -v node_modules | grep -v "/dist/" | grep -v "/.turbo/" | grep -v "/tree/"
For each match, extract:
-
file — relative path
-
line — line number
-
type — file-level (/* eslint-disable ... */ at top), next-line (// eslint-disable-next-line ... ), or inline (// eslint-disable-line ... )
-
rules — the specific rules being suppressed (or [all] if no rule listed)
-
comment — any explanatory text after the rule names
Group suppressions by rule name for reporting.
Phase 5: Identify config update candidates
Apply this decision table to the by-rule error map. Threshold for reporting: any rule with ≥ 3 unfixed occurrences.
Condition Candidate type Suggested action
@typescript-eslint/no-unused-vars fires AND variable names start with _
False positive Confirm varsIgnorePattern: '^_' is set
no-console fires in apps/api/** or scripts/**
Wrong scope Verify backend override has 'no-console': 'off'
import/order fires ≥ 5× in same file Structural pattern File uses mid-file imports by design — add /* eslint-disable import/order */ at file top
import/no-duplicates fires ≥ 3× in same file Structural pattern Consolidate imports from the same module
prettier/prettier fires ≥ 5× in a package Missing --fix Add --fix to that package's lint script
Any rule fires ≥ 10× across ≥ 3 packages Global pattern Consider a rule config change in root eslint.config.js
Any rule fires ≥ 3× in test/script/config files Wrong scope Add a file-glob override block in eslint.config.js
no-constant-condition in loop bodies False positive Add { checkLoops: false } in backend rule config
jsx-a11y/* on same element pattern ≥ 3× Presentational Element needs role="presentation"
- keyboard handler
Phase 6: Attempt manual fixes for remaining errors
For errors NOT identified as config-update candidates, attempt minimal fixes:
-
no-unused-vars — prefix with _ or remove if clearly dead code
-
no-console in frontend — replace with logger.info/warn/error (add @repo/logger import if missing)
-
no-case-declarations — wrap case body in { } braces
-
no-useless-escape — remove the unnecessary backslash
-
jsx-a11y/click-events-have-key-events — add onKeyDown matching the onClick
-
jsx-a11y/no-static-element-interactions — add role="presentation" if element is decorative
If a fix is unclear or risky, skip and note it rather than guessing.
After manual fixes, re-run affected packages to confirm they pass:
pnpm turbo run lint --force --continue --filter=@repo/affected-pkg 2>&1
Phase 7: Persist to KB (skip if --dry-run)
Three writes to the KB — all via mcp__knowledge-base__kb_add or mcp__knowledge-base__kb_update .
7a: Lint run summary
Search for an existing run note from today:
mcp__knowledge-base__kb_search({ query: "lint run {YYYY-MM-DD}", tags: ["lint-run"], limit: 1 })
If none found, create a new entry. If found, skip (one run note per day is sufficient unless scope changes).
kb_add payload (entry_type: 'note' , role: 'dev' ):
tags: ["lint-run", "eslint", "{YYYY-MM-DD}", "{PASS|FAIL}"]
content:
Lint Run — {YYYY-MM-DD}
Result: PASS | FAIL Scope: full-repo | {package list} Packages: {N passed} / {N total} ({N} failed) Errors: {N} Warnings: {N} Suppressions (eslint-disable): {N total in codebase} ({N new since last run if known}) Config candidates identified: {N}
Errors by Rule
| Rule | Count | Example Files |
|---|---|---|
rule-name | N | file:line, file:line |
Packages with Errors
| Package | Errors | Warnings |
|---|---|---|
| @repo/pkg | N | N |
Manual Fixes Applied
{bulleted list or "None"}
Still Failing
{bulleted list or "None — all errors resolved."}
7b: Config update candidates
For each candidate identified in Phase 5:
First, search for an existing constraint entry for this rule:
mcp__knowledge-base__kb_search({ query: "ESLint config candidate {rule-name}", tags: ["eslint-config-candidate"], entry_type: "constraint", limit: 1 })
If found: call kb_update to increment the occurrence count and update last_seen in the content.
If not found: call kb_add :
entry_type: "constraint" role: "dev" tags: ["lint", "eslint-config-candidate", "eslint", "{rule-name}"]
content:
ESLint Config Candidate: {rule-name}
First seen: {YYYY-MM-DD} Last seen: {YYYY-MM-DD} Total occurrences: {N} Runs where this appeared: 1
Why This Is a Candidate
{description from the decision table — e.g. "Fires 8× across 4 packages, suggesting a global config change"}
Suggested Change
{concrete code snippet — e.g.:} ```js // eslint.config.js '@typescript-eslint/no-unused-vars': ['error', { varsIgnorePattern: '^', argsIgnorePattern: '^', }] ```
Affected Files (most recent run)
{file:line list}
Status
open — not yet addressed
When a candidate is eventually resolved (config updated), update the entry's content to mark it resolved and describe the fix applied.
7c: Suppression inventory
Suppressions are hidden errors. Track them as a single living inventory note.
Search for the existing suppression inventory:
mcp__knowledge-base__kb_search({ query: "eslint-disable suppression inventory", tags: ["eslint-disable-inventory"], limit: 1 })
If found: call kb_update with refreshed content. If not found: call kb_add .
entry_type: "note" role: "dev" tags: ["lint", "eslint-disable-inventory", "technical-debt", "eslint"]
content:
ESLint Suppression Inventory
Last updated: {YYYY-MM-DD} Total suppressions: {N}
These are lint violations that have been silenced with eslint-disable comments. They are NOT fixes — they are deferred errors. Each one represents real technical debt. The goal is to drive this number toward zero by fixing root causes and updating ESLint config to eliminate false positives.
Suppressions by Rule
| Rule | Count | Type | Notes |
|---|---|---|---|
rule-name | N | file-level / next-line / inline | any comment text |
All Suppressions
{rule-name} ({N} suppressions)
| File | Line | Type | Comment |
|---|---|---|---|
| path/to/file.ts | 42 | next-line | intentional: mid-file import pattern |
| path/to/other.ts | 1 | file-level | WIP: upload page not yet wired up |
{rule-name-2} ...
Trend
{If previous inventory exists, compare counts:}
rule-name: {N} → {N} ({+N | -N | no change})- Net: {+N | -N} suppressions since last run
How to Reduce Suppressions
For each rule appearing here:
- If it's a false positive → update
eslint.config.jsrule config (see Config Candidates) - If it's a real error being hidden → fix the underlying code and remove the disable comment
- If it's a structural pattern (e.g. mid-file imports) → add a targeted file-level disable with a clear comment explaining WHY
Phase 8: Terminal output
Always print to terminal regardless of --dry-run.
═══════════════════════════════════════════════════════ /lint-fix — {date} ═══════════════════════════════════════════════════════
Result: {PASS | FAIL} Packages: {N passed} / {N total} ({N} failed) Errors: {N} Warnings: {N}
{If PASS:} ✓ All packages lint clean.
{If errors remain:} ── Errors by Rule ────────────────────────────────────── {count}× {rule-name} {file:line, file:line, ...}
── Packages with Errors ──────────────────────────────── @repo/pkg1 {E} errors {W} warnings
── Config Update Candidates ──────────────────────────── {rule-name} — {count}× → {one-line suggestion} KB: {entry_type} written/updated
{or "None"}
── Suppressions (eslint-disable) ─────────────────────── Total in codebase: {N} By rule: {count}× {rule-name} ({file-level|next-line|inline}) KB: suppression inventory updated
── Manual Fixes Applied ──────────────────────────────── {file — what changed} {or "None"}
── Still Failing ─────────────────────────────────────── {file:line rule-name (reason not fixed)} {or "None — all errors resolved."}
── New Suppressions Introduced (vs previous run) ─────── {file:line rule-name (eslint-disable added this story)} {or "None"}
── KB Writes ─────────────────────────────────────────── ✓ Lint run summary stored ✓ {N} config candidate(s) written/updated ✓ Suppression inventory updated ({N} total suppressions)
── GATE VERDICT ──────────────────────────────────────── {If no errors and no new suppressions:} ✅ LINT PASS — story may proceed
{If any errors remain OR new eslint-disable added:} ❌ LINT FAIL — story must return to /dev-fix-story Reason: {N errors remaining | N new suppressions added} eslint-disable is NOT an acceptable fix. Fix the root cause.
═══════════════════════════════════════════════════════
KB Entry Reference
What entry_type
Tags When created
Run summary note
lint-run , eslint , YYYY-MM-DD
Every run (once per day)
Config candidate constraint
lint , eslint-config-candidate , {rule-name}
When rule hits ≥3 occurrences
Suppression inventory note
lint , eslint-disable-inventory , technical-debt
First run; updated each run
Integration Notes
-
All workflow skills (/dev-implement-story , /dev-fix-story , /qa-verify-story ) should call /lint-fix rather than pnpm lint directly.
-
Config candidates accumulate in the KB over time. When the same candidate appears in 3+ runs, that is a strong signal to update eslint.config.js permanently — search the KB for tags: ["eslint-config-candidate"] to review open candidates.
-
The suppression inventory is the single source of truth for technical debt hidden by eslint-disable . Review it before adding new suppressions.
Config files to update when acting on candidates
File What to change
eslint.config.js
Rule configs, ignore patterns, file-glob overrides
packages/*/package.json
Add --fix to lint script
CLAUDE.md Common Pitfalls Document new conventions