karajan-code 4.70.4 → 5.0.1

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 (390) 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/register-roles-skills.js +1 -1
  68. package/src/cli/retired-commands.js +49 -0
  69. package/src/cli.js +18 -5
  70. package/src/commands/agent-run.js +40 -26
  71. package/src/commands/audit.js +127 -124
  72. package/src/commands/board.js +5 -9
  73. package/src/commands/check.js +29 -16
  74. package/src/commands/doctor.js +2 -0
  75. package/src/commands/env.js +176 -150
  76. package/src/commands/go.js +71 -53
  77. package/src/commands/harden.js +90 -81
  78. package/src/commands/hu-start.js +52 -0
  79. package/src/commands/hu.js +288 -221
  80. package/src/commands/identity.js +111 -78
  81. package/src/commands/init-atril.js +47 -0
  82. package/src/commands/init.js +11 -40
  83. package/src/commands/install-tools.js +70 -85
  84. package/src/commands/main-guard.js +78 -0
  85. package/src/commands/policy.js +226 -184
  86. package/src/commands/qmd.js +27 -21
  87. package/src/commands/rag.js +121 -55
  88. package/src/commands/review-gate.js +421 -341
  89. package/src/commands/skills.js +4 -1
  90. package/src/commands/start.js +4 -5
  91. package/src/commands/steward.js +96 -47
  92. package/src/commands/tournament.js +46 -37
  93. package/src/commands/worktree.js +107 -26
  94. package/src/config/defaults.js +1 -0
  95. package/src/config/loader.js +34 -17
  96. package/src/config/role-resolver.js +1 -0
  97. package/src/config/schema.js +1 -0
  98. package/src/config/secrets.js +36 -0
  99. package/src/config.js +1 -0
  100. package/src/environment/adr.js +16 -2
  101. package/src/git/karajan-label.js +20 -8
  102. package/src/guards/main-guard.js +54 -0
  103. package/src/harden/hook-templates.js +16 -1
  104. package/src/harden/sentinel/posttooluse.mjs +18 -6
  105. package/src/harden/sentinel/pretooluse-sentinel.mjs +64 -36
  106. package/src/harden/sentinel/sentinel-lib.mjs +129 -26
  107. package/src/harden/sentinel/sentinel-shell.mjs +5 -16
  108. package/src/harden/sentinel/sessionstart.mjs +3 -2
  109. package/src/harden/sentinel/stop.mjs +5 -4
  110. package/src/harden/workflow-templates.js +1 -1
  111. package/src/hu/board-lookup.js +29 -0
  112. package/src/hu/gh-identity.js +23 -0
  113. package/src/hu/github-board.js +4 -2
  114. package/src/hu/lane-prune.js +83 -13
  115. package/src/hu/lane-setup.js +65 -0
  116. package/src/hu/lanes.js +66 -0
  117. package/src/hu/team-board-mirror.js +3 -2
  118. package/src/infrastructure/command-runner.js +1 -1
  119. package/src/infrastructure/file-system-service.js +1 -1
  120. package/src/mcp/handlers/direct-handlers.js +2 -292
  121. package/src/mcp/handlers/hu-handlers.js +1 -35
  122. package/src/mcp/handlers/management-handlers.js +2 -42
  123. package/src/mcp/handlers/rag-handler.js +9 -1
  124. package/src/mcp/server-handlers.js +16 -22
  125. package/src/mcp/tools.js +1 -196
  126. package/src/plan/lane-plans.js +54 -0
  127. package/src/plan/plan-store.js +7 -4
  128. package/src/policy/add.js +26 -12
  129. package/src/policy/adr-number-check.js +31 -0
  130. package/src/prompts/audit.js +299 -339
  131. package/src/prompts/card-context.js +10 -5
  132. package/src/prompts/reviewer.js +5 -2
  133. package/src/rag/auto-update.js +71 -44
  134. package/src/rag/bootstrap-index.js +46 -54
  135. package/src/rag/indexer.js +9 -4
  136. package/src/rag/migrate.js +1 -1
  137. package/src/rag/onnx-fallback.js +2 -2
  138. package/src/rag/pid-lease.js +181 -0
  139. package/src/rag/project-store.js +2 -12
  140. package/src/review/card-first.js +57 -46
  141. package/src/review/gate-gitignore.js +23 -4
  142. package/src/review/gate-hook.js +12 -4
  143. package/src/review/one-shot-review.js +45 -35
  144. package/src/review/publish-pr.js +14 -2
  145. package/src/review/repo-docs.js +73 -0
  146. package/src/review/repo-stack.js +59 -0
  147. package/src/review/reviewer-fallback.js +19 -4
  148. package/src/review/seal-commit.js +29 -0
  149. package/src/review/sonar-pregate.js +59 -44
  150. package/src/review/third-party-review.js +90 -16
  151. package/src/review/third-party-size.js +43 -0
  152. package/src/review/third-party-skills.js +54 -0
  153. package/src/review/verify-pr.js +1 -24
  154. package/src/roles/audit-role.js +106 -125
  155. package/src/skills/atril-cli.js +19 -1
  156. package/src/skills/atril-skills.js +5 -4
  157. package/src/skills/skills-search.js +72 -0
  158. package/src/skills/translate.js +62 -0
  159. package/src/sonar/scanner.js +116 -101
  160. package/src/steward/invariants.js +5 -4
  161. package/src/steward/phantom-sweep.js +66 -0
  162. package/src/tournament/judge.js +60 -53
  163. package/src/utils/gh-account.js +37 -0
  164. package/src/utils/git.js +0 -13
  165. package/src/utils/home-migration.js +7 -1
  166. package/src/utils/main-checkout.js +53 -0
  167. package/src/utils/qmd-collection.js +3 -1
  168. package/src/utils/welcome.js +3 -3
  169. package/templates/{skills → atril/commands}/kj-architect.md +5 -0
  170. package/templates/{skills → atril/commands}/kj-audit.md +5 -0
  171. package/templates/{skills → atril/commands}/kj-board.md +5 -0
  172. package/templates/{skills → atril/commands}/kj-code.md +5 -0
  173. package/templates/{skills → atril/commands}/kj-csv-transform.md +5 -0
  174. package/templates/{skills → atril/commands}/kj-data-report.md +5 -0
  175. package/templates/{skills → atril/commands}/kj-discover.md +5 -0
  176. package/templates/{skills → atril/commands}/kj-rag-query.md +5 -0
  177. package/templates/{skills → atril/commands}/kj-review.md +5 -0
  178. package/templates/{skills → atril/commands}/kj-security.md +5 -0
  179. package/templates/{skills → atril/commands}/kj-sonar.md +5 -0
  180. package/templates/{skills → atril/commands}/kj-sql-analysis.md +5 -0
  181. package/templates/{skills → atril/commands}/kj-test.md +5 -0
  182. package/packages/hu-board/public/utils/run-launcher.js +0 -169
  183. package/packages/hu-board/src/run-tracker.js +0 -121
  184. package/src/activity-log.js +0 -59
  185. package/src/agents/host-agent.js +0 -38
  186. package/src/agents/model-registry.js +0 -142
  187. package/src/audit/plan-adherence.js +0 -224
  188. package/src/autonomy/arbiter-role.js +0 -71
  189. package/src/autonomy/arbiter.js +0 -22
  190. package/src/autonomy/decider.js +0 -18
  191. package/src/autonomy/decision-resolver.js +0 -58
  192. package/src/autonomy/outcome-report.js +0 -55
  193. package/src/autonomy/stage-ask.js +0 -30
  194. package/src/brain/decision-tracker.js +0 -125
  195. package/src/brain/decisor.js +0 -243
  196. package/src/brain/solomon-consult.js +0 -171
  197. package/src/brain/standby-scheduler.js +0 -8
  198. package/src/budget/comparison.js +0 -96
  199. package/src/canvas/parse-md.js +0 -238
  200. package/src/canvas/schema.js +0 -101
  201. package/src/canvas/to-flat.js +0 -85
  202. package/src/canvas/validate.js +0 -163
  203. package/src/ci/dispatch.js +0 -99
  204. package/src/ci/index.js +0 -3
  205. package/src/ci/pr-diff.js +0 -26
  206. package/src/cli/register-standby.js +0 -26
  207. package/src/commands/architect.js +0 -111
  208. package/src/commands/autorun.js +0 -49
  209. package/src/commands/researcher.js +0 -54
  210. package/src/commands/resume.js +0 -72
  211. package/src/commands/review.js +0 -89
  212. package/src/commands/run.js +0 -185
  213. package/src/commands/standby.js +0 -117
  214. package/src/commands/status.js +0 -36
  215. package/src/commands/triage.js +0 -78
  216. package/src/commands/undo.js +0 -62
  217. package/src/domains/domain-loader.js +0 -245
  218. package/src/domains/domain-registry.js +0 -140
  219. package/src/domains/domain-synthesizer.js +0 -129
  220. package/src/git/automation.js +0 -433
  221. package/src/git/coder-commit-guard.js +0 -40
  222. package/src/git/hu-automation.js +0 -243
  223. package/src/git/hu-snapshot.js +0 -10
  224. package/src/git/symlink-targets.js +0 -57
  225. package/src/golden/asserter.js +0 -92
  226. package/src/golden/loader.js +0 -52
  227. package/src/golden/runner.js +0 -92
  228. package/src/golden/schema.js +0 -41
  229. package/src/guards/duplicate-members.js +0 -86
  230. package/src/guards/intent-guard.js +0 -161
  231. package/src/guards/perf-guard.js +0 -126
  232. package/src/guards/policy-guard.js +0 -90
  233. package/src/guards/policy-resolver.js +0 -85
  234. package/src/guards/secret-redactor.js +0 -71
  235. package/src/hu/acceptance-runner.js +0 -138
  236. package/src/hu/lazy-planner.js +0 -109
  237. package/src/hu/parallel-executor.js +0 -94
  238. package/src/hu/routing-metrics.js +0 -17
  239. package/src/hu/split-validator.js +0 -143
  240. package/src/hu/splitting-detector.js +0 -158
  241. package/src/hu/splitting-generator.js +0 -175
  242. package/src/hu/store.js +0 -394
  243. package/src/infrastructure/mocks.js +0 -182
  244. package/src/mcp/handlers/run-handler.js +0 -318
  245. package/src/mcp/sovereignty-guard.js +0 -113
  246. package/src/mcp/tool-arg-normalizers.js +0 -16
  247. package/src/mutate/reviewer-signal.js +0 -76
  248. package/src/orchestrator/agent-fallback.js +0 -81
  249. package/src/orchestrator/brain-coordinator.js +0 -186
  250. package/src/orchestrator/ci-integration.js +0 -139
  251. package/src/orchestrator/config-init.js +0 -426
  252. package/src/orchestrator/direct-actions.js +0 -217
  253. package/src/orchestrator/drivers/error-recovery.js +0 -287
  254. package/src/orchestrator/drivers/init-context.js +0 -238
  255. package/src/orchestrator/drivers/iteration-loop.js +0 -382
  256. package/src/orchestrator/drivers/iteration-phases/coder-and-refactorer.js +0 -69
  257. package/src/orchestrator/drivers/iteration-phases/guards.js +0 -91
  258. package/src/orchestrator/drivers/iteration-phases/handle-approved.js +0 -29
  259. package/src/orchestrator/drivers/iteration-phases/quality-gates.js +0 -125
  260. package/src/orchestrator/drivers/iteration-phases/reviewer-gate.js +0 -62
  261. package/src/orchestrator/drivers/post-loop.js +0 -404
  262. package/src/orchestrator/drivers/pre-loop-phases/auto-hu-batch.js +0 -137
  263. package/src/orchestrator/drivers/pre-loop-phases/config-deprecations.js +0 -47
  264. package/src/orchestrator/drivers/pre-loop-phases/ensure-addyosmani-skills.js +0 -84
  265. package/src/orchestrator/drivers/pre-loop-phases/inject-loaded-plan.js +0 -393
  266. package/src/orchestrator/drivers/pre-loop.js +0 -524
  267. package/src/orchestrator/drivers/run-hu-batch.js +0 -468
  268. package/src/orchestrator/feedback-enrichment.js +0 -140
  269. package/src/orchestrator/feedback-queue.js +0 -151
  270. package/src/orchestrator/flow-control.js +0 -170
  271. package/src/orchestrator/flow-runner.js +0 -365
  272. package/src/orchestrator/fs-leak-detector.js +0 -228
  273. package/src/orchestrator/hu-scheduler.js +0 -114
  274. package/src/orchestrator/hu-sub-pipeline.js +0 -642
  275. package/src/orchestrator/iteration-gate.js +0 -73
  276. package/src/orchestrator/iteration-stages.js +0 -9
  277. package/src/orchestrator/parallel-limiter.js +0 -74
  278. package/src/orchestrator/pipeline-context.js +0 -72
  279. package/src/orchestrator/post-loop-stages.js +0 -468
  280. package/src/orchestrator/pre-loop-stages.js +0 -11
  281. package/src/orchestrator/preflight-checks.js +0 -487
  282. package/src/orchestrator/repair/repair-detector.js +0 -117
  283. package/src/orchestrator/repair/repair-runner.js +0 -142
  284. package/src/orchestrator/reviewer-fallback.js +0 -45
  285. package/src/orchestrator/role-output-compressor.js +0 -189
  286. package/src/orchestrator/session-journal.js +0 -308
  287. package/src/orchestrator/solomon-escalation.js +0 -331
  288. package/src/orchestrator/solomon-rules.js +0 -212
  289. package/src/orchestrator/stages/acceptance-coverage-stage.js +0 -47
  290. package/src/orchestrator/stages/architect-stage.js +0 -172
  291. package/src/orchestrator/stages/coder-stage.js +0 -506
  292. package/src/orchestrator/stages/domain-curator-stage.js +0 -82
  293. package/src/orchestrator/stages/format-stage.js +0 -61
  294. package/src/orchestrator/stages/hu-reviewer-phases/detect-and-apply-splits.js +0 -127
  295. package/src/orchestrator/stages/hu-reviewer-stage.js +0 -419
  296. package/src/orchestrator/stages/perf-stage.js +0 -140
  297. package/src/orchestrator/stages/planner-stage.js +0 -85
  298. package/src/orchestrator/stages/rag-context-stage.js +0 -80
  299. package/src/orchestrator/stages/rag-preload-decisor.js +0 -58
  300. package/src/orchestrator/stages/research-stage.js +0 -69
  301. package/src/orchestrator/stages/reviewer-stage.js +0 -431
  302. package/src/orchestrator/stages/sonar-stage.js +0 -439
  303. package/src/orchestrator/stages/stage-classes.js +0 -84
  304. package/src/orchestrator/stages/stage-executor.js +0 -155
  305. package/src/orchestrator/stages/suite-green-stage.js +0 -62
  306. package/src/orchestrator/stages/tdd-discipline-stage.js +0 -56
  307. package/src/orchestrator/stages/tool-judge-stage.js +0 -53
  308. package/src/orchestrator/stages/triage-stage.js +0 -172
  309. package/src/orchestrator/standby.js +0 -71
  310. package/src/orchestrator/verification-gate.js +0 -183
  311. package/src/orchestrator.js +0 -22
  312. package/src/plan/adr-generator.js +0 -170
  313. package/src/plan/adr-loader.js +0 -60
  314. package/src/plan/plan-executor.js +0 -100
  315. package/src/plan/plan-validation.js +0 -4
  316. package/src/prompts/acceptance-coverage.js +0 -32
  317. package/src/prompts/arbiter.js +0 -38
  318. package/src/prompts/architect.js +0 -103
  319. package/src/prompts/hu-reviewer.js +0 -244
  320. package/src/prompts/tool-correctness-judge.js +0 -31
  321. package/src/prompts/triage.js +0 -57
  322. package/src/rag/embedder.js +0 -2
  323. package/src/rag/embedders/_cloud-base.js +0 -2
  324. package/src/rag/embedders/cohere.js +0 -2
  325. package/src/rag/embedders/mistral.js +0 -2
  326. package/src/rag/embedders/onnx.js +0 -2
  327. package/src/rag/embedders/openai.js +0 -2
  328. package/src/rag/embedders/voyage.js +0 -2
  329. package/src/rag/rerank.js +0 -4
  330. package/src/rag/where-parser.js +0 -4
  331. package/src/repeat-detector.js +0 -88
  332. package/src/review/diff-generator.js +0 -112
  333. package/src/review/git-stash-helper.js +0 -104
  334. package/src/review/schema.js +0 -31
  335. package/src/review/scope-filter.js +0 -152
  336. package/src/review/snapshot-diff.js +0 -122
  337. package/src/review/tdd-discipline.js +0 -90
  338. package/src/review/tdd-policy.js +0 -69
  339. package/src/roles/acceptance-coverage-role.js +0 -78
  340. package/src/roles/architect-role.js +0 -77
  341. package/src/roles/coder-role.js +0 -113
  342. package/src/roles/commiter-role.js +0 -113
  343. package/src/roles/domain-curator-role.js +0 -134
  344. package/src/roles/hu-reviewer-role.js +0 -64
  345. package/src/roles/impeccable-role.js +0 -85
  346. package/src/roles/index.js +0 -18
  347. package/src/roles/karajan-brain-role.js +0 -134
  348. package/src/roles/perf-role.js +0 -122
  349. package/src/roles/planner-role.js +0 -151
  350. package/src/roles/refactorer-role.js +0 -30
  351. package/src/roles/repairer-role.js +0 -181
  352. package/src/roles/researcher-role.js +0 -70
  353. package/src/roles/reviewer-role.js +0 -115
  354. package/src/roles/security-role.js +0 -73
  355. package/src/roles/solomon-role.js +0 -271
  356. package/src/roles/sonar-role.js +0 -72
  357. package/src/roles/tester-role.js +0 -235
  358. package/src/roles/tool-correctness-judge-role.js +0 -78
  359. package/src/roles/triage-role.js +0 -99
  360. package/src/session/journal/decisions-writer.js +0 -200
  361. package/src/session/journal/iteration-logger.js +0 -194
  362. package/src/session/journal/summary-writer.js +0 -320
  363. package/src/session/journal/tree-writer.js +0 -164
  364. package/src/session/journal/write-all.js +0 -89
  365. package/src/session-cleanup.js +0 -116
  366. package/src/skills/semantic-detector.js +0 -167
  367. package/src/skills/skill-detector.js +0 -385
  368. package/src/sonar/cloud-scanner.js +0 -78
  369. package/src/tracker-bootstrap.js +0 -44
  370. package/src/types/index.js +0 -38
  371. package/src/types/policy.js +0 -43
  372. package/src/types/solomon.js +0 -87
  373. package/src/utils/async-lock.js +0 -31
  374. package/src/utils/budget.js +0 -229
  375. package/src/utils/cwd-confirm.js +0 -39
  376. package/src/utils/display/event-handlers.js +0 -403
  377. package/src/utils/display/formatters.js +0 -132
  378. package/src/utils/display/header.js +0 -47
  379. package/src/utils/display/session-summary.js +0 -118
  380. package/src/utils/display/solomon.js +0 -42
  381. package/src/utils/pricing.js +0 -29
  382. package/src/utils/quota-simulator.js +0 -56
  383. package/src/utils/role-assigner.js +0 -137
  384. package/src/utils/rtk-wrapper.js +0 -87
  385. package/src/utils/run-registry.js +0 -9
  386. package/src/utils/status-dashboard.js +0 -254
  387. package/src/utils/tool-call-extractor.js +0 -56
  388. package/src/webperf/devtools-detect.js +0 -112
  389. package/src/webperf/webperf-role-integration.js +0 -66
  390. package/templates/skills/kj-run.md +0 -73
@@ -73,10 +73,14 @@ export function reapZombieHus({ db, setHuStatus, setHuFailResult, opts = {}, now
73
73
  const localHuId = hu.id.includes("::") ? hu.id.split("::").slice(1).join("::") : hu.id;
74
74
  let planPersisted = false;
75
75
  if (hu.plan_id) {
76
+ let result = null;
76
77
  try {
77
- const result = setHuStatus({ planId: hu.plan_id, huId: localHuId, status: "pending", projectId: hu.project_id });
78
+ result = setHuStatus({ planId: hu.plan_id, huId: localHuId, status: "pending", projectId: hu.project_id });
78
79
  planPersisted = !!(result && result.ok);
79
80
  } catch { /* swallow — never block startup */ }
81
+ // KJC-BUG-0405: a read-only plan mirrors the shared board; its running HU is a
82
+ // teammate's live work, not a zombie. Neither the plan nor the DB is touched.
83
+ if (result?.readonly) continue;
80
84
  // KJC-TSK-0404: además de resetear status=pending, marcar la HU con
81
85
  // result=fail + blocker. El siguiente `kj run` ve el blocker y se
82
86
  // lo inyecta al coder como "## PREVIOUS ATTEMPT FAILED" para que
@@ -14,26 +14,14 @@
14
14
  * Steps 2-4 must share a single on-disk write: we never ack the mutation to
15
15
  * the UI without persisting it, otherwise a reload would silently revert.
16
16
  */
17
- import { readFileSync, existsSync, readdirSync, mkdirSync, openSync } from 'node:fs';
18
- import { join, dirname } from 'node:path';
17
+ import { readFileSync, existsSync, readdirSync } from 'node:fs';
18
+ import { join } from 'node:path';
19
19
  import { writeJsonAtomicSync } from 'karajan-core/atomic-write';
20
- import { spawn } from 'node:child_process';
21
- import { trackRun, untrack } from './run-tracker.js';
22
- import { getHuBoardRunsDir } from './db.js';
23
- import { fileURLToPath } from 'node:url';
24
20
  import { updateHuStatus, certifyAllHus, updateHu } from 'karajan-core/plan-hu-ops';
25
21
  import { validateBlockedByChange } from 'karajan-core/plan-validation';
26
22
  import { syncPlanFile } from './sync.js';
27
23
  import { getDb } from './db.js';
28
24
 
29
- // Repo root → so we can spawn `node <repo>/src/cli.js run ...` without
30
- // depending on `kj` being in $PATH of the board process.
31
- const REPO_ROOT = (() => {
32
- const here = dirname(fileURLToPath(import.meta.url)); // packages/hu-board/src
33
- return join(here, '..', '..', '..'); // → repo root
34
- })();
35
- const KJ_CLI = join(REPO_ROOT, 'src', 'cli.js');
36
-
37
25
  /**
38
26
  * Root directory where `kj plan` writes `<slug>/<planId>.json`. Honours
39
27
  * `KJ_PLANS_DIR` (tests) then `KJ_HOME/plans` (power users) then
@@ -90,7 +78,7 @@ function readPlan(filePath) {
90
78
  // in the issues; the board shows it and refuses to edit it, so the two never diverge.
91
79
  function mirrorGuard(plan) {
92
80
  if (!plan?.readonly) return null;
93
- return { ok: false, error: `this plan mirrors the shared board (${plan.readonly}) and is read-only here: change it with kj hu move` };
81
+ return { ok: false, readonly: true, error: `this plan mirrors the shared board (${plan.readonly}) and is read-only here: change it with kj hu move` };
94
82
  }
95
83
 
96
84
  function writePlan(filePath, plan) {
@@ -218,45 +206,6 @@ export function setHuFailResult({ planId, huId, blocker, projectId }) {
218
206
  return { ok: true, hu };
219
207
  }
220
208
 
221
- /**
222
- * KJC-TSK-0408 step 2/3: deshace los cambios de una HU restaurando
223
- * el snapshot git tomado pre-run. Marca status=pending, result=null
224
- * y outcome.reverted=true para que la UI muestre "deshecha".
225
- *
226
- * @param {object} args
227
- * @param {string} args.planId
228
- * @param {string} args.huId
229
- * @param {string} [args.projectId]
230
- * @returns {Promise<{ ok: boolean, sha?: string, error?: string }>}
231
- */
232
- export async function revertHuFromSnapshot({ planId, huId, projectId }) {
233
- const filePath = findPlanFilePath(planId, projectId);
234
- if (!filePath) return { ok: false, error: `plan not found: ${planId}` };
235
- const plan = readPlan(filePath);
236
- const mirrored = mirrorGuard(plan);
237
- if (mirrored) return mirrored;
238
- const hu = plan.hus?.find(h => h.id === huId);
239
- if (!hu) return { ok: false, error: `hu not found: ${huId}` };
240
- const snapshotSha = hu.outcome?.snapshot_sha;
241
- if (!snapshotSha) {
242
- return { ok: false, error: 'esta HU no tiene snapshot — solo HUs ejecutadas con KJC-TSK-0408 pueden deshacerse' };
243
- }
244
- const projectDir = plan.projectDir;
245
- if (!projectDir) return { ok: false, error: 'plan sin projectDir' };
246
-
247
- const { restoreHuSnapshot } = await import('karajan-core/hu-snapshot');
248
- const restore = await restoreHuSnapshot({ projectDir, huId });
249
- if (!restore.ok) return { ok: false, error: `restore falló: ${restore.error}` };
250
-
251
- hu.status = 'pending';
252
- hu.result = null;
253
- hu.outcome = { ...(hu.outcome || {}), reverted: true, revertedAt: new Date().toISOString() };
254
- hu.updatedAt = new Date().toISOString();
255
- writePlan(filePath, plan);
256
- syncPlanFile(filePath);
257
- return { ok: true, sha: restore.sha };
258
- }
259
-
260
209
  /**
261
210
  * Bulk certify: equivalent to `kj plan ready <planId>` over HTTP. Flips
262
211
  * every pending HU to certified and sets `plan.status = "ready"`. No-op
@@ -344,102 +293,3 @@ export function markPlanReady({ planId, projectId }) {
344
293
  syncPlanFile(filePath);
345
294
  return { ok: true, count, planStatus: plan.status };
346
295
  }
347
-
348
- /**
349
- * Launch `kj run --plan <planId>` as a detached child. This is the
350
- * "Run plan" button's server-side handler: the board no longer asks the
351
- * user to drop to the terminal to kick off execution.
352
- *
353
- * We spawn against the repo's own CLI (`node <repo>/src/cli.js`) instead
354
- * of a `kj` binary on $PATH — the board is always shipped with the repo
355
- * that hosts it, so that entry point is guaranteed to exist. Tests set
356
- * `KJ_RUN_SPAWN_MODE=echo` to replace the spawn with a no-op that just
357
- * returns a fake pid, so we can assert payload shape without booting the
358
- * whole orchestrator inside vitest.
359
- *
360
- * Output is redirected to `~/.karajan/hu-board-runs/<planId>.log` so the
361
- * board can tail it later if we wire up a live log viewer — for now it's
362
- * just a durable trail. stdin is `ignore` (coder subprocess requirement
363
- * documented in CLAUDE.md).
364
- *
365
- * @param {object} args
366
- * @param {string} args.planId
367
- * @param {string} [args.projectId]
368
- * @param {string} [args.taskOverride] - optional custom task string
369
- * @returns {{ ok: true, pid: number, logPath: string } | { ok: false, error: string }}
370
- */
371
- export function runPlan({ planId, projectId, taskOverride, huIds = null } = {}) {
372
- const filePath = findPlanFilePath(planId, projectId);
373
- if (!filePath) return { ok: false, error: `plan not found: ${planId}` };
374
-
375
- const plan = readPlan(filePath);
376
- const mirrored = mirrorGuard(plan);
377
- if (mirrored) return mirrored;
378
- if (!Array.isArray(plan.hus) || plan.hus.length === 0) {
379
- return { ok: false, error: 'plan has no hus[]' };
380
- }
381
- const projectDir = plan.projectDir;
382
- if (!projectDir) {
383
- return {
384
- ok: false,
385
- error:
386
- 'plan has no projectDir stamped — re-run `kj plan` to regenerate it, '
387
- + 'then try again (plans pre-v2.7.4 did not persist the dir).',
388
- };
389
- }
390
-
391
- // PR4: when huIds is set, validate they exist before spawning so
392
- // we surface "unknown HU" as an HTTP 400 instead of letting the
393
- // CLI fail mid-startup with a less obvious message.
394
- let huFlag = null;
395
- if (Array.isArray(huIds) && huIds.length > 0) {
396
- const validIds = new Set(plan.hus.map((h) => h.id));
397
- const unknown = huIds.filter((id) => !validIds.has(id));
398
- if (unknown.length > 0) {
399
- return { ok: false, error: `unknown HU id(s) in plan ${planId}: ${unknown.join(', ')}` };
400
- }
401
- huFlag = huIds.join(',');
402
- }
403
-
404
- const runsDir = getHuBoardRunsDir(); // KJC-TSK-0421 — centralised in db.js
405
- mkdirSync(runsDir, { recursive: true });
406
- // PR4: when running a single HU, suffix the log with the HU id so
407
- // a subsequent full-plan run doesn't overwrite the per-HU log.
408
- const logName = huFlag
409
- ? `${planId}--hu-${huIds.join('-').replace(/[^a-zA-Z0-9_-]/g, '_').slice(0, 60)}.log`
410
- : `${planId}.log`;
411
- const logPath = join(runsDir, logName);
412
-
413
- const task = taskOverride || plan.task || `run plan ${planId}`;
414
- const args = [KJ_CLI, 'run', '--plan', planId];
415
- if (huFlag) args.push('--hu', huFlag);
416
- args.push(task);
417
-
418
- // Escape hatch for tests — hard-kill the spawn so vitest doesn't boot
419
- // the full orchestrator. The shape of the response is what we assert.
420
- if (process.env.KJ_RUN_SPAWN_MODE === 'echo') {
421
- return { ok: true, pid: 0, logPath, argv: [process.execPath, ...args], projectDir, huIds: huIds || null };
422
- }
423
-
424
- const out = openSync(logPath, 'a');
425
- const err = openSync(logPath, 'a');
426
- const child = spawn(process.execPath, args, {
427
- cwd: projectDir,
428
- detached: true,
429
- stdio: ['ignore', out, err],
430
- // Strip CLAUDECODE so the Claude agent subprocess trick in
431
- // claude-agent.js doesn't misbehave (documented in CLAUDE.md).
432
- env: { ...process.env, CLAUDECODE: undefined },
433
- });
434
- child.unref();
435
- // KJC-TSK-0396: registrar el PID para que el botón ⏹ Stop del board
436
- // sepa qué procesos matar. trackRun usa Set, así que llamadas
437
- // duplicadas no inflan el registro. Cuando el proceso muera, el
438
- // event 'exit' lo des-registra (defensa adicional: reaper periódico
439
- // que limpia PIDs muertos cada 30s).
440
- if (child.pid) {
441
- trackRun(planId, { pid: child.pid, huIds: huIds || null });
442
- child.on('exit', () => { untrack(planId, child.pid); });
443
- }
444
- return { ok: true, pid: child.pid ?? 0, logPath };
445
- }
@@ -0,0 +1,55 @@
1
+ // KJC-TSK-0541: reading the plan JSONs, shared by the API routers and testable without Express.
2
+ // Every scan covers the canonical and the legacy plans roots (KJC-BUG-0059).
3
+ import fsp from 'node:fs/promises';
4
+ import path from 'node:path';
5
+
6
+ import { getHuBoardPlansDirs } from './db.js';
7
+
8
+ /** Async existence probe (KJC-TSK-0539: no sync fs in handlers). */
9
+ export const pathExists = (p) => fsp.access(p).then(() => true, () => false);
10
+
11
+ /**
12
+ * mtime-keyed cache for parsed plan JSON (KJC-TSK-0539). The plan scans
13
+ * used to re-read and re-parse every plan file on EVERY request; with N
14
+ * plans and the board polling, that was O(N) parses per poll. The cache
15
+ * serves the parsed object while the file's mtime is unchanged and
16
+ * re-reads transparently when kj writes the plan.
17
+ */
18
+ const planFileCache = new Map();
19
+ export async function readPlanCached(p) {
20
+ const { mtimeMs } = await fsp.stat(p);
21
+ const hit = planFileCache.get(p);
22
+ if (hit && hit.mtimeMs === mtimeMs) return hit.plan;
23
+ const plan = JSON.parse(await fsp.readFile(p, 'utf8'));
24
+ planFileCache.set(p, { mtimeMs, plan });
25
+ return plan;
26
+ }
27
+
28
+ /** Every readable plan JSON of a project; an unreadable file is skipped. */
29
+ export async function* projectPlans(projectId) {
30
+ for (const root of getHuBoardPlansDirs()) {
31
+ const plansDir = path.join(root, projectId);
32
+ if (!(await pathExists(plansDir))) continue;
33
+ for (const f of (await fsp.readdir(plansDir)).filter((n) => n.endsWith('.json'))) {
34
+ let plan;
35
+ try {
36
+ plan = await readPlanCached(path.join(plansDir, f));
37
+ } catch {
38
+ continue;
39
+ }
40
+ yield plan;
41
+ }
42
+ }
43
+ }
44
+
45
+ /** The path of `plan-<planId>.json` under any project of any plans root, or null. */
46
+ export async function findPlanFile(planId) {
47
+ for (const plansRoot of getHuBoardPlansDirs()) {
48
+ if (!(await pathExists(plansRoot))) continue;
49
+ for (const projSlug of await fsp.readdir(plansRoot)) {
50
+ const candidate = path.join(plansRoot, projSlug, `plan-${planId}.json`);
51
+ if (await pathExists(candidate)) return candidate;
52
+ }
53
+ }
54
+ return null;
55
+ }
@@ -0,0 +1,122 @@
1
+ // KJC-TSK-0541: the kj commands the board launches, their run logs and the retired `kj run`
2
+ // routes, composed by routes/api.js.
3
+ import { Router } from 'express';
4
+ import fsp from 'node:fs/promises';
5
+ import path from 'node:path';
6
+
7
+ import { getHuBoardRunsDir } from '../../db.js';
8
+ import { runKjCommand, listSupportedCommands, startCardInLane } from '../../command-runner.js';
9
+ import { pathExists } from '../../plans-service.js';
10
+ import { requestProjectDir } from '../../project-dir.js';
11
+
12
+ const router = Router();
13
+
14
+ /**
15
+ * KJC-TSK-1319: the board launched `kj run` for a plan, an HU or a whole
16
+ * project. kj run is retired in 5.0 (your agent does the work, kj governs
17
+ * it), so these routes say so and launch nothing. The board never imports
18
+ * the CLI tree (KJC-TSK-0632): the notice lives here.
19
+ */
20
+ const RUN_RETIRED = {
21
+ error: 'kj run was retired in 5.0. Your agent (Claude Code, OpenCode...) does the work now and kj governs it: split the task with kj brief planner + kj hu add, one lane per card with kj worktree start, and kj review --staged before every commit.',
22
+ guide: 'https://github.com/manufosela/karajan-code/blob/main/MIGRATION-v5.md',
23
+ };
24
+ router.post(['/plans/:planId/run', '/hus/:huId/run', '/projects/:id/run'], (_req, res) => {
25
+ res.status(410).json(RUN_RETIRED);
26
+ });
27
+
28
+ /**
29
+ * GET /api/commands - List the kj commands the board can launch.
30
+ * The UI uses this to render the Actions panel without hardcoding
31
+ * the command list on the client.
32
+ */
33
+ router.get('/commands', (_req, res) => {
34
+ res.json({ commands: listSupportedCommands() });
35
+ });
36
+
37
+ /**
38
+ * POST /api/commands/:command - Spawn a kj <command> as a detached
39
+ * child. Body fields depend on the command (see command-runner.js
40
+ * for the per-command input schema). Returns a commandId the client
41
+ * can pass to GET /api/runs/:commandId/log to tail the output.
42
+ *
43
+ * The board never executes free-form arg arrays — the command name
44
+ * must be in the whitelist and each command builds its argv from a
45
+ * typed schema. Anything outside that fails 400.
46
+ */
47
+ router.post('/commands/:command', (req, res) => {
48
+ try {
49
+ const result = runKjCommand({ command: req.params.command, input: req.body || {} });
50
+ if (!result.ok) return res.status(400).json({ error: result.error });
51
+ res.json({
52
+ ok: true,
53
+ commandId: result.commandId,
54
+ pid: result.pid,
55
+ logPath: result.logPath,
56
+ });
57
+ } catch (err) {
58
+ res.status(500).json({ error: err.message });
59
+ }
60
+ });
61
+
62
+ /**
63
+ * POST /api/hu/:id/start - KJC-TSK-1324: the ▶ of a card. The body names the board project;
64
+ * its directory is resolved here, never taken from the client (ADR 0011). A card that already
65
+ * has a lane gets 409 with that lane and nothing is launched; otherwise `kj hu start <id>
66
+ * --code` runs detached and the response carries the commandId to tail.
67
+ */
68
+ router.post('/hu/:id/start', (req, res) => {
69
+ let projectDir;
70
+ try {
71
+ projectDir = requestProjectDir(req.body?.project);
72
+ } catch (err) {
73
+ return res.status(400).json({ error: err.message });
74
+ }
75
+ if (!projectDir) return res.status(400).json({ error: 'name the board project the card belongs to' });
76
+ try {
77
+ const result = startCardInLane({ id: req.params.id, projectDir });
78
+ if (result.lane) return res.status(409).json({ error: result.error, lane: result.lane });
79
+ if (!result.ok) return res.status(400).json({ error: result.error });
80
+ return res.json({ ok: true, commandId: result.commandId, pid: result.pid, logPath: result.logPath });
81
+ } catch (err) {
82
+ return res.status(500).json({ error: err.message });
83
+ }
84
+ });
85
+
86
+ /**
87
+ * GET /api/runs/:commandId/log - Generalisation of the per-plan log
88
+ * tail. The original `/api/plans/:planId/log` reads
89
+ * `<runsDir>/<planId>.log`; this one reads `<runsDir>/<commandId>.log`
90
+ * where `commandId` is what `/api/commands/:command` returned.
91
+ *
92
+ * Same offset semantics + 1 MiB cap as the plans endpoint.
93
+ */
94
+ router.get('/runs/:commandId/log', async (req, res) => {
95
+ try {
96
+ const runsDir = getHuBoardRunsDir(); // KJC-TSK-0421
97
+ const logPath = path.join(runsDir, `${req.params.commandId}.log`);
98
+ if (!(await pathExists(logPath))) {
99
+ return res.json({ exists: false, size: 0, content: '' });
100
+ }
101
+ const stat = await fsp.stat(logPath);
102
+ const offset = Math.max(0, Math.min(stat.size, Number(req.query.offset) || 0));
103
+ const MAX_READ = 1024 * 1024;
104
+ const length = Math.min(MAX_READ, stat.size - offset);
105
+ let content = '';
106
+ if (length > 0) {
107
+ const fh = await fsp.open(logPath, 'r');
108
+ try {
109
+ const buf = Buffer.alloc(length);
110
+ await fh.read(buf, 0, length, offset);
111
+ content = buf.toString('utf-8');
112
+ } finally {
113
+ await fh.close();
114
+ }
115
+ }
116
+ res.json({ exists: true, size: stat.size, content });
117
+ } catch (err) {
118
+ res.status(500).json({ error: err.message });
119
+ }
120
+ });
121
+
122
+ export default router;
@@ -0,0 +1,55 @@
1
+ // KJC-TSK-0541: the plan endpoints of /api, composed by routes/api.js.
2
+ import { Router } from 'express';
3
+ import fsp from 'node:fs/promises';
4
+
5
+ import { addTombstone } from '../../db.js';
6
+ import { markPlanReady } from '../../plan-mutations.js';
7
+ import { findPlanFile } from '../../plans-service.js';
8
+
9
+ const router = Router();
10
+
11
+ /**
12
+ * DELETE /api/plans/:planId - Delete a single plan file.
13
+ *
14
+ * Removes `~/.kj/plans/<projectSlug>/plan-<planId>.json` and tombstones
15
+ * the plan id so the next watcher event doesn't re-sync it. Does NOT
16
+ * delete the project: a project can have multiple plans, deleting one
17
+ * plan only removes that plan's HUs from the board.
18
+ */
19
+ router.delete('/plans/:planId', async (req, res) => {
20
+ try {
21
+ const planId = req.params.planId;
22
+ // Locate the plan file by scanning both canonical and legacy plans
23
+ // roots — KJC-BUG-0059 (v2.19.3). Pre-fix this hard-coded
24
+ // `getKjHome()/../.kj/plans` which silently misses plans saved by
25
+ // post-2.19.0 callers under `~/.karajan/plans/`.
26
+ let foundPath = null;
27
+ try {
28
+ foundPath = await findPlanFile(planId);
29
+ } catch { /* plansRoot missing */ }
30
+ addTombstone('plan', planId, { source: 'api', fsPaths: foundPath ? [foundPath] : [] });
31
+ if (foundPath) { try { await fsp.unlink(foundPath); } catch { /* */ } }
32
+ res.json({ deleted: true, fileRemoved: !!foundPath });
33
+ } catch (err) {
34
+ res.status(500).json({ error: err.message });
35
+ }
36
+ });
37
+
38
+ /**
39
+ * POST /api/plans/:planId/ready - Bulk-certify every pending HU of a plan.
40
+ * Equivalent to the CLI `kj plan ready <planId>`. Body is optional — if the
41
+ * client knows the projectId we use it as a fast path, otherwise we fall
42
+ * back to scanning the plans dir.
43
+ */
44
+ router.post('/plans/:planId/ready', (req, res) => {
45
+ try {
46
+ const { projectId } = req.body || {};
47
+ const result = markPlanReady({ planId: req.params.planId, projectId });
48
+ if (!result.ok) return res.status(404).json({ error: result.error });
49
+ res.json({ ready: true, planId: req.params.planId, count: result.count, planStatus: result.planStatus });
50
+ } catch (err) {
51
+ res.status(500).json({ error: err.message });
52
+ }
53
+ });
54
+
55
+ export default router;