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
@@ -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;