karajan-code 4.70.4 → 5.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 (380) hide show
  1. package/README.md +2 -2
  2. package/package.json +6 -14
  3. package/packages/hu-board/package.json +1 -1
  4. package/packages/hu-board/public/app.js +4 -18
  5. package/packages/hu-board/public/index.html +5 -2
  6. package/packages/hu-board/public/styles.css +26 -0
  7. package/packages/hu-board/public/utils/api.js +0 -74
  8. package/packages/hu-board/public/utils/board-tour.js +62 -0
  9. package/packages/hu-board/public/utils/board-view.js +49 -144
  10. package/packages/hu-board/public/utils/command-launcher.js +8 -70
  11. package/packages/hu-board/public/utils/formatters.js +10 -0
  12. package/packages/hu-board/public/utils/governance-view.js +13 -1
  13. package/packages/hu-board/public/utils/hu-actions.js +17 -68
  14. package/packages/hu-board/public/utils/init-listeners.js +7 -8
  15. package/packages/hu-board/public/utils/log-panel.js +3 -16
  16. package/packages/hu-board/public/utils/maggle-mode.js +2 -12
  17. package/packages/hu-board/public/utils/modals.js +7 -0
  18. package/packages/hu-board/public/utils/project-picker-view.js +1 -1
  19. package/packages/hu-board/public/utils/server-push.js +3 -22
  20. package/packages/hu-board/public/utils/shared-board-notice.js +33 -0
  21. package/packages/hu-board/public/utils/story-detail-view.js +5 -19
  22. package/packages/hu-board/src/cleanup-zombies.js +0 -1
  23. package/packages/hu-board/src/command-runner.js +36 -25
  24. package/packages/hu-board/src/db-connection.js +282 -0
  25. package/packages/hu-board/src/db-paths.js +113 -0
  26. package/packages/hu-board/src/db-queries.js +188 -0
  27. package/packages/hu-board/src/db-tombstones.js +51 -0
  28. package/packages/hu-board/src/db.js +28 -619
  29. package/packages/hu-board/src/ephemeral-cleaner.js +2 -3
  30. package/packages/hu-board/src/hu-zombie-reaper.js +5 -1
  31. package/packages/hu-board/src/plan-mutations.js +3 -153
  32. package/packages/hu-board/src/plans-service.js +55 -0
  33. package/packages/hu-board/src/routes/api/commands.js +122 -0
  34. package/packages/hu-board/src/routes/api/plans.js +55 -0
  35. package/packages/hu-board/src/routes/api/projects.js +267 -0
  36. package/packages/hu-board/src/routes/api/prompts.js +135 -0
  37. package/packages/hu-board/src/routes/api/sessions.js +95 -0
  38. package/packages/hu-board/src/routes/api/stories.js +183 -0
  39. package/packages/hu-board/src/routes/api/system.js +241 -0
  40. package/packages/hu-board/src/routes/api/tombstones.js +38 -0
  41. package/packages/hu-board/src/routes/api.js +24 -1371
  42. package/packages/hu-board/src/routes/governance.js +23 -1
  43. package/packages/hu-board/src/routes/lanes.js +40 -0
  44. package/packages/hu-board/src/routes/rag.js +4 -3
  45. package/packages/hu-board/src/server.js +20 -34
  46. package/packages/hu-board/src/sync-files.js +318 -0
  47. package/packages/hu-board/src/sync-project.js +99 -0
  48. package/packages/hu-board/src/sync.js +22 -566
  49. package/packages/hu-board/src/zombie-reaper.js +1 -1
  50. package/scripts/dual-publish.mjs +19 -3
  51. package/scripts/postinstall.js +15 -2
  52. package/scripts/verify-pack-mcp.mjs +11 -7
  53. package/scripts/verify-pack.mjs +4 -4
  54. package/src/audit/audit-history.js +3 -2
  55. package/src/audit/dead-exports.js +68 -59
  56. package/src/audit/deterministic-summary.js +33 -13
  57. package/src/audit/npm-signatures.js +41 -0
  58. package/src/audit/sbom.js +35 -0
  59. package/src/checks/agent-token.js +112 -0
  60. package/src/checks/dir-setup.js +34 -1
  61. package/src/checks/native-build.js +21 -39
  62. package/src/checks/runner.js +128 -142
  63. package/src/cli/advanced-commands.js +6 -6
  64. package/src/cli/register-meta.js +42 -82
  65. package/src/cli/register-pipeline.js +8 -143
  66. package/src/cli/register-plan.js +9 -100
  67. package/src/cli/retired-commands.js +49 -0
  68. package/src/cli.js +18 -5
  69. package/src/commands/agent-run.js +40 -26
  70. package/src/commands/audit.js +127 -124
  71. package/src/commands/board.js +5 -9
  72. package/src/commands/check.js +29 -16
  73. package/src/commands/doctor.js +2 -0
  74. package/src/commands/env.js +176 -150
  75. package/src/commands/go.js +71 -53
  76. package/src/commands/harden.js +90 -81
  77. package/src/commands/hu-start.js +52 -0
  78. package/src/commands/hu.js +288 -221
  79. package/src/commands/identity.js +111 -78
  80. package/src/commands/init-atril.js +47 -0
  81. package/src/commands/init.js +11 -40
  82. package/src/commands/install-tools.js +70 -85
  83. package/src/commands/main-guard.js +78 -0
  84. package/src/commands/policy.js +226 -184
  85. package/src/commands/qmd.js +27 -21
  86. package/src/commands/rag.js +75 -48
  87. package/src/commands/review-gate.js +421 -341
  88. package/src/commands/start.js +4 -5
  89. package/src/commands/steward.js +96 -47
  90. package/src/commands/tournament.js +46 -37
  91. package/src/commands/worktree.js +94 -25
  92. package/src/config/loader.js +34 -17
  93. package/src/config/secrets.js +36 -0
  94. package/src/config.js +1 -0
  95. package/src/environment/adr.js +16 -2
  96. package/src/git/karajan-label.js +20 -8
  97. package/src/guards/main-guard.js +54 -0
  98. package/src/harden/hook-templates.js +16 -1
  99. package/src/harden/sentinel/posttooluse.mjs +18 -6
  100. package/src/harden/sentinel/pretooluse-sentinel.mjs +64 -36
  101. package/src/harden/sentinel/sentinel-lib.mjs +129 -26
  102. package/src/harden/sentinel/sentinel-shell.mjs +5 -16
  103. package/src/harden/sentinel/sessionstart.mjs +3 -2
  104. package/src/harden/sentinel/stop.mjs +5 -4
  105. package/src/harden/workflow-templates.js +1 -1
  106. package/src/hu/board-lookup.js +29 -0
  107. package/src/hu/gh-identity.js +23 -0
  108. package/src/hu/github-board.js +4 -2
  109. package/src/hu/lane-prune.js +15 -7
  110. package/src/hu/lane-setup.js +65 -0
  111. package/src/hu/lanes.js +66 -0
  112. package/src/hu/team-board-mirror.js +3 -2
  113. package/src/infrastructure/command-runner.js +1 -1
  114. package/src/infrastructure/file-system-service.js +1 -1
  115. package/src/mcp/handlers/direct-handlers.js +2 -292
  116. package/src/mcp/handlers/hu-handlers.js +1 -35
  117. package/src/mcp/handlers/management-handlers.js +2 -42
  118. package/src/mcp/server-handlers.js +16 -22
  119. package/src/mcp/tools.js +1 -196
  120. package/src/plan/lane-plans.js +54 -0
  121. package/src/plan/plan-store.js +7 -4
  122. package/src/policy/add.js +26 -12
  123. package/src/policy/adr-number-check.js +31 -0
  124. package/src/prompts/audit.js +299 -339
  125. package/src/prompts/card-context.js +10 -5
  126. package/src/prompts/reviewer.js +5 -2
  127. package/src/rag/auto-update.js +56 -43
  128. package/src/rag/bootstrap-index.js +28 -54
  129. package/src/rag/migrate.js +1 -1
  130. package/src/rag/onnx-fallback.js +2 -2
  131. package/src/rag/pid-lease.js +70 -0
  132. package/src/rag/project-store.js +2 -12
  133. package/src/review/card-first.js +57 -46
  134. package/src/review/gate-gitignore.js +23 -4
  135. package/src/review/gate-hook.js +12 -4
  136. package/src/review/one-shot-review.js +45 -35
  137. package/src/review/publish-pr.js +14 -2
  138. package/src/review/repo-docs.js +73 -0
  139. package/src/review/repo-stack.js +59 -0
  140. package/src/review/reviewer-fallback.js +19 -4
  141. package/src/review/seal-commit.js +29 -0
  142. package/src/review/sonar-pregate.js +59 -44
  143. package/src/review/third-party-review.js +90 -16
  144. package/src/review/third-party-size.js +43 -0
  145. package/src/review/third-party-skills.js +54 -0
  146. package/src/review/verify-pr.js +1 -24
  147. package/src/roles/audit-role.js +106 -125
  148. package/src/skills/atril-skills.js +5 -4
  149. package/src/sonar/scanner.js +116 -101
  150. package/src/steward/invariants.js +5 -4
  151. package/src/steward/phantom-sweep.js +66 -0
  152. package/src/tournament/judge.js +60 -53
  153. package/src/utils/gh-account.js +37 -0
  154. package/src/utils/git.js +0 -13
  155. package/src/utils/home-migration.js +7 -1
  156. package/src/utils/main-checkout.js +53 -0
  157. package/src/utils/qmd-collection.js +3 -1
  158. package/src/utils/welcome.js +3 -3
  159. package/templates/{skills → atril/commands}/kj-architect.md +5 -0
  160. package/templates/{skills → atril/commands}/kj-audit.md +5 -0
  161. package/templates/{skills → atril/commands}/kj-board.md +5 -0
  162. package/templates/{skills → atril/commands}/kj-code.md +5 -0
  163. package/templates/{skills → atril/commands}/kj-csv-transform.md +5 -0
  164. package/templates/{skills → atril/commands}/kj-data-report.md +5 -0
  165. package/templates/{skills → atril/commands}/kj-discover.md +5 -0
  166. package/templates/{skills → atril/commands}/kj-rag-query.md +5 -0
  167. package/templates/{skills → atril/commands}/kj-review.md +5 -0
  168. package/templates/{skills → atril/commands}/kj-security.md +5 -0
  169. package/templates/{skills → atril/commands}/kj-sonar.md +5 -0
  170. package/templates/{skills → atril/commands}/kj-sql-analysis.md +5 -0
  171. package/templates/{skills → atril/commands}/kj-test.md +5 -0
  172. package/packages/hu-board/public/utils/run-launcher.js +0 -169
  173. package/packages/hu-board/src/run-tracker.js +0 -121
  174. package/src/activity-log.js +0 -59
  175. package/src/agents/host-agent.js +0 -38
  176. package/src/agents/model-registry.js +0 -142
  177. package/src/audit/plan-adherence.js +0 -224
  178. package/src/autonomy/arbiter-role.js +0 -71
  179. package/src/autonomy/arbiter.js +0 -22
  180. package/src/autonomy/decider.js +0 -18
  181. package/src/autonomy/decision-resolver.js +0 -58
  182. package/src/autonomy/outcome-report.js +0 -55
  183. package/src/autonomy/stage-ask.js +0 -30
  184. package/src/brain/decision-tracker.js +0 -125
  185. package/src/brain/decisor.js +0 -243
  186. package/src/brain/solomon-consult.js +0 -171
  187. package/src/brain/standby-scheduler.js +0 -8
  188. package/src/budget/comparison.js +0 -96
  189. package/src/canvas/parse-md.js +0 -238
  190. package/src/canvas/schema.js +0 -101
  191. package/src/canvas/to-flat.js +0 -85
  192. package/src/canvas/validate.js +0 -163
  193. package/src/ci/dispatch.js +0 -99
  194. package/src/ci/index.js +0 -3
  195. package/src/ci/pr-diff.js +0 -26
  196. package/src/cli/register-standby.js +0 -26
  197. package/src/commands/architect.js +0 -111
  198. package/src/commands/autorun.js +0 -49
  199. package/src/commands/researcher.js +0 -54
  200. package/src/commands/resume.js +0 -72
  201. package/src/commands/review.js +0 -89
  202. package/src/commands/run.js +0 -185
  203. package/src/commands/standby.js +0 -117
  204. package/src/commands/status.js +0 -36
  205. package/src/commands/triage.js +0 -78
  206. package/src/commands/undo.js +0 -62
  207. package/src/domains/domain-loader.js +0 -245
  208. package/src/domains/domain-registry.js +0 -140
  209. package/src/domains/domain-synthesizer.js +0 -129
  210. package/src/git/automation.js +0 -433
  211. package/src/git/coder-commit-guard.js +0 -40
  212. package/src/git/hu-automation.js +0 -243
  213. package/src/git/hu-snapshot.js +0 -10
  214. package/src/git/symlink-targets.js +0 -57
  215. package/src/golden/asserter.js +0 -92
  216. package/src/golden/loader.js +0 -52
  217. package/src/golden/runner.js +0 -92
  218. package/src/golden/schema.js +0 -41
  219. package/src/guards/duplicate-members.js +0 -86
  220. package/src/guards/intent-guard.js +0 -161
  221. package/src/guards/perf-guard.js +0 -126
  222. package/src/guards/policy-guard.js +0 -90
  223. package/src/guards/policy-resolver.js +0 -85
  224. package/src/guards/secret-redactor.js +0 -71
  225. package/src/hu/acceptance-runner.js +0 -138
  226. package/src/hu/lazy-planner.js +0 -109
  227. package/src/hu/parallel-executor.js +0 -94
  228. package/src/hu/routing-metrics.js +0 -17
  229. package/src/hu/split-validator.js +0 -143
  230. package/src/hu/splitting-detector.js +0 -158
  231. package/src/hu/splitting-generator.js +0 -175
  232. package/src/hu/store.js +0 -394
  233. package/src/infrastructure/mocks.js +0 -182
  234. package/src/mcp/handlers/run-handler.js +0 -318
  235. package/src/mcp/sovereignty-guard.js +0 -113
  236. package/src/mcp/tool-arg-normalizers.js +0 -16
  237. package/src/mutate/reviewer-signal.js +0 -76
  238. package/src/orchestrator/agent-fallback.js +0 -81
  239. package/src/orchestrator/brain-coordinator.js +0 -186
  240. package/src/orchestrator/ci-integration.js +0 -139
  241. package/src/orchestrator/config-init.js +0 -426
  242. package/src/orchestrator/direct-actions.js +0 -217
  243. package/src/orchestrator/drivers/error-recovery.js +0 -287
  244. package/src/orchestrator/drivers/init-context.js +0 -238
  245. package/src/orchestrator/drivers/iteration-loop.js +0 -382
  246. package/src/orchestrator/drivers/iteration-phases/coder-and-refactorer.js +0 -69
  247. package/src/orchestrator/drivers/iteration-phases/guards.js +0 -91
  248. package/src/orchestrator/drivers/iteration-phases/handle-approved.js +0 -29
  249. package/src/orchestrator/drivers/iteration-phases/quality-gates.js +0 -125
  250. package/src/orchestrator/drivers/iteration-phases/reviewer-gate.js +0 -62
  251. package/src/orchestrator/drivers/post-loop.js +0 -404
  252. package/src/orchestrator/drivers/pre-loop-phases/auto-hu-batch.js +0 -137
  253. package/src/orchestrator/drivers/pre-loop-phases/config-deprecations.js +0 -47
  254. package/src/orchestrator/drivers/pre-loop-phases/ensure-addyosmani-skills.js +0 -84
  255. package/src/orchestrator/drivers/pre-loop-phases/inject-loaded-plan.js +0 -393
  256. package/src/orchestrator/drivers/pre-loop.js +0 -524
  257. package/src/orchestrator/drivers/run-hu-batch.js +0 -468
  258. package/src/orchestrator/feedback-enrichment.js +0 -140
  259. package/src/orchestrator/feedback-queue.js +0 -151
  260. package/src/orchestrator/flow-control.js +0 -170
  261. package/src/orchestrator/flow-runner.js +0 -365
  262. package/src/orchestrator/fs-leak-detector.js +0 -228
  263. package/src/orchestrator/hu-scheduler.js +0 -114
  264. package/src/orchestrator/hu-sub-pipeline.js +0 -642
  265. package/src/orchestrator/iteration-gate.js +0 -73
  266. package/src/orchestrator/iteration-stages.js +0 -9
  267. package/src/orchestrator/parallel-limiter.js +0 -74
  268. package/src/orchestrator/pipeline-context.js +0 -72
  269. package/src/orchestrator/post-loop-stages.js +0 -468
  270. package/src/orchestrator/pre-loop-stages.js +0 -11
  271. package/src/orchestrator/preflight-checks.js +0 -487
  272. package/src/orchestrator/repair/repair-detector.js +0 -117
  273. package/src/orchestrator/repair/repair-runner.js +0 -142
  274. package/src/orchestrator/reviewer-fallback.js +0 -45
  275. package/src/orchestrator/role-output-compressor.js +0 -189
  276. package/src/orchestrator/session-journal.js +0 -308
  277. package/src/orchestrator/solomon-escalation.js +0 -331
  278. package/src/orchestrator/solomon-rules.js +0 -212
  279. package/src/orchestrator/stages/acceptance-coverage-stage.js +0 -47
  280. package/src/orchestrator/stages/architect-stage.js +0 -172
  281. package/src/orchestrator/stages/coder-stage.js +0 -506
  282. package/src/orchestrator/stages/domain-curator-stage.js +0 -82
  283. package/src/orchestrator/stages/format-stage.js +0 -61
  284. package/src/orchestrator/stages/hu-reviewer-phases/detect-and-apply-splits.js +0 -127
  285. package/src/orchestrator/stages/hu-reviewer-stage.js +0 -419
  286. package/src/orchestrator/stages/perf-stage.js +0 -140
  287. package/src/orchestrator/stages/planner-stage.js +0 -85
  288. package/src/orchestrator/stages/rag-context-stage.js +0 -80
  289. package/src/orchestrator/stages/rag-preload-decisor.js +0 -58
  290. package/src/orchestrator/stages/research-stage.js +0 -69
  291. package/src/orchestrator/stages/reviewer-stage.js +0 -431
  292. package/src/orchestrator/stages/sonar-stage.js +0 -439
  293. package/src/orchestrator/stages/stage-classes.js +0 -84
  294. package/src/orchestrator/stages/stage-executor.js +0 -155
  295. package/src/orchestrator/stages/suite-green-stage.js +0 -62
  296. package/src/orchestrator/stages/tdd-discipline-stage.js +0 -56
  297. package/src/orchestrator/stages/tool-judge-stage.js +0 -53
  298. package/src/orchestrator/stages/triage-stage.js +0 -172
  299. package/src/orchestrator/standby.js +0 -71
  300. package/src/orchestrator/verification-gate.js +0 -183
  301. package/src/orchestrator.js +0 -22
  302. package/src/plan/adr-generator.js +0 -170
  303. package/src/plan/adr-loader.js +0 -60
  304. package/src/plan/plan-executor.js +0 -100
  305. package/src/plan/plan-validation.js +0 -4
  306. package/src/prompts/acceptance-coverage.js +0 -32
  307. package/src/prompts/arbiter.js +0 -38
  308. package/src/prompts/architect.js +0 -103
  309. package/src/prompts/hu-reviewer.js +0 -244
  310. package/src/prompts/tool-correctness-judge.js +0 -31
  311. package/src/prompts/triage.js +0 -57
  312. package/src/rag/embedder.js +0 -2
  313. package/src/rag/embedders/_cloud-base.js +0 -2
  314. package/src/rag/embedders/cohere.js +0 -2
  315. package/src/rag/embedders/mistral.js +0 -2
  316. package/src/rag/embedders/onnx.js +0 -2
  317. package/src/rag/embedders/openai.js +0 -2
  318. package/src/rag/embedders/voyage.js +0 -2
  319. package/src/rag/rerank.js +0 -4
  320. package/src/rag/where-parser.js +0 -4
  321. package/src/repeat-detector.js +0 -88
  322. package/src/review/diff-generator.js +0 -112
  323. package/src/review/git-stash-helper.js +0 -104
  324. package/src/review/schema.js +0 -31
  325. package/src/review/scope-filter.js +0 -152
  326. package/src/review/snapshot-diff.js +0 -122
  327. package/src/review/tdd-discipline.js +0 -90
  328. package/src/review/tdd-policy.js +0 -69
  329. package/src/roles/acceptance-coverage-role.js +0 -78
  330. package/src/roles/architect-role.js +0 -77
  331. package/src/roles/coder-role.js +0 -113
  332. package/src/roles/commiter-role.js +0 -113
  333. package/src/roles/domain-curator-role.js +0 -134
  334. package/src/roles/hu-reviewer-role.js +0 -64
  335. package/src/roles/impeccable-role.js +0 -85
  336. package/src/roles/index.js +0 -18
  337. package/src/roles/karajan-brain-role.js +0 -134
  338. package/src/roles/perf-role.js +0 -122
  339. package/src/roles/planner-role.js +0 -151
  340. package/src/roles/refactorer-role.js +0 -30
  341. package/src/roles/repairer-role.js +0 -181
  342. package/src/roles/researcher-role.js +0 -70
  343. package/src/roles/reviewer-role.js +0 -115
  344. package/src/roles/security-role.js +0 -73
  345. package/src/roles/solomon-role.js +0 -271
  346. package/src/roles/sonar-role.js +0 -72
  347. package/src/roles/tester-role.js +0 -235
  348. package/src/roles/tool-correctness-judge-role.js +0 -78
  349. package/src/roles/triage-role.js +0 -99
  350. package/src/session/journal/decisions-writer.js +0 -200
  351. package/src/session/journal/iteration-logger.js +0 -194
  352. package/src/session/journal/summary-writer.js +0 -320
  353. package/src/session/journal/tree-writer.js +0 -164
  354. package/src/session/journal/write-all.js +0 -89
  355. package/src/session-cleanup.js +0 -116
  356. package/src/skills/semantic-detector.js +0 -167
  357. package/src/skills/skill-detector.js +0 -385
  358. package/src/sonar/cloud-scanner.js +0 -78
  359. package/src/tracker-bootstrap.js +0 -44
  360. package/src/types/index.js +0 -38
  361. package/src/types/policy.js +0 -43
  362. package/src/types/solomon.js +0 -87
  363. package/src/utils/async-lock.js +0 -31
  364. package/src/utils/budget.js +0 -229
  365. package/src/utils/cwd-confirm.js +0 -39
  366. package/src/utils/display/event-handlers.js +0 -403
  367. package/src/utils/display/formatters.js +0 -132
  368. package/src/utils/display/header.js +0 -47
  369. package/src/utils/display/session-summary.js +0 -118
  370. package/src/utils/display/solomon.js +0 -42
  371. package/src/utils/pricing.js +0 -29
  372. package/src/utils/quota-simulator.js +0 -56
  373. package/src/utils/role-assigner.js +0 -137
  374. package/src/utils/rtk-wrapper.js +0 -87
  375. package/src/utils/run-registry.js +0 -9
  376. package/src/utils/status-dashboard.js +0 -254
  377. package/src/utils/tool-call-extractor.js +0 -56
  378. package/src/webperf/devtools-detect.js +0 -112
  379. package/src/webperf/webperf-role-integration.js +0 -66
  380. package/templates/skills/kj-run.md +0 -73
@@ -1,376 +1,31 @@
1
- import Database from 'better-sqlite3';
2
- import { join, dirname } from 'node:path';
3
- import { mkdirSync, renameSync, rmSync } from 'node:fs';
4
- import { tmpdir } from 'node:os';
5
-
6
- /** @type {import('better-sqlite3').Database | null} */
7
- let db = null;
8
-
9
- // Per-process tmp dir for VITEST runs that forget to set KJ_HOME.
10
- // Without this, a test that touches initDb() / upsertProject() drops
11
- // rows into the developer's real `~/.karajan/hu-board.db` and the
12
- // running board UI shows phantom projects (observed: "sess-1",
13
- // "default", "Karajan Code" with 49 test fixture plans). Memoise so
14
- // every consumer in the same process agrees on the path. The
15
- // trailing `.karajan` segment keeps semantic parity with non-VITEST
16
- // defaults so any caller assuming the path ends in `.karajan` still
17
- // works.
18
- let _vitestKjHome = null;
19
- function vitestTmpKjHome() {
20
- if (_vitestKjHome) return _vitestKjHome;
21
- _vitestKjHome = join(
22
- tmpdir(),
23
- `karajan-vitest-${process.pid}-${Math.random().toString(36).slice(2, 10)}`,
24
- '.karajan'
25
- );
26
- // KJC-BUG-0075: clean up on clean exit so /tmp doesn't accumulate one
27
- // dir per fork × run. We rm the *parent* (the `karajan-vitest-<pid>-<rand>`
28
- // wrapper), not the `.karajan` segment, so the whole tmp tree goes.
29
- // Only fires on graceful exit; SIGKILL leaks are caught by the
30
- // global-setup mtime purge at the start of the next run.
31
- const parent = dirname(_vitestKjHome);
32
- process.on('exit', () => {
33
- try {
34
- rmSync(parent, { recursive: true, force: true });
35
- } catch {
36
- // best-effort: never crash a test process on cleanup failure
37
- }
38
- });
39
- return _vitestKjHome;
40
- }
41
-
42
- /**
43
- * Returns the KJ home directory, respecting KJ_HOME env var.
44
- * Under VITEST without KJ_HOME, returns a per-process tmp dir to
45
- * prevent accidental writes into the developer's real `~/.karajan/`.
46
- * @returns {string}
47
- */
48
- export function getKjHome() {
49
- // KJC-TSK-0420: same precedence chain as src/utils/paths.js::resolveHome():
50
- // KARAJAN_HOME (canonical) > KJ_HOME (legacy) > VITEST tmp > ~/.karajan.
51
- // Kept inline rather than cross-workspace imported so the hu-board package
52
- // does not gain a new src/ dep; the precedence behaviour is the same.
53
- if (process.env.KARAJAN_HOME) return process.env.KARAJAN_HOME;
54
- if (process.env.KJ_HOME) return process.env.KJ_HOME;
55
- if (process.env.VITEST) return vitestTmpKjHome();
56
- return join(process.env.HOME || '/root', '.karajan');
57
- }
58
-
59
- /**
60
- * Canonical path to the HU Board's run-logs directory
61
- * (`<karajan-home>/hu-board-runs/`). Centralised here so the four
62
- * board callers (plan-mutations, command-runner, api.js GET routes
63
- * for plan + commandId logs) and the garbage-collector all agree on
64
- * the same string — KJC-TSK-0421.
65
- *
66
- * @returns {string}
67
- */
68
- export function getHuBoardRunsDir() {
69
- return join(getKjHome(), 'hu-board-runs');
70
- }
71
-
72
- /**
73
- * Canonical path to the plans directory the board scans
74
- * (`<karajan-home>/plans/`). Respects `KJ_PLANS_DIR` as an absolute
75
- * override, mirroring sync.js. KJC-BUG-0059 (v2.19.3): preflight
76
- * endpoint used a hard-coded `~/.kj/plans/` legacy fallback that
77
- * survived the v2.19.0 consolidation, causing the board to report
78
- * "directorio del proyecto: no detectado" even when the plan on disk
79
- * had a valid `projectDir`.
80
- *
81
- * @returns {string}
82
- */
83
- export function getHuBoardPlansDir() {
84
- return process.env.KJ_PLANS_DIR || join(getKjHome(), 'plans');
85
- }
86
-
87
- /**
88
- * Legacy `~/.kj/plans/` directory — only returned when KJ_PLANS_DIR
89
- * is unset (it would be the same path anyway when overridden).
90
- * Callers iterate canonical + legacy so users who have not yet run
91
- * the v2.19.0 auto-migrator still see their plans. KJC-BUG-0059.
92
- *
93
- * @returns {string|null}
94
- */
95
- export function getHuBoardLegacyPlansDir() {
96
- if (process.env.KJ_PLANS_DIR) return null;
97
- return join(process.env.HOME || '/root', '.kj', 'plans');
98
- }
99
-
100
- /**
101
- * Ordered list of every plans root the board should scan: canonical
102
- * first (`~/.karajan/plans/`), legacy second when applicable
103
- * (`~/.kj/plans/`). Honours `KJ_PLANS_DIR` as a single override,
104
- * matching `sync.js`'s fullScan / startWatcher precedence so the
105
- * preflight, plans-outcome, delete-project and delete-plan endpoints
106
- * see the same set of files the watcher sees. KJC-BUG-0059.
107
- *
108
- * @returns {string[]}
109
- */
110
- export function getHuBoardPlansDirs() {
111
- const dirs = [getHuBoardPlansDir()];
112
- const legacy = getHuBoardLegacyPlansDir();
113
- if (legacy) dirs.push(legacy);
114
- return dirs;
115
- }
116
-
117
- // Bump when the schema gains a column / index / table that older
118
- // Karajan versions cannot understand. A DB written by a newer Karajan
119
- // refuses to open here so it does not get silently downgraded.
120
- const SCHEMA_VERSION = 1;
121
-
122
- /**
123
- * Tries to open the SQLite database; if it is corrupt, moves it aside
124
- * and opens a fresh one. `fullScan` afterwards rebuilds the cache from
125
- * the JSON files on disk — the DB is a cache, never the source of
126
- * truth. Without this the board crashed at startup with a raw
127
- * `SqliteError: database disk image is malformed` whenever the WAL
128
- * checkpoint died in the wrong place.
129
- *
130
- * @param {string} dbPath
131
- * @returns {import('better-sqlite3').Database}
132
- */
133
- function openDbResilient(dbPath) {
134
- try {
135
- const handle = new Database(dbPath);
136
- // better-sqlite3 opens lazily — the first pragma is what actually
137
- // touches the file and surfaces "file is not a database". Probe
138
- // `schema_version` (a built-in SQLite pragma) to force it.
139
- handle.pragma("schema_version", { simple: true });
140
- return handle;
141
- } catch (err) {
142
- const code = err?.code || "";
143
- const msg = err?.message || "";
144
- if (code === "SQLITE_CORRUPT" || code === "SQLITE_NOTADB" || /malformed|not a database/i.test(msg)) {
145
- const aside = `${dbPath}.corrupt-${Date.now()}`;
146
- try { renameSync(dbPath, aside); } catch { /* may already be gone */ }
147
- // eslint-disable-next-line no-console
148
- console.warn(`[hu-board] DB at ${dbPath} was corrupt; moved aside to ${aside}. Rebuilding from disk via fullScan.`);
149
- return new Database(dbPath);
150
- }
151
- throw err;
152
- }
153
- }
154
-
155
- /**
156
- * Initializes the SQLite database and creates tables if they don't exist.
157
- * @returns {import('better-sqlite3').Database}
158
- */
159
- export function initDb() {
160
- const kjHome = getKjHome();
161
- mkdirSync(kjHome, { recursive: true });
162
-
163
- const dbPath = join(kjHome, 'hu-board.db');
164
- db = openDbResilient(dbPath);
165
-
166
- db.pragma('journal_mode = WAL');
167
- db.pragma('foreign_keys = ON');
168
- // Wait up to 5s for a competing writer (e.g. concurrent fullScan +
169
- // mutation) before giving up. Without this any contention surfaced
170
- // as SQLITE_BUSY and crashed the request mid-flight.
171
- db.pragma('busy_timeout = 5000');
172
-
173
- // Schema-version guard. If the DB was written by a newer Karajan with
174
- // a different schema, refuse to open instead of silently mis-binding
175
- // named parameters against a renamed column.
176
- const onDisk = db.pragma('user_version', { simple: true });
177
- if (onDisk > SCHEMA_VERSION) {
178
- throw new Error(
179
- `[hu-board] DB schema is from a newer Karajan (user_version=${onDisk}, this build expects ${SCHEMA_VERSION}). `
180
- + `Upgrade karajan-code or remove ${dbPath} (it will be rebuilt from disk).`
181
- );
182
- }
183
- if (onDisk < SCHEMA_VERSION) {
184
- db.pragma(`user_version = ${SCHEMA_VERSION}`);
185
- }
186
-
187
- db.exec(`
188
- CREATE TABLE IF NOT EXISTS projects (
189
- id TEXT PRIMARY KEY,
190
- name TEXT,
191
- first_seen TEXT,
192
- last_activity TEXT,
193
- total_stories INTEGER DEFAULT 0
194
- );
195
-
196
- CREATE TABLE IF NOT EXISTS stories (
197
- id TEXT PRIMARY KEY,
198
- project_id TEXT,
199
- session_id TEXT,
200
- status TEXT DEFAULT 'pending',
201
- title TEXT,
202
- original_text TEXT,
203
- certified_as TEXT,
204
- certified_want TEXT,
205
- certified_so_that TEXT,
206
- quality_total INTEGER,
207
- quality_d1 INTEGER,
208
- quality_d2 INTEGER,
209
- quality_d3 INTEGER,
210
- quality_d4 INTEGER,
211
- quality_d5 INTEGER,
212
- quality_d6 INTEGER,
213
- antipatterns TEXT,
214
- ac_format TEXT,
215
- acceptance_criteria TEXT,
216
- created_at TEXT,
217
- updated_at TEXT,
218
- certified_at TEXT
219
- );
220
-
221
- CREATE TABLE IF NOT EXISTS sessions (
222
- id TEXT PRIMARY KEY,
223
- project_id TEXT,
224
- task TEXT,
225
- status TEXT,
226
- created_at TEXT,
227
- updated_at TEXT,
228
- iterations INTEGER DEFAULT 0,
229
- duration_ms INTEGER,
230
- approved INTEGER DEFAULT 0,
231
- commits TEXT,
232
- stages_completed TEXT,
233
- checkpoints TEXT,
234
- llm_calls TEXT,
235
- config_snapshot TEXT,
236
- budget TEXT
237
- );
238
-
239
- CREATE TABLE IF NOT EXISTS context_requests (
240
- id INTEGER PRIMARY KEY AUTOINCREMENT,
241
- story_id TEXT,
242
- fields_needed TEXT,
243
- question TEXT,
244
- answer TEXT,
245
- requested_at TEXT,
246
- answered_at TEXT
247
- );
248
-
249
- -- KJC-TSK-0380 — Tombstones: "deleted, do not resurrect".
250
- -- The board's data is reconstructed from the filesystem on every
251
- -- fullScan(), so a plain DELETE from the table is silently undone
252
- -- the next time chokidar fires. The tombstone table holds the ids
253
- -- the user explicitly killed; sync* functions check it before any
254
- -- upsert and skip + clean fs paths.
255
- CREATE TABLE IF NOT EXISTS tombstones (
256
- resource_type TEXT NOT NULL,
257
- resource_id TEXT NOT NULL,
258
- deleted_at TEXT NOT NULL,
259
- source TEXT NOT NULL,
260
- fs_paths TEXT,
261
- PRIMARY KEY (resource_type, resource_id)
262
- );
263
-
264
- CREATE INDEX IF NOT EXISTS idx_stories_project ON stories(project_id);
265
- CREATE INDEX IF NOT EXISTS idx_stories_status ON stories(status);
266
- CREATE INDEX IF NOT EXISTS idx_sessions_project ON sessions(project_id);
267
- CREATE INDEX IF NOT EXISTS idx_context_story ON context_requests(story_id);
268
- CREATE INDEX IF NOT EXISTS idx_tombstones_deleted_at ON tombstones(deleted_at);
269
- `);
270
-
271
- // --- Migrations ---
272
- // Each ALTER is idempotent on restart — we swallow the duplicate-column
273
- // error so adding one over time doesn't force a board DB nuke.
274
- //
275
- // plan_id: lets the mutation endpoints locate the source-of-truth plan
276
- // JSON at ~/.karajan/plans/<slug>/<planId>.json without a scan.
277
- // ac_count: number of acceptance criteria, surfaced on the card.
278
- // test_count: number of acceptance_tests, surfaced on the card.
279
- // blocked_by: denormalised JSON array of HU ids this story waits on,
280
- // so the card can render `⏳ waits for: lao-001` inline.
281
- try { db.exec('ALTER TABLE stories ADD COLUMN plan_id TEXT'); } catch { /* already migrated */ }
282
- try { db.exec('ALTER TABLE stories ADD COLUMN ac_count INTEGER'); } catch { /* already migrated */ }
283
- try { db.exec('ALTER TABLE stories ADD COLUMN test_count INTEGER'); } catch { /* already migrated */ }
284
- try { db.exec('ALTER TABLE stories ADD COLUMN blocked_by TEXT'); } catch { /* already migrated */ }
285
- // acceptance_tests is the raw JSON of `hu.acceptance_tests` — the
286
- // plan stores each test as either a plain string or an object with
287
- // { name, description, given/when/then, ... }. The modal renders
288
- // the list; the card just shows the count (test_count above).
289
- try { db.exec('ALTER TABLE stories ADD COLUMN acceptance_tests TEXT'); } catch { /* already migrated */ }
290
- // plan_order is the index of the HU in `plan.hus[]` as the planner
291
- // emitted it. The planner's output is already topological (it calls
292
- // addHu with `blocked_by: [prevId]`), so sorting the board by this
293
- // column puts roots first and dependents last — matching what
294
- // `kj run --plan` will actually execute.
295
- try { db.exec('ALTER TABLE stories ADD COLUMN plan_order INTEGER'); } catch { /* already migrated */ }
296
- // spec_section (v2.7.5 PR C): the SPEC.md heading this HU
297
- // implements (e.g. "5.3" or "§5 Initial Scope"). Surfaces in the
298
- // card + modal so the user can trace HU → spec coverage.
299
- try { db.exec('ALTER TABLE stories ADD COLUMN spec_section TEXT'); } catch { /* already migrated */ }
300
- // outcome (PR3): JSON blob with what actually happened during
301
- // execution — iterations, duration, commits, branch, blockers,
302
- // human summary. Stamped by hu-sub-pipeline at the end of each
303
- // HU and ingested by syncPlanFile so the board can show the 📄
304
- // indicator and detail modal without parsing the plan JSON
305
- // again. Stored as TEXT (JSON.stringify'd) for simplicity —
306
- // parsed on read by the UI.
307
- try { db.exec('ALTER TABLE stories ADD COLUMN outcome TEXT'); } catch { /* already migrated */ }
308
- // KJC-TSK-0394: result (pass | fail | partial | null) ortogonal al status.
309
- // Diferente del campo `outcome` arriba (que es blob JSON con metadata del
310
- // run). result es el enum de "último resultado" para mostrar como badge.
311
- try { db.exec('ALTER TABLE stories ADD COLUMN result TEXT'); } catch { /* already migrated */ }
312
- // KJC-TSK-0406: coder_model y reviewer_model independientes por HU.
313
- // Asignados por el triage según complexity (KJC-TSK-0405) y override-able
314
- // desde el modal del board. coder_provider y reviewer_provider permiten
315
- // cross-provider review (claude↔codex) sin tocar el config global.
316
- try { db.exec('ALTER TABLE stories ADD COLUMN coder_model TEXT'); } catch { /* already migrated */ }
317
- try { db.exec('ALTER TABLE stories ADD COLUMN reviewer_model TEXT'); } catch { /* already migrated */ }
318
- try { db.exec('ALTER TABLE stories ADD COLUMN coder_provider TEXT'); } catch { /* already migrated */ }
319
- try { db.exec('ALTER TABLE stories ADD COLUMN reviewer_provider TEXT'); } catch { /* already migrated */ }
320
- // KJC-PRP-0002 PR6: free-form handle of the human (or dev_XXX AI) who
321
- // owns this HU on a team-shared board. NULL = unassigned. Only surfaced
322
- // in the UI when the parent project is `is_shared = 1`, but stored
323
- // unconditionally so solo devs who later promote a plan don't lose
324
- // values they typed pre-share.
325
- try { db.exec('ALTER TABLE stories ADD COLUMN assignee TEXT'); } catch { /* already migrated */ }
326
- // KJC-TSK-0514 (Cost C): persisted USD cost of running this HU. NULL
327
- // until the orchestrator (Cost D) calls setStoryCost on HU close. UI
328
- // renders "—" while NULL. Stored as REAL so the SQL aggregations in
329
- // Cost E (/api/projects/:id/cost) can SUM directly without parsing.
330
- try { db.exec('ALTER TABLE stories ADD COLUMN cost_usd REAL'); } catch { /* already migrated */ }
331
- // KJC-TSK-0525 (Φ0-G): persisted cached/input tokens per HU so the
332
- // board can render the "🎯 N% cached" badge without re-parsing the
333
- // session summary. NULL = unmeasured (older runs, cold runs without
334
- // cache support). INTEGER because providers report token counts as
335
- // ints. Ratio is computed at read time as cached_tokens / tokens_in.
336
- try { db.exec('ALTER TABLE stories ADD COLUMN cached_tokens INTEGER'); } catch { /* already migrated */ }
337
- try { db.exec('ALTER TABLE stories ADD COLUMN tokens_in INTEGER'); } catch { /* already migrated */ }
338
- try { db.exec('CREATE INDEX IF NOT EXISTS idx_stories_plan ON stories(plan_id)'); } catch { /* ignore */ }
339
-
340
- // is_test (KJC-TSK-0371 — board polish #3): per-project flag that
341
- // controls the ephemeral-cleanup heuristic. NULL = follow heuristic
342
- // (id under tmp_/test_/demo_/kj-test- → ephemeral after 24h);
343
- // 1 = always treat as ephemeral; 0 = NEVER auto-clean (user override).
344
- // The UI surfaces this as a toggle on each project card.
345
- try { db.exec('ALTER TABLE projects ADD COLUMN is_test INTEGER'); } catch { /* already migrated */ }
346
-
347
- // is_shared (KJC-PRP-0002 PR3): 1 when at least one plan of the project came
348
- // from `<projectDir>/.karajan-shared/plans/`. Surfaced as a 🔗 badge on the
349
- // board so the user can tell at a glance which projects are team-tracked.
350
- try { db.exec('ALTER TABLE projects ADD COLUMN is_shared INTEGER'); } catch { /* already migrated */ }
351
-
352
- return db;
353
- }
354
-
355
- /**
356
- * Returns the current database instance.
357
- * @returns {import('better-sqlite3').Database}
358
- */
359
- export function getDb() {
360
- if (!db) throw new Error('Database not initialized. Call initDb() first.');
361
- return db;
362
- }
363
-
364
- /**
365
- * Lazily initialise the DB if it isn't open yet (KJC-BUG-0090). The cost
366
- * writers run from `kj run`/`kj autorun` paths that may never start the board
367
- * (which is what normally calls initDb), so cost writes failed with
368
- * "Database not initialized". This makes those writers self-sufficient.
369
- */
370
- export function ensureDb() {
371
- if (!db) initDb();
372
- return db;
373
- }
1
+ import { getDb } from './db-connection.js';
2
+
3
+ // KJC-TSK-0542: db.js is the facade every caller imports. The paths and the connection (with
4
+ // the schema) live in their own modules and are re-exported from here.
5
+ export {
6
+ getKjHome,
7
+ getHuBoardRunsDir,
8
+ getHuBoardPlansDir,
9
+ getHuBoardLegacyPlansDir,
10
+ getHuBoardPlansDirs,
11
+ } from './db-paths.js';
12
+ export { initDb, getDb, ensureDb, closeDb } from './db-connection.js';
13
+ export {
14
+ addTombstone,
15
+ isTombstoned,
16
+ getTombstone,
17
+ removeTombstone,
18
+ listTombstones,
19
+ } from './db-tombstones.js';
20
+ export {
21
+ getProjects,
22
+ getStoriesByProject,
23
+ getProjectCost,
24
+ getStoryDetail,
25
+ getSessionsByProject,
26
+ getSessionDetail,
27
+ getDashboardStats,
28
+ } from './db-queries.js';
374
29
 
375
30
  /**
376
31
  * Inserts or updates a project record.
@@ -655,191 +310,6 @@ export function upsertSession(session) {
655
310
  });
656
311
  }
657
312
 
658
- /**
659
- * Returns all projects with aggregated stats.
660
- * @returns {Array<object>}
661
- */
662
- export function getProjects() {
663
- return getDb().prepare(`
664
- SELECT p.*,
665
- (SELECT COUNT(*) FROM stories s WHERE s.project_id = p.id) AS story_count,
666
- (SELECT COUNT(*) FROM stories s WHERE s.project_id = p.id AND s.status = 'certified') AS certified_count,
667
- (SELECT COUNT(*) FROM stories s WHERE s.project_id = p.id AND s.status = 'pending') AS pending_count,
668
- (SELECT COUNT(*) FROM sessions ss WHERE ss.project_id = p.id) AS session_count
669
- FROM projects p
670
- ORDER BY p.last_activity DESC
671
- `).all();
672
- }
673
-
674
- /**
675
- * Returns stories for a specific project.
676
- * @param {string} projectId
677
- * @returns {Array<object>}
678
- */
679
- /**
680
- * Fetch all stories of a project ordered by plan_order ASC (roots first,
681
- * dependents last — matches what `kj run --plan` will actually execute)
682
- * and falling back to updated_at DESC for legacy rows that never had
683
- * plan_order stamped.
684
- */
685
- export function getStoriesByProject(projectId) {
686
- return getDb().prepare(`
687
- SELECT * FROM stories WHERE project_id = ?
688
- ORDER BY plan_order IS NULL, plan_order ASC, updated_at DESC
689
- `).all(projectId);
690
- }
691
-
692
- /**
693
- * Aggregate $ cost for a project (KJC-TSK-0516, Cost E).
694
- * Sums stories.cost_usd, groups by plan_id ('unassigned' for NULL).
695
- * Returns null if project does not exist; otherwise an object with
696
- * totalUsd=0 and byPlan=[] when no stories carry cost_usd yet.
697
- * @param {string} projectId
698
- * @returns {{ totalUsd:number, byPlan:Array<{planId:string,totalUsd:number,huCount:number}>, unknownModelTokens:object, currency:string } | null}
699
- */
700
- export function getProjectCost(projectId) {
701
- const project = getDb()
702
- .prepare('SELECT id FROM projects WHERE id = ?')
703
- .get(projectId);
704
- if (!project) return null;
705
-
706
- // Φ0-G: cached_tokens / tokens_in are nullable independently of
707
- // cost_usd, so we read all three columns but keep cost as the
708
- // primary filter (a row with only cache telemetry but no $ should
709
- // not appear in the per-plan totals — cost rules the byPlan list).
710
- const rows = getDb()
711
- .prepare(
712
- `SELECT plan_id, cost_usd, cached_tokens, tokens_in FROM stories
713
- WHERE project_id = ? AND cost_usd IS NOT NULL`
714
- )
715
- .all(projectId);
716
-
717
- const round4 = (n) => Math.round(n * 10000) / 10000;
718
- const byPlanMap = new Map();
719
- let totalUsd = 0;
720
- let totalCached = 0;
721
- let totalTokensIn = 0;
722
- for (const r of rows) {
723
- const planId = r.plan_id || 'unassigned';
724
- const cost = Number(r.cost_usd);
725
- if (!Number.isFinite(cost)) continue;
726
- totalUsd += cost;
727
- const cached = Number(r.cached_tokens);
728
- const tin = Number(r.tokens_in);
729
- if (Number.isFinite(cached) && cached >= 0) totalCached += cached;
730
- if (Number.isFinite(tin) && tin >= 0) totalTokensIn += tin;
731
- const cur = byPlanMap.get(planId) || { planId, totalUsd: 0, huCount: 0, cachedTokens: 0, tokensIn: 0 };
732
- cur.totalUsd += cost;
733
- cur.huCount += 1;
734
- if (Number.isFinite(cached) && cached >= 0) cur.cachedTokens += cached;
735
- if (Number.isFinite(tin) && tin >= 0) cur.tokensIn += tin;
736
- byPlanMap.set(planId, cur);
737
- }
738
-
739
- const ratio = (cached, tin) => tin > 0 ? Math.round((cached / tin) * 1000) / 10 : null;
740
- const byPlan = Array.from(byPlanMap.values())
741
- .map((p) => ({
742
- planId: p.planId,
743
- totalUsd: round4(p.totalUsd),
744
- huCount: p.huCount,
745
- cachedTokens: p.cachedTokens,
746
- tokensIn: p.tokensIn,
747
- cachedRatioPct: ratio(p.cachedTokens, p.tokensIn),
748
- }))
749
- .sort((a, b) => b.totalUsd - a.totalUsd);
750
-
751
- return {
752
- totalUsd: round4(totalUsd),
753
- byPlan,
754
- cachedTokens: totalCached,
755
- tokensIn: totalTokensIn,
756
- cachedRatioPct: ratio(totalCached, totalTokensIn),
757
- unknownModelTokens: { tokensIn: 0, tokensOut: 0, models: [] },
758
- currency: 'USD',
759
- };
760
- }
761
-
762
- /**
763
- * Returns full story detail including context requests.
764
- * @param {string} storyId
765
- * @returns {object | null}
766
- */
767
- export function getStoryDetail(storyId) {
768
- const story = getDb().prepare('SELECT * FROM stories WHERE id = ?').get(storyId);
769
- if (!story) return null;
770
-
771
- const contextRequests = getDb().prepare(
772
- 'SELECT * FROM context_requests WHERE story_id = ? ORDER BY requested_at DESC'
773
- ).all(storyId);
774
-
775
- return { ...story, context_requests: contextRequests };
776
- }
777
-
778
- /**
779
- * Returns sessions for a specific project.
780
- *
781
- * The `project_name` column is joined in so the UI can show a human
782
- * label like `tmp_kj-test-4 · trivial(doc) · 08:35` instead of just
783
- * the cryptic `s_2026-05-07T...` id.
784
- *
785
- * @param {string} projectId
786
- * @returns {Array<object>}
787
- */
788
- export function getSessionsByProject(projectId) {
789
- return getDb().prepare(`
790
- SELECT s.id, s.project_id, s.task, s.status, s.created_at, s.updated_at,
791
- s.iterations, s.duration_ms, s.approved, s.stages_completed,
792
- p.name AS project_name
793
- FROM sessions s
794
- LEFT JOIN projects p ON p.id = s.project_id
795
- WHERE s.project_id = ?
796
- ORDER BY s.created_at DESC
797
- `).all(projectId);
798
- }
799
-
800
- /**
801
- * Returns full session detail (with the joined project_name).
802
- * @param {string} sessionId
803
- * @returns {object | null}
804
- */
805
- export function getSessionDetail(sessionId) {
806
- return getDb().prepare(`
807
- SELECT s.*, p.name AS project_name
808
- FROM sessions s
809
- LEFT JOIN projects p ON p.id = s.project_id
810
- WHERE s.id = ?
811
- `).get(sessionId);
812
- }
813
-
814
- /**
815
- * Returns global dashboard statistics.
816
- * @returns {object}
817
- */
818
- export function getDashboardStats() {
819
- const db = getDb();
820
- const totalStories = db.prepare('SELECT COUNT(*) AS count FROM stories').get().count;
821
- const certifiedStories = db.prepare("SELECT COUNT(*) AS count FROM stories WHERE status = 'certified'").get().count;
822
- const pendingStories = db.prepare("SELECT COUNT(*) AS count FROM stories WHERE status = 'pending'").get().count;
823
- const doneStories = db.prepare("SELECT COUNT(*) AS count FROM stories WHERE status = 'done'").get().count;
824
- const needsContextStories = db.prepare("SELECT COUNT(*) AS count FROM stories WHERE status = 'needs_context'").get().count;
825
- const avgQuality = db.prepare('SELECT AVG(quality_total) AS avg FROM stories WHERE quality_total IS NOT NULL').get().avg;
826
- const totalSessions = db.prepare('SELECT COUNT(*) AS count FROM sessions').get().count;
827
- const approvedSessions = db.prepare('SELECT COUNT(*) AS count FROM sessions WHERE approved = 1').get().count;
828
- const totalProjects = db.prepare('SELECT COUNT(*) AS count FROM projects').get().count;
829
-
830
- return {
831
- total_stories: totalStories,
832
- certified_stories: certifiedStories,
833
- pending_stories: pendingStories,
834
- done_stories: doneStories,
835
- needs_context_stories: needsContextStories,
836
- avg_quality: avgQuality ? Math.round(avgQuality * 10) / 10 : null,
837
- total_sessions: totalSessions,
838
- approved_sessions: approvedSessions,
839
- total_projects: totalProjects,
840
- };
841
- }
842
-
843
313
  /**
844
314
  * Inserts a context request for a story.
845
315
  * @param {object} req
@@ -913,64 +383,3 @@ export function deleteSession(sessionId) {
913
383
  const res = db.prepare('DELETE FROM sessions WHERE id = ?').run(sessionId);
914
384
  return res.changes > 0;
915
385
  }
916
-
917
- // --- Tombstones (KJC-TSK-0380) ---------------------------------------
918
- // "This (resource_type, resource_id) is dead — don't bring it back." The
919
- // sync layer consults this table BEFORE every upsert and skips records
920
- // whose id is buried. Restoration is explicit via removeTombstone().
921
-
922
- export function addTombstone(resourceType, resourceId, opts = {}) {
923
- getDb().prepare(`
924
- INSERT OR REPLACE INTO tombstones (resource_type, resource_id, deleted_at, source, fs_paths)
925
- VALUES (?, ?, ?, ?, ?)
926
- `).run(
927
- resourceType, resourceId,
928
- new Date().toISOString(),
929
- opts.source || 'api',
930
- opts.fsPaths ? JSON.stringify(opts.fsPaths) : null,
931
- );
932
- }
933
-
934
- export function isTombstoned(resourceType, resourceId) {
935
- return !!getDb().prepare(
936
- 'SELECT 1 FROM tombstones WHERE resource_type = ? AND resource_id = ? LIMIT 1'
937
- ).get(resourceType, resourceId);
938
- }
939
-
940
- /**
941
- * Read a single tombstone row (or null). Needed so callers can compare
942
- * `deleted_at` against a resource's own timestamp before deciding whether
943
- * to revive it (KJC-BUG-0055).
944
- */
945
- export function getTombstone(resourceType, resourceId) {
946
- const row = getDb().prepare(
947
- 'SELECT resource_type, resource_id, deleted_at, source, fs_paths FROM tombstones WHERE resource_type = ? AND resource_id = ?'
948
- ).get(resourceType, resourceId);
949
- if (!row) return null;
950
- return { ...row, fs_paths: row.fs_paths ? JSON.parse(row.fs_paths) : [] };
951
- }
952
-
953
- export function removeTombstone(resourceType, resourceId) {
954
- return getDb().prepare(
955
- 'DELETE FROM tombstones WHERE resource_type = ? AND resource_id = ?'
956
- ).run(resourceType, resourceId).changes > 0;
957
- }
958
-
959
- export function listTombstones() {
960
- return getDb().prepare(
961
- 'SELECT resource_type, resource_id, deleted_at, source, fs_paths FROM tombstones ORDER BY deleted_at DESC'
962
- ).all().map((row) => ({
963
- ...row,
964
- fs_paths: row.fs_paths ? JSON.parse(row.fs_paths) : [],
965
- }));
966
- }
967
-
968
- /**
969
- * Closes the database connection.
970
- */
971
- export function closeDb() {
972
- if (db) {
973
- db.close();
974
- db = null;
975
- }
976
- }
@@ -27,7 +27,7 @@
27
27
  *
28
28
  * What "cleanup" means: DELETE the project row + cascade-delete its
29
29
  * stories and sessions, ADD a project tombstone, and rm-rf the
30
- * on-disk dirs (`hu-stories/<sid>/`, `sessions/<sid>/`,
30
+ * on-disk dirs (`sessions/<sid>/`,
31
31
  * `~/.kj/plans/<projectId>/`). Without the tombstone + fs cleanup,
32
32
  * the very next chokidar `add` event or `fullScan` would re-import
33
33
  * those files as a brand-new project (KJC-BUG-0055).
@@ -123,7 +123,7 @@ export function findEphemeralProjects(projects, opts = {}) {
123
123
  * has no effect on the second pass.
124
124
  *
125
125
  * @param {object} deps
126
- * @param {object} deps.db better-sqlite3 Database handle
126
+ * @param {object} deps.db SQLite handle (karajan-core/db adapter)
127
127
  * @param {object} [deps.opts] threshold overrides
128
128
  * @param {Function} [deps.now] () => number, for tests
129
129
  * @returns {Array<{ id: string, reason: string, stories_deleted: number, sessions_deleted: number }>}
@@ -165,7 +165,6 @@ export function cleanupEphemeralProjects({
165
165
  const sessionIds = selSessions.all(project.id).map((r) => r.id);
166
166
  const fsPaths = home
167
167
  ? [
168
- ...storyIds.map((sid) => path.join(home, "hu-stories", sid)),
169
168
  ...sessionIds.map((sid) => path.join(home, "sessions", sid)),
170
169
  path.join(plansRoot, project.id),
171
170
  ]