karajan-code 4.70.3 → 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 (388) 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/ai-surface.js +9 -7
  61. package/src/checks/config-files.js +8 -7
  62. package/src/checks/dir-setup.js +34 -1
  63. package/src/checks/native-build.js +21 -39
  64. package/src/checks/runner.js +128 -142
  65. package/src/cli/advanced-commands.js +6 -6
  66. package/src/cli/register-meta.js +42 -82
  67. package/src/cli/register-pipeline.js +8 -140
  68. package/src/cli/register-plan.js +9 -100
  69. package/src/cli/retired-commands.js +49 -0
  70. package/src/cli.js +18 -5
  71. package/src/commands/agent-run.js +40 -26
  72. package/src/commands/audit.js +127 -124
  73. package/src/commands/board.js +5 -9
  74. package/src/commands/check.js +29 -16
  75. package/src/commands/doctor.js +2 -0
  76. package/src/commands/env.js +176 -148
  77. package/src/commands/go.js +76 -55
  78. package/src/commands/harden.js +105 -86
  79. package/src/commands/hu-start.js +52 -0
  80. package/src/commands/hu.js +288 -221
  81. package/src/commands/identity.js +111 -78
  82. package/src/commands/init-atril.js +47 -0
  83. package/src/commands/init.js +11 -40
  84. package/src/commands/install-tools.js +70 -85
  85. package/src/commands/main-guard.js +78 -0
  86. package/src/commands/policy.js +226 -184
  87. package/src/commands/qmd.js +27 -21
  88. package/src/commands/rag.js +75 -48
  89. package/src/commands/review-gate.js +422 -339
  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 +94 -25
  94. package/src/config/loader.js +34 -17
  95. package/src/config/overrides.js +2 -1
  96. package/src/config/secrets.js +36 -0
  97. package/src/config.js +1 -0
  98. package/src/environment/adr.js +16 -2
  99. package/src/environment/board-access.js +11 -9
  100. package/src/git/karajan-label.js +20 -8
  101. package/src/guards/main-guard.js +54 -0
  102. package/src/harden/branch-protection.js +36 -3
  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 +81 -35
  106. package/src/harden/sentinel/sentinel-lib.mjs +149 -23
  107. package/src/harden/sentinel/sentinel-shell.mjs +64 -0
  108. package/src/harden/sentinel/sessionstart.mjs +3 -2
  109. package/src/harden/sentinel/stop.mjs +5 -4
  110. package/src/harden/workflow-engine.js +12 -1
  111. package/src/harden/workflow-templates.js +1 -1
  112. package/src/hu/board-lookup.js +29 -0
  113. package/src/hu/gh-identity.js +23 -0
  114. package/src/hu/github-board.js +4 -2
  115. package/src/hu/lane-prune.js +15 -7
  116. package/src/hu/lane-setup.js +65 -0
  117. package/src/hu/lanes.js +66 -0
  118. package/src/hu/team-board-mirror.js +3 -2
  119. package/src/infrastructure/command-runner.js +1 -1
  120. package/src/infrastructure/file-system-service.js +1 -1
  121. package/src/mcp/handlers/direct-handlers.js +2 -292
  122. package/src/mcp/handlers/hu-handlers.js +44 -55
  123. package/src/mcp/handlers/management-handlers.js +2 -42
  124. package/src/mcp/server-handlers.js +16 -22
  125. package/src/mcp/tools.js +5 -200
  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 +56 -43
  134. package/src/rag/bootstrap-index.js +28 -54
  135. package/src/rag/migrate.js +1 -1
  136. package/src/rag/onnx-fallback.js +2 -2
  137. package/src/rag/pid-lease.js +70 -0
  138. package/src/rag/project-store.js +2 -12
  139. package/src/review/card-first.js +57 -46
  140. package/src/review/dual-review.js +5 -4
  141. package/src/review/gate-gitignore.js +23 -4
  142. package/src/review/gate-hook.js +47 -0
  143. package/src/review/one-shot-review.js +51 -37
  144. package/src/review/publish-pr.js +18 -3
  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 +34 -3
  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 +2 -26
  154. package/src/roles/audit-role.js +106 -125
  155. package/src/skills/atril-skills.js +5 -4
  156. package/src/sonar/scanner.js +116 -101
  157. package/src/steward/invariants.js +5 -4
  158. package/src/steward/phantom-sweep.js +66 -0
  159. package/src/tournament/judge.js +60 -53
  160. package/src/utils/agent-home.js +33 -0
  161. package/src/utils/gh-account.js +37 -0
  162. package/src/utils/git.js +0 -13
  163. package/src/utils/home-migration.js +7 -1
  164. package/src/utils/main-checkout.js +53 -0
  165. package/src/utils/qmd-collection.js +3 -1
  166. package/src/utils/welcome.js +3 -3
  167. package/templates/{skills → atril/commands}/kj-architect.md +5 -0
  168. package/templates/{skills → atril/commands}/kj-audit.md +5 -0
  169. package/templates/{skills → atril/commands}/kj-board.md +5 -0
  170. package/templates/{skills → atril/commands}/kj-code.md +5 -0
  171. package/templates/{skills → atril/commands}/kj-csv-transform.md +5 -0
  172. package/templates/{skills → atril/commands}/kj-data-report.md +5 -0
  173. package/templates/{skills → atril/commands}/kj-discover.md +5 -0
  174. package/templates/{skills → atril/commands}/kj-rag-query.md +5 -0
  175. package/templates/{skills → atril/commands}/kj-review.md +5 -0
  176. package/templates/{skills → atril/commands}/kj-security.md +5 -0
  177. package/templates/{skills → atril/commands}/kj-sonar.md +5 -0
  178. package/templates/{skills → atril/commands}/kj-sql-analysis.md +5 -0
  179. package/templates/{skills → atril/commands}/kj-test.md +5 -0
  180. package/packages/hu-board/public/utils/run-launcher.js +0 -169
  181. package/packages/hu-board/src/run-tracker.js +0 -121
  182. package/src/activity-log.js +0 -59
  183. package/src/agents/host-agent.js +0 -38
  184. package/src/agents/model-registry.js +0 -142
  185. package/src/audit/plan-adherence.js +0 -224
  186. package/src/autonomy/arbiter-role.js +0 -71
  187. package/src/autonomy/arbiter.js +0 -22
  188. package/src/autonomy/decider.js +0 -18
  189. package/src/autonomy/decision-resolver.js +0 -58
  190. package/src/autonomy/outcome-report.js +0 -55
  191. package/src/autonomy/stage-ask.js +0 -30
  192. package/src/brain/decision-tracker.js +0 -125
  193. package/src/brain/decisor.js +0 -243
  194. package/src/brain/solomon-consult.js +0 -171
  195. package/src/brain/standby-scheduler.js +0 -8
  196. package/src/budget/comparison.js +0 -96
  197. package/src/canvas/parse-md.js +0 -238
  198. package/src/canvas/schema.js +0 -101
  199. package/src/canvas/to-flat.js +0 -85
  200. package/src/canvas/validate.js +0 -163
  201. package/src/ci/dispatch.js +0 -99
  202. package/src/ci/index.js +0 -3
  203. package/src/ci/pr-diff.js +0 -26
  204. package/src/cli/register-standby.js +0 -26
  205. package/src/commands/architect.js +0 -111
  206. package/src/commands/autorun.js +0 -49
  207. package/src/commands/researcher.js +0 -54
  208. package/src/commands/resume.js +0 -72
  209. package/src/commands/review.js +0 -89
  210. package/src/commands/run.js +0 -185
  211. package/src/commands/standby.js +0 -117
  212. package/src/commands/status.js +0 -36
  213. package/src/commands/triage.js +0 -78
  214. package/src/commands/undo.js +0 -62
  215. package/src/domains/domain-loader.js +0 -245
  216. package/src/domains/domain-registry.js +0 -140
  217. package/src/domains/domain-synthesizer.js +0 -129
  218. package/src/git/automation.js +0 -433
  219. package/src/git/coder-commit-guard.js +0 -40
  220. package/src/git/hu-automation.js +0 -243
  221. package/src/git/hu-snapshot.js +0 -10
  222. package/src/git/symlink-targets.js +0 -57
  223. package/src/golden/asserter.js +0 -92
  224. package/src/golden/loader.js +0 -52
  225. package/src/golden/runner.js +0 -92
  226. package/src/golden/schema.js +0 -41
  227. package/src/guards/duplicate-members.js +0 -86
  228. package/src/guards/intent-guard.js +0 -161
  229. package/src/guards/perf-guard.js +0 -126
  230. package/src/guards/policy-guard.js +0 -90
  231. package/src/guards/policy-resolver.js +0 -85
  232. package/src/guards/secret-redactor.js +0 -71
  233. package/src/hu/acceptance-runner.js +0 -138
  234. package/src/hu/lazy-planner.js +0 -109
  235. package/src/hu/parallel-executor.js +0 -94
  236. package/src/hu/routing-metrics.js +0 -17
  237. package/src/hu/split-validator.js +0 -143
  238. package/src/hu/splitting-detector.js +0 -158
  239. package/src/hu/splitting-generator.js +0 -175
  240. package/src/hu/store.js +0 -394
  241. package/src/infrastructure/mocks.js +0 -182
  242. package/src/mcp/handlers/run-handler.js +0 -318
  243. package/src/mcp/sovereignty-guard.js +0 -113
  244. package/src/mcp/tool-arg-normalizers.js +0 -16
  245. package/src/mutate/reviewer-signal.js +0 -76
  246. package/src/orchestrator/agent-fallback.js +0 -81
  247. package/src/orchestrator/brain-coordinator.js +0 -186
  248. package/src/orchestrator/ci-integration.js +0 -139
  249. package/src/orchestrator/config-init.js +0 -426
  250. package/src/orchestrator/direct-actions.js +0 -217
  251. package/src/orchestrator/drivers/error-recovery.js +0 -287
  252. package/src/orchestrator/drivers/init-context.js +0 -238
  253. package/src/orchestrator/drivers/iteration-loop.js +0 -382
  254. package/src/orchestrator/drivers/iteration-phases/coder-and-refactorer.js +0 -69
  255. package/src/orchestrator/drivers/iteration-phases/guards.js +0 -91
  256. package/src/orchestrator/drivers/iteration-phases/handle-approved.js +0 -29
  257. package/src/orchestrator/drivers/iteration-phases/quality-gates.js +0 -125
  258. package/src/orchestrator/drivers/iteration-phases/reviewer-gate.js +0 -62
  259. package/src/orchestrator/drivers/post-loop.js +0 -404
  260. package/src/orchestrator/drivers/pre-loop-phases/auto-hu-batch.js +0 -137
  261. package/src/orchestrator/drivers/pre-loop-phases/config-deprecations.js +0 -47
  262. package/src/orchestrator/drivers/pre-loop-phases/ensure-addyosmani-skills.js +0 -84
  263. package/src/orchestrator/drivers/pre-loop-phases/inject-loaded-plan.js +0 -393
  264. package/src/orchestrator/drivers/pre-loop.js +0 -524
  265. package/src/orchestrator/drivers/run-hu-batch.js +0 -468
  266. package/src/orchestrator/feedback-enrichment.js +0 -140
  267. package/src/orchestrator/feedback-queue.js +0 -151
  268. package/src/orchestrator/flow-control.js +0 -170
  269. package/src/orchestrator/flow-runner.js +0 -365
  270. package/src/orchestrator/fs-leak-detector.js +0 -228
  271. package/src/orchestrator/hu-scheduler.js +0 -114
  272. package/src/orchestrator/hu-sub-pipeline.js +0 -642
  273. package/src/orchestrator/iteration-gate.js +0 -73
  274. package/src/orchestrator/iteration-stages.js +0 -9
  275. package/src/orchestrator/parallel-limiter.js +0 -74
  276. package/src/orchestrator/pipeline-context.js +0 -72
  277. package/src/orchestrator/post-loop-stages.js +0 -468
  278. package/src/orchestrator/pre-loop-stages.js +0 -11
  279. package/src/orchestrator/preflight-checks.js +0 -487
  280. package/src/orchestrator/repair/repair-detector.js +0 -117
  281. package/src/orchestrator/repair/repair-runner.js +0 -142
  282. package/src/orchestrator/reviewer-fallback.js +0 -45
  283. package/src/orchestrator/role-output-compressor.js +0 -189
  284. package/src/orchestrator/session-journal.js +0 -308
  285. package/src/orchestrator/solomon-escalation.js +0 -331
  286. package/src/orchestrator/solomon-rules.js +0 -212
  287. package/src/orchestrator/stages/acceptance-coverage-stage.js +0 -47
  288. package/src/orchestrator/stages/architect-stage.js +0 -172
  289. package/src/orchestrator/stages/coder-stage.js +0 -506
  290. package/src/orchestrator/stages/domain-curator-stage.js +0 -82
  291. package/src/orchestrator/stages/format-stage.js +0 -61
  292. package/src/orchestrator/stages/hu-reviewer-phases/detect-and-apply-splits.js +0 -127
  293. package/src/orchestrator/stages/hu-reviewer-stage.js +0 -419
  294. package/src/orchestrator/stages/perf-stage.js +0 -140
  295. package/src/orchestrator/stages/planner-stage.js +0 -85
  296. package/src/orchestrator/stages/rag-context-stage.js +0 -80
  297. package/src/orchestrator/stages/rag-preload-decisor.js +0 -58
  298. package/src/orchestrator/stages/research-stage.js +0 -69
  299. package/src/orchestrator/stages/reviewer-stage.js +0 -431
  300. package/src/orchestrator/stages/sonar-stage.js +0 -439
  301. package/src/orchestrator/stages/stage-classes.js +0 -84
  302. package/src/orchestrator/stages/stage-executor.js +0 -155
  303. package/src/orchestrator/stages/suite-green-stage.js +0 -62
  304. package/src/orchestrator/stages/tdd-discipline-stage.js +0 -56
  305. package/src/orchestrator/stages/tool-judge-stage.js +0 -53
  306. package/src/orchestrator/stages/triage-stage.js +0 -172
  307. package/src/orchestrator/standby.js +0 -71
  308. package/src/orchestrator/verification-gate.js +0 -183
  309. package/src/orchestrator.js +0 -22
  310. package/src/plan/adr-generator.js +0 -170
  311. package/src/plan/adr-loader.js +0 -60
  312. package/src/plan/plan-executor.js +0 -100
  313. package/src/plan/plan-validation.js +0 -4
  314. package/src/prompts/acceptance-coverage.js +0 -32
  315. package/src/prompts/arbiter.js +0 -38
  316. package/src/prompts/architect.js +0 -103
  317. package/src/prompts/hu-reviewer.js +0 -244
  318. package/src/prompts/tool-correctness-judge.js +0 -31
  319. package/src/prompts/triage.js +0 -57
  320. package/src/rag/embedder.js +0 -2
  321. package/src/rag/embedders/_cloud-base.js +0 -2
  322. package/src/rag/embedders/cohere.js +0 -2
  323. package/src/rag/embedders/mistral.js +0 -2
  324. package/src/rag/embedders/onnx.js +0 -2
  325. package/src/rag/embedders/openai.js +0 -2
  326. package/src/rag/embedders/voyage.js +0 -2
  327. package/src/rag/rerank.js +0 -4
  328. package/src/rag/where-parser.js +0 -4
  329. package/src/repeat-detector.js +0 -88
  330. package/src/review/diff-generator.js +0 -112
  331. package/src/review/git-stash-helper.js +0 -104
  332. package/src/review/schema.js +0 -31
  333. package/src/review/scope-filter.js +0 -152
  334. package/src/review/snapshot-diff.js +0 -122
  335. package/src/review/tdd-discipline.js +0 -90
  336. package/src/review/tdd-policy.js +0 -69
  337. package/src/roles/acceptance-coverage-role.js +0 -78
  338. package/src/roles/architect-role.js +0 -77
  339. package/src/roles/coder-role.js +0 -113
  340. package/src/roles/commiter-role.js +0 -113
  341. package/src/roles/domain-curator-role.js +0 -134
  342. package/src/roles/hu-reviewer-role.js +0 -64
  343. package/src/roles/impeccable-role.js +0 -85
  344. package/src/roles/index.js +0 -18
  345. package/src/roles/karajan-brain-role.js +0 -134
  346. package/src/roles/perf-role.js +0 -122
  347. package/src/roles/planner-role.js +0 -151
  348. package/src/roles/refactorer-role.js +0 -30
  349. package/src/roles/repairer-role.js +0 -181
  350. package/src/roles/researcher-role.js +0 -70
  351. package/src/roles/reviewer-role.js +0 -115
  352. package/src/roles/security-role.js +0 -73
  353. package/src/roles/solomon-role.js +0 -271
  354. package/src/roles/sonar-role.js +0 -72
  355. package/src/roles/tester-role.js +0 -235
  356. package/src/roles/tool-correctness-judge-role.js +0 -78
  357. package/src/roles/triage-role.js +0 -99
  358. package/src/session/journal/decisions-writer.js +0 -200
  359. package/src/session/journal/iteration-logger.js +0 -194
  360. package/src/session/journal/summary-writer.js +0 -320
  361. package/src/session/journal/tree-writer.js +0 -164
  362. package/src/session/journal/write-all.js +0 -89
  363. package/src/session-cleanup.js +0 -116
  364. package/src/skills/semantic-detector.js +0 -167
  365. package/src/skills/skill-detector.js +0 -385
  366. package/src/sonar/cloud-scanner.js +0 -78
  367. package/src/tracker-bootstrap.js +0 -44
  368. package/src/types/index.js +0 -38
  369. package/src/types/policy.js +0 -43
  370. package/src/types/solomon.js +0 -87
  371. package/src/utils/async-lock.js +0 -31
  372. package/src/utils/budget.js +0 -229
  373. package/src/utils/cwd-confirm.js +0 -39
  374. package/src/utils/display/event-handlers.js +0 -403
  375. package/src/utils/display/formatters.js +0 -132
  376. package/src/utils/display/header.js +0 -47
  377. package/src/utils/display/session-summary.js +0 -118
  378. package/src/utils/display/solomon.js +0 -42
  379. package/src/utils/pricing.js +0 -29
  380. package/src/utils/quota-simulator.js +0 -56
  381. package/src/utils/role-assigner.js +0 -137
  382. package/src/utils/rtk-wrapper.js +0 -87
  383. package/src/utils/run-registry.js +0 -9
  384. package/src/utils/status-dashboard.js +0 -254
  385. package/src/utils/tool-call-extractor.js +0 -56
  386. package/src/webperf/devtools-detect.js +0 -112
  387. package/src/webperf/webperf-role-integration.js +0 -66
  388. 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;