@karajan-family/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
package/README.md CHANGED
@@ -110,9 +110,9 @@ Tier B is the guarantee floor — hosts without hooks lose A, never B; C re-veri
110
110
 
111
111
  Every chokepoint decision lands in a hash-chained log (`kj policy anchor` seals its head in git), and `kj policy report` turns that log into evidence of process: per rule, how often it warned, denied or was exempted, which denials are still open, which grants are alive, expired or renewed — so a rule "gains teeth" on data, not on a hunch, and a renewed exception is read for what it is: the policy asking to change.
112
112
 
113
- ## Headless mode
113
+ ## Unattended runs (CI and automation)
114
114
 
115
- The classic multiagent pipeline lives on for CI and automation: `kj run "<task>"` orchestrates coder/reviewer/tester subprocess roles unattended, with the same gates. Agents and CI pass `--non-interactive` (or `KJ_NON_INTERACTIVE=1`): safe gates auto-answer, FAIL findings stop the run with a real exit code. `kj advanced` lists the full surface. [Headless mode docs](https://karajancode.com/docs/v4/headless/).
115
+ The classic multiagent pipeline (`kj run` and its family) was retired in 5.0: your agent does the work and Karajan governs it. In CI and scripts, pass `--non-interactive` (or `KJ_NON_INTERACTIVE=1`): safe gates auto-answer, FAIL findings stop with a real exit code. Moving from 4.x? See [MIGRATION-v5.md](MIGRATION-v5.md). `kj advanced` lists the full surface.
116
116
 
117
117
  ## v3 (historical)
118
118
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@karajan-family/code",
3
- "version": "4.70.4",
3
+ "version": "5.0.0",
4
4
  "description": "Local multi-agent coding orchestrator with TDD, SonarQube, and code review pipeline",
5
5
  "type": "module",
6
6
  "license": "AGPL-3.0",
@@ -94,30 +94,24 @@
94
94
  "prepublishOnly": "node scripts/verify-pack.mjs",
95
95
  "verify-quickstart": "node scripts/quickstart-gate.mjs"
96
96
  },
97
- "simple-git-hooks": {
98
- "pre-commit": "npx lint-staged"
99
- },
100
- "lint-staged": {
101
- "src/**/*.js": "eslint --max-warnings=999"
102
- },
103
97
  "overrides": {
104
98
  "fast-uri": "^3.1.2"
105
99
  },
106
100
  "dependencies": {
107
101
  "@babel/parser": "^7.29.7",
108
- "@karajan-family/atril": "^1.0.0",
102
+ "@karajan-family/atril": "^1.1.0",
109
103
  "@karajan-family/governance": "^0.1.0",
110
104
  "@modelcontextprotocol/sdk": "^1.29.0",
111
105
  "@xterm/xterm": "^6.0.0",
112
- "better-sqlite3": "^12.10.0",
113
106
  "chokidar": "^5.0.0",
114
107
  "commander": "^15.0.0",
108
+ "driver.js": "1.10.2",
115
109
  "execa": "^9.6.0",
116
110
  "express": "^5.1.0",
117
111
  "express-rate-limit": "^8.5.0",
118
112
  "helmet": "^8.1.0",
119
113
  "js-yaml": "^4.2.0",
120
- "karajan-core": "^1.6.0",
114
+ "karajan-core": "^1.7.0",
121
115
  "karajan-rag": "^1.2.0",
122
116
  "knip": "^6.15.0",
123
117
  "madge": "^8.0.0",
@@ -133,19 +127,17 @@
133
127
  },
134
128
  "devDependencies": {
135
129
  "@eslint/js": "^10.0.1",
136
- "@vitest/coverage-v8": "^4.1.8",
130
+ "@vitest/coverage-v8": "^4.1.12",
137
131
  "esbuild": "^0.28.0",
138
132
  "eslint": "^10.4.1",
139
133
  "eslint-plugin-import-x": "^4.16.2",
140
134
  "eslint-plugin-node-security": "5.2.2",
141
135
  "eslint-plugin-security": "^4.0.0",
142
136
  "globals": "^17.6.0",
143
- "lint-staged": "^17.0.7",
144
137
  "postject": "^1.0.0-alpha.6",
145
138
  "prettier": "^3.4.2",
146
- "simple-git-hooks": "^2.13.1",
147
139
  "smol-toml": "^1.7.0",
148
- "vitest": "^4.1.8"
140
+ "vitest": "^4.1.12"
149
141
  },
150
142
  "publishConfig": {
151
143
  "access": "public"
@@ -14,8 +14,8 @@
14
14
  },
15
15
  "dependencies": {
16
16
  "@xterm/xterm": "^6.0.0",
17
- "better-sqlite3": "^12.10.0",
18
17
  "chokidar": "^5.0.0",
18
+ "driver.js": "1.10.2",
19
19
  "express": "^5.1.0",
20
20
  "express-rate-limit": "^8.5.0",
21
21
  "helmet": "^8.1.0",
@@ -123,24 +123,9 @@ function renderEmptyState(title, text) {
123
123
 
124
124
  // ---- Plan execution ----
125
125
  //
126
- // The board delegates execution to the CLI via `POST /api/projects/:id/run`.
127
- // There's no more intermediate "certify" step: the user reviews HUs
128
- // (editing inline if needed — see edit-in-place PR), and when happy
129
- // clicks "Run plan", which spawns `kj run --plan <id>` as a detached
130
- // child on the server. Status updates flow back through the plan JSON
131
- // watcher without any manual refresh.
132
- //
133
- // We still expose a per-HU status PATCH endpoint for corrective moves
134
- // (e.g. retry a failed HU manually), but it's no longer the primary
135
- // action on the card.
126
+ // KJC-TSK-1319: the board no longer launches kj run (retired in 5.0); your
127
+ // agent does the work. A per-HU status PATCH stays for corrective moves.
136
128
 
137
- /**
138
- * Launch the pipeline over every plan of a project. Returns when the
139
- * spawn ack is received — not when the run completes. Live progress is
140
- * reflected automatically by the plans-dir watcher on the server, so
141
- * the board re-renders without us having to poll.
142
- * @param {string} projectId
143
- */
144
129
  // Track the most recently launched planId so the "View log" button in
145
130
  // the section header can re-open the viewer without the user hunting
146
131
  // through plan ids.
@@ -249,6 +234,7 @@ function handleRoute() {
249
234
  btn.classList.toggle('active', btn.dataset.view === currentView);
250
235
  });
251
236
 
252
- render();
237
+ // KJC-TSK-1198: the view's promise, so the boot can wait for it before the tour.
238
+ return render();
253
239
  }
254
240
 
@@ -6,6 +6,7 @@
6
6
  <title>Karajan HU Board</title>
7
7
  <link rel="stylesheet" href="/styles.css">
8
8
  <link rel="stylesheet" href="/vendor/xterm/css/xterm.css">
9
+ <link rel="stylesheet" href="/vendor/driver/driver.css">
9
10
  <link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'><rect width='32' height='32' rx='6' fill='%237c3aed'/><text x='16' y='22' text-anchor='middle' fill='white' font-size='16' font-family='sans-serif' font-weight='bold'>K</text></svg>">
10
11
  </head>
11
12
  <body>
@@ -25,7 +26,7 @@
25
26
  <div class="header__controls">
26
27
  <button class="control-btn" id="help-btn" title="What does each view do? Quick reference for Board / Graph / Dashboard / Sessions / Pipeline.">?</button>
27
28
  <button class="control-btn" id="config-btn" title="Configurar Karajan (modelos, agentes, tope de iteraciones, …)">⚙</button>
28
- <button class="control-btn" id="commands-btn" title="Launch a Karajan CLI command (kj plan, kj architect, kj clean…)">⚡</button>
29
+ <button class="control-btn" id="commands-btn" title="Launch a Karajan CLI command (kj clean)">⚡</button>
29
30
  <button class="control-btn" id="conversation-btn" title="Conversación con tu agente — la terminal real, dentro del board (ADR 0008)" onclick="openConversationPanel()">💬</button>
30
31
  <button class="control-btn" id="restart-btn" title="Restart the HU Board server (preserves URL, the page reconnects automatically)">🔁</button>
31
32
  <button class="control-btn" id="sync-btn" title="Re-scan disk for new batches">🔄</button>
@@ -72,6 +73,9 @@
72
73
  <script src="/utils/story-detail-view.js"></script>
73
74
  <script src="/utils/preflight-view.js"></script>
74
75
  <script src="/utils/plan-rollup-view.js"></script>
76
+ <script src="/utils/shared-board-notice.js"></script>
77
+ <script src="/vendor/driver/driver.js.iife.js"></script>
78
+ <script src="/utils/board-tour.js"></script>
75
79
  <script src="/utils/log-panel.js"></script>
76
80
  <script src="/vendor/xterm/lib/xterm.js"></script>
77
81
  <script src="/utils/terminal-panel.js"></script>
@@ -80,7 +84,6 @@
80
84
  <script src="/utils/story-edit-form.js"></script>
81
85
  <script src="/utils/config-editor.js"></script>
82
86
  <script src="/utils/command-launcher.js"></script>
83
- <script src="/utils/run-launcher.js"></script>
84
87
  <script src="/utils/server-push.js"></script>
85
88
  <script src="/app.js"></script>
86
89
  <script src="/utils/init-listeners.js"></script>
@@ -374,6 +374,14 @@ a:hover {
374
374
  color: var(--text-muted);
375
375
  }
376
376
 
377
+ /* KJC-TSK-1313: the worktree lane working the card. */
378
+ .story-card__lane {
379
+ font-family: var(--font-mono);
380
+ font-size: 0.75rem;
381
+ color: var(--color-green);
382
+ overflow-wrap: anywhere;
383
+ }
384
+
377
385
  .quality-bar {
378
386
  display: inline-flex;
379
387
  gap: 1px;
@@ -438,6 +446,24 @@ a:hover {
438
446
  margin-top: 4px;
439
447
  }
440
448
 
449
+ /* KJC-TSK-1324: ▶ starts a pending card in its own lane. */
450
+ .story-card__play {
451
+ margin-left: auto;
452
+ min-width: 28px;
453
+ min-height: 28px;
454
+ border: 1px solid var(--color-green);
455
+ border-radius: 6px;
456
+ background: transparent;
457
+ color: var(--color-green);
458
+ cursor: pointer;
459
+ }
460
+ .story-card__play:hover,
461
+ .story-card__play:focus-visible {
462
+ background: rgba(52, 211, 153, 0.15);
463
+ outline: 2px solid var(--color-green);
464
+ outline-offset: 1px;
465
+ }
466
+
441
467
  /* ---- Story Detail Modal ---- */
442
468
  .modal-backdrop {
443
469
  position: fixed;
@@ -6,9 +6,6 @@
6
6
  // Contents:
7
7
  // - api(): thin JSON fetch wrapper used everywhere in the UI.
8
8
  // - triggerSync(): POST /api/sync — fired on page load and the 🔄 button.
9
- // - Standby banner: _standbyData state + refreshStandby() +
10
- // _renderStandbyBanner() + _tickStandbyCountdowns() +
11
- // startStandbyPolling() — KJC-TSK-0414 PR4.
12
9
  // - Server-restart detector: pollServerVersion() + __versionBaseline +
13
10
  // window.forceRefresh — KJC-TSK-0379.
14
11
  //
@@ -22,77 +19,6 @@ async function api(path) {
22
19
  return res.json();
23
20
  }
24
21
 
25
- // KJC-TSK-0414 PR4: standby banner — sesiones hibernadas pendientes de resume.
26
- // Polleamos /api/standby cada 30s + tick countdown cada 1s.
27
- let _standbyData = { sessions: [] };
28
- let _standbyPollTimer = null;
29
- let _standbyTickTimer = null;
30
-
31
- function _fmtCountdown(ms) {
32
- if (ms <= 0) return '✓ resume inmediato';
33
- const s = Math.round(ms / 1000);
34
- const h = Math.floor(s / 3600);
35
- const m = Math.floor((s % 3600) / 60);
36
- const sec = s % 60;
37
- return `${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}:${String(sec).padStart(2, '0')}`;
38
- }
39
-
40
- async function refreshStandby() {
41
- try {
42
- const r = await api('/api/standby');
43
- _standbyData = r || { sessions: [] };
44
- _renderStandbyBanner();
45
- } catch { /* silencioso — endpoint nuevo, podría no existir en versiones viejas */ }
46
- }
47
-
48
- function _renderStandbyBanner() {
49
- let banner = document.getElementById('standby-banner');
50
- const sessions = _standbyData.sessions || [];
51
- if (sessions.length === 0) {
52
- if (banner) banner.remove();
53
- return;
54
- }
55
- if (!banner) {
56
- banner = document.createElement('div');
57
- banner.id = 'standby-banner';
58
- banner.style.cssText = 'background:rgba(251,191,36,0.12);border-left:4px solid #fbbf24;padding:10px 16px;margin:0 0 12px;font-size:0.85rem';
59
- const app = document.getElementById('app');
60
- if (app?.parentNode) app.parentNode.insertBefore(banner, app);
61
- }
62
- const now = Date.now();
63
- banner.innerHTML = `
64
- <div style="display:flex;align-items:center;gap:12px;flex-wrap:wrap">
65
- <strong>💤 ${sessions.length} sesión(es) hibernadas:</strong>
66
- ${sessions.map((s) => {
67
- const ms = new Date(s.cooldownUntil).getTime() - now;
68
- const label = _fmtCountdown(ms);
69
- const reason = s.reason || '?';
70
- const planInfo = s.planId ? ` · ${s.planId}${s.huId ? '/' + s.huId : ''}` : '';
71
- return `<span data-session="${esc(s.sessionId)}" data-cooldown="${esc(s.cooldownUntil)}" style="background:rgba(0,0,0,0.15);padding:4px 8px;border-radius:4px;font-family:monospace" title="${esc(reason)}${esc(planInfo)}">${esc(s.sessionId)} · <span class="standby-countdown">${label}</span></span>`;
72
- }).join('')}
73
- <span style="margin-left:auto;color:var(--text-muted);font-size:0.75rem">Auto-resume al llegar el cooldown (board) o <code>kj resume &lt;id&gt;</code></span>
74
- </div>
75
- `;
76
- }
77
-
78
- function _tickStandbyCountdowns() {
79
- const banner = document.getElementById('standby-banner');
80
- if (!banner) return;
81
- const now = Date.now();
82
- for (const chip of banner.querySelectorAll('[data-cooldown]')) {
83
- const target = new Date(chip.getAttribute('data-cooldown')).getTime();
84
- const span = chip.querySelector('.standby-countdown');
85
- if (span) span.textContent = _fmtCountdown(target - now);
86
- }
87
- }
88
-
89
- function startStandbyPolling() {
90
- if (_standbyPollTimer || _standbyTickTimer) return;
91
- refreshStandby();
92
- _standbyPollTimer = setInterval(refreshStandby, 30_000);
93
- _standbyTickTimer = setInterval(_tickStandbyCountdowns, 1000);
94
- }
95
-
96
22
  /**
97
23
  * Trigger a full re-scan of disk data (hu-stories + sessions).
98
24
  * Called on page load and via the sync button.
@@ -0,0 +1,62 @@
1
+ // KJC-TSK-1198 — guided tour of the HU Board (driver.js, served from the dependency at
2
+ // /vendor/driver). It starts by itself the first time; the help modal relaunches it.
3
+ //
4
+ // Classic script (no exports). Globals consumed: window.driver.js (driver.js.iife.js).
5
+
6
+ const BOARD_TOUR_SEEN_KEY = 'kj-board-tour-seen';
7
+
8
+ // [selector, title, description, what to say when it is not on screen (else the step is skipped)]
9
+ const BOARD_TOUR = [
10
+ ['[data-view="dashboard"]', 'Dashboard', 'Every project the board knows, with its stats. Click a project to open it.'],
11
+ ['#help-btn', 'Help', 'A quick reference of each view, and this tour again whenever you want it.'],
12
+ ['#config-btn', 'Settings', 'Configure Karajan: models, agents, iteration cap.'],
13
+ ['#commands-btn', 'Commands', 'Launch a kj command (kj clean) and watch its log.'],
14
+ ['#conversation-btn', 'Conversation', 'Your agent’s real terminal, inside the board.'],
15
+ ['#restart-btn', 'Restart', 'Restart the board server. The page reconnects by itself.'],
16
+ ['#sync-btn', 'Re-scan', 'Read the plans and sessions on disk again when something new is missing.'],
17
+ ['#force-refresh-btn', 'Clean reload', 'Wipe the browser caches and reload, for when the page looks stale.'],
18
+ ['#project-nav', 'Project bar', 'Board is the kanban of HUs, Graph who blocks whom, Sessions every kj run, Governance the rules and decision log, Pipeline the live runs, RAG the index of the code, Wiki the project docs.',
19
+ 'Open a project from the Dashboard: a bar appears with its views (Board, Graph, Sessions, Governance, Pipeline, RAG, Wiki).'],
20
+ ['#preflight-panel', 'Project checks', 'Whether the project is ready to run: remote, clean tree, agents. Red means a blocker; open it to see why.'],
21
+ ['#rag-panel', 'Search', 'Ask anything about the project’s plans, onboarding or code.'],
22
+ ['#shared-board-notice [role="note"]', 'Shared board', 'These HUs are the team’s, from GitHub Issues: read-only here, changed with kj hu move.'],
23
+ ['.kanban', 'Kanban', 'Pending, Running and Done. Click a card to see the HU in detail.'],
24
+ ];
25
+
26
+ const isOnScreen = (selector) => {
27
+ const el = document.querySelector(selector);
28
+ return Boolean(el) && el.offsetParent !== null;
29
+ };
30
+
31
+ /** The tour for what is on screen now: absent elements are skipped or explained in the middle. */
32
+ function boardTourSteps() {
33
+ return BOARD_TOUR.flatMap(([element, title, description, whenAbsent]) => {
34
+ if (isOnScreen(element)) return [{ element, popover: { title, description } }];
35
+ return whenAbsent ? [{ popover: { title, description: whenAbsent } }] : [];
36
+ });
37
+ }
38
+
39
+ /** Run the whole tour; once closed or skipped, it does not start by itself again. */
40
+ function startBoardTour() {
41
+ const tour = window.driver.js.driver({
42
+ showProgress: true,
43
+ allowKeyboardControl: true,
44
+ steps: boardTourSteps(),
45
+ onDestroyed: () => {
46
+ try { localStorage.setItem(BOARD_TOUR_SEEN_KEY, '1'); }
47
+ catch (err) { console.warn('board tour: cannot remember it was seen:', err.message); }
48
+ },
49
+ });
50
+ tour.drive();
51
+ }
52
+
53
+ /** First visit only. A browser that cannot remember it never gets it by itself. */
54
+ function maybeStartFirstTour() {
55
+ try {
56
+ if (localStorage.getItem(BOARD_TOUR_SEEN_KEY)) return false;
57
+ } catch {
58
+ return false;
59
+ }
60
+ startBoardTour();
61
+ return true;
62
+ }
@@ -6,12 +6,11 @@
6
6
  //
7
7
  // Globals consumed from app.js / earlier scripts:
8
8
  // - api(), esc(), renderEmptyState(), renderProjectPicker(),
9
- // renderPreflightPanel(), renderPlanRollup(), confirmRunWithPreflight(),
10
- // runProject(), openGenericLogPanel(), humaniseProjectName(),
9
+ // renderPreflightPanel(), renderPlanRollup(),
10
+ // openGenericLogPanel(), humaniseProjectName(),
11
11
  // resolveProjectMeta(), shortStoryId(), truncate(), scoreClass(),
12
12
  // qualityBar(), timeAgo(), showError(), showConfirm(),
13
- // showStoryDetail(), showOutcomeModal(), runSingleHuFromCard(),
14
- // renameProjectModal()
13
+ // showStoryDetail(), showOutcomeModal(), renameProjectModal()
15
14
  // - mutable: selectedProject, lastOpenedLog, projectNameCache,
16
15
  // projectInitialsCache
17
16
  // All lookups are runtime-bound; load order only needs board-view.js to live
@@ -35,10 +34,13 @@ async function renderBoard() {
35
34
  return;
36
35
  }
37
36
 
38
- const [stories, projectCost] = await Promise.all([
37
+ const [stories, projectCost, lanes] = await Promise.all([
39
38
  api(`/api/projects/${encodeURIComponent(selectedProject)}/stories`),
40
39
  api(`/api/projects/${encodeURIComponent(selectedProject)}/cost`).catch(() => null),
40
+ api(`/api/lanes/${encodeURIComponent(selectedProject)}`).catch((err) => ({ byStory: {}, error: String(err.message || err) })),
41
41
  ]);
42
+ lanesByStory = lanes.byStory || {};
43
+ if (lanes.error) console.warn(`[board] worktree lanes unavailable: ${lanes.error}`);
42
44
  const costSummary = projectCost ? formatProjectCostSummary(projectCost) : null;
43
45
  // Φ0-G (KJC-TSK-0525): aggregated cache-hit badge at the project
44
46
  // header, rendered alongside the project cost chip.
@@ -63,11 +65,9 @@ async function renderBoard() {
63
65
  // columna "Failed" — vuelven a Pending con result=fail y badge ✗.
64
66
  // status=failed legacy se trata como pending (migración lazy).
65
67
  const columns = {
66
- pending: stories.filter((s) =>
67
- ['pending', 'certified', 'needs_context', 'blocked', 'failed'].includes(s.status)
68
- ),
69
- running: stories.filter((s) => ['coding', 'reviewing'].includes(s.status)),
70
- done: stories.filter((s) => s.status === 'done'),
68
+ pending: stories.filter((s) => kanbanColumnOf(s.status) === 'pending'),
69
+ running: stories.filter((s) => kanbanColumnOf(s.status) === 'running'),
70
+ done: stories.filter((s) => kanbanColumnOf(s.status) === 'done'),
71
71
  };
72
72
 
73
73
  if (stories.length === 0) {
@@ -75,15 +75,8 @@ async function renderBoard() {
75
75
  return;
76
76
  }
77
77
 
78
- // "Run plan" bulk action: visible when a project is selected AND at
79
- // least one HU is still awaiting execution AND nothing's currently
80
- // running (to avoid accidentally launching a second pipeline over a
81
- // live one). Replaces the old "Mark as certified" button — the
82
- // intermediate "certified" state was noise from the user's POV.
83
- const awaitingCount = columns.pending.length;
84
- const runningCount = columns.running.length;
85
- const canRun = Boolean(selectedProject) && awaitingCount > 0 && runningCount === 0;
86
- const isRunning = runningCount > 0;
78
+ // KJC-TSK-1351: no "⚙ N running" badge in the header. It counted the HUs the board's kj run
79
+ // could launch; with kj run retired the Running column's own count says how many are in progress.
87
80
 
88
81
  // KJC-TSK-0403: 3 canonical lanes. Failed eliminado — HUs con
89
82
  // result=fail aparecen en Pending con badge ✗.
@@ -106,32 +99,13 @@ async function renderBoard() {
106
99
  <span class="section-header__count" title="stories">${stories.length} ${maggleText('board.stories', 'stories')}</span>
107
100
  ${costSummary ? `<span class="section-header__cost" title="${esc(costSummary.tooltip)}">💵 ${esc(costSummary.label)}</span>` : ''}
108
101
  ${cacheSummary ? `<span class="section-header__cache" title="${esc(cacheSummary.tooltip)}">${esc(cacheSummary.label)}</span>` : ''}
109
- ${isRunning ? `
110
- <button id="running-badge-btn" class="section-header__badge"
111
- style="margin-left:auto;padding:4px 10px;font-size:0.8rem;background:var(--color-yellow,#eab308);color:#000;border-radius:var(--radius-sm);font-weight:600;border:none;cursor:pointer;"
112
- title="Abrir el log de la HU en marcha">
113
- ⚙ ${runningCount} ${maggleText('board.running', 'running')}…
114
- </button>
115
- <button id="stop-run-btn" class="control-btn"
116
- style="padding:4px 10px;font-size:0.8rem;background:var(--color-red,#ef4444);color:#fff;border:none;border-radius:var(--radius-sm);cursor:pointer;font-weight:600;"
117
- title="${maggleText('board.stopTitle', 'Abortar todos los kj run en marcha de este proyecto (SIGTERM con escalado a SIGKILL tras 5s)')}">
118
- ${maggleText('board.stop', '⏹ Stop')}
119
- </button>
120
- ` : ''}
121
102
  ${lastOpenedLog ? `
122
103
  <button class="control-btn" id="view-log-btn"
123
- style="${isRunning ? '' : 'margin-left:auto;'}padding:6px 12px;font-size:0.85rem;background:var(--bg-primary);border:1px solid var(--border);color:var(--text);border-radius:var(--radius-sm);cursor:pointer;"
104
+ style="margin-left:auto;padding:6px 12px;font-size:0.85rem;background:var(--bg-primary);border:1px solid var(--border);color:var(--text);border-radius:var(--radius-sm);cursor:pointer;"
124
105
  title="Re-open the log of the most recent run/command">
125
106
  ${maggleText('board.viewLog', '📜 View log')}
126
107
  </button>
127
108
  ` : ''}
128
- ${canRun ? `
129
- <button class="control-btn" id="run-plan-btn"
130
- style="margin-left:auto;padding:6px 14px;font-size:0.9rem;background:var(--color-green);color:#fff;border:none;border-radius:var(--radius-sm);cursor:pointer;font-weight:600;"
131
- title="${maggleText('board.runTitle', 'Launch kj run --plan over every plan in this project')}">
132
- ${maggleText('board.run', '▶ Run plan')} (${awaitingCount} ${maggleText('board.story', 'HU')}${awaitingCount === 1 ? '' : 's'})
133
- </button>
134
- ` : ''}
135
109
  </div>
136
110
  <div id="preflight-panel" class="preflight-panel" style="margin:8px 0;">
137
111
  <div class="preflight-panel__loading" style="font-size:0.8rem;color:var(--text-muted);padding:6px 10px;">
@@ -150,24 +124,13 @@ async function renderBoard() {
150
124
  </button>
151
125
  <div id="rag-results" style="flex-basis:100%;font-size:0.8rem;color:var(--text-muted);"></div>
152
126
  </div>
127
+ <div id="shared-board-notice"></div>
153
128
  <div id="plan-rollup-banner"></div>
154
129
  <div class="kanban">
155
130
  ${visibleColumns.map((c) => renderKanbanColumn(c.title, c.cls, c.rows)).join('')}
156
131
  </div>
157
132
  `;
158
133
 
159
- if (canRun) {
160
- document.getElementById('run-plan-btn').addEventListener('click', async () => {
161
- // Pre-run safety: ask preflight if anything risky is missing
162
- // (no remote, dirty tree, missing agents, etc.). The modal
163
- // shows blockers + warnings in plain Spanish and the user
164
- // can cancel or proceed anyway. If everything is green we
165
- // skip the modal and run straight away.
166
- const ok = await confirmRunWithPreflight(selectedProject);
167
- if (!ok) return;
168
- await runProject(selectedProject);
169
- });
170
- }
171
134
  // Preflight panel is rendered AFTER the kanban so we can fetch
172
135
  // it without blocking the initial paint. Replaces a placeholder
173
136
  // div in-place (no full re-render).
@@ -176,6 +139,8 @@ async function renderBoard() {
176
139
  // project has a stamped outcome. Non-blocking — if it fails the
177
140
  // user just doesn't see the banner.
178
141
  renderPlanRollup(selectedProject).catch(() => {});
142
+ // KJC-TSK-1197: explain the shared board when this project mirrors one.
143
+ renderSharedBoardNotice(selectedProject).catch((err) => console.warn('shared board notice:', err.message));
179
144
  const viewLogBtn = document.getElementById('view-log-btn');
180
145
  if (viewLogBtn && lastOpenedLog) {
181
146
  viewLogBtn.addEventListener('click', () => openGenericLogPanel({
@@ -184,76 +149,9 @@ async function renderBoard() {
184
149
  tailUrl: lastOpenedLog.tailUrl,
185
150
  }));
186
151
  }
187
- // Wire the yellow "⚙ N running…" badge so it opens the live log of the
188
- // first running HU. Without this the badge was decorative — and if the
189
- // run was started before the current page load (refresh / new browser
190
- // session) `lastOpenedLog` is null, so the "📜 View log" button isn't
191
- // even rendered. Then the user has no way to see the live output.
192
- //
193
- // For a single-HU run the log lives at:
194
- // ~/.karajan/hu-board-runs/<planId>--hu-<localHuId>.log
195
- // which the /api/runs/:commandId/log endpoint serves when commandId
196
- // matches the basename. The composite id stored in SQLite is
197
- // "<projectId>::<localHuId>", so we strip the prefix to derive
198
- // localHuId and concatenate with plan_id (already on the story row).
199
- const runningBadgeBtn = document.getElementById('running-badge-btn');
200
- if (runningBadgeBtn) {
201
- runningBadgeBtn.addEventListener('click', async () => {
202
- const running = stories.find((s) => s.status === 'coding' || s.status === 'reviewing');
203
- if (!running) return;
204
- const planId = running.plan_id;
205
- if (!planId) {
206
- await showError('Esta HU no tiene plan_id asociado, no puedo localizar su log.', { title: 'Sin log' });
207
- return;
208
- }
209
- const localHuId = running.id.includes('::') ? running.id.split('::').pop() : running.id;
210
- const logBasename = `${planId}--hu-${localHuId}`;
211
- const tailUrl = (offset) => `/api/runs/${encodeURIComponent(logBasename)}/log?offset=${offset || 0}`;
212
- lastOpenedLog = { id: logBasename, label: `HU ${localHuId}`, tailUrl };
213
- openGenericLogPanel({ id: logBasename, label: `HU ${localHuId}`, tailUrl });
214
- });
215
- }
216
-
217
- // KJC-TSK-0396: botón ⏹ Stop. Abortar todos los kj run vivos del
218
- // proyecto. Si hay múltiples planes corriendo (caso raro pero
219
- // posible), itera sobre cada planId único de las HUs running.
220
- const stopRunBtn = document.getElementById('stop-run-btn');
221
- if (stopRunBtn) {
222
- stopRunBtn.addEventListener('click', async () => {
223
- const runningHus = stories.filter((s) => s.status === 'coding' || s.status === 'reviewing');
224
- const planIds = [...new Set(runningHus.map((s) => s.plan_id).filter(Boolean))];
225
- if (planIds.length === 0) {
226
- await showError('No hay plan_id asociado a las HUs en marcha.', { title: 'Sin runs' });
227
- return;
228
- }
229
- const ok = await showConfirm(
230
- `Esto matará ${planIds.length === 1 ? 'el proceso' : `los ${planIds.length} procesos`} del orquestador en marcha y dejará las HUs en curso (coding/reviewing) en pending para que puedas relanzarlas.\n\n¿Seguro?`,
231
- { title: 'Abortar kj run', okLabel: 'Stop', cancelLabel: 'Cancelar', destructive: true }
232
- );
233
- if (!ok) return;
234
- const summaries = [];
235
- for (const planId of planIds) {
236
- try {
237
- const res = await fetch(`/api/runs/${encodeURIComponent(planId)}/stop`, {
238
- method: 'POST', headers: { 'Content-Type': 'application/json' }, body: '{}',
239
- });
240
- const body = await res.json();
241
- if (!res.ok) summaries.push(`${planId}: error ${body.error || res.status}`);
242
- else summaries.push(`${planId}: ${body.stopped} SIGTERM, ${body.killed} SIGKILL, ${body.hu_reset_count || 0} HUs → pending`);
243
- } catch (err) {
244
- summaries.push(`${planId}: ${err.message}`);
245
- }
246
- }
247
- await fetch('/api/sync', { method: 'POST' }).catch(() => {});
248
- await renderBoard();
249
- if (summaries.some((s) => s.includes('SIGKILL') && !s.startsWith('0 ')) && summaries.some((s) => /\b[1-9]\d* SIGKILL/.test(s))) {
250
- await showError(
251
- `Algún proceso no respondió a SIGTERM en 5s y fue forzado con SIGKILL:\n\n${summaries.join('\n')}`,
252
- { title: 'Forzado con SIGKILL' }
253
- );
254
- }
255
- });
256
- }
152
+ // KJC-TSK-1351: the "⚙ N running" badge is a count. It used to open the log of a
153
+ // kj run, and ⏹ Stop killed it: with kj run retired an HU in progress is someone
154
+ // working on it, with no process or log of the board's to open or stop.
257
155
 
258
156
  // RAG search panel (KJC-PCS-0049 Step 8) — input + scope + render hits.
259
157
  const ragInput = document.getElementById('rag-query');
@@ -315,6 +213,25 @@ function renderKanbanColumn(title, cssClass, stories) {
315
213
  `;
316
214
  }
317
215
 
216
+ // KJC-TSK-1313: story id → the worktree lanes working it (GET /api/lanes/:project).
217
+ let lanesByStory = {};
218
+
219
+ /** KJC-TSK-1316: the lane's PR as a link (https only); a closed or merged one says so. */
220
+ function renderLanePr(pr) {
221
+ if (!pr || !/^https:\/\//.test(String(pr.url))) return '';
222
+ const state = pr.state === 'OPEN' ? '' : ` (${esc(String(pr.state).toLowerCase())})`;
223
+ return ` · <a href="${esc(pr.url)}" target="_blank" rel="noopener noreferrer" onclick="event.stopPropagation()">PR #${esc(String(pr.number))}</a>${state}`;
224
+ }
225
+
226
+ /** One badge per lane working the card: its name, branch and commits ahead of the base. */
227
+ function renderLaneBadges(story) {
228
+ return (lanesByStory[story.id] || []).map((l) => {
229
+ const ahead = typeof l.ahead === 'number' ? ` · ${l.ahead} ↑` : '';
230
+ const title = `carril ${l.slug} · ${l.path}`;
231
+ return `<span class="story-card__lane" title="${esc(title)}">🌿 ${esc(l.slug)} (${esc(l.branch)})${ahead}${renderLanePr(l.pr)}</span>`;
232
+ }).join('');
233
+ }
234
+
318
235
  /**
319
236
  * Renders a story card for the kanban board.
320
237
  * @param {object} story
@@ -345,36 +262,15 @@ function renderStoryCard(story) {
345
262
  // this on the card so the user edits the HU before trying to run.
346
263
  const missingTestContract = testCount === 0 && ['pending', 'certified'].includes(story.status);
347
264
 
348
- // PR4: per-HU ▶ Run button. Visible when:
349
- // - the HU is in a re-runnable lifecycle state (certified / failed /
350
- // pending / done — KJC-TSK-0394 step 2 adds `done` so the user can
351
- // replay a HU whose `result` was fail or partial without having to
352
- // "reset to pending" first) AND
353
- // - it has at least one acceptance test (otherwise the run would
354
- // refuse) AND
355
- // - it isn't currently coding/reviewing AND
356
- // - KJC-BUG-0048: it has no unresolved blocked_by deps. The card
357
- // already shows "⏳ waits for: ..." below the title; the ▶ button
358
- // must match that gating or the user can launch a HU whose deps
359
- // don't exist yet, hitting the missing-prereq path at runtime.
360
- const canRunHu = ['certified', 'failed', 'pending', 'done'].includes(story.status)
361
- && testCount > 0
362
- && !['coding', 'reviewing'].includes(story.status)
363
- && blockedBy.length === 0;
364
-
265
+ // KJC-TSK-1319: the per-HU ▶ launched kj run, retired in 5.0.
365
266
  return `
366
267
  <div class="story-card" data-story-id="${esc(story.id)}" data-status="${esc(story.status || 'pending')}" onclick="showStoryDetail('${esc(story.id)}')">
367
268
  <div class="story-card__id" title="${esc(story.id)}">
368
269
  ${esc(shortId)}
369
- ${canRunHu ? `
370
- <button class="story-card__run-btn"
371
- style="float:right;padding:1px 6px;font-size:0.7rem;background:var(--color-green);color:#fff;border:none;border-radius:var(--radius-sm);cursor:pointer;"
372
- title="Lanzar solo esta HU"
373
- onclick="event.stopPropagation(); window.runSingleHuFromCard('${esc(story.id)}', '${esc(title.replace(/'/g, '&#39;'))}');">▶</button>
374
- ` : ''}
375
270
  </div>
376
271
  <div class="story-card__title">${esc(truncate(title, 100))}</div>
377
272
  <div class="story-card__meta" style="gap:10px">
273
+ ${renderLaneBadges(story)}
378
274
  ${acCount > 0 ? `<span title="${acCount} acceptance criteria">📋 ${acCount} AC${acCount === 1 ? '' : 's'}</span>` : ''}
379
275
  ${testCount > 0 ? `<span title="${testCount} acceptance tests declared">✅ ${testCount} test${testCount === 1 ? '' : 's'}</span>` : ''}
380
276
  ${story.spec_section ? `<span title="Implements SPEC ${esc(story.spec_section)}" style="font-family:var(--font-mono, monospace)">📖 §${esc(story.spec_section)}</span>` : ''}
@@ -415,11 +311,20 @@ function renderStoryCard(story) {
415
311
  ${renderResultBadge(computeEffectiveResult(story))}
416
312
  <span class="story-card__time">${timeAgo(story.updated_at)}</span>
417
313
  ${renderOutcomeChip(story.outcome)}
314
+ ${renderPlayButton(story)}
418
315
  </div>
419
316
  </div>
420
317
  `;
421
318
  }
422
319
 
320
+ // KJC-TSK-1324: ▶ starts a pending card without a lane: kj opens its lane and the panel coder
321
+ // writes it there (kj hu start --code). Review and commit stay with the person.
322
+ function renderPlayButton(story) {
323
+ if (story.status !== 'pending' || (lanesByStory[story.id] || []).length > 0) return '';
324
+ const hint = 'Start: open its lane and have your coder write it';
325
+ return `<button type="button" class="story-card__play" title="${hint}" aria-label="${esc(`${hint} (${story.id})`)}" onclick="startCardLane('${esc(story.id)}', event)">▶</button>`;
326
+ }
327
+
423
328
  /**
424
329
  * KJC-TSK-0394 step 5: result "efectivo" para la UI. Sobre el campo
425
330
  * persistido `story.result` (si existe), o inferido del status legacy