@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,616 @@
1
+ /**
2
+ * HU Sub-Pipeline — executes each certified HU as an independent sub-pipeline
3
+ * with per-HU state tracking, topological ordering, and dependency-aware blocking.
4
+ */
5
+ import { topologicalSort } from "../hu/graph.js";
6
+ import { updateStoryStatus, loadHuBatch, saveHuBatch, HU_STATUS } from "../hu/store.js";
7
+ import { emitProgress, makeEvent } from "../utils/events.js";
8
+ import { refineHuWithContext } from "../hu/lazy-planner.js";
9
+ import { join } from "node:path";
10
+ import { acquireSlot, releaseSlot } from "karajan-core/slot-registry";
11
+ import { getKarajanHome } from "karajan-core/paths";
12
+ import { findParallelGroups, createWorktree, mergeWorktree, removeWorktree } from "../hu/parallel-executor.js";
13
+ import { bootstrapWorktree } from "../hu/worktree-bootstrap.js";
14
+ import { partitionConflictFree } from "./hu-scheduler.js";
15
+ import { createParallelLimiter, planBudgetUsd } from "./parallel-limiter.js";
16
+
17
+ /**
18
+ * Determine whether the HU reviewer result needs the sub-pipeline path.
19
+ *
20
+ * Pre-fix this returned true only when there were > 1 certified stories.
21
+ * That broke single-HU runs (`kj run --plan <id> --hu <huId>`, the path
22
+ * the board's per-card ▶ button drives): with exactly one HU certified
23
+ * the flow fell through to the standard single-task pipeline, which
24
+ * does NOT call updateStoryStatus / saveHuBatch / emit hu:* events.
25
+ * Result: the HU stayed forever in `coding` even after the run finished
26
+ * because nobody persisted "done"/"failed" anywhere — board kanban
27
+ * showed "1 running…" indefinitely, plan JSON kept the stale status.
28
+ *
29
+ * The sub-pipeline already handles a 1-HU batch as a degenerate case
30
+ * (no dependency graph to resolve, no parallel batching) — so the fix
31
+ * is to take that path for any non-empty batch, not just >1.
32
+ *
33
+ * @param {object|null} huReviewerResult - stageResults.huReviewer
34
+ * @returns {boolean}
35
+ */
36
+ export function needsSubPipeline(huReviewerResult) {
37
+ if (!huReviewerResult?.ok) return false;
38
+ const certified = (huReviewerResult.stories || []).filter(
39
+ s => s.status === "certified"
40
+ );
41
+ return certified.length >= 1;
42
+ }
43
+
44
+ /**
45
+ * Mark all stories that depend (directly or transitively) on a failed story as "blocked".
46
+ * @param {object} batch - The HU batch object.
47
+ * @param {string} failedId - The ID of the failed story.
48
+ * @returns {string[]} IDs of newly blocked stories.
49
+ */
50
+ export function blockDependents(batch, failedId) {
51
+ const blocked = [];
52
+ const failedSet = new Set([failedId]);
53
+ // Iterate until no new blocked stories are found (handles transitive deps)
54
+ let changed = true;
55
+ while (changed) {
56
+ changed = false;
57
+ for (const story of batch.stories) {
58
+ if (failedSet.has(story.id)) continue;
59
+ if (story.status === HU_STATUS.BLOCKED) continue;
60
+ const deps = story.blocked_by || [];
61
+ if (deps.some(dep => failedSet.has(dep))) {
62
+ updateStoryStatus(batch, story.id, HU_STATUS.BLOCKED);
63
+ failedSet.add(story.id);
64
+ blocked.push(story.id);
65
+ changed = true;
66
+ }
67
+ }
68
+ }
69
+ return blocked;
70
+ }
71
+
72
+ /**
73
+ * Build task description for the coder from a certified HU story.
74
+ * @param {object} story - A story object from the batch.
75
+ * @returns {string}
76
+ */
77
+ function buildHuTask(story) {
78
+ if (story.certified?.text) return story.certified.text;
79
+ if (story.original?.text) return story.original.text;
80
+ return `Implement HU ${story.id}`;
81
+ }
82
+
83
+ /**
84
+ * Sum the cost_usd of a slice of BudgetTracker entries. Returns null
85
+ * (not 0) when the slice is empty or contains no usable cost data —
86
+ * the board uses null as "unmeasured" and renders no badge, while 0
87
+ * means "really free run". Mixing the two would silently hide free
88
+ * runs and falsely show "$0.00" for runs with no telemetry.
89
+ *
90
+ * Cost D (KJC-TSK-0515): the orchestrator calls this once per HU at
91
+ * runSingleHu exit, passing the slice of entries recorded during that
92
+ * HU (snapshotted via the entries.length cursor before the iteration
93
+ * starts). The resulting number flows through buildHuOutcome →
94
+ * notifyOutcome → setStoryCost in inject-loaded-plan.js.
95
+ *
96
+ * @param {Array<{cost_usd?: number}>} entries
97
+ * @returns {number|null}
98
+ */
99
+ function sumCostUsd(entries) {
100
+ if (!Array.isArray(entries) || entries.length === 0) return null;
101
+ let total = 0;
102
+ let hasAny = false;
103
+ for (const e of entries) {
104
+ const n = Number(e?.cost_usd);
105
+ if (Number.isFinite(n)) {
106
+ total += n;
107
+ hasAny = true;
108
+ }
109
+ }
110
+ if (!hasAny) return null;
111
+ return Math.round(total * 1e6) / 1e6;
112
+ }
113
+
114
+ /**
115
+ * Sum an integer token-counter field across a slice of BudgetTracker
116
+ * entries (Φ0-G, KJC-TSK-0525). Same null-vs-0 semantics as sumCostUsd:
117
+ * returns null when nothing measurable was recorded so the board can
118
+ * distinguish "unmeasured" from "really zero".
119
+ *
120
+ * @param {Array<object>} entries
121
+ * @param {string} field — entry key to sum, e.g. "cached_tokens"
122
+ * @returns {number|null}
123
+ */
124
+ function sumTokenField(entries, field) {
125
+ if (!Array.isArray(entries) || entries.length === 0) return null;
126
+ let total = 0;
127
+ let hasAny = false;
128
+ for (const e of entries) {
129
+ const n = Number(e?.[field]);
130
+ if (Number.isFinite(n) && n >= 0) {
131
+ total += n;
132
+ hasAny = true;
133
+ }
134
+ }
135
+ if (!hasAny) return null;
136
+ return Math.round(total);
137
+ }
138
+
139
+ /**
140
+ * Build the per-HU outcome blob from whatever metadata the iteration
141
+ * loop returned. Tolerant by design — most fields are optional and we
142
+ * default to safe values so the board can render something useful
143
+ * even on weird failure modes (HU crashed before producing summary,
144
+ * runIterationFn missing the commits array, etc.).
145
+ *
146
+ * Plain-Spanish summary lives at the bottom of the priority order so
147
+ * we surface what the run actually said before falling back to
148
+ * generic phrases.
149
+ *
150
+ * @param {object} args
151
+ * @param {object} args.story
152
+ * @param {object} args.iterResult — whatever runIterationFn returned
153
+ * @param {string} args.status — HU_STATUS.* terminal value
154
+ * @param {number} args.startedAt — Date.now() when runSingleHu started
155
+ * @param {Array} [args.huBudgetEntries] — slice of BudgetTracker
156
+ * entries recorded during this HU. Cost D (KJC-TSK-0515).
157
+ * @returns {object}
158
+ */
159
+ function buildHuOutcome({ story: _story, iterResult, status, startedAt, huBudgetEntries }) {
160
+ const r = iterResult || {};
161
+ const git = r.git || {};
162
+ // Iterations are tracked in the standard pipeline as
163
+ // `result.iterations`, in the tests-first pipeline as the loop
164
+ // counter — fall back to whatever we can find.
165
+ const iterations = r.iterations ?? r.iteration ?? r.attempts ?? null;
166
+ const commits = Array.isArray(git.commits)
167
+ ? git.commits.map((c) => (typeof c === "string" ? c : c.hash || c.sha || "")).filter(Boolean)
168
+ : (Array.isArray(r.commits) ? r.commits : []);
169
+ const blockers = [];
170
+ if (r.error) blockers.push(String(r.error));
171
+ if (r.reason && r.reason !== "approved") blockers.push(String(r.reason));
172
+ if (Array.isArray(r.deferredIssues)) {
173
+ for (const d of r.deferredIssues) blockers.push(typeof d === "string" ? d : d.message || JSON.stringify(d));
174
+ }
175
+ // Plain-Spanish, one-or-two sentences.
176
+ let summary;
177
+ if (r.review?.summary) summary = String(r.review.summary);
178
+ else if (status === "done") summary = `HU completada${commits.length ? ` con ${commits.length} commit(s)` : ""}.`;
179
+ else if (status === "failed") summary = `HU falló${blockers.length ? `: ${blockers[0]}` : ""}.`;
180
+ else summary = `HU ${status}.`;
181
+ return {
182
+ status,
183
+ iterations: typeof iterations === "number" ? iterations : null,
184
+ duration_ms: typeof startedAt === "number" ? Date.now() - startedAt : null,
185
+ branch: r.branch || git.branch || null,
186
+ commits,
187
+ pr_url: r.pr_url || r.prUrl || git.pr_url || null,
188
+ blockers,
189
+ summary,
190
+ cost_usd: sumCostUsd(huBudgetEntries),
191
+ // Φ0-G (KJC-TSK-0525): cached/input tokens drive the "🎯 N% cached"
192
+ // board badge. Null when nothing measurable was recorded so the UI
193
+ // can hide the chip on cold runs or providers without cache.
194
+ cached_tokens: sumTokenField(huBudgetEntries, 'cached_tokens'),
195
+ tokens_in: sumTokenField(huBudgetEntries, 'tokens_in'),
196
+ finishedAt: new Date().toISOString(),
197
+ };
198
+ }
199
+
200
+ /**
201
+ * Execute a single HU through the coder→sonar→reviewer sub-pipeline.
202
+ * Extracted to support both sequential and parallel execution paths.
203
+ *
204
+ * @param {object} params
205
+ * @returns {Promise<{huId: string, approved: boolean, result?: object, error?: string, blockedDependents?: string[]}>}
206
+ */
207
+ async function runSingleHu({ storyId, batch, batchSessionId, runIterationFn, emitter, eventBase, logger, config, results, worktreePath, laneSlot = null, onStatusChange = null, onOutcome = null, budgetTracker = null }) {
208
+ // PR1 (live HU status): every saveHuBatch should also notify the
209
+ // plan JSON so the board reflects state in real time. Defined as a
210
+ // local helper so we don't repeat the try/null-check boilerplate.
211
+ const notifyStatus = async (huId, status) => {
212
+ if (typeof onStatusChange !== "function") return;
213
+ try { await onStatusChange(huId, status); }
214
+ catch (err) { logger?.warn?.(`onStatusChange(${huId}, ${status}) failed: ${err.message}`); }
215
+ };
216
+ // PR3 (per-HU outcome): single notification at the end of the HU
217
+ // with the rich outcome blob. Plan JSON gets it stamped before
218
+ // we return, so the board's chokidar fires once and the card
219
+ // shows its summary indicator.
220
+ const notifyOutcome = async (huId, outcome) => {
221
+ if (typeof onOutcome !== "function") return;
222
+ try { await onOutcome(huId, outcome); }
223
+ catch (err) { logger?.warn?.(`onOutcome(${huId}) failed: ${err.message}`); }
224
+ };
225
+
226
+ // PR3: capture the start time so duration can be reported in the
227
+ // outcome (wall-clock per HU is what the user needs in the board,
228
+ // not internal stage timings).
229
+ const huStartedAt = Date.now();
230
+ // Cost D (KJC-TSK-0515): snapshot the budget cursor so we can slice
231
+ // out only the entries this HU produced. BudgetTracker is session-
232
+ // scoped (it accumulates across all HUs of the run), so without this
233
+ // cursor every HU after the first would inherit the previous ones'
234
+ // cost too. Null-safe — older callers without a tracker still work.
235
+ const huBudgetStartIdx = budgetTracker?.entries?.length ?? 0;
236
+ const sliceHuBudget = () => {
237
+ if (!budgetTracker?.entries) return [];
238
+ return budgetTracker.entries.slice(huBudgetStartIdx);
239
+ };
240
+ const story = batch.stories.find(s => s.id === storyId);
241
+
242
+ // --- Lazy refinement ---
243
+ if (story.needsRefinement && config) {
244
+ const completedHus = results
245
+ .filter(r => r.approved)
246
+ .map(r => {
247
+ const completedStory = batch.stories.find(s => s.id === r.huId);
248
+ return {
249
+ ...completedStory,
250
+ resultSummary: r.result?.summary || r.result?.reason || null
251
+ };
252
+ });
253
+
254
+ emitProgress(emitter, makeEvent("hu:refine-start", { ...eventBase, stage: "hu-sub-pipeline" }, {
255
+ message: `Refining HU ${story.id} with context from ${completedHus.length} completed HU(s)`,
256
+ detail: { huId: story.id, completedCount: completedHus.length }
257
+ }));
258
+
259
+ try {
260
+ const refined = await refineHuWithContext(story, completedHus, config, logger);
261
+ Object.assign(story, refined);
262
+ story.needsRefinement = false;
263
+ await saveHuBatch(batchSessionId, batch);
264
+
265
+ emitProgress(emitter, makeEvent("hu:refine-end", { ...eventBase, stage: "hu-sub-pipeline" }, {
266
+ message: `HU ${story.id} refined successfully`,
267
+ detail: { huId: story.id }
268
+ }));
269
+ } catch (err) {
270
+ logger.warn(`Lazy refinement failed for HU ${story.id}: ${err.message} — proceeding with original`);
271
+ story.needsRefinement = false;
272
+ }
273
+ }
274
+
275
+ const huTask = buildHuTask(story);
276
+
277
+ // --- hu:start ---
278
+ emitProgress(emitter, makeEvent("hu:start", { ...eventBase, stage: "hu-sub-pipeline" }, {
279
+ message: `Starting HU ${story.id}`,
280
+ detail: { huId: story.id, title: story.certified?.title || story.id, worktreePath: worktreePath || null }
281
+ }));
282
+
283
+ updateStoryStatus(batch, story.id, HU_STATUS.CODING);
284
+ await saveHuBatch(batchSessionId, batch);
285
+ await notifyStatus(story.id, HU_STATUS.CODING);
286
+ emitProgress(emitter, makeEvent("hu:status-change", { ...eventBase, stage: "hu-sub-pipeline" }, {
287
+ message: `HU ${story.id} status → coding`,
288
+ detail: { huId: story.id, status: HU_STATUS.CODING, timestamp: new Date().toISOString() }
289
+ }));
290
+
291
+ try {
292
+ // PAR-E2 (KJC-TSK-0629): lanes handed a worktree must aim every git and
293
+ // filesystem touchpoint at it — laneOpts carries that path to the runner.
294
+ const iterResult = await runIterationFn(huTask, story, { worktreePath: worktreePath || null, laneSlot });
295
+ const approved = Boolean(iterResult?.approved);
296
+
297
+ // --- Transition to reviewing (post-coder, pre-reviewer evaluation) ---
298
+ updateStoryStatus(batch, story.id, HU_STATUS.REVIEWING);
299
+ await saveHuBatch(batchSessionId, batch);
300
+ await notifyStatus(story.id, HU_STATUS.REVIEWING);
301
+ emitProgress(emitter, makeEvent("hu:status-change", { ...eventBase, stage: "hu-sub-pipeline" }, {
302
+ message: `HU ${story.id} status → reviewing`,
303
+ detail: { huId: story.id, status: HU_STATUS.REVIEWING, timestamp: new Date().toISOString() }
304
+ }));
305
+
306
+ if (approved) {
307
+ updateStoryStatus(batch, story.id, HU_STATUS.DONE);
308
+ await saveHuBatch(batchSessionId, batch);
309
+ await notifyStatus(story.id, HU_STATUS.DONE);
310
+ // PR3: build + persist the per-HU outcome.
311
+ const okOutcome = buildHuOutcome({ story, iterResult, status: HU_STATUS.DONE, startedAt: huStartedAt, huBudgetEntries: sliceHuBudget() });
312
+ await notifyOutcome(story.id, okOutcome);
313
+ emitProgress(emitter, makeEvent("hu:status-change", { ...eventBase, stage: "hu-sub-pipeline" }, {
314
+ message: `HU ${story.id} status → done`,
315
+ detail: { huId: story.id, status: HU_STATUS.DONE, timestamp: new Date().toISOString() }
316
+ }));
317
+ emitProgress(emitter, makeEvent("hu:end", { ...eventBase, stage: "hu-sub-pipeline" }, {
318
+ status: "ok",
319
+ message: `HU ${story.id} completed successfully`,
320
+ detail: { huId: story.id, approved: true, outcome: okOutcome }
321
+ }));
322
+ return { huId: story.id, approved: true, result: iterResult };
323
+ } else {
324
+ updateStoryStatus(batch, story.id, HU_STATUS.FAILED);
325
+ await saveHuBatch(batchSessionId, batch);
326
+ await notifyStatus(story.id, HU_STATUS.FAILED);
327
+ const failOutcome = buildHuOutcome({ story, iterResult, status: HU_STATUS.FAILED, startedAt: huStartedAt, huBudgetEntries: sliceHuBudget() });
328
+ await notifyOutcome(story.id, failOutcome);
329
+ emitProgress(emitter, makeEvent("hu:status-change", { ...eventBase, stage: "hu-sub-pipeline" }, {
330
+ message: `HU ${story.id} status → failed`,
331
+ detail: { huId: story.id, status: HU_STATUS.FAILED, timestamp: new Date().toISOString() }
332
+ }));
333
+ emitProgress(emitter, makeEvent("hu:end", { ...eventBase, stage: "hu-sub-pipeline" }, {
334
+ status: "fail",
335
+ message: `HU ${story.id} failed`,
336
+ detail: { huId: story.id, approved: false, reason: iterResult?.reason, outcome: failOutcome }
337
+ }));
338
+ return { huId: story.id, approved: false, result: iterResult };
339
+ }
340
+ } catch (err) {
341
+ updateStoryStatus(batch, story.id, HU_STATUS.FAILED);
342
+ await saveHuBatch(batchSessionId, batch);
343
+ await notifyStatus(story.id, HU_STATUS.FAILED);
344
+ const errOutcome = buildHuOutcome({ story, iterResult: { error: err.message }, status: HU_STATUS.FAILED, startedAt: huStartedAt, huBudgetEntries: sliceHuBudget() });
345
+ await notifyOutcome(story.id, errOutcome);
346
+ emitProgress(emitter, makeEvent("hu:status-change", { ...eventBase, stage: "hu-sub-pipeline" }, {
347
+ message: `HU ${story.id} status → failed`,
348
+ detail: { huId: story.id, status: HU_STATUS.FAILED, timestamp: new Date().toISOString() }
349
+ }));
350
+ emitProgress(emitter, makeEvent("hu:end", { ...eventBase, stage: "hu-sub-pipeline" }, {
351
+ status: "fail",
352
+ message: `HU ${story.id} threw: ${err.message}`,
353
+ detail: { huId: story.id, approved: false, error: err.message }
354
+ }));
355
+ return { huId: story.id, approved: false, error: err.message };
356
+ }
357
+ }
358
+
359
+ /**
360
+ * Run all certified HUs as sub-pipelines in topological order.
361
+ *
362
+ * @param {object} params
363
+ * @param {object} params.huReviewerResult - stageResults.huReviewer (from pre-loop)
364
+ * @param {Function} params.runIterationFn - async (task) => { approved, result, ... }
365
+ * Callback that runs the full coder→sonar→reviewer iteration loop for a given task.
366
+ * Returns an object with at least { approved: boolean }.
367
+ * @param {object} params.emitter - Event emitter
368
+ * @param {object} params.eventBase - Base event fields
369
+ * @param {object} params.logger - Logger instance
370
+ * @returns {Promise<{approved: boolean, results: object[], blockedIds: string[]}>}
371
+ */
372
+ export async function runHuSubPipeline({ huReviewerResult, runIterationFn, emitter, eventBase, logger, config = null, onStatusChange = null, onOutcome = null, budgetTracker = null }) {
373
+ const batchSessionId = huReviewerResult.batchSessionId;
374
+ let batch;
375
+ try {
376
+ batch = await loadHuBatch(batchSessionId);
377
+ } catch {
378
+ logger.warn("HU sub-pipeline: could not load batch, falling back to in-memory stories");
379
+ // Build a minimal batch from the stage result
380
+ batch = {
381
+ session_id: batchSessionId,
382
+ stories: huReviewerResult.stories || []
383
+ };
384
+ }
385
+
386
+ // Plan-driven runs: the plan JSON is the source of truth for the
387
+ // ENTIRE HU, NOT just status, NOT the on-disk hu-stories batch.
388
+ //
389
+ // PR #537 reconciled `status` and fixed the "All HUs completed
390
+ // successfully for zero work" bug. But status is only ONE field of
391
+ // the HU. The 2026-04-29 incident hit a different field — a user
392
+ // edited the plan JSON to fix a broken jq query inside the HU's
393
+ // `acceptance_tests`, but the persisted batch kept the broken
394
+ // version, so the acceptance runner used the broken test, the coder
395
+ // burned every iteration trying to make an irreparable test pass,
396
+ // and the HU failed.
397
+ //
398
+ // Treat the plan as authoritative for ALL content fields the user
399
+ // might edit between runs (acceptance_tests, title, scope,
400
+ // blocked_by, spec_section, original.text). In-flight runtime
401
+ // context that doesn't live in the plan (worktree path, refinement
402
+ // state, certification quality scores, context_requests) is
403
+ // preserved.
404
+ if (huReviewerResult.source?.plan === true && Array.isArray(huReviewerResult.stories)) {
405
+ // Fields the plan owns. Anything NOT in this set is preserved
406
+ // from the persisted batch (in-flight runtime state).
407
+ const PLAN_OWNED_FIELDS = [
408
+ "status",
409
+ "title",
410
+ "scope",
411
+ "blocked_by",
412
+ "acceptance_tests",
413
+ "acceptance_criteria",
414
+ "spec_section",
415
+ "task_type",
416
+ "certified", // canonical HU body
417
+ "original", // original text reference
418
+ ];
419
+ const planById = new Map(huReviewerResult.stories.map((s) => [s.id, s]));
420
+ let statusChanges = 0;
421
+ let contentChanges = 0;
422
+ for (const persisted of batch.stories) {
423
+ const fromPlan = planById.get(persisted.id);
424
+ if (!fromPlan) continue;
425
+ for (const field of PLAN_OWNED_FIELDS) {
426
+ if (!(field in fromPlan)) continue;
427
+ const before = JSON.stringify(persisted[field]);
428
+ const after = JSON.stringify(fromPlan[field]);
429
+ if (before === after) continue;
430
+ persisted[field] = fromPlan[field];
431
+ if (field === "status") statusChanges += 1;
432
+ else contentChanges += 1;
433
+ }
434
+ }
435
+ // Stories present in the plan but missing from disk → append.
436
+ const persistedIds = new Set(batch.stories.map((s) => s.id));
437
+ let appended = 0;
438
+ for (const fromPlan of huReviewerResult.stories) {
439
+ if (!persistedIds.has(fromPlan.id)) {
440
+ batch.stories.push(fromPlan);
441
+ appended += 1;
442
+ }
443
+ }
444
+ const totalChanges = statusChanges + contentChanges + appended;
445
+ if (totalChanges > 0) {
446
+ const parts = [];
447
+ if (statusChanges > 0) parts.push(`${statusChanges} status`);
448
+ if (contentChanges > 0) parts.push(`${contentChanges} content field(s)`);
449
+ if (appended > 0) parts.push(`${appended} appended`);
450
+ logger.info(`HU sub-pipeline: reconciled from plan — ${parts.join(", ")} (disk batch was stale)`);
451
+ await saveHuBatch(batchSessionId, batch);
452
+ }
453
+ }
454
+
455
+ const certifiedStories = batch.stories.filter(s => s.status === HU_STATUS.CERTIFIED);
456
+ let orderedIds;
457
+ try {
458
+ orderedIds = topologicalSort(certifiedStories);
459
+ } catch { /* cyclic dependency */
460
+ orderedIds = certifiedStories.map(s => s.id);
461
+ }
462
+
463
+ const results = [];
464
+ const blockedIds = [];
465
+ let allApproved = true;
466
+
467
+ // --- Group HUs into parallel batches ---
468
+ const parallelBatches = findParallelGroups(certifiedStories, orderedIds);
469
+
470
+ // Governance (KJC-TSK-0626): the dependency groups above used to launch
471
+ // UNBOUNDED via Promise.all — a plan with N independent HUs ran N full
472
+ // pipelines at once, with no cap, no shared budget and no scope checks.
473
+ // Now each group is partitioned into conflict-free chunks of at most
474
+ // max_parallel_hus (default 1 = fully sequential, today's safe path) and
475
+ // a shared limiter gates every launch against the PLAN budget.
476
+ const maxParallel = Math.max(1, Number(config?.session?.max_parallel_hus ?? 1));
477
+ const limiter = createParallelLimiter({
478
+ maxParallel,
479
+ budgetTracker,
480
+ planBudgetUsd: planBudgetUsd({ maxBudgetUsd: config?.max_budget_usd, maxParallel }),
481
+ });
482
+ let stopReason = null;
483
+
484
+ outer: for (const group of parallelBatches) {
485
+ for (const chunk of partitionConflictFree(batch.stories, group, maxParallel)) {
486
+ // Filter out HUs that were blocked by a failed dependency in a previous batch
487
+ const runnableIds = chunk.filter(id => {
488
+ const story = batch.stories.find(s => s.id === id);
489
+ return story && story.status !== HU_STATUS.BLOCKED;
490
+ });
491
+
492
+ if (runnableIds.length === 0) continue;
493
+
494
+ const blockReason = limiter.launchBlockReason();
495
+ if (blockReason) {
496
+ stopReason = blockReason;
497
+ allApproved = false;
498
+ logger.warn(`HU sub-pipeline stopped before launching ${runnableIds.join(", ")}: ${blockReason}`);
499
+ break outer;
500
+ }
501
+
502
+ // Emit parallel batch start event
503
+ emitProgress(emitter, makeEvent("hu:parallel-start", { ...eventBase, stage: "hu-sub-pipeline" }, {
504
+ message: `Starting parallel batch of ${runnableIds.length} HU(s): ${runnableIds.join(", ")}`,
505
+ detail: { batchIds: runnableIds, parallel: runnableIds.length > 1 }
506
+ }));
507
+
508
+ if (runnableIds.length === 1) {
509
+ // Single HU: run as before, no worktree needed
510
+ const singleResult = await runSingleHu({
511
+ storyId: runnableIds[0], batch, batchSessionId, runIterationFn,
512
+ emitter, eventBase, logger, config, results, onStatusChange, onOutcome, budgetTracker
513
+ });
514
+ results.push(singleResult);
515
+ if (!singleResult.approved) {
516
+ allApproved = false;
517
+ const newlyBlocked = blockDependents(batch, singleResult.huId);
518
+ blockedIds.push(...newlyBlocked);
519
+ // PR1 (live HU status): notify the plan JSON of every newly
520
+ // BLOCKED HU so the board moves them into the blocked column
521
+ // without waiting for the run to finish.
522
+ if (typeof onStatusChange === "function") {
523
+ for (const blockedHuId of newlyBlocked) {
524
+ try { await onStatusChange(blockedHuId, HU_STATUS.BLOCKED); }
525
+ catch (err) { logger?.warn?.(`onStatusChange(${blockedHuId}, blocked) failed: ${err.message}`); }
526
+ }
527
+ }
528
+ }
529
+ await saveHuBatch(batchSessionId, batch);
530
+ } else {
531
+ // Multiple HUs: create worktrees, run in parallel
532
+ const projectDir = config?.projectDir || process.cwd();
533
+ const worktrees = new Map();
534
+ // PAR-H (KJC-TSK-0631): every lane gets a stable numeric slot so
535
+ // services it starts can offset their ports (KJ_LANE_SLOT /
536
+ // KJ_PORT_OFFSET reach the coder and the acceptance tests).
537
+ const slotRegistryPath = join(getKarajanHome(), "worktree-slots.json");
538
+ const laneSlots = new Map();
539
+
540
+ // Create worktrees for each HU in the batch
541
+ for (const id of runnableIds) {
542
+ try {
543
+ const wtPath = await createWorktree(projectDir, id);
544
+ worktrees.set(id, wtPath);
545
+ // PAR-G (KJC-TSK-0630): a fresh worktree has no node_modules and
546
+ // no initialized submodules — make the lane operative before the
547
+ // coder lands. Best-effort: warnings never block the lane.
548
+ await bootstrapWorktree({ worktreePath: wtPath, setupCommand: config?.session?.worktree_setup || null, logger });
549
+ try {
550
+ const { slot } = await acquireSlot({ registryPath: slotRegistryPath, id: `${projectDir}::${id}` });
551
+ laneSlots.set(id, slot);
552
+ } catch (err) {
553
+ logger.warn(`Failed to acquire lane slot for HU ${id}: ${err.message} — lane runs without port offset`);
554
+ }
555
+ } catch (err) {
556
+ logger.warn(`Failed to create worktree for HU ${id}: ${err.message} — will run sequentially`);
557
+ }
558
+ }
559
+
560
+ // Run all HUs in the batch concurrently, each holding a limiter slot
561
+ // (belt-and-braces: chunks are already ≤ maxParallel).
562
+ const batchPromises = runnableIds.map(async (storyId) => {
563
+ await limiter.acquire();
564
+ try {
565
+ return await runSingleHu({
566
+ storyId, batch, batchSessionId, runIterationFn,
567
+ emitter, eventBase, logger, config, results,
568
+ worktreePath: worktrees.get(storyId),
569
+ laneSlot: laneSlots.get(storyId) ?? null,
570
+ onStatusChange, onOutcome, budgetTracker
571
+ });
572
+ } finally {
573
+ limiter.release();
574
+ }
575
+ });
576
+
577
+ const batchResults = await Promise.all(batchPromises);
578
+
579
+ // Process results: merge successful worktrees sequentially, clean up failed ones
580
+ for (const res of batchResults) {
581
+ results.push(res);
582
+ if (res.approved && worktrees.has(res.huId)) {
583
+ try {
584
+ await mergeWorktree(projectDir, res.huId);
585
+ await releaseSlot({ registryPath: slotRegistryPath, id: `${projectDir}::${res.huId}` }).catch(() => {});
586
+ } catch (err) {
587
+ logger.warn(`Failed to merge worktree for HU ${res.huId}: ${err.message}`);
588
+ }
589
+ } else if (!res.approved) {
590
+ allApproved = false;
591
+ const newlyBlocked = blockDependents(batch, res.huId);
592
+ blockedIds.push(...newlyBlocked);
593
+ // PR1 (live HU status): same as the single-HU path — push
594
+ // BLOCKED transitions to the plan JSON immediately so the
595
+ // board shows them moving to the blocked column live.
596
+ if (typeof onStatusChange === "function") {
597
+ for (const blockedHuId of newlyBlocked) {
598
+ try { await onStatusChange(blockedHuId, HU_STATUS.BLOCKED); }
599
+ catch (err) { logger?.warn?.(`onStatusChange(${blockedHuId}, blocked) failed: ${err.message}`); }
600
+ }
601
+ }
602
+ // Clean up failed worktree
603
+ if (worktrees.has(res.huId)) {
604
+ try { await removeWorktree(projectDir, res.huId); } catch { /* ignore */ }
605
+ await releaseSlot({ registryPath: slotRegistryPath, id: `${projectDir}::${res.huId}` }).catch(() => {});
606
+ }
607
+ }
608
+ }
609
+
610
+ await saveHuBatch(batchSessionId, batch);
611
+ }
612
+ }
613
+ }
614
+
615
+ return { approved: allApproved, results, blockedIds, ...(stopReason ? { stopReason } : {}) };
616
+ }