karajan-code 4.70.3 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (388) hide show
  1. package/README.md +2 -2
  2. package/package.json +6 -14
  3. package/packages/hu-board/package.json +1 -1
  4. package/packages/hu-board/public/app.js +4 -18
  5. package/packages/hu-board/public/index.html +5 -2
  6. package/packages/hu-board/public/styles.css +26 -0
  7. package/packages/hu-board/public/utils/api.js +0 -74
  8. package/packages/hu-board/public/utils/board-tour.js +62 -0
  9. package/packages/hu-board/public/utils/board-view.js +49 -144
  10. package/packages/hu-board/public/utils/command-launcher.js +8 -70
  11. package/packages/hu-board/public/utils/formatters.js +10 -0
  12. package/packages/hu-board/public/utils/governance-view.js +13 -1
  13. package/packages/hu-board/public/utils/hu-actions.js +17 -68
  14. package/packages/hu-board/public/utils/init-listeners.js +7 -8
  15. package/packages/hu-board/public/utils/log-panel.js +3 -16
  16. package/packages/hu-board/public/utils/maggle-mode.js +2 -12
  17. package/packages/hu-board/public/utils/modals.js +7 -0
  18. package/packages/hu-board/public/utils/project-picker-view.js +1 -1
  19. package/packages/hu-board/public/utils/server-push.js +3 -22
  20. package/packages/hu-board/public/utils/shared-board-notice.js +33 -0
  21. package/packages/hu-board/public/utils/story-detail-view.js +5 -19
  22. package/packages/hu-board/src/cleanup-zombies.js +0 -1
  23. package/packages/hu-board/src/command-runner.js +36 -25
  24. package/packages/hu-board/src/db-connection.js +282 -0
  25. package/packages/hu-board/src/db-paths.js +113 -0
  26. package/packages/hu-board/src/db-queries.js +188 -0
  27. package/packages/hu-board/src/db-tombstones.js +51 -0
  28. package/packages/hu-board/src/db.js +28 -619
  29. package/packages/hu-board/src/ephemeral-cleaner.js +2 -3
  30. package/packages/hu-board/src/hu-zombie-reaper.js +5 -1
  31. package/packages/hu-board/src/plan-mutations.js +3 -153
  32. package/packages/hu-board/src/plans-service.js +55 -0
  33. package/packages/hu-board/src/routes/api/commands.js +122 -0
  34. package/packages/hu-board/src/routes/api/plans.js +55 -0
  35. package/packages/hu-board/src/routes/api/projects.js +267 -0
  36. package/packages/hu-board/src/routes/api/prompts.js +135 -0
  37. package/packages/hu-board/src/routes/api/sessions.js +95 -0
  38. package/packages/hu-board/src/routes/api/stories.js +183 -0
  39. package/packages/hu-board/src/routes/api/system.js +241 -0
  40. package/packages/hu-board/src/routes/api/tombstones.js +38 -0
  41. package/packages/hu-board/src/routes/api.js +24 -1371
  42. package/packages/hu-board/src/routes/governance.js +23 -1
  43. package/packages/hu-board/src/routes/lanes.js +40 -0
  44. package/packages/hu-board/src/routes/rag.js +4 -3
  45. package/packages/hu-board/src/server.js +20 -34
  46. package/packages/hu-board/src/sync-files.js +318 -0
  47. package/packages/hu-board/src/sync-project.js +99 -0
  48. package/packages/hu-board/src/sync.js +22 -566
  49. package/packages/hu-board/src/zombie-reaper.js +1 -1
  50. package/scripts/dual-publish.mjs +19 -3
  51. package/scripts/postinstall.js +15 -2
  52. package/scripts/verify-pack-mcp.mjs +11 -7
  53. package/scripts/verify-pack.mjs +4 -4
  54. package/src/audit/audit-history.js +3 -2
  55. package/src/audit/dead-exports.js +68 -59
  56. package/src/audit/deterministic-summary.js +33 -13
  57. package/src/audit/npm-signatures.js +41 -0
  58. package/src/audit/sbom.js +35 -0
  59. package/src/checks/agent-token.js +112 -0
  60. package/src/checks/ai-surface.js +9 -7
  61. package/src/checks/config-files.js +8 -7
  62. package/src/checks/dir-setup.js +34 -1
  63. package/src/checks/native-build.js +21 -39
  64. package/src/checks/runner.js +128 -142
  65. package/src/cli/advanced-commands.js +6 -6
  66. package/src/cli/register-meta.js +42 -82
  67. package/src/cli/register-pipeline.js +8 -140
  68. package/src/cli/register-plan.js +9 -100
  69. package/src/cli/retired-commands.js +49 -0
  70. package/src/cli.js +18 -5
  71. package/src/commands/agent-run.js +40 -26
  72. package/src/commands/audit.js +127 -124
  73. package/src/commands/board.js +5 -9
  74. package/src/commands/check.js +29 -16
  75. package/src/commands/doctor.js +2 -0
  76. package/src/commands/env.js +176 -148
  77. package/src/commands/go.js +76 -55
  78. package/src/commands/harden.js +105 -86
  79. package/src/commands/hu-start.js +52 -0
  80. package/src/commands/hu.js +288 -221
  81. package/src/commands/identity.js +111 -78
  82. package/src/commands/init-atril.js +47 -0
  83. package/src/commands/init.js +11 -40
  84. package/src/commands/install-tools.js +70 -85
  85. package/src/commands/main-guard.js +78 -0
  86. package/src/commands/policy.js +226 -184
  87. package/src/commands/qmd.js +27 -21
  88. package/src/commands/rag.js +75 -48
  89. package/src/commands/review-gate.js +422 -339
  90. package/src/commands/start.js +4 -5
  91. package/src/commands/steward.js +96 -47
  92. package/src/commands/tournament.js +46 -37
  93. package/src/commands/worktree.js +94 -25
  94. package/src/config/loader.js +34 -17
  95. package/src/config/overrides.js +2 -1
  96. package/src/config/secrets.js +36 -0
  97. package/src/config.js +1 -0
  98. package/src/environment/adr.js +16 -2
  99. package/src/environment/board-access.js +11 -9
  100. package/src/git/karajan-label.js +20 -8
  101. package/src/guards/main-guard.js +54 -0
  102. package/src/harden/branch-protection.js +36 -3
  103. package/src/harden/hook-templates.js +16 -1
  104. package/src/harden/sentinel/posttooluse.mjs +18 -6
  105. package/src/harden/sentinel/pretooluse-sentinel.mjs +81 -35
  106. package/src/harden/sentinel/sentinel-lib.mjs +149 -23
  107. package/src/harden/sentinel/sentinel-shell.mjs +64 -0
  108. package/src/harden/sentinel/sessionstart.mjs +3 -2
  109. package/src/harden/sentinel/stop.mjs +5 -4
  110. package/src/harden/workflow-engine.js +12 -1
  111. package/src/harden/workflow-templates.js +1 -1
  112. package/src/hu/board-lookup.js +29 -0
  113. package/src/hu/gh-identity.js +23 -0
  114. package/src/hu/github-board.js +4 -2
  115. package/src/hu/lane-prune.js +15 -7
  116. package/src/hu/lane-setup.js +65 -0
  117. package/src/hu/lanes.js +66 -0
  118. package/src/hu/team-board-mirror.js +3 -2
  119. package/src/infrastructure/command-runner.js +1 -1
  120. package/src/infrastructure/file-system-service.js +1 -1
  121. package/src/mcp/handlers/direct-handlers.js +2 -292
  122. package/src/mcp/handlers/hu-handlers.js +44 -55
  123. package/src/mcp/handlers/management-handlers.js +2 -42
  124. package/src/mcp/server-handlers.js +16 -22
  125. package/src/mcp/tools.js +5 -200
  126. package/src/plan/lane-plans.js +54 -0
  127. package/src/plan/plan-store.js +7 -4
  128. package/src/policy/add.js +26 -12
  129. package/src/policy/adr-number-check.js +31 -0
  130. package/src/prompts/audit.js +299 -339
  131. package/src/prompts/card-context.js +10 -5
  132. package/src/prompts/reviewer.js +5 -2
  133. package/src/rag/auto-update.js +56 -43
  134. package/src/rag/bootstrap-index.js +28 -54
  135. package/src/rag/migrate.js +1 -1
  136. package/src/rag/onnx-fallback.js +2 -2
  137. package/src/rag/pid-lease.js +70 -0
  138. package/src/rag/project-store.js +2 -12
  139. package/src/review/card-first.js +57 -46
  140. package/src/review/dual-review.js +5 -4
  141. package/src/review/gate-gitignore.js +23 -4
  142. package/src/review/gate-hook.js +47 -0
  143. package/src/review/one-shot-review.js +51 -37
  144. package/src/review/publish-pr.js +18 -3
  145. package/src/review/repo-docs.js +73 -0
  146. package/src/review/repo-stack.js +59 -0
  147. package/src/review/reviewer-fallback.js +34 -3
  148. package/src/review/seal-commit.js +29 -0
  149. package/src/review/sonar-pregate.js +59 -44
  150. package/src/review/third-party-review.js +90 -16
  151. package/src/review/third-party-size.js +43 -0
  152. package/src/review/third-party-skills.js +54 -0
  153. package/src/review/verify-pr.js +2 -26
  154. package/src/roles/audit-role.js +106 -125
  155. package/src/skills/atril-skills.js +5 -4
  156. package/src/sonar/scanner.js +116 -101
  157. package/src/steward/invariants.js +5 -4
  158. package/src/steward/phantom-sweep.js +66 -0
  159. package/src/tournament/judge.js +60 -53
  160. package/src/utils/agent-home.js +33 -0
  161. package/src/utils/gh-account.js +37 -0
  162. package/src/utils/git.js +0 -13
  163. package/src/utils/home-migration.js +7 -1
  164. package/src/utils/main-checkout.js +53 -0
  165. package/src/utils/qmd-collection.js +3 -1
  166. package/src/utils/welcome.js +3 -3
  167. package/templates/{skills → atril/commands}/kj-architect.md +5 -0
  168. package/templates/{skills → atril/commands}/kj-audit.md +5 -0
  169. package/templates/{skills → atril/commands}/kj-board.md +5 -0
  170. package/templates/{skills → atril/commands}/kj-code.md +5 -0
  171. package/templates/{skills → atril/commands}/kj-csv-transform.md +5 -0
  172. package/templates/{skills → atril/commands}/kj-data-report.md +5 -0
  173. package/templates/{skills → atril/commands}/kj-discover.md +5 -0
  174. package/templates/{skills → atril/commands}/kj-rag-query.md +5 -0
  175. package/templates/{skills → atril/commands}/kj-review.md +5 -0
  176. package/templates/{skills → atril/commands}/kj-security.md +5 -0
  177. package/templates/{skills → atril/commands}/kj-sonar.md +5 -0
  178. package/templates/{skills → atril/commands}/kj-sql-analysis.md +5 -0
  179. package/templates/{skills → atril/commands}/kj-test.md +5 -0
  180. package/packages/hu-board/public/utils/run-launcher.js +0 -169
  181. package/packages/hu-board/src/run-tracker.js +0 -121
  182. package/src/activity-log.js +0 -59
  183. package/src/agents/host-agent.js +0 -38
  184. package/src/agents/model-registry.js +0 -142
  185. package/src/audit/plan-adherence.js +0 -224
  186. package/src/autonomy/arbiter-role.js +0 -71
  187. package/src/autonomy/arbiter.js +0 -22
  188. package/src/autonomy/decider.js +0 -18
  189. package/src/autonomy/decision-resolver.js +0 -58
  190. package/src/autonomy/outcome-report.js +0 -55
  191. package/src/autonomy/stage-ask.js +0 -30
  192. package/src/brain/decision-tracker.js +0 -125
  193. package/src/brain/decisor.js +0 -243
  194. package/src/brain/solomon-consult.js +0 -171
  195. package/src/brain/standby-scheduler.js +0 -8
  196. package/src/budget/comparison.js +0 -96
  197. package/src/canvas/parse-md.js +0 -238
  198. package/src/canvas/schema.js +0 -101
  199. package/src/canvas/to-flat.js +0 -85
  200. package/src/canvas/validate.js +0 -163
  201. package/src/ci/dispatch.js +0 -99
  202. package/src/ci/index.js +0 -3
  203. package/src/ci/pr-diff.js +0 -26
  204. package/src/cli/register-standby.js +0 -26
  205. package/src/commands/architect.js +0 -111
  206. package/src/commands/autorun.js +0 -49
  207. package/src/commands/researcher.js +0 -54
  208. package/src/commands/resume.js +0 -72
  209. package/src/commands/review.js +0 -89
  210. package/src/commands/run.js +0 -185
  211. package/src/commands/standby.js +0 -117
  212. package/src/commands/status.js +0 -36
  213. package/src/commands/triage.js +0 -78
  214. package/src/commands/undo.js +0 -62
  215. package/src/domains/domain-loader.js +0 -245
  216. package/src/domains/domain-registry.js +0 -140
  217. package/src/domains/domain-synthesizer.js +0 -129
  218. package/src/git/automation.js +0 -433
  219. package/src/git/coder-commit-guard.js +0 -40
  220. package/src/git/hu-automation.js +0 -243
  221. package/src/git/hu-snapshot.js +0 -10
  222. package/src/git/symlink-targets.js +0 -57
  223. package/src/golden/asserter.js +0 -92
  224. package/src/golden/loader.js +0 -52
  225. package/src/golden/runner.js +0 -92
  226. package/src/golden/schema.js +0 -41
  227. package/src/guards/duplicate-members.js +0 -86
  228. package/src/guards/intent-guard.js +0 -161
  229. package/src/guards/perf-guard.js +0 -126
  230. package/src/guards/policy-guard.js +0 -90
  231. package/src/guards/policy-resolver.js +0 -85
  232. package/src/guards/secret-redactor.js +0 -71
  233. package/src/hu/acceptance-runner.js +0 -138
  234. package/src/hu/lazy-planner.js +0 -109
  235. package/src/hu/parallel-executor.js +0 -94
  236. package/src/hu/routing-metrics.js +0 -17
  237. package/src/hu/split-validator.js +0 -143
  238. package/src/hu/splitting-detector.js +0 -158
  239. package/src/hu/splitting-generator.js +0 -175
  240. package/src/hu/store.js +0 -394
  241. package/src/infrastructure/mocks.js +0 -182
  242. package/src/mcp/handlers/run-handler.js +0 -318
  243. package/src/mcp/sovereignty-guard.js +0 -113
  244. package/src/mcp/tool-arg-normalizers.js +0 -16
  245. package/src/mutate/reviewer-signal.js +0 -76
  246. package/src/orchestrator/agent-fallback.js +0 -81
  247. package/src/orchestrator/brain-coordinator.js +0 -186
  248. package/src/orchestrator/ci-integration.js +0 -139
  249. package/src/orchestrator/config-init.js +0 -426
  250. package/src/orchestrator/direct-actions.js +0 -217
  251. package/src/orchestrator/drivers/error-recovery.js +0 -287
  252. package/src/orchestrator/drivers/init-context.js +0 -238
  253. package/src/orchestrator/drivers/iteration-loop.js +0 -382
  254. package/src/orchestrator/drivers/iteration-phases/coder-and-refactorer.js +0 -69
  255. package/src/orchestrator/drivers/iteration-phases/guards.js +0 -91
  256. package/src/orchestrator/drivers/iteration-phases/handle-approved.js +0 -29
  257. package/src/orchestrator/drivers/iteration-phases/quality-gates.js +0 -125
  258. package/src/orchestrator/drivers/iteration-phases/reviewer-gate.js +0 -62
  259. package/src/orchestrator/drivers/post-loop.js +0 -404
  260. package/src/orchestrator/drivers/pre-loop-phases/auto-hu-batch.js +0 -137
  261. package/src/orchestrator/drivers/pre-loop-phases/config-deprecations.js +0 -47
  262. package/src/orchestrator/drivers/pre-loop-phases/ensure-addyosmani-skills.js +0 -84
  263. package/src/orchestrator/drivers/pre-loop-phases/inject-loaded-plan.js +0 -393
  264. package/src/orchestrator/drivers/pre-loop.js +0 -524
  265. package/src/orchestrator/drivers/run-hu-batch.js +0 -468
  266. package/src/orchestrator/feedback-enrichment.js +0 -140
  267. package/src/orchestrator/feedback-queue.js +0 -151
  268. package/src/orchestrator/flow-control.js +0 -170
  269. package/src/orchestrator/flow-runner.js +0 -365
  270. package/src/orchestrator/fs-leak-detector.js +0 -228
  271. package/src/orchestrator/hu-scheduler.js +0 -114
  272. package/src/orchestrator/hu-sub-pipeline.js +0 -642
  273. package/src/orchestrator/iteration-gate.js +0 -73
  274. package/src/orchestrator/iteration-stages.js +0 -9
  275. package/src/orchestrator/parallel-limiter.js +0 -74
  276. package/src/orchestrator/pipeline-context.js +0 -72
  277. package/src/orchestrator/post-loop-stages.js +0 -468
  278. package/src/orchestrator/pre-loop-stages.js +0 -11
  279. package/src/orchestrator/preflight-checks.js +0 -487
  280. package/src/orchestrator/repair/repair-detector.js +0 -117
  281. package/src/orchestrator/repair/repair-runner.js +0 -142
  282. package/src/orchestrator/reviewer-fallback.js +0 -45
  283. package/src/orchestrator/role-output-compressor.js +0 -189
  284. package/src/orchestrator/session-journal.js +0 -308
  285. package/src/orchestrator/solomon-escalation.js +0 -331
  286. package/src/orchestrator/solomon-rules.js +0 -212
  287. package/src/orchestrator/stages/acceptance-coverage-stage.js +0 -47
  288. package/src/orchestrator/stages/architect-stage.js +0 -172
  289. package/src/orchestrator/stages/coder-stage.js +0 -506
  290. package/src/orchestrator/stages/domain-curator-stage.js +0 -82
  291. package/src/orchestrator/stages/format-stage.js +0 -61
  292. package/src/orchestrator/stages/hu-reviewer-phases/detect-and-apply-splits.js +0 -127
  293. package/src/orchestrator/stages/hu-reviewer-stage.js +0 -419
  294. package/src/orchestrator/stages/perf-stage.js +0 -140
  295. package/src/orchestrator/stages/planner-stage.js +0 -85
  296. package/src/orchestrator/stages/rag-context-stage.js +0 -80
  297. package/src/orchestrator/stages/rag-preload-decisor.js +0 -58
  298. package/src/orchestrator/stages/research-stage.js +0 -69
  299. package/src/orchestrator/stages/reviewer-stage.js +0 -431
  300. package/src/orchestrator/stages/sonar-stage.js +0 -439
  301. package/src/orchestrator/stages/stage-classes.js +0 -84
  302. package/src/orchestrator/stages/stage-executor.js +0 -155
  303. package/src/orchestrator/stages/suite-green-stage.js +0 -62
  304. package/src/orchestrator/stages/tdd-discipline-stage.js +0 -56
  305. package/src/orchestrator/stages/tool-judge-stage.js +0 -53
  306. package/src/orchestrator/stages/triage-stage.js +0 -172
  307. package/src/orchestrator/standby.js +0 -71
  308. package/src/orchestrator/verification-gate.js +0 -183
  309. package/src/orchestrator.js +0 -22
  310. package/src/plan/adr-generator.js +0 -170
  311. package/src/plan/adr-loader.js +0 -60
  312. package/src/plan/plan-executor.js +0 -100
  313. package/src/plan/plan-validation.js +0 -4
  314. package/src/prompts/acceptance-coverage.js +0 -32
  315. package/src/prompts/arbiter.js +0 -38
  316. package/src/prompts/architect.js +0 -103
  317. package/src/prompts/hu-reviewer.js +0 -244
  318. package/src/prompts/tool-correctness-judge.js +0 -31
  319. package/src/prompts/triage.js +0 -57
  320. package/src/rag/embedder.js +0 -2
  321. package/src/rag/embedders/_cloud-base.js +0 -2
  322. package/src/rag/embedders/cohere.js +0 -2
  323. package/src/rag/embedders/mistral.js +0 -2
  324. package/src/rag/embedders/onnx.js +0 -2
  325. package/src/rag/embedders/openai.js +0 -2
  326. package/src/rag/embedders/voyage.js +0 -2
  327. package/src/rag/rerank.js +0 -4
  328. package/src/rag/where-parser.js +0 -4
  329. package/src/repeat-detector.js +0 -88
  330. package/src/review/diff-generator.js +0 -112
  331. package/src/review/git-stash-helper.js +0 -104
  332. package/src/review/schema.js +0 -31
  333. package/src/review/scope-filter.js +0 -152
  334. package/src/review/snapshot-diff.js +0 -122
  335. package/src/review/tdd-discipline.js +0 -90
  336. package/src/review/tdd-policy.js +0 -69
  337. package/src/roles/acceptance-coverage-role.js +0 -78
  338. package/src/roles/architect-role.js +0 -77
  339. package/src/roles/coder-role.js +0 -113
  340. package/src/roles/commiter-role.js +0 -113
  341. package/src/roles/domain-curator-role.js +0 -134
  342. package/src/roles/hu-reviewer-role.js +0 -64
  343. package/src/roles/impeccable-role.js +0 -85
  344. package/src/roles/index.js +0 -18
  345. package/src/roles/karajan-brain-role.js +0 -134
  346. package/src/roles/perf-role.js +0 -122
  347. package/src/roles/planner-role.js +0 -151
  348. package/src/roles/refactorer-role.js +0 -30
  349. package/src/roles/repairer-role.js +0 -181
  350. package/src/roles/researcher-role.js +0 -70
  351. package/src/roles/reviewer-role.js +0 -115
  352. package/src/roles/security-role.js +0 -73
  353. package/src/roles/solomon-role.js +0 -271
  354. package/src/roles/sonar-role.js +0 -72
  355. package/src/roles/tester-role.js +0 -235
  356. package/src/roles/tool-correctness-judge-role.js +0 -78
  357. package/src/roles/triage-role.js +0 -99
  358. package/src/session/journal/decisions-writer.js +0 -200
  359. package/src/session/journal/iteration-logger.js +0 -194
  360. package/src/session/journal/summary-writer.js +0 -320
  361. package/src/session/journal/tree-writer.js +0 -164
  362. package/src/session/journal/write-all.js +0 -89
  363. package/src/session-cleanup.js +0 -116
  364. package/src/skills/semantic-detector.js +0 -167
  365. package/src/skills/skill-detector.js +0 -385
  366. package/src/sonar/cloud-scanner.js +0 -78
  367. package/src/tracker-bootstrap.js +0 -44
  368. package/src/types/index.js +0 -38
  369. package/src/types/policy.js +0 -43
  370. package/src/types/solomon.js +0 -87
  371. package/src/utils/async-lock.js +0 -31
  372. package/src/utils/budget.js +0 -229
  373. package/src/utils/cwd-confirm.js +0 -39
  374. package/src/utils/display/event-handlers.js +0 -403
  375. package/src/utils/display/formatters.js +0 -132
  376. package/src/utils/display/header.js +0 -47
  377. package/src/utils/display/session-summary.js +0 -118
  378. package/src/utils/display/solomon.js +0 -42
  379. package/src/utils/pricing.js +0 -29
  380. package/src/utils/quota-simulator.js +0 -56
  381. package/src/utils/role-assigner.js +0 -137
  382. package/src/utils/rtk-wrapper.js +0 -87
  383. package/src/utils/run-registry.js +0 -9
  384. package/src/utils/status-dashboard.js +0 -254
  385. package/src/utils/tool-call-extractor.js +0 -56
  386. package/src/webperf/devtools-detect.js +0 -112
  387. package/src/webperf/webperf-role-integration.js +0 -66
  388. package/templates/skills/kj-run.md +0 -73
@@ -1,109 +0,0 @@
1
- /**
2
- * Lazy HU Planner — refines HUs one at a time using context from completed HUs.
3
- *
4
- * Instead of fully planning all HUs upfront, only the first HU is decomposed
5
- * with full acceptance criteria. Subsequent HUs are refined lazily before
6
- * execution, incorporating learnings from previously completed HUs.
7
- */
8
- import { createAgent } from "../agents/index.js";
9
- import { extractFirstJson } from "../utils/json-extract.js";
10
- import { withBrainRecovery } from "../brain/with-brain-recovery.js";
11
-
12
- const SUBAGENT_PREAMBLE = [
13
- "IMPORTANT: You are running as a Karajan sub-agent.",
14
- "Do NOT ask about using Karajan, do NOT mention Karajan, do NOT suggest orchestration.",
15
- "Do NOT use any MCP tools. Focus only on refining the user story."
16
- ].join(" ");
17
-
18
- /**
19
- * Build a prompt asking the AI to refine an HU based on context from completed HUs.
20
- * @param {object} hu - The HU to refine (with at least id, original.text or certified.text).
21
- * @param {Array<object>} completedHus - Array of completed HU results with summaries.
22
- * @returns {string} The assembled refinement prompt.
23
- */
24
- export function buildRefinementPrompt(hu, completedHus) {
25
- const sections = [SUBAGENT_PREAMBLE];
26
-
27
- sections.push("## HU Refinement");
28
- sections.push(
29
- "You are a senior product owner. Refine the following user story by adding or updating its acceptance criteria.",
30
- "Use the context from previously completed HUs to make the criteria more precise and avoid redundant work."
31
- );
32
-
33
- if (completedHus.length > 0) {
34
- sections.push("## Previously Completed HUs");
35
- for (const completed of completedHus) {
36
- const title = completed.certified?.title || completed.id;
37
- const summary = completed.resultSummary || completed.certified?.text || completed.original?.text || "No summary available";
38
- sections.push(`### ${completed.id} — ${title}\nResult: ${summary}`);
39
- }
40
- }
41
-
42
- const huText = hu.certified?.text || hu.original?.text || `Implement HU ${hu.id}`;
43
- sections.push(`## HU to Refine\n### ${hu.id}\n${huText}`);
44
-
45
- sections.push(
46
- "Return a single valid JSON object and nothing else.",
47
- `JSON schema: {"id":string,"title":string,"text":string,"acceptanceCriteria":[string]}`,
48
- "Rules:",
49
- "- Keep the same HU id",
50
- "- Refine the description to be more specific based on what was learned",
51
- "- Add concrete, testable acceptance criteria",
52
- "- Avoid repeating work already done in completed HUs",
53
- "- The text field should be the full story description ready for implementation"
54
- );
55
-
56
- return sections.join("\n\n");
57
- }
58
-
59
- /**
60
- * Refine an HU using AI, incorporating context from completed HUs.
61
- * @param {object} hu - The HU story object to refine.
62
- * @param {Array<object>} completedHus - Completed HU stories with results.
63
- * @param {object} config - Pipeline configuration.
64
- * @param {object} logger - Logger instance.
65
- * @returns {Promise<object>} Updated HU object with refined description and AC.
66
- */
67
- export async function refineHuWithContext(hu, completedHus, config, logger) {
68
- const provider = config?.roles?.hu_reviewer?.provider || config?.roles?.coder?.provider || "claude";
69
- const prompt = buildRefinementPrompt(hu, completedHus);
70
-
71
- logger.info(`Lazy planner: refining HU ${hu.id} with context from ${completedHus.length} completed HU(s)`);
72
-
73
- const agent = createAgent(provider, config, logger);
74
- let result;
75
- try {
76
- // KJC-TSK-0413 step C
77
- result = await withBrainRecovery({
78
- agent, taskArgs: { prompt, role: "hu-reviewer" },
79
- role: "lazy-planner", provider, logger,
80
- });
81
- } catch (err) {
82
- logger.warn(`Lazy planner: refinement failed for HU ${hu.id}: ${err.message}`);
83
- return hu;
84
- }
85
-
86
- if (!result.ok) {
87
- logger.warn(`Lazy planner: refinement returned not-ok for HU ${hu.id}`);
88
- return hu;
89
- }
90
-
91
- const parsed = extractFirstJson(result.output);
92
- if (!parsed || !parsed.text) {
93
- logger.warn(`Lazy planner: could not parse refinement output for HU ${hu.id}`);
94
- return hu;
95
- }
96
-
97
- // Update the HU with refined content
98
- const refined = { ...hu };
99
- refined.certified = {
100
- ...(hu.certified || {}),
101
- text: parsed.text,
102
- title: parsed.title || hu.certified?.title || hu.id,
103
- acceptanceCriteria: parsed.acceptanceCriteria || []
104
- };
105
- refined.needsRefinement = false;
106
-
107
- logger.info(`Lazy planner: HU ${hu.id} refined successfully`);
108
- return refined;
109
- }
@@ -1,94 +0,0 @@
1
- /**
2
- * Parallel HU execution using git worktrees.
3
- *
4
- * Analyses the dependency graph to find groups of HUs that can run
5
- * concurrently, creates isolated git worktrees for each parallel HU,
6
- * and merges results back sequentially.
7
- */
8
- import { execFile } from "node:child_process";
9
- import { promisify } from "node:util";
10
- import path from "node:path";
11
-
12
- const execFileAsync = promisify(execFile);
13
-
14
- /** Directory inside the project where worktrees are created. */
15
- const WORKTREE_DIR = ".kj/worktrees";
16
-
17
- /**
18
- * Given stories and their topological order, return groups (batches) of HUs
19
- * that can execute in parallel. Two HUs can run in parallel when neither
20
- * depends (directly or transitively) on the other.
21
- *
22
- * The algorithm walks the topological order and assigns each HU to the
23
- * earliest batch where all its dependencies have already been placed in
24
- * a previous batch.
25
- *
26
- * @param {Array<{id: string, blocked_by?: string[]}>} stories
27
- * @param {string[]} order - topologically sorted story IDs
28
- * @returns {string[][]} Array of batches; each batch is an array of story IDs.
29
- */
30
- export function findParallelGroups(stories, order) {
31
- if (!order.length) return [];
32
-
33
- const storyMap = new Map(stories.map(s => [s.id, s]));
34
- // batchIndex[id] = index of the batch the story was assigned to
35
- const batchIndex = new Map();
36
- const batches = [];
37
-
38
- for (const id of order) {
39
- const story = storyMap.get(id);
40
- const deps = story?.blocked_by || [];
41
- // This HU must go into the batch *after* the latest dependency batch
42
- let earliest = 0;
43
- for (const dep of deps) {
44
- if (batchIndex.has(dep)) {
45
- earliest = Math.max(earliest, batchIndex.get(dep) + 1);
46
- }
47
- }
48
- if (!batches[earliest]) batches[earliest] = [];
49
- batches[earliest].push(id);
50
- batchIndex.set(id, earliest);
51
- }
52
-
53
- return batches;
54
- }
55
-
56
- /**
57
- * Create a git worktree for a given HU.
58
- *
59
- * @param {string} projectDir - Root directory of the git repo.
60
- * @param {string} huId - Story identifier (used for branch and directory name).
61
- * @returns {Promise<string>} Absolute path to the created worktree.
62
- */
63
- export async function createWorktree(projectDir, huId) {
64
- const worktreePath = path.join(projectDir, WORKTREE_DIR, huId);
65
- const branchName = `kj-hu-${huId}`;
66
- await execFileAsync("git", ["worktree", "add", worktreePath, "-b", branchName], { cwd: projectDir });
67
- return worktreePath;
68
- }
69
-
70
- /**
71
- * Remove a git worktree (forcefully).
72
- *
73
- * @param {string} projectDir - Root directory of the git repo.
74
- * @param {string} huId - Story identifier.
75
- * @returns {Promise<void>}
76
- */
77
- export async function removeWorktree(projectDir, huId) {
78
- const worktreePath = path.join(projectDir, WORKTREE_DIR, huId);
79
- await execFileAsync("git", ["worktree", "remove", worktreePath, "--force"], { cwd: projectDir });
80
- }
81
-
82
- /**
83
- * Merge a worktree branch back into the current branch and clean up.
84
- *
85
- * @param {string} projectDir - Root directory of the git repo.
86
- * @param {string} huId - Story identifier.
87
- * @returns {Promise<void>}
88
- */
89
- export async function mergeWorktree(projectDir, huId) {
90
- const branchName = `kj-hu-${huId}`;
91
- await execFileAsync("git", ["merge", branchName, "--no-edit"], { cwd: projectDir });
92
- await removeWorktree(projectDir, huId);
93
- await execFileAsync("git", ["branch", "-d", branchName], { cwd: projectDir });
94
- }
@@ -1,17 +0,0 @@
1
- /**
2
- * KJC-TSK-0702 (MR-E): the per-HU measurement model routing is judged by. The
3
- * sub-pipeline puts it on the HU outcome; karajan-core's setHuOutcome keeps a
4
- * fixed set of fields, so the measured ones are kept here, in the plan, where
5
- * kj report reads them. Unmeasured fields stay out rather than written as null.
6
- */
7
- // KJC-TSK-1071 (MR-D3): coder_routing is the coder's routing decision for the HU.
8
- export const ROUTING_FIELDS = ["cost_usd", "tokens_in", "tokens_out", "cached_tokens", "coder", "reviewer_rejections", "coder_routing"];
9
-
10
- export function keepRoutingMetrics(plan, huId, outcome) {
11
- const hu = plan?.hus?.find((h) => h.id === huId);
12
- if (!hu?.outcome) return false;
13
- for (const field of ROUTING_FIELDS) {
14
- if (outcome?.[field] !== null && outcome?.[field] !== undefined) hu.outcome[field] = outcome[field];
15
- }
16
- return true;
17
- }
@@ -1,143 +0,0 @@
1
- /**
2
- * Validates sub-HUs produced by the splitting generator against 4 mandatory criteria.
3
- * Uses heuristic checks (no AI calls).
4
- */
5
-
6
- /** Maximum devPoints for a sub-HU to be considered completable in 3 days. */
7
- const MAX_DEV_POINTS = 3;
8
-
9
- /** Text length threshold — stories longer than this are likely too big for 3 days. */
10
- const TEXT_LENGTH_THRESHOLD = 2000;
11
-
12
- /**
13
- * Regex patterns that indicate a horizontal (layer-only) split — both EN and ES.
14
- * @type {RegExp[]}
15
- */
16
- export const HORIZONTAL_PATTERNS = [
17
- /\bonly the (api|backend|frontend|database|ui)\b/i,
18
- /\bjust the (endpoint|interface|model|schema)\b/i,
19
- /\bsolo (el endpoint|la interfaz|el modelo|la base de datos|el backend|el frontend)\b/i,
20
- /\bimplementar (el|la|los) \w+ sin\b/i
21
- ];
22
-
23
- /**
24
- * Patterns that indicate a sub-HU is not independently deployable.
25
- * @type {RegExp[]}
26
- */
27
- const DEPENDENCY_PATTERNS = [
28
- /\bpart \d+ of \d+\b/i,
29
- /\bdepends on .+ being deployed first\b/i,
30
- /\brequires .+ to be completed before\b/i,
31
- /\bparte \d+ de \d+\b/i
32
- ];
33
-
34
- /**
35
- * Check whether the text has a role/goal/benefit structure (independently valuable).
36
- * Looks for patterns like "As a <role>, I want <goal> so that <benefit>".
37
- * Also accepts structured objects with role/goal/benefit fields.
38
- * @param {object} subHU
39
- * @returns {boolean}
40
- */
41
- function checkIndependentlyValuable(subHU) {
42
- // Check structured fields first
43
- const desc = subHU.descriptionStructured || subHU.description_structured;
44
- if (Array.isArray(desc) && desc.length > 0) {
45
- const entry = desc[0];
46
- if (entry.role && entry.goal && entry.benefit) return true;
47
- }
48
- if (subHU.role && subHU.goal && subHU.benefit) return true;
49
-
50
- // Check text for As a/I want/so that pattern
51
- const text = extractText(subHU);
52
- const hasRole = /\b(as an?|como)\b/i.test(text);
53
- const hasGoal = /\b(i want|quiero|i need|necesito)\b/i.test(text);
54
- const hasBenefit = /\b(so that|para que|in order to|de modo que)\b/i.test(text);
55
- return hasRole && hasGoal && hasBenefit;
56
- }
57
-
58
- /**
59
- * Check that the sub-HU does not reference sequential dependencies.
60
- * @param {object} subHU
61
- * @returns {boolean}
62
- */
63
- function checkDeployableAlone(subHU) {
64
- const text = extractText(subHU);
65
- return !DEPENDENCY_PATTERNS.some(p => p.test(text));
66
- }
67
-
68
- /**
69
- * Check whether the sub-HU is completable within 3 days.
70
- * Uses devPoints if available, otherwise falls back to text length heuristic.
71
- * @param {object} subHU
72
- * @returns {boolean}
73
- */
74
- function checkCompletableIn3Days(subHU) {
75
- if (typeof subHU.devPoints === "number") {
76
- return subHU.devPoints <= MAX_DEV_POINTS;
77
- }
78
- const text = extractText(subHU);
79
- return text.length <= TEXT_LENGTH_THRESHOLD;
80
- }
81
-
82
- /**
83
- * Check the sub-HU is a vertical slice (not a horizontal layer-only story).
84
- * @param {object} subHU
85
- * @returns {boolean}
86
- */
87
- function checkIsVertical(subHU) {
88
- const text = extractText(subHU);
89
- return !HORIZONTAL_PATTERNS.some(p => p.test(text));
90
- }
91
-
92
- /**
93
- * Extract all relevant text from a sub-HU for pattern matching.
94
- * @param {object} subHU
95
- * @returns {string}
96
- */
97
- function extractText(subHU) {
98
- const parts = [
99
- subHU.title,
100
- subHU.text,
101
- subHU.description,
102
- subHU.acceptanceCriteria
103
- ].filter(Boolean);
104
-
105
- // Include structured description text
106
- const desc = subHU.descriptionStructured || subHU.description_structured;
107
- if (Array.isArray(desc)) {
108
- for (const entry of desc) {
109
- if (entry.role) parts.push(entry.role);
110
- if (entry.goal) parts.push(entry.goal);
111
- if (entry.benefit) parts.push(entry.benefit);
112
- }
113
- }
114
-
115
- return parts.join(" ");
116
- }
117
-
118
- /**
119
- * Validate a sub-HU against 4 mandatory split criteria.
120
- *
121
- * @param {object} subHU - The sub-HU object to validate.
122
- * @returns {{ valid: boolean, criteria: { independently_valuable: boolean, deployable_alone: boolean, completable_in_3_days: boolean, is_vertical: boolean }, failures: string[] }}
123
- */
124
- export function validateSplitCriteria(subHU) {
125
- const criteria = {
126
- independently_valuable: checkIndependentlyValuable(subHU),
127
- deployable_alone: checkDeployableAlone(subHU),
128
- completable_in_3_days: checkCompletableIn3Days(subHU),
129
- is_vertical: checkIsVertical(subHU)
130
- };
131
-
132
- const failures = [];
133
- if (!criteria.independently_valuable) failures.push("Sub-HU does not describe independent user-facing value (missing role/goal/benefit)");
134
- if (!criteria.deployable_alone) failures.push("Sub-HU references sequential deployment dependencies");
135
- if (!criteria.completable_in_3_days) failures.push("Sub-HU appears too large to complete in 3 days");
136
- if (!criteria.is_vertical) failures.push("Sub-HU describes a horizontal slice instead of a vertical one");
137
-
138
- return {
139
- valid: failures.length === 0,
140
- criteria,
141
- failures
142
- };
143
- }
@@ -1,158 +0,0 @@
1
- /**
2
- * Heuristic-based HU splitting detection.
3
- * Analyzes user story text for indicators that suggest the HU should be split.
4
- */
5
-
6
- /**
7
- * @typedef {object} SplitIndicator
8
- * @property {string} type - Indicator type (e.g. "multiple_and", "multiple_roles")
9
- * @property {string} detail - Human-readable description of what was detected
10
- * @property {number} weight - How strongly this indicator suggests splitting (1-3)
11
- */
12
-
13
- /** @type {Record<string, string>} */
14
- export const HEURISTIC_DESCRIPTIONS = Object.freeze({
15
- workflow_steps: "Split by workflow steps: each step of the user journey becomes its own HU.",
16
- data_entity: "Split by data entity: each distinct entity (model, resource) gets its own HU.",
17
- user_role: "Split by user role: each actor/persona gets their own HU.",
18
- crud_operations: "Split by CRUD operations: separate create, read, update, delete into individual HUs.",
19
- happy_sad_path: "Split by happy/sad path: main flow in one HU, error/edge cases in another.",
20
- interface_boundary: "Split by interface boundary: frontend, backend, integration each get their own HU."
21
- });
22
-
23
- const HEURISTIC_KEYS = Object.keys(HEURISTIC_DESCRIPTIONS);
24
-
25
- /**
26
- * Detect indicators in HU text that suggest the story should be split.
27
- * @param {string} text - The HU text to analyze.
28
- * @returns {SplitIndicator[]} Array of detected indicators (empty if no splitting needed).
29
- */
30
- export function detectIndicators(text) {
31
- if (!text || typeof text !== "string") return [];
32
-
33
- const indicators = [];
34
- const lower = text.toLowerCase();
35
-
36
- // Multiple "and" connectors suggest compound story
37
- const andMatches = lower.match(/\band\b/g);
38
- if (andMatches && andMatches.length >= 2) {
39
- indicators.push({
40
- type: "multiple_and",
41
- detail: `Found ${andMatches.length} "and" connectors suggesting compound story`,
42
- weight: 2
43
- });
44
- }
45
-
46
- // Multiple roles mentioned
47
- const rolePatterns = /\bas (?:a |an )?(\w+)/gi;
48
- const roles = [...text.matchAll(rolePatterns)].map(m => m[1].toLowerCase());
49
- const uniqueRoles = new Set(roles);
50
- if (uniqueRoles.size > 1) {
51
- indicators.push({
52
- type: "multiple_roles",
53
- detail: `Multiple roles detected: ${[...uniqueRoles].join(", ")}`,
54
- weight: 3
55
- });
56
- }
57
-
58
- // Multiple acceptance criteria groups or numbered lists
59
- const acCount = (text.match(/^[-*\d+.]\s/gm) || []).length;
60
- if (acCount > 5) {
61
- indicators.push({
62
- type: "many_acceptance_criteria",
63
- detail: `${acCount} acceptance criteria items detected`,
64
- weight: 2
65
- });
66
- }
67
-
68
- // CRUD keywords
69
- const crudKeywords = ["create", "read", "update", "delete", "edit", "remove", "list", "view"];
70
- const foundCrud = crudKeywords.filter(kw => lower.includes(kw));
71
- if (foundCrud.length >= 3) {
72
- indicators.push({
73
- type: "crud_operations",
74
- detail: `Multiple CRUD operations: ${foundCrud.join(", ")}`,
75
- weight: 2
76
- });
77
- }
78
-
79
- // Multiple "so that" / "in order to" benefits
80
- const benefitMatches = lower.match(/\b(so that|in order to)\b/g);
81
- if (benefitMatches && benefitMatches.length > 1) {
82
- indicators.push({
83
- type: "multiple_benefits",
84
- detail: `${benefitMatches.length} benefit clauses detected`,
85
- weight: 2
86
- });
87
- }
88
-
89
- // Workflow/step keywords
90
- const stepKeywords = ["first", "then", "after that", "finally", "next", "step"];
91
- const foundSteps = stepKeywords.filter(kw => lower.includes(kw));
92
- if (foundSteps.length >= 2) {
93
- indicators.push({
94
- type: "workflow_steps",
95
- detail: `Sequential workflow indicators: ${foundSteps.join(", ")}`,
96
- weight: 2
97
- });
98
- }
99
-
100
- return indicators;
101
- }
102
-
103
- /**
104
- * Select the best splitting heuristic based on detected indicators.
105
- * @param {SplitIndicator[]} indicators - Detected split indicators.
106
- * @param {string[]} [excludeHeuristics=[]] - Heuristics to skip (already tried).
107
- * @returns {string|null} The selected heuristic key, or null if none applicable.
108
- */
109
- export function selectHeuristic(indicators, excludeHeuristics = []) {
110
- if (!indicators || indicators.length === 0) return null;
111
-
112
- const excluded = new Set(excludeHeuristics);
113
-
114
- // Map indicator types to preferred heuristics
115
- const heuristicScores = {};
116
- for (const key of HEURISTIC_KEYS) {
117
- if (!excluded.has(key)) {
118
- heuristicScores[key] = 0;
119
- }
120
- }
121
-
122
- for (const ind of indicators) {
123
- switch (ind.type) {
124
- case "multiple_roles":
125
- if (!excluded.has("user_role")) heuristicScores.user_role = (heuristicScores.user_role || 0) + ind.weight;
126
- break;
127
- case "crud_operations":
128
- if (!excluded.has("crud_operations")) heuristicScores.crud_operations = (heuristicScores.crud_operations || 0) + ind.weight;
129
- break;
130
- case "workflow_steps":
131
- if (!excluded.has("workflow_steps")) heuristicScores.workflow_steps = (heuristicScores.workflow_steps || 0) + ind.weight;
132
- break;
133
- case "multiple_and":
134
- case "many_acceptance_criteria":
135
- if (!excluded.has("workflow_steps")) heuristicScores.workflow_steps = (heuristicScores.workflow_steps || 0) + ind.weight;
136
- if (!excluded.has("data_entity")) heuristicScores.data_entity = (heuristicScores.data_entity || 0) + ind.weight;
137
- break;
138
- case "multiple_benefits":
139
- if (!excluded.has("happy_sad_path")) heuristicScores.happy_sad_path = (heuristicScores.happy_sad_path || 0) + ind.weight;
140
- if (!excluded.has("interface_boundary")) heuristicScores.interface_boundary = (heuristicScores.interface_boundary || 0) + ind.weight;
141
- break;
142
- default:
143
- break;
144
- }
145
- }
146
-
147
- // Pick highest scoring non-excluded heuristic
148
- let best = null;
149
- let bestScore = 0;
150
- for (const [key, score] of Object.entries(heuristicScores)) {
151
- if (score > bestScore) {
152
- bestScore = score;
153
- best = key;
154
- }
155
- }
156
-
157
- return best;
158
- }
@@ -1,175 +0,0 @@
1
- /**
2
- * HU Splitting Generator.
3
- * Uses an AI agent to split a HU based on a selected heuristic,
4
- * then formats proposals for FDE confirmation.
5
- */
6
-
7
- import { createAgent } from "../agents/index.js";
8
- import { extractFirstJson } from "../utils/json-extract.js";
9
- import { HEURISTIC_DESCRIPTIONS } from "./splitting-detector.js";
10
- import { withBrainRecovery } from "../brain/with-brain-recovery.js";
11
-
12
- const SUBAGENT_PREAMBLE = [
13
- "IMPORTANT: You are running as a Karajan sub-agent.",
14
- "Do NOT ask about using Karajan, do NOT mention Karajan, do NOT suggest orchestration.",
15
- "Do NOT use any MCP tools. Focus only on splitting this HU."
16
- ].join(" ");
17
-
18
- /**
19
- * Build the prompt for the AI agent that will split the HU.
20
- * @param {{id: string, text: string}} hu - The HU to split.
21
- * @param {string} heuristic - The heuristic key to apply.
22
- * @returns {string} The assembled prompt.
23
- */
24
- export function buildSplitPrompt(hu, heuristic) {
25
- const heuristicDesc = HEURISTIC_DESCRIPTIONS[heuristic] || heuristic;
26
- const baseId = hu.id || "HU-XXX";
27
-
28
- const sections = [SUBAGENT_PREAMBLE];
29
-
30
- sections.push("## Task: Split User Story");
31
- sections.push(
32
- "You are a senior product owner. Split the following user story into smaller, independent sub-HUs.",
33
- `Apply this splitting heuristic: **${heuristicDesc}**`
34
- );
35
-
36
- sections.push(`## Original User Story (${baseId})\n${hu.text}`);
37
-
38
- sections.push(
39
- "## Output Requirements",
40
- "Return a single valid JSON object and nothing else.",
41
- `JSON schema: {"subHUs":[{"id":string,"title":string,"text":string,"acceptanceCriteria":[string],"blocked_by":[string]}],"heuristic":string,"reason":string}`,
42
- "Rules:",
43
- `- Each sub-HU id must follow the pattern: ${baseId}-A, ${baseId}-B, ${baseId}-C, etc.`,
44
- "- text must follow role/goal/benefit format: 'As a [role], I want to [goal], so that [benefit]'",
45
- "- acceptanceCriteria must be concrete, testable statements",
46
- "- blocked_by: first sub-HU has empty array, each subsequent one is blocked by the previous (sequential chain)",
47
- "- Generate between 2 and 5 sub-HUs",
48
- `- heuristic must be "${heuristic}"`,
49
- "- reason must explain why this heuristic was applied"
50
- );
51
-
52
- return sections.join("\n\n");
53
- }
54
-
55
- /**
56
- * Parse the AI agent's split output into a structured proposal.
57
- * @param {string} raw - Raw agent output text.
58
- * @returns {{subHUs: Array, heuristic: string, reason: string}|null}
59
- */
60
- export function parseSplitOutput(raw) {
61
- const parsed = extractFirstJson(raw);
62
- if (!parsed || !Array.isArray(parsed.subHUs) || parsed.subHUs.length < 2) return null;
63
-
64
- const subHUs = parsed.subHUs
65
- .filter(s => s && s.id && s.title && s.text)
66
- .map(s => ({
67
- id: s.id,
68
- title: s.title,
69
- text: s.text,
70
- acceptanceCriteria: Array.isArray(s.acceptanceCriteria) ? s.acceptanceCriteria : [],
71
- blocked_by: Array.isArray(s.blocked_by) ? s.blocked_by : []
72
- }));
73
-
74
- if (subHUs.length < 2) return null;
75
-
76
- return {
77
- subHUs,
78
- heuristic: parsed.heuristic || "unknown",
79
- reason: parsed.reason || ""
80
- };
81
- }
82
-
83
- /**
84
- * Use an AI agent to generate a split proposal for a HU based on a heuristic.
85
- * @param {{id: string, text: string}} hu - The HU to split.
86
- * @param {string} heuristic - The heuristic key to apply.
87
- * @param {object} config - Karajan config object.
88
- * @param {object} logger - Logger instance.
89
- * @returns {Promise<{subHUs: Array, heuristic: string, reason: string}|null>}
90
- */
91
- export async function generateSplitProposal(hu, heuristic, config, logger) {
92
- const provider = config?.roles?.hu_reviewer?.provider
93
- || config?.roles?.coder?.provider
94
- || "claude";
95
-
96
- const agent = createAgent(provider, config, logger);
97
- const prompt = buildSplitPrompt(hu, heuristic);
98
-
99
- let result;
100
- try {
101
- // KJC-TSK-0413 step C
102
- result = await withBrainRecovery({
103
- agent, taskArgs: { prompt, role: "hu-splitter" },
104
- role: "hu-splitter", provider, logger,
105
- });
106
- } catch (err) {
107
- logger.warn(`HU split generation threw: ${err.message}`);
108
- return null;
109
- }
110
-
111
- if (!result.ok) {
112
- logger.warn(`HU split generation failed: ${result.error || "unknown"}`);
113
- return null;
114
- }
115
-
116
- const proposal = parseSplitOutput(result.output);
117
- if (!proposal) {
118
- logger.warn("HU split generation returned unparseable output");
119
- return null;
120
- }
121
-
122
- return proposal;
123
- }
124
-
125
- /**
126
- * Format a split proposal as human-readable text for the FDE to review.
127
- * @param {{subHUs: Array<{id: string, title: string, text: string, acceptanceCriteria?: string[], blocked_by?: string[]}>, heuristic: string, reason: string}} proposal
128
- * @returns {string}
129
- */
130
- export function formatSplitProposalForFDE(proposal) {
131
- const lines = [];
132
- const heuristicDesc = HEURISTIC_DESCRIPTIONS[proposal.heuristic] || proposal.heuristic;
133
-
134
- lines.push(`Heuristic: ${heuristicDesc}`);
135
- lines.push(`Reason: ${proposal.reason}`);
136
- lines.push("");
137
-
138
- for (const sub of proposal.subHUs) {
139
- lines.push(`--- ${sub.id}: ${sub.title} ---`);
140
- lines.push(sub.text);
141
- if (sub.acceptanceCriteria && sub.acceptanceCriteria.length > 0) {
142
- lines.push("Acceptance Criteria:");
143
- for (const ac of sub.acceptanceCriteria) {
144
- lines.push(` - ${ac}`);
145
- }
146
- }
147
- if (sub.blocked_by && sub.blocked_by.length > 0) {
148
- lines.push(`Depends on: ${sub.blocked_by.join(", ")}`);
149
- }
150
- lines.push("");
151
- }
152
-
153
- return lines.join("\n");
154
- }
155
-
156
- /**
157
- * Set blocked_by for each sub-HU: first inherits original's dependencies,
158
- * each subsequent is blocked by its predecessor (sequential chain).
159
- * @param {Array<{id: string, blocked_by?: string[]}>} subHUs - The sub-HUs to update.
160
- * @param {{blocked_by?: string[]}} originalHu - The original HU with its dependencies.
161
- * @returns {Array<{id: string, blocked_by: string[]}>} Updated sub-HUs with correct dependencies.
162
- */
163
- export function buildSplitDependencies(subHUs, originalHu) {
164
- if (!subHUs || subHUs.length === 0) return [];
165
-
166
- const originalDeps = originalHu?.blocked_by || [];
167
-
168
- return subHUs.map((sub, idx) => {
169
- const blockedBy = idx === 0
170
- ? [...originalDeps]
171
- : [subHUs[idx - 1].id];
172
-
173
- return { ...sub, blocked_by: blockedBy };
174
- });
175
- }