@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,453 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * KB Labs QA History Tracker
5
+ *
6
+ * Tracks QA metrics over time and detects regressions.
7
+ *
8
+ * Usage:
9
+ * npx kb-devkit-qa-history save # Save current QA results
10
+ * npx kb-devkit-qa-history show # Show history
11
+ * npx kb-devkit-qa-history trends # Show trends
12
+ * npx kb-devkit-qa-history regressions # Detect regressions
13
+ */
14
+
15
+ import { execSync } from 'child_process'
16
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs'
17
+ import { join } from 'path'
18
+
19
+ const HISTORY_DIR = '.qa-history'
20
+ const HISTORY_FILE = join(HISTORY_DIR, 'history.json')
21
+
22
+ // ANSI colors
23
+ const colors = {
24
+ reset: '\x1b[0m',
25
+ bright: '\x1b[1m',
26
+ red: '\x1b[31m',
27
+ green: '\x1b[32m',
28
+ yellow: '\x1b[33m',
29
+ blue: '\x1b[34m',
30
+ cyan: '\x1b[36m',
31
+ gray: '\x1b[90m',
32
+ }
33
+
34
+ function log(msg, color = 'reset') {
35
+ console.log(`${colors[color]}${msg}${colors.reset}`)
36
+ }
37
+
38
+ // Load history
39
+ function loadHistory() {
40
+ if (!existsSync(HISTORY_FILE)) {
41
+ return []
42
+ }
43
+ try {
44
+ return JSON.parse(readFileSync(HISTORY_FILE, 'utf-8'))
45
+ } catch (err) {
46
+ log('Failed to load history', 'red')
47
+ return []
48
+ }
49
+ }
50
+
51
+ // Save history
52
+ function saveHistory(history) {
53
+ if (!existsSync(HISTORY_DIR)) {
54
+ mkdirSync(HISTORY_DIR, { recursive: true })
55
+ }
56
+ writeFileSync(HISTORY_FILE, JSON.stringify(history, null, 2))
57
+ }
58
+
59
+ // Get current QA results from baseline files (no re-run!)
60
+ function getCurrentQA() {
61
+ // Read baseline files directly
62
+ const baseline = {
63
+ build: existsSync('.baseline/build-failures.json') ? JSON.parse(readFileSync('.baseline/build-failures.json', 'utf-8')) : null,
64
+ lint: existsSync('.baseline/lint-errors-aggregated.json') ? JSON.parse(readFileSync('.baseline/lint-errors-aggregated.json', 'utf-8')) : null,
65
+ typeCheck: existsSync('.baseline/type-errors.json') ? JSON.parse(readFileSync('.baseline/type-errors.json', 'utf-8')) : null,
66
+ test: existsSync('.baseline/test-results.json') ? JSON.parse(readFileSync('.baseline/test-results.json', 'utf-8')) : null,
67
+ }
68
+
69
+ // Extract failed packages from baseline
70
+ const failures = {
71
+ build: baseline.build?.failures || [],
72
+ lint: baseline.lint?.packages?.filter(p => p.errors > 0 || p.warnings > 0).map(p => p.name) || [],
73
+ typeCheck: baseline.typeCheck?.packages?.filter(p => p.errors > 0).map(p => p.name) || [],
74
+ test: [], // No per-package test data
75
+ }
76
+
77
+ return {
78
+ status: (failures.build.length + failures.lint.length + failures.typeCheck.length + failures.test.length) === 0 ? 'passed' : 'failed',
79
+ filter: { package: null, repo: null, scope: null },
80
+ summary: {
81
+ build: { passed: 0, failed: failures.build.length, skipped: 0 },
82
+ lint: { passed: 0, failed: failures.lint.length, skipped: 0 },
83
+ typeCheck: { passed: 0, failed: failures.typeCheck.length, skipped: 0 },
84
+ test: { passed: 0, failed: failures.test.length, skipped: 0 },
85
+ },
86
+ failures,
87
+ baseline: null,
88
+ byRepo: null,
89
+ }
90
+ }
91
+
92
+ // Get git info
93
+ function getGitInfo() {
94
+ try {
95
+ const commit = execSync('git rev-parse --short HEAD', { encoding: 'utf-8' }).trim()
96
+ const branch = execSync('git rev-parse --abbrev-ref HEAD', { encoding: 'utf-8' }).trim()
97
+ const message = execSync('git log -1 --pretty=%B', { encoding: 'utf-8' }).trim()
98
+ return { commit, branch, message }
99
+ } catch (err) {
100
+ return { commit: 'unknown', branch: 'unknown', message: '' }
101
+ }
102
+ }
103
+
104
+ // Save current QA results to history
105
+ function saveCurrentQA() {
106
+ log('\nšŸ“Š Reading baseline files...', 'cyan')
107
+ const qa = getCurrentQA()
108
+ const git = getGitInfo()
109
+
110
+ const history = loadHistory()
111
+
112
+ const entry = {
113
+ timestamp: new Date().toISOString(),
114
+ git,
115
+ status: qa.status,
116
+ filter: qa.filter, // NEW: Save filter info (package/repo/scope)
117
+ summary: qa.summary,
118
+ failures: {
119
+ build: qa.failures.build.length,
120
+ lint: qa.failures.lint.length,
121
+ typeCheck: qa.failures.typeCheck.length,
122
+ test: qa.failures.test.length,
123
+ },
124
+ // NEW: Save per-package failure lists
125
+ failedPackages: {
126
+ build: qa.failures.build || [],
127
+ lint: qa.failures.lint || [],
128
+ typeCheck: qa.failures.typeCheck || [],
129
+ test: qa.failures.test || [],
130
+ },
131
+ baseline: qa.baseline,
132
+ byRepo: qa.byRepo, // NEW: Save per-repo aggregation if available
133
+ }
134
+
135
+ history.push(entry)
136
+
137
+ // Keep last 50 entries
138
+ if (history.length > 50) {
139
+ history.splice(0, history.length - 50)
140
+ }
141
+
142
+ saveHistory(history)
143
+
144
+ log(`\nāœ… QA results saved (${history.length} entries in history)`, 'green')
145
+ log(` Commit: ${git.commit} (${git.branch})`, 'gray')
146
+ log(` Status: ${qa.status === 'passed' ? 'āœ…' : 'āŒ'} ${qa.status}`, qa.status === 'passed' ? 'green' : 'red')
147
+
148
+ // Show filter info if present
149
+ if (qa.filter && (qa.filter.package || qa.filter.repo || qa.filter.scope)) {
150
+ const filterStr = qa.filter.package || qa.filter.repo || qa.filter.scope
151
+ log(` Filter: ${filterStr}`, 'gray')
152
+ }
153
+ }
154
+
155
+ // Show history
156
+ function showHistory() {
157
+ const history = loadHistory()
158
+
159
+ if (history.length === 0) {
160
+ log('No QA history found. Run "npx kb-devkit-qa-history save" first.', 'yellow')
161
+ return
162
+ }
163
+
164
+ log(`\nšŸ“Š QA History (last ${history.length} entries)\n`, 'cyan')
165
+ log('─'.repeat(100), 'gray')
166
+ log(`${'Date'.padEnd(20)} ${'Commit'.padEnd(10)} ${'Status'.padEnd(10)} ${'Build'.padEnd(8)} ${'Lint'.padEnd(8)} ${'Types'.padEnd(8)} ${'Tests'.padEnd(8)}`, 'bright')
167
+ log('─'.repeat(100), 'gray')
168
+
169
+ for (const entry of history.slice(-20)) {
170
+ const date = new Date(entry.timestamp).toLocaleString()
171
+ const status = entry.status === 'passed' ? 'āœ… passed' : 'āŒ failed'
172
+ const statusColor = entry.status === 'passed' ? 'green' : 'red'
173
+
174
+ const build = entry.failures.build
175
+ const lint = entry.failures.lint
176
+ const types = entry.failures.typeCheck
177
+ const tests = entry.failures.test
178
+
179
+ const buildStr = build === 0 ? `${colors.green}āœ“${colors.reset}` : `${colors.red}${build}F${colors.reset}`
180
+ const lintStr = lint === 0 ? `${colors.green}āœ“${colors.reset}` : `${colors.red}${lint}F${colors.reset}`
181
+ const typesStr = types === 0 ? `${colors.green}āœ“${colors.reset}` : `${colors.red}${types}F${colors.reset}`
182
+ const testsStr = tests === 0 ? `${colors.green}āœ“${colors.reset}` : `${colors.red}${tests}F${colors.reset}`
183
+
184
+ console.log(`${date.padEnd(20)} ${entry.git.commit.padEnd(10)} ${colors[statusColor]}${status.padEnd(10)}${colors.reset} ${buildStr.padEnd(8)} ${lintStr.padEnd(8)} ${typesStr.padEnd(8)} ${testsStr.padEnd(8)}`)
185
+ }
186
+
187
+ log('─'.repeat(100), 'gray')
188
+ }
189
+
190
+ // Show trends
191
+ function showTrends() {
192
+ const history = loadHistory()
193
+
194
+ if (history.length < 2) {
195
+ log('Not enough history data. Need at least 2 entries.', 'yellow')
196
+ return
197
+ }
198
+
199
+ log('\nšŸ“ˆ QA Trends\n', 'cyan')
200
+
201
+ const recent = history.slice(-10)
202
+ const first = recent[0]
203
+ const last = recent[recent.length - 1]
204
+
205
+ // Calculate trends
206
+ const trends = {
207
+ build: last.failures.build - first.failures.build,
208
+ lint: last.failures.lint - first.failures.lint,
209
+ typeCheck: last.failures.typeCheck - first.failures.typeCheck,
210
+ test: last.failures.test - first.failures.test,
211
+ }
212
+
213
+ const categories = [
214
+ { name: 'Build', key: 'build', icon: 'šŸ”Ø' },
215
+ { name: 'Lint', key: 'lint', icon: 'šŸ”' },
216
+ { name: 'Type Check', key: 'typeCheck', icon: 'šŸ“˜' },
217
+ { name: 'Tests', key: 'test', icon: '🧪' },
218
+ ]
219
+
220
+ for (const cat of categories) {
221
+ const trend = trends[cat.key]
222
+ const current = last.failures[cat.key]
223
+ const previous = first.failures[cat.key]
224
+
225
+ let trendStr = ''
226
+ let trendColor = 'gray'
227
+
228
+ if (trend > 0) {
229
+ trendStr = `+${trend} āŒ (regression)`
230
+ trendColor = 'red'
231
+ } else if (trend < 0) {
232
+ trendStr = `${trend} āœ… (improvement)`
233
+ trendColor = 'green'
234
+ } else {
235
+ trendStr = '→ no change'
236
+ trendColor = 'yellow'
237
+ }
238
+
239
+ log(`${cat.icon} ${cat.name.padEnd(12)} ${previous} → ${current} ${colors[trendColor]}${trendStr}${colors.reset}`)
240
+
241
+ // Show package-level details if available
242
+ if (last.failedPackages && first.failedPackages) {
243
+ const previousFails = new Set(first.failedPackages[cat.key] || [])
244
+ const currentFails = new Set(last.failedPackages[cat.key] || [])
245
+
246
+ // New failures
247
+ const newFails = [...currentFails].filter(pkg => !previousFails.has(pkg))
248
+ if (newFails.length > 0) {
249
+ log(` šŸ†• New failures (${newFails.length}):`, 'red')
250
+ for (const pkg of newFails.slice(0, 3)) {
251
+ log(` - ${pkg}`, 'red')
252
+ }
253
+ if (newFails.length > 3) {
254
+ log(` ... and ${newFails.length - 3} more`, 'gray')
255
+ }
256
+ }
257
+
258
+ // Fixed packages
259
+ const fixed = [...previousFails].filter(pkg => !currentFails.has(pkg))
260
+ if (fixed.length > 0) {
261
+ log(` āœ… Fixed (${fixed.length}):`, 'green')
262
+ for (const pkg of fixed.slice(0, 3)) {
263
+ log(` - ${pkg}`, 'green')
264
+ }
265
+ if (fixed.length > 3) {
266
+ log(` ... and ${fixed.length - 3} more`, 'gray')
267
+ }
268
+ }
269
+ }
270
+ }
271
+
272
+ // NEW: Baseline trends for all check types
273
+ if (last.baseline && first.baseline) {
274
+ log('')
275
+ log('šŸ“Š Baseline Trends:', 'cyan')
276
+
277
+ const checkTypes = [
278
+ { key: 'build', label: 'Build', icon: 'šŸ”Ø' },
279
+ { key: 'lint', label: 'Lint', icon: 'šŸ”' },
280
+ { key: 'typeCheck', label: 'Type Check', icon: 'šŸ“˜' },
281
+ { key: 'test', label: 'Tests', icon: '🧪' },
282
+ ]
283
+
284
+ for (const checkType of checkTypes) {
285
+ const lastBaseline = last.baseline[checkType.key]
286
+ const firstBaseline = first.baseline[checkType.key]
287
+
288
+ if (!lastBaseline || !firstBaseline) {continue}
289
+
290
+ const delta = lastBaseline.summary.current - firstBaseline.summary.current
291
+
292
+ let trendStr = ''
293
+ let trendColor = 'gray'
294
+
295
+ if (delta > 0) {
296
+ trendStr = `+${delta} āŒ (regression)`
297
+ trendColor = 'red'
298
+ } else if (delta < 0) {
299
+ trendStr = `${delta} āœ… (improvement)`
300
+ trendColor = 'green'
301
+ } else {
302
+ trendStr = '→ no change'
303
+ trendColor = 'yellow'
304
+ }
305
+
306
+ log(` ${checkType.icon} ${checkType.label.padEnd(12)} ${firstBaseline.summary.current} → ${lastBaseline.summary.current} ${colors[trendColor]}${trendStr}${colors.reset}`)
307
+ }
308
+ }
309
+
310
+ log('')
311
+ log(`Period: ${new Date(first.timestamp).toLocaleDateString()} → ${new Date(last.timestamp).toLocaleDateString()}`, 'gray')
312
+ }
313
+
314
+ // Detect regressions
315
+ function detectRegressions() {
316
+ const history = loadHistory()
317
+
318
+ if (history.length < 2) {
319
+ log('Not enough history data. Need at least 2 entries.', 'yellow')
320
+ return
321
+ }
322
+
323
+ const last = history[history.length - 1]
324
+ const previous = history[history.length - 2]
325
+
326
+ log('\nšŸ” Regression Detection\n', 'cyan')
327
+ log(`Comparing: ${previous.git.commit} → ${last.git.commit}`, 'gray')
328
+ log('')
329
+
330
+ let hasRegression = false
331
+
332
+ // Check each category
333
+ const categories = ['build', 'lint', 'typeCheck', 'test']
334
+
335
+ for (const cat of categories) {
336
+ const delta = last.failures[cat] - previous.failures[cat]
337
+
338
+ if (delta > 0) {
339
+ hasRegression = true
340
+ log(`āŒ ${cat}: +${delta} new failures`, 'red')
341
+
342
+ // NEW: Show which packages regressed using failedPackages
343
+ if (last.failedPackages && last.failedPackages[cat]) {
344
+ const previousFails = new Set(previous.failedPackages?.[cat] || [])
345
+ const newFails = last.failedPackages[cat].filter(pkg => !previousFails.has(pkg))
346
+
347
+ if (newFails.length > 0) {
348
+ log(` New failures:`, 'red')
349
+ for (const pkg of newFails.slice(0, 5)) {
350
+ log(` - ${pkg}`, 'red')
351
+ }
352
+ if (newFails.length > 5) {
353
+ log(` ... and ${newFails.length - 5} more`, 'gray')
354
+ }
355
+ }
356
+ }
357
+ } else if (delta < 0) {
358
+ log(`āœ… ${cat}: ${Math.abs(delta)} failures fixed`, 'green')
359
+
360
+ // NEW: Show which packages were fixed
361
+ if (previous.failedPackages && previous.failedPackages[cat]) {
362
+ const currentFails = new Set(last.failedPackages?.[cat] || [])
363
+ const fixed = previous.failedPackages[cat].filter(pkg => !currentFails.has(pkg))
364
+
365
+ if (fixed.length > 0) {
366
+ log(` Fixed packages:`, 'green')
367
+ for (const pkg of fixed.slice(0, 3)) {
368
+ log(` - ${pkg}`, 'green')
369
+ }
370
+ if (fixed.length > 3) {
371
+ log(` ... and ${fixed.length - 3} more`, 'gray')
372
+ }
373
+ }
374
+ }
375
+ }
376
+ }
377
+
378
+ // NEW: Check all baseline categories (not just typeCheck)
379
+ if (last.baseline && previous.baseline) {
380
+ const checkTypes = ['build', 'lint', 'typeCheck', 'test']
381
+
382
+ for (const checkType of checkTypes) {
383
+ const lastBaseline = last.baseline[checkType]
384
+ const prevBaseline = previous.baseline[checkType]
385
+
386
+ if (!lastBaseline || !prevBaseline) {continue}
387
+
388
+ const delta = lastBaseline.summary.current - prevBaseline.summary.current
389
+
390
+ if (delta > 0) {
391
+ hasRegression = true
392
+ log(`āŒ ${checkType} baseline: +${delta} new failures`, 'red')
393
+
394
+ if (lastBaseline.newFailures?.length > 0) {
395
+ log(` New failing packages:`, 'red')
396
+ for (const pkg of lastBaseline.newFailures.slice(0, 5)) {
397
+ log(` - ${pkg}`, 'red')
398
+ }
399
+ if (lastBaseline.newFailures.length > 5) {
400
+ log(` ... and ${lastBaseline.newFailures.length - 5} more`, 'gray')
401
+ }
402
+ }
403
+ } else if (delta < 0) {
404
+ log(`āœ… ${checkType} baseline: ${Math.abs(delta)} failures fixed`, 'green')
405
+ }
406
+ }
407
+ }
408
+
409
+ log('')
410
+
411
+ if (hasRegression) {
412
+ log('āŒ REGRESSIONS DETECTED!', 'red')
413
+ log('', '')
414
+ log('Recommendations:', 'yellow')
415
+ log(' 1. Run "npx kb-devkit-qa --json" to see details', 'yellow')
416
+ log(' 2. Fix new failures before merging', 'yellow')
417
+ log(' 3. Update baseline if improvements: "pnpm baseline:update"', 'yellow')
418
+ process.exit(1)
419
+ } else {
420
+ log('āœ… No regressions detected', 'green')
421
+ process.exit(0)
422
+ }
423
+ }
424
+
425
+ // Main
426
+ function main() {
427
+ const args = process.argv.slice(2)
428
+ const command = args[0] || 'show'
429
+
430
+ switch (command) {
431
+ case 'save':
432
+ saveCurrentQA()
433
+ break
434
+ case 'show':
435
+ showHistory()
436
+ break
437
+ case 'trends':
438
+ showTrends()
439
+ break
440
+ case 'regressions':
441
+ detectRegressions()
442
+ break
443
+ default:
444
+ log('Usage:', 'yellow')
445
+ log(' npx kb-devkit-qa-history save [--from-file qa-results.json] # Save QA results', 'gray')
446
+ log(' npx kb-devkit-qa-history show # Show history', 'gray')
447
+ log(' npx kb-devkit-qa-history trends # Show trends', 'gray')
448
+ log(' npx kb-devkit-qa-history regressions # Detect regressions', 'gray')
449
+ break
450
+ }
451
+ }
452
+
453
+ main()