@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,203 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * @kb-labs/devkit - Naming Convention Validator
5
+ *
6
+ * Validates that all packages follow the Pyramid Rule:
7
+ * @kb-labs/{repo}-{package}
8
+ *
9
+ * Folder name MUST match {repo}-{package}
10
+ */
11
+
12
+ import fs from 'node:fs';
13
+ import path from 'node:path';
14
+ import { fileURLToPath } from 'node:url';
15
+
16
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
17
+
18
+ // ANSI colors
19
+ const colors = {
20
+ reset: '\x1b[0m',
21
+ red: '\x1b[31m',
22
+ green: '\x1b[32m',
23
+ yellow: '\x1b[33m',
24
+ blue: '\x1b[34m',
25
+ gray: '\x1b[90m',
26
+ };
27
+
28
+ function log(message, color = 'reset') {
29
+ console.log(`${colors[color]}${message}${colors.reset}`);
30
+ }
31
+
32
+ function extractRepoName(repoPath) {
33
+ // kb-labs-core → core
34
+ // kb-labs-cli → cli
35
+ const match = repoPath.match(/kb-labs-([^/]+)/);
36
+ return match ? match[1] : null;
37
+ }
38
+
39
+ function validatePackage(packageJsonPath, repoName) {
40
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
41
+ const packageName = packageJson.name;
42
+
43
+ if (!packageName || !packageName.startsWith('@kb-labs/')) {
44
+ return { valid: true, skip: true, reason: 'Not a @kb-labs package' };
45
+ }
46
+
47
+ const folderPath = path.dirname(packageJsonPath);
48
+ const folderName = path.basename(folderPath);
49
+
50
+ // Extract package name without @kb-labs/ prefix
51
+ const shortName = packageName.replace('@kb-labs/', '');
52
+
53
+ // Expected pattern: {repo}-{package}
54
+ const expectedPrefix = `${repoName}-`;
55
+
56
+ // Check 1: Package name should start with repo prefix
57
+ if (!shortName.startsWith(expectedPrefix)) {
58
+ return {
59
+ valid: false,
60
+ packageName,
61
+ folderName,
62
+ expectedPackageName: `@kb-labs/${expectedPrefix}${folderName.replace(new RegExp(`^${repoName}-`), '')}`,
63
+ issue: `Package name missing repo prefix`,
64
+ suggestion: `Rename to @kb-labs/${expectedPrefix}${folderName.replace(new RegExp(`^${repoName}-`), '')}`,
65
+ };
66
+ }
67
+
68
+ // Check 2: Folder name should match package name
69
+ if (folderName !== shortName) {
70
+ return {
71
+ valid: false,
72
+ packageName,
73
+ folderName,
74
+ expectedFolderName: shortName,
75
+ issue: 'Folder name does not match package name',
76
+ suggestion: `Rename folder to ${shortName}`,
77
+ };
78
+ }
79
+
80
+ // Check 3: Folder name should have repo prefix
81
+ if (!folderName.startsWith(expectedPrefix)) {
82
+ return {
83
+ valid: false,
84
+ packageName,
85
+ folderName,
86
+ expectedFolderName: `${expectedPrefix}${folderName}`,
87
+ issue: 'Folder name missing repo prefix',
88
+ suggestion: `Rename folder to ${expectedPrefix}${folderName}`,
89
+ };
90
+ }
91
+
92
+ return { valid: true, packageName, folderName };
93
+ }
94
+
95
+ function findPackages(rootDir) {
96
+ const packages = [];
97
+
98
+ // Find all kb-labs-* directories
99
+ const entries = fs.readdirSync(rootDir, { withFileTypes: true });
100
+
101
+ for (const entry of entries) {
102
+ if (!entry.isDirectory() || !entry.name.startsWith('kb-labs-')) {continue;}
103
+
104
+ const repoPath = path.join(rootDir, entry.name);
105
+ const packagesDir = path.join(repoPath, 'packages');
106
+
107
+ if (!fs.existsSync(packagesDir)) {continue;}
108
+
109
+ const repoName = extractRepoName(entry.name);
110
+ if (!repoName) {continue;}
111
+
112
+ // Find all package.json files in packages/
113
+ const packageDirs = fs.readdirSync(packagesDir, { withFileTypes: true });
114
+
115
+ for (const pkgDir of packageDirs) {
116
+ if (!pkgDir.isDirectory()) {continue;}
117
+
118
+ const packageJsonPath = path.join(packagesDir, pkgDir.name, 'package.json');
119
+
120
+ if (fs.existsSync(packageJsonPath)) {
121
+ packages.push({
122
+ path: packageJsonPath,
123
+ repoName,
124
+ repoPath: entry.name,
125
+ });
126
+ }
127
+ }
128
+ }
129
+
130
+ return packages;
131
+ }
132
+
133
+ function main() {
134
+ const rootDir = process.cwd();
135
+
136
+ log('\nšŸ“¦ KB Labs Naming Convention Validator\n', 'blue');
137
+ log('Checking Pyramid Rule: @kb-labs/{repo}-{package}\n', 'gray');
138
+
139
+ const packages = findPackages(rootDir);
140
+
141
+ if (packages.length === 0) {
142
+ log('āš ļø No KB Labs packages found in current directory', 'yellow');
143
+ log(' Run this command from the monorepo root containing kb-labs-* folders\n', 'gray');
144
+ process.exit(0);
145
+ }
146
+
147
+ log(`Found ${packages.length} packages to validate\n`, 'gray');
148
+
149
+ const violations = [];
150
+ const valid = [];
151
+
152
+ for (const pkg of packages) {
153
+ const result = validatePackage(pkg.path, pkg.repoName);
154
+
155
+ if (result.skip) {continue;}
156
+
157
+ if (result.valid) {
158
+ valid.push({ ...result, repo: pkg.repoPath });
159
+ } else {
160
+ violations.push({ ...result, repo: pkg.repoPath, path: pkg.path });
161
+ }
162
+ }
163
+
164
+ // Print valid packages
165
+ if (valid.length > 0) {
166
+ log('āœ… Valid packages:', 'green');
167
+ for (const v of valid) {
168
+ log(` ${v.folderName.padEnd(30)} → ${v.packageName}`, 'gray');
169
+ }
170
+ log('');
171
+ }
172
+
173
+ // Print violations
174
+ if (violations.length > 0) {
175
+ log(`āŒ Found ${violations.length} naming convention violation(s):\n`, 'red');
176
+
177
+ for (let i = 0; i < violations.length; i++) {
178
+ const v = violations[i];
179
+ log(`${i + 1}. ${v.repo}/packages/${v.folderName}/`, 'yellow');
180
+ log(` Issue: ${v.issue}`, 'red');
181
+ log(` Current package name: ${v.packageName}`, 'gray');
182
+ log(` Current folder name: ${v.folderName}`, 'gray');
183
+
184
+ if (v.expectedPackageName) {
185
+ log(` Expected package: ${v.expectedPackageName}`, 'green');
186
+ }
187
+ if (v.expectedFolderName) {
188
+ log(` Expected folder: ${v.expectedFolderName}`, 'green');
189
+ }
190
+
191
+ log(` Suggestion: ${v.suggestion}`, 'blue');
192
+ log('');
193
+ }
194
+
195
+ log('šŸ“– See docs/naming-convention.md for the full guide\n', 'gray');
196
+ process.exit(1);
197
+ }
198
+
199
+ log(`āœ… All ${valid.length} packages follow the Pyramid Rule!\n`, 'green');
200
+ process.exit(0);
201
+ }
202
+
203
+ main();
@@ -0,0 +1,452 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * @kb-labs/devkit - Dependency Visualizer
5
+ *
6
+ * Generates visualizations:
7
+ * 1. Dependency graph (who depends on whom)
8
+ * 2. Package size metrics
9
+ * 3. Dependency tree (hierarchical view)
10
+ * 4. Module coupling analysis
11
+ *
12
+ * Usage:
13
+ * kb-devkit-visualize # Show all visualizations
14
+ * kb-devkit-visualize --graph # Show dependency graph
15
+ * kb-devkit-visualize --package cli-core # Show dependencies of specific package
16
+ * kb-devkit-visualize --stats # Show package statistics
17
+ */
18
+
19
+ import fs from 'node:fs';
20
+ import path from 'node:path';
21
+ import { fileURLToPath } from 'node:url';
22
+
23
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
24
+
25
+ // ANSI colors
26
+ const colors = {
27
+ reset: '\x1b[0m',
28
+ red: '\x1b[31m',
29
+ green: '\x1b[32m',
30
+ yellow: '\x1b[33m',
31
+ blue: '\x1b[34m',
32
+ magenta: '\x1b[35m',
33
+ cyan: '\x1b[36m',
34
+ gray: '\x1b[90m',
35
+ bold: '\x1b[1m',
36
+ };
37
+
38
+ function log(message, color = 'reset') {
39
+ console.log(`${colors[color]}${message}${colors.reset}`);
40
+ }
41
+
42
+ // Parse CLI arguments
43
+ const args = process.argv.slice(2);
44
+ const options = {
45
+ package: args.find((arg) => arg.startsWith('--package='))?.split('=')[1],
46
+ graph: args.includes('--graph'),
47
+ stats: args.includes('--stats'),
48
+ tree: args.includes('--tree'),
49
+ all: !args.includes('--graph') && !args.includes('--stats') && !args.includes('--tree'),
50
+ };
51
+
52
+ /**
53
+ * Find all packages in monorepo
54
+ */
55
+ function findPackages(rootDir, filterPackage) {
56
+ const packages = [];
57
+ const entries = fs.readdirSync(rootDir, { withFileTypes: true });
58
+
59
+ for (const entry of entries) {
60
+ if (!entry.isDirectory() || !entry.name.startsWith('kb-labs-')) {continue;}
61
+
62
+ const repoPath = path.join(rootDir, entry.name);
63
+ const packagesDir = path.join(repoPath, 'packages');
64
+
65
+ if (!fs.existsSync(packagesDir)) {continue;}
66
+
67
+ const packageDirs = fs.readdirSync(packagesDir, { withFileTypes: true });
68
+
69
+ for (const pkgDir of packageDirs) {
70
+ if (!pkgDir.isDirectory()) {continue;}
71
+
72
+ // Filter by package name if specified
73
+ if (filterPackage && pkgDir.name !== filterPackage) {continue;}
74
+
75
+ const packageJsonPath = path.join(packagesDir, pkgDir.name, 'package.json');
76
+
77
+ if (fs.existsSync(packageJsonPath)) {
78
+ packages.push(packageJsonPath);
79
+ }
80
+ }
81
+ }
82
+
83
+ return packages;
84
+ }
85
+
86
+ /**
87
+ * Build dependency graph
88
+ */
89
+ function buildDependencyGraph(packages) {
90
+ const graph = new Map(); // package -> {dependencies, dependents, size}
91
+ const packageInfo = new Map(); // package -> {path, packageJson}
92
+
93
+ // First pass: collect all packages
94
+ for (const packagePath of packages) {
95
+ const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf-8'));
96
+ const packageName = packageJson.name;
97
+
98
+ if (!packageName || !packageName.startsWith('@kb-labs/')) {continue;}
99
+
100
+ const packageDir = path.dirname(packagePath);
101
+ const size = calculatePackageSize(packageDir);
102
+
103
+ graph.set(packageName, {
104
+ dependencies: [],
105
+ dependents: [],
106
+ size,
107
+ });
108
+
109
+ packageInfo.set(packageName, {
110
+ path: packagePath,
111
+ packageJson,
112
+ });
113
+ }
114
+
115
+ // Second pass: build dependency relationships
116
+ for (const [packageName, info] of packageInfo.entries()) {
117
+ const allDeps = {
118
+ ...info.packageJson.dependencies,
119
+ ...info.packageJson.devDependencies,
120
+ };
121
+
122
+ for (const dep of Object.keys(allDeps)) {
123
+ if (dep.startsWith('@kb-labs/') && graph.has(dep)) {
124
+ graph.get(packageName).dependencies.push(dep);
125
+ graph.get(dep).dependents.push(packageName);
126
+ }
127
+ }
128
+ }
129
+
130
+ return { graph, packageInfo };
131
+ }
132
+
133
+ /**
134
+ * Calculate package size (count of files)
135
+ */
136
+ function calculatePackageSize(packageDir) {
137
+ const srcDir = path.join(packageDir, 'src');
138
+
139
+ if (!fs.existsSync(srcDir)) {
140
+ return 0;
141
+ }
142
+
143
+ let fileCount = 0;
144
+ let lineCount = 0;
145
+
146
+ function walk(dir) {
147
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
148
+
149
+ for (const entry of entries) {
150
+ const fullPath = path.join(dir, entry.name);
151
+
152
+ if (entry.isDirectory()) {
153
+ walk(fullPath);
154
+ } else if (entry.isFile() && /\.(ts|tsx|js|jsx)$/.test(entry.name)) {
155
+ fileCount++;
156
+ const content = fs.readFileSync(fullPath, 'utf-8');
157
+ lineCount += content.split('\n').length;
158
+ }
159
+ }
160
+ }
161
+
162
+ walk(srcDir);
163
+
164
+ return { fileCount, lineCount };
165
+ }
166
+
167
+ /**
168
+ * Render dependency graph
169
+ */
170
+ function renderDependencyGraph(graph, packageInfo, filterPackage) {
171
+ log('\nšŸ“Š Dependency Graph:\n', 'blue');
172
+
173
+ const packages = Array.from(graph.keys());
174
+
175
+ if (filterPackage) {
176
+ // Show dependencies of specific package
177
+ const fullPackageName = packages.find((p) => p.includes(filterPackage));
178
+
179
+ if (!fullPackageName) {
180
+ log(` Package "${filterPackage}" not found\n`, 'red');
181
+ return;
182
+ }
183
+
184
+ const { dependencies, dependents } = graph.get(fullPackageName);
185
+
186
+ log(`Package: ${fullPackageName}\n`, 'cyan');
187
+
188
+ if (dependencies.length > 0) {
189
+ log(' Dependencies (packages this depends on):', 'yellow');
190
+ for (const dep of dependencies) {
191
+ log(` └─ ${dep}`, 'gray');
192
+ }
193
+ log('');
194
+ } else {
195
+ log(' No dependencies', 'gray');
196
+ log('');
197
+ }
198
+
199
+ if (dependents.length > 0) {
200
+ log(' Dependents (packages that depend on this):', 'green');
201
+ for (const dependent of dependents) {
202
+ log(` └─ ${dependent}`, 'gray');
203
+ }
204
+ log('');
205
+ } else {
206
+ log(' No dependents', 'gray');
207
+ log('');
208
+ }
209
+ } else {
210
+ // Show full graph overview
211
+ log('Packages by dependency count:\n', 'gray');
212
+
213
+ const byDependencyCount = packages
214
+ .map((p) => ({
215
+ name: p,
216
+ dependencies: graph.get(p).dependencies.length,
217
+ dependents: graph.get(p).dependents.length,
218
+ }))
219
+ .sort((a, b) => b.dependencies - a.dependencies);
220
+
221
+ for (let i = 0; i < Math.min(byDependencyCount.length, 20); i++) {
222
+ const pkg = byDependencyCount[i];
223
+ const depBar = 'ā–ˆ'.repeat(Math.min(pkg.dependencies, 30));
224
+ const deptBar = 'ā–“'.repeat(Math.min(pkg.dependents, 30));
225
+
226
+ log(`${(i + 1).toString().padStart(3)}. ${pkg.name}`, 'cyan');
227
+ log(` Deps: ${colors.yellow}${depBar}${colors.reset} ${pkg.dependencies}`, 'gray');
228
+ log(` Used: ${colors.green}${deptBar}${colors.reset} ${pkg.dependents}`, 'gray');
229
+ }
230
+
231
+ if (byDependencyCount.length > 20) {
232
+ log(`\n ... and ${byDependencyCount.length - 20} more packages`, 'gray');
233
+ }
234
+
235
+ log('');
236
+ }
237
+ }
238
+
239
+ /**
240
+ * Render package statistics
241
+ */
242
+ function renderPackageStats(graph, packageInfo) {
243
+ log('\nšŸ“ˆ Package Statistics:\n', 'blue');
244
+
245
+ const packages = Array.from(graph.keys());
246
+ const stats = packages.map((p) => ({
247
+ name: p,
248
+ ...graph.get(p),
249
+ repo: p.split('/')[1].split('-')[0], // Extract repo name (core, cli, etc.)
250
+ }));
251
+
252
+ // Group by repository
253
+ const byRepo = new Map();
254
+ for (const pkg of stats) {
255
+ if (!byRepo.has(pkg.repo)) {
256
+ byRepo.set(pkg.repo, []);
257
+ }
258
+ byRepo.get(pkg.repo).push(pkg);
259
+ }
260
+
261
+ log('By Repository:\n', 'gray');
262
+
263
+ const repoStats = Array.from(byRepo.entries()).map(([repo, packages]) => ({
264
+ repo,
265
+ count: packages.length,
266
+ totalFiles: packages.reduce((sum, p) => sum + (p.size.fileCount || 0), 0),
267
+ totalLines: packages.reduce((sum, p) => sum + (p.size.lineCount || 0), 0),
268
+ }));
269
+
270
+ repoStats.sort((a, b) => b.count - a.count);
271
+
272
+ for (const stat of repoStats) {
273
+ log(` ${stat.repo.padEnd(15)} ${stat.count.toString().padStart(3)} packages`, 'cyan');
274
+ log(` ${''.padEnd(15)} ${stat.totalFiles.toString().padStart(3)} files, ${stat.totalLines.toLocaleString().padStart(7)} lines`, 'gray');
275
+ }
276
+
277
+ log('');
278
+
279
+ // Overall stats
280
+ const totalPackages = packages.length;
281
+ const totalDeps = stats.reduce((sum, p) => sum + p.dependencies.length, 0);
282
+ const avgDeps = (totalDeps / totalPackages).toFixed(1);
283
+ const maxDeps = Math.max(...stats.map((p) => p.dependencies.length));
284
+ const totalFiles = stats.reduce((sum, p) => sum + (p.size.fileCount || 0), 0);
285
+ const totalLines = stats.reduce((sum, p) => sum + (p.size.lineCount || 0), 0);
286
+
287
+ log('Overall:', 'gray');
288
+ log(` Total packages: ${totalPackages}`, 'cyan');
289
+ log(` Total dependencies: ${totalDeps}`, 'yellow');
290
+ log(` Average deps/pkg: ${avgDeps}`, 'yellow');
291
+ log(` Max dependencies: ${maxDeps}`, 'yellow');
292
+ log(` Total files: ${totalFiles}`, 'green');
293
+ log(` Total lines: ${totalLines.toLocaleString()}`, 'green');
294
+ log('');
295
+
296
+ // Most depended-on packages
297
+ const mostUsed = stats
298
+ .filter((p) => p.dependents.length > 0)
299
+ .sort((a, b) => b.dependents.length - a.dependents.length)
300
+ .slice(0, 10);
301
+
302
+ if (mostUsed.length > 0) {
303
+ log('Most Depended-On Packages:\n', 'gray');
304
+
305
+ for (let i = 0; i < mostUsed.length; i++) {
306
+ const pkg = mostUsed[i];
307
+ const bar = 'ā–ˆ'.repeat(Math.min(pkg.dependents.length, 40));
308
+ log(` ${(i + 1).toString().padStart(2)}. ${pkg.name}`, 'cyan');
309
+ log(` ${colors.green}${bar}${colors.reset} ${pkg.dependents.length} dependents`, 'gray');
310
+ }
311
+ log('');
312
+ }
313
+
314
+ // Largest packages
315
+ const largest = stats
316
+ .filter((p) => p.size.lineCount > 0)
317
+ .sort((a, b) => b.size.lineCount - a.size.lineCount)
318
+ .slice(0, 10);
319
+
320
+ if (largest.length > 0) {
321
+ log('Largest Packages (by lines of code):\n', 'gray');
322
+
323
+ for (let i = 0; i < largest.length; i++) {
324
+ const pkg = largest[i];
325
+ log(` ${(i + 1).toString().padStart(2)}. ${pkg.name}`, 'cyan');
326
+ log(` ${pkg.size.lineCount.toLocaleString()} lines, ${pkg.size.fileCount} files`, 'gray');
327
+ }
328
+ log('');
329
+ }
330
+
331
+ // Leaf packages (no dependents)
332
+ const leaves = stats.filter((p) => p.dependents.length === 0);
333
+
334
+ if (leaves.length > 0) {
335
+ log(`Leaf Packages (${leaves.length} packages with no dependents):\n`, 'gray');
336
+
337
+ for (let i = 0; i < Math.min(leaves.length, 10); i++) {
338
+ const pkg = leaves[i];
339
+ log(` ${(i + 1).toString().padStart(2)}. ${pkg.name}`, 'yellow');
340
+ }
341
+
342
+ if (leaves.length > 10) {
343
+ log(` ... and ${leaves.length - 10} more`, 'gray');
344
+ }
345
+
346
+ log('');
347
+ }
348
+ }
349
+
350
+ /**
351
+ * Render dependency tree
352
+ */
353
+ function renderDependencyTree(graph, packageInfo, filterPackage) {
354
+ log('\n🌳 Dependency Tree:\n', 'blue');
355
+
356
+ const packages = Array.from(graph.keys());
357
+
358
+ if (!filterPackage) {
359
+ log(' Specify --package=<name> to see dependency tree\n', 'gray');
360
+ return;
361
+ }
362
+
363
+ const fullPackageName = packages.find((p) => p.includes(filterPackage));
364
+
365
+ if (!fullPackageName) {
366
+ log(` Package "${filterPackage}" not found\n`, 'red');
367
+ return;
368
+ }
369
+
370
+ const visited = new Set();
371
+
372
+ function printTree(packageName, depth = 0, prefix = '', isLast = true) {
373
+ if (visited.has(packageName)) {
374
+ log(`${prefix}${isLast ? '└─' : 'ā”œā”€'} ${packageName} ${colors.gray}(circular)${colors.reset}`, 'yellow');
375
+ return;
376
+ }
377
+
378
+ visited.add(packageName);
379
+
380
+ const symbol = isLast ? '└─' : 'ā”œā”€';
381
+ const color = depth === 0 ? 'cyan' : 'gray';
382
+ log(`${prefix}${depth > 0 ? symbol : ''} ${packageName}`, color);
383
+
384
+ const { dependencies } = graph.get(packageName);
385
+
386
+ for (let i = 0; i < dependencies.length; i++) {
387
+ const dep = dependencies[i];
388
+ const isLastDep = i === dependencies.length - 1;
389
+ const newPrefix = depth === 0 ? ' ' : prefix + (isLast ? ' ' : '│ ');
390
+
391
+ printTree(dep, depth + 1, newPrefix, isLastDep);
392
+ }
393
+
394
+ visited.delete(packageName);
395
+ }
396
+
397
+ printTree(fullPackageName);
398
+
399
+ log('');
400
+ }
401
+
402
+ /**
403
+ * Main function
404
+ */
405
+ function main() {
406
+ const rootDir = process.cwd();
407
+
408
+ log('\nšŸŽØ KB Labs Visualizer\n', 'blue');
409
+
410
+ if (options.package) {
411
+ log(`Analyzing package: ${options.package}\n`, 'gray');
412
+ } else {
413
+ log('Analyzing all packages...\n', 'gray');
414
+ }
415
+
416
+ const packages = findPackages(rootDir, options.package);
417
+
418
+ if (packages.length === 0) {
419
+ log('āš ļø No KB Labs packages found', 'yellow');
420
+ log(' Run this command from the monorepo root\n', 'gray');
421
+ process.exit(0);
422
+ }
423
+
424
+ log(`Found ${packages.length} package(s)\n`, 'gray');
425
+
426
+ // Build dependency graph
427
+ const { graph, packageInfo } = buildDependencyGraph(packages);
428
+
429
+ // Render visualizations
430
+ if (options.graph || options.all) {
431
+ renderDependencyGraph(graph, packageInfo, options.package);
432
+ }
433
+
434
+ if (options.stats || options.all) {
435
+ renderPackageStats(graph, packageInfo);
436
+ }
437
+
438
+ if (options.tree) {
439
+ renderDependencyTree(graph, packageInfo, options.package);
440
+ }
441
+
442
+ log('šŸ’” Tips:', 'blue');
443
+ log(' • Use --graph to see dependency relationships', 'gray');
444
+ log(' • Use --stats to see package statistics', 'gray');
445
+ log(' • Use --tree --package=<name> to see dependency tree', 'gray');
446
+ log(' • Use --package=<name> to filter by specific package', 'gray');
447
+ log('');
448
+
449
+ process.exit(0);
450
+ }
451
+
452
+ main();