@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,290 @@
1
+ # Package.json Templates
2
+
3
+ Standard package.json templates for KB Labs monorepo packages.
4
+
5
+ ## Philosophy
6
+
7
+ **Minimum Required Standard:**
8
+ - All packages MUST have the sections and scripts defined in these templates
9
+ - Packages CAN add MORE sections and scripts as needed
10
+ - Packages CANNOT have LESS than the standard (enforced by `kb-devkit-check-structure`)
11
+
12
+ ## Templates
13
+
14
+ ### Library Package (Standard)
15
+ **File:** `package.json.template`
16
+
17
+ **Use for:** Most packages (90%)
18
+ - Core libraries
19
+ - Contracts packages
20
+ - Utilities
21
+ - API clients
22
+
23
+ **Required sections:**
24
+ - `name`, `version`, `type`, `private`, `description`
25
+ - `main`, `types`, `exports`
26
+ - `files`, `sideEffects`
27
+ - `scripts` (8 required scripts)
28
+ - `dependencies`, `devDependencies`
29
+ - `engines`
30
+
31
+ **Required scripts:**
32
+ ```json
33
+ {
34
+ "clean": "rimraf dist",
35
+ "build": "tsup --config tsup.config.ts",
36
+ "dev": "tsup --config tsup.config.ts --watch",
37
+ "lint": "eslint src --ext .ts",
38
+ "lint:fix": "eslint src --ext .ts --fix",
39
+ "type-check": "tsc --noEmit",
40
+ "test": "vitest run",
41
+ "test:watch": "vitest"
42
+ }
43
+ ```
44
+
45
+ ### Binary Package
46
+ **File:** `package.json.bin.template`
47
+
48
+ **Use for:** CLI binaries
49
+ - `cli-bin`
50
+ - Daemon executables
51
+ - Standalone tools
52
+
53
+ **Differences from library:**
54
+ - Has `bin` field instead of `main`/`types`
55
+ - No `exports` (binaries don't export modules)
56
+
57
+ ## Required DevDependencies
58
+
59
+ **All packages MUST have:**
60
+ ```json
61
+ {
62
+ "@kb-labs/devkit": "workspace:*",
63
+ "@types/node": "^24.3.3",
64
+ "rimraf": "^6.0.1",
65
+ "tsup": "^8.5.0",
66
+ "typescript": "^5.6.3",
67
+ "vitest": "^3.2.4"
68
+ }
69
+ ```
70
+
71
+ **Why these are required:**
72
+ - `@kb-labs/devkit` - Build system presets (tsup, eslint, tsconfig)
73
+ - `@types/node` - TypeScript types for Node.js
74
+ - `rimraf` - Cross-platform file deletion (clean script)
75
+ - `tsup` - Build tool (used in all packages)
76
+ - `typescript` - TypeScript compiler (type-check script)
77
+ - `vitest` - Test runner (test scripts)
78
+
79
+ ## Optional Additions
80
+
81
+ **Packages CAN add:**
82
+ - Additional scripts (e.g., `test:benchmarks`, `test:all`)
83
+ - Additional dependencies
84
+ - Additional `exports` entries (subpath exports)
85
+ - Custom fields (e.g., `repository`, `keywords`)
86
+
87
+ **Examples:**
88
+ ```json
89
+ {
90
+ "scripts": {
91
+ // ✅ Required scripts (must have all 8)
92
+ "clean": "rimraf dist",
93
+ "build": "tsup --config tsup.config.ts",
94
+ "dev": "tsup --config tsup.config.ts --watch",
95
+ "lint": "eslint src --ext .ts",
96
+ "lint:fix": "eslint src --ext .ts --fix",
97
+ "type-check": "tsc --noEmit",
98
+ "test": "vitest run",
99
+ "test:watch": "vitest",
100
+
101
+ // ✅ Additional scripts (OK to add)
102
+ "test:benchmarks": "vitest run tests/benchmarks",
103
+ "test:all": "vitest run && vitest run tests/benchmarks",
104
+ "pretype-check": "pnpm --filter @kb-labs/package build"
105
+ },
106
+ "exports": {
107
+ // ✅ Required default export
108
+ ".": {
109
+ "import": "./dist/index.js",
110
+ "types": "./dist/index.d.ts"
111
+ },
112
+ // ✅ Additional exports (OK to add)
113
+ "./analyzer": {
114
+ "import": "./dist/analyzer/index.js",
115
+ "types": "./dist/analyzer/index.d.ts"
116
+ }
117
+ }
118
+ }
119
+ ```
120
+
121
+ ## Validation
122
+
123
+ **Check package.json structure:**
124
+ ```bash
125
+ npx kb-devkit-check-structure
126
+ ```
127
+
128
+ **What it checks:**
129
+ - ✅ All required sections present
130
+ - ✅ All required scripts present
131
+ - ✅ All required devDependencies present
132
+ - ✅ Correct `type: "module"`
133
+ - ✅ Correct `engines` versions
134
+ - ⚠️ Warns about missing optional sections (repository, keywords)
135
+
136
+ **Auto-fix missing scripts:**
137
+ ```bash
138
+ npx kb-devkit-check-structure --fix
139
+ ```
140
+
141
+ ## Common Issues
142
+
143
+ ### Missing Scripts
144
+
145
+ **Problem:**
146
+ ```bash
147
+ pnpm -r run test
148
+ # ❌ Error: "test" script not found in @kb-labs/some-package
149
+ ```
150
+
151
+ **Solution:**
152
+ ```bash
153
+ # Check which packages are missing scripts
154
+ npx kb-devkit-check-structure
155
+
156
+ # Auto-add missing scripts
157
+ npx kb-devkit-check-structure --fix
158
+ ```
159
+
160
+ ### Wrong Script Names
161
+
162
+ **Problem:**
163
+ ```json
164
+ {
165
+ "scripts": {
166
+ "test:unit": "vitest run" // ❌ Wrong, should be "test"
167
+ }
168
+ }
169
+ ```
170
+
171
+ **Solution:**
172
+ ```json
173
+ {
174
+ "scripts": {
175
+ "test": "vitest run", // ✅ Required
176
+ "test:unit": "vitest run" // ✅ Additional is OK
177
+ }
178
+ }
179
+ ```
180
+
181
+ ### Missing DevDependencies
182
+
183
+ **Problem:**
184
+ ```bash
185
+ pnpm run build
186
+ # ❌ Error: Cannot find module 'tsup'
187
+ ```
188
+
189
+ **Solution:**
190
+ ```bash
191
+ # Check missing devDependencies
192
+ npx kb-devkit-check-structure --verbose
193
+
194
+ # Install missing deps
195
+ pnpm install
196
+ ```
197
+
198
+ ## Migration Guide
199
+
200
+ **For existing packages:**
201
+
202
+ 1. **Check current status:**
203
+ ```bash
204
+ npx kb-devkit-check-structure --package=@kb-labs/your-package
205
+ ```
206
+
207
+ 2. **Add missing scripts:**
208
+ ```bash
209
+ npx kb-devkit-check-structure --package=@kb-labs/your-package --fix
210
+ ```
211
+
212
+ 3. **Verify:**
213
+ ```bash
214
+ cd packages/your-package
215
+ pnpm run build
216
+ pnpm run test
217
+ pnpm run lint
218
+ ```
219
+
220
+ **For new packages:**
221
+
222
+ 1. **Copy template:**
223
+ ```bash
224
+ cp kb-labs-devkit/templates/package-json/package.json.template package.json
225
+ ```
226
+
227
+ 2. **Update metadata:**
228
+ - Change `name` to your package name
229
+ - Update `description`
230
+ - Add dependencies
231
+
232
+ 3. **Verify:**
233
+ ```bash
234
+ npx kb-devkit-check-structure --package=@kb-labs/your-package
235
+ ```
236
+
237
+ ## Design Decisions
238
+
239
+ ### Why "More is OK, Less is Not"?
240
+
241
+ **Consistency vs Flexibility:**
242
+ - **Minimum standard** ensures all packages work with monorepo scripts (`pnpm -r run build`)
243
+ - **Additional scripts** allow package-specific needs (benchmarks, integration tests)
244
+ - **Enforced baseline** prevents "missing script" errors in CI/CD
245
+
246
+ ### Why These Exact Scripts?
247
+
248
+ **Based on actual usage:**
249
+ - `clean` - Required before rebuild (prevents stale artifacts)
250
+ - `build` - Used by CI, pre-commit hooks, dependencies
251
+ - `dev` - Developer workflow (watch mode)
252
+ - `lint` - Code quality checks
253
+ - `lint:fix` - Auto-fix linting issues
254
+ - `type-check` - TypeScript validation (separate from build)
255
+ - `test` - CI test runs
256
+ - `test:watch` - Developer workflow (TDD)
257
+
258
+ ### Why Vitest (not Jest)?
259
+
260
+ **Modern, fast, ESM-native:**
261
+ - ✅ Native ESM support (no config needed)
262
+ - ✅ 10x faster than Jest for small tests
263
+ - ✅ Compatible with Vite ecosystem
264
+ - ✅ Built-in TypeScript support
265
+ - ✅ Watch mode with UI
266
+
267
+ **Migration from Jest:**
268
+ ```bash
269
+ # Replace jest with vitest
270
+ pnpm remove jest @types/jest
271
+ pnpm add -D vitest
272
+
273
+ # Update scripts
274
+ # "test": "jest" → "test": "vitest run"
275
+ # "test:watch": "jest --watch" → "test:watch": "vitest"
276
+ ```
277
+
278
+ ## Future Improvements
279
+
280
+ **Planned features:**
281
+ - Auto-migration tool (`kb-devkit-migrate-package-json`)
282
+ - Version alignment checker (ensure all packages use same vitest/tsup versions)
283
+ - Script validation (ensure scripts actually work)
284
+ - Dependency audit (find unused deps)
285
+
286
+ ## See Also
287
+
288
+ - [Config Templates](../configs/README.md) - tsup, eslint, tsconfig templates
289
+ - [DevKit README](../../README.md) - Complete DevKit documentation
290
+ - [Check Structure Tool](../../bin/devkit-check-structure.mjs) - Validation script
@@ -0,0 +1,40 @@
1
+ {
2
+ "$comment": "Binary package template for @kb-labs CLI binaries",
3
+ "name": "@kb-labs/package-bin",
4
+ "version": "0.1.0",
5
+ "type": "module",
6
+ "private": true,
7
+ "description": "CLI binary package",
8
+ "bin": {
9
+ "kb": "./dist/bin.cjs"
10
+ },
11
+ "files": [
12
+ "dist",
13
+ "README.md",
14
+ "LICENSE"
15
+ ],
16
+ "sideEffects": false,
17
+ "scripts": {
18
+ "clean": "rimraf dist",
19
+ "build": "tsup --config tsup.config.ts",
20
+ "dev": "tsup --config tsup.config.ts --watch",
21
+ "lint": "eslint src --ext .ts",
22
+ "lint:fix": "eslint src --ext .ts --fix",
23
+ "type-check": "tsc --noEmit",
24
+ "test": "vitest run",
25
+ "test:watch": "vitest"
26
+ },
27
+ "dependencies": {},
28
+ "devDependencies": {
29
+ "@kb-labs/devkit": "workspace:*",
30
+ "@types/node": "^24.3.3",
31
+ "rimraf": "^6.0.1",
32
+ "tsup": "^8.5.0",
33
+ "typescript": "^5.6.3",
34
+ "vitest": "^3.2.4"
35
+ },
36
+ "engines": {
37
+ "node": ">=20.0.0",
38
+ "pnpm": ">=9.0.0"
39
+ }
40
+ }
@@ -0,0 +1,45 @@
1
+ {
2
+ "$comment": "Standard package.json template for @kb-labs packages",
3
+ "name": "@kb-labs/package-name",
4
+ "version": "0.1.0",
5
+ "type": "module",
6
+ "private": true,
7
+ "description": "Package description",
8
+ "main": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "import": "./dist/index.js",
13
+ "types": "./dist/index.d.ts"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist",
18
+ "README.md",
19
+ "LICENSE"
20
+ ],
21
+ "sideEffects": false,
22
+ "scripts": {
23
+ "clean": "rimraf dist",
24
+ "build": "tsup --config tsup.config.ts",
25
+ "dev": "tsup --config tsup.config.ts --watch",
26
+ "lint": "eslint src --ext .ts",
27
+ "lint:fix": "eslint src --ext .ts --fix",
28
+ "type-check": "tsc --noEmit",
29
+ "test": "vitest run",
30
+ "test:watch": "vitest"
31
+ },
32
+ "dependencies": {},
33
+ "devDependencies": {
34
+ "@kb-labs/devkit": "workspace:*",
35
+ "@types/node": "^24.3.3",
36
+ "rimraf": "^6.0.1",
37
+ "tsup": "^8.5.0",
38
+ "typescript": "^5.6.3",
39
+ "vitest": "^3.2.4"
40
+ },
41
+ "engines": {
42
+ "node": ">=20.0.0",
43
+ "pnpm": ">=9.0.0"
44
+ }
45
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/tsconfig",
3
+ "compilerOptions": {
4
+ "target": "ES2022",
5
+ "module": "ESNext",
6
+ "moduleResolution": "bundler",
7
+ "lib": [
8
+ "ES2022"
9
+ ],
10
+ "strict": true,
11
+ "noUncheckedIndexedAccess": true,
12
+ "noImplicitOverride": true,
13
+ "noFallthroughCasesInSwitch": true,
14
+ "resolveJsonModule": true,
15
+ "esModuleInterop": true,
16
+ "skipLibCheck": true,
17
+ "forceConsistentCasingInFileNames": true,
18
+ "isolatedModules": true,
19
+ "sourceMap": true
20
+ }
21
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "extends": "./base.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist",
5
+ "declaration": true,
6
+ "sourceMap": true
7
+ },
8
+ "include": [
9
+ "src",
10
+ "bin"
11
+ ]
12
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * @module @kb-labs/core/config/__tests__/cache.spec.ts
3
+ * Tests for filesystem cache functionality
4
+ */
5
+ export {};
6
+ //# sourceMappingURL=cache.spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.spec.d.ts","sourceRoot":"","sources":["../../../../kb-labs-core/packages/config/src/__tests__/cache.spec.ts"],"names":[],"mappings":"AAAA;;;GAGG"}
@@ -0,0 +1,85 @@
1
+ /**
2
+ * @module @kb-labs/core/config/__tests__/cache.spec.ts
3
+ * Tests for filesystem cache functionality
4
+ */
5
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
6
+ import { promises as fsp } from 'node:fs';
7
+ import path from 'node:path';
8
+ import { tmpdir } from 'node:os';
9
+ import { fsCache, clearCaches } from '../cache/fs-cache';
10
+ import { readConfigFile } from '../api/read-config';
11
+ describe('Filesystem Cache', () => {
12
+ let testDir;
13
+ beforeEach(async () => {
14
+ testDir = path.join(tmpdir(), `kb-labs-cache-test-${Date.now()}`);
15
+ await fsp.mkdir(testDir, { recursive: true });
16
+ clearCaches();
17
+ });
18
+ afterEach(async () => {
19
+ await fsp.rm(testDir, { recursive: true, force: true });
20
+ clearCaches();
21
+ });
22
+ describe('Cache Operations', () => {
23
+ it('should cache file reads', async () => {
24
+ const configPath = path.join(testDir, 'test-config.json');
25
+ const configData = { test: 'value' };
26
+ await fsp.writeFile(configPath, JSON.stringify(configData, null, 2));
27
+ // First read - should not be cached
28
+ const result1 = await readConfigFile(configPath);
29
+ expect(result1.data).toEqual(configData);
30
+ // Second read - should be cached
31
+ const result2 = await readConfigFile(configPath);
32
+ expect(result2.data).toEqual(configData);
33
+ });
34
+ it('should invalidate cache on file change', async () => {
35
+ const configPath = path.join(testDir, 'test-config.json');
36
+ const configData1 = { test: 'value1' };
37
+ const configData2 = { test: 'value2' };
38
+ await fsp.writeFile(configPath, JSON.stringify(configData1, null, 2));
39
+ // First read
40
+ const result1 = await readConfigFile(configPath);
41
+ expect(result1.data).toEqual(configData1);
42
+ // Small delay to ensure mtime changes
43
+ await new Promise(resolve => setTimeout(resolve, 10));
44
+ // Modify file
45
+ await fsp.writeFile(configPath, JSON.stringify(configData2, null, 2));
46
+ // Second read - should get new data
47
+ const result2 = await readConfigFile(configPath);
48
+ expect(result2.data).toEqual(configData2);
49
+ });
50
+ it('should clear caches', async () => {
51
+ const configPath = path.join(testDir, 'test-config.json');
52
+ const configData = { test: 'value' };
53
+ await fsp.writeFile(configPath, JSON.stringify(configData, null, 2));
54
+ // Read to populate cache
55
+ await readConfigFile(configPath);
56
+ // Clear cache
57
+ clearCaches();
58
+ // Verify cache is cleared by checking stats
59
+ const stats = fsCache.getStats();
60
+ expect(stats.size).toBe(0);
61
+ });
62
+ it('should handle YAML files', async () => {
63
+ const configPath = path.join(testDir, 'test-config.yaml');
64
+ const configData = { test: 'value', nested: { key: 'value' } };
65
+ await fsp.writeFile(configPath, `test: value\nnested:\n key: value`);
66
+ const result = await readConfigFile(configPath);
67
+ expect(result.format).toBe('yaml');
68
+ expect(result.data).toEqual(configData);
69
+ });
70
+ });
71
+ describe('Cache Statistics', () => {
72
+ it('should track cache size', async () => {
73
+ const configPath1 = path.join(testDir, 'config1.json');
74
+ const configPath2 = path.join(testDir, 'config2.json');
75
+ await fsp.writeFile(configPath1, JSON.stringify({ test1: 'value1' }));
76
+ await fsp.writeFile(configPath2, JSON.stringify({ test2: 'value2' }));
77
+ // Read both files
78
+ await readConfigFile(configPath1);
79
+ await readConfigFile(configPath2);
80
+ const stats = fsCache.getStats();
81
+ expect(stats.size).toBeGreaterThan(0);
82
+ });
83
+ });
84
+ });
85
+ //# sourceMappingURL=cache.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.spec.js","sourceRoot":"","sources":["../../../../kb-labs-core/packages/config/src/__tests__/cache.spec.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * @module @kb-labs/core/config/__tests__/fs-atomic.spec.ts
3
+ * Tests for atomic file operations and workspace validation
4
+ */
5
+ export {};
6
+ //# sourceMappingURL=fs-atomic.spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fs-atomic.spec.d.ts","sourceRoot":"","sources":["../../../../kb-labs-core/packages/config/src/__tests__/fs-atomic.spec.ts"],"names":[],"mappings":"AAAA;;;GAGG"}
@@ -0,0 +1,153 @@
1
+ /**
2
+ * @module @kb-labs/core/config/__tests__/fs-atomic.spec.ts
3
+ * Tests for atomic file operations and workspace validation
4
+ */
5
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
6
+ import { promises as fsp } from 'node:fs';
7
+ import path from 'node:path';
8
+ import { tmpdir } from 'node:os';
9
+ import { writeFileAtomic, ensureWithinWorkspace } from '../utils/fs-atomic';
10
+ import { KbError } from '../errors/kb-error';
11
+ describe('fs-atomic', () => {
12
+ let testDir;
13
+ beforeEach(async () => {
14
+ testDir = path.join(tmpdir(), `kb-labs-fs-atomic-test-${Date.now()}`);
15
+ await fsp.mkdir(testDir, { recursive: true });
16
+ });
17
+ afterEach(async () => {
18
+ await fsp.rm(testDir, { recursive: true, force: true });
19
+ });
20
+ describe('writeFileAtomic', () => {
21
+ it('should write file atomically', async () => {
22
+ const filePath = path.join(testDir, 'test.txt');
23
+ const content = 'Hello, World!';
24
+ await writeFileAtomic(filePath, content);
25
+ const result = await fsp.readFile(filePath, 'utf-8');
26
+ expect(result).toBe(content);
27
+ });
28
+ it('should create parent directories', async () => {
29
+ const filePath = path.join(testDir, 'nested', 'deep', 'test.txt');
30
+ const content = 'Nested content';
31
+ await writeFileAtomic(filePath, content);
32
+ const result = await fsp.readFile(filePath, 'utf-8');
33
+ expect(result).toBe(content);
34
+ });
35
+ it('should handle Uint8Array data', async () => {
36
+ const filePath = path.join(testDir, 'binary.txt');
37
+ const content = new Uint8Array([72, 101, 108, 108, 111]); // "Hello"
38
+ await writeFileAtomic(filePath, content);
39
+ const result = await fsp.readFile(filePath);
40
+ expect(Array.from(result)).toEqual(Array.from(content));
41
+ });
42
+ it('should clean up temp file on error', async () => {
43
+ const filePath = path.join(testDir, 'test.txt');
44
+ // Mock fs.writeFile to throw error
45
+ const originalWriteFile = fsp.writeFile;
46
+ vi.spyOn(fsp, 'writeFile').mockRejectedValueOnce(new Error('Write failed'));
47
+ await expect(writeFileAtomic(filePath, 'content')).rejects.toThrow('Write failed');
48
+ // Check that temp file was cleaned up
49
+ const files = await fsp.readdir(testDir);
50
+ const tempFiles = files.filter(f => f.startsWith('test.txt.tmp-'));
51
+ expect(tempFiles).toHaveLength(0);
52
+ // Restore original function
53
+ vi.restoreAllMocks();
54
+ });
55
+ it('should handle cleanup errors gracefully', async () => {
56
+ const filePath = path.join(testDir, 'test.txt');
57
+ // Mock fs.writeFile to throw error
58
+ const originalWriteFile = fsp.writeFile;
59
+ const originalUnlink = fsp.unlink;
60
+ vi.spyOn(fsp, 'writeFile').mockRejectedValueOnce(new Error('Write failed'));
61
+ vi.spyOn(fsp, 'unlink').mockRejectedValueOnce(new Error('Cleanup failed'));
62
+ // Should not throw cleanup error
63
+ await expect(writeFileAtomic(filePath, 'content')).rejects.toThrow('Write failed');
64
+ // Restore original functions
65
+ vi.restoreAllMocks();
66
+ });
67
+ it('should overwrite existing file', async () => {
68
+ const filePath = path.join(testDir, 'existing.txt');
69
+ const originalContent = 'Original content';
70
+ const newContent = 'New content';
71
+ // Create original file
72
+ await fsp.writeFile(filePath, originalContent);
73
+ // Overwrite atomically
74
+ await writeFileAtomic(filePath, newContent);
75
+ const result = await fsp.readFile(filePath, 'utf-8');
76
+ expect(result).toBe(newContent);
77
+ });
78
+ it('should generate unique temp file names', async () => {
79
+ const filePath = path.join(testDir, 'test.txt');
80
+ const content = 'content';
81
+ // Mock Date.now and Math.random for predictable temp names
82
+ const mockDateNow = vi.spyOn(Date, 'now').mockReturnValue(1234567890);
83
+ const mockMathRandom = vi.spyOn(Math, 'random').mockReturnValue(0.5);
84
+ await writeFileAtomic(filePath, content);
85
+ // Check that temp file name was generated correctly
86
+ const files = await fsp.readdir(testDir);
87
+ expect(files).toContain('test.txt');
88
+ mockDateNow.mockRestore();
89
+ mockMathRandom.mockRestore();
90
+ });
91
+ });
92
+ describe('ensureWithinWorkspace', () => {
93
+ it('should allow paths within workspace', () => {
94
+ const workspaceRoot = '/workspace';
95
+ const targetPath = '/workspace/subdir/file.txt';
96
+ expect(() => ensureWithinWorkspace(targetPath, workspaceRoot)).not.toThrow();
97
+ });
98
+ it('should allow relative paths within workspace', () => {
99
+ const workspaceRoot = '/workspace';
100
+ const targetPath = path.join(workspaceRoot, 'subdir', 'file.txt');
101
+ expect(() => ensureWithinWorkspace(targetPath, workspaceRoot)).not.toThrow();
102
+ });
103
+ it('should throw error for paths outside workspace', () => {
104
+ const workspaceRoot = '/workspace';
105
+ const targetPath = '/outside/file.txt';
106
+ expect(() => ensureWithinWorkspace(targetPath, workspaceRoot)).toThrow(KbError);
107
+ });
108
+ it('should throw error for paths that escape via ..', () => {
109
+ const workspaceRoot = '/workspace';
110
+ const targetPath = '/workspace/../outside/file.txt';
111
+ expect(() => ensureWithinWorkspace(targetPath, workspaceRoot)).toThrow(KbError);
112
+ });
113
+ it('should handle edge case with trailing slashes', () => {
114
+ const workspaceRoot = '/workspace/';
115
+ const targetPath = '/workspace/file.txt';
116
+ expect(() => ensureWithinWorkspace(targetPath, workspaceRoot)).not.toThrow();
117
+ });
118
+ it('should handle same directory as workspace', () => {
119
+ const workspaceRoot = '/workspace';
120
+ const targetPath = '/workspace';
121
+ expect(() => ensureWithinWorkspace(targetPath, workspaceRoot)).not.toThrow();
122
+ });
123
+ it('should throw error with correct error details', () => {
124
+ const workspaceRoot = '/workspace';
125
+ const targetPath = '/outside/file.txt';
126
+ expect(() => ensureWithinWorkspace(targetPath, workspaceRoot)).toThrow(expect.objectContaining({
127
+ code: 'ERR_PATH_OUTSIDE_WORKSPACE',
128
+ message: 'Refusing to write outside workspace: /outside/file.txt',
129
+ hint: 'Check cwd or use a relative path within the workspace',
130
+ meta: {
131
+ targetPath: '/outside/file.txt',
132
+ workspaceRoot: '/workspace',
133
+ },
134
+ }));
135
+ });
136
+ it('should handle empty workspace root', () => {
137
+ const workspaceRoot = '';
138
+ const targetPath = '/some/path';
139
+ expect(() => ensureWithinWorkspace(targetPath, workspaceRoot)).toThrow(KbError);
140
+ });
141
+ it('should handle empty target path', () => {
142
+ const workspaceRoot = '/workspace';
143
+ const targetPath = '';
144
+ expect(() => ensureWithinWorkspace(targetPath, workspaceRoot)).toThrow(KbError);
145
+ });
146
+ it('should resolve paths correctly', () => {
147
+ const workspaceRoot = '/workspace';
148
+ const targetPath = path.resolve(workspaceRoot, 'subdir', '..', 'subdir', 'file.txt');
149
+ expect(() => ensureWithinWorkspace(targetPath, workspaceRoot)).not.toThrow();
150
+ });
151
+ });
152
+ });
153
+ //# sourceMappingURL=fs-atomic.spec.js.map