{ "$schema": "https://unpkg.com/knip@6/schema.json", // Guards the invariant in CLAUDE.md "Package Boundaries": every workspace // declares the external packages it actually imports, and nothing else. // `.npmrc` sets `shamefully-hoist=true`, so an undeclared import still // resolves at runtime and an obsolete declaration still installs — neither // failure is visible without a tool that reads the import graph. // // Scoped to `files`, `dependencies`, and `unlisted` on purpose. `exports` is // the noisy one here: packages/ui and packages/views are consumed through // per-file `exports` maps, so a named export used by exactly one app still // reads as "unused" from inside its own workspace. "include": ["files", "dependencies", "unlisted"], // Go, and a workspace with its own Expo toolchain, lint pipeline, and React // version (see apps/mobile/CLAUDE.md). Neither is covered by this config. "ignoreWorkspaces": ["apps/mobile"], "workspaces": { ".": { "entry": ["scripts/*.mjs", "scripts/brand/*.mjs"], "project": ["scripts/*.mjs", "scripts/brand/*.mjs"], // scripts/theme-preview.mjs drives Tailwind through the PostCSS API; the // root has no Tailwind stylesheets for the plugin to read. "tailwind": false }, "apps/web": { "entry": ["eslint.config.mjs"], // Referenced only by the `@type {import('postcss-load-config').Config}` // JSDoc annotation in postcss.config.mjs. Declaring it would add a // runtime dependency to satisfy a type comment. "ignoreDependencies": ["postcss-load-config"] }, "apps/desktop": { // window-state.node-test.mjs is run by hand (`node --test`, see its // header) rather than by a package script, so nothing imports it. // bundle-cli.node-test.mjs is run the same way. test/electron-guard.ts // is the `electron` alias target in vitest.config.ts, a path string // knip does not follow. "entry": [ "eslint.config.mjs", "src/main/*.node-test.mjs", "scripts/*.node-test.mjs", "test/electron-guard.ts" ], // Consumed by `extends` in tsconfig.node.json / tsconfig.web.json. // knip follows `extends` from the default tsconfig.json only. "ignoreDependencies": ["@multica/tsconfig"] }, "apps/docs": {}, // The three shared packages export raw .ts/.tsx compiled by their // consumers (CLAUDE.md "Project Shape"), so `entry` names only the config // files knip would otherwise miss and the apps supply the real entry // points — a file is live when an app reaches it. // // That holds for views and core, whose `exports` name specific files // (44 of views' 45). It does NOT hold for the four wildcard exports in // packages/ui: knip expands each into a glob over its whole directory and // registers every match as an entry point, so nothing under // components/ui, components/common, markdown, or hooks can ever be // reported unused here. `entry: []`, negated `entry` patterns, and // `--production` were all tried — manifest entries come from a separate // code path that none of them subtract from. // // scripts/check-ui-wildcard-exports.mjs covers exactly those four // directories and runs as its own blocking CI step. Narrow a wildcard // export and knip picks that directory back up; the script reads the same // manifest, so the two stay in step without overlapping. "packages/ui": { // shimmer-text-bundle.mjs is imported by e2e/shimmer-text.spec.ts, which // sits outside every workspace, and hands the fixture to esbuild as an // entry-point path. "entry": [ "eslint.config.mjs", "test/shimmer-text-bundle.mjs", "test/shimmer-text-fixture.tsx" ], "project": ["**/*.{ts,tsx}"] }, "packages/views": { // Passed to vitest as `--reporter=./test/timing-reporter.ts` when // refreshing shard timings (packages/views/test/README.md). "entry": ["test/timing-reporter.ts"], "project": ["**/*.{ts,tsx}"] }, "packages/core": { "entry": ["eslint.config.mjs"], "project": ["**/*.{ts,tsx}"] }, "packages/plugin-sdk": { "entry": ["eslint.config.mjs"] }, "packages/eslint-config": {}, "packages/tsconfig": {}, "packages/vitest-setup": {} } }