Implement the 007-add-knip feature that adds Knip unused code detection to the quality gate and as a standalone command

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Lukas
2026-03-05 12:24:27 +01:00
parent 0bbd6f27f9
commit c4a90c9982
11 changed files with 965 additions and 16 deletions

View File

@@ -5,7 +5,8 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
## Commands
```bash
pnpm check # Merge gate — must pass before every commit (format + lint + typecheck + test)
pnpm check # Merge gate — must pass before every commit (knip + format + lint + typecheck + test)
pnpm knip # Unused code detection (Knip)
pnpm test # Run all tests (Vitest)
pnpm test:watch # Tests in watch mode
pnpm typecheck # tsc --build (project references)
@@ -54,6 +55,7 @@ The constitution (`.specify/memory/constitution.md`) governs all feature work:
- TypeScript 5.x (strict mode, verbatimModuleSyntax) + React 19, Vite (003-remove-combatant)
- In-memory React state (local-first, single-user MVP) (003-remove-combatant)
- TypeScript 5.x (project), Go binary via npm (Lefthook) + `lefthook` (npm devDependency) (006-pre-commit-gate)
- TypeScript 5.x (strict mode, verbatimModuleSyntax) + Knip v5 (new), Biome 2.0, Vitest, Vite 6, React 19 (007-add-knip)
## Recent Changes
- 003-remove-combatant: Added TypeScript 5.x (strict mode, verbatimModuleSyntax) + React 19, Vite

10
knip.json Normal file
View File

@@ -0,0 +1,10 @@
{
"$schema": "https://unpkg.com/knip@5/schema.json",
"workspaces": {
".": {
"entry": ["scripts/*.mjs"]
},
"packages/*": {},
"apps/*": {}
}
}

View File

@@ -3,6 +3,7 @@
"packageManager": "pnpm@10.6.0",
"devDependencies": {
"@biomejs/biome": "2.0.0",
"knip": "^5.85.0",
"lefthook": "^1.11.0",
"typescript": "^5.8.0",
"vitest": "^3.0.0"
@@ -16,6 +17,7 @@
"typecheck": "tsc --build",
"test": "vitest run",
"test:watch": "vitest",
"check": "biome check . && tsc --build && vitest run"
"knip": "knip",
"check": "knip && biome check . && tsc --build && vitest run"
}
}

506
pnpm-lock.yaml generated
View File

@@ -11,6 +11,9 @@ importers:
'@biomejs/biome':
specifier: 2.0.0
version: 2.0.0
knip:
specifier: ^5.85.0
version: 5.85.0(@types/node@25.3.3)(typescript@5.9.3)
lefthook:
specifier: ^1.11.0
version: 1.13.6
@@ -19,7 +22,7 @@ importers:
version: 5.9.3
vitest:
specifier: ^3.0.0
version: 3.2.4
version: 3.2.4(@types/node@25.3.3)(jiti@2.6.1)
apps/web:
dependencies:
@@ -44,10 +47,10 @@ importers:
version: 19.2.3(@types/react@19.2.14)
'@vitejs/plugin-react':
specifier: ^4.3.0
version: 4.7.0(vite@6.4.1)
version: 4.7.0(vite@6.4.1(@types/node@25.3.3)(jiti@2.6.1))
vite:
specifier: ^6.2.0
version: 6.4.1
version: 6.4.1(@types/node@25.3.3)(jiti@2.6.1)
packages/application:
dependencies:
@@ -195,6 +198,15 @@ packages:
cpu: [x64]
os: [win32]
'@emnapi/core@1.8.1':
resolution: {integrity: sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==}
'@emnapi/runtime@1.8.1':
resolution: {integrity: sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==}
'@emnapi/wasi-threads@1.1.0':
resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==}
'@esbuild/aix-ppc64@0.25.12':
resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==}
engines: {node: '>=18'}
@@ -367,6 +379,121 @@ packages:
'@jridgewell/trace-mapping@0.3.31':
resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
'@napi-rs/wasm-runtime@1.1.1':
resolution: {integrity: sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==}
'@nodelib/fs.scandir@2.1.5':
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
engines: {node: '>= 8'}
'@nodelib/fs.stat@2.0.5':
resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==}
engines: {node: '>= 8'}
'@nodelib/fs.walk@1.2.8':
resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
engines: {node: '>= 8'}
'@oxc-resolver/binding-android-arm-eabi@11.19.1':
resolution: {integrity: sha512-aUs47y+xyXHUKlbhqHUjBABjvycq6YSD7bpxSW7vplUmdzAlJ93yXY6ZR0c1o1x5A/QKbENCvs3+NlY8IpIVzg==}
cpu: [arm]
os: [android]
'@oxc-resolver/binding-android-arm64@11.19.1':
resolution: {integrity: sha512-oolbkRX+m7Pq2LNjr/kKgYeC7bRDMVTWPgxBGMjSpZi/+UskVo4jsMU3MLheZV55jL6c3rNelPl4oD60ggYmqA==}
cpu: [arm64]
os: [android]
'@oxc-resolver/binding-darwin-arm64@11.19.1':
resolution: {integrity: sha512-nUC6d2i3R5B12sUW4O646qD5cnMXf2oBGPLIIeaRfU9doJRORAbE2SGv4eW6rMqhD+G7nf2Y8TTJTLiiO3Q/dQ==}
cpu: [arm64]
os: [darwin]
'@oxc-resolver/binding-darwin-x64@11.19.1':
resolution: {integrity: sha512-cV50vE5+uAgNcFa3QY1JOeKDSkM/9ReIcc/9wn4TavhW/itkDGrXhw9jaKnkQnGbjJ198Yh5nbX/Gr2mr4Z5jQ==}
cpu: [x64]
os: [darwin]
'@oxc-resolver/binding-freebsd-x64@11.19.1':
resolution: {integrity: sha512-xZOQiYGFxtk48PBKff+Zwoym7ScPAIVp4c14lfLxizO2LTTTJe5sx9vQNGrBymrf/vatSPNMD4FgsaaRigPkqw==}
cpu: [x64]
os: [freebsd]
'@oxc-resolver/binding-linux-arm-gnueabihf@11.19.1':
resolution: {integrity: sha512-lXZYWAC6kaGe/ky2su94e9jN9t6M0/6c+GrSlCqL//XO1cxi5lpAhnJYdyrKfm0ZEr/c7RNyAx3P7FSBcBd5+A==}
cpu: [arm]
os: [linux]
'@oxc-resolver/binding-linux-arm-musleabihf@11.19.1':
resolution: {integrity: sha512-veG1kKsuK5+t2IsO9q0DErYVSw2azvCVvWHnfTOS73WE0STdLLB7Q1bB9WR+yHPQM76ASkFyRbogWo1GR1+WbQ==}
cpu: [arm]
os: [linux]
'@oxc-resolver/binding-linux-arm64-gnu@11.19.1':
resolution: {integrity: sha512-heV2+jmXyYnUrpUXSPugqWDRpnsQcDm2AX4wzTuvgdlZfoNYO0O3W2AVpJYaDn9AG4JdM6Kxom8+foE7/BcSig==}
cpu: [arm64]
os: [linux]
'@oxc-resolver/binding-linux-arm64-musl@11.19.1':
resolution: {integrity: sha512-jvo2Pjs1c9KPxMuMPIeQsgu0mOJF9rEb3y3TdpsrqwxRM+AN6/nDDwv45n5ZrUnQMsdBy5gIabioMKnQfWo9ew==}
cpu: [arm64]
os: [linux]
'@oxc-resolver/binding-linux-ppc64-gnu@11.19.1':
resolution: {integrity: sha512-vLmdNxWCdN7Uo5suays6A/+ywBby2PWBBPXctWPg5V0+eVuzsJxgAn6MMB4mPlshskYbppjpN2Zg83ArHze9gQ==}
cpu: [ppc64]
os: [linux]
'@oxc-resolver/binding-linux-riscv64-gnu@11.19.1':
resolution: {integrity: sha512-/b+WgR+VTSBxzgOhDO7TlMXC1ufPIMR6Vj1zN+/x+MnyXGW7prTLzU9eW85Aj7Th7CCEG9ArCbTeqxCzFWdg2w==}
cpu: [riscv64]
os: [linux]
'@oxc-resolver/binding-linux-riscv64-musl@11.19.1':
resolution: {integrity: sha512-YlRdeWb9j42p29ROh+h4eg/OQ3dTJlpHSa+84pUM9+p6i3djtPz1q55yLJhgW9XfDch7FN1pQ/Vd6YP+xfRIuw==}
cpu: [riscv64]
os: [linux]
'@oxc-resolver/binding-linux-s390x-gnu@11.19.1':
resolution: {integrity: sha512-EDpafVOQWF8/MJynsjOGFThcqhRHy417sRyLfQmeiamJ8qVhSKAn2Dn2VVKUGCjVB9C46VGjhNo7nOPUi1x6uA==}
cpu: [s390x]
os: [linux]
'@oxc-resolver/binding-linux-x64-gnu@11.19.1':
resolution: {integrity: sha512-NxjZe+rqWhr+RT8/Ik+5ptA3oz7tUw361Wa5RWQXKnfqwSSHdHyrw6IdcTfYuml9dM856AlKWZIUXDmA9kkiBQ==}
cpu: [x64]
os: [linux]
'@oxc-resolver/binding-linux-x64-musl@11.19.1':
resolution: {integrity: sha512-cM/hQwsO3ReJg5kR+SpI69DMfvNCp+A/eVR4b4YClE5bVZwz8rh2Nh05InhwI5HR/9cArbEkzMjcKgTHS6UaNw==}
cpu: [x64]
os: [linux]
'@oxc-resolver/binding-openharmony-arm64@11.19.1':
resolution: {integrity: sha512-QF080IowFB0+9Rh6RcD19bdgh49BpQHUW5TajG1qvWHvmrQznTZZjYlgE2ltLXyKY+qs4F/v5xuX1XS7Is+3qA==}
cpu: [arm64]
os: [openharmony]
'@oxc-resolver/binding-wasm32-wasi@11.19.1':
resolution: {integrity: sha512-w8UCKhX826cP/ZLokXDS6+milN8y4X7zidsAttEdWlVoamTNf6lhBJldaWr3ukTDiye7s4HRcuPEPOXNC432Vg==}
engines: {node: '>=14.0.0'}
cpu: [wasm32]
'@oxc-resolver/binding-win32-arm64-msvc@11.19.1':
resolution: {integrity: sha512-nJ4AsUVZrVKwnU/QRdzPCCrO0TrabBqgJ8pJhXITdZGYOV28TIYystV1VFLbQ7DtAcaBHpocT5/ZJnF78YJPtQ==}
cpu: [arm64]
os: [win32]
'@oxc-resolver/binding-win32-ia32-msvc@11.19.1':
resolution: {integrity: sha512-EW+ND5q2Tl+a3pH81l1QbfgbF3HmqgwLfDfVithRFheac8OTcnbXt/JxqD2GbDkb7xYEqy1zNaVFRr3oeG8npA==}
cpu: [ia32]
os: [win32]
'@oxc-resolver/binding-win32-x64-msvc@11.19.1':
resolution: {integrity: sha512-6hIU3RQu45B+VNTY4Ru8ppFwjVS/S5qwYyGhBotmjxfEKk41I2DlGtRfGJndZ5+6lneE2pwloqunlOyZuX/XAw==}
cpu: [x64]
os: [win32]
'@rolldown/pluginutils@1.0.0-beta.27':
resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==}
@@ -495,6 +622,9 @@ packages:
cpu: [x64]
os: [win32]
'@tybys/wasm-util@0.10.1':
resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==}
'@types/babel__core@7.20.5':
resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==}
@@ -516,6 +646,9 @@ packages:
'@types/estree@1.0.8':
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
'@types/node@25.3.3':
resolution: {integrity: sha512-DpzbrH7wIcBaJibpKo9nnSQL0MTRdnWttGyE5haGwK86xgMOkFLp7vEyfQPGLOJh5wNYiJ3V9PmUMDhV9u8kkQ==}
'@types/react-dom@19.2.3':
resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==}
peerDependencies:
@@ -559,6 +692,9 @@ packages:
'@vitest/utils@3.2.4':
resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==}
argparse@2.0.1:
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
assertion-error@2.0.1:
resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
engines: {node: '>=12'}
@@ -568,6 +704,10 @@ packages:
engines: {node: '>=6.0.0'}
hasBin: true
braces@3.0.3:
resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
engines: {node: '>=8'}
browserslist@4.28.1:
resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==}
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
@@ -629,6 +769,16 @@ packages:
resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==}
engines: {node: '>=12.0.0'}
fast-glob@3.3.3:
resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==}
engines: {node: '>=8.6.0'}
fastq@1.20.1:
resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==}
fd-package-json@2.0.0:
resolution: {integrity: sha512-jKmm9YtsNXN789RS/0mSzOC1NUq9mkVd65vbSSVsKdjGvYXBuE4oWe2QOEoFeRmJg+lPuZxpmrfFclNhoRMneQ==}
fdir@6.5.0:
resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
engines: {node: '>=12.0.0'}
@@ -638,6 +788,15 @@ packages:
picomatch:
optional: true
fill-range@7.1.1:
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
engines: {node: '>=8'}
formatly@0.3.0:
resolution: {integrity: sha512-9XNj/o4wrRFyhSMJOvsuyMwy8aUfBaZ1VrqHVfohyXf0Sw0e+yfKG+xZaY3arGCOMdwFsqObtzVOc1gU9KiT9w==}
engines: {node: '>=18.3.0'}
hasBin: true
fsevents@2.3.3:
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
@@ -647,12 +806,36 @@ packages:
resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
engines: {node: '>=6.9.0'}
glob-parent@5.1.2:
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
engines: {node: '>= 6'}
is-extglob@2.1.1:
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
engines: {node: '>=0.10.0'}
is-glob@4.0.3:
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
engines: {node: '>=0.10.0'}
is-number@7.0.0:
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
engines: {node: '>=0.12.0'}
jiti@2.6.1:
resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==}
hasBin: true
js-tokens@4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
js-tokens@9.0.1:
resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==}
js-yaml@4.1.1:
resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==}
hasBin: true
jsesc@3.1.0:
resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==}
engines: {node: '>=6'}
@@ -663,6 +846,14 @@ packages:
engines: {node: '>=6'}
hasBin: true
knip@5.85.0:
resolution: {integrity: sha512-V2kyON+DZiYdNNdY6GALseiNCwX7dYdpz9Pv85AUn69Gk0UKCts+glOKWfe5KmaMByRjM9q17Mzj/KinTVOyxg==}
engines: {node: '>=18.18.0'}
hasBin: true
peerDependencies:
'@types/node': '>=18'
typescript: '>=5.0.4 <7'
lefthook-darwin-arm64@1.13.6:
resolution: {integrity: sha512-m6Lb77VGc84/Qo21Lhq576pEvcgFCnvloEiP02HbAHcIXD0RTLy9u2yAInrixqZeaz13HYtdDaI7OBYAAdVt8A==}
cpu: [arm64]
@@ -726,6 +917,17 @@ packages:
magic-string@0.30.21:
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
merge2@1.4.1:
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
engines: {node: '>= 8'}
micromatch@4.0.8:
resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
engines: {node: '>=8.6'}
minimist@1.2.8:
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
ms@2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
@@ -737,6 +939,9 @@ packages:
node-releases@2.0.27:
resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==}
oxc-resolver@11.19.1:
resolution: {integrity: sha512-qE/CIg/spwrTBFt5aKmwe3ifeDdLfA2NESN30E42X/lII5ClF8V7Wt6WIJhcGZjp0/Q+nQ+9vgxGk//xZNX2hg==}
pathe@2.0.3:
resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
@@ -747,6 +952,10 @@ packages:
picocolors@1.1.1:
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
picomatch@2.3.1:
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
engines: {node: '>=8.6'}
picomatch@4.0.3:
resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
engines: {node: '>=12'}
@@ -755,6 +964,9 @@ packages:
resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==}
engines: {node: ^10 || ^12 || >=14}
queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
react-dom@19.2.4:
resolution: {integrity: sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==}
peerDependencies:
@@ -768,11 +980,18 @@ packages:
resolution: {integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==}
engines: {node: '>=0.10.0'}
reusify@1.1.0:
resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==}
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
rollup@4.59.0:
resolution: {integrity: sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==}
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
hasBin: true
run-parallel@1.2.0:
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
scheduler@0.27.0:
resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==}
@@ -783,6 +1002,10 @@ packages:
siginfo@2.0.0:
resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
smol-toml@1.6.0:
resolution: {integrity: sha512-4zemZi0HvTnYwLfrpk/CF9LOd9Lt87kAt50GnqhMpyF9U3poDAP2+iukq2bZsO/ufegbYehBkqINbsWxj4l4cw==}
engines: {node: '>= 18'}
source-map-js@1.2.1:
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
engines: {node: '>=0.10.0'}
@@ -793,6 +1016,10 @@ packages:
std-env@3.10.0:
resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==}
strip-json-comments@5.0.3:
resolution: {integrity: sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==}
engines: {node: '>=14.16'}
strip-literal@3.1.0:
resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==}
@@ -818,11 +1045,21 @@ packages:
resolution: {integrity: sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==}
engines: {node: '>=14.0.0'}
to-regex-range@5.0.1:
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
engines: {node: '>=8.0'}
tslib@2.8.1:
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
typescript@5.9.3:
resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
engines: {node: '>=14.17'}
hasBin: true
undici-types@7.18.2:
resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==}
update-browserslist-db@1.2.3:
resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==}
hasBin: true
@@ -902,6 +1139,10 @@ packages:
jsdom:
optional: true
walk-up-path@4.0.0:
resolution: {integrity: sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A==}
engines: {node: 20 || >=22}
why-is-node-running@2.3.0:
resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==}
engines: {node: '>=8'}
@@ -910,6 +1151,9 @@ packages:
yallist@3.1.1:
resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
zod@4.3.6:
resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==}
snapshots:
'@babel/code-frame@7.29.0':
@@ -1059,6 +1303,22 @@ snapshots:
'@biomejs/cli-win32-x64@2.0.0':
optional: true
'@emnapi/core@1.8.1':
dependencies:
'@emnapi/wasi-threads': 1.1.0
tslib: 2.8.1
optional: true
'@emnapi/runtime@1.8.1':
dependencies:
tslib: 2.8.1
optional: true
'@emnapi/wasi-threads@1.1.0':
dependencies:
tslib: 2.8.1
optional: true
'@esbuild/aix-ppc64@0.25.12':
optional: true
@@ -1156,6 +1416,87 @@ snapshots:
'@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.5
'@napi-rs/wasm-runtime@1.1.1':
dependencies:
'@emnapi/core': 1.8.1
'@emnapi/runtime': 1.8.1
'@tybys/wasm-util': 0.10.1
optional: true
'@nodelib/fs.scandir@2.1.5':
dependencies:
'@nodelib/fs.stat': 2.0.5
run-parallel: 1.2.0
'@nodelib/fs.stat@2.0.5': {}
'@nodelib/fs.walk@1.2.8':
dependencies:
'@nodelib/fs.scandir': 2.1.5
fastq: 1.20.1
'@oxc-resolver/binding-android-arm-eabi@11.19.1':
optional: true
'@oxc-resolver/binding-android-arm64@11.19.1':
optional: true
'@oxc-resolver/binding-darwin-arm64@11.19.1':
optional: true
'@oxc-resolver/binding-darwin-x64@11.19.1':
optional: true
'@oxc-resolver/binding-freebsd-x64@11.19.1':
optional: true
'@oxc-resolver/binding-linux-arm-gnueabihf@11.19.1':
optional: true
'@oxc-resolver/binding-linux-arm-musleabihf@11.19.1':
optional: true
'@oxc-resolver/binding-linux-arm64-gnu@11.19.1':
optional: true
'@oxc-resolver/binding-linux-arm64-musl@11.19.1':
optional: true
'@oxc-resolver/binding-linux-ppc64-gnu@11.19.1':
optional: true
'@oxc-resolver/binding-linux-riscv64-gnu@11.19.1':
optional: true
'@oxc-resolver/binding-linux-riscv64-musl@11.19.1':
optional: true
'@oxc-resolver/binding-linux-s390x-gnu@11.19.1':
optional: true
'@oxc-resolver/binding-linux-x64-gnu@11.19.1':
optional: true
'@oxc-resolver/binding-linux-x64-musl@11.19.1':
optional: true
'@oxc-resolver/binding-openharmony-arm64@11.19.1':
optional: true
'@oxc-resolver/binding-wasm32-wasi@11.19.1':
dependencies:
'@napi-rs/wasm-runtime': 1.1.1
optional: true
'@oxc-resolver/binding-win32-arm64-msvc@11.19.1':
optional: true
'@oxc-resolver/binding-win32-ia32-msvc@11.19.1':
optional: true
'@oxc-resolver/binding-win32-x64-msvc@11.19.1':
optional: true
'@rolldown/pluginutils@1.0.0-beta.27': {}
'@rollup/rollup-android-arm-eabi@4.59.0':
@@ -1233,6 +1574,11 @@ snapshots:
'@rollup/rollup-win32-x64-msvc@4.59.0':
optional: true
'@tybys/wasm-util@0.10.1':
dependencies:
tslib: 2.8.1
optional: true
'@types/babel__core@7.20.5':
dependencies:
'@babel/parser': 7.29.0
@@ -1263,6 +1609,10 @@ snapshots:
'@types/estree@1.0.8': {}
'@types/node@25.3.3':
dependencies:
undici-types: 7.18.2
'@types/react-dom@19.2.3(@types/react@19.2.14)':
dependencies:
'@types/react': 19.2.14
@@ -1271,7 +1621,7 @@ snapshots:
dependencies:
csstype: 3.2.3
'@vitejs/plugin-react@4.7.0(vite@6.4.1)':
'@vitejs/plugin-react@4.7.0(vite@6.4.1(@types/node@25.3.3)(jiti@2.6.1))':
dependencies:
'@babel/core': 7.29.0
'@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.29.0)
@@ -1279,7 +1629,7 @@ snapshots:
'@rolldown/pluginutils': 1.0.0-beta.27
'@types/babel__core': 7.20.5
react-refresh: 0.17.0
vite: 6.4.1
vite: 6.4.1(@types/node@25.3.3)(jiti@2.6.1)
transitivePeerDependencies:
- supports-color
@@ -1291,13 +1641,13 @@ snapshots:
chai: 5.3.3
tinyrainbow: 2.0.0
'@vitest/mocker@3.2.4(vite@6.4.1)':
'@vitest/mocker@3.2.4(vite@6.4.1(@types/node@25.3.3)(jiti@2.6.1))':
dependencies:
'@vitest/spy': 3.2.4
estree-walker: 3.0.3
magic-string: 0.30.21
optionalDependencies:
vite: 6.4.1
vite: 6.4.1(@types/node@25.3.3)(jiti@2.6.1)
'@vitest/pretty-format@3.2.4':
dependencies:
@@ -1325,10 +1675,16 @@ snapshots:
loupe: 3.2.1
tinyrainbow: 2.0.0
argparse@2.0.1: {}
assertion-error@2.0.1: {}
baseline-browser-mapping@2.10.0: {}
braces@3.0.3:
dependencies:
fill-range: 7.1.1
browserslist@4.28.1:
dependencies:
baseline-browser-mapping: 2.10.0
@@ -1402,23 +1758,82 @@ snapshots:
expect-type@1.3.0: {}
fast-glob@3.3.3:
dependencies:
'@nodelib/fs.stat': 2.0.5
'@nodelib/fs.walk': 1.2.8
glob-parent: 5.1.2
merge2: 1.4.1
micromatch: 4.0.8
fastq@1.20.1:
dependencies:
reusify: 1.1.0
fd-package-json@2.0.0:
dependencies:
walk-up-path: 4.0.0
fdir@6.5.0(picomatch@4.0.3):
optionalDependencies:
picomatch: 4.0.3
fill-range@7.1.1:
dependencies:
to-regex-range: 5.0.1
formatly@0.3.0:
dependencies:
fd-package-json: 2.0.0
fsevents@2.3.3:
optional: true
gensync@1.0.0-beta.2: {}
glob-parent@5.1.2:
dependencies:
is-glob: 4.0.3
is-extglob@2.1.1: {}
is-glob@4.0.3:
dependencies:
is-extglob: 2.1.1
is-number@7.0.0: {}
jiti@2.6.1: {}
js-tokens@4.0.0: {}
js-tokens@9.0.1: {}
js-yaml@4.1.1:
dependencies:
argparse: 2.0.1
jsesc@3.1.0: {}
json5@2.2.3: {}
knip@5.85.0(@types/node@25.3.3)(typescript@5.9.3):
dependencies:
'@nodelib/fs.walk': 1.2.8
'@types/node': 25.3.3
fast-glob: 3.3.3
formatly: 0.3.0
jiti: 2.6.1
js-yaml: 4.1.1
minimist: 1.2.8
oxc-resolver: 11.19.1
picocolors: 1.1.1
picomatch: 4.0.3
smol-toml: 1.6.0
strip-json-comments: 5.0.3
typescript: 5.9.3
zod: 4.3.6
lefthook-darwin-arm64@1.13.6:
optional: true
@@ -1472,18 +1887,52 @@ snapshots:
dependencies:
'@jridgewell/sourcemap-codec': 1.5.5
merge2@1.4.1: {}
micromatch@4.0.8:
dependencies:
braces: 3.0.3
picomatch: 2.3.1
minimist@1.2.8: {}
ms@2.1.3: {}
nanoid@3.3.11: {}
node-releases@2.0.27: {}
oxc-resolver@11.19.1:
optionalDependencies:
'@oxc-resolver/binding-android-arm-eabi': 11.19.1
'@oxc-resolver/binding-android-arm64': 11.19.1
'@oxc-resolver/binding-darwin-arm64': 11.19.1
'@oxc-resolver/binding-darwin-x64': 11.19.1
'@oxc-resolver/binding-freebsd-x64': 11.19.1
'@oxc-resolver/binding-linux-arm-gnueabihf': 11.19.1
'@oxc-resolver/binding-linux-arm-musleabihf': 11.19.1
'@oxc-resolver/binding-linux-arm64-gnu': 11.19.1
'@oxc-resolver/binding-linux-arm64-musl': 11.19.1
'@oxc-resolver/binding-linux-ppc64-gnu': 11.19.1
'@oxc-resolver/binding-linux-riscv64-gnu': 11.19.1
'@oxc-resolver/binding-linux-riscv64-musl': 11.19.1
'@oxc-resolver/binding-linux-s390x-gnu': 11.19.1
'@oxc-resolver/binding-linux-x64-gnu': 11.19.1
'@oxc-resolver/binding-linux-x64-musl': 11.19.1
'@oxc-resolver/binding-openharmony-arm64': 11.19.1
'@oxc-resolver/binding-wasm32-wasi': 11.19.1
'@oxc-resolver/binding-win32-arm64-msvc': 11.19.1
'@oxc-resolver/binding-win32-ia32-msvc': 11.19.1
'@oxc-resolver/binding-win32-x64-msvc': 11.19.1
pathe@2.0.3: {}
pathval@2.0.1: {}
picocolors@1.1.1: {}
picomatch@2.3.1: {}
picomatch@4.0.3: {}
postcss@8.5.8:
@@ -1492,6 +1941,8 @@ snapshots:
picocolors: 1.1.1
source-map-js: 1.2.1
queue-microtask@1.2.3: {}
react-dom@19.2.4(react@19.2.4):
dependencies:
react: 19.2.4
@@ -1501,6 +1952,8 @@ snapshots:
react@19.2.4: {}
reusify@1.1.0: {}
rollup@4.59.0:
dependencies:
'@types/estree': 1.0.8
@@ -1532,18 +1985,26 @@ snapshots:
'@rollup/rollup-win32-x64-msvc': 4.59.0
fsevents: 2.3.3
run-parallel@1.2.0:
dependencies:
queue-microtask: 1.2.3
scheduler@0.27.0: {}
semver@6.3.1: {}
siginfo@2.0.0: {}
smol-toml@1.6.0: {}
source-map-js@1.2.1: {}
stackback@0.0.2: {}
std-env@3.10.0: {}
strip-json-comments@5.0.3: {}
strip-literal@3.1.0:
dependencies:
js-tokens: 9.0.1
@@ -1563,21 +2024,30 @@ snapshots:
tinyspy@4.0.4: {}
to-regex-range@5.0.1:
dependencies:
is-number: 7.0.0
tslib@2.8.1:
optional: true
typescript@5.9.3: {}
undici-types@7.18.2: {}
update-browserslist-db@1.2.3(browserslist@4.28.1):
dependencies:
browserslist: 4.28.1
escalade: 3.2.0
picocolors: 1.1.1
vite-node@3.2.4:
vite-node@3.2.4(@types/node@25.3.3)(jiti@2.6.1):
dependencies:
cac: 6.7.14
debug: 4.4.3
es-module-lexer: 1.7.0
pathe: 2.0.3
vite: 6.4.1
vite: 6.4.1(@types/node@25.3.3)(jiti@2.6.1)
transitivePeerDependencies:
- '@types/node'
- jiti
@@ -1592,7 +2062,7 @@ snapshots:
- tsx
- yaml
vite@6.4.1:
vite@6.4.1(@types/node@25.3.3)(jiti@2.6.1):
dependencies:
esbuild: 0.25.12
fdir: 6.5.0(picomatch@4.0.3)
@@ -1601,13 +2071,15 @@ snapshots:
rollup: 4.59.0
tinyglobby: 0.2.15
optionalDependencies:
'@types/node': 25.3.3
fsevents: 2.3.3
jiti: 2.6.1
vitest@3.2.4:
vitest@3.2.4(@types/node@25.3.3)(jiti@2.6.1):
dependencies:
'@types/chai': 5.2.3
'@vitest/expect': 3.2.4
'@vitest/mocker': 3.2.4(vite@6.4.1)
'@vitest/mocker': 3.2.4(vite@6.4.1(@types/node@25.3.3)(jiti@2.6.1))
'@vitest/pretty-format': 3.2.4
'@vitest/runner': 3.2.4
'@vitest/snapshot': 3.2.4
@@ -1625,9 +2097,11 @@ snapshots:
tinyglobby: 0.2.15
tinypool: 1.1.1
tinyrainbow: 2.0.0
vite: 6.4.1
vite-node: 3.2.4
vite: 6.4.1(@types/node@25.3.3)(jiti@2.6.1)
vite-node: 3.2.4(@types/node@25.3.3)(jiti@2.6.1)
why-is-node-running: 2.3.0
optionalDependencies:
'@types/node': 25.3.3
transitivePeerDependencies:
- jiti
- less
@@ -1642,9 +2116,13 @@ snapshots:
- tsx
- yaml
walk-up-path@4.0.0: {}
why-is-node-running@2.3.0:
dependencies:
siginfo: 2.0.0
stackback: 0.0.2
yallist@3.1.1: {}
zod@4.3.6: {}

View File

@@ -0,0 +1,34 @@
# Specification Quality Checklist: Add Knip Unused Code Detection
**Purpose**: Validate specification completeness and quality before proceeding to planning
**Created**: 2026-03-05
**Feature**: [spec.md](../spec.md)
## Content Quality
- [x] No implementation details (languages, frameworks, APIs)
- [x] Focused on user value and business needs
- [x] Written for non-technical stakeholders
- [x] All mandatory sections completed
## Requirement Completeness
- [x] No [NEEDS CLARIFICATION] markers remain
- [x] Requirements are testable and unambiguous
- [x] Success criteria are measurable
- [x] Success criteria are technology-agnostic (no implementation details)
- [x] All acceptance scenarios are defined
- [x] Edge cases are identified
- [x] Scope is clearly bounded
- [x] Dependencies and assumptions identified
## Feature Readiness
- [x] All functional requirements have clear acceptance criteria
- [x] User scenarios cover primary flows
- [x] Feature meets measurable outcomes defined in Success Criteria
- [x] No implementation details leak into specification
## Notes
- All items pass. Spec is ready for `/speckit.clarify` or `/speckit.plan`.

View File

@@ -0,0 +1,24 @@
# Data Model: Add Knip Unused Code Detection
**Date**: 2026-03-05
## Overview
This feature is a developer tooling integration. It introduces no domain entities, state transitions, or persistent data. The only artifacts are configuration files.
## Configuration Artifacts
### Knip Configuration (`knip.json`)
- **Purpose**: Defines workspace scope and any overrides for Knip's auto-detection.
- **Location**: Repository root.
- **Key fields**: `$schema`, `workspaces` (maps workspace glob patterns to per-workspace config).
### Root Package Scripts (modification)
- **Artifact**: `package.json` `scripts` field.
- **Change**: Add `knip` script; update `check` script to include Knip in the quality gate chain.
## No Domain Impact
This feature does not modify domain types, application use cases, or adapter code. It only adds a static analysis tool to the build/check pipeline.

128
specs/007-add-knip/plan.md Normal file
View File

@@ -0,0 +1,128 @@
# Implementation Plan: Add Knip Unused Code Detection
**Branch**: `007-add-knip` | **Date**: 2026-03-05 | **Spec**: [spec.md](./spec.md)
**Input**: Feature specification from `/specs/007-add-knip/spec.md`
## Summary
Add Knip v5 as a root devDependency to detect unused files, exports, dependencies, devDependencies, and types across the pnpm workspace. Configure it with a workspace-aware `knip.json`, expose a standalone `pnpm knip` command, and integrate it into the existing `pnpm check` quality gate so it runs on every commit via Lefthook.
## Technical Context
**Language/Version**: TypeScript 5.x (strict mode, verbatimModuleSyntax)
**Primary Dependencies**: Knip v5 (new), Biome 2.0, Vitest, Vite 6, React 19
**Storage**: N/A
**Testing**: Vitest (existing); manual verification of Knip output
**Target Platform**: Node.js (developer tooling)
**Project Type**: pnpm monorepo (packages/domain, packages/application, apps/web)
**Performance Goals**: N/A (developer-time static analysis)
**Constraints**: Must pass on the current codebase with zero false positives
**Scale/Scope**: 3 workspace packages
## Constitution Check
*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*
| Principle | Status | Notes |
|-----------|--------|-------|
| I. Deterministic Domain Core | PASS | No domain changes. |
| II. Layered Architecture | PASS | No layer changes; Knip is root-level tooling only. |
| III. Agent Boundary | PASS | No agent layer involved. |
| IV. Clarification-First | PASS | Feature is well-defined; no ambiguities. |
| V. Escalation Gates | PASS | Implementing per spec. |
| VI. MVP Baseline Language | PASS | No scope restrictions introduced. |
| VII. No Gameplay Rules | PASS | Tooling feature only. |
| Dev Workflow: Automated checks | PASS | Knip becomes part of the merge gate (`pnpm check`). |
**Post-design re-check**: All gates still pass. No design decisions impact domain, layers, or architectural boundaries.
## Project Structure
### Documentation (this feature)
```text
specs/007-add-knip/
├── spec.md
├── plan.md # This file
├── research.md
├── data-model.md
├── quickstart.md
└── checklists/
└── requirements.md
```
### Source Code (repository root)
```text
# Files modified
package.json # Add knip devDep, add "knip" script, update "check" script
# Files created
knip.json # Root-level Knip workspace configuration
```
**Structure Decision**: No new source directories. This feature only adds a config file and modifies the root `package.json` scripts. The existing monorepo structure (`packages/*`, `apps/*`) is referenced by Knip's workspace config but not changed.
## Implementation Details
### 1. Install Knip
Add `knip` as a root devDependency:
```bash
pnpm add -Dw knip
```
### 2. Create `knip.json`
Root-level configuration leveraging auto-detection:
```json
{
"$schema": "https://unpkg.com/knip@5/schema.json",
"entry": ["scripts/*.mjs"],
"workspaces": {
"packages/*": {},
"apps/*": {}
}
}
```
Knip auto-detects:
- pnpm workspace packages from `pnpm-workspace.yaml`
- Entry points from each `package.json` (`main`, `exports`, `types`)
- TypeScript config from `tsconfig.json` files (including project references)
- Vitest test patterns from `vitest.config.ts`
- Vite config and plugins from `vite.config.ts`
- Biome config from `biome.json`
### 3. Update `package.json` Scripts
Add standalone command and integrate into quality gate:
```json
{
"scripts": {
"knip": "knip",
"check": "knip && biome check . && tsc --build && vitest run"
}
}
```
Knip runs first because it's fast and catches structural issues before heavier checks.
### 4. Validate
1. Run `pnpm knip` — must pass clean on the current codebase (SC-002).
2. If false positives appear, tune `knip.json` with `ignore`, `ignoreDependencies`, or plugin-specific overrides.
3. Run `pnpm check` — full gate must pass (SC-001).
4. Introduce an intentional unused export → verify `pnpm knip` catches it (SC-001).
5. Remove the intentional unused export → verify clean again.
### 5. Update Agent Context
Run `.specify/scripts/bash/update-agent-context.sh claude` to register Knip as a project technology.
## Complexity Tracking
No constitution violations. No complexity justifications needed.

View File

@@ -0,0 +1,29 @@
# Quickstart: Add Knip Unused Code Detection
**Date**: 2026-03-05
## What This Feature Does
Adds Knip to the project to detect unused files, exports, dependencies, devDependencies, and types across the pnpm workspace. Enforces it as part of the `pnpm check` quality gate (which runs on every commit via Lefthook).
## Files Changed
| File | Change |
|------|--------|
| `package.json` | Add `knip` devDependency; add `knip` script; update `check` script |
| `knip.json` (new) | Workspace-aware Knip configuration |
## How to Use
```bash
# Run unused-code check standalone
pnpm knip
# Run full quality gate (now includes Knip)
pnpm check
```
## Verification
1. `pnpm check` passes on the current codebase (no false positives).
2. Add an unused export to any file → `pnpm knip` reports it → `pnpm check` fails.

View File

@@ -0,0 +1,41 @@
# Research: Add Knip Unused Code Detection
**Date**: 2026-03-05
## Decision 1: Knip Configuration Approach
**Decision**: Use workspace-aware `knip.json` at the repo root with minimal explicit configuration, relying on Knip's auto-detection for plugins and entry points.
**Rationale**: Knip v5 auto-detects pnpm workspaces from `pnpm-workspace.yaml` and enables plugins (Vite, Vitest, TypeScript, Biome) based on `package.json` dependencies. The project follows standard conventions, so auto-detection covers most cases. Explicit workspace entries in `knip.json` provide a safety net and clear documentation of scope.
**Alternatives considered**:
- Zero config (no `knip.json`): Works but less explicit; harder for contributors to understand what's scanned.
- Per-package configs: Unnecessary complexity; root-level workspace config covers the monorepo.
## Decision 2: Quality Gate Integration
**Decision**: Add `knip` as a separate script in root `package.json` and chain it into the existing `check` script.
**Rationale**: The current `check` script is `biome check . && tsc --build && vitest run`. Adding `knip` to this chain (e.g., `knip && biome check . && ...`) makes it part of the pre-commit gate via Lefthook without any Lefthook config changes. Running Knip first is efficient since it's fast and catches structural issues before heavier checks.
**Alternatives considered**:
- Separate Lefthook job: Adds config complexity; the existing single `pnpm check` job is cleaner.
- Only standalone command (not in gate): Doesn't enforce the quality bar on every commit.
## Decision 3: Handling the `scripts/` Directory
**Decision**: Configure Knip to recognize `scripts/check-layer-boundaries.mjs` as an entry point so it isn't flagged as unused.
**Rationale**: This script is imported by Vitest tests but lives outside the standard workspace packages. Knip needs to know it's intentionally referenced. The root workspace can include it as an entry pattern.
**Alternatives considered**:
- Ignoring the scripts directory entirely: Would miss actual unused scripts in the future.
## Decision 4: Knip Version
**Decision**: Install latest Knip v5 (`knip@5`).
**Rationale**: v5 is the current stable major version with full pnpm workspace support and 138+ built-in plugins.
**Alternatives considered**:
- Pinning exact version: Less flexible for patch updates; caret range (`^5`) is standard practice.

View File

@@ -0,0 +1,79 @@
# Feature Specification: Add Knip Unused Code Detection
**Feature Branch**: `007-add-knip`
**Created**: 2026-03-05
**Status**: Draft
**Input**: User description: "Add Knip to the project to detect unused files, exports, dependencies, devDependencies, and types across the pnpm workspace and enforce it as part of the quality gate."
## User Scenarios & Testing *(mandatory)*
### User Story 1 - Detect Unused Code on Commit (Priority: P1)
As a developer, I want unused files, exports, dependencies, devDependencies, and types to be automatically detected when I commit, so that dead code never accumulates in the codebase.
**Why this priority**: This is the core value proposition — catching unused code as part of the existing quality gate ensures every commit keeps the codebase clean without requiring manual effort.
**Independent Test**: Can be fully tested by introducing an unused export into any workspace package and running the quality gate; the gate should fail with a clear report identifying the unused export.
**Acceptance Scenarios**:
1. **Given** the quality gate is run, **When** all files, exports, dependencies, and types are in use, **Then** the unused-code check passes successfully.
2. **Given** a file contains an unused export, **When** the quality gate is run, **Then** the check fails and reports the specific unused export and its file location.
3. **Given** a workspace package lists a dependency that is never imported, **When** the quality gate is run, **Then** the check fails and reports the unused dependency and the package it belongs to.
4. **Given** a file exists that is not imported or referenced anywhere, **When** the quality gate is run, **Then** the check fails and reports the unused file.
5. **Given** a type or interface is exported but never imported elsewhere, **When** the quality gate is run, **Then** the check fails and reports the unused type.
---
### User Story 2 - Run Unused-Code Check Independently (Priority: P2)
As a developer, I want to run the unused-code detection as a standalone command, so that I can inspect and fix issues before committing.
**Why this priority**: Developers need a fast feedback loop to discover and address unused code during development, not just at commit time.
**Independent Test**: Can be tested by running a dedicated command from the workspace root and verifying it produces output listing any unused items found across all workspace packages.
**Acceptance Scenarios**:
1. **Given** the developer is at the workspace root, **When** they run the standalone unused-code command, **Then** it analyzes all workspace packages and reports results.
2. **Given** unused items exist across multiple workspace packages, **When** the standalone command is run, **Then** all unused items are reported with their package and file location.
---
### Edge Cases
- What happens when a file is only used as an entry point (e.g., `main` or `exports` field in `package.json`)? It must not be falsely reported as unused.
- What happens when test files import modules only used in tests? Test-only dependencies and test utilities must not be flagged.
- What happens when configuration files (e.g., `vite.config.ts`, `vitest.config.ts`) reference plugins or packages? These must not be flagged as unused.
- What happens when workspace packages cross-reference each other via the `workspace:*` protocol? Internal workspace dependencies must be recognized.
- What happens when a type is re-exported from a barrel file? The re-export chain must be traced correctly.
## Requirements *(mandatory)*
### Functional Requirements
- **FR-001**: The system MUST detect unused files across all workspace packages (`packages/domain`, `packages/application`, `apps/web`).
- **FR-002**: The system MUST detect unused exports (functions, constants, types, interfaces) across all workspace packages.
- **FR-003**: The system MUST detect unused `dependencies` and `devDependencies` listed in each workspace package's `package.json`.
- **FR-004**: The system MUST be integrated into the existing quality gate (`pnpm check`) so that any unused code causes the gate to fail.
- **FR-005**: The system MUST provide a standalone command runnable from the workspace root to check for unused code independently of the full quality gate.
- **FR-006**: The system MUST correctly recognize entry points defined in each package's `package.json` (`main`, `exports`, `types` fields) and not flag them as unused.
- **FR-007**: The system MUST correctly handle pnpm workspace cross-references (`workspace:*` protocol) and not flag internal workspace dependencies as unused.
- **FR-008**: The system MUST correctly recognize test file patterns and not flag test-only utilities or test dependencies as unused when they are consumed by tests.
- **FR-009**: The system MUST correctly recognize configuration files and their plugin/dependency references.
## Success Criteria *(mandatory)*
### Measurable Outcomes
- **SC-001**: The quality gate fails when any unused file, export, dependency, or type is present, with zero false negatives for straightforward unused items.
- **SC-002**: The quality gate passes on the current codebase without false positives (no legitimate code is flagged as unused).
- **SC-003**: A developer can run the standalone unused-code check and receive results covering all workspace packages.
- **SC-004**: The unused-code report identifies the specific item (file, export name, dependency name) and its location (package and file path) for each finding.
## Assumptions
- Knip is the chosen tool for unused-code detection as specified by the user.
- The tool will be added as a root-level development dependency since it analyzes the entire workspace.
- Knip's built-in support for pnpm workspaces, TypeScript, Vitest, React, and Vite will handle most configuration automatically with minimal manual setup.
- The existing Lefthook pre-commit hook runs `pnpm check`, so adding the unused-code check to `pnpm check` automatically enforces it on every commit.

122
specs/007-add-knip/tasks.md Normal file
View File

@@ -0,0 +1,122 @@
# Tasks: Add Knip Unused Code Detection
**Input**: Design documents from `/specs/007-add-knip/`
**Prerequisites**: plan.md (required), spec.md (required), research.md, data-model.md, quickstart.md
**Tests**: No automated test tasks — this is a tooling feature validated by running `pnpm knip` and `pnpm check`.
**Organization**: Tasks follow the two user stories (US1: quality gate enforcement, US2: standalone command).
## Format: `[ID] [P?] [Story] Description`
- **[P]**: Can run in parallel (different files, no dependencies)
- **[Story]**: Which user story this task belongs to (e.g., US1, US2)
- Include exact file paths in descriptions
## Phase 1: Setup
**Purpose**: Install Knip and create configuration
- [x] T001 Install Knip v5 as a root devDependency via `pnpm add -Dw knip`
- [x] T002 Create `knip.json` at the repository root with workspace-aware configuration covering `packages/*` and `apps/*`, including `$schema` for editor support
---
## Phase 2: Foundational (Blocking Prerequisites)
**Purpose**: Ensure Knip passes cleanly on the current codebase before integrating into the gate
**CRITICAL**: Must resolve all false positives before wiring into the quality gate
- [x] T003 Run `pnpm knip` against the current codebase and capture output
- [x] T004 If false positives are reported, tune `knip.json` with `ignore`, `ignoreDependencies`, `entry`, or plugin-specific overrides until the codebase passes cleanly (FR-006 through FR-009)
**Checkpoint**: `pnpm knip` exits with code 0 on the current codebase (SC-002)
---
## Phase 3: User Story 1 - Detect Unused Code on Commit (Priority: P1)
**Goal**: Integrate Knip into the `pnpm check` quality gate so unused code is caught on every commit via Lefthook.
**Independent Test**: Introduce an unused export in any workspace package, run `pnpm check`, and confirm it fails with a clear report. Remove the unused export and confirm `pnpm check` passes.
### Implementation for User Story 1
- [x] T005 [US1] Update the `check` script in `package.json` to prepend `knip &&` before `biome check .` so unused code is checked as part of the quality gate
- [x] T006 [US1] Run `pnpm check` end-to-end and verify it passes on the current codebase (SC-001, SC-002)
- [x] T007 [US1] Manually verify detection: (a) add a temporary unused export to `packages/domain/src/index.ts`, run `pnpm check`, confirm it fails with a report identifying the unused export and its file location (SC-001, SC-004), then remove the temporary change; (b) verify that exports re-exported through barrel files (e.g., `index.ts`) are correctly traced and not falsely flagged
**Checkpoint**: Quality gate enforces unused-code detection on every commit. US1 acceptance scenarios 15 are satisfied.
---
## Phase 4: User Story 2 - Run Unused-Code Check Independently (Priority: P2)
**Goal**: Provide a standalone `pnpm knip` command developers can run without the full quality gate.
**Independent Test**: Run `pnpm knip` from the workspace root and verify it analyzes all three workspace packages and reports results.
### Implementation for User Story 2
- [x] T008 [US2] Add a `"knip": "knip"` script to `package.json` so developers can run `pnpm knip` independently
- [x] T009 [US2] Verify `pnpm knip` runs from the workspace root and reports results covering all workspace packages (`packages/domain`, `packages/application`, `apps/web`) (SC-003, SC-004)
**Checkpoint**: Developers can run `pnpm knip` standalone. US2 acceptance scenarios 12 are satisfied.
---
## Phase 5: Polish & Cross-Cutting Concerns
**Purpose**: Final validation and documentation
- [x] T010 Run quickstart.md validation: confirm `pnpm knip` and `pnpm check` both work as documented
- [x] T011 Update CLAUDE.md commands section if `pnpm knip` should be listed as a project command
---
## Dependencies & Execution Order
### Phase Dependencies
- **Setup (Phase 1)**: No dependencies — start immediately
- **Foundational (Phase 2)**: Depends on Phase 1 (T001, T002 must complete first)
- **User Story 1 (Phase 3)**: Depends on Phase 2 (clean Knip pass required before wiring into gate)
- **User Story 2 (Phase 4)**: Depends on Phase 3 (T008 modifies the same `package.json` as T005, so must follow it)
- **Polish (Phase 5)**: Depends on Phases 3 and 4
### User Story Dependencies
- **User Story 1 (P1)**: Depends on Foundational phase — needs clean codebase pass before gate integration
- **User Story 2 (P2)**: Depends on US1 — T008 modifies the same `package.json` as T005, so must execute after it
### Parallel Opportunities
- T001 and T002 are sequential (T002 needs Knip installed for schema validation)
- T008 (US2) modifies `package.json` (same as T005), so execute sequentially after T005
---
## Implementation Strategy
### MVP First (User Story 1 Only)
1. Complete Phase 1: Install Knip, create config
2. Complete Phase 2: Ensure clean pass on current codebase
3. Complete Phase 3: Wire into quality gate
4. **STOP and VALIDATE**: `pnpm check` passes clean; intentional unused code is caught
### Incremental Delivery
1. Phase 1 + Phase 2 → Knip works locally
2. Add US1 (Phase 3) → Quality gate enforced on every commit (MVP!)
3. Add US2 (Phase 4) → Standalone `pnpm knip` command available
4. Phase 5 → Documentation updated
---
## Notes
- All tasks modify root-level files only (no domain/application/adapter changes)
- The Lefthook pre-commit hook already runs `pnpm check`, so no Lefthook config changes needed
- If Knip reports false positives in Phase 2, the most common fixes are `ignoreDependencies` for tooling packages and `entry` patterns for non-standard entry points