@karajan-family/code 4.21.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 (746) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +98 -0
  3. package/bin/karajan-mcp.js +2 -0
  4. package/bin/kj-rag-mcp.js +75 -0
  5. package/bin/kj-tail +361 -0
  6. package/bin/kj.js +2 -0
  7. package/docs/README.es.md +80 -0
  8. package/docs/karajan-code-logo-small.png +0 -0
  9. package/library/logical-clocks-lamport.md +40 -0
  10. package/package.json +140 -0
  11. package/packages/ai-trash/README.md +37 -0
  12. package/packages/ai-trash/bin/kj-trash.js +10 -0
  13. package/packages/ai-trash/package.json +43 -0
  14. package/packages/ai-trash/src/cli.js +200 -0
  15. package/packages/ai-trash/src/destructive-parser.js +74 -0
  16. package/packages/ai-trash/src/git-snapshot.js +128 -0
  17. package/packages/ai-trash/src/hook.js +137 -0
  18. package/packages/ai-trash/src/index.js +14 -0
  19. package/packages/ai-trash/src/install.js +73 -0
  20. package/packages/ai-trash/src/logger.js +60 -0
  21. package/packages/ai-trash/src/manifest.js +91 -0
  22. package/packages/ai-trash/src/permissions.js +26 -0
  23. package/packages/ai-trash/src/snapshot.js +66 -0
  24. package/packages/ai-trash/src/ulid.js +66 -0
  25. package/packages/hu-board/README.md +69 -0
  26. package/packages/hu-board/package.json +28 -0
  27. package/packages/hu-board/public/app.js +259 -0
  28. package/packages/hu-board/public/index.html +77 -0
  29. package/packages/hu-board/public/pipeline.css +132 -0
  30. package/packages/hu-board/public/pipeline.html +48 -0
  31. package/packages/hu-board/public/pipeline.js +200 -0
  32. package/packages/hu-board/public/rag.html +21 -0
  33. package/packages/hu-board/public/rag.js +39 -0
  34. package/packages/hu-board/public/styles.css +973 -0
  35. package/packages/hu-board/public/utils/api.js +139 -0
  36. package/packages/hu-board/public/utils/board-view.js +473 -0
  37. package/packages/hu-board/public/utils/command-launcher.js +213 -0
  38. package/packages/hu-board/public/utils/config-editor.js +181 -0
  39. package/packages/hu-board/public/utils/dashboard-view.js +103 -0
  40. package/packages/hu-board/public/utils/formatters.js +300 -0
  41. package/packages/hu-board/public/utils/graph-view.js +216 -0
  42. package/packages/hu-board/public/utils/hu-actions.js +224 -0
  43. package/packages/hu-board/public/utils/init-listeners.js +180 -0
  44. package/packages/hu-board/public/utils/log-panel.js +222 -0
  45. package/packages/hu-board/public/utils/modals.js +309 -0
  46. package/packages/hu-board/public/utils/plan-rollup-view.js +53 -0
  47. package/packages/hu-board/public/utils/preflight-view.js +89 -0
  48. package/packages/hu-board/public/utils/project-actions.js +143 -0
  49. package/packages/hu-board/public/utils/project-picker-view.js +139 -0
  50. package/packages/hu-board/public/utils/run-launcher.js +169 -0
  51. package/packages/hu-board/public/utils/server-push.js +252 -0
  52. package/packages/hu-board/public/utils/sessions-view.js +199 -0
  53. package/packages/hu-board/public/utils/story-detail-view.js +324 -0
  54. package/packages/hu-board/public/utils/story-edit-form.js +271 -0
  55. package/packages/hu-board/public/wiki.html +32 -0
  56. package/packages/hu-board/public/wiki.js +53 -0
  57. package/packages/hu-board/src/auth.js +92 -0
  58. package/packages/hu-board/src/boot-info.js +16 -0
  59. package/packages/hu-board/src/cleanup-zombies.js +145 -0
  60. package/packages/hu-board/src/command-runner.js +133 -0
  61. package/packages/hu-board/src/config-yaml.js +531 -0
  62. package/packages/hu-board/src/db.js +963 -0
  63. package/packages/hu-board/src/ephemeral-cleaner.js +196 -0
  64. package/packages/hu-board/src/event-bus.js +58 -0
  65. package/packages/hu-board/src/format.js +207 -0
  66. package/packages/hu-board/src/hu-zombie-reaper.js +103 -0
  67. package/packages/hu-board/src/journal-parser.js +180 -0
  68. package/packages/hu-board/src/plan-mutations.js +424 -0
  69. package/packages/hu-board/src/preflight.js +286 -0
  70. package/packages/hu-board/src/routes/api.js +1374 -0
  71. package/packages/hu-board/src/routes/pipeline.js +45 -0
  72. package/packages/hu-board/src/routes/rag.js +42 -0
  73. package/packages/hu-board/src/routes/wiki.js +43 -0
  74. package/packages/hu-board/src/run-tracker.js +121 -0
  75. package/packages/hu-board/src/server.js +438 -0
  76. package/packages/hu-board/src/sync.js +732 -0
  77. package/packages/hu-board/src/token-store.js +56 -0
  78. package/packages/hu-board/src/zombie-reaper.js +206 -0
  79. package/scripts/ai-attribution-guard.yml +84 -0
  80. package/scripts/audit-test-diet.mjs +151 -0
  81. package/scripts/build-sea.mjs +150 -0
  82. package/scripts/diet-baseline.mjs +170 -0
  83. package/scripts/dual-publish.mjs +67 -0
  84. package/scripts/esbuild-sea.config.mjs +273 -0
  85. package/scripts/git-hooks/post-merge +18 -0
  86. package/scripts/install-binary.ps1 +174 -0
  87. package/scripts/install-binary.sh +159 -0
  88. package/scripts/install-guard-all-repos.sh +85 -0
  89. package/scripts/install-kj.sh +193 -0
  90. package/scripts/install.js +901 -0
  91. package/scripts/install.sh +7 -0
  92. package/scripts/postinstall.js +117 -0
  93. package/scripts/protect-all-repos.sh +88 -0
  94. package/scripts/quickstart-gate.mjs +97 -0
  95. package/scripts/regen-arch-stats.sh +71 -0
  96. package/scripts/setup-multi-instance.sh +150 -0
  97. package/scripts/verify-pack.mjs +278 -0
  98. package/src/activity-log.js +59 -0
  99. package/src/agents/agy-agent.js +62 -0
  100. package/src/agents/aider-agent.js +78 -0
  101. package/src/agents/availability.js +52 -0
  102. package/src/agents/base-agent.js +134 -0
  103. package/src/agents/claude-agent.js +413 -0
  104. package/src/agents/codex-agent.js +112 -0
  105. package/src/agents/copilot-agent.js +65 -0
  106. package/src/agents/gemini-agent.js +85 -0
  107. package/src/agents/host-agent.js +38 -0
  108. package/src/agents/index.js +59 -0
  109. package/src/agents/kimi-agent.js +45 -0
  110. package/src/agents/model-registry.js +137 -0
  111. package/src/agents/opencode-agent.js +72 -0
  112. package/src/agents/qwen-agent.js +20 -0
  113. package/src/agents/resolve-bin.js +73 -0
  114. package/src/audit/agent-readiness.js +259 -0
  115. package/src/audit/ai-slop-findings.js +102 -0
  116. package/src/audit/audit-fallback.js +66 -0
  117. package/src/audit/audit-history-display.js +61 -0
  118. package/src/audit/audit-history.js +102 -0
  119. package/src/audit/basal-cost.js +281 -0
  120. package/src/audit/circular-deps.js +144 -0
  121. package/src/audit/dead-exports.js +186 -0
  122. package/src/audit/deterministic-summary.js +327 -0
  123. package/src/audit/harness-scorecard.js +100 -0
  124. package/src/audit/harness-section.js +81 -0
  125. package/src/audit/infra-findings.js +100 -0
  126. package/src/audit/injection-findings.js +114 -0
  127. package/src/audit/issue-filter.js +133 -0
  128. package/src/audit/osv-findings.js +167 -0
  129. package/src/audit/plan-adherence.js +224 -0
  130. package/src/audit/semgrep-findings.js +129 -0
  131. package/src/audit/sonar-findings.js +93 -0
  132. package/src/audit/webperf-input.js +133 -0
  133. package/src/autonomy/arbiter-role.js +71 -0
  134. package/src/autonomy/arbiter.js +22 -0
  135. package/src/autonomy/decider.js +18 -0
  136. package/src/autonomy/decision-resolver.js +58 -0
  137. package/src/autonomy/outcome-report.js +55 -0
  138. package/src/autonomy/policy.js +41 -0
  139. package/src/autonomy/stage-ask.js +30 -0
  140. package/src/bootstrap.js +276 -0
  141. package/src/brain/agent-error-classifier.js +116 -0
  142. package/src/brain/decision-tracker.js +125 -0
  143. package/src/brain/decisor.js +243 -0
  144. package/src/brain/solomon-consult.js +171 -0
  145. package/src/brain/standby-scheduler.js +8 -0
  146. package/src/brain/standby-store.js +12 -0
  147. package/src/brain/with-brain-recovery.js +235 -0
  148. package/src/budget/comparison.js +96 -0
  149. package/src/canvas/parse-md.js +238 -0
  150. package/src/canvas/schema.js +101 -0
  151. package/src/canvas/to-flat.js +85 -0
  152. package/src/canvas/validate.js +163 -0
  153. package/src/checks/ai-surface.js +79 -0
  154. package/src/checks/ai-trash.js +34 -0
  155. package/src/checks/binaries.js +207 -0
  156. package/src/checks/ci.js +105 -0
  157. package/src/checks/config-files.js +217 -0
  158. package/src/checks/dir-setup.js +103 -0
  159. package/src/checks/guarantee-level.js +38 -0
  160. package/src/checks/hardware.js +150 -0
  161. package/src/checks/harness-scorecard.js +41 -0
  162. package/src/checks/mcp-health.js +169 -0
  163. package/src/checks/method.js +73 -0
  164. package/src/checks/native-build.js +67 -0
  165. package/src/checks/node.js +70 -0
  166. package/src/checks/ollama.js +41 -0
  167. package/src/checks/orphans.js +77 -0
  168. package/src/checks/ports.js +188 -0
  169. package/src/checks/project-checks.js +245 -0
  170. package/src/checks/qmd.js +41 -0
  171. package/src/checks/rag-hooks.js +54 -0
  172. package/src/checks/release-check.js +102 -0
  173. package/src/checks/rtk.js +36 -0
  174. package/src/checks/runner.js +326 -0
  175. package/src/checks/skills.js +53 -0
  176. package/src/checks/sonar.js +67 -0
  177. package/src/checks/squeezr.js +36 -0
  178. package/src/checks/system.js +53 -0
  179. package/src/checks/tokens.js +172 -0
  180. package/src/checks/types.js +103 -0
  181. package/src/ci/dispatch.js +99 -0
  182. package/src/ci/index.js +3 -0
  183. package/src/ci/pr-diff.js +26 -0
  184. package/src/ci/repo.js +45 -0
  185. package/src/cli/_shared.js +30 -0
  186. package/src/cli/advanced-commands.js +61 -0
  187. package/src/cli/register-meta.js +772 -0
  188. package/src/cli/register-pipeline.js +335 -0
  189. package/src/cli/register-plan.js +224 -0
  190. package/src/cli/register-roles-skills.js +54 -0
  191. package/src/cli/register-sonar.js +28 -0
  192. package/src/cli/register-standby.js +26 -0
  193. package/src/cli.js +155 -0
  194. package/src/commands/advanced.js +31 -0
  195. package/src/commands/agent-run.js +63 -0
  196. package/src/commands/agents.js +105 -0
  197. package/src/commands/architect.js +123 -0
  198. package/src/commands/audit.js +430 -0
  199. package/src/commands/autorun.js +49 -0
  200. package/src/commands/board.js +458 -0
  201. package/src/commands/check.js +40 -0
  202. package/src/commands/clean.js +145 -0
  203. package/src/commands/code.js +45 -0
  204. package/src/commands/config.js +74 -0
  205. package/src/commands/discover.js +105 -0
  206. package/src/commands/doctor.js +251 -0
  207. package/src/commands/env.js +207 -0
  208. package/src/commands/harden.js +203 -0
  209. package/src/commands/hu.js +210 -0
  210. package/src/commands/identity.js +60 -0
  211. package/src/commands/init.js +950 -0
  212. package/src/commands/install-tools.js +432 -0
  213. package/src/commands/mutate.js +94 -0
  214. package/src/commands/ollama.js +44 -0
  215. package/src/commands/onboard.js +45 -0
  216. package/src/commands/plan/_shared.js +55 -0
  217. package/src/commands/plan/add-hu.js +23 -0
  218. package/src/commands/plan/delete.js +9 -0
  219. package/src/commands/plan/fix.js +99 -0
  220. package/src/commands/plan/generate.js +532 -0
  221. package/src/commands/plan/index.js +13 -0
  222. package/src/commands/plan/list.js +21 -0
  223. package/src/commands/plan/migrate-result.js +41 -0
  224. package/src/commands/plan/ready.js +46 -0
  225. package/src/commands/plan/remove-hu.js +17 -0
  226. package/src/commands/plan/share.js +85 -0
  227. package/src/commands/plan/show.js +81 -0
  228. package/src/commands/plan/unshare.js +41 -0
  229. package/src/commands/plan/validate.js +20 -0
  230. package/src/commands/plan.js +17 -0
  231. package/src/commands/policy.js +164 -0
  232. package/src/commands/privacy.js +45 -0
  233. package/src/commands/qmd.js +86 -0
  234. package/src/commands/rag-mcp.js +18 -0
  235. package/src/commands/rag.js +173 -0
  236. package/src/commands/report-issue.js +77 -0
  237. package/src/commands/report.js +469 -0
  238. package/src/commands/researcher.js +54 -0
  239. package/src/commands/resume.js +68 -0
  240. package/src/commands/review-gate.js +355 -0
  241. package/src/commands/review.js +89 -0
  242. package/src/commands/roles.js +116 -0
  243. package/src/commands/run.js +185 -0
  244. package/src/commands/scan.js +18 -0
  245. package/src/commands/skills.js +86 -0
  246. package/src/commands/sonar.js +53 -0
  247. package/src/commands/standby.js +117 -0
  248. package/src/commands/start.js +121 -0
  249. package/src/commands/status.js +36 -0
  250. package/src/commands/sync.js +103 -0
  251. package/src/commands/telemetry.js +70 -0
  252. package/src/commands/tournament.js +85 -0
  253. package/src/commands/triage.js +78 -0
  254. package/src/commands/undo.js +62 -0
  255. package/src/commands/watch.js +46 -0
  256. package/src/commands/webperf.js +162 -0
  257. package/src/commands/worktree.js +76 -0
  258. package/src/config/defaults.js +270 -0
  259. package/src/config/loader.js +204 -0
  260. package/src/config/overrides.js +220 -0
  261. package/src/config/role-resolver.js +171 -0
  262. package/src/config/schema.js +299 -0
  263. package/src/config/test-harness.js +78 -0
  264. package/src/config.js +34 -0
  265. package/src/domains/domain-loader.js +245 -0
  266. package/src/domains/domain-registry.js +140 -0
  267. package/src/domains/domain-synthesizer.js +129 -0
  268. package/src/environment/adr.js +45 -0
  269. package/src/environment/board-access.js +97 -0
  270. package/src/environment/board-select.js +59 -0
  271. package/src/environment/briefs.js +129 -0
  272. package/src/environment/issue-report.js +72 -0
  273. package/src/environment/mcp-wiring.js +44 -0
  274. package/src/environment/playbook.js +127 -0
  275. package/src/git/automation.js +334 -0
  276. package/src/git/hu-automation.js +171 -0
  277. package/src/git/hu-snapshot.js +10 -0
  278. package/src/golden/asserter.js +92 -0
  279. package/src/golden/loader.js +52 -0
  280. package/src/golden/runner.js +92 -0
  281. package/src/golden/schema.js +41 -0
  282. package/src/guards/intent-guard.js +161 -0
  283. package/src/guards/output-guard.js +149 -0
  284. package/src/guards/perf-guard.js +126 -0
  285. package/src/guards/policy-guard.js +90 -0
  286. package/src/guards/policy-resolver.js +85 -0
  287. package/src/guards/secret-redactor.js +71 -0
  288. package/src/harden/advisory.js +203 -0
  289. package/src/harden/alternatives.js +50 -0
  290. package/src/harden/check.js +81 -0
  291. package/src/harden/config-engine.js +128 -0
  292. package/src/harden/config-templates.js +122 -0
  293. package/src/harden/guidelines-engine.js +48 -0
  294. package/src/harden/guidelines-templates.js +36 -0
  295. package/src/harden/harden-engine.js +80 -0
  296. package/src/harden/harness-hooks.js +107 -0
  297. package/src/harden/hook-commands.js +25 -0
  298. package/src/harden/hook-templates.js +183 -0
  299. package/src/harden/interactive.js +76 -0
  300. package/src/harden/sentinel-hooks.js +721 -0
  301. package/src/harden/stack-roots.js +105 -0
  302. package/src/harden/test-on-push.js +38 -0
  303. package/src/harden/workflow-engine.js +116 -0
  304. package/src/harden/workflow-templates.js +285 -0
  305. package/src/hu/acceptance-runner.js +138 -0
  306. package/src/hu/auto-generator.js +358 -0
  307. package/src/hu/graph.js +71 -0
  308. package/src/hu/lazy-planner.js +109 -0
  309. package/src/hu/model-router.js +108 -0
  310. package/src/hu/parallel-executor.js +94 -0
  311. package/src/hu/split-validator.js +143 -0
  312. package/src/hu/splitting-detector.js +158 -0
  313. package/src/hu/splitting-generator.js +175 -0
  314. package/src/hu/store.js +392 -0
  315. package/src/hu/worktree-bootstrap.js +59 -0
  316. package/src/identity/bootstrap.js +48 -0
  317. package/src/identity/compare.js +38 -0
  318. package/src/identity/detect.js +51 -0
  319. package/src/identity/store.js +66 -0
  320. package/src/infrastructure/command-runner.js +44 -0
  321. package/src/infrastructure/environment.js +38 -0
  322. package/src/infrastructure/file-system-service.js +63 -0
  323. package/src/infrastructure/mocks.js +182 -0
  324. package/src/lang/chunk-go.js +94 -0
  325. package/src/lang/chunk-java.js +90 -0
  326. package/src/lang/chunk-python.js +88 -0
  327. package/src/lang/chunk-rust.js +102 -0
  328. package/src/lang/go.js +14 -0
  329. package/src/lang/java.js +16 -0
  330. package/src/lang/js.js +8 -0
  331. package/src/lang/python.js +14 -0
  332. package/src/lang/registry.js +79 -0
  333. package/src/lang/rust.js +14 -0
  334. package/src/lang/tree-sitter-loader.js +45 -0
  335. package/src/mcp/direct-role-runner.js +112 -0
  336. package/src/mcp/error-classifiers.js +66 -0
  337. package/src/mcp/handlers/direct-handlers.js +486 -0
  338. package/src/mcp/handlers/hu-handlers.js +93 -0
  339. package/src/mcp/handlers/management-handlers.js +265 -0
  340. package/src/mcp/handlers/rag-handler.js +52 -0
  341. package/src/mcp/handlers/run-handler.js +313 -0
  342. package/src/mcp/orphan-guard.js +131 -0
  343. package/src/mcp/preflight.js +47 -0
  344. package/src/mcp/progress.js +149 -0
  345. package/src/mcp/response-compressor.js +81 -0
  346. package/src/mcp/run-kj.js +135 -0
  347. package/src/mcp/server-handlers.js +79 -0
  348. package/src/mcp/server.js +66 -0
  349. package/src/mcp/shared-helpers.js +297 -0
  350. package/src/mcp/sovereignty-guard.js +112 -0
  351. package/src/mcp/suggest-handler.js +92 -0
  352. package/src/mcp/tool-arg-normalizers.js +16 -0
  353. package/src/mcp/tools.js +432 -0
  354. package/src/mutate/diff-scope.js +105 -0
  355. package/src/mutate/reviewer-signal.js +76 -0
  356. package/src/mutate/runner.js +72 -0
  357. package/src/mutate/tool-registry.js +90 -0
  358. package/src/onboarder/cache.js +24 -0
  359. package/src/onboarder/collectors/framework-multilang.js +64 -0
  360. package/src/onboarder/collectors/index.js +136 -0
  361. package/src/orchestrator/agent-fallback.js +81 -0
  362. package/src/orchestrator/brain-coordinator.js +186 -0
  363. package/src/orchestrator/ci-integration.js +139 -0
  364. package/src/orchestrator/config-init.js +510 -0
  365. package/src/orchestrator/direct-actions.js +217 -0
  366. package/src/orchestrator/drivers/error-recovery.js +287 -0
  367. package/src/orchestrator/drivers/init-context.js +227 -0
  368. package/src/orchestrator/drivers/iteration-loop.js +380 -0
  369. package/src/orchestrator/drivers/iteration-phases/coder-and-refactorer.js +69 -0
  370. package/src/orchestrator/drivers/iteration-phases/guards.js +84 -0
  371. package/src/orchestrator/drivers/iteration-phases/handle-approved.js +29 -0
  372. package/src/orchestrator/drivers/iteration-phases/quality-gates.js +114 -0
  373. package/src/orchestrator/drivers/iteration-phases/reviewer-gate.js +60 -0
  374. package/src/orchestrator/drivers/post-loop.js +383 -0
  375. package/src/orchestrator/drivers/pre-loop-phases/auto-hu-batch.js +136 -0
  376. package/src/orchestrator/drivers/pre-loop-phases/config-deprecations.js +47 -0
  377. package/src/orchestrator/drivers/pre-loop-phases/ensure-addyosmani-skills.js +84 -0
  378. package/src/orchestrator/drivers/pre-loop-phases/inject-loaded-plan.js +391 -0
  379. package/src/orchestrator/drivers/pre-loop.js +507 -0
  380. package/src/orchestrator/drivers/run-hu-batch.js +427 -0
  381. package/src/orchestrator/feedback-enrichment.js +140 -0
  382. package/src/orchestrator/feedback-queue.js +151 -0
  383. package/src/orchestrator/flow-control.js +170 -0
  384. package/src/orchestrator/flow-runner.js +364 -0
  385. package/src/orchestrator/fs-leak-detector.js +210 -0
  386. package/src/orchestrator/hu-scheduler.js +114 -0
  387. package/src/orchestrator/hu-sub-pipeline.js +616 -0
  388. package/src/orchestrator/integrations.js +81 -0
  389. package/src/orchestrator/iteration-gate.js +73 -0
  390. package/src/orchestrator/iteration-stages.js +9 -0
  391. package/src/orchestrator/parallel-limiter.js +74 -0
  392. package/src/orchestrator/pipeline-context.js +72 -0
  393. package/src/orchestrator/post-loop-stages.js +468 -0
  394. package/src/orchestrator/pre-loop-stages.js +11 -0
  395. package/src/orchestrator/preflight-checks.js +462 -0
  396. package/src/orchestrator/repair/repair-detector.js +117 -0
  397. package/src/orchestrator/repair/repair-runner.js +142 -0
  398. package/src/orchestrator/reviewer-fallback.js +45 -0
  399. package/src/orchestrator/role-output-compressor.js +189 -0
  400. package/src/orchestrator/session-journal.js +308 -0
  401. package/src/orchestrator/solomon-escalation.js +305 -0
  402. package/src/orchestrator/solomon-rules.js +212 -0
  403. package/src/orchestrator/stages/architect-stage.js +172 -0
  404. package/src/orchestrator/stages/coder-stage.js +480 -0
  405. package/src/orchestrator/stages/domain-curator-stage.js +82 -0
  406. package/src/orchestrator/stages/hu-reviewer-phases/detect-and-apply-splits.js +127 -0
  407. package/src/orchestrator/stages/hu-reviewer-stage.js +394 -0
  408. package/src/orchestrator/stages/perf-stage.js +140 -0
  409. package/src/orchestrator/stages/planner-stage.js +85 -0
  410. package/src/orchestrator/stages/rag-context-stage.js +77 -0
  411. package/src/orchestrator/stages/rag-preload-decisor.js +58 -0
  412. package/src/orchestrator/stages/research-stage.js +69 -0
  413. package/src/orchestrator/stages/reviewer-stage.js +422 -0
  414. package/src/orchestrator/stages/sonar-stage.js +421 -0
  415. package/src/orchestrator/stages/stage-classes.js +84 -0
  416. package/src/orchestrator/stages/stage-executor.js +155 -0
  417. package/src/orchestrator/stages/tdd-discipline-stage.js +56 -0
  418. package/src/orchestrator/stages/tool-judge-stage.js +53 -0
  419. package/src/orchestrator/stages/triage-stage.js +172 -0
  420. package/src/orchestrator/standby.js +71 -0
  421. package/src/orchestrator/verification-gate.js +183 -0
  422. package/src/orchestrator.js +22 -0
  423. package/src/plan/adr-generator.js +170 -0
  424. package/src/plan/adr-loader.js +60 -0
  425. package/src/plan/plan-executor.js +100 -0
  426. package/src/plan/plan-fixer.js +192 -0
  427. package/src/plan/plan-hu-ops.js +16 -0
  428. package/src/plan/plan-id.js +4 -0
  429. package/src/plan/plan-migrate-result.js +24 -0
  430. package/src/plan/plan-reviewer.js +236 -0
  431. package/src/plan/plan-schema.js +334 -0
  432. package/src/plan/plan-store.js +260 -0
  433. package/src/plan/plan-structural-pass.js +122 -0
  434. package/src/plan/plan-validation.js +4 -0
  435. package/src/plan/preflight-hu.js +105 -0
  436. package/src/plan/spec-detector.js +61 -0
  437. package/src/plan/tests-synthesizer.js +131 -0
  438. package/src/planning-game/adapter.js +108 -0
  439. package/src/planning-game/architect-adrs.js +45 -0
  440. package/src/planning-game/client.js +108 -0
  441. package/src/planning-game/decomposition.js +87 -0
  442. package/src/planning-game/pipeline-adapter.js +251 -0
  443. package/src/plugins/loader.js +67 -0
  444. package/src/policy/add.js +111 -0
  445. package/src/policy/decisions.js +42 -0
  446. package/src/policy/engine.js +82 -0
  447. package/src/policy/exceptions.js +60 -0
  448. package/src/privacy/onboarding.js +35 -0
  449. package/src/privacy/scan.js +120 -0
  450. package/src/prompts/arbiter.js +38 -0
  451. package/src/prompts/architect.js +103 -0
  452. package/src/prompts/audit.js +474 -0
  453. package/src/prompts/coder.js +287 -0
  454. package/src/prompts/diff-clip.js +27 -0
  455. package/src/prompts/discover.js +222 -0
  456. package/src/prompts/hu-reviewer.js +219 -0
  457. package/src/prompts/planner.js +312 -0
  458. package/src/prompts/prompt-layout.js +80 -0
  459. package/src/prompts/prompt-resolver.js +120 -0
  460. package/src/prompts/reviewer.js +75 -0
  461. package/src/prompts/rtk-snippet.js +25 -0
  462. package/src/prompts/spec-reviewer.js +66 -0
  463. package/src/prompts/split-signal.js +82 -0
  464. package/src/prompts/start-decision.js +52 -0
  465. package/src/prompts/tool-correctness-judge.js +31 -0
  466. package/src/prompts/triage.js +57 -0
  467. package/src/rag/auto-update.js +72 -0
  468. package/src/rag/chunker-ast.js +56 -0
  469. package/src/rag/chunker.js +132 -0
  470. package/src/rag/embedder.js +2 -0
  471. package/src/rag/embedders/_cloud-base.js +2 -0
  472. package/src/rag/embedders/cohere.js +2 -0
  473. package/src/rag/embedders/factory.js +2 -0
  474. package/src/rag/embedders/mistral.js +2 -0
  475. package/src/rag/embedders/onnx.js +2 -0
  476. package/src/rag/embedders/openai.js +2 -0
  477. package/src/rag/embedders/voyage.js +2 -0
  478. package/src/rag/eval.js +61 -0
  479. package/src/rag/governed-embedder.js +39 -0
  480. package/src/rag/indexer.js +199 -0
  481. package/src/rag/library.js +62 -0
  482. package/src/rag/ollama-capability.js +59 -0
  483. package/src/rag/ollama-manager.js +98 -0
  484. package/src/rag/onnx-fallback.js +69 -0
  485. package/src/rag/rerank.js +4 -0
  486. package/src/rag/retriever.js +2 -0
  487. package/src/rag/vec-store.js +17 -0
  488. package/src/rag/watcher.js +62 -0
  489. package/src/rag/where-parser.js +4 -0
  490. package/src/repeat-detector.js +88 -0
  491. package/src/review/card-first.js +99 -0
  492. package/src/review/card-verify.js +83 -0
  493. package/src/review/diff-generator.js +112 -0
  494. package/src/review/gate-gitignore.js +77 -0
  495. package/src/review/git-stash-helper.js +104 -0
  496. package/src/review/mutation-pregate.js +48 -0
  497. package/src/review/one-shot-review.js +122 -0
  498. package/src/review/parser.js +80 -0
  499. package/src/review/policy-gate.js +26 -0
  500. package/src/review/profiles.js +66 -0
  501. package/src/review/reviewer-fallback.js +125 -0
  502. package/src/review/schema.js +31 -0
  503. package/src/review/scope-filter.js +152 -0
  504. package/src/review/snapshot-diff.js +122 -0
  505. package/src/review/solomon-arbitration.js +113 -0
  506. package/src/review/sonar-pregate.js +63 -0
  507. package/src/review/tdd-discipline.js +90 -0
  508. package/src/review/tdd-policy.js +69 -0
  509. package/src/review/tests-with-code.js +33 -0
  510. package/src/review/verdict-store.js +87 -0
  511. package/src/review/workspace.js +21 -0
  512. package/src/roles/agent-role.js +194 -0
  513. package/src/roles/architect-role.js +77 -0
  514. package/src/roles/audit-role.js +258 -0
  515. package/src/roles/base-role.js +165 -0
  516. package/src/roles/coder-role.js +107 -0
  517. package/src/roles/commiter-role.js +110 -0
  518. package/src/roles/discover-role.js +81 -0
  519. package/src/roles/domain-curator-role.js +141 -0
  520. package/src/roles/hu-reviewer-role.js +64 -0
  521. package/src/roles/impeccable-role.js +85 -0
  522. package/src/roles/index.js +18 -0
  523. package/src/roles/karajan-brain-role.js +134 -0
  524. package/src/roles/onboarder-role.js +61 -0
  525. package/src/roles/perf-role.js +122 -0
  526. package/src/roles/planner-role.js +151 -0
  527. package/src/roles/refactorer-role.js +30 -0
  528. package/src/roles/repairer-role.js +181 -0
  529. package/src/roles/researcher-role.js +70 -0
  530. package/src/roles/reviewer-role.js +104 -0
  531. package/src/roles/security-role.js +73 -0
  532. package/src/roles/solomon-role.js +265 -0
  533. package/src/roles/sonar-role.js +70 -0
  534. package/src/roles/spec-reviewer-role.js +106 -0
  535. package/src/roles/tester-role.js +235 -0
  536. package/src/roles/tool-correctness-judge-role.js +78 -0
  537. package/src/roles/triage-role.js +99 -0
  538. package/src/session/journal/decisions-writer.js +200 -0
  539. package/src/session/journal/iteration-logger.js +194 -0
  540. package/src/session/journal/summary-writer.js +320 -0
  541. package/src/session/journal/tree-writer.js +164 -0
  542. package/src/session/journal/write-all.js +89 -0
  543. package/src/session/mutators.js +339 -0
  544. package/src/session/runtime-overrides.js +59 -0
  545. package/src/session/store.js +106 -0
  546. package/src/session-cleanup.js +116 -0
  547. package/src/skills/addyosmani-catalog.js +315 -0
  548. package/src/skills/addyosmani-role-map.js +139 -0
  549. package/src/skills/openskills-client.js +157 -0
  550. package/src/skills/semantic-detector.js +167 -0
  551. package/src/skills/skill-detector.js +385 -0
  552. package/src/skills/skill-loader.js +219 -0
  553. package/src/skills/skills-cache.js +128 -0
  554. package/src/sonar/api.js +128 -0
  555. package/src/sonar/cloud-scanner.js +78 -0
  556. package/src/sonar/config-resolver.js +88 -0
  557. package/src/sonar/credentials.js +60 -0
  558. package/src/sonar/discovery.js +124 -0
  559. package/src/sonar/enforcer.js +19 -0
  560. package/src/sonar/issue-filter.js +12 -0
  561. package/src/sonar/manager.js +181 -0
  562. package/src/sonar/project-key.js +120 -0
  563. package/src/sonar/scanner.js +307 -0
  564. package/src/sonar/token-bootstrap.js +205 -0
  565. package/src/sonar/token-recovery.js +67 -0
  566. package/src/spec-review/refine-loop.js +58 -0
  567. package/src/spec-review/run-spec-review.js +119 -0
  568. package/src/start/assessment.js +74 -0
  569. package/src/start/maturity.js +73 -0
  570. package/src/start/start-decider-role.js +79 -0
  571. package/src/start/sweep.js +157 -0
  572. package/src/sync/apply-canvas-patch.js +55 -0
  573. package/src/sync/detect-drift.js +113 -0
  574. package/src/tournament/crown.js +88 -0
  575. package/src/tournament/judge.js +150 -0
  576. package/src/tournament/run.js +142 -0
  577. package/src/tournament/scoreboard.js +124 -0
  578. package/src/tracker-bootstrap.js +44 -0
  579. package/src/types/agent.js +55 -0
  580. package/src/types/config.js +114 -0
  581. package/src/types/hu.js +47 -0
  582. package/src/types/index.js +38 -0
  583. package/src/types/policy.js +43 -0
  584. package/src/types/session.js +75 -0
  585. package/src/types/solomon.js +87 -0
  586. package/src/types/stage.js +68 -0
  587. package/src/utils/agent-detect.js +93 -0
  588. package/src/utils/ai-trash.js +48 -0
  589. package/src/utils/async-lock.js +31 -0
  590. package/src/utils/atomic-write.js +6 -0
  591. package/src/utils/banner.js +66 -0
  592. package/src/utils/binary-sources.js +70 -0
  593. package/src/utils/board-prompt-bridge.js +108 -0
  594. package/src/utils/budget.js +229 -0
  595. package/src/utils/cli-ask-question.js +93 -0
  596. package/src/utils/cli-progress.js +220 -0
  597. package/src/utils/cli-run-log.js +83 -0
  598. package/src/utils/cwd-confirm.js +39 -0
  599. package/src/utils/derive-project-name-from-cwd.js +119 -0
  600. package/src/utils/display/event-handlers.js +403 -0
  601. package/src/utils/display/formatters.js +132 -0
  602. package/src/utils/display/header.js +47 -0
  603. package/src/utils/display/resume-hint.js +41 -0
  604. package/src/utils/display/session-summary.js +118 -0
  605. package/src/utils/display/solomon.js +42 -0
  606. package/src/utils/display/spec-findings.js +44 -0
  607. package/src/utils/doc-links.js +29 -0
  608. package/src/utils/docker-install.js +34 -0
  609. package/src/utils/escape-regexp.js +15 -0
  610. package/src/utils/events.js +35 -0
  611. package/src/utils/fs.js +19 -0
  612. package/src/utils/garbage-collector.js +481 -0
  613. package/src/utils/git.js +279 -0
  614. package/src/utils/home-migration.js +87 -0
  615. package/src/utils/injection-guard.js +186 -0
  616. package/src/utils/install-hints.js +180 -0
  617. package/src/utils/json-extract.js +64 -0
  618. package/src/utils/locale.js +59 -0
  619. package/src/utils/logger.js +96 -0
  620. package/src/utils/managed-markers.js +93 -0
  621. package/src/utils/manifest-sniff.js +82 -0
  622. package/src/utils/messages.js +61 -0
  623. package/src/utils/model-selector.js +107 -0
  624. package/src/utils/os-detect.js +79 -0
  625. package/src/utils/paths.js +13 -0
  626. package/src/utils/pending-user-action.js +95 -0
  627. package/src/utils/port-check.js +6 -0
  628. package/src/utils/port-occupant.js +96 -0
  629. package/src/utils/pricing.js +29 -0
  630. package/src/utils/process.js +4 -0
  631. package/src/utils/project-detect.js +160 -0
  632. package/src/utils/prompt-project-name.js +35 -0
  633. package/src/utils/provider-env.js +116 -0
  634. package/src/utils/qmd-collection.js +72 -0
  635. package/src/utils/qmd-detect.js +18 -0
  636. package/src/utils/qmd-install.js +37 -0
  637. package/src/utils/quota-simulator.js +56 -0
  638. package/src/utils/rate-limit-detector.js +181 -0
  639. package/src/utils/repo-cleaner.js +79 -0
  640. package/src/utils/retry.js +88 -0
  641. package/src/utils/role-assigner.js +137 -0
  642. package/src/utils/role-env.js +58 -0
  643. package/src/utils/rtk-detect.js +18 -0
  644. package/src/utils/rtk-install.js +38 -0
  645. package/src/utils/rtk-wrapper.js +87 -0
  646. package/src/utils/run-context.js +75 -0
  647. package/src/utils/run-log.js +217 -0
  648. package/src/utils/run-registry.js +9 -0
  649. package/src/utils/shared-paths.js +12 -0
  650. package/src/utils/squeezr-detect.js +21 -0
  651. package/src/utils/squeezr-install.js +37 -0
  652. package/src/utils/stack-detect.js +135 -0
  653. package/src/utils/stall-detector.js +159 -0
  654. package/src/utils/status-dashboard.js +254 -0
  655. package/src/utils/task-file.js +109 -0
  656. package/src/utils/telemetry.js +124 -0
  657. package/src/utils/templates-root.js +61 -0
  658. package/src/utils/tool-call-extractor.js +56 -0
  659. package/src/utils/tool-governor.js +116 -0
  660. package/src/utils/tool-installer.js +93 -0
  661. package/src/utils/update-check.js +280 -0
  662. package/src/utils/vector-store-orphans.js +89 -0
  663. package/src/utils/welcome.js +47 -0
  664. package/src/utils/with-timeout.js +42 -0
  665. package/src/utils/wizard.js +47 -0
  666. package/src/webperf/cwv-gate.js +94 -0
  667. package/src/webperf/devtools-detect.js +112 -0
  668. package/src/webperf/scanner.js +209 -0
  669. package/src/webperf/webperf-role-integration.js +66 -0
  670. package/templates/coder-rules.md +45 -0
  671. package/templates/docker-compose.sonar.yml +60 -0
  672. package/templates/domains/example-dental.md +57 -0
  673. package/templates/kj.config.yml +128 -0
  674. package/templates/review-rules.md +17 -0
  675. package/templates/roles/architect/anthropic.md +74 -0
  676. package/templates/roles/architect/default.md +72 -0
  677. package/templates/roles/architect/google.md +61 -0
  678. package/templates/roles/architect/openai.md +60 -0
  679. package/templates/roles/architect/opencode.md +59 -0
  680. package/templates/roles/architect.md +76 -0
  681. package/templates/roles/audit.md +68 -0
  682. package/templates/roles/coder/anthropic.md +81 -0
  683. package/templates/roles/coder/default.md +78 -0
  684. package/templates/roles/coder/google.md +87 -0
  685. package/templates/roles/coder/openai.md +68 -0
  686. package/templates/roles/coder/opencode.md +58 -0
  687. package/templates/roles/coder.md +152 -0
  688. package/templates/roles/commiter.md +44 -0
  689. package/templates/roles/discover.md +167 -0
  690. package/templates/roles/domain-curator.md +52 -0
  691. package/templates/roles/hu-reviewer.md +236 -0
  692. package/templates/roles/impeccable-design.md +87 -0
  693. package/templates/roles/impeccable.md +125 -0
  694. package/templates/roles/karajan-brain.md +95 -0
  695. package/templates/roles/onboarder.md +45 -0
  696. package/templates/roles/planner/anthropic.md +59 -0
  697. package/templates/roles/planner/default.md +57 -0
  698. package/templates/roles/planner/google.md +75 -0
  699. package/templates/roles/planner/openai.md +55 -0
  700. package/templates/roles/planner/opencode.md +62 -0
  701. package/templates/roles/planner.md +61 -0
  702. package/templates/roles/refactorer.md +48 -0
  703. package/templates/roles/repairer.md +103 -0
  704. package/templates/roles/researcher.md +41 -0
  705. package/templates/roles/reviewer/anthropic.md +61 -0
  706. package/templates/roles/reviewer/default.md +62 -0
  707. package/templates/roles/reviewer/google.md +63 -0
  708. package/templates/roles/reviewer/openai.md +57 -0
  709. package/templates/roles/reviewer/opencode.md +42 -0
  710. package/templates/roles/reviewer-paranoid.md +38 -0
  711. package/templates/roles/reviewer-relaxed.md +34 -0
  712. package/templates/roles/reviewer-strict.md +37 -0
  713. package/templates/roles/reviewer.md +62 -0
  714. package/templates/roles/security.md +54 -0
  715. package/templates/roles/solomon/anthropic.md +71 -0
  716. package/templates/roles/solomon/default.md +83 -0
  717. package/templates/roles/solomon/google.md +61 -0
  718. package/templates/roles/solomon/openai.md +53 -0
  719. package/templates/roles/solomon/opencode.md +55 -0
  720. package/templates/roles/solomon.md +83 -0
  721. package/templates/roles/sonar.md +49 -0
  722. package/templates/roles/spec-reviewer.md +47 -0
  723. package/templates/roles/tester.md +50 -0
  724. package/templates/roles/triage.md +69 -0
  725. package/templates/skills/kj-architect.md +45 -0
  726. package/templates/skills/kj-audit.md +63 -0
  727. package/templates/skills/kj-board.md +16 -0
  728. package/templates/skills/kj-code.md +51 -0
  729. package/templates/skills/kj-csv-transform.md +40 -0
  730. package/templates/skills/kj-data-report.md +44 -0
  731. package/templates/skills/kj-discover.md +24 -0
  732. package/templates/skills/kj-rag-query.md +50 -0
  733. package/templates/skills/kj-review.md +47 -0
  734. package/templates/skills/kj-run.md +73 -0
  735. package/templates/skills/kj-security.md +49 -0
  736. package/templates/skills/kj-sonar.md +41 -0
  737. package/templates/skills/kj-sql-analysis.md +40 -0
  738. package/templates/skills/kj-test.md +40 -0
  739. package/templates/workflows/automerge.yml +30 -0
  740. package/templates/workflows/houston-override.yml +46 -0
  741. package/templates/workflows/kj-ci-gateway.yml +58 -0
  742. package/vendor/tree-sitter-grammars/README.md +21 -0
  743. package/vendor/tree-sitter-grammars/go.wasm +0 -0
  744. package/vendor/tree-sitter-grammars/java.wasm +0 -0
  745. package/vendor/tree-sitter-grammars/python.wasm +0 -0
  746. package/vendor/tree-sitter-grammars/rust.wasm +0 -0
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Extracted from `src/orchestrator/drivers/pre-loop.js` in TSK-0337
3
+ * (audit recommendation #6). Previously an internal helper inside
4
+ * pre-loop.js; moved here so the driver stays under 600 LOC.
5
+ * Behaviour unchanged.
6
+ *
7
+ * Auto-generates an HU batch from triage's decomposition recommendation
8
+ * when no manual `huFile` is present. Runs after researcher/architect/
9
+ * planner so context is available for better HU generation. Sets
10
+ * `stageResults.huReviewer` so `needsSubPipeline` picks it up later.
11
+ *
12
+ * Side effects:
13
+ * - Persists `batch.json` to `~/.karajan/hu-stories/<batchSessionId>/`
14
+ * so the sub-pipeline can update story status via `saveHuBatch`.
15
+ * - Auto-starts the HU board (skipped under VITEST / NODE_ENV=test).
16
+ */
17
+
18
+ import { emitProgress, makeEvent } from "#utils/events.js";
19
+ import { detectProjectStack } from "#utils/stack-detect.js";
20
+
21
+ export async function maybeGenerateAutoHuBatch({
22
+ flags, stageResults, task, logger, emitter, eventBase, projectDir, session,
23
+ }) {
24
+ // Skip if user passed a manual hu-file
25
+ if (flags?.huFile) return;
26
+ // Skip if hu-reviewer already produced a batch (manual enable + PG stories)
27
+ if (stageResults.huReviewer) return;
28
+ // Need triage decomposition recommendation
29
+ const shouldDecompose = stageResults.triage?.shouldDecompose;
30
+ const subtasks = stageResults.triage?.subtasks;
31
+ if (!shouldDecompose || !Array.isArray(subtasks) || subtasks.length < 2) return;
32
+
33
+ const { generateHuBatch } = await import("#hu/auto-generator.js");
34
+
35
+ // Detect if project is new: empty dir or only .git/.karajan/.gitignore
36
+ let isNewProject = false;
37
+ try {
38
+ const fs = await import("node:fs/promises");
39
+ const entries = await fs.readdir(projectDir);
40
+ const relevant = entries.filter(e => !e.startsWith(".git") && e !== ".karajan" && e !== ".gitignore");
41
+ isNewProject = relevant.length === 0;
42
+ } catch { /* ignore */ }
43
+
44
+ // Stack hints from planner + architect text. Now multi-ecosystem
45
+ // (was 13/16 JS keywords — Python/Go/Rust barely existed). Combined
46
+ // with `detectProjectStack(projectDir)` below to override the text
47
+ // heuristic with the actual filesystem reality when available.
48
+ const stackHints = [];
49
+ const combined = `${stageResults.planner?.plan || ""} ${stageResults.architect?.architecture ? JSON.stringify(stageResults.architect.architecture) : ""} ${task}`.toLowerCase();
50
+ const stackKeywords = [
51
+ "express", "vite", "vitest", "jest", "next", "astro", "react", "vue", "svelte", "nestjs", "monorepo", "workspaces",
52
+ "pytest", "flask", "fastapi", "django", "numpy", "pandas",
53
+ "gin", "fiber", "go",
54
+ "cargo", "rust",
55
+ "spring",
56
+ ];
57
+ for (const kw of stackKeywords) {
58
+ if (combined.includes(kw)) stackHints.push(kw);
59
+ }
60
+
61
+ // Filesystem trumps text. detectProjectStack inspects package.json /
62
+ // pyproject.toml / go.mod / Cargo.toml, so a pure-python repo gets
63
+ // `language: "python"` regardless of what the task text says.
64
+ let detectedLanguage = null;
65
+ try {
66
+ const fsStack = await detectProjectStack(projectDir);
67
+ if (fsStack?.language) detectedLanguage = fsStack.language;
68
+ } catch { /* best-effort */ }
69
+
70
+ const batch = generateHuBatch({
71
+ originalTask: task,
72
+ subtasks,
73
+ stackHints,
74
+ isNewProject,
75
+ language: detectedLanguage,
76
+ researcherContext: stageResults.researcher?.summary || null,
77
+ architectContext: stageResults.architect?.architecture ? JSON.stringify(stageResults.architect.architecture) : null
78
+ });
79
+
80
+ // Persist batch to HU store so hu-sub-pipeline can update story status via saveHuBatch.
81
+ // Use session.id as batchSessionId.
82
+ const batchSessionId = `auto-${session.id}`;
83
+ try {
84
+ const fs = await import("node:fs/promises");
85
+ const path = await import("node:path");
86
+ const { getKarajanHome } = await import("#utils/paths.js");
87
+ const huDir = path.join(getKarajanHome(), "hu-stories", batchSessionId);
88
+ await fs.mkdir(huDir, { recursive: true });
89
+ const persistBatch = {
90
+ session_id: batchSessionId,
91
+ project_id: batchSessionId,
92
+ project_name: batch.projectName,
93
+ created_at: new Date().toISOString(),
94
+ updated_at: new Date().toISOString(),
95
+ stories: batch.stories
96
+ };
97
+ await fs.writeFile(path.join(huDir, "batch.json"), JSON.stringify(persistBatch, null, 2));
98
+ } catch (err) {
99
+ logger.warn(`Auto-HU: failed to persist batch (${err.message}) — sub-pipeline will use in-memory fallback`);
100
+ }
101
+
102
+ // Wrap in format compatible with needsSubPipeline + runHuSubPipeline
103
+ stageResults.huReviewer = {
104
+ ok: true,
105
+ stories: batch.stories,
106
+ total: batch.total,
107
+ certified: batch.certified,
108
+ batchSessionId,
109
+ auto_generated: true,
110
+ source: batch.source
111
+ };
112
+
113
+ logger.info(`Auto-HU: generated ${batch.total} stories (${batch.source.triage_subtasks} subtasks${isNewProject ? ", new project" : ""}${stackHints.length ? `, stack: ${stackHints.join(",")}` : ""})`);
114
+ emitProgress(emitter, makeEvent("hu:auto-generated", { ...eventBase, stage: "hu-auto-gen" }, {
115
+ message: `Auto-generated ${batch.total} HU(s) from triage decomposition`,
116
+ detail: { total: batch.total, subtasks: batch.source.triage_subtasks, isNewProject, stackHints, projectName: batch.projectName }
117
+ }));
118
+
119
+ // Auto-start the board so the user can see the generated HUs.
120
+ // Always fires when auto-HU runs, independent of hu_board.auto_start flag.
121
+ // Never during vitest — would race the PID file and leave a detached
122
+ // node process around after the suite (TSK-0273).
123
+ if (process.env.VITEST || process.env.NODE_ENV === "test") return;
124
+ try {
125
+ const { startBoard, renderBoardBanner } = await import("../../../commands/board.js");
126
+ const desiredPort = session.config_snapshot?.hu_board?.port ?? 4000;
127
+ const boardResult = await startBoard(desiredPort);
128
+ const url = boardResult.url;
129
+ const status = boardResult.alreadyRunning ? "already running" : "started";
130
+ const projectName = batch.projectName || "Auto-generated HUs";
131
+ console.log(renderBoardBanner({ url, status, projectName }));
132
+ logger.info(`HU Board ${status} at ${url} (project: ${projectName})`);
133
+ } catch (err) {
134
+ logger.warn(`HU Board auto-start failed (non-blocking): ${err.message}`);
135
+ }
136
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Extracted from `src/orchestrator/drivers/pre-loop.js` in TSK-0337
3
+ * (audit recommendation #6: continue pre-loop extraction). pre-loop.js
4
+ * had grown to 626 LOC, over the 600-LOC ceiling that the file's own
5
+ * docstring set. Splitting the deprecation warnings, addyosmani skill
6
+ * bootstrap, and auto-HU batch generation into their own modules brings
7
+ * the driver back to ~440 LOC.
8
+ *
9
+ * Behaviour unchanged. The function is exported with the same name and
10
+ * signature so the move is mechanical.
11
+ *
12
+ * Emits one-shot warnings for config keys ignored since v2.7.4. Currently:
13
+ * - `sonarqube.enabled` (kj.config.yml) → ignored, sonar is intrinsic
14
+ * - `--no-sonar` CLI flag → ignored
15
+ */
16
+
17
+ import { emitProgress, makeEvent } from "#utils/events.js";
18
+
19
+ export function emitConfigDeprecations(config, logger, emitter, eventBase) {
20
+ const dep = config?._deprecated;
21
+ if (!dep) return;
22
+
23
+ if (dep.sonarqubeEnabledKey) {
24
+ const m =
25
+ "DEPRECATED: `sonarqube.enabled` in kj.config.yml is ignored since v2.7.4. " +
26
+ "Sonar is intrinsic to Karajan for code tasks (sw/refactor/add-tests) and " +
27
+ "skipped for non-code tasks (audit/doc/infra/analysis/no-code) by policy. " +
28
+ "Remove the key from your config to silence this warning.";
29
+ logger.warn(m);
30
+ emitProgress(emitter, makeEvent("config:deprecated", { ...eventBase, stage: "config" }, {
31
+ message: m,
32
+ detail: { key: "sonarqube.enabled", since: "v2.7.4" },
33
+ }));
34
+ }
35
+
36
+ if (dep.noSonarFlag) {
37
+ const m =
38
+ "DEPRECATED: `--no-sonar` flag is ignored since v2.7.4. Sonar runs for code " +
39
+ "tasks by policy. To skip Sonar on a one-off run, use a non-code task type " +
40
+ "(e.g. `--task-type doc`) or rely on Solomon's runtime override.";
41
+ logger.warn(m);
42
+ emitProgress(emitter, makeEvent("config:deprecated", { ...eventBase, stage: "config" }, {
43
+ message: m,
44
+ detail: { flag: "--no-sonar", since: "v2.7.4" },
45
+ }));
46
+ }
47
+ }
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Extracted from `src/orchestrator/drivers/pre-loop.js` in TSK-0337
3
+ * (audit recommendation #6). Previously a 60-line export inside
4
+ * pre-loop.js; moved here to keep that driver under the 600-LOC
5
+ * ceiling its own docstring set. Behaviour and signature unchanged.
6
+ *
7
+ * Refreshes the addyosmani/agent-skills catalog (clones or pulls,
8
+ * gated by `refreshDays`) and resolves which slugs are relevant to
9
+ * the current task. Sets the result on the session via the
10
+ * `setAddyosmaniSkills` mutator so downstream stages can read it
11
+ * without re-running git.
12
+ *
13
+ * Failures are non-blocking: a missing git, a network blip, or an
14
+ * unparseable catalog all just mark the catalog unavailable and
15
+ * emit a `skills:addyosmani-unavailable` event. The pipeline keeps
16
+ * going — the addyosmani sources are an optional supplement.
17
+ */
18
+
19
+ import {
20
+ refreshIfStale as refreshAddyosmaniCatalog,
21
+ listAvailableSlugs as listAddyosmaniSlugs,
22
+ } from "#skills/addyosmani-catalog.js";
23
+ import { resolveAddyosmaniSlugs } from "#skills/addyosmani-role-map.js";
24
+ import { setAddyosmaniSkills } from "#session/mutators.js";
25
+ import { emitProgress, makeEvent } from "#utils/events.js";
26
+
27
+ export async function ensureAddyosmaniSkills({ task, config, logger, session, emitter, eventBase }) {
28
+ const skillsConfig = config?.skills || {};
29
+ const sources = Array.isArray(skillsConfig.sources) ? skillsConfig.sources : ["addyosmani", "openskills", "local"];
30
+ const addyConfig = skillsConfig.addyosmani || {};
31
+ // Test harness override: config.testHarness.defaultAddyosmaniEnabled=false
32
+ // prevents orchestrator tests from spawning git. Tests that need the real
33
+ // catalog opt in by setting config.skills.addyosmani.enabled = true.
34
+ // Post-v2.7.5 no longer reads globalThis directly — config.testHarness is
35
+ // populated by the loader from the global or the production default.
36
+ const harnessDefault = config?.testHarness?.defaultAddyosmaniEnabled;
37
+ const enabledFromConfig = addyConfig.enabled === true
38
+ || (addyConfig.enabled !== false && harnessDefault !== false);
39
+ const enabled = enabledFromConfig && sources.includes("addyosmani");
40
+ if (!enabled) return;
41
+
42
+ const refreshDays = Number.isFinite(addyConfig.refreshDays) ? addyConfig.refreshDays : 7;
43
+ const refreshMs = Math.max(0, refreshDays) * 24 * 60 * 60 * 1000;
44
+
45
+ try {
46
+ const refreshResult = await refreshAddyosmaniCatalog({
47
+ refreshMs,
48
+ repoUrl: addyConfig.repoUrl,
49
+ logger,
50
+ });
51
+
52
+ if (!refreshResult.ok) {
53
+ setAddyosmaniSkills(session, { available: false, reason: refreshResult.error || "refresh failed" });
54
+ emitProgress(emitter, makeEvent("skills:addyosmani-unavailable", { ...eventBase, stage: "skills" }, {
55
+ message: `addyosmani/agent-skills catalog unavailable: ${refreshResult.error || refreshResult.action}`,
56
+ detail: { action: refreshResult.action, hint: "Install git to enable process skills from addyosmani/agent-skills" },
57
+ }));
58
+ return;
59
+ }
60
+
61
+ const available = new Set(await listAddyosmaniSlugs());
62
+ const resolved = resolveAddyosmaniSlugs({ role: null, task }); // role resolution happens per-stage later
63
+ const valid = resolved.filter((slug) => available.has(slug));
64
+
65
+ setAddyosmaniSkills(session, {
66
+ available: true,
67
+ action: refreshResult.action,
68
+ resolvedSlugs: valid,
69
+ allAvailable: Array.from(available),
70
+ });
71
+
72
+ emitProgress(emitter, makeEvent("skills:addyosmani-ready", { ...eventBase, stage: "skills" }, {
73
+ message: `addyosmani/agent-skills ${refreshResult.action} — ${available.size} slug(s) available, ${valid.length} relevant to task`,
74
+ detail: {
75
+ action: refreshResult.action,
76
+ relevantSlugs: valid,
77
+ availableCount: available.size,
78
+ },
79
+ }));
80
+ } catch (err) {
81
+ logger.warn(`addyosmani catalog step failed (non-blocking): ${err.message}`);
82
+ setAddyosmaniSkills(session, { available: false, reason: err.message });
83
+ }
84
+ }
@@ -0,0 +1,391 @@
1
+ /**
2
+ * Pre-loop phase: load a persisted plan and inject it into stageResults.
3
+ *
4
+ * Extracted from `src/orchestrator/drivers/pre-loop.js` in PR-K (audit
5
+ * follow-up) to drag runPreLoopStages out of god-function territory
6
+ * (was 496 LOC; extracting this 189-LOC block brings it to ~310).
7
+ *
8
+ * Behaviour is byte-for-byte identical to the inlined version. The
9
+ * extraction keeps the dynamic-import pattern (deferred load of
10
+ * plan-store / plan-schema / plan-executor / plan-hu-ops) so the
11
+ * dynamic-imports budget test stays unchanged.
12
+ *
13
+ * Returns { handled, plannedTask }:
14
+ * - handled=true → caller short-circuits (plan was loaded; skip
15
+ * researcher/architect/planner phases below).
16
+ * - handled=false → caller continues with the regular planning flow.
17
+ * Either flags.plan was unset OR loadPlan returned null OR the
18
+ * try/catch swallowed an error (logged as a warning).
19
+ *
20
+ * The function still mutates session via the existing mutators
21
+ * (setPlanRef, setPlanSyncCallback, setLiveStatusUpdater,
22
+ * setLiveOutcomeUpdater, setPreLoopContext) and stageResults
23
+ * (researcher / architect / planner / huReviewer slots). That
24
+ * mutation contract is unchanged from the inlined version — see
25
+ * tests/architecture/session-write-boundary.test.js for the
26
+ * mutator-routing invariant.
27
+ */
28
+
29
+ import { saveSession } from "#session/store.js";
30
+ import {
31
+ setPlanRef, setPlanSyncCallback, setLiveStatusUpdater,
32
+ setLiveOutcomeUpdater, setPreLoopContext,
33
+ } from "#session/mutators.js";
34
+ import { emitProgress, makeEvent } from "#utils/events.js";
35
+ import { listActiveRuns } from "../../../utils/run-registry.js";
36
+
37
+ /**
38
+ * @param {object} args
39
+ * @param {object} args.flags — runFlow flags (reads flags.plan, flags.hu)
40
+ * @param {object} args.updatedConfig — the running config (reads projectDir)
41
+ * @param {object} args.session
42
+ * @param {object} args.stageResults — mutated: researcher / architect / planner / huReviewer slots
43
+ * @param {object} args.eventBase
44
+ * @param {object} args.emitter
45
+ * @param {object} args.logger
46
+ * @param {string} args.task — original task description, used to build plannedTask
47
+ * @returns {Promise<{handled: boolean, plannedTask: string|null}>}
48
+ */
49
+ export async function injectLoadedPlan({ flags, updatedConfig, session, stageResults, eventBase, emitter, logger, task }) {
50
+ if (!flags.plan) return { handled: false, plannedTask: null };
51
+
52
+ try {
53
+ const { loadPlan, savePlan: savePlanToDisk } = await import("../../../plan/plan-store.js");
54
+ const { isPlanV2 } = await import("../../../plan/plan-schema.js");
55
+ const { planToHuBatch, syncResultsToPlan } = await import("../../../plan/plan-executor.js");
56
+ const projectDir = updatedConfig.projectDir || process.cwd();
57
+ const loadedPlan = await loadPlan(projectDir, flags.plan);
58
+ if (!loadedPlan) {
59
+ logger.warn(`Plan ${flags.plan} not found — falling back to normal pipeline`);
60
+ return { handled: false, plannedTask: null };
61
+ }
62
+
63
+ logger.info(`Loaded persisted plan: ${flags.plan} (v${loadedPlan.version || 1})`);
64
+ emitProgress(emitter, makeEvent("plan:loaded", { ...eventBase, stage: "plan" }, {
65
+ message: `Plan loaded: ${flags.plan}`,
66
+ detail: { planId: flags.plan, task: loadedPlan.task, version: loadedPlan.version, huCount: loadedPlan.hus?.length || 0 }
67
+ }));
68
+
69
+ // Zombie reconciliation: a previous run may have died mid-flight,
70
+ // leaving HUs stuck in coding/reviewing/running. The board-side
71
+ // reaper only runs inside the board; without this, a headless
72
+ // `kj run --plan` would refuse to pick those HUs up.
73
+ const reaped = await reconcilePlanHuZombies({ loadedPlan, planId: flags.plan, logger });
74
+ if (reaped > 0) {
75
+ await savePlanToDisk(projectDir, loadedPlan);
76
+ emitProgress(emitter, makeEvent("plan:zombies-reaped", { ...eventBase, stage: "plan" }, {
77
+ message: `Reset ${reaped} stale HU(s) from a previous run that did not finish`,
78
+ detail: { planId: flags.plan, count: reaped }
79
+ }));
80
+ }
81
+ stageResults.researcher = { ok: true, summary: "Loaded from persisted plan", fromPlan: flags.plan };
82
+ stageResults.architect = { ok: true, summary: "Loaded from persisted plan", fromPlan: flags.plan };
83
+ stageResults.planner = { ok: true, summary: "Loaded from persisted plan", fromPlan: flags.plan };
84
+
85
+ // Tests-first gate (v2.7.5): refuse to run a plan when any HU
86
+ // has no acceptance_tests. Mirrors the gate in `kj plan ready`
87
+ // so the CLI path and the board's "▶ Run plan" button enforce
88
+ // the same contract.
89
+ if (isPlanV2(loadedPlan) && Array.isArray(loadedPlan.hus)) {
90
+ assertEveryHuHasTests({ loadedPlan, planId: flags.plan, eventBase, emitter, logger });
91
+
92
+ // PR-E (auto-certify + anomaly guards): the user expects
93
+ // pending HUs to be runnable; "certified" was an invisible
94
+ // wall. Helpers in plan-hu-ops.js so they can be unit-tested
95
+ // without spinning up the orchestrator. Pulled from the same
96
+ // dynamic import we use a few lines below for updateHuStatus +
97
+ // setHuOutcome — no extra import budget.
98
+ const planHuOps = await import("../../../plan/plan-hu-ops.js");
99
+ await autoCertifyAndAssertRunnable({ planHuOps, loadedPlan, planId: flags.plan, projectDir, savePlanToDisk, eventBase, emitter, logger });
100
+
101
+ // v2 plan with HUs → inject as huReviewer so sub-pipeline
102
+ // picks them up. Sets every callback (sync, live status, live
103
+ // outcome) and stamps plan.status = "running".
104
+ if (loadedPlan.hus?.length > 0) {
105
+ const huBatch = planToHuBatch(loadedPlan);
106
+ applyHuFilterIfRequested({ huBatch, flags, eventBase, emitter, logger });
107
+ if (huBatch.ok) {
108
+ stageResults.huReviewer = huBatch;
109
+ wirePlanCallbacks({ session, loadedPlan, projectDir, syncResultsToPlan, savePlanToDisk, planHuOps, logger });
110
+ logger.info(`Plan ${flags.plan}: ${huBatch.certified} HUs ready for execution`);
111
+ emitProgress(emitter, makeEvent("plan:hus-loaded", { ...eventBase, stage: "plan" }, {
112
+ message: `${huBatch.certified} HUs loaded from plan`,
113
+ detail: { planId: flags.plan, total: huBatch.total, certified: huBatch.certified }
114
+ }));
115
+ loadedPlan.status = "running";
116
+ await savePlanToDisk(projectDir, loadedPlan);
117
+ }
118
+ }
119
+ }
120
+
121
+ // Inject contexts that the legacy planning phases would have
122
+ // produced. Then build a plannedTask string for the non-HU /
123
+ // fallback iteration path.
124
+ const ctx = loadedPlan.context || {};
125
+ setPreLoopContext(session, {
126
+ research: ctx.researchContext || loadedPlan.researchContext || null,
127
+ architect: ctx.architectContext || loadedPlan.architectContext || null,
128
+ plan: loadedPlan.approach || loadedPlan.plan || null,
129
+ });
130
+ await saveSession(session);
131
+
132
+ return { handled: true, plannedTask: buildPlannedTaskFromLoadedPlan({ task, loadedPlan }) };
133
+ } catch (err) {
134
+ logger.warn(`Plan loading failed: ${err.message} — falling back to normal pipeline`);
135
+ return { handled: false, plannedTask: null };
136
+ }
137
+ }
138
+
139
+ /**
140
+ * Reset HUs that look mid-execution but have no live run behind them.
141
+ *
142
+ * Resilience audit, Phase 3 (Hallazgo A1 / status-zombi). A previous
143
+ * `kj run --plan` flipped HUs to `coding`/`reviewing`/`running` in the
144
+ * plan JSON, then died (SIGKILL, terminal closed, OOM, crash). The
145
+ * board-side `hu-zombie-reaper` only queries SQLite and only runs
146
+ * inside the board process, so the plan JSON on disk kept stale
147
+ * in-flight statuses forever. The next `kj run --plan` then refused to
148
+ * pick those HUs up.
149
+ *
150
+ * Cross-check via `run-registry`: if there is a live run with our
151
+ * planId, do NOT touch — that run owns the in-flight HUs. Otherwise
152
+ * any in-flight status with no live owner is a zombie → reset to
153
+ * `pending` so it can run again.
154
+ *
155
+ * Mutates `loadedPlan.hus` in place. Returns the number of HUs reset
156
+ * so the caller can persist + emit a progress event.
157
+ *
158
+ * @param {object} args
159
+ * @param {object} args.loadedPlan
160
+ * @param {string} args.planId
161
+ * @param {object} [args.logger]
162
+ * @returns {number}
163
+ */
164
+ export function reconcilePlanHuZombies({ loadedPlan, planId, logger }) {
165
+ if (!Array.isArray(loadedPlan?.hus) || loadedPlan.hus.length === 0) return 0;
166
+ const activeRuns = listActiveRuns({ planId });
167
+ if (activeRuns.length > 0) return 0;
168
+ const ZOMBIE_STATES = new Set(["coding", "reviewing", "running"]);
169
+ let reaped = 0;
170
+ for (const hu of loadedPlan.hus) {
171
+ if (ZOMBIE_STATES.has(hu.status)) {
172
+ const previous = hu.status;
173
+ hu.status = "pending";
174
+ reaped += 1;
175
+ logger?.warn?.(`[plan] HU ${hu.id || hu.hu_id || "?"} was '${previous}' but no live run owns the plan — reset to 'pending'.`);
176
+ }
177
+ }
178
+ return reaped;
179
+ }
180
+
181
+ /**
182
+ * Throws when any HU lacks an acceptance_tests array with at least
183
+ * one entry. Logs + emits the same `plan:tests-missing` progress
184
+ * event as the inlined version did.
185
+ */
186
+ function assertEveryHuHasTests({ loadedPlan, planId, eventBase, emitter, logger }) {
187
+ const missing = loadedPlan.hus.filter(
188
+ (h) => !Array.isArray(h.acceptance_tests) || h.acceptance_tests.length === 0
189
+ );
190
+ if (missing.length === 0) return;
191
+ const offenders = missing.map((h) => ` - ${h.id} ${(h.title || "").slice(0, 60)}`).join("\n");
192
+ const msg =
193
+ `Refusing to run plan ${planId}: ${missing.length}/${loadedPlan.hus.length} HU(s) have no acceptance_tests.\n`
194
+ + `${offenders}\n\n`
195
+ + "Tests-first flow requires a test contract per HU. Edit each HU on the board\n"
196
+ + "(http://localhost:4000) via the ✎ Edit button, or re-run `kj plan` to regenerate.";
197
+ logger.error(msg);
198
+ emitProgress(emitter, makeEvent("plan:tests-missing", { ...eventBase, stage: "plan" }, {
199
+ status: "fail",
200
+ message: `Plan ${planId}: ${missing.length} HU(s) missing acceptance_tests`,
201
+ detail: { planId, missingHuIds: missing.map((h) => h.id) }
202
+ }));
203
+ throw new Error(`Plan ${planId} has ${missing.length} HU(s) with no acceptance_tests — see the run log for details.`);
204
+ }
205
+
206
+ /**
207
+ * Promotes pending HUs with tests to certified, then asserts at
208
+ * least one HU is runnable. Throws (with `plan:no-runnable` event)
209
+ * when nothing is left to do, so the orchestrator never silently
210
+ * falls back to single-task mode and burns tokens.
211
+ */
212
+ async function autoCertifyAndAssertRunnable({ planHuOps, loadedPlan, planId, projectDir, savePlanToDisk, eventBase, emitter, logger }) {
213
+ const { autoCertifyPendingHus, assertPlanRunnable } = planHuOps;
214
+ const promoted = autoCertifyPendingHus(loadedPlan);
215
+ if (promoted > 0) {
216
+ await savePlanToDisk(projectDir, loadedPlan);
217
+ logger.info(`Plan ${planId}: auto-certified ${promoted} pending HU(s) — ready to run`);
218
+ emitProgress(emitter, makeEvent("plan:auto-certified", { ...eventBase, stage: "plan" }, {
219
+ message: `Auto-certificadas ${promoted} HU(s) pendientes`,
220
+ detail: { planId, count: promoted }
221
+ }));
222
+ }
223
+ try {
224
+ assertPlanRunnable(loadedPlan, planId);
225
+ } catch (err) {
226
+ logger.error(err.message);
227
+ emitProgress(emitter, makeEvent("plan:no-runnable", { ...eventBase, stage: "plan" }, {
228
+ status: "fail", message: err.message,
229
+ detail: { planId }
230
+ }));
231
+ throw err;
232
+ }
233
+ }
234
+
235
+ /**
236
+ * PR4: when --hu <ids> is set, narrow the batch to those HU ids by
237
+ * marking the rest as `done` in memory. The plan JSON on disk is
238
+ * NOT touched — this is a run-time filter only. Throws when none of
239
+ * the requested ids exist.
240
+ */
241
+ function applyHuFilterIfRequested({ huBatch, flags, eventBase, emitter, logger }) {
242
+ if (!huBatch.ok || !flags.hu) return;
243
+ const requested = String(flags.hu).split(",").map((s) => s.trim()).filter(Boolean);
244
+ if (requested.length === 0) {
245
+ logger.warn(`--hu was empty after parsing — falling back to running every certified HU`);
246
+ return;
247
+ }
248
+ // Humanización IDs: resolver cada ref a un hu.id largo aceptando 3 formas:
249
+ // 1. `hu_<planId>_<NNN>` (id largo, exacto)
250
+ // 2. `INFRA-001` (short_id que el planner asignó)
251
+ // 3. `001` (sufijo numérico — busca un hu.id que termine en `_001`)
252
+ // El resolver es case-insensitive para short_id y sufijo.
253
+ const resolvedIds = new Set();
254
+ const unresolved = [];
255
+ for (const ref of requested) {
256
+ const id = resolveHuRef(ref, huBatch.stories);
257
+ if (id) resolvedIds.add(id);
258
+ else unresolved.push(ref);
259
+ }
260
+ const found = huBatch.stories.filter((s) => resolvedIds.has(s.id));
261
+ if (unresolved.length > 0) {
262
+ logger.warn(`--hu: ignored ${unresolved.length} unknown HU ref(s): ${unresolved.join(", ")}`);
263
+ }
264
+ if (found.length === 0) {
265
+ throw new Error(`--hu: none of the requested HU ref(s) exist in plan ${flags.plan}: ${requested.join(", ")}`);
266
+ }
267
+ for (const story of huBatch.stories) {
268
+ if (!resolvedIds.has(story.id)) story.status = "done";
269
+ }
270
+ huBatch.certified = found.length;
271
+ logger.info(`Plan ${flags.plan}: --hu filter active — ${found.length}/${huBatch.total} HU(s) will run (${found.map((s) => s.id).join(", ")})`);
272
+ emitProgress(emitter, makeEvent("plan:hu-filter", { ...eventBase, stage: "plan" }, {
273
+ message: `--hu filter: ${found.length} HU(s) selected, ${huBatch.total - found.length} skipped`,
274
+ detail: { planId: flags.plan, requested, ignored: unresolved, willRun: found.map((s) => s.id) }
275
+ }));
276
+ }
277
+
278
+ /**
279
+ * Wire the four session-scoped callbacks the run will invoke as it
280
+ * progresses: PR1 live status (per HU), PR3 live outcome (per HU
281
+ * exit), and the syncResultsToPlan rollup (end-of-run).
282
+ */
283
+ function wirePlanCallbacks({ session, loadedPlan, projectDir, syncResultsToPlan, savePlanToDisk, planHuOps, logger }) {
284
+ setPlanRef(session, { planId: loadedPlan.planId, projectDir });
285
+ setPlanSyncCallback(session, async (subResult) => {
286
+ syncResultsToPlan(loadedPlan, subResult);
287
+ await savePlanToDisk(projectDir, loadedPlan);
288
+ });
289
+ // PR1: live HU status. Without this the plan only updates at
290
+ // end-of-run and the board's Kanban looks frozen during execution.
291
+ // (`planHuOps` was loaded above for PR-E auto-certify — reuse the
292
+ // reference to stay under the dynamic-import budget.)
293
+ const { updateHuStatus: updateHuStatusFn, setHuOutcome: setHuOutcomeFn } = planHuOps;
294
+ setLiveStatusUpdater(session, async (huId, status) => {
295
+ try {
296
+ if (!updateHuStatusFn(loadedPlan, huId, status)) return;
297
+ await savePlanToDisk(projectDir, loadedPlan);
298
+ } catch (err) {
299
+ logger?.warn?.(`Live status update failed for ${huId}: ${err.message}`);
300
+ }
301
+ });
302
+ // PR3: per-HU outcome. Stamped once per HU at runSingleHu's exit
303
+ // so the board can show the 📄 indicator as soon as each HU
304
+ // finishes (iterations / duration / commits / blockers / summary).
305
+ //
306
+ // Cost D (KJC-TSK-0515): the outcome may carry `cost_usd` (USD
307
+ // spent on this HU). When present and finite, also persist it to
308
+ // board.db so the per-card badge (Cost F) and the project total
309
+ // chip (Cost G) have data to show — without this write, the
310
+ // `cost_usd` column would always stay NULL and the UI would render
311
+ // nothing. Lazy-import to keep the pre-loop phase free of board
312
+ // deps until we actually need them.
313
+ setLiveOutcomeUpdater(session, async (huId, outcome) => {
314
+ try {
315
+ if (!setHuOutcomeFn(loadedPlan, huId, outcome)) return;
316
+ await savePlanToDisk(projectDir, loadedPlan);
317
+ if (outcome && Number.isFinite(outcome.cost_usd)) {
318
+ try {
319
+ const { ensureDb, setStoryCost, setStoryCachedTokens } = await import("../../../../packages/hu-board/src/db.js");
320
+ ensureDb(); // KJC-BUG-0090: autorun/run may not have started the board
321
+ setStoryCost(huId, outcome.cost_usd);
322
+ // Φ0-G (KJC-TSK-0525): cached/in tokens travel in the same
323
+ // outcome blob as cost. Write them together so the board
324
+ // can render the "🎯 N% cached" badge on the same render.
325
+ // Either may be null (older runs, no cache) — setter handles it.
326
+ if (Number.isFinite(outcome.cached_tokens) || Number.isFinite(outcome.tokens_in)) {
327
+ setStoryCachedTokens(
328
+ huId,
329
+ Number.isFinite(outcome.cached_tokens) ? outcome.cached_tokens : null,
330
+ Number.isFinite(outcome.tokens_in) ? outcome.tokens_in : null,
331
+ );
332
+ }
333
+ } catch (err) {
334
+ logger?.warn?.(`Cost write failed for ${huId}: ${err.message}`);
335
+ }
336
+ }
337
+ } catch (err) {
338
+ logger?.warn?.(`Live outcome update failed for ${huId}: ${err.message}`);
339
+ }
340
+ });
341
+ }
342
+
343
+ /**
344
+ * Resuelve una referencia humana a HU al `id` canónico largo. Acepta:
345
+ * - id largo exacto: `hu_<planId>_<NNN>` → match directo.
346
+ * - short_id: `INFRA-001`, `AUTH-SIGNUP` → match case-insensitive contra hu.short_id.
347
+ * - sufijo numérico: `001` → match contra hu.id terminado en `_001`.
348
+ * Devuelve el hu.id largo o null si no resuelve.
349
+ *
350
+ * @param {string} ref
351
+ * @param {Array<{id: string, short_id?: string|null}>} hus
352
+ * @returns {string|null}
353
+ */
354
+ function resolveHuRef(ref, hus) {
355
+ if (!ref || !Array.isArray(hus)) return null;
356
+ // Match exacto contra id largo (camino caliente).
357
+ const exact = hus.find((s) => s.id === ref);
358
+ if (exact) return exact.id;
359
+ // Match case-insensitive contra short_id.
360
+ const lower = ref.toLowerCase();
361
+ const byShort = hus.find((s) => s.short_id && String(s.short_id).toLowerCase() === lower);
362
+ if (byShort) return byShort.id;
363
+ // Match por sufijo numérico (`001` → cualquier hu cuyo id termine en `_001`).
364
+ if (/^\d+$/.test(ref)) {
365
+ const padded = ref.padStart(3, "0");
366
+ const bySuffix = hus.find((s) => s.id.endsWith(`_${padded}`));
367
+ if (bySuffix) return bySuffix.id;
368
+ }
369
+ return null;
370
+ }
371
+
372
+ /**
373
+ * Build a single plannedTask string for the non-HU / fallback
374
+ * iteration path. The HU sub-pipeline doesn't read this; only the
375
+ * legacy single-iteration loop does. Identical to the inlined
376
+ * version's behaviour.
377
+ */
378
+ function buildPlannedTaskFromLoadedPlan({ task, loadedPlan }) {
379
+ const plan = loadedPlan.plan || (loadedPlan.approach ? { approach: loadedPlan.approach } : null);
380
+ if (plan && typeof plan === "object" && plan.steps) {
381
+ const stepList = plan.steps.map((s, idx) => `${idx + 1}. ${s.description || s}`).join("\n");
382
+ return `${task}\n\n## Implementation Plan\n${plan.approach || ""}\n\n## Steps\n${stepList}`;
383
+ }
384
+ if (typeof plan === "string") {
385
+ return `${task}\n\n## Implementation Plan\n${plan}`;
386
+ }
387
+ if (loadedPlan.approach) {
388
+ return `${task}\n\n## Approach\n${loadedPlan.approach}`;
389
+ }
390
+ return task;
391
+ }