karajan-code 4.70.4 → 5.0.1

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 (390) hide show
  1. package/README.md +2 -2
  2. package/package.json +6 -14
  3. package/packages/hu-board/package.json +1 -1
  4. package/packages/hu-board/public/app.js +4 -18
  5. package/packages/hu-board/public/index.html +5 -2
  6. package/packages/hu-board/public/styles.css +26 -0
  7. package/packages/hu-board/public/utils/api.js +0 -74
  8. package/packages/hu-board/public/utils/board-tour.js +62 -0
  9. package/packages/hu-board/public/utils/board-view.js +49 -144
  10. package/packages/hu-board/public/utils/command-launcher.js +8 -70
  11. package/packages/hu-board/public/utils/formatters.js +10 -0
  12. package/packages/hu-board/public/utils/governance-view.js +13 -1
  13. package/packages/hu-board/public/utils/hu-actions.js +17 -68
  14. package/packages/hu-board/public/utils/init-listeners.js +7 -8
  15. package/packages/hu-board/public/utils/log-panel.js +3 -16
  16. package/packages/hu-board/public/utils/maggle-mode.js +2 -12
  17. package/packages/hu-board/public/utils/modals.js +7 -0
  18. package/packages/hu-board/public/utils/project-picker-view.js +1 -1
  19. package/packages/hu-board/public/utils/server-push.js +3 -22
  20. package/packages/hu-board/public/utils/shared-board-notice.js +33 -0
  21. package/packages/hu-board/public/utils/story-detail-view.js +5 -19
  22. package/packages/hu-board/src/cleanup-zombies.js +0 -1
  23. package/packages/hu-board/src/command-runner.js +36 -25
  24. package/packages/hu-board/src/db-connection.js +282 -0
  25. package/packages/hu-board/src/db-paths.js +113 -0
  26. package/packages/hu-board/src/db-queries.js +188 -0
  27. package/packages/hu-board/src/db-tombstones.js +51 -0
  28. package/packages/hu-board/src/db.js +28 -619
  29. package/packages/hu-board/src/ephemeral-cleaner.js +2 -3
  30. package/packages/hu-board/src/hu-zombie-reaper.js +5 -1
  31. package/packages/hu-board/src/plan-mutations.js +3 -153
  32. package/packages/hu-board/src/plans-service.js +55 -0
  33. package/packages/hu-board/src/routes/api/commands.js +122 -0
  34. package/packages/hu-board/src/routes/api/plans.js +55 -0
  35. package/packages/hu-board/src/routes/api/projects.js +267 -0
  36. package/packages/hu-board/src/routes/api/prompts.js +135 -0
  37. package/packages/hu-board/src/routes/api/sessions.js +95 -0
  38. package/packages/hu-board/src/routes/api/stories.js +183 -0
  39. package/packages/hu-board/src/routes/api/system.js +241 -0
  40. package/packages/hu-board/src/routes/api/tombstones.js +38 -0
  41. package/packages/hu-board/src/routes/api.js +24 -1371
  42. package/packages/hu-board/src/routes/governance.js +23 -1
  43. package/packages/hu-board/src/routes/lanes.js +40 -0
  44. package/packages/hu-board/src/routes/rag.js +4 -3
  45. package/packages/hu-board/src/server.js +20 -34
  46. package/packages/hu-board/src/sync-files.js +318 -0
  47. package/packages/hu-board/src/sync-project.js +99 -0
  48. package/packages/hu-board/src/sync.js +22 -566
  49. package/packages/hu-board/src/zombie-reaper.js +1 -1
  50. package/scripts/dual-publish.mjs +19 -3
  51. package/scripts/postinstall.js +15 -2
  52. package/scripts/verify-pack-mcp.mjs +11 -7
  53. package/scripts/verify-pack.mjs +4 -4
  54. package/src/audit/audit-history.js +3 -2
  55. package/src/audit/dead-exports.js +68 -59
  56. package/src/audit/deterministic-summary.js +33 -13
  57. package/src/audit/npm-signatures.js +41 -0
  58. package/src/audit/sbom.js +35 -0
  59. package/src/checks/agent-token.js +112 -0
  60. package/src/checks/dir-setup.js +34 -1
  61. package/src/checks/native-build.js +21 -39
  62. package/src/checks/runner.js +128 -142
  63. package/src/cli/advanced-commands.js +6 -6
  64. package/src/cli/register-meta.js +42 -82
  65. package/src/cli/register-pipeline.js +8 -143
  66. package/src/cli/register-plan.js +9 -100
  67. package/src/cli/register-roles-skills.js +1 -1
  68. package/src/cli/retired-commands.js +49 -0
  69. package/src/cli.js +18 -5
  70. package/src/commands/agent-run.js +40 -26
  71. package/src/commands/audit.js +127 -124
  72. package/src/commands/board.js +5 -9
  73. package/src/commands/check.js +29 -16
  74. package/src/commands/doctor.js +2 -0
  75. package/src/commands/env.js +176 -150
  76. package/src/commands/go.js +71 -53
  77. package/src/commands/harden.js +90 -81
  78. package/src/commands/hu-start.js +52 -0
  79. package/src/commands/hu.js +288 -221
  80. package/src/commands/identity.js +111 -78
  81. package/src/commands/init-atril.js +47 -0
  82. package/src/commands/init.js +11 -40
  83. package/src/commands/install-tools.js +70 -85
  84. package/src/commands/main-guard.js +78 -0
  85. package/src/commands/policy.js +226 -184
  86. package/src/commands/qmd.js +27 -21
  87. package/src/commands/rag.js +121 -55
  88. package/src/commands/review-gate.js +421 -341
  89. package/src/commands/skills.js +4 -1
  90. package/src/commands/start.js +4 -5
  91. package/src/commands/steward.js +96 -47
  92. package/src/commands/tournament.js +46 -37
  93. package/src/commands/worktree.js +107 -26
  94. package/src/config/defaults.js +1 -0
  95. package/src/config/loader.js +34 -17
  96. package/src/config/role-resolver.js +1 -0
  97. package/src/config/schema.js +1 -0
  98. package/src/config/secrets.js +36 -0
  99. package/src/config.js +1 -0
  100. package/src/environment/adr.js +16 -2
  101. package/src/git/karajan-label.js +20 -8
  102. package/src/guards/main-guard.js +54 -0
  103. package/src/harden/hook-templates.js +16 -1
  104. package/src/harden/sentinel/posttooluse.mjs +18 -6
  105. package/src/harden/sentinel/pretooluse-sentinel.mjs +64 -36
  106. package/src/harden/sentinel/sentinel-lib.mjs +129 -26
  107. package/src/harden/sentinel/sentinel-shell.mjs +5 -16
  108. package/src/harden/sentinel/sessionstart.mjs +3 -2
  109. package/src/harden/sentinel/stop.mjs +5 -4
  110. package/src/harden/workflow-templates.js +1 -1
  111. package/src/hu/board-lookup.js +29 -0
  112. package/src/hu/gh-identity.js +23 -0
  113. package/src/hu/github-board.js +4 -2
  114. package/src/hu/lane-prune.js +83 -13
  115. package/src/hu/lane-setup.js +65 -0
  116. package/src/hu/lanes.js +66 -0
  117. package/src/hu/team-board-mirror.js +3 -2
  118. package/src/infrastructure/command-runner.js +1 -1
  119. package/src/infrastructure/file-system-service.js +1 -1
  120. package/src/mcp/handlers/direct-handlers.js +2 -292
  121. package/src/mcp/handlers/hu-handlers.js +1 -35
  122. package/src/mcp/handlers/management-handlers.js +2 -42
  123. package/src/mcp/handlers/rag-handler.js +9 -1
  124. package/src/mcp/server-handlers.js +16 -22
  125. package/src/mcp/tools.js +1 -196
  126. package/src/plan/lane-plans.js +54 -0
  127. package/src/plan/plan-store.js +7 -4
  128. package/src/policy/add.js +26 -12
  129. package/src/policy/adr-number-check.js +31 -0
  130. package/src/prompts/audit.js +299 -339
  131. package/src/prompts/card-context.js +10 -5
  132. package/src/prompts/reviewer.js +5 -2
  133. package/src/rag/auto-update.js +71 -44
  134. package/src/rag/bootstrap-index.js +46 -54
  135. package/src/rag/indexer.js +9 -4
  136. package/src/rag/migrate.js +1 -1
  137. package/src/rag/onnx-fallback.js +2 -2
  138. package/src/rag/pid-lease.js +181 -0
  139. package/src/rag/project-store.js +2 -12
  140. package/src/review/card-first.js +57 -46
  141. package/src/review/gate-gitignore.js +23 -4
  142. package/src/review/gate-hook.js +12 -4
  143. package/src/review/one-shot-review.js +45 -35
  144. package/src/review/publish-pr.js +14 -2
  145. package/src/review/repo-docs.js +73 -0
  146. package/src/review/repo-stack.js +59 -0
  147. package/src/review/reviewer-fallback.js +19 -4
  148. package/src/review/seal-commit.js +29 -0
  149. package/src/review/sonar-pregate.js +59 -44
  150. package/src/review/third-party-review.js +90 -16
  151. package/src/review/third-party-size.js +43 -0
  152. package/src/review/third-party-skills.js +54 -0
  153. package/src/review/verify-pr.js +1 -24
  154. package/src/roles/audit-role.js +106 -125
  155. package/src/skills/atril-cli.js +19 -1
  156. package/src/skills/atril-skills.js +5 -4
  157. package/src/skills/skills-search.js +72 -0
  158. package/src/skills/translate.js +62 -0
  159. package/src/sonar/scanner.js +116 -101
  160. package/src/steward/invariants.js +5 -4
  161. package/src/steward/phantom-sweep.js +66 -0
  162. package/src/tournament/judge.js +60 -53
  163. package/src/utils/gh-account.js +37 -0
  164. package/src/utils/git.js +0 -13
  165. package/src/utils/home-migration.js +7 -1
  166. package/src/utils/main-checkout.js +53 -0
  167. package/src/utils/qmd-collection.js +3 -1
  168. package/src/utils/welcome.js +3 -3
  169. package/templates/{skills → atril/commands}/kj-architect.md +5 -0
  170. package/templates/{skills → atril/commands}/kj-audit.md +5 -0
  171. package/templates/{skills → atril/commands}/kj-board.md +5 -0
  172. package/templates/{skills → atril/commands}/kj-code.md +5 -0
  173. package/templates/{skills → atril/commands}/kj-csv-transform.md +5 -0
  174. package/templates/{skills → atril/commands}/kj-data-report.md +5 -0
  175. package/templates/{skills → atril/commands}/kj-discover.md +5 -0
  176. package/templates/{skills → atril/commands}/kj-rag-query.md +5 -0
  177. package/templates/{skills → atril/commands}/kj-review.md +5 -0
  178. package/templates/{skills → atril/commands}/kj-security.md +5 -0
  179. package/templates/{skills → atril/commands}/kj-sonar.md +5 -0
  180. package/templates/{skills → atril/commands}/kj-sql-analysis.md +5 -0
  181. package/templates/{skills → atril/commands}/kj-test.md +5 -0
  182. package/packages/hu-board/public/utils/run-launcher.js +0 -169
  183. package/packages/hu-board/src/run-tracker.js +0 -121
  184. package/src/activity-log.js +0 -59
  185. package/src/agents/host-agent.js +0 -38
  186. package/src/agents/model-registry.js +0 -142
  187. package/src/audit/plan-adherence.js +0 -224
  188. package/src/autonomy/arbiter-role.js +0 -71
  189. package/src/autonomy/arbiter.js +0 -22
  190. package/src/autonomy/decider.js +0 -18
  191. package/src/autonomy/decision-resolver.js +0 -58
  192. package/src/autonomy/outcome-report.js +0 -55
  193. package/src/autonomy/stage-ask.js +0 -30
  194. package/src/brain/decision-tracker.js +0 -125
  195. package/src/brain/decisor.js +0 -243
  196. package/src/brain/solomon-consult.js +0 -171
  197. package/src/brain/standby-scheduler.js +0 -8
  198. package/src/budget/comparison.js +0 -96
  199. package/src/canvas/parse-md.js +0 -238
  200. package/src/canvas/schema.js +0 -101
  201. package/src/canvas/to-flat.js +0 -85
  202. package/src/canvas/validate.js +0 -163
  203. package/src/ci/dispatch.js +0 -99
  204. package/src/ci/index.js +0 -3
  205. package/src/ci/pr-diff.js +0 -26
  206. package/src/cli/register-standby.js +0 -26
  207. package/src/commands/architect.js +0 -111
  208. package/src/commands/autorun.js +0 -49
  209. package/src/commands/researcher.js +0 -54
  210. package/src/commands/resume.js +0 -72
  211. package/src/commands/review.js +0 -89
  212. package/src/commands/run.js +0 -185
  213. package/src/commands/standby.js +0 -117
  214. package/src/commands/status.js +0 -36
  215. package/src/commands/triage.js +0 -78
  216. package/src/commands/undo.js +0 -62
  217. package/src/domains/domain-loader.js +0 -245
  218. package/src/domains/domain-registry.js +0 -140
  219. package/src/domains/domain-synthesizer.js +0 -129
  220. package/src/git/automation.js +0 -433
  221. package/src/git/coder-commit-guard.js +0 -40
  222. package/src/git/hu-automation.js +0 -243
  223. package/src/git/hu-snapshot.js +0 -10
  224. package/src/git/symlink-targets.js +0 -57
  225. package/src/golden/asserter.js +0 -92
  226. package/src/golden/loader.js +0 -52
  227. package/src/golden/runner.js +0 -92
  228. package/src/golden/schema.js +0 -41
  229. package/src/guards/duplicate-members.js +0 -86
  230. package/src/guards/intent-guard.js +0 -161
  231. package/src/guards/perf-guard.js +0 -126
  232. package/src/guards/policy-guard.js +0 -90
  233. package/src/guards/policy-resolver.js +0 -85
  234. package/src/guards/secret-redactor.js +0 -71
  235. package/src/hu/acceptance-runner.js +0 -138
  236. package/src/hu/lazy-planner.js +0 -109
  237. package/src/hu/parallel-executor.js +0 -94
  238. package/src/hu/routing-metrics.js +0 -17
  239. package/src/hu/split-validator.js +0 -143
  240. package/src/hu/splitting-detector.js +0 -158
  241. package/src/hu/splitting-generator.js +0 -175
  242. package/src/hu/store.js +0 -394
  243. package/src/infrastructure/mocks.js +0 -182
  244. package/src/mcp/handlers/run-handler.js +0 -318
  245. package/src/mcp/sovereignty-guard.js +0 -113
  246. package/src/mcp/tool-arg-normalizers.js +0 -16
  247. package/src/mutate/reviewer-signal.js +0 -76
  248. package/src/orchestrator/agent-fallback.js +0 -81
  249. package/src/orchestrator/brain-coordinator.js +0 -186
  250. package/src/orchestrator/ci-integration.js +0 -139
  251. package/src/orchestrator/config-init.js +0 -426
  252. package/src/orchestrator/direct-actions.js +0 -217
  253. package/src/orchestrator/drivers/error-recovery.js +0 -287
  254. package/src/orchestrator/drivers/init-context.js +0 -238
  255. package/src/orchestrator/drivers/iteration-loop.js +0 -382
  256. package/src/orchestrator/drivers/iteration-phases/coder-and-refactorer.js +0 -69
  257. package/src/orchestrator/drivers/iteration-phases/guards.js +0 -91
  258. package/src/orchestrator/drivers/iteration-phases/handle-approved.js +0 -29
  259. package/src/orchestrator/drivers/iteration-phases/quality-gates.js +0 -125
  260. package/src/orchestrator/drivers/iteration-phases/reviewer-gate.js +0 -62
  261. package/src/orchestrator/drivers/post-loop.js +0 -404
  262. package/src/orchestrator/drivers/pre-loop-phases/auto-hu-batch.js +0 -137
  263. package/src/orchestrator/drivers/pre-loop-phases/config-deprecations.js +0 -47
  264. package/src/orchestrator/drivers/pre-loop-phases/ensure-addyosmani-skills.js +0 -84
  265. package/src/orchestrator/drivers/pre-loop-phases/inject-loaded-plan.js +0 -393
  266. package/src/orchestrator/drivers/pre-loop.js +0 -524
  267. package/src/orchestrator/drivers/run-hu-batch.js +0 -468
  268. package/src/orchestrator/feedback-enrichment.js +0 -140
  269. package/src/orchestrator/feedback-queue.js +0 -151
  270. package/src/orchestrator/flow-control.js +0 -170
  271. package/src/orchestrator/flow-runner.js +0 -365
  272. package/src/orchestrator/fs-leak-detector.js +0 -228
  273. package/src/orchestrator/hu-scheduler.js +0 -114
  274. package/src/orchestrator/hu-sub-pipeline.js +0 -642
  275. package/src/orchestrator/iteration-gate.js +0 -73
  276. package/src/orchestrator/iteration-stages.js +0 -9
  277. package/src/orchestrator/parallel-limiter.js +0 -74
  278. package/src/orchestrator/pipeline-context.js +0 -72
  279. package/src/orchestrator/post-loop-stages.js +0 -468
  280. package/src/orchestrator/pre-loop-stages.js +0 -11
  281. package/src/orchestrator/preflight-checks.js +0 -487
  282. package/src/orchestrator/repair/repair-detector.js +0 -117
  283. package/src/orchestrator/repair/repair-runner.js +0 -142
  284. package/src/orchestrator/reviewer-fallback.js +0 -45
  285. package/src/orchestrator/role-output-compressor.js +0 -189
  286. package/src/orchestrator/session-journal.js +0 -308
  287. package/src/orchestrator/solomon-escalation.js +0 -331
  288. package/src/orchestrator/solomon-rules.js +0 -212
  289. package/src/orchestrator/stages/acceptance-coverage-stage.js +0 -47
  290. package/src/orchestrator/stages/architect-stage.js +0 -172
  291. package/src/orchestrator/stages/coder-stage.js +0 -506
  292. package/src/orchestrator/stages/domain-curator-stage.js +0 -82
  293. package/src/orchestrator/stages/format-stage.js +0 -61
  294. package/src/orchestrator/stages/hu-reviewer-phases/detect-and-apply-splits.js +0 -127
  295. package/src/orchestrator/stages/hu-reviewer-stage.js +0 -419
  296. package/src/orchestrator/stages/perf-stage.js +0 -140
  297. package/src/orchestrator/stages/planner-stage.js +0 -85
  298. package/src/orchestrator/stages/rag-context-stage.js +0 -80
  299. package/src/orchestrator/stages/rag-preload-decisor.js +0 -58
  300. package/src/orchestrator/stages/research-stage.js +0 -69
  301. package/src/orchestrator/stages/reviewer-stage.js +0 -431
  302. package/src/orchestrator/stages/sonar-stage.js +0 -439
  303. package/src/orchestrator/stages/stage-classes.js +0 -84
  304. package/src/orchestrator/stages/stage-executor.js +0 -155
  305. package/src/orchestrator/stages/suite-green-stage.js +0 -62
  306. package/src/orchestrator/stages/tdd-discipline-stage.js +0 -56
  307. package/src/orchestrator/stages/tool-judge-stage.js +0 -53
  308. package/src/orchestrator/stages/triage-stage.js +0 -172
  309. package/src/orchestrator/standby.js +0 -71
  310. package/src/orchestrator/verification-gate.js +0 -183
  311. package/src/orchestrator.js +0 -22
  312. package/src/plan/adr-generator.js +0 -170
  313. package/src/plan/adr-loader.js +0 -60
  314. package/src/plan/plan-executor.js +0 -100
  315. package/src/plan/plan-validation.js +0 -4
  316. package/src/prompts/acceptance-coverage.js +0 -32
  317. package/src/prompts/arbiter.js +0 -38
  318. package/src/prompts/architect.js +0 -103
  319. package/src/prompts/hu-reviewer.js +0 -244
  320. package/src/prompts/tool-correctness-judge.js +0 -31
  321. package/src/prompts/triage.js +0 -57
  322. package/src/rag/embedder.js +0 -2
  323. package/src/rag/embedders/_cloud-base.js +0 -2
  324. package/src/rag/embedders/cohere.js +0 -2
  325. package/src/rag/embedders/mistral.js +0 -2
  326. package/src/rag/embedders/onnx.js +0 -2
  327. package/src/rag/embedders/openai.js +0 -2
  328. package/src/rag/embedders/voyage.js +0 -2
  329. package/src/rag/rerank.js +0 -4
  330. package/src/rag/where-parser.js +0 -4
  331. package/src/repeat-detector.js +0 -88
  332. package/src/review/diff-generator.js +0 -112
  333. package/src/review/git-stash-helper.js +0 -104
  334. package/src/review/schema.js +0 -31
  335. package/src/review/scope-filter.js +0 -152
  336. package/src/review/snapshot-diff.js +0 -122
  337. package/src/review/tdd-discipline.js +0 -90
  338. package/src/review/tdd-policy.js +0 -69
  339. package/src/roles/acceptance-coverage-role.js +0 -78
  340. package/src/roles/architect-role.js +0 -77
  341. package/src/roles/coder-role.js +0 -113
  342. package/src/roles/commiter-role.js +0 -113
  343. package/src/roles/domain-curator-role.js +0 -134
  344. package/src/roles/hu-reviewer-role.js +0 -64
  345. package/src/roles/impeccable-role.js +0 -85
  346. package/src/roles/index.js +0 -18
  347. package/src/roles/karajan-brain-role.js +0 -134
  348. package/src/roles/perf-role.js +0 -122
  349. package/src/roles/planner-role.js +0 -151
  350. package/src/roles/refactorer-role.js +0 -30
  351. package/src/roles/repairer-role.js +0 -181
  352. package/src/roles/researcher-role.js +0 -70
  353. package/src/roles/reviewer-role.js +0 -115
  354. package/src/roles/security-role.js +0 -73
  355. package/src/roles/solomon-role.js +0 -271
  356. package/src/roles/sonar-role.js +0 -72
  357. package/src/roles/tester-role.js +0 -235
  358. package/src/roles/tool-correctness-judge-role.js +0 -78
  359. package/src/roles/triage-role.js +0 -99
  360. package/src/session/journal/decisions-writer.js +0 -200
  361. package/src/session/journal/iteration-logger.js +0 -194
  362. package/src/session/journal/summary-writer.js +0 -320
  363. package/src/session/journal/tree-writer.js +0 -164
  364. package/src/session/journal/write-all.js +0 -89
  365. package/src/session-cleanup.js +0 -116
  366. package/src/skills/semantic-detector.js +0 -167
  367. package/src/skills/skill-detector.js +0 -385
  368. package/src/sonar/cloud-scanner.js +0 -78
  369. package/src/tracker-bootstrap.js +0 -44
  370. package/src/types/index.js +0 -38
  371. package/src/types/policy.js +0 -43
  372. package/src/types/solomon.js +0 -87
  373. package/src/utils/async-lock.js +0 -31
  374. package/src/utils/budget.js +0 -229
  375. package/src/utils/cwd-confirm.js +0 -39
  376. package/src/utils/display/event-handlers.js +0 -403
  377. package/src/utils/display/formatters.js +0 -132
  378. package/src/utils/display/header.js +0 -47
  379. package/src/utils/display/session-summary.js +0 -118
  380. package/src/utils/display/solomon.js +0 -42
  381. package/src/utils/pricing.js +0 -29
  382. package/src/utils/quota-simulator.js +0 -56
  383. package/src/utils/role-assigner.js +0 -137
  384. package/src/utils/rtk-wrapper.js +0 -87
  385. package/src/utils/run-registry.js +0 -9
  386. package/src/utils/status-dashboard.js +0 -254
  387. package/src/utils/tool-call-extractor.js +0 -56
  388. package/src/webperf/devtools-detect.js +0 -112
  389. package/src/webperf/webperf-role-integration.js +0 -66
  390. package/templates/skills/kj-run.md +0 -73
@@ -1,137 +0,0 @@
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, boardSlug } = await import("../../../commands/board.js");
126
- const desiredPort = session.config_snapshot?.hu_board?.port ?? 4000;
127
- // KJC-TSK-0884: its project's scoped view, not the global dashboard.
128
- const boardResult = await startBoard(desiredPort, { projectSlug: projectDir ? boardSlug(projectDir) : null });
129
- const url = boardResult.url;
130
- const status = boardResult.alreadyRunning ? "already running" : "started";
131
- const projectName = batch.projectName || "Auto-generated HUs";
132
- console.log(renderBoardBanner({ url, status, projectName }));
133
- logger.info(`HU Board ${status} at ${url} (project: ${projectName})`);
134
- } catch (err) {
135
- logger.warn(`HU Board auto-start failed (non-blocking): ${err.message}`);
136
- }
137
- }
@@ -1,47 +0,0 @@
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
- }
@@ -1,84 +0,0 @@
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
- }
@@ -1,393 +0,0 @@
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
- import { keepRoutingMetrics } from "../../../hu/routing-metrics.js";
37
-
38
- /**
39
- * @param {object} args
40
- * @param {object} args.flags — runFlow flags (reads flags.plan, flags.hu)
41
- * @param {object} args.updatedConfig — the running config (reads projectDir)
42
- * @param {object} args.session
43
- * @param {object} args.stageResults — mutated: researcher / architect / planner / huReviewer slots
44
- * @param {object} args.eventBase
45
- * @param {object} args.emitter
46
- * @param {object} args.logger
47
- * @param {string} args.task — original task description, used to build plannedTask
48
- * @returns {Promise<{handled: boolean, plannedTask: string|null}>}
49
- */
50
- export async function injectLoadedPlan({ flags, updatedConfig, session, stageResults, eventBase, emitter, logger, task }) {
51
- if (!flags.plan) return { handled: false, plannedTask: null };
52
-
53
- try {
54
- const { loadPlan, savePlan: savePlanToDisk } = await import("../../../plan/plan-store.js");
55
- const { isPlanV2 } = await import("../../../plan/plan-schema.js");
56
- const { planToHuBatch, syncResultsToPlan } = await import("../../../plan/plan-executor.js");
57
- const projectDir = updatedConfig.projectDir || process.cwd();
58
- const loadedPlan = await loadPlan(projectDir, flags.plan);
59
- if (!loadedPlan) {
60
- logger.warn(`Plan ${flags.plan} not found — falling back to normal pipeline`);
61
- return { handled: false, plannedTask: null };
62
- }
63
-
64
- logger.info(`Loaded persisted plan: ${flags.plan} (v${loadedPlan.version || 1})`);
65
- emitProgress(emitter, makeEvent("plan:loaded", { ...eventBase, stage: "plan" }, {
66
- message: `Plan loaded: ${flags.plan}`,
67
- detail: { planId: flags.plan, task: loadedPlan.task, version: loadedPlan.version, huCount: loadedPlan.hus?.length || 0 }
68
- }));
69
-
70
- // Zombie reconciliation: a previous run may have died mid-flight,
71
- // leaving HUs stuck in coding/reviewing/running. The board-side
72
- // reaper only runs inside the board; without this, a headless
73
- // `kj run --plan` would refuse to pick those HUs up.
74
- const reaped = await reconcilePlanHuZombies({ loadedPlan, planId: flags.plan, logger });
75
- if (reaped > 0) {
76
- await savePlanToDisk(projectDir, loadedPlan);
77
- emitProgress(emitter, makeEvent("plan:zombies-reaped", { ...eventBase, stage: "plan" }, {
78
- message: `Reset ${reaped} stale HU(s) from a previous run that did not finish`,
79
- detail: { planId: flags.plan, count: reaped }
80
- }));
81
- }
82
- stageResults.researcher = { ok: true, summary: "Loaded from persisted plan", fromPlan: flags.plan };
83
- stageResults.architect = { ok: true, summary: "Loaded from persisted plan", fromPlan: flags.plan };
84
- stageResults.planner = { ok: true, summary: "Loaded from persisted plan", fromPlan: flags.plan };
85
-
86
- // Tests-first gate (v2.7.5): refuse to run a plan when any HU
87
- // has no acceptance_tests. Mirrors the gate in `kj plan ready`
88
- // so the CLI path and the board's "▶ Run plan" button enforce
89
- // the same contract.
90
- if (isPlanV2(loadedPlan) && Array.isArray(loadedPlan.hus)) {
91
- assertEveryHuHasTests({ loadedPlan, planId: flags.plan, eventBase, emitter, logger });
92
-
93
- // PR-E (auto-certify + anomaly guards): the user expects
94
- // pending HUs to be runnable; "certified" was an invisible
95
- // wall. Helpers in plan-hu-ops.js so they can be unit-tested
96
- // without spinning up the orchestrator. Pulled from the same
97
- // dynamic import we use a few lines below for updateHuStatus +
98
- // setHuOutcome — no extra import budget.
99
- const planHuOps = await import("../../../plan/plan-hu-ops.js");
100
- await autoCertifyAndAssertRunnable({ planHuOps, loadedPlan, planId: flags.plan, projectDir, savePlanToDisk, eventBase, emitter, logger });
101
-
102
- // v2 plan with HUs → inject as huReviewer so sub-pipeline
103
- // picks them up. Sets every callback (sync, live status, live
104
- // outcome) and stamps plan.status = "running".
105
- if (loadedPlan.hus?.length > 0) {
106
- const huBatch = planToHuBatch(loadedPlan);
107
- applyHuFilterIfRequested({ huBatch, flags, eventBase, emitter, logger });
108
- if (huBatch.ok) {
109
- stageResults.huReviewer = huBatch;
110
- wirePlanCallbacks({ session, loadedPlan, projectDir, syncResultsToPlan, savePlanToDisk, planHuOps, logger });
111
- logger.info(`Plan ${flags.plan}: ${huBatch.certified} HUs ready for execution`);
112
- emitProgress(emitter, makeEvent("plan:hus-loaded", { ...eventBase, stage: "plan" }, {
113
- message: `${huBatch.certified} HUs loaded from plan`,
114
- detail: { planId: flags.plan, total: huBatch.total, certified: huBatch.certified }
115
- }));
116
- loadedPlan.status = "running";
117
- await savePlanToDisk(projectDir, loadedPlan);
118
- }
119
- }
120
- }
121
-
122
- // Inject contexts that the legacy planning phases would have
123
- // produced. Then build a plannedTask string for the non-HU /
124
- // fallback iteration path.
125
- const ctx = loadedPlan.context || {};
126
- setPreLoopContext(session, {
127
- research: ctx.researchContext || loadedPlan.researchContext || null,
128
- architect: ctx.architectContext || loadedPlan.architectContext || null,
129
- plan: loadedPlan.approach || loadedPlan.plan || null,
130
- });
131
- await saveSession(session);
132
-
133
- return { handled: true, plannedTask: buildPlannedTaskFromLoadedPlan({ task, loadedPlan }) };
134
- } catch (err) {
135
- logger.warn(`Plan loading failed: ${err.message} — falling back to normal pipeline`);
136
- return { handled: false, plannedTask: null };
137
- }
138
- }
139
-
140
- /**
141
- * Reset HUs that look mid-execution but have no live run behind them.
142
- *
143
- * Resilience audit, Phase 3 (Hallazgo A1 / status-zombi). A previous
144
- * `kj run --plan` flipped HUs to `coding`/`reviewing`/`running` in the
145
- * plan JSON, then died (SIGKILL, terminal closed, OOM, crash). The
146
- * board-side `hu-zombie-reaper` only queries SQLite and only runs
147
- * inside the board process, so the plan JSON on disk kept stale
148
- * in-flight statuses forever. The next `kj run --plan` then refused to
149
- * pick those HUs up.
150
- *
151
- * Cross-check via `run-registry`: if there is a live run with our
152
- * planId, do NOT touch — that run owns the in-flight HUs. Otherwise
153
- * any in-flight status with no live owner is a zombie → reset to
154
- * `pending` so it can run again.
155
- *
156
- * Mutates `loadedPlan.hus` in place. Returns the number of HUs reset
157
- * so the caller can persist + emit a progress event.
158
- *
159
- * @param {object} args
160
- * @param {object} args.loadedPlan
161
- * @param {string} args.planId
162
- * @param {object} [args.logger]
163
- * @returns {number}
164
- */
165
- export function reconcilePlanHuZombies({ loadedPlan, planId, logger }) {
166
- if (!Array.isArray(loadedPlan?.hus) || loadedPlan.hus.length === 0) return 0;
167
- const activeRuns = listActiveRuns({ planId });
168
- if (activeRuns.length > 0) return 0;
169
- const ZOMBIE_STATES = new Set(["coding", "reviewing", "running"]);
170
- let reaped = 0;
171
- for (const hu of loadedPlan.hus) {
172
- if (ZOMBIE_STATES.has(hu.status)) {
173
- const previous = hu.status;
174
- hu.status = "pending";
175
- reaped += 1;
176
- logger?.warn?.(`[plan] HU ${hu.id || hu.hu_id || "?"} was '${previous}' but no live run owns the plan — reset to 'pending'.`);
177
- }
178
- }
179
- return reaped;
180
- }
181
-
182
- /**
183
- * Throws when any HU lacks an acceptance_tests array with at least
184
- * one entry. Logs + emits the same `plan:tests-missing` progress
185
- * event as the inlined version did.
186
- */
187
- function assertEveryHuHasTests({ loadedPlan, planId, eventBase, emitter, logger }) {
188
- const missing = loadedPlan.hus.filter(
189
- (h) => !Array.isArray(h.acceptance_tests) || h.acceptance_tests.length === 0
190
- );
191
- if (missing.length === 0) return;
192
- const offenders = missing.map((h) => ` - ${h.id} ${(h.title || "").slice(0, 60)}`).join("\n");
193
- const msg =
194
- `Refusing to run plan ${planId}: ${missing.length}/${loadedPlan.hus.length} HU(s) have no acceptance_tests.\n`
195
- + `${offenders}\n\n`
196
- + "Tests-first flow requires a test contract per HU. Edit each HU on the board\n"
197
- + "(http://localhost:4000) via the ✎ Edit button, or re-run `kj plan` to regenerate.";
198
- logger.error(msg);
199
- emitProgress(emitter, makeEvent("plan:tests-missing", { ...eventBase, stage: "plan" }, {
200
- status: "fail",
201
- message: `Plan ${planId}: ${missing.length} HU(s) missing acceptance_tests`,
202
- detail: { planId, missingHuIds: missing.map((h) => h.id) }
203
- }));
204
- throw new Error(`Plan ${planId} has ${missing.length} HU(s) with no acceptance_tests — see the run log for details.`);
205
- }
206
-
207
- /**
208
- * Promotes pending HUs with tests to certified, then asserts at
209
- * least one HU is runnable. Throws (with `plan:no-runnable` event)
210
- * when nothing is left to do, so the orchestrator never silently
211
- * falls back to single-task mode and burns tokens.
212
- */
213
- async function autoCertifyAndAssertRunnable({ planHuOps, loadedPlan, planId, projectDir, savePlanToDisk, eventBase, emitter, logger }) {
214
- const { autoCertifyPendingHus, assertPlanRunnable } = planHuOps;
215
- const promoted = autoCertifyPendingHus(loadedPlan);
216
- if (promoted > 0) {
217
- await savePlanToDisk(projectDir, loadedPlan);
218
- logger.info(`Plan ${planId}: auto-certified ${promoted} pending HU(s) — ready to run`);
219
- emitProgress(emitter, makeEvent("plan:auto-certified", { ...eventBase, stage: "plan" }, {
220
- message: `Auto-certificadas ${promoted} HU(s) pendientes`,
221
- detail: { planId, count: promoted }
222
- }));
223
- }
224
- try {
225
- assertPlanRunnable(loadedPlan, planId);
226
- } catch (err) {
227
- logger.error(err.message);
228
- emitProgress(emitter, makeEvent("plan:no-runnable", { ...eventBase, stage: "plan" }, {
229
- status: "fail", message: err.message,
230
- detail: { planId }
231
- }));
232
- throw err;
233
- }
234
- }
235
-
236
- /**
237
- * PR4: when --hu <ids> is set, narrow the batch to those HU ids by
238
- * marking the rest as `done` in memory. The plan JSON on disk is
239
- * NOT touched — this is a run-time filter only. Throws when none of
240
- * the requested ids exist.
241
- */
242
- function applyHuFilterIfRequested({ huBatch, flags, eventBase, emitter, logger }) {
243
- if (!huBatch.ok || !flags.hu) return;
244
- const requested = String(flags.hu).split(",").map((s) => s.trim()).filter(Boolean);
245
- if (requested.length === 0) {
246
- logger.warn(`--hu was empty after parsing — falling back to running every certified HU`);
247
- return;
248
- }
249
- // Humanización IDs: resolver cada ref a un hu.id largo aceptando 3 formas:
250
- // 1. `hu_<planId>_<NNN>` (id largo, exacto)
251
- // 2. `INFRA-001` (short_id que el planner asignó)
252
- // 3. `001` (sufijo numérico — busca un hu.id que termine en `_001`)
253
- // El resolver es case-insensitive para short_id y sufijo.
254
- const resolvedIds = new Set();
255
- const unresolved = [];
256
- for (const ref of requested) {
257
- const id = resolveHuRef(ref, huBatch.stories);
258
- if (id) resolvedIds.add(id);
259
- else unresolved.push(ref);
260
- }
261
- const found = huBatch.stories.filter((s) => resolvedIds.has(s.id));
262
- if (unresolved.length > 0) {
263
- logger.warn(`--hu: ignored ${unresolved.length} unknown HU ref(s): ${unresolved.join(", ")}`);
264
- }
265
- if (found.length === 0) {
266
- throw new Error(`--hu: none of the requested HU ref(s) exist in plan ${flags.plan}: ${requested.join(", ")}`);
267
- }
268
- for (const story of huBatch.stories) {
269
- if (!resolvedIds.has(story.id)) story.status = "done";
270
- }
271
- huBatch.certified = found.length;
272
- logger.info(`Plan ${flags.plan}: --hu filter active — ${found.length}/${huBatch.total} HU(s) will run (${found.map((s) => s.id).join(", ")})`);
273
- emitProgress(emitter, makeEvent("plan:hu-filter", { ...eventBase, stage: "plan" }, {
274
- message: `--hu filter: ${found.length} HU(s) selected, ${huBatch.total - found.length} skipped`,
275
- detail: { planId: flags.plan, requested, ignored: unresolved, willRun: found.map((s) => s.id) }
276
- }));
277
- }
278
-
279
- /**
280
- * Wire the four session-scoped callbacks the run will invoke as it
281
- * progresses: PR1 live status (per HU), PR3 live outcome (per HU
282
- * exit), and the syncResultsToPlan rollup (end-of-run).
283
- */
284
- function wirePlanCallbacks({ session, loadedPlan, projectDir, syncResultsToPlan, savePlanToDisk, planHuOps, logger }) {
285
- setPlanRef(session, { planId: loadedPlan.planId, projectDir });
286
- setPlanSyncCallback(session, async (subResult) => {
287
- syncResultsToPlan(loadedPlan, subResult);
288
- await savePlanToDisk(projectDir, loadedPlan);
289
- });
290
- // PR1: live HU status. Without this the plan only updates at
291
- // end-of-run and the board's Kanban looks frozen during execution.
292
- // (`planHuOps` was loaded above for PR-E auto-certify — reuse the
293
- // reference to stay under the dynamic-import budget.)
294
- const { updateHuStatus: updateHuStatusFn, setHuOutcome: setHuOutcomeFn } = planHuOps;
295
- setLiveStatusUpdater(session, async (huId, status) => {
296
- try {
297
- if (!updateHuStatusFn(loadedPlan, huId, status)) return;
298
- await savePlanToDisk(projectDir, loadedPlan);
299
- } catch (err) {
300
- logger?.warn?.(`Live status update failed for ${huId}: ${err.message}`);
301
- }
302
- });
303
- // PR3: per-HU outcome. Stamped once per HU at runSingleHu's exit
304
- // so the board can show the 📄 indicator as soon as each HU
305
- // finishes (iterations / duration / commits / blockers / summary).
306
- //
307
- // Cost D (KJC-TSK-0515): the outcome may carry `cost_usd` (USD
308
- // spent on this HU). When present and finite, also persist it to
309
- // board.db so the per-card badge (Cost F) and the project total
310
- // chip (Cost G) have data to show — without this write, the
311
- // `cost_usd` column would always stay NULL and the UI would render
312
- // nothing. Lazy-import to keep the pre-loop phase free of board
313
- // deps until we actually need them.
314
- setLiveOutcomeUpdater(session, async (huId, outcome) => {
315
- try {
316
- if (!setHuOutcomeFn(loadedPlan, huId, outcome)) return;
317
- keepRoutingMetrics(loadedPlan, huId, outcome); // KJC-TSK-0702: the measured fields core does not keep
318
- await savePlanToDisk(projectDir, loadedPlan);
319
- if (outcome && Number.isFinite(outcome.cost_usd)) {
320
- try {
321
- const { ensureDb, setStoryCost, setStoryCachedTokens } = await import("../../../../packages/hu-board/src/db.js");
322
- ensureDb(); // KJC-BUG-0090: autorun/run may not have started the board
323
- setStoryCost(huId, outcome.cost_usd);
324
- // Φ0-G (KJC-TSK-0525): cached/in tokens travel in the same
325
- // outcome blob as cost. Write them together so the board
326
- // can render the "🎯 N% cached" badge on the same render.
327
- // Either may be null (older runs, no cache) — setter handles it.
328
- if (Number.isFinite(outcome.cached_tokens) || Number.isFinite(outcome.tokens_in)) {
329
- setStoryCachedTokens(
330
- huId,
331
- Number.isFinite(outcome.cached_tokens) ? outcome.cached_tokens : null,
332
- Number.isFinite(outcome.tokens_in) ? outcome.tokens_in : null,
333
- );
334
- }
335
- } catch (err) {
336
- logger?.warn?.(`Cost write failed for ${huId}: ${err.message}`);
337
- }
338
- }
339
- } catch (err) {
340
- logger?.warn?.(`Live outcome update failed for ${huId}: ${err.message}`);
341
- }
342
- });
343
- }
344
-
345
- /**
346
- * Resuelve una referencia humana a HU al `id` canónico largo. Acepta:
347
- * - id largo exacto: `hu_<planId>_<NNN>` → match directo.
348
- * - short_id: `INFRA-001`, `AUTH-SIGNUP` → match case-insensitive contra hu.short_id.
349
- * - sufijo numérico: `001` → match contra hu.id terminado en `_001`.
350
- * Devuelve el hu.id largo o null si no resuelve.
351
- *
352
- * @param {string} ref
353
- * @param {Array<{id: string, short_id?: string|null}>} hus
354
- * @returns {string|null}
355
- */
356
- function resolveHuRef(ref, hus) {
357
- if (!ref || !Array.isArray(hus)) return null;
358
- // Match exacto contra id largo (camino caliente).
359
- const exact = hus.find((s) => s.id === ref);
360
- if (exact) return exact.id;
361
- // Match case-insensitive contra short_id.
362
- const lower = ref.toLowerCase();
363
- const byShort = hus.find((s) => s.short_id && String(s.short_id).toLowerCase() === lower);
364
- if (byShort) return byShort.id;
365
- // Match por sufijo numérico (`001` → cualquier hu cuyo id termine en `_001`).
366
- if (/^\d+$/.test(ref)) {
367
- const padded = ref.padStart(3, "0");
368
- const bySuffix = hus.find((s) => s.id.endsWith(`_${padded}`));
369
- if (bySuffix) return bySuffix.id;
370
- }
371
- return null;
372
- }
373
-
374
- /**
375
- * Build a single plannedTask string for the non-HU / fallback
376
- * iteration path. The HU sub-pipeline doesn't read this; only the
377
- * legacy single-iteration loop does. Identical to the inlined
378
- * version's behaviour.
379
- */
380
- function buildPlannedTaskFromLoadedPlan({ task, loadedPlan }) {
381
- const plan = loadedPlan.plan || (loadedPlan.approach ? { approach: loadedPlan.approach } : null);
382
- if (plan && typeof plan === "object" && plan.steps) {
383
- const stepList = plan.steps.map((s, idx) => `${idx + 1}. ${s.description || s}`).join("\n");
384
- return `${task}\n\n## Implementation Plan\n${plan.approach || ""}\n\n## Steps\n${stepList}`;
385
- }
386
- if (typeof plan === "string") {
387
- return `${task}\n\n## Implementation Plan\n${plan}`;
388
- }
389
- if (loadedPlan.approach) {
390
- return `${task}\n\n## Approach\n${loadedPlan.approach}`;
391
- }
392
- return task;
393
- }