@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,199 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * @kb-labs/devkit - Package Build Freshness Tracker
5
+ *
6
+ * Detects stale packages in monorepo dependency chains.
7
+ * Critical: identifies when package A uses old version of dependency B,
8
+ * even though B has been rebuilt with newer version.
9
+ *
10
+ * Usage:
11
+ * npx kb-devkit-freshness # Default table output
12
+ * npx kb-devkit-freshness --json # JSON for AI agents
13
+ * npx kb-devkit-freshness --md # Markdown for docs
14
+ * npx kb-devkit-freshness --tree # Dependency tree view
15
+ * npx kb-devkit-freshness --package=cli-core # Single package analysis
16
+ * npx kb-devkit-freshness --only-stale # Show only stale packages
17
+ * npx kb-devkit-freshness --age-days=30 # Packages not built in 30+ days
18
+ * npx kb-devkit-freshness --high-impact=5 # Packages affecting 5+ others
19
+ * npx kb-devkit-freshness --suggest-rebuild # Show rebuild order
20
+ */
21
+
22
+ import fs from 'node:fs';
23
+ import path from 'node:path';
24
+ import { fileURLToPath } from 'node:url';
25
+
26
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
27
+
28
+ // ANSI colors
29
+ const colors = {
30
+ reset: '\x1b[0m',
31
+ red: '\x1b[31m',
32
+ green: '\x1b[32m',
33
+ yellow: '\x1b[33m',
34
+ blue: '\x1b[34m',
35
+ magenta: '\x1b[35m',
36
+ cyan: '\x1b[36m',
37
+ gray: '\x1b[90m',
38
+ bold: '\x1b[1m',
39
+ };
40
+
41
+ function log(message, color = 'reset') {
42
+ if (!options.json) {
43
+ console.log(`${colors[color]}${message}${colors.reset}`);
44
+ }
45
+ }
46
+
47
+ // Parse CLI arguments
48
+ const args = process.argv.slice(2);
49
+ const options = {
50
+ package: args.find((arg) => arg.startsWith('--package='))?.split('=')[1],
51
+ json: args.includes('--json'),
52
+ md: args.includes('--md'),
53
+ tree: args.includes('--tree'),
54
+ onlyStale: args.includes('--only-stale'),
55
+ suggestRebuild: args.includes('--suggest-rebuild'),
56
+ ageDays: parseInt(args.find((arg) => arg.startsWith('--age-days='))?.split('=')[1]) || null,
57
+ highImpact: parseInt(args.find((arg) => arg.startsWith('--high-impact='))?.split('=')[1]) || null,
58
+ help: args.includes('--help') || args.includes('-h'),
59
+ };
60
+
61
+ /**
62
+ * Show help text
63
+ */
64
+ function showHelp() {
65
+ console.log(`
66
+ 📦 KB Labs Package Build Freshness Tracker
67
+
68
+ Detects stale packages in monorepo dependency chains.
69
+
70
+ USAGE:
71
+ npx kb-devkit-freshness [options]
72
+
73
+ OPTIONS:
74
+ --json Output JSON format (AI-optimized)
75
+ --md Output Markdown format
76
+ --tree Show dependency tree view
77
+ --package=<name> Analyze single package
78
+ --only-stale Show only stale packages
79
+ --age-days=<N> Show packages not built in N+ days
80
+ --high-impact=<N> Show packages affecting N+ others
81
+ --suggest-rebuild Show suggested rebuild order
82
+ -h, --help Show this help
83
+
84
+ EXAMPLES:
85
+ # Default table output
86
+ npx kb-devkit-freshness
87
+
88
+ # JSON for AI agents
89
+ npx kb-devkit-freshness --json
90
+
91
+ # Single package analysis with tree view
92
+ npx kb-devkit-freshness --package=cli-core --tree
93
+
94
+ # Find high-impact stale packages
95
+ npx kb-devkit-freshness --only-stale --high-impact=5
96
+
97
+ # Packages not built in 30+ days
98
+ npx kb-devkit-freshness --age-days=30 --suggest-rebuild
99
+
100
+ STALENESS CRITERIA:
101
+ 1. Version Mismatch - Built version != current version
102
+ 2. Time-based - Source files newer than dist files
103
+ 3. Dependency Staleness - Dependency rebuilt after this package (CRITICAL!)
104
+
105
+ For more info: https://github.com/kb-labs/kb-labs
106
+ `);
107
+ }
108
+
109
+ /**
110
+ * Main function
111
+ */
112
+ async function main() {
113
+ if (options.help) {
114
+ showHelp();
115
+ process.exit(0);
116
+ }
117
+
118
+ const rootDir = process.cwd();
119
+
120
+ if (!options.json && !options.md) {
121
+ log('\n🔍 KB Labs Package Build Freshness Tracker\n', 'blue');
122
+ if (options.package) {
123
+ log(`Analyzing package: ${options.package}\n`, 'gray');
124
+ } else {
125
+ log('Analyzing all packages...\n', 'gray');
126
+ }
127
+ }
128
+
129
+ try {
130
+ // Phase 1: Find packages
131
+ const { findPackages } = await import('../src/freshness/metadata.js');
132
+ const packages = findPackages(rootDir, options.package);
133
+
134
+ if (packages.length === 0) {
135
+ log('⚠️ No KB Labs packages found', 'yellow');
136
+ log(' Run this command from the monorepo root\n', 'gray');
137
+ process.exit(0);
138
+ }
139
+
140
+ if (!options.json && !options.md) {
141
+ log(`Found ${packages.length} package(s) to analyze\n`, 'gray');
142
+ }
143
+
144
+ // Phase 2: Collect metadata
145
+ const { collectAllMetadata } = await import('../src/freshness/metadata.js');
146
+ const metadata = await collectAllMetadata(packages);
147
+
148
+ // Phase 3: Build dependency graph
149
+ const { buildDependencyGraph } = await import('../src/freshness/graph.js');
150
+ const graph = buildDependencyGraph(packages, metadata);
151
+
152
+ // Phase 4: Analyze staleness
153
+ const { analyzeStaleness } = await import('../src/freshness/analyzer.js');
154
+ const freshnessResults = new Map();
155
+
156
+ for (const pkg of packages) {
157
+ const packageJson = JSON.parse(fs.readFileSync(pkg, 'utf-8'));
158
+ const packageName = packageJson.name;
159
+ const meta = metadata.get(packageName);
160
+
161
+ if (meta) {
162
+ const result = analyzeStaleness(packageName, meta, graph, metadata, options);
163
+ freshnessResults.set(packageName, result);
164
+ }
165
+ }
166
+
167
+ // Phase 5: Propagate staleness and calculate impact
168
+ const { propagateStaleness } = await import('../src/freshness/propagate.js');
169
+ propagateStaleness(freshnessResults, graph);
170
+
171
+ // Phase 6: Format output
172
+ const formatters = await import('../src/freshness/formatters.js');
173
+
174
+ if (options.json) {
175
+ formatters.printJSON(freshnessResults, graph, metadata, options);
176
+ } else if (options.md) {
177
+ formatters.printMarkdown(freshnessResults, graph, metadata, options);
178
+ } else if (options.tree) {
179
+ formatters.printTree(freshnessResults, graph, metadata, options);
180
+ } else {
181
+ formatters.printTable(freshnessResults, graph, metadata, options);
182
+ }
183
+
184
+ // Exit code: 1 if stale packages found, 0 otherwise
185
+ const staleCount = Array.from(freshnessResults.values()).filter(
186
+ (r) => r.status === 'stale'
187
+ ).length;
188
+
189
+ process.exit(staleCount > 0 ? 1 : 0);
190
+ } catch (error) {
191
+ console.error(`\n❌ Error: ${error.message}\n`);
192
+ if (process.env.DEBUG) {
193
+ console.error(error.stack);
194
+ }
195
+ process.exit(2);
196
+ }
197
+ }
198
+
199
+ main();
@@ -0,0 +1,489 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * @kb-labs/devkit - Health Check
5
+ *
6
+ * Comprehensive monorepo health check that runs all critical validations
7
+ * and provides a single health score with actionable recommendations.
8
+ *
9
+ * Checks:
10
+ * 1. Missing runtime dependencies (imports not in package.json)
11
+ * 2. Workspace vs link inconsistencies (cross-repo deps)
12
+ * 3. Build failures
13
+ * 4. TypeScript errors
14
+ * 5. Circular dependencies
15
+ * 6. Orphan packages
16
+ * 7. Duplicate dependencies
17
+ * 8. Naming conventions
18
+ *
19
+ * Usage:
20
+ * kb-devkit-health # Full health check
21
+ * kb-devkit-health --quick # Skip slow checks (builds, types)
22
+ * kb-devkit-health --json # JSON output
23
+ * kb-devkit-health --package cli-core # Check specific package
24
+ */
25
+
26
+ import fs from 'node:fs';
27
+ import path from 'node:path';
28
+ import { fileURLToPath } from 'node:url';
29
+ import { execSync } from 'node:child_process';
30
+
31
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
32
+
33
+ // ANSI colors
34
+ const colors = {
35
+ reset: '\x1b[0m',
36
+ red: '\x1b[31m',
37
+ green: '\x1b[32m',
38
+ yellow: '\x1b[33m',
39
+ blue: '\x1b[34m',
40
+ cyan: '\x1b[36m',
41
+ gray: '\x1b[90m',
42
+ bold: '\x1b[1m',
43
+ };
44
+
45
+ // Parse CLI args
46
+ const args = process.argv.slice(2);
47
+ const quick = args.includes('--quick');
48
+ const jsonOutput = args.includes('--json');
49
+ const packageFilter = args.find((arg) => arg.startsWith('--package='))?.split('=')[1];
50
+
51
+ // Find monorepo root
52
+ function findMonorepoRoot() {
53
+ let dir = process.cwd();
54
+ while (dir !== '/') {
55
+ if (fs.existsSync(path.join(dir, 'pnpm-workspace.yaml'))) {
56
+ return dir;
57
+ }
58
+ dir = path.dirname(dir);
59
+ }
60
+ throw new Error('Not in a pnpm workspace');
61
+ }
62
+
63
+ const rootDir = findMonorepoRoot();
64
+
65
+ /**
66
+ * Find all packages in monorepo
67
+ */
68
+ function findAllPackages() {
69
+ const packages = [];
70
+ const searchDirs = [
71
+ 'packages',
72
+ 'kb-labs-*/packages',
73
+ ];
74
+
75
+ for (const pattern of searchDirs) {
76
+ const dirs = pattern.includes('*')
77
+ ? execSync(`find ${rootDir} -type d -path "*/${pattern}" 2>/dev/null || true`, { encoding: 'utf-8' })
78
+ .split('\n')
79
+ .filter(Boolean)
80
+ : [path.join(rootDir, pattern)];
81
+
82
+ for (const dir of dirs) {
83
+ if (!fs.existsSync(dir)) {continue;}
84
+
85
+ const subDirs = fs.readdirSync(dir, { withFileTypes: true })
86
+ .filter((d) => d.isDirectory())
87
+ .map((d) => path.join(dir, d.name));
88
+
89
+ for (const subDir of subDirs) {
90
+ const pkgPath = path.join(subDir, 'package.json');
91
+ if (fs.existsSync(pkgPath)) {
92
+ try {
93
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
94
+ if (packageFilter && !pkg.name.includes(packageFilter)) {continue;}
95
+ packages.push({ name: pkg.name, path: subDir, pkg });
96
+ } catch (err) {
97
+ // Skip invalid package.json
98
+ }
99
+ }
100
+ }
101
+ }
102
+ }
103
+
104
+ return packages;
105
+ }
106
+
107
+ /**
108
+ * Check if dist/ is stale (older than src/)
109
+ */
110
+ function isDistStale(pkgPath) {
111
+ const distFile = path.join(pkgPath, 'dist/index.js');
112
+ if (!fs.existsSync(distFile)) {return false;}
113
+
114
+ const distMtime = fs.statSync(distFile).mtime.getTime();
115
+
116
+ // Check all source files
117
+ const srcDir = path.join(pkgPath, 'src');
118
+ if (!fs.existsSync(srcDir)) {return false;}
119
+
120
+ const checkDir = (dir) => {
121
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
122
+ for (const entry of entries) {
123
+ const fullPath = path.join(dir, entry.name);
124
+ if (entry.isDirectory()) {
125
+ if (checkDir(fullPath)) {return true;}
126
+ } else if (entry.name.endsWith('.ts') || entry.name.endsWith('.tsx')) {
127
+ const srcMtime = fs.statSync(fullPath).mtime.getTime();
128
+ if (srcMtime > distMtime) {return true;}
129
+ }
130
+ }
131
+ return false;
132
+ };
133
+
134
+ return checkDir(srcDir);
135
+ }
136
+
137
+ /**
138
+ * Check 1: Missing runtime dependencies
139
+ * Parse dist/index.js and check if all imports are declared in package.json
140
+ * NOTE: Results may be inaccurate if dist/ is stale
141
+ */
142
+ function checkMissingRuntimeDeps(packages) {
143
+ const issues = [];
144
+ const stalePackages = [];
145
+
146
+ for (const { name, path: pkgPath, pkg } of packages) {
147
+ const distFile = path.join(pkgPath, 'dist/index.js');
148
+ if (!fs.existsSync(distFile)) {continue;}
149
+
150
+ // Check if dist is stale
151
+ if (isDistStale(pkgPath)) {
152
+ stalePackages.push(name);
153
+ }
154
+
155
+ const content = fs.readFileSync(distFile, 'utf-8');
156
+ const importMatches = content.matchAll(/from ['"]([^'"]+)['"]/g);
157
+
158
+ const declaredDeps = {
159
+ ...pkg.dependencies,
160
+ ...pkg.peerDependencies,
161
+ };
162
+
163
+ // Node.js built-in modules (don't need to be in package.json)
164
+ const nodeBuiltins = new Set([
165
+ 'assert', 'buffer', 'child_process', 'cluster', 'console', 'constants',
166
+ 'crypto', 'dgram', 'dns', 'domain', 'events', 'fs', 'http', 'https',
167
+ 'module', 'net', 'os', 'path', 'perf_hooks', 'process', 'punycode',
168
+ 'querystring', 'readline', 'repl', 'stream', 'string_decoder', 'sys',
169
+ 'timers', 'tls', 'tty', 'url', 'util', 'v8', 'vm', 'wasi', 'worker_threads',
170
+ 'zlib', 'fs/promises',
171
+ ]);
172
+
173
+ const missing = new Set();
174
+
175
+ for (const [, importPath] of importMatches) {
176
+ // Skip relative imports and node builtins
177
+ if (importPath.startsWith('.') || importPath.startsWith('node:')) {continue;}
178
+
179
+ // Extract package name (handle @scope/package)
180
+ const pkgName = importPath.startsWith('@')
181
+ ? importPath.split('/').slice(0, 2).join('/')
182
+ : importPath.split('/')[0];
183
+
184
+ // Skip Node.js built-ins
185
+ if (nodeBuiltins.has(pkgName)) {continue;}
186
+
187
+ // Check if declared
188
+ if (!declaredDeps[pkgName]) {
189
+ missing.add(pkgName);
190
+ }
191
+ }
192
+
193
+ if (missing.size > 0) {
194
+ issues.push({
195
+ package: name,
196
+ missing: Array.from(missing),
197
+ });
198
+ }
199
+ }
200
+
201
+ return { issues, stalePackages };
202
+ }
203
+
204
+ /**
205
+ * Check 2: Workspace vs link inconsistencies
206
+ * Find cross-repo dependencies that use workspace:* instead of link:
207
+ */
208
+ function checkWorkspaceLinkIssues(packages) {
209
+ const issues = [];
210
+
211
+ // Build map of package locations by repo
212
+ const packagesByRepo = new Map();
213
+ for (const { name, path: pkgPath } of packages) {
214
+ const repo = pkgPath.split('/').find((p) => p.startsWith('kb-labs-'));
215
+ if (!packagesByRepo.has(repo)) {
216
+ packagesByRepo.set(repo, []);
217
+ }
218
+ packagesByRepo.get(repo).push(name);
219
+ }
220
+
221
+ for (const { name, path: pkgPath, pkg } of packages) {
222
+ const myRepo = pkgPath.split('/').find((p) => p.startsWith('kb-labs-'));
223
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
224
+
225
+ for (const [depName, depVersion] of Object.entries(deps)) {
226
+ if (depVersion !== 'workspace:*') {continue;}
227
+
228
+ // Check if this dep is in a different repo
229
+ let depRepo = null;
230
+ for (const [repo, pkgs] of packagesByRepo.entries()) {
231
+ if (pkgs.includes(depName)) {
232
+ depRepo = repo;
233
+ break;
234
+ }
235
+ }
236
+
237
+ if (depRepo && depRepo !== myRepo) {
238
+ issues.push({
239
+ package: name,
240
+ dependency: depName,
241
+ myRepo,
242
+ depRepo,
243
+ currentVersion: depVersion,
244
+ });
245
+ }
246
+ }
247
+ }
248
+
249
+ return issues;
250
+ }
251
+
252
+ /**
253
+ * Check 3: Build failures
254
+ * Try to build each package and collect errors
255
+ */
256
+ function checkBuilds(packages) {
257
+ const issues = [];
258
+
259
+ for (const { name, path: pkgPath } of packages) {
260
+ try {
261
+ execSync('pnpm run build', {
262
+ cwd: pkgPath,
263
+ stdio: 'pipe',
264
+ encoding: 'utf-8',
265
+ timeout: 30000, // 30s timeout per package
266
+ });
267
+ } catch (err) {
268
+ issues.push({
269
+ package: name,
270
+ error: err.stderr?.trim() || err.message,
271
+ });
272
+ }
273
+ }
274
+
275
+ return issues;
276
+ }
277
+
278
+ /**
279
+ * Check 4: TypeScript errors (using existing types-audit)
280
+ */
281
+ function checkTypeScriptErrors() {
282
+ try {
283
+ const output = execSync('node kb-labs-devkit/bin/devkit-types-audit.mjs --errors-only --json', {
284
+ cwd: rootDir,
285
+ encoding: 'utf-8',
286
+ stdio: 'pipe',
287
+ });
288
+ const result = JSON.parse(output);
289
+ return result.packagesWithErrors || [];
290
+ } catch (err) {
291
+ return null; // Tool might not exist yet
292
+ }
293
+ }
294
+
295
+ /**
296
+ * Run all checks and compute health score
297
+ */
298
+ async function runHealthCheck() {
299
+ const startTime = Date.now();
300
+ const packages = findAllPackages();
301
+
302
+ if (!jsonOutput) {
303
+ console.log(`${colors.cyan}${colors.bold}🏥 KB Labs Monorepo Health Check${colors.reset}\n`);
304
+ console.log(`${colors.gray}Analyzing ${packages.length} package(s)...${colors.reset}\n`);
305
+ }
306
+
307
+ const depsCheck = checkMissingRuntimeDeps(packages);
308
+ const results = {
309
+ missingRuntimeDeps: depsCheck.issues,
310
+ stalePackages: depsCheck.stalePackages,
311
+ workspaceLinkIssues: checkWorkspaceLinkIssues(packages),
312
+ buildFailures: quick ? [] : checkBuilds(packages),
313
+ typeScriptErrors: quick ? [] : checkTypeScriptErrors(),
314
+ };
315
+
316
+ // Compute health score
317
+ let score = 100;
318
+ const criticalIssues = [];
319
+ const warnings = [];
320
+
321
+ // Stale packages warning (doesn't affect score, but important)
322
+ if (results.stalePackages.length > 0) {
323
+ warnings.push({
324
+ type: 'stale-builds',
325
+ severity: 'warning',
326
+ count: results.stalePackages.length,
327
+ message: `${results.stalePackages.length} package(s) with stale dist/ (results may be inaccurate)`,
328
+ details: results.stalePackages,
329
+ recommendation: 'Run: npx kb-devkit-freshness --only-stale',
330
+ });
331
+ }
332
+
333
+ // Missing runtime deps: -10 per package
334
+ if (results.missingRuntimeDeps.length > 0) {
335
+ const penalty = Math.min(results.missingRuntimeDeps.length * 10, 50);
336
+ score -= penalty;
337
+ criticalIssues.push({
338
+ type: 'missing-runtime-deps',
339
+ severity: 'critical',
340
+ count: results.missingRuntimeDeps.length,
341
+ message: `${results.missingRuntimeDeps.length} package(s) with missing runtime dependencies`,
342
+ details: results.missingRuntimeDeps,
343
+ });
344
+ }
345
+
346
+ // Workspace vs link issues: -5 per package
347
+ if (results.workspaceLinkIssues.length > 0) {
348
+ const penalty = Math.min(results.workspaceLinkIssues.length * 5, 25);
349
+ score -= penalty;
350
+ criticalIssues.push({
351
+ type: 'workspace-link-issues',
352
+ severity: 'critical',
353
+ count: results.workspaceLinkIssues.length,
354
+ message: `${results.workspaceLinkIssues.length} cross-repo dep(s) using workspace:* instead of link:`,
355
+ details: results.workspaceLinkIssues,
356
+ });
357
+ }
358
+
359
+ // Build failures: -15 per package
360
+ if (results.buildFailures.length > 0) {
361
+ const penalty = Math.min(results.buildFailures.length * 15, 60);
362
+ score -= penalty;
363
+ criticalIssues.push({
364
+ type: 'build-failures',
365
+ severity: 'critical',
366
+ count: results.buildFailures.length,
367
+ message: `${results.buildFailures.length} package(s) failed to build`,
368
+ details: results.buildFailures,
369
+ });
370
+ }
371
+
372
+ // TypeScript errors: warning only
373
+ if (results.typeScriptErrors && results.typeScriptErrors.length > 0) {
374
+ warnings.push({
375
+ type: 'typescript-errors',
376
+ severity: 'warning',
377
+ count: results.typeScriptErrors.length,
378
+ message: `${results.typeScriptErrors.length} package(s) with TypeScript errors`,
379
+ details: results.typeScriptErrors,
380
+ });
381
+ }
382
+
383
+ // Compute grade
384
+ const grade = score >= 90 ? 'A' : score >= 80 ? 'B' : score >= 70 ? 'C' : score >= 60 ? 'D' : 'F';
385
+
386
+ const duration = Date.now() - startTime;
387
+
388
+ // Output results
389
+ if (jsonOutput) {
390
+ console.log(JSON.stringify({
391
+ score,
392
+ grade,
393
+ duration,
394
+ packages: packages.length,
395
+ criticalIssues,
396
+ warnings,
397
+ details: results,
398
+ }, null, 2));
399
+ } else {
400
+ printResults(score, grade, criticalIssues, warnings, duration);
401
+ }
402
+
403
+ // Exit with error if critical issues found
404
+ if (criticalIssues.length > 0) {
405
+ process.exit(1);
406
+ }
407
+ }
408
+
409
+ /**
410
+ * Pretty print results
411
+ */
412
+ function printResults(score, grade, criticalIssues, warnings, duration) {
413
+ // Critical issues
414
+ if (criticalIssues.length > 0) {
415
+ console.log(`${colors.red}${colors.bold}❌ CRITICAL ISSUES (blocking)${colors.reset}\n`);
416
+ for (const issue of criticalIssues) {
417
+ console.log(` ${colors.red}•${colors.reset} ${issue.message}`);
418
+
419
+ // Show top 5 details
420
+ const details = issue.details.slice(0, 5);
421
+ for (const detail of details) {
422
+ if (issue.type === 'missing-runtime-deps') {
423
+ console.log(` ${colors.gray}${detail.package}: ${detail.missing.join(', ')}${colors.reset}`);
424
+ } else if (issue.type === 'workspace-link-issues') {
425
+ console.log(` ${colors.gray}${detail.package} → ${detail.dependency} (${detail.myRepo} → ${detail.depRepo})${colors.reset}`);
426
+ } else if (issue.type === 'build-failures') {
427
+ console.log(` ${colors.gray}${detail.package}${colors.reset}`);
428
+ }
429
+ }
430
+
431
+ if (issue.details.length > 5) {
432
+ console.log(` ${colors.gray}... and ${issue.details.length - 5} more${colors.reset}`);
433
+ }
434
+ console.log();
435
+ }
436
+ }
437
+
438
+ // Warnings
439
+ if (warnings.length > 0) {
440
+ console.log(`${colors.yellow}${colors.bold}⚠️ WARNINGS${colors.reset}\n`);
441
+ for (const warning of warnings) {
442
+ console.log(` ${colors.yellow}•${colors.reset} ${warning.message}`);
443
+
444
+ // Show stale packages
445
+ if (warning.type === 'stale-builds' && warning.details.length > 0) {
446
+ const showPackages = warning.details.slice(0, 5);
447
+ for (const pkg of showPackages) {
448
+ console.log(` ${colors.gray}${pkg}${colors.reset}`);
449
+ }
450
+ if (warning.details.length > 5) {
451
+ console.log(` ${colors.gray}... and ${warning.details.length - 5} more${colors.reset}`);
452
+ }
453
+ console.log(` ${colors.cyan}→ ${warning.recommendation}${colors.reset}`);
454
+ }
455
+ }
456
+ console.log();
457
+ }
458
+
459
+ // Health score
460
+ const scoreColor = score >= 80 ? colors.green : score >= 60 ? colors.yellow : colors.red;
461
+ console.log(`${colors.bold}Health Score:${colors.reset} ${scoreColor}${score}/100 (Grade ${grade})${colors.reset}`);
462
+ console.log(`${colors.gray}Duration: ${duration}ms${colors.reset}\n`);
463
+
464
+ // Recommendations
465
+ if (criticalIssues.length > 0) {
466
+ console.log(`${colors.bold}Recommended Actions:${colors.reset}\n`);
467
+
468
+ if (criticalIssues.some((i) => i.type === 'missing-runtime-deps')) {
469
+ console.log(` 1. Fix missing runtime dependencies:`);
470
+ console.log(` ${colors.cyan}kb-devkit-fix-deps --add-missing${colors.reset}\n`);
471
+ }
472
+
473
+ if (criticalIssues.some((i) => i.type === 'workspace-link-issues')) {
474
+ console.log(` 2. Fix workspace vs link issues manually in package.json`);
475
+ console.log(` Change ${colors.gray}workspace:*${colors.reset} → ${colors.cyan}link:../../../kb-labs-xxx/packages/xxx${colors.reset}\n`);
476
+ }
477
+
478
+ if (criticalIssues.some((i) => i.type === 'build-failures')) {
479
+ console.log(` 3. Fix build failures:`);
480
+ console.log(` ${colors.cyan}pnpm --filter <package> run build${colors.reset}\n`);
481
+ }
482
+ }
483
+ }
484
+
485
+ // Run health check
486
+ runHealthCheck().catch((err) => {
487
+ console.error(`${colors.red}Health check failed: ${err.message}${colors.reset}`);
488
+ process.exit(1);
489
+ });