// Dead-code configuration. JSONC rather than JSON so this can say what the // gate proves — the previous JSON file could not, and the difference matters // more here than anywhere else in the repo. // // ## What `pnpm knip` (the CI gate in .github/workflows/knip.yml) proves // // A module, export or dependency that nothing in the repository reaches is a // build failure. That is a real property and it catches the ordinary case: a // helper left behind after its last caller was rewritten. // // ## What it does NOT prove, demonstrated // // It does not prove a module is still reachable from production. Vitest is a // knip plugin here, so `src/**/__tests__/**` are entry files, and a module // whose ONLY importer is its own test counts as used — forever. Planting // `src/lib/dead-probe.ts` with a single test importing it left `pnpm knip` at // exit 0. Nothing in the default pass can see that class of dead code, so do // not read a green Dead-code gate as "every module is still wired to // something that ships". // // ## The pass that does see it // // pnpm knip --production --include files // // Production mode drops the plugin-supplied test entries and keeps only the // entry and project patterns suffixed with `!` below — the app router tree, // the proxy, instrumentation, the pg-boss workers, and `scripts/` (maintainer // tooling is not production, but it is a legitimate non-test consumer, so a // module reached only from a script is not the thing we are hunting). The `!` // suffix is inert in the default pass, so both modes read one config. // // This is a review tool, not a second gate, for two reasons written down so // the next reader does not have to rediscover them: // // 1. It reports 22 files as of 2026-08-31. Some are test-only by // construction (the guard helpers under `src/__tests__/helpers/`, the // settings section harness, a score fixture) and some are deliberately // unreachable from production (`src/lib/ai/mock-client.ts` is excluded // structurally by the `PROVIDER_CHAIN_TYPES` allowlist). Baselining the // rest without triaging each one would encode noise as an invariant. // // Re-measure the count when you touch this block. It said 21 until the // v1.38.0 score fixture landed, and a number nobody re-runs drifts // quietly until the whole paragraph reads as guesswork. // // One entry is on that list ON PURPOSE and should stay: // `src/lib/signals/adapters/metric-status.ts`. Its only importer is // `src/lib/signals/__tests__/registry-invariants.test.ts`, which is // exactly what the module is for — it projects the signal registry onto // the `MetricStatusMeta` shape so that test can assert the two agree // field-for-field and catch future drift. Deleting it to clear the line // would remove a check rather than ballast. The production pass is right // about the mechanics here and wrong about the conclusion, which is the // whole reason this is a review tool and not a gate. // 2. It has false positives, though not always the same ones. // `src/lib/pwa/sw-cache-policy.ts` is the standing example: it runs in // every browser, `public/sw.js` mirrors its logic by hand because a // service worker cannot import app modules, and `sw.js:83` names that // lockstep. Nothing static can see that edge. // // This slot used to cite `src/lib/mood/note-backfill.ts`, reported // because `scripts/` sits outside the tsconfig that defines the `@/` // alias. That one no longer appears — the module and its importing // script both still exist, so the resolution improved under us. Left // here as a note rather than deleted, because it is the shape of the // problem: an example that heals silently makes the surrounding // reasoning look wrong when someone checks it. // // Run it in the deep-audit step, the same way // `scripts/audit-column-readers.ts` is run: read the list, judge each entry, // do not treat the exit code as a verdict. { "$schema": "https://unpkg.com/knip@6/schema.json", // `!` marks an entry as production. Read the block above before adding or // removing one — the suffix is what separates the two passes. "entry": [ "src/app/**/page.tsx!", "src/app/**/layout.tsx!", "src/app/**/route.ts!", "src/app/**/loading.tsx!", "src/app/**/not-found.tsx!", "src/app/**/error.tsx!", "src/app/**/global-error.tsx!", "src/app/**/template.tsx!", "src/app/**/opengraph-image.tsx!", "src/app/**/twitter-image.tsx!", "src/app/**/icon.tsx!", "src/app/**/apple-icon.tsx!", "src/app/**/manifest.ts!", "src/app/**/robots.ts!", "src/app/**/sitemap.ts!", "src/app/**/default.tsx!", "src/proxy.ts!", "src/instrumentation.ts!", "src/lib/jobs/**/*.ts!", "src/lib/ai/coach/eval/judge.ts!", "scripts/**/*.ts!", "scripts/**/*.mjs!", "e2e/**/*.spec.ts", "e2e/**/*.test.ts", "tests/**/*.test.ts", "tests/**/setup.ts", // Reached only through the browser project's `next/navigation` alias in // vitest.config.mts, which no import statement names. "tests/browser/next-navigation.ts", "vitest.config.ts", "vitest.config.integration.ts", "playwright.config.ts", "next.config.ts!", "eslint.config.*", "knip.jsonc", ], "project": [ "src/**/*.{ts,tsx}!", "scripts/**/*.{ts,mjs}!", "tests/**/*.ts", "tests/**/*.tsx", "e2e/**/*.ts", ], "ignore": [ "src/generated/**", "prisma/**", "public/**", ".next/**", "**/__mocks__/**", "e2e/setup/test-helpers.ts", "src/lib/logging/index.ts", // v1.42 release-branch contract stubs: each is created ahead of its // consumers so the packages built in parallel share one signature. Drop // a line the moment its first consumer lands; the release integration // checklist removes whatever is left before the tag. "src/lib/environment/air-quality-contract.ts", // The day view and timeline contract (#613): constants and wire types the // server, the two views and the Coach/MCP tool build against. "src/lib/day/contract.ts", ], "ignoreDependencies": [ "@types/node", "@types/react", "@types/react-dom", "@prisma/client", "shadcn", "tailwindcss", "testcontainers", "tw-animate-css", "@napi-rs/canvas", "postcss", ], "ignoreBinaries": [ "docker", "docker-compose", "pg_isready", "tsx", "cosign", // The runtime GeoLite2 fetch extracts in pure Node, but its test builds a // MaxMind-shaped tar.gz fixture with the POSIX `tar` (present in CI + dev). "tar", ], // Scoped to a single file: an export used only inside its own module is not // a finding. It is also why the default pass cannot answer the question the // production pass answers — this setting is about exports, not reachability. // // It had a second effect nobody asked for, worth knowing because it means // the gate under-reported for a year. Until knip 6.28.0 this setting also // exempted every RE-EXPORT: a barrel line was unconditionally marked // "referenced in its own file", so `export { x } from "./x"` that nothing // imported was never reported. knip 6.28.0 (PR webpro-nl/knip#1895, fixing // issue #1894 — `--fix` deleted the source export and left the barrel line // behind, emitting code that did not compile) dropped the exemption. The // upgrade from 6.16.1 took this repo from 0 findings to 206 on unchanged // code; all but two were barrel lines with no importer, and they are gone // now. The suppression was entirely gated on this setting — with it off, // 6.16.1 and 6.32.2 agree exactly. So a green Dead-code gate on any knip // before 6.28.0 said nothing about re-exports. "ignoreExportsUsedInFile": true, "ignoreIssues": { "src/components/ui/**": ["exports", "types"], "src/lib/validations/**": ["types"], "src/hooks/_internal/use-query-client-safe.ts": ["exports"], "src/lib/google-health/sync.ts": ["exports"], }, }