@kb-labs/devkit 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (295) hide show
  1. package/.cursorrules +32 -0
  2. package/.github/CODEOWNERS +2 -0
  3. package/.github/actions/setup-node-pnpm/action.yml +47 -0
  4. package/.github/workflow-templates/ci.yml +13 -0
  5. package/.github/workflow-templates/drift-check.yml +10 -0
  6. package/.github/workflow-templates/profiles-validate.yml +16 -0
  7. package/.github/workflow-templates/release.yml +8 -0
  8. package/.github/workflows/ci-reusable.yml +131 -0
  9. package/.github/workflows/drift-check-reusable.yml +23 -0
  10. package/.github/workflows/fixtures.yml +74 -0
  11. package/.github/workflows/profiles-validate-reusable.yml +67 -0
  12. package/.github/workflows/release-reusable.yml +50 -0
  13. package/.vscode/settings.json +23 -0
  14. package/AGENTS.md +130 -0
  15. package/LICENSE +21 -0
  16. package/README.md +1542 -0
  17. package/agents/devkit-maintainer/context.globs +15 -0
  18. package/agents/devkit-maintainer/permissions.yml +17 -0
  19. package/agents/devkit-maintainer/prompt.md +28 -0
  20. package/agents/devkit-maintainer/runbook.md +31 -0
  21. package/agents/docs-crafter/prompt.md +24 -0
  22. package/agents/docs-crafter/runbook.md +18 -0
  23. package/agents/release-manager/context.globs +7 -0
  24. package/agents/release-manager/prompt.md +27 -0
  25. package/agents/release-manager/runbook.md +17 -0
  26. package/agents/test-generator/context.globs +7 -0
  27. package/agents/test-generator/prompt.md +27 -0
  28. package/agents/test-generator/runbook.md +18 -0
  29. package/bin/devkit-architecture.mjs +1225 -0
  30. package/bin/devkit-build-order.mjs +500 -0
  31. package/bin/devkit-check-build-readiness.mjs +222 -0
  32. package/bin/devkit-check-commands.mjs +394 -0
  33. package/bin/devkit-check-configs.mjs +461 -0
  34. package/bin/devkit-check-deprecated.mjs +532 -0
  35. package/bin/devkit-check-duplicates.mjs +431 -0
  36. package/bin/devkit-check-exports.mjs +561 -0
  37. package/bin/devkit-check-imports.mjs +712 -0
  38. package/bin/devkit-check-paths.mjs +670 -0
  39. package/bin/devkit-check-scripts.mjs +335 -0
  40. package/bin/devkit-check-structure.mjs +495 -0
  41. package/bin/devkit-check-types.mjs +450 -0
  42. package/bin/devkit-ci.mjs +261 -0
  43. package/bin/devkit-core-gate.mjs +225 -0
  44. package/bin/devkit-fix-deps.mjs +1169 -0
  45. package/bin/devkit-freshness.mjs +199 -0
  46. package/bin/devkit-health.mjs +489 -0
  47. package/bin/devkit-migrate-configs.mjs +370 -0
  48. package/bin/devkit-paths.mjs +192 -0
  49. package/bin/devkit-stats.mjs +453 -0
  50. package/bin/devkit-sync.mjs +12 -0
  51. package/bin/devkit-tsup-external.mjs +148 -0
  52. package/bin/devkit-types-audit.mjs +615 -0
  53. package/bin/devkit-types-order.mjs +637 -0
  54. package/bin/devkit-validate-naming.mjs +203 -0
  55. package/bin/devkit-visualize.mjs +452 -0
  56. package/bin/kb-devkit-qa-history.mjs +453 -0
  57. package/bin/kb-devkit-qa.mjs +915 -0
  58. package/eslint/node.js +134 -0
  59. package/eslint/react.js +260 -0
  60. package/package.json +186 -0
  61. package/prettier/index.json +10 -0
  62. package/sync/index.mjs +693 -0
  63. package/templates/configs/README.md +306 -0
  64. package/templates/configs/eslint.config.js +27 -0
  65. package/templates/configs/package.json.bin +25 -0
  66. package/templates/configs/package.json.lib +30 -0
  67. package/templates/configs/tsconfig.build.json +15 -0
  68. package/templates/configs/tsconfig.json +9 -0
  69. package/templates/configs/tsup.config.bin.ts +34 -0
  70. package/templates/configs/tsup.config.cli.ts +41 -0
  71. package/templates/configs/tsup.config.dual.ts +46 -0
  72. package/templates/configs/tsup.config.ts +36 -0
  73. package/templates/package-json/README.md +290 -0
  74. package/templates/package-json/package.json.bin.template +40 -0
  75. package/templates/package-json/package.json.template +45 -0
  76. package/tsconfig/base.json +21 -0
  77. package/tsconfig/cli.json +12 -0
  78. package/tsconfig/dist/__tests__/cache.spec.d.ts +6 -0
  79. package/tsconfig/dist/__tests__/cache.spec.d.ts.map +1 -0
  80. package/tsconfig/dist/__tests__/cache.spec.js +85 -0
  81. package/tsconfig/dist/__tests__/cache.spec.js.map +1 -0
  82. package/tsconfig/dist/__tests__/fs-atomic.spec.d.ts +6 -0
  83. package/tsconfig/dist/__tests__/fs-atomic.spec.d.ts.map +1 -0
  84. package/tsconfig/dist/__tests__/fs-atomic.spec.js +153 -0
  85. package/tsconfig/dist/__tests__/fs-atomic.spec.js.map +1 -0
  86. package/tsconfig/dist/__tests__/init-workspace.spec.d.ts +6 -0
  87. package/tsconfig/dist/__tests__/init-workspace.spec.d.ts.map +1 -0
  88. package/tsconfig/dist/__tests__/init-workspace.spec.js +99 -0
  89. package/tsconfig/dist/__tests__/init-workspace.spec.js.map +1 -0
  90. package/tsconfig/dist/__tests__/kb-error.spec.d.ts +6 -0
  91. package/tsconfig/dist/__tests__/kb-error.spec.d.ts.map +1 -0
  92. package/tsconfig/dist/__tests__/kb-error.spec.js +190 -0
  93. package/tsconfig/dist/__tests__/kb-error.spec.js.map +1 -0
  94. package/tsconfig/dist/__tests__/preset-lockfile.spec.d.ts +6 -0
  95. package/tsconfig/dist/__tests__/preset-lockfile.spec.d.ts.map +1 -0
  96. package/tsconfig/dist/__tests__/preset-lockfile.spec.js +142 -0
  97. package/tsconfig/dist/__tests__/preset-lockfile.spec.js.map +1 -0
  98. package/tsconfig/dist/__tests__/product-config-profiles.spec.d.ts +6 -0
  99. package/tsconfig/dist/__tests__/product-config-profiles.spec.d.ts.map +1 -0
  100. package/tsconfig/dist/__tests__/product-config-profiles.spec.js +100 -0
  101. package/tsconfig/dist/__tests__/product-config-profiles.spec.js.map +1 -0
  102. package/tsconfig/dist/__tests__/product-config.spec.d.ts +6 -0
  103. package/tsconfig/dist/__tests__/product-config.spec.d.ts.map +1 -0
  104. package/tsconfig/dist/__tests__/product-config.spec.js +298 -0
  105. package/tsconfig/dist/__tests__/product-config.spec.js.map +1 -0
  106. package/tsconfig/dist/__tests__/runtime.spec.d.ts +2 -0
  107. package/tsconfig/dist/__tests__/runtime.spec.d.ts.map +1 -0
  108. package/tsconfig/dist/__tests__/runtime.spec.js +127 -0
  109. package/tsconfig/dist/__tests__/runtime.spec.js.map +1 -0
  110. package/tsconfig/dist/__tests__/upsert-lockfile.spec.d.ts +6 -0
  111. package/tsconfig/dist/__tests__/upsert-lockfile.spec.d.ts.map +1 -0
  112. package/tsconfig/dist/__tests__/upsert-lockfile.spec.js +251 -0
  113. package/tsconfig/dist/__tests__/upsert-lockfile.spec.js.map +1 -0
  114. package/tsconfig/dist/__tests__/validate-config.spec.d.ts +2 -0
  115. package/tsconfig/dist/__tests__/validate-config.spec.d.ts.map +1 -0
  116. package/tsconfig/dist/__tests__/validate-config.spec.js +14 -0
  117. package/tsconfig/dist/__tests__/validate-config.spec.js.map +1 -0
  118. package/tsconfig/dist/api/init-workspace.d.ts +10 -0
  119. package/tsconfig/dist/api/init-workspace.d.ts.map +1 -0
  120. package/tsconfig/dist/api/init-workspace.js +191 -0
  121. package/tsconfig/dist/api/init-workspace.js.map +1 -0
  122. package/tsconfig/dist/api/product-config.d.ts +21 -0
  123. package/tsconfig/dist/api/product-config.d.ts.map +1 -0
  124. package/tsconfig/dist/api/product-config.js +192 -0
  125. package/tsconfig/dist/api/product-config.js.map +1 -0
  126. package/tsconfig/dist/api/read-config.d.ts +22 -0
  127. package/tsconfig/dist/api/read-config.d.ts.map +1 -0
  128. package/tsconfig/dist/api/read-config.js +105 -0
  129. package/tsconfig/dist/api/read-config.js.map +1 -0
  130. package/tsconfig/dist/api/upsert-lockfile.d.ts +10 -0
  131. package/tsconfig/dist/api/upsert-lockfile.d.ts.map +1 -0
  132. package/tsconfig/dist/api/upsert-lockfile.js +63 -0
  133. package/tsconfig/dist/api/upsert-lockfile.js.map +1 -0
  134. package/tsconfig/dist/cache/fs-cache.d.ts +38 -0
  135. package/tsconfig/dist/cache/fs-cache.d.ts.map +1 -0
  136. package/tsconfig/dist/cache/fs-cache.js +142 -0
  137. package/tsconfig/dist/cache/fs-cache.js.map +1 -0
  138. package/tsconfig/dist/errors/kb-error.d.ts +32 -0
  139. package/tsconfig/dist/errors/kb-error.d.ts.map +1 -0
  140. package/tsconfig/dist/errors/kb-error.js +54 -0
  141. package/tsconfig/dist/errors/kb-error.js.map +1 -0
  142. package/tsconfig/dist/fs/__tests__/fs.spec.d.ts +2 -0
  143. package/tsconfig/dist/fs/__tests__/fs.spec.d.ts.map +1 -0
  144. package/tsconfig/dist/fs/__tests__/fs.spec.js +22 -0
  145. package/tsconfig/dist/fs/__tests__/fs.spec.js.map +1 -0
  146. package/tsconfig/dist/fs/fs.d.ts +6 -0
  147. package/tsconfig/dist/fs/fs.d.ts.map +1 -0
  148. package/tsconfig/dist/fs/fs.js +12 -0
  149. package/tsconfig/dist/fs/fs.js.map +1 -0
  150. package/tsconfig/dist/fs/index.d.ts +2 -0
  151. package/tsconfig/dist/fs/index.d.ts.map +1 -0
  152. package/tsconfig/dist/fs/index.js +2 -0
  153. package/tsconfig/dist/fs/index.js.map +1 -0
  154. package/tsconfig/dist/hash/config-hash.d.ts +17 -0
  155. package/tsconfig/dist/hash/config-hash.d.ts.map +1 -0
  156. package/tsconfig/dist/hash/config-hash.js +55 -0
  157. package/tsconfig/dist/hash/config-hash.js.map +1 -0
  158. package/tsconfig/dist/index.d.ts +5 -0
  159. package/tsconfig/dist/index.d.ts.map +1 -0
  160. package/tsconfig/dist/index.js +5 -0
  161. package/tsconfig/dist/index.js.map +1 -0
  162. package/tsconfig/dist/lockfile/lockfile.d.ts +54 -0
  163. package/tsconfig/dist/lockfile/lockfile.d.ts.map +1 -0
  164. package/tsconfig/dist/lockfile/lockfile.js +141 -0
  165. package/tsconfig/dist/lockfile/lockfile.js.map +1 -0
  166. package/tsconfig/dist/logging/__tests__/logger.spec.d.ts +2 -0
  167. package/tsconfig/dist/logging/__tests__/logger.spec.d.ts.map +1 -0
  168. package/tsconfig/dist/logging/__tests__/logger.spec.js +65 -0
  169. package/tsconfig/dist/logging/__tests__/logger.spec.js.map +1 -0
  170. package/tsconfig/dist/logging/__tests__/redaction.spec.d.ts +2 -0
  171. package/tsconfig/dist/logging/__tests__/redaction.spec.d.ts.map +1 -0
  172. package/tsconfig/dist/logging/__tests__/redaction.spec.js +34 -0
  173. package/tsconfig/dist/logging/__tests__/redaction.spec.js.map +1 -0
  174. package/tsconfig/dist/logging/__tests__/sinks-and-env.spec.d.ts +2 -0
  175. package/tsconfig/dist/logging/__tests__/sinks-and-env.spec.d.ts.map +1 -0
  176. package/tsconfig/dist/logging/__tests__/sinks-and-env.spec.js +90 -0
  177. package/tsconfig/dist/logging/__tests__/sinks-and-env.spec.js.map +1 -0
  178. package/tsconfig/dist/logging/index.d.ts +6 -0
  179. package/tsconfig/dist/logging/index.d.ts.map +1 -0
  180. package/tsconfig/dist/logging/index.js +6 -0
  181. package/tsconfig/dist/logging/index.js.map +1 -0
  182. package/tsconfig/dist/logging/logger.d.ts +9 -0
  183. package/tsconfig/dist/logging/logger.d.ts.map +1 -0
  184. package/tsconfig/dist/logging/logger.js +101 -0
  185. package/tsconfig/dist/logging/logger.js.map +1 -0
  186. package/tsconfig/dist/logging/redaction.d.ts +7 -0
  187. package/tsconfig/dist/logging/redaction.d.ts.map +1 -0
  188. package/tsconfig/dist/logging/redaction.js +23 -0
  189. package/tsconfig/dist/logging/redaction.js.map +1 -0
  190. package/tsconfig/dist/logging/sinks/json.d.ts +4 -0
  191. package/tsconfig/dist/logging/sinks/json.d.ts.map +1 -0
  192. package/tsconfig/dist/logging/sinks/json.js +23 -0
  193. package/tsconfig/dist/logging/sinks/json.js.map +1 -0
  194. package/tsconfig/dist/logging/sinks/stdout.d.ts +3 -0
  195. package/tsconfig/dist/logging/sinks/stdout.d.ts.map +1 -0
  196. package/tsconfig/dist/logging/sinks/stdout.js +24 -0
  197. package/tsconfig/dist/logging/sinks/stdout.js.map +1 -0
  198. package/tsconfig/dist/logging/types/index.d.ts +2 -0
  199. package/tsconfig/dist/logging/types/index.d.ts.map +1 -0
  200. package/tsconfig/dist/logging/types/index.js +2 -0
  201. package/tsconfig/dist/logging/types/index.js.map +1 -0
  202. package/tsconfig/dist/logging/types/types.d.ts +37 -0
  203. package/tsconfig/dist/logging/types/types.d.ts.map +1 -0
  204. package/tsconfig/dist/logging/types/types.js +2 -0
  205. package/tsconfig/dist/logging/types/types.js.map +1 -0
  206. package/tsconfig/dist/merge/layered-merge.d.ts +16 -0
  207. package/tsconfig/dist/merge/layered-merge.d.ts.map +1 -0
  208. package/tsconfig/dist/merge/layered-merge.js +97 -0
  209. package/tsconfig/dist/merge/layered-merge.js.map +1 -0
  210. package/tsconfig/dist/preset/resolve-preset.d.ts +29 -0
  211. package/tsconfig/dist/preset/resolve-preset.d.ts.map +1 -0
  212. package/tsconfig/dist/preset/resolve-preset.js +104 -0
  213. package/tsconfig/dist/preset/resolve-preset.js.map +1 -0
  214. package/tsconfig/dist/repo/__tests__/repo.spec.d.ts +2 -0
  215. package/tsconfig/dist/repo/__tests__/repo.spec.d.ts.map +1 -0
  216. package/tsconfig/dist/repo/__tests__/repo.spec.js +25 -0
  217. package/tsconfig/dist/repo/__tests__/repo.spec.js.map +1 -0
  218. package/tsconfig/dist/repo/index.d.ts +2 -0
  219. package/tsconfig/dist/repo/index.d.ts.map +1 -0
  220. package/tsconfig/dist/repo/index.js +2 -0
  221. package/tsconfig/dist/repo/index.js.map +1 -0
  222. package/tsconfig/dist/repo/repo.d.ts +6 -0
  223. package/tsconfig/dist/repo/repo.d.ts.map +1 -0
  224. package/tsconfig/dist/repo/repo.js +25 -0
  225. package/tsconfig/dist/repo/repo.js.map +1 -0
  226. package/tsconfig/dist/runtime/index.d.ts +2 -0
  227. package/tsconfig/dist/runtime/index.d.ts.map +1 -0
  228. package/tsconfig/dist/runtime/index.js +2 -0
  229. package/tsconfig/dist/runtime/index.js.map +1 -0
  230. package/tsconfig/dist/runtime/runtime.d.ts +46 -0
  231. package/tsconfig/dist/runtime/runtime.d.ts.map +1 -0
  232. package/tsconfig/dist/runtime/runtime.js +126 -0
  233. package/tsconfig/dist/runtime/runtime.js.map +1 -0
  234. package/tsconfig/dist/tsconfig.tools.tsbuildinfo +1 -0
  235. package/tsconfig/dist/tsconfig.tsbuildinfo +1 -0
  236. package/tsconfig/dist/types/index.d.ts +2 -0
  237. package/tsconfig/dist/types/index.d.ts.map +1 -0
  238. package/tsconfig/dist/types/index.js +2 -0
  239. package/tsconfig/dist/types/index.js.map +1 -0
  240. package/tsconfig/dist/types/init.d.ts +34 -0
  241. package/tsconfig/dist/types/init.d.ts.map +1 -0
  242. package/tsconfig/dist/types/init.js +6 -0
  243. package/tsconfig/dist/types/init.js.map +1 -0
  244. package/tsconfig/dist/types/preset.d.ts +27 -0
  245. package/tsconfig/dist/types/preset.d.ts.map +1 -0
  246. package/tsconfig/dist/types/preset.js +6 -0
  247. package/tsconfig/dist/types/preset.js.map +1 -0
  248. package/tsconfig/dist/types/types.d.ts +6 -0
  249. package/tsconfig/dist/types/types.d.ts.map +1 -0
  250. package/tsconfig/dist/types/types.js +2 -0
  251. package/tsconfig/dist/types/types.js.map +1 -0
  252. package/tsconfig/dist/utils/__tests__/env.spec.d.ts +2 -0
  253. package/tsconfig/dist/utils/__tests__/env.spec.d.ts.map +1 -0
  254. package/tsconfig/dist/utils/__tests__/env.spec.js +33 -0
  255. package/tsconfig/dist/utils/__tests__/env.spec.js.map +1 -0
  256. package/tsconfig/dist/utils/env.d.ts +7 -0
  257. package/tsconfig/dist/utils/env.d.ts.map +1 -0
  258. package/tsconfig/dist/utils/env.js +25 -0
  259. package/tsconfig/dist/utils/env.js.map +1 -0
  260. package/tsconfig/dist/utils/fs-atomic.d.ts +15 -0
  261. package/tsconfig/dist/utils/fs-atomic.d.ts.map +1 -0
  262. package/tsconfig/dist/utils/fs-atomic.js +45 -0
  263. package/tsconfig/dist/utils/fs-atomic.js.map +1 -0
  264. package/tsconfig/dist/utils/index.d.ts +3 -0
  265. package/tsconfig/dist/utils/index.d.ts.map +1 -0
  266. package/tsconfig/dist/utils/index.js +3 -0
  267. package/tsconfig/dist/utils/index.js.map +1 -0
  268. package/tsconfig/dist/utils/paths.d.ts +21 -0
  269. package/tsconfig/dist/utils/paths.d.ts.map +1 -0
  270. package/tsconfig/dist/utils/paths.js +32 -0
  271. package/tsconfig/dist/utils/paths.js.map +1 -0
  272. package/tsconfig/dist/utils/product-normalize.d.ts +27 -0
  273. package/tsconfig/dist/utils/product-normalize.d.ts.map +1 -0
  274. package/tsconfig/dist/utils/product-normalize.js +45 -0
  275. package/tsconfig/dist/utils/product-normalize.js.map +1 -0
  276. package/tsconfig/dist/validation/validate-config.d.ts +7 -0
  277. package/tsconfig/dist/validation/validate-config.d.ts.map +1 -0
  278. package/tsconfig/dist/validation/validate-config.js +22 -0
  279. package/tsconfig/dist/validation/validate-config.js.map +1 -0
  280. package/tsconfig/lib.json +13 -0
  281. package/tsconfig/node.json +12 -0
  282. package/tsconfig/react-app.json +8 -0
  283. package/tsconfig/react-lib.json +8 -0
  284. package/tsconfig/test.json +15 -0
  285. package/tsup/bin.js +172 -0
  286. package/tsup/dual.js +155 -0
  287. package/tsup/external-sync.mjs +41 -0
  288. package/tsup/external.mjs +109 -0
  289. package/tsup/node.js +65 -0
  290. package/tsup/react-lib.js +18 -0
  291. package/tsup/sdk.js +118 -0
  292. package/vite/react-app.js +50 -0
  293. package/vitest/node.js +37 -0
  294. package/vitest/react.js +39 -0
  295. package/vitest/vitest-setup.ts +9 -0
@@ -0,0 +1,1169 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * @kb-labs/devkit - Dependency Auto-Fixer
5
+ *
6
+ * Automatically fixes common dependency issues:
7
+ * 1. Removes unused dependencies
8
+ * 2. Adds missing workspace dependencies
9
+ * 3. Adds missing npm packages
10
+ * 4. Aligns duplicate dependency versions
11
+ *
12
+ * Usage:
13
+ * kb-devkit-fix-deps --remove-unused # Remove unused dependencies
14
+ * kb-devkit-fix-deps --add-missing # Add missing workspace deps
15
+ * kb-devkit-fix-deps --add-missing-npm # Add missing npm packages
16
+ * kb-devkit-fix-deps --align-versions # Align duplicate versions
17
+ * kb-devkit-fix-deps --all # Apply all fixes
18
+ * kb-devkit-fix-deps --dry-run # Show what would be changed
19
+ * kb-devkit-fix-deps --orphans # Find packages not used by anyone
20
+ */
21
+
22
+ import fs from 'node:fs';
23
+ import path from 'node:path';
24
+ import { fileURLToPath } from 'node:url';
25
+ import { execSync } from 'node:child_process';
26
+
27
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
28
+
29
+ // ANSI colors
30
+ const colors = {
31
+ reset: '\x1b[0m',
32
+ red: '\x1b[31m',
33
+ green: '\x1b[32m',
34
+ yellow: '\x1b[33m',
35
+ blue: '\x1b[34m',
36
+ magenta: '\x1b[35m',
37
+ cyan: '\x1b[36m',
38
+ gray: '\x1b[90m',
39
+ };
40
+
41
+ function log(message, color = 'reset') {
42
+ console.log(`${colors[color]}${message}${colors.reset}`);
43
+ }
44
+
45
+ // Parse CLI arguments
46
+ const args = process.argv.slice(2);
47
+ const options = {
48
+ removeUnused: args.includes('--remove-unused'),
49
+ addMissing: args.includes('--add-missing'),
50
+ addMissingNpm: args.includes('--add-missing-npm'),
51
+ alignVersions: args.includes('--align-versions'),
52
+ all: args.includes('--all'),
53
+ dryRun: args.includes('--dry-run'),
54
+ verbose: args.includes('--verbose') || args.includes('-v'),
55
+ showStats: args.includes('--stats'),
56
+ showOrphans: args.includes('--orphans'),
57
+ json: args.includes('--json'),
58
+ package: args.find((arg) => arg.startsWith('--package='))?.split('=')[1],
59
+ };
60
+
61
+ // If --all, enable all fixes
62
+ if (options.all) {
63
+ options.removeUnused = true;
64
+ options.addMissing = true;
65
+ options.addMissingNpm = true;
66
+ options.alignVersions = true;
67
+ }
68
+
69
+ /**
70
+ * Find all packages in monorepo
71
+ */
72
+ function findPackages(rootDir, filterPackage) {
73
+ const packages = [];
74
+ const entries = fs.readdirSync(rootDir, { withFileTypes: true });
75
+
76
+ for (const entry of entries) {
77
+ if (!entry.isDirectory() || !entry.name.startsWith('kb-labs-')) {continue;}
78
+
79
+ const repoPath = path.join(rootDir, entry.name);
80
+ const packagesDir = path.join(repoPath, 'packages');
81
+
82
+ if (!fs.existsSync(packagesDir)) {continue;}
83
+
84
+ const packageDirs = fs.readdirSync(packagesDir, { withFileTypes: true });
85
+
86
+ for (const pkgDir of packageDirs) {
87
+ if (!pkgDir.isDirectory()) {continue;}
88
+
89
+ if (filterPackage && pkgDir.name !== filterPackage) {continue;}
90
+
91
+ const packageJsonPath = path.join(packagesDir, pkgDir.name, 'package.json');
92
+
93
+ if (fs.existsSync(packageJsonPath)) {
94
+ packages.push(packageJsonPath);
95
+ }
96
+ }
97
+ }
98
+
99
+ return packages;
100
+ }
101
+
102
+ /**
103
+ * Extract imports from source files
104
+ */
105
+ function extractImportsFromFile(filePath) {
106
+ const content = fs.readFileSync(filePath, 'utf-8');
107
+ const imports = new Set();
108
+
109
+ const patterns = [
110
+ // Static imports: import X from 'pkg'
111
+ /import\s+(?:[\w*{}\n\r\t, ]+\s+from\s+)?['"]([^'"]+)['"]/g,
112
+ // Dynamic imports: import('pkg')
113
+ /import\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
114
+ // Require: require('pkg')
115
+ /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
116
+ // Template literal dynamic imports: import(`pkg`)
117
+ /import\s*\(\s*`([^`]+)`\s*\)/g,
118
+ // Template literal require: require(`pkg`)
119
+ /require\s*\(\s*`([^`]+)`\s*\)/g,
120
+ ];
121
+
122
+ for (const pattern of patterns) {
123
+ let match;
124
+ while ((match = pattern.exec(content)) !== null) {
125
+ const importPath = match[1];
126
+
127
+ // Skip template variables like ${var} - we can't analyze these statically
128
+ if (importPath.includes('${')) {
129
+ continue;
130
+ }
131
+
132
+ // Extract package name (handle scoped packages)
133
+ if (!importPath.startsWith('.') && !importPath.startsWith('/')) {
134
+ const pkgMatch = importPath.match(/^(@[^/]+\/[^/]+|[^/]+)/);
135
+ if (pkgMatch) {
136
+ imports.add(pkgMatch[1]);
137
+ }
138
+ }
139
+ }
140
+ }
141
+
142
+ return imports;
143
+ }
144
+
145
+ /**
146
+ * Find all imports in package
147
+ */
148
+ function findUsedDependencies(packageDir) {
149
+ const used = new Set();
150
+
151
+ // Directories to scan
152
+ const dirsToScan = ['src', 'test', 'tests', '__tests__', 'scripts', 'bin'];
153
+
154
+ for (const dir of dirsToScan) {
155
+ const dirPath = path.join(packageDir, dir);
156
+ if (fs.existsSync(dirPath)) {
157
+ walkDir(dirPath, used);
158
+ }
159
+ }
160
+
161
+ // Also scan config files in root
162
+ const configPatterns = [
163
+ 'tsup.config.ts',
164
+ 'tsup.config.js',
165
+ 'vitest.config.ts',
166
+ 'vitest.config.js',
167
+ 'vite.config.ts',
168
+ 'vite.config.js',
169
+ 'eslint.config.js',
170
+ 'eslint.config.mjs',
171
+ 'jest.config.ts',
172
+ 'jest.config.js',
173
+ 'rollup.config.ts',
174
+ 'rollup.config.js',
175
+ ];
176
+
177
+ for (const configFile of configPatterns) {
178
+ const configPath = path.join(packageDir, configFile);
179
+ if (fs.existsSync(configPath)) {
180
+ const imports = extractImportsFromFile(configPath);
181
+ imports.forEach((imp) => used.add(imp));
182
+ }
183
+ }
184
+
185
+ // Scan package.json scripts for CLI tool usage
186
+ const packageJsonPath = path.join(packageDir, 'package.json');
187
+ if (fs.existsSync(packageJsonPath)) {
188
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
189
+ if (packageJson.scripts) {
190
+ for (const script of Object.values(packageJson.scripts)) {
191
+ // Extract command names from scripts (first word before space or flags)
192
+ const commands = script.match(/\b([a-z0-9@\-_/.]+)/gi) || [];
193
+ for (const cmd of commands) {
194
+ // Extract package name from command (e.g., "tsx" from "tsx src/index.ts")
195
+ // Handle scoped packages (e.g., "@vitest/ui")
196
+ if (cmd.startsWith('@')) {
197
+ const scopedMatch = cmd.match(/^(@[^/]+\/[^/\s]+)/);
198
+ if (scopedMatch) {
199
+ used.add(scopedMatch[1]);
200
+ }
201
+ } else {
202
+ const pkgMatch = cmd.match(/^([a-z0-9\-_]+)/);
203
+ if (pkgMatch) {
204
+ used.add(pkgMatch[1]);
205
+ }
206
+ }
207
+ }
208
+ }
209
+ }
210
+ }
211
+
212
+ return used;
213
+ }
214
+
215
+ /**
216
+ * Walk directory and collect imports
217
+ */
218
+ function walkDir(dir, used) {
219
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
220
+
221
+ for (const entry of entries) {
222
+ const fullPath = path.join(dir, entry.name);
223
+
224
+ if (entry.isDirectory() && entry.name !== 'node_modules' && entry.name !== 'dist') {
225
+ walkDir(fullPath, used);
226
+ } else if (entry.isFile() && /\.(ts|tsx|js|jsx|mjs|cjs)$/.test(entry.name)) {
227
+ const imports = extractImportsFromFile(fullPath);
228
+ imports.forEach((imp) => used.add(imp));
229
+ }
230
+ }
231
+ }
232
+
233
+ /**
234
+ * Remove unused dependencies
235
+ */
236
+ function removeUnusedDependencies(packageJsonPath, dryRun, verbose = false) {
237
+ const packageDir = path.dirname(packageJsonPath);
238
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
239
+ const packageName = packageJson.name;
240
+
241
+ if (!packageName || !packageName.startsWith('@kb-labs/')) {
242
+ return { removed: 0, deps: [], kept: [] };
243
+ }
244
+
245
+ const used = findUsedDependencies(packageDir);
246
+ const kept = []; // For verbose output
247
+ const allDeps = { ...packageJson.dependencies, ...packageJson.devDependencies };
248
+ const peerDeps = packageJson.peerDependencies || {};
249
+ const toRemove = [];
250
+
251
+ // Also mark dependencies used in bin scripts as used
252
+ if (packageJson.bin) {
253
+ const binScripts = typeof packageJson.bin === 'string'
254
+ ? [packageJson.bin]
255
+ : Object.values(packageJson.bin);
256
+
257
+ for (const binPath of binScripts) {
258
+ const fullBinPath = path.join(packageDir, binPath);
259
+ if (fs.existsSync(fullBinPath)) {
260
+ const imports = extractImportsFromFile(fullBinPath);
261
+ imports.forEach((imp) => used.add(imp));
262
+ }
263
+ }
264
+ }
265
+
266
+ // Build tools that should not be removed (used via bin/cli, not imports)
267
+ const keepDeps = new Set([
268
+ // TypeScript & build
269
+ 'typescript',
270
+ 'tsup',
271
+ 'esbuild',
272
+ 'rollup',
273
+ 'vite',
274
+ 'rimraf',
275
+ 'concurrently',
276
+ 'cross-env',
277
+ 'tsx',
278
+ 'ts-node',
279
+ 'npm-run-all',
280
+ 'npm-run-all2',
281
+ 'wait-on',
282
+ 'nodemon',
283
+ 'chokidar-cli',
284
+
285
+ // Linting & formatting
286
+ 'eslint',
287
+ 'prettier',
288
+ 'oxlint',
289
+
290
+ // Testing
291
+ 'vitest',
292
+ 'jest',
293
+ 'mocha',
294
+ 'nyc',
295
+ 'c8',
296
+ '@vitest/coverage-v8',
297
+ '@vitest/ui',
298
+ 'playwright',
299
+ '@playwright/test',
300
+ 'happy-dom',
301
+ 'jsdom',
302
+
303
+ // KB Labs devkit
304
+ '@kb-labs/devkit',
305
+
306
+ // Node types (used implicitly)
307
+ '@types/node',
308
+
309
+ // Package managers & publish tools
310
+ 'changesets',
311
+ '@changesets/cli',
312
+ 'turbo',
313
+ 'lerna',
314
+ ]);
315
+
316
+ // Patterns for packages that shouldn't be removed
317
+ const keepPatterns = [
318
+ /^@types\//, // Type definitions
319
+ /^eslint-/, // ESLint plugins/configs
320
+ /^@eslint\//, // ESLint scoped packages
321
+ /^@vitest\//, // Vitest plugins
322
+ /^@testing-library\//, // Testing library
323
+ /^@typescript-eslint/, // TS ESLint
324
+ /^@vitejs\//, // Vite plugins
325
+ /^vite-plugin-/, // Vite plugins
326
+ /^rollup-plugin-/, // Rollup plugins
327
+ /^@rollup\//, // Rollup scoped
328
+ /^prettier-plugin-/, // Prettier plugins
329
+ ];
330
+
331
+ for (const dep of Object.keys(allDeps)) {
332
+ // Always keep workspace dependencies (they define the architecture)
333
+ const depValue = allDeps[dep];
334
+ if (typeof depValue === 'string' && (depValue.startsWith('workspace:') || depValue.startsWith('link:'))) {
335
+ if (verbose) {kept.push({ dep, reason: 'workspace/link dependency' });}
336
+ continue;
337
+ }
338
+
339
+ // Skip if it's used in code
340
+ if (used.has(dep)) {
341
+ if (verbose) {kept.push({ dep, reason: 'used in code' });}
342
+ continue;
343
+ }
344
+
345
+ // Skip if it's a peer dependency (consumers might need it)
346
+ if (peerDeps[dep]) {
347
+ if (verbose) {kept.push({ dep, reason: 'peer dependency' });}
348
+ continue;
349
+ }
350
+
351
+ // Skip if it's in keepDeps
352
+ if (keepDeps.has(dep)) {
353
+ if (verbose) {kept.push({ dep, reason: 'build tool' });}
354
+ continue;
355
+ }
356
+
357
+ // Skip if matches any keep pattern
358
+ const matchedPattern = keepPatterns.find((pattern) => pattern.test(dep));
359
+ if (matchedPattern) {
360
+ if (verbose) {kept.push({ dep, reason: `pattern: ${matchedPattern.source}` });}
361
+ continue;
362
+ }
363
+
364
+ toRemove.push(dep);
365
+ }
366
+
367
+ if (toRemove.length > 0) {
368
+ if (!dryRun) {
369
+ // Remove from both dependencies and devDependencies
370
+ for (const dep of toRemove) {
371
+ delete packageJson.dependencies?.[dep];
372
+ delete packageJson.devDependencies?.[dep];
373
+ }
374
+
375
+ fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n');
376
+ }
377
+
378
+ return { removed: toRemove.length, deps: toRemove, packageName, kept, totalDeps: Object.keys(allDeps).length };
379
+ }
380
+
381
+ return { removed: 0, deps: [], packageName, kept, totalDeps: Object.keys(allDeps).length };
382
+ }
383
+
384
+ /**
385
+ * Add missing workspace dependencies
386
+ */
387
+ function addMissingDependencies(packageJsonPath, dryRun, allPackages) {
388
+ const packageDir = path.dirname(packageJsonPath);
389
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
390
+ const packageName = packageJson.name;
391
+
392
+ if (!packageName || !packageName.startsWith('@kb-labs/')) {
393
+ return { added: 0, deps: [] };
394
+ }
395
+
396
+ const used = findUsedDependencies(packageDir);
397
+ const existing = { ...packageJson.dependencies, ...packageJson.devDependencies };
398
+ const toAdd = [];
399
+
400
+ // Find missing workspace packages
401
+ for (const dep of used) {
402
+ if (dep.startsWith('@kb-labs/') && !existing[dep]) {
403
+ // Check if this package exists in workspace
404
+ const exists = allPackages.some((p) => {
405
+ const json = JSON.parse(fs.readFileSync(p, 'utf-8'));
406
+ return json.name === dep;
407
+ });
408
+
409
+ if (exists) {
410
+ toAdd.push(dep);
411
+ }
412
+ }
413
+ }
414
+
415
+ if (toAdd.length > 0) {
416
+ if (!dryRun) {
417
+ if (!packageJson.dependencies) {
418
+ packageJson.dependencies = {};
419
+ }
420
+
421
+ for (const dep of toAdd) {
422
+ packageJson.dependencies[dep] = 'workspace:*';
423
+ }
424
+
425
+ // Sort dependencies
426
+ packageJson.dependencies = Object.fromEntries(
427
+ Object.entries(packageJson.dependencies).sort()
428
+ );
429
+
430
+ fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n');
431
+ }
432
+
433
+ return { added: toAdd.length, deps: toAdd, packageName };
434
+ }
435
+
436
+ return { added: 0, deps: [], packageName };
437
+ }
438
+
439
+ /**
440
+ * Add missing npm dependencies (non-workspace packages)
441
+ * Uses devkit-check-imports to find missing npm packages
442
+ */
443
+ function addMissingNpmDependencies(packageJsonPath, dryRun) {
444
+ const packageDir = path.dirname(packageJsonPath);
445
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
446
+ const packageName = packageJson.name;
447
+
448
+ if (!packageName || !packageName.startsWith('@kb-labs/')) {
449
+ return { added: 0, deps: [], packageName };
450
+ }
451
+
452
+ // Run devkit-check-imports for this package to get missing npm deps
453
+ const shortName = packageName.replace('@kb-labs/', '');
454
+ const missingNpmDeps = [];
455
+
456
+ try {
457
+ const output = execSync(
458
+ `node ${path.join(__dirname, 'devkit-check-imports.mjs')} --package=${shortName} 2>&1`,
459
+ { encoding: 'utf-8', stdio: 'pipe' }
460
+ );
461
+
462
+ // Parse output to extract missing npm dependencies
463
+ const lines = output.split('\n');
464
+ let inMissingNpmSection = false;
465
+
466
+ for (let i = 0; i < lines.length; i++) {
467
+ const line = lines[i];
468
+
469
+ // Check if we're in the "Missing npm dependencies" section
470
+ if (line.includes('Missing npm dependencies')) {
471
+ inMissingNpmSection = true;
472
+ continue;
473
+ }
474
+
475
+ // Exit section when we hit a separator, new section, or empty line after deps
476
+ if (inMissingNpmSection && (line.includes('────') || line.includes('📊') || line.includes('🔴') || line.includes('🟠') || line.includes('🔄') || line.includes('Summary'))) {
477
+ inMissingNpmSection = false;
478
+ break;
479
+ }
480
+
481
+ // Extract dependency name from lines like "[36m pino[0m"
482
+ if (inMissingNpmSection && line.trim() && !line.includes('└─') && !line.includes('Used in')) {
483
+ // Remove ANSI color codes and whitespace
484
+ const cleaned = line.replace(/\x1b\[[0-9;]*m/g, '').replace(/\[[0-9]+m/g, '').trim();
485
+ if (cleaned && /^[a-z@]/.test(cleaned) && !cleaned.includes('Missing') && !cleaned.includes('dependencies')) {
486
+ missingNpmDeps.push(cleaned);
487
+ }
488
+ }
489
+ }
490
+ } catch (error) {
491
+ // If check-imports fails (non-zero exit), still try to parse output
492
+ if (error.stdout) {
493
+ const lines = error.stdout.toString().split('\n');
494
+ let inMissingNpmSection = false;
495
+
496
+ for (const line of lines) {
497
+ if (line.includes('Missing npm dependencies')) {
498
+ inMissingNpmSection = true;
499
+ continue;
500
+ }
501
+
502
+ if (inMissingNpmSection && (line.includes('────') || line.includes('📊') || line.includes('🔴') || line.includes('🟠') || line.includes('🔄'))) {
503
+ inMissingNpmSection = false;
504
+ break;
505
+ }
506
+
507
+ if (inMissingNpmSection && line.trim() && !line.includes('└─') && !line.includes('Used in')) {
508
+ const cleaned = line.replace(/\x1b\[[0-9;]*m/g, '').replace(/\[[0-9]+m/g, '').trim();
509
+ if (cleaned && /^[a-z@]/.test(cleaned) && !cleaned.includes('Missing')) {
510
+ missingNpmDeps.push(cleaned);
511
+ }
512
+ }
513
+ }
514
+ }
515
+
516
+ // If still no deps found, return empty
517
+ if (missingNpmDeps.length === 0) {
518
+ return { added: 0, deps: [], packageName };
519
+ }
520
+ }
521
+
522
+ if (missingNpmDeps.length === 0) {
523
+ return { added: 0, deps: [], packageName };
524
+ }
525
+
526
+ const existing = { ...packageJson.dependencies, ...packageJson.devDependencies };
527
+ const toAdd = [];
528
+
529
+ // Get most common version from other packages in monorepo
530
+ const rootDir = path.resolve(__dirname, '..');
531
+ const allPackages = findPackages(rootDir);
532
+
533
+ for (const npmDep of missingNpmDeps) {
534
+ // Skip if trying to add package to itself
535
+ if (npmDep === packageName) {
536
+ continue;
537
+ }
538
+
539
+ if (!existing[npmDep]) {
540
+ // Find version used in other packages
541
+ let version = null;
542
+ for (const pkgPath of allPackages) {
543
+ const pkgJson = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
544
+ const allDeps = { ...pkgJson.dependencies, ...pkgJson.devDependencies };
545
+ if (allDeps[npmDep]) {
546
+ version = allDeps[npmDep];
547
+ break;
548
+ }
549
+ }
550
+
551
+ // If not found in other packages, use 'latest' placeholder
552
+ if (!version) {
553
+ version = 'latest';
554
+ }
555
+
556
+ toAdd.push({ name: npmDep, version });
557
+ }
558
+ }
559
+
560
+ if (toAdd.length > 0) {
561
+ if (!dryRun) {
562
+ if (!packageJson.dependencies) {
563
+ packageJson.dependencies = {};
564
+ }
565
+
566
+ for (const { name, version } of toAdd) {
567
+ packageJson.dependencies[name] = version;
568
+ }
569
+
570
+ // Sort dependencies
571
+ packageJson.dependencies = Object.fromEntries(
572
+ Object.entries(packageJson.dependencies).sort()
573
+ );
574
+
575
+ fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n');
576
+ }
577
+
578
+ return { added: toAdd.length, deps: toAdd.map((d) => `${d.name}@${d.version}`), packageName };
579
+ }
580
+
581
+ return { added: 0, deps: [], packageName };
582
+ }
583
+
584
+ /**
585
+ * Collect version statistics
586
+ */
587
+ function collectVersionStats(packages) {
588
+ const versionMap = new Map();
589
+
590
+ for (const packagePath of packages) {
591
+ const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf-8'));
592
+ const allDeps = {
593
+ ...packageJson.dependencies,
594
+ ...packageJson.devDependencies,
595
+ };
596
+
597
+ for (const [dep, version] of Object.entries(allDeps)) {
598
+ if (version.startsWith('workspace:') || version.startsWith('link:')) {
599
+ continue;
600
+ }
601
+
602
+ if (!versionMap.has(dep)) {
603
+ versionMap.set(dep, new Map());
604
+ }
605
+
606
+ const versions = versionMap.get(dep);
607
+ if (!versions.has(version)) {
608
+ versions.set(version, []);
609
+ }
610
+ versions.get(version).push(packagePath);
611
+ }
612
+ }
613
+
614
+ return versionMap;
615
+ }
616
+
617
+ /**
618
+ * Align dependency versions
619
+ */
620
+ function alignDependencyVersions(packages, dryRun) {
621
+ const versionMap = collectVersionStats(packages);
622
+ let totalAligned = 0;
623
+ const changes = [];
624
+
625
+ for (const [dep, versions] of versionMap.entries()) {
626
+ if (versions.size <= 1) {continue;}
627
+
628
+ // Pick the most common version (or newest if tie)
629
+ const versionArray = Array.from(versions.entries()).sort(
630
+ (a, b) => b[1].length - a[1].length
631
+ );
632
+
633
+ const targetVersion = versionArray[0][0];
634
+ const targetCount = versionArray[0][1].length;
635
+
636
+ // Align all packages to target version
637
+ for (const [version, packagePaths] of versions.entries()) {
638
+ if (version === targetVersion) {continue;}
639
+
640
+ for (const packagePath of packagePaths) {
641
+ if (!dryRun) {
642
+ const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf-8'));
643
+
644
+ if (packageJson.dependencies?.[dep]) {
645
+ packageJson.dependencies[dep] = targetVersion;
646
+ }
647
+ if (packageJson.devDependencies?.[dep]) {
648
+ packageJson.devDependencies[dep] = targetVersion;
649
+ }
650
+
651
+ fs.writeFileSync(packagePath, JSON.stringify(packageJson, null, 2) + '\n');
652
+ }
653
+
654
+ totalAligned++;
655
+ changes.push({
656
+ package: JSON.parse(fs.readFileSync(packagePath, 'utf-8')).name,
657
+ dep,
658
+ from: version,
659
+ to: targetVersion,
660
+ });
661
+ }
662
+ }
663
+ }
664
+
665
+ return { totalAligned, changes };
666
+ }
667
+
668
+ /**
669
+ * Show dependency statistics
670
+ */
671
+ function showDependencyStats(packages) {
672
+ log('\n📊 Dependency Statistics\n', 'cyan');
673
+
674
+ let totalDeps = 0;
675
+ let totalDevDeps = 0;
676
+ let totalPeerDeps = 0;
677
+ let unusedCount = 0;
678
+ const depUsage = new Map(); // dep -> count of packages using it
679
+
680
+ for (const packagePath of packages) {
681
+ const packageDir = path.dirname(packagePath);
682
+ const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf-8'));
683
+
684
+ const deps = Object.keys(packageJson.dependencies || {});
685
+ const devDeps = Object.keys(packageJson.devDependencies || {});
686
+ const peerDeps = Object.keys(packageJson.peerDependencies || {});
687
+
688
+ totalDeps += deps.length;
689
+ totalDevDeps += devDeps.length;
690
+ totalPeerDeps += peerDeps.length;
691
+
692
+ // Track dep usage across packages
693
+ for (const dep of [...deps, ...devDeps]) {
694
+ depUsage.set(dep, (depUsage.get(dep) || 0) + 1);
695
+ }
696
+
697
+ // Check for unused
698
+ const used = findUsedDependencies(packageDir);
699
+ for (const dep of deps) {
700
+ if (!used.has(dep) && !dep.startsWith('@types/')) {
701
+ unusedCount++;
702
+ }
703
+ }
704
+ }
705
+
706
+ log(`📦 Total packages: ${packages.length}`, 'cyan');
707
+ log(`📚 Total dependencies: ${totalDeps}`, 'cyan');
708
+ log(`🔧 Total devDependencies: ${totalDevDeps}`, 'cyan');
709
+ log(`🔗 Total peerDependencies: ${totalPeerDeps}`, 'cyan');
710
+ log('', 'reset');
711
+
712
+ // Top used dependencies
713
+ const sortedDeps = Array.from(depUsage.entries())
714
+ .sort((a, b) => b[1] - a[1])
715
+ .slice(0, 10);
716
+
717
+ log('🔝 Top 10 Most Used Dependencies:\n', 'blue');
718
+ for (let i = 0; i < sortedDeps.length; i++) {
719
+ const [dep, count] = sortedDeps[i];
720
+ log(` ${(i + 1).toString().padStart(2)}. ${dep} (${count} packages)`, 'gray');
721
+ }
722
+ log('', 'reset');
723
+
724
+ // Potentially unused (very rough estimate)
725
+ if (unusedCount > 0) {
726
+ log(`⚠️ Potentially unused deps: ${unusedCount}`, 'yellow');
727
+ log(' Run --remove-unused --dry-run to see details\n', 'gray');
728
+ } else {
729
+ log('✅ No obviously unused dependencies detected\n', 'green');
730
+ }
731
+ }
732
+
733
+ /**
734
+ * Find orphan packages - packages that no other package depends on
735
+ */
736
+ function findOrphanPackages(packages) {
737
+ // Build map of package names to their paths
738
+ const packageMap = new Map();
739
+ for (const packagePath of packages) {
740
+ const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf-8'));
741
+ if (packageJson.name?.startsWith('@kb-labs/')) {
742
+ packageMap.set(packageJson.name, {
743
+ path: packagePath,
744
+ dir: path.dirname(packagePath),
745
+ repo: path.basename(path.dirname(path.dirname(path.dirname(packagePath)))),
746
+ });
747
+ }
748
+ }
749
+
750
+ // Collect all dependencies across all packages
751
+ const dependedOn = new Set();
752
+ for (const packagePath of packages) {
753
+ const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf-8'));
754
+
755
+ // Combine all dependency types
756
+ const allDeps = [
757
+ ...Object.keys(packageJson.dependencies || {}),
758
+ ...Object.keys(packageJson.devDependencies || {}),
759
+ ...Object.keys(packageJson.peerDependencies || {}),
760
+ ];
761
+
762
+ for (const dep of allDeps) {
763
+ if (dep.startsWith('@kb-labs/')) {
764
+ dependedOn.add(dep);
765
+ }
766
+ }
767
+ }
768
+
769
+ // Find packages that are NOT depended on by anyone
770
+ const orphans = [];
771
+ for (const [name, info] of packageMap.entries()) {
772
+ if (!dependedOn.has(name)) {
773
+ orphans.push({ name, ...info });
774
+ }
775
+ }
776
+
777
+ return { orphans, totalPackages: packageMap.size, dependedOn: dependedOn.size };
778
+ }
779
+
780
+ /**
781
+ * Categorize orphan packages
782
+ */
783
+ function categorizeOrphans(orphans) {
784
+ const categories = {
785
+ cliEntryPoints: [], // CLI tools (expected to be orphans)
786
+ apps: [], // Application entry points
787
+ plugins: [], // Plugin packages
788
+ templates: [], // Template packages
789
+ external: [], // Packages meant for external consumption
790
+ internal: [], // Internal packages (potential issues)
791
+ };
792
+
793
+ for (const pkg of orphans) {
794
+ const name = pkg.name.replace('@kb-labs/', '');
795
+
796
+ // CLI entry points (expected orphans)
797
+ if (name.endsWith('-cli') || name === 'cli-core' || name === 'cli-bin') {
798
+ categories.cliEntryPoints.push(pkg);
799
+ }
800
+ // Plugin packages (often standalone) - check both name pattern and repo
801
+ else if (name.endsWith('-plugin') || name.includes('plugin-') || pkg.repo === 'kb-labs-plugin') {
802
+ categories.plugins.push(pkg);
803
+ }
804
+ // App entry points
805
+ else if (name.endsWith('-app') || name.endsWith('-api') || name.endsWith('-server')) {
806
+ categories.apps.push(pkg);
807
+ }
808
+ // Template packages
809
+ else if (name.includes('template-') || name.includes('-template')) {
810
+ categories.templates.push(pkg);
811
+ }
812
+ // External-facing packages (libs meant for consumers)
813
+ else if (
814
+ name.endsWith('-core') || // Core packages often consumed externally
815
+ name === 'ui-core' || // UI library
816
+ name === 'ui-react' || // React components
817
+ name === 'data-client' || // Client library
818
+ pkg.repo === 'kb-labs-studio' || // Studio packages are user-facing
819
+ // Core infrastructure packages (meant for external consumption)
820
+ name === 'core-framework' ||
821
+ name === 'core-state-daemon' ||
822
+ name === 'core-tenant' ||
823
+ name === 'core-profile-toolkit'
824
+ ) {
825
+ categories.external.push(pkg);
826
+ }
827
+ // Internal packages (potential dead code)
828
+ else {
829
+ categories.internal.push(pkg);
830
+ }
831
+ }
832
+
833
+ return categories;
834
+ }
835
+
836
+ /**
837
+ * Show orphan packages analysis
838
+ */
839
+ function showOrphanPackages(packages) {
840
+ log('\n👻 Orphan Packages Analysis\n', 'cyan');
841
+ log('Finding packages that no other package depends on...\n', 'gray');
842
+
843
+ const { orphans, totalPackages, dependedOn } = findOrphanPackages(packages);
844
+ const categories = categorizeOrphans(orphans);
845
+
846
+ log(`📦 Total @kb-labs/* packages: ${totalPackages}`, 'cyan');
847
+ log(`🔗 Packages with dependents: ${dependedOn}`, 'cyan');
848
+ log(`👻 Orphan packages: ${orphans.length}`, orphans.length > 0 ? 'yellow' : 'green');
849
+ log('', 'reset');
850
+
851
+ // Show by category
852
+ if (categories.cliEntryPoints.length > 0) {
853
+ log(`✅ CLI Entry Points (${categories.cliEntryPoints.length}) - Expected orphans:`, 'green');
854
+ for (const pkg of categories.cliEntryPoints) {
855
+ log(` ${pkg.name}`, 'gray');
856
+ }
857
+ log('', 'reset');
858
+ }
859
+
860
+ if (categories.apps.length > 0) {
861
+ log(`✅ Application Entry Points (${categories.apps.length}) - Expected orphans:`, 'green');
862
+ for (const pkg of categories.apps) {
863
+ log(` ${pkg.name}`, 'gray');
864
+ }
865
+ log('', 'reset');
866
+ }
867
+
868
+ if (categories.plugins.length > 0) {
869
+ log(`📦 Plugin Packages (${categories.plugins.length}) - Usually standalone:`, 'blue');
870
+ for (const pkg of categories.plugins) {
871
+ log(` ${pkg.name}`, 'gray');
872
+ }
873
+ log('', 'reset');
874
+ }
875
+
876
+ if (categories.templates.length > 0) {
877
+ log(`📄 Template Packages (${categories.templates.length}) - Expected orphans:`, 'blue');
878
+ for (const pkg of categories.templates) {
879
+ log(` ${pkg.name}`, 'gray');
880
+ }
881
+ log('', 'reset');
882
+ }
883
+
884
+ if (categories.external.length > 0) {
885
+ log(`📤 External/Library Packages (${categories.external.length}) - Consumed externally:`, 'blue');
886
+ for (const pkg of categories.external) {
887
+ log(` ${pkg.name}`, 'gray');
888
+ }
889
+ log('', 'reset');
890
+ }
891
+
892
+ // Internal packages - these might be dead code
893
+ if (categories.internal.length > 0) {
894
+ log(`⚠️ Internal Packages Without Dependents (${categories.internal.length}) - Review needed:`, 'yellow');
895
+ log(' These might be dead code or missing from dependency lists:\n', 'gray');
896
+
897
+ // Group by repo
898
+ const byRepo = new Map();
899
+ for (const pkg of categories.internal) {
900
+ if (!byRepo.has(pkg.repo)) {
901
+ byRepo.set(pkg.repo, []);
902
+ }
903
+ byRepo.get(pkg.repo).push(pkg);
904
+ }
905
+
906
+ for (const [repo, pkgs] of byRepo.entries()) {
907
+ log(` ${repo}/`, 'yellow');
908
+ for (const pkg of pkgs) {
909
+ log(` ${pkg.name}`, 'red');
910
+ }
911
+ }
912
+ log('', 'reset');
913
+
914
+ log('💡 Actions:', 'blue');
915
+ log(' 1. If these packages are used externally, they\'re fine as orphans', 'gray');
916
+ log(' 2. If they should be used internally, add them as dependencies', 'gray');
917
+ log(' 3. If they\'re truly unused, consider removing them', 'gray');
918
+ log('', 'reset');
919
+ } else {
920
+ log('✅ No suspicious internal orphan packages found\n', 'green');
921
+ }
922
+
923
+ // Summary
924
+ const expectedOrphans =
925
+ categories.cliEntryPoints.length +
926
+ categories.apps.length +
927
+ categories.plugins.length +
928
+ categories.templates.length +
929
+ categories.external.length;
930
+
931
+ log('─'.repeat(60), 'gray');
932
+ log('📊 Summary:', 'blue');
933
+ log(` Expected orphans (CLI/Apps/Plugins/Templates/External): ${expectedOrphans}`, 'green');
934
+ log(` Review needed (internal packages): ${categories.internal.length}`, categories.internal.length > 0 ? 'yellow' : 'green');
935
+ log('', 'reset');
936
+
937
+ return { orphans, categories };
938
+ }
939
+
940
+ /**
941
+ * Main function
942
+ */
943
+ function main() {
944
+ const rootDir = process.cwd();
945
+
946
+ if (!options.json) {
947
+ log('\n🔧 KB Labs Dependency Fixer\n', 'blue');
948
+
949
+ if (options.dryRun) {
950
+ log('🔍 DRY RUN MODE - No changes will be made\n', 'yellow');
951
+ }
952
+ }
953
+
954
+ const packages = findPackages(rootDir, options.package);
955
+
956
+ if (packages.length === 0) {
957
+ if (!options.json) {
958
+ log('⚠️ No KB Labs packages found', 'yellow');
959
+ }
960
+ process.exit(0);
961
+ }
962
+
963
+ if (!options.json) {
964
+ log(`Found ${packages.length} package(s)\n`, 'gray');
965
+ }
966
+
967
+ // Stats mode - just show dependency statistics
968
+ if (options.showStats) {
969
+ showDependencyStats(packages);
970
+ process.exit(0);
971
+ }
972
+
973
+ // Orphan packages mode - find packages not used by anyone
974
+ if (options.showOrphans) {
975
+ if (options.json) {
976
+ const { orphans, totalPackages, dependedOn } = findOrphanPackages(packages);
977
+ const categories = categorizeOrphans(orphans);
978
+ console.log(JSON.stringify({
979
+ totalPackages,
980
+ dependedOn,
981
+ orphanCount: orphans.length,
982
+ categories: {
983
+ cliEntryPoints: categories.cliEntryPoints.map(p => p.name),
984
+ apps: categories.apps.map(p => p.name),
985
+ plugins: categories.plugins.map(p => p.name),
986
+ templates: categories.templates.map(p => p.name),
987
+ external: categories.external.map(p => p.name),
988
+ internal: categories.internal.map(p => p.name),
989
+ },
990
+ reviewNeeded: categories.internal.map(p => ({ name: p.name, repo: p.repo })),
991
+ }, null, 2));
992
+ process.exit(categories.internal.length > 0 ? 1 : 0);
993
+ }
994
+ const { categories } = showOrphanPackages(packages);
995
+ // Exit with error if there are suspicious internal orphans
996
+ process.exit(categories.internal.length > 0 ? 1 : 0);
997
+ }
998
+
999
+ let hasChanges = false;
1000
+
1001
+ // Remove unused dependencies
1002
+ if (options.removeUnused) {
1003
+ log('🗑️ Removing unused dependencies...\n', 'cyan');
1004
+
1005
+ let totalRemoved = 0;
1006
+ let totalScanned = 0;
1007
+ const removals = [];
1008
+
1009
+ for (const packagePath of packages) {
1010
+ const result = removeUnusedDependencies(packagePath, options.dryRun, options.verbose);
1011
+ totalScanned += result.totalDeps || 0;
1012
+
1013
+ if (result.removed > 0) {
1014
+ totalRemoved += result.removed;
1015
+ removals.push(result);
1016
+
1017
+ log(` ${result.packageName}`, 'yellow');
1018
+ for (const dep of result.deps) {
1019
+ log(` - ${dep}`, 'red');
1020
+ }
1021
+
1022
+ // Show kept deps in verbose mode
1023
+ if (options.verbose && result.kept.length > 0) {
1024
+ log(` kept:`, 'gray');
1025
+ for (const { dep, reason } of result.kept.slice(0, 5)) {
1026
+ log(` ✓ ${dep} (${reason})`, 'gray');
1027
+ }
1028
+ if (result.kept.length > 5) {
1029
+ log(` ... and ${result.kept.length - 5} more`, 'gray');
1030
+ }
1031
+ }
1032
+ }
1033
+ }
1034
+
1035
+ if (totalRemoved > 0) {
1036
+ hasChanges = true;
1037
+ log(`\n ✅ Removed ${totalRemoved} unused dependency(ies)`, 'green');
1038
+ log(` 📊 Scanned ${totalScanned} total dependencies across ${packages.length} packages\n`, 'gray');
1039
+ } else {
1040
+ log(' ✅ No unused dependencies found\n', 'green');
1041
+ }
1042
+ }
1043
+
1044
+ // Add missing dependencies
1045
+ if (options.addMissing) {
1046
+ log('➕ Adding missing workspace dependencies...\n', 'cyan');
1047
+
1048
+ let totalAdded = 0;
1049
+ const additions = [];
1050
+
1051
+ for (const packagePath of packages) {
1052
+ const result = addMissingDependencies(packagePath, options.dryRun, packages);
1053
+ if (result.added > 0) {
1054
+ totalAdded += result.added;
1055
+ additions.push(result);
1056
+
1057
+ log(` ${result.packageName}`, 'yellow');
1058
+ for (const dep of result.deps) {
1059
+ log(` + ${dep}`, 'gray');
1060
+ }
1061
+ }
1062
+ }
1063
+
1064
+ if (totalAdded > 0) {
1065
+ hasChanges = true;
1066
+ log(`\n ✅ Added ${totalAdded} missing dependency(ies)\n`, 'green');
1067
+ } else {
1068
+ log(' ✅ No missing dependencies found\n', 'green');
1069
+ }
1070
+ }
1071
+
1072
+ // Add missing npm dependencies
1073
+ if (options.addMissingNpm) {
1074
+ log('📦 Adding missing npm dependencies...\n', 'cyan');
1075
+
1076
+ let totalAdded = 0;
1077
+ const additions = [];
1078
+
1079
+ for (const packagePath of packages) {
1080
+ const result = addMissingNpmDependencies(packagePath, options.dryRun);
1081
+ if (result.added > 0) {
1082
+ totalAdded += result.added;
1083
+ additions.push(result);
1084
+
1085
+ log(` ${result.packageName}`, 'yellow');
1086
+ for (const dep of result.deps) {
1087
+ log(` + ${dep}`, 'gray');
1088
+ }
1089
+ }
1090
+ }
1091
+
1092
+ if (totalAdded > 0) {
1093
+ hasChanges = true;
1094
+ log(`\n ✅ Added ${totalAdded} missing npm package(s)\n`, 'green');
1095
+ log(` 💡 Run 'pnpm install' to install new dependencies\n`, 'cyan');
1096
+ } else {
1097
+ log(' ✅ No missing npm dependencies found\n', 'green');
1098
+ }
1099
+ }
1100
+
1101
+ // Align versions
1102
+ if (options.alignVersions) {
1103
+ log('🔄 Aligning dependency versions...\n', 'cyan');
1104
+
1105
+ const { totalAligned, changes } = alignDependencyVersions(packages, options.dryRun);
1106
+
1107
+ if (totalAligned > 0) {
1108
+ hasChanges = true;
1109
+
1110
+ // Group by dependency
1111
+ const byDep = new Map();
1112
+ for (const change of changes) {
1113
+ if (!byDep.has(change.dep)) {
1114
+ byDep.set(change.dep, []);
1115
+ }
1116
+ byDep.get(change.dep).push(change);
1117
+ }
1118
+
1119
+ for (const [dep, depChanges] of byDep.entries()) {
1120
+ const targetVersion = depChanges[0].to;
1121
+ log(` ${dep} → ${targetVersion}`, 'yellow');
1122
+ log(` ${depChanges.length} package(s) updated`, 'gray');
1123
+ }
1124
+
1125
+ log(`\n ✅ Aligned ${totalAligned} dependency version(s)\n`, 'green');
1126
+ } else {
1127
+ log(' ✅ No version conflicts found\n', 'green');
1128
+ }
1129
+ }
1130
+
1131
+ // Summary
1132
+ if (!options.removeUnused && !options.addMissing && !options.alignVersions) {
1133
+ log('⚠️ No fix mode specified\n', 'yellow');
1134
+ log('Fix modes:', 'blue');
1135
+ log(' --remove-unused Remove unused dependencies', 'gray');
1136
+ log(' --add-missing Add missing workspace dependencies', 'gray');
1137
+ log(' --align-versions Align duplicate dependency versions', 'gray');
1138
+ log(' --all Apply all fixes', 'gray');
1139
+ log('', 'reset');
1140
+ log('Analysis modes:', 'blue');
1141
+ log(' --stats Show dependency statistics', 'gray');
1142
+ log(' --orphans Find packages not used by anyone', 'gray');
1143
+ log('', 'reset');
1144
+ log('Options:', 'blue');
1145
+ log(' --dry-run Preview changes without applying', 'gray');
1146
+ log(' --verbose, -v Show why dependencies were kept', 'gray');
1147
+ log(' --json Output results in JSON format', 'gray');
1148
+ log(' --package=<name> Process only specific package', 'gray');
1149
+ log('');
1150
+ log('Examples:', 'blue');
1151
+ log(' npx kb-devkit-fix-deps --stats', 'gray');
1152
+ log(' npx kb-devkit-fix-deps --orphans', 'gray');
1153
+ log(' npx kb-devkit-fix-deps --orphans --json', 'gray');
1154
+ log(' npx kb-devkit-fix-deps --remove-unused --dry-run', 'gray');
1155
+ log(' npx kb-devkit-fix-deps --all --package=cli-core', 'gray');
1156
+ log('');
1157
+ process.exit(0);
1158
+ }
1159
+
1160
+ if (options.dryRun && hasChanges) {
1161
+ log('💡 Run without --dry-run to apply changes\n', 'blue');
1162
+ } else if (!options.dryRun && hasChanges) {
1163
+ log('💡 Don\'t forget to run: pnpm install\n', 'blue');
1164
+ }
1165
+
1166
+ process.exit(0);
1167
+ }
1168
+
1169
+ main();