From a640e84142b710d862c1351bcfbf3a5b4ca87f63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Sun, 2 Aug 2026 16:36:25 +0200 Subject: [PATCH] Exclude the worktrees the tooling was already meant to exclude git, eslint and vitest each carry an exclusion for local development worktrees, and all three name /.worktrees/ while the convention actually in use is /.wt-*/. Every one of them was therefore walking eleven full copies of the source tree. The consequences differed by tool and none of them announced itself. pnpm lint took ten minutes instead of one and reported warnings from whatever commits those copies happened to sit on. vitest collected duplicate suites that drag in a second React instance, which fails in ways that have nothing to do with the working tree. git left them merely untracked, one git add -A away from committing the repository eleven times over. With the true warning count finally visible, teach no-unused-vars the underscore convention this codebase already follows and drop the five disable directives that convention made redundant. pnpm lint is clean. A non-underscore unused argument still warns; that was checked rather than assumed. --- .gitignore | 7 ++++++- eslint.config.mjs | 19 +++++++++++++++++++ src/app/api/feature-flags/route.ts | 1 - src/app/api/measurement-categories/route.ts | 1 - src/app/api/sync/state/route.ts | 1 - .../onboarding/goals-chip-picker.tsx | 1 - src/lib/cycle/gate.ts | 1 - vitest.config.mts | 9 +++++++-- 8 files changed, 32 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index 9299475b7..b470644a6 100644 --- a/.gitignore +++ b/.gitignore @@ -87,5 +87,10 @@ next-env.d.ts .audit-*.mjs -# Isolated development worktrees +# Isolated development worktrees. `/.wt-*/` is the convention actually in use; +# each one is a full copy of the tree with its own node_modules, so leaving it +# untracked-but-visible means one `git add -A` commits the repository eleven +# times over. The same two names are excluded from eslint and vitest, for the +# same reason in a different tool. +/.wt-*/ /.worktrees/ \ No newline at end of file diff --git a/eslint.config.mjs b/eslint.config.mjs index 82fa8cfae..9f0e9130f 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -16,6 +16,10 @@ const eslintConfig = defineConfig([ // v1.31.4 — ignore local tool worktrees so a worktree on another // commit cannot poison `pnpm lint` locally. // CI never sees these paths; the rules protect the dev environment. + // `.wt-*` is the convention actually in use; the two names below predate + // it. Without the first pattern `pnpm lint` walks every worktree in the + // root, which is both slow and exactly the poisoning this list prevents. + ".wt-*/**", ".claude/**", ".worktrees/**", // The project-local ESLint rule plugin is CommonJS (require/module @@ -32,6 +36,21 @@ const eslintConfig = defineConfig([ { plugins: { healthlog: healthlogPlugin }, rules: { + // The codebase already marks a deliberately unused binding by prefixing + // it with an underscore — a required-but-ignored route handler argument, + // a mock signature that has to match the real one. The rule did not know + // that convention, so seven such bindings warned and `pnpm lint` could + // not be read as pass/fail. Teaching it the convention is what makes a + // real warning visible again. + "@typescript-eslint/no-unused-vars": [ + "warn", + { + argsIgnorePattern: "^_", + varsIgnorePattern: "^_", + caughtErrorsIgnorePattern: "^_", + destructuredArrayIgnorePattern: "^_", + }, + ], "healthlog/queryKey-factory": "error", // v1.5.6 — every outbound fetch under src/lib + src/app must route // through the safeFetch wrapper (manual-redirect + timeout, and the diff --git a/src/app/api/feature-flags/route.ts b/src/app/api/feature-flags/route.ts index e92422307..a8d9928db 100644 --- a/src/app/api/feature-flags/route.ts +++ b/src/app/api/feature-flags/route.ts @@ -40,7 +40,6 @@ import { apiSuccess } from "@/lib/api-response"; import { getAssistantFlags } from "@/lib/feature-flags"; import { annotate } from "@/lib/logging/context"; -// eslint-disable-next-line @typescript-eslint/no-unused-vars export const GET = apiHandler(async (_request: NextRequest) => { await requireAuth(); annotate({ action: { name: "feature-flags.read" } }); diff --git a/src/app/api/measurement-categories/route.ts b/src/app/api/measurement-categories/route.ts index ef7f55f56..597322014 100644 --- a/src/app/api/measurement-categories/route.ts +++ b/src/app/api/measurement-categories/route.ts @@ -62,7 +62,6 @@ const CATEGORY_ORDER: readonly MeasurementCategory[] = [ "metabolic", ] as const; -// eslint-disable-next-line @typescript-eslint/no-unused-vars export const GET = apiHandler(async (_request: NextRequest) => { await requireAuth(); diff --git a/src/app/api/sync/state/route.ts b/src/app/api/sync/state/route.ts index dd9ce0a4e..b45e269a1 100644 --- a/src/app/api/sync/state/route.ts +++ b/src/app/api/sync/state/route.ts @@ -43,7 +43,6 @@ import { TOMBSTONE_RETENTION_DAYS, } from "@/lib/auth/native-client"; -// eslint-disable-next-line @typescript-eslint/no-unused-vars export const GET = apiHandler(async (_request: NextRequest) => { const { user } = await requireAuth(); diff --git a/src/components/onboarding/goals-chip-picker.tsx b/src/components/onboarding/goals-chip-picker.tsx index a637858a9..a1a64d06a 100644 --- a/src/components/onboarding/goals-chip-picker.tsx +++ b/src/components/onboarding/goals-chip-picker.tsx @@ -97,7 +97,6 @@ export interface GoalsChipPickerProps { userId: string; } -// eslint-disable-next-line @typescript-eslint/no-unused-vars export function GoalsChipPicker({ userId: _userId }: GoalsChipPickerProps) { const { t } = useTranslations(); const router = useRouter(); diff --git a/src/lib/cycle/gate.ts b/src/lib/cycle/gate.ts index 839ff1e02..c198a7361 100644 --- a/src/lib/cycle/gate.ts +++ b/src/lib/cycle/gate.ts @@ -88,7 +88,6 @@ export type CycleGateResult = */ export async function requireCycleEnabled( userId: string, - // eslint-disable-next-line @typescript-eslint/no-unused-vars _gender: string | null | undefined, ): Promise { const [profile, available] = await Promise.all([ diff --git a/vitest.config.mts b/vitest.config.mts index 777007d6f..e19110a15 100644 --- a/vitest.config.mts +++ b/vitest.config.mts @@ -31,8 +31,13 @@ export default defineConfig({ "tests/integration/**", // Playwright E2E suite — driven separately via `pnpm e2e`. "e2e/**", - // Local agent worktrees create copies of `src/` under `.claude/` - // or `.worktrees/`; vitest must not collect stale duplicate suites. + // Local tool worktrees create full copies of `src/` in the repo root; + // vitest must not collect stale duplicate suites from them. The copies + // also carry their own `node_modules`, so a collected duplicate drags in + // a SECOND React instance and fails in ways that have nothing to do with + // the working tree. `.wt-*` is the convention actually in use — the two + // names below predate it and are kept because both still occur. + ".wt-*/**", ".claude/worktrees/**", ".worktrees/**", ],