karajan-code 4.70.4 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (380) hide show
  1. package/README.md +2 -2
  2. package/package.json +6 -14
  3. package/packages/hu-board/package.json +1 -1
  4. package/packages/hu-board/public/app.js +4 -18
  5. package/packages/hu-board/public/index.html +5 -2
  6. package/packages/hu-board/public/styles.css +26 -0
  7. package/packages/hu-board/public/utils/api.js +0 -74
  8. package/packages/hu-board/public/utils/board-tour.js +62 -0
  9. package/packages/hu-board/public/utils/board-view.js +49 -144
  10. package/packages/hu-board/public/utils/command-launcher.js +8 -70
  11. package/packages/hu-board/public/utils/formatters.js +10 -0
  12. package/packages/hu-board/public/utils/governance-view.js +13 -1
  13. package/packages/hu-board/public/utils/hu-actions.js +17 -68
  14. package/packages/hu-board/public/utils/init-listeners.js +7 -8
  15. package/packages/hu-board/public/utils/log-panel.js +3 -16
  16. package/packages/hu-board/public/utils/maggle-mode.js +2 -12
  17. package/packages/hu-board/public/utils/modals.js +7 -0
  18. package/packages/hu-board/public/utils/project-picker-view.js +1 -1
  19. package/packages/hu-board/public/utils/server-push.js +3 -22
  20. package/packages/hu-board/public/utils/shared-board-notice.js +33 -0
  21. package/packages/hu-board/public/utils/story-detail-view.js +5 -19
  22. package/packages/hu-board/src/cleanup-zombies.js +0 -1
  23. package/packages/hu-board/src/command-runner.js +36 -25
  24. package/packages/hu-board/src/db-connection.js +282 -0
  25. package/packages/hu-board/src/db-paths.js +113 -0
  26. package/packages/hu-board/src/db-queries.js +188 -0
  27. package/packages/hu-board/src/db-tombstones.js +51 -0
  28. package/packages/hu-board/src/db.js +28 -619
  29. package/packages/hu-board/src/ephemeral-cleaner.js +2 -3
  30. package/packages/hu-board/src/hu-zombie-reaper.js +5 -1
  31. package/packages/hu-board/src/plan-mutations.js +3 -153
  32. package/packages/hu-board/src/plans-service.js +55 -0
  33. package/packages/hu-board/src/routes/api/commands.js +122 -0
  34. package/packages/hu-board/src/routes/api/plans.js +55 -0
  35. package/packages/hu-board/src/routes/api/projects.js +267 -0
  36. package/packages/hu-board/src/routes/api/prompts.js +135 -0
  37. package/packages/hu-board/src/routes/api/sessions.js +95 -0
  38. package/packages/hu-board/src/routes/api/stories.js +183 -0
  39. package/packages/hu-board/src/routes/api/system.js +241 -0
  40. package/packages/hu-board/src/routes/api/tombstones.js +38 -0
  41. package/packages/hu-board/src/routes/api.js +24 -1371
  42. package/packages/hu-board/src/routes/governance.js +23 -1
  43. package/packages/hu-board/src/routes/lanes.js +40 -0
  44. package/packages/hu-board/src/routes/rag.js +4 -3
  45. package/packages/hu-board/src/server.js +20 -34
  46. package/packages/hu-board/src/sync-files.js +318 -0
  47. package/packages/hu-board/src/sync-project.js +99 -0
  48. package/packages/hu-board/src/sync.js +22 -566
  49. package/packages/hu-board/src/zombie-reaper.js +1 -1
  50. package/scripts/dual-publish.mjs +19 -3
  51. package/scripts/postinstall.js +15 -2
  52. package/scripts/verify-pack-mcp.mjs +11 -7
  53. package/scripts/verify-pack.mjs +4 -4
  54. package/src/audit/audit-history.js +3 -2
  55. package/src/audit/dead-exports.js +68 -59
  56. package/src/audit/deterministic-summary.js +33 -13
  57. package/src/audit/npm-signatures.js +41 -0
  58. package/src/audit/sbom.js +35 -0
  59. package/src/checks/agent-token.js +112 -0
  60. package/src/checks/dir-setup.js +34 -1
  61. package/src/checks/native-build.js +21 -39
  62. package/src/checks/runner.js +128 -142
  63. package/src/cli/advanced-commands.js +6 -6
  64. package/src/cli/register-meta.js +42 -82
  65. package/src/cli/register-pipeline.js +8 -143
  66. package/src/cli/register-plan.js +9 -100
  67. package/src/cli/retired-commands.js +49 -0
  68. package/src/cli.js +18 -5
  69. package/src/commands/agent-run.js +40 -26
  70. package/src/commands/audit.js +127 -124
  71. package/src/commands/board.js +5 -9
  72. package/src/commands/check.js +29 -16
  73. package/src/commands/doctor.js +2 -0
  74. package/src/commands/env.js +176 -150
  75. package/src/commands/go.js +71 -53
  76. package/src/commands/harden.js +90 -81
  77. package/src/commands/hu-start.js +52 -0
  78. package/src/commands/hu.js +288 -221
  79. package/src/commands/identity.js +111 -78
  80. package/src/commands/init-atril.js +47 -0
  81. package/src/commands/init.js +11 -40
  82. package/src/commands/install-tools.js +70 -85
  83. package/src/commands/main-guard.js +78 -0
  84. package/src/commands/policy.js +226 -184
  85. package/src/commands/qmd.js +27 -21
  86. package/src/commands/rag.js +75 -48
  87. package/src/commands/review-gate.js +421 -341
  88. package/src/commands/start.js +4 -5
  89. package/src/commands/steward.js +96 -47
  90. package/src/commands/tournament.js +46 -37
  91. package/src/commands/worktree.js +94 -25
  92. package/src/config/loader.js +34 -17
  93. package/src/config/secrets.js +36 -0
  94. package/src/config.js +1 -0
  95. package/src/environment/adr.js +16 -2
  96. package/src/git/karajan-label.js +20 -8
  97. package/src/guards/main-guard.js +54 -0
  98. package/src/harden/hook-templates.js +16 -1
  99. package/src/harden/sentinel/posttooluse.mjs +18 -6
  100. package/src/harden/sentinel/pretooluse-sentinel.mjs +64 -36
  101. package/src/harden/sentinel/sentinel-lib.mjs +129 -26
  102. package/src/harden/sentinel/sentinel-shell.mjs +5 -16
  103. package/src/harden/sentinel/sessionstart.mjs +3 -2
  104. package/src/harden/sentinel/stop.mjs +5 -4
  105. package/src/harden/workflow-templates.js +1 -1
  106. package/src/hu/board-lookup.js +29 -0
  107. package/src/hu/gh-identity.js +23 -0
  108. package/src/hu/github-board.js +4 -2
  109. package/src/hu/lane-prune.js +15 -7
  110. package/src/hu/lane-setup.js +65 -0
  111. package/src/hu/lanes.js +66 -0
  112. package/src/hu/team-board-mirror.js +3 -2
  113. package/src/infrastructure/command-runner.js +1 -1
  114. package/src/infrastructure/file-system-service.js +1 -1
  115. package/src/mcp/handlers/direct-handlers.js +2 -292
  116. package/src/mcp/handlers/hu-handlers.js +1 -35
  117. package/src/mcp/handlers/management-handlers.js +2 -42
  118. package/src/mcp/server-handlers.js +16 -22
  119. package/src/mcp/tools.js +1 -196
  120. package/src/plan/lane-plans.js +54 -0
  121. package/src/plan/plan-store.js +7 -4
  122. package/src/policy/add.js +26 -12
  123. package/src/policy/adr-number-check.js +31 -0
  124. package/src/prompts/audit.js +299 -339
  125. package/src/prompts/card-context.js +10 -5
  126. package/src/prompts/reviewer.js +5 -2
  127. package/src/rag/auto-update.js +56 -43
  128. package/src/rag/bootstrap-index.js +28 -54
  129. package/src/rag/migrate.js +1 -1
  130. package/src/rag/onnx-fallback.js +2 -2
  131. package/src/rag/pid-lease.js +70 -0
  132. package/src/rag/project-store.js +2 -12
  133. package/src/review/card-first.js +57 -46
  134. package/src/review/gate-gitignore.js +23 -4
  135. package/src/review/gate-hook.js +12 -4
  136. package/src/review/one-shot-review.js +45 -35
  137. package/src/review/publish-pr.js +14 -2
  138. package/src/review/repo-docs.js +73 -0
  139. package/src/review/repo-stack.js +59 -0
  140. package/src/review/reviewer-fallback.js +19 -4
  141. package/src/review/seal-commit.js +29 -0
  142. package/src/review/sonar-pregate.js +59 -44
  143. package/src/review/third-party-review.js +90 -16
  144. package/src/review/third-party-size.js +43 -0
  145. package/src/review/third-party-skills.js +54 -0
  146. package/src/review/verify-pr.js +1 -24
  147. package/src/roles/audit-role.js +106 -125
  148. package/src/skills/atril-skills.js +5 -4
  149. package/src/sonar/scanner.js +116 -101
  150. package/src/steward/invariants.js +5 -4
  151. package/src/steward/phantom-sweep.js +66 -0
  152. package/src/tournament/judge.js +60 -53
  153. package/src/utils/gh-account.js +37 -0
  154. package/src/utils/git.js +0 -13
  155. package/src/utils/home-migration.js +7 -1
  156. package/src/utils/main-checkout.js +53 -0
  157. package/src/utils/qmd-collection.js +3 -1
  158. package/src/utils/welcome.js +3 -3
  159. package/templates/{skills → atril/commands}/kj-architect.md +5 -0
  160. package/templates/{skills → atril/commands}/kj-audit.md +5 -0
  161. package/templates/{skills → atril/commands}/kj-board.md +5 -0
  162. package/templates/{skills → atril/commands}/kj-code.md +5 -0
  163. package/templates/{skills → atril/commands}/kj-csv-transform.md +5 -0
  164. package/templates/{skills → atril/commands}/kj-data-report.md +5 -0
  165. package/templates/{skills → atril/commands}/kj-discover.md +5 -0
  166. package/templates/{skills → atril/commands}/kj-rag-query.md +5 -0
  167. package/templates/{skills → atril/commands}/kj-review.md +5 -0
  168. package/templates/{skills → atril/commands}/kj-security.md +5 -0
  169. package/templates/{skills → atril/commands}/kj-sonar.md +5 -0
  170. package/templates/{skills → atril/commands}/kj-sql-analysis.md +5 -0
  171. package/templates/{skills → atril/commands}/kj-test.md +5 -0
  172. package/packages/hu-board/public/utils/run-launcher.js +0 -169
  173. package/packages/hu-board/src/run-tracker.js +0 -121
  174. package/src/activity-log.js +0 -59
  175. package/src/agents/host-agent.js +0 -38
  176. package/src/agents/model-registry.js +0 -142
  177. package/src/audit/plan-adherence.js +0 -224
  178. package/src/autonomy/arbiter-role.js +0 -71
  179. package/src/autonomy/arbiter.js +0 -22
  180. package/src/autonomy/decider.js +0 -18
  181. package/src/autonomy/decision-resolver.js +0 -58
  182. package/src/autonomy/outcome-report.js +0 -55
  183. package/src/autonomy/stage-ask.js +0 -30
  184. package/src/brain/decision-tracker.js +0 -125
  185. package/src/brain/decisor.js +0 -243
  186. package/src/brain/solomon-consult.js +0 -171
  187. package/src/brain/standby-scheduler.js +0 -8
  188. package/src/budget/comparison.js +0 -96
  189. package/src/canvas/parse-md.js +0 -238
  190. package/src/canvas/schema.js +0 -101
  191. package/src/canvas/to-flat.js +0 -85
  192. package/src/canvas/validate.js +0 -163
  193. package/src/ci/dispatch.js +0 -99
  194. package/src/ci/index.js +0 -3
  195. package/src/ci/pr-diff.js +0 -26
  196. package/src/cli/register-standby.js +0 -26
  197. package/src/commands/architect.js +0 -111
  198. package/src/commands/autorun.js +0 -49
  199. package/src/commands/researcher.js +0 -54
  200. package/src/commands/resume.js +0 -72
  201. package/src/commands/review.js +0 -89
  202. package/src/commands/run.js +0 -185
  203. package/src/commands/standby.js +0 -117
  204. package/src/commands/status.js +0 -36
  205. package/src/commands/triage.js +0 -78
  206. package/src/commands/undo.js +0 -62
  207. package/src/domains/domain-loader.js +0 -245
  208. package/src/domains/domain-registry.js +0 -140
  209. package/src/domains/domain-synthesizer.js +0 -129
  210. package/src/git/automation.js +0 -433
  211. package/src/git/coder-commit-guard.js +0 -40
  212. package/src/git/hu-automation.js +0 -243
  213. package/src/git/hu-snapshot.js +0 -10
  214. package/src/git/symlink-targets.js +0 -57
  215. package/src/golden/asserter.js +0 -92
  216. package/src/golden/loader.js +0 -52
  217. package/src/golden/runner.js +0 -92
  218. package/src/golden/schema.js +0 -41
  219. package/src/guards/duplicate-members.js +0 -86
  220. package/src/guards/intent-guard.js +0 -161
  221. package/src/guards/perf-guard.js +0 -126
  222. package/src/guards/policy-guard.js +0 -90
  223. package/src/guards/policy-resolver.js +0 -85
  224. package/src/guards/secret-redactor.js +0 -71
  225. package/src/hu/acceptance-runner.js +0 -138
  226. package/src/hu/lazy-planner.js +0 -109
  227. package/src/hu/parallel-executor.js +0 -94
  228. package/src/hu/routing-metrics.js +0 -17
  229. package/src/hu/split-validator.js +0 -143
  230. package/src/hu/splitting-detector.js +0 -158
  231. package/src/hu/splitting-generator.js +0 -175
  232. package/src/hu/store.js +0 -394
  233. package/src/infrastructure/mocks.js +0 -182
  234. package/src/mcp/handlers/run-handler.js +0 -318
  235. package/src/mcp/sovereignty-guard.js +0 -113
  236. package/src/mcp/tool-arg-normalizers.js +0 -16
  237. package/src/mutate/reviewer-signal.js +0 -76
  238. package/src/orchestrator/agent-fallback.js +0 -81
  239. package/src/orchestrator/brain-coordinator.js +0 -186
  240. package/src/orchestrator/ci-integration.js +0 -139
  241. package/src/orchestrator/config-init.js +0 -426
  242. package/src/orchestrator/direct-actions.js +0 -217
  243. package/src/orchestrator/drivers/error-recovery.js +0 -287
  244. package/src/orchestrator/drivers/init-context.js +0 -238
  245. package/src/orchestrator/drivers/iteration-loop.js +0 -382
  246. package/src/orchestrator/drivers/iteration-phases/coder-and-refactorer.js +0 -69
  247. package/src/orchestrator/drivers/iteration-phases/guards.js +0 -91
  248. package/src/orchestrator/drivers/iteration-phases/handle-approved.js +0 -29
  249. package/src/orchestrator/drivers/iteration-phases/quality-gates.js +0 -125
  250. package/src/orchestrator/drivers/iteration-phases/reviewer-gate.js +0 -62
  251. package/src/orchestrator/drivers/post-loop.js +0 -404
  252. package/src/orchestrator/drivers/pre-loop-phases/auto-hu-batch.js +0 -137
  253. package/src/orchestrator/drivers/pre-loop-phases/config-deprecations.js +0 -47
  254. package/src/orchestrator/drivers/pre-loop-phases/ensure-addyosmani-skills.js +0 -84
  255. package/src/orchestrator/drivers/pre-loop-phases/inject-loaded-plan.js +0 -393
  256. package/src/orchestrator/drivers/pre-loop.js +0 -524
  257. package/src/orchestrator/drivers/run-hu-batch.js +0 -468
  258. package/src/orchestrator/feedback-enrichment.js +0 -140
  259. package/src/orchestrator/feedback-queue.js +0 -151
  260. package/src/orchestrator/flow-control.js +0 -170
  261. package/src/orchestrator/flow-runner.js +0 -365
  262. package/src/orchestrator/fs-leak-detector.js +0 -228
  263. package/src/orchestrator/hu-scheduler.js +0 -114
  264. package/src/orchestrator/hu-sub-pipeline.js +0 -642
  265. package/src/orchestrator/iteration-gate.js +0 -73
  266. package/src/orchestrator/iteration-stages.js +0 -9
  267. package/src/orchestrator/parallel-limiter.js +0 -74
  268. package/src/orchestrator/pipeline-context.js +0 -72
  269. package/src/orchestrator/post-loop-stages.js +0 -468
  270. package/src/orchestrator/pre-loop-stages.js +0 -11
  271. package/src/orchestrator/preflight-checks.js +0 -487
  272. package/src/orchestrator/repair/repair-detector.js +0 -117
  273. package/src/orchestrator/repair/repair-runner.js +0 -142
  274. package/src/orchestrator/reviewer-fallback.js +0 -45
  275. package/src/orchestrator/role-output-compressor.js +0 -189
  276. package/src/orchestrator/session-journal.js +0 -308
  277. package/src/orchestrator/solomon-escalation.js +0 -331
  278. package/src/orchestrator/solomon-rules.js +0 -212
  279. package/src/orchestrator/stages/acceptance-coverage-stage.js +0 -47
  280. package/src/orchestrator/stages/architect-stage.js +0 -172
  281. package/src/orchestrator/stages/coder-stage.js +0 -506
  282. package/src/orchestrator/stages/domain-curator-stage.js +0 -82
  283. package/src/orchestrator/stages/format-stage.js +0 -61
  284. package/src/orchestrator/stages/hu-reviewer-phases/detect-and-apply-splits.js +0 -127
  285. package/src/orchestrator/stages/hu-reviewer-stage.js +0 -419
  286. package/src/orchestrator/stages/perf-stage.js +0 -140
  287. package/src/orchestrator/stages/planner-stage.js +0 -85
  288. package/src/orchestrator/stages/rag-context-stage.js +0 -80
  289. package/src/orchestrator/stages/rag-preload-decisor.js +0 -58
  290. package/src/orchestrator/stages/research-stage.js +0 -69
  291. package/src/orchestrator/stages/reviewer-stage.js +0 -431
  292. package/src/orchestrator/stages/sonar-stage.js +0 -439
  293. package/src/orchestrator/stages/stage-classes.js +0 -84
  294. package/src/orchestrator/stages/stage-executor.js +0 -155
  295. package/src/orchestrator/stages/suite-green-stage.js +0 -62
  296. package/src/orchestrator/stages/tdd-discipline-stage.js +0 -56
  297. package/src/orchestrator/stages/tool-judge-stage.js +0 -53
  298. package/src/orchestrator/stages/triage-stage.js +0 -172
  299. package/src/orchestrator/standby.js +0 -71
  300. package/src/orchestrator/verification-gate.js +0 -183
  301. package/src/orchestrator.js +0 -22
  302. package/src/plan/adr-generator.js +0 -170
  303. package/src/plan/adr-loader.js +0 -60
  304. package/src/plan/plan-executor.js +0 -100
  305. package/src/plan/plan-validation.js +0 -4
  306. package/src/prompts/acceptance-coverage.js +0 -32
  307. package/src/prompts/arbiter.js +0 -38
  308. package/src/prompts/architect.js +0 -103
  309. package/src/prompts/hu-reviewer.js +0 -244
  310. package/src/prompts/tool-correctness-judge.js +0 -31
  311. package/src/prompts/triage.js +0 -57
  312. package/src/rag/embedder.js +0 -2
  313. package/src/rag/embedders/_cloud-base.js +0 -2
  314. package/src/rag/embedders/cohere.js +0 -2
  315. package/src/rag/embedders/mistral.js +0 -2
  316. package/src/rag/embedders/onnx.js +0 -2
  317. package/src/rag/embedders/openai.js +0 -2
  318. package/src/rag/embedders/voyage.js +0 -2
  319. package/src/rag/rerank.js +0 -4
  320. package/src/rag/where-parser.js +0 -4
  321. package/src/repeat-detector.js +0 -88
  322. package/src/review/diff-generator.js +0 -112
  323. package/src/review/git-stash-helper.js +0 -104
  324. package/src/review/schema.js +0 -31
  325. package/src/review/scope-filter.js +0 -152
  326. package/src/review/snapshot-diff.js +0 -122
  327. package/src/review/tdd-discipline.js +0 -90
  328. package/src/review/tdd-policy.js +0 -69
  329. package/src/roles/acceptance-coverage-role.js +0 -78
  330. package/src/roles/architect-role.js +0 -77
  331. package/src/roles/coder-role.js +0 -113
  332. package/src/roles/commiter-role.js +0 -113
  333. package/src/roles/domain-curator-role.js +0 -134
  334. package/src/roles/hu-reviewer-role.js +0 -64
  335. package/src/roles/impeccable-role.js +0 -85
  336. package/src/roles/index.js +0 -18
  337. package/src/roles/karajan-brain-role.js +0 -134
  338. package/src/roles/perf-role.js +0 -122
  339. package/src/roles/planner-role.js +0 -151
  340. package/src/roles/refactorer-role.js +0 -30
  341. package/src/roles/repairer-role.js +0 -181
  342. package/src/roles/researcher-role.js +0 -70
  343. package/src/roles/reviewer-role.js +0 -115
  344. package/src/roles/security-role.js +0 -73
  345. package/src/roles/solomon-role.js +0 -271
  346. package/src/roles/sonar-role.js +0 -72
  347. package/src/roles/tester-role.js +0 -235
  348. package/src/roles/tool-correctness-judge-role.js +0 -78
  349. package/src/roles/triage-role.js +0 -99
  350. package/src/session/journal/decisions-writer.js +0 -200
  351. package/src/session/journal/iteration-logger.js +0 -194
  352. package/src/session/journal/summary-writer.js +0 -320
  353. package/src/session/journal/tree-writer.js +0 -164
  354. package/src/session/journal/write-all.js +0 -89
  355. package/src/session-cleanup.js +0 -116
  356. package/src/skills/semantic-detector.js +0 -167
  357. package/src/skills/skill-detector.js +0 -385
  358. package/src/sonar/cloud-scanner.js +0 -78
  359. package/src/tracker-bootstrap.js +0 -44
  360. package/src/types/index.js +0 -38
  361. package/src/types/policy.js +0 -43
  362. package/src/types/solomon.js +0 -87
  363. package/src/utils/async-lock.js +0 -31
  364. package/src/utils/budget.js +0 -229
  365. package/src/utils/cwd-confirm.js +0 -39
  366. package/src/utils/display/event-handlers.js +0 -403
  367. package/src/utils/display/formatters.js +0 -132
  368. package/src/utils/display/header.js +0 -47
  369. package/src/utils/display/session-summary.js +0 -118
  370. package/src/utils/display/solomon.js +0 -42
  371. package/src/utils/pricing.js +0 -29
  372. package/src/utils/quota-simulator.js +0 -56
  373. package/src/utils/role-assigner.js +0 -137
  374. package/src/utils/rtk-wrapper.js +0 -87
  375. package/src/utils/run-registry.js +0 -9
  376. package/src/utils/status-dashboard.js +0 -254
  377. package/src/utils/tool-call-extractor.js +0 -56
  378. package/src/webperf/devtools-detect.js +0 -112
  379. package/src/webperf/webperf-role-integration.js +0 -66
  380. package/templates/skills/kj-run.md +0 -73
@@ -51,15 +51,14 @@ export function briefCommand({ config = null, flags = {}, role = null }) {
51
51
  return { role, brief: text };
52
52
  }
53
53
 
54
- export async function envInstallCommand({ config = null, logger = null, flags = {} }) {
55
- const projectDir = config?.projectDir || process.cwd();
56
- // KJC-BUG-0273: what is dirty NOW is the person's; kj commits only what it generates below.
57
- const dirtyBefore = contractChanges(projectDir);
58
-
59
- // KJC-TSK-0709 — the board is chosen BEFORE the playbook renders (its
60
- // tracking line depends on the backend): interactive installs ask when
61
- // alternatives are reachable, headless installs name the default. The
62
- // choice persists so it is only ever asked once.
54
+ /**
55
+ * KJC-TSK-0709 — the board is chosen BEFORE the playbook renders (its
56
+ * tracking line depends on the backend): interactive installs ask when
57
+ * alternatives are reachable, headless installs name the default. The
58
+ * choice persists so it is only ever asked once.
59
+ * @returns {Promise<object>} the config with the effective backend
60
+ */
61
+ async function withChosenBoard(config, projectDir) {
63
62
  try {
64
63
  const wizard = process.stdin.isTTY && process.stdout.isTTY ? createWizard() : null;
65
64
  const sel = await pickStateBackend({ projectDir, wizard, isTTY: Boolean(wizard), logger: console });
@@ -67,10 +66,151 @@ export async function envInstallCommand({ config = null, logger = null, flags =
67
66
  // The effective backend feeds the render on EVERY non-error path —
68
67
  // chosen, declared-in-file or default — so the playbook never falls
69
68
  // back to hu-board while the selection said otherwise.
70
- config = { ...config, state_backend: sel.backend, board: sel.boardName ? { ...config?.board, name: sel.boardName } : config?.board };
69
+ return { ...config, state_backend: sel.backend, board: sel.boardName ? { ...config?.board, name: sel.boardName } : config?.board };
71
70
  } catch (err) {
72
71
  console.log(`⚠ board selection failed (${err.message}) — continuing with the configured default`);
72
+ return config;
73
+ }
74
+ }
75
+
76
+ /** Stops the install with a pending block (exit 3): the playbook stays installed. */
77
+ function pend(result, items, opts) {
78
+ result.exitCode = PENDING_EXIT_CODE;
79
+ const block = renderPendingBlock(items, opts);
80
+ console.log(block);
81
+ return block;
82
+ }
83
+
84
+ /**
85
+ * KJC-TSK-0685 (user rule): Karajan does not run without a board — it is
86
+ * what guarantees ordered, card-first work. Verify an OPERATIONAL access
87
+ * path to the declared backend BEFORE anything else; no path → block
88
+ * (exit 3) with the exact steps. The playbook stays installed.
89
+ * @returns {boolean} true when the install must stop
90
+ */
91
+ function boardBlocked(result, config, projectDir) {
92
+ const access = verifyBoardAccess({ config, projectDir });
93
+ if (access.ok) {
94
+ if (access.backend !== "hu-board") console.log(`✓ board access verified: ${access.via}`);
95
+ return false;
96
+ }
97
+ result.boardError = access.needed.join(" | ");
98
+ pend(result,
99
+ [{ tool: `${access.name || access.backend} board`, action: "needs-user", reason: `Karajan does not run without a board. To use ${access.name || access.backend}: ${access.needed.join(" ")}` }],
100
+ { retry: "kj env install" });
101
+ return true;
102
+ }
103
+
104
+ /**
105
+ * KJC-BUG-0133 — installing IS activating. The setup flow used to order
106
+ * harden + the verdict gate as TEXT steps the agent runs (or narrates);
107
+ * a session that skipped them got a decorative method — field case
108
+ * 2026-08-02: installed karajan, then wrote everything by hand with
109
+ * nothing to stop it. env install now does them itself, idempotently.
110
+ * @returns {Promise<boolean>} true when the install must stop
111
+ */
112
+ async function enforcementBlocked(result, config, projectDir) {
113
+ if (!existsSync(join(projectDir, ".git"))) {
114
+ pend(result,
115
+ [{ tool: "git", action: "needs-user", reason: "the enforcement gates live in git hooks — run `git init` (Karajan's guarantees do not exist without git)" }],
116
+ { retry: "kj env install" });
117
+ return true;
118
+ }
119
+ try {
120
+ const h = await hardenCommand({ projectDir, logger: console });
121
+ if (h?.ok === false) throw new Error(h.error || "kj harden failed");
122
+ // KJC-BUG-0188: a headless harden never binds an identity by itself, and
123
+ // it must not. Blocking here beats the Sentinel denying every git and gh
124
+ // call later, with nothing on screen tying that back to this install.
125
+ if (h?.identityPending) {
126
+ result.pendingBlock = pend(result,
127
+ [{ tool: "identity", action: "needs-user", reason: h.identityPending, command: h.identityCommand }],
128
+ { retry: "kj env install", why: "this clone has no declared identity, and kj never binds one on its own" });
129
+ return true;
130
+ }
131
+ const gate = await reviewGateCommand({ config, flags: { installGate: true } });
132
+ // KJC-BUG-0447: a gate no hook runs is decorative; never call it active.
133
+ if (!gate?.enforced) throw new Error("the review gate is declared but no pre-commit hook runs it");
134
+ console.log("✓ enforcement active: git hooks + cross-AI verdict gate — a commit outside the method is rejected, not narrated");
135
+ return false;
136
+ } catch (err) {
137
+ pend(result,
138
+ [{ tool: "enforcement", action: "needs-user", reason: `hooks/gate could not be installed: ${err.message} — without them the method is narratable` }],
139
+ { retry: "kj env install" });
140
+ return true;
73
141
  }
142
+ }
143
+
144
+ /**
145
+ * KJC-TSK-0683: on limited machines (no Ollama, no Docker) the DEFAULT
146
+ * provider falls back to the built-in ONNX embedder instead of blocking.
147
+ * Only for the first index and only when the user didn't pick a provider
148
+ * explicitly — the election is persisted (dims differ, it must stick).
149
+ */
150
+ async function tryOnnxFallback({ config, projectDir, logger, why }) {
151
+ if (config?.rag?.embedder?.provider) return false;
152
+ console.log(`⚠ default embedder unavailable (${why}) — trying the built-in ONNX embedder (no install needed)…`);
153
+ try {
154
+ // KJC-BUG-0128: an empty store may carry a vec table at the wrong
155
+ // dim (the old probe created it at 768) — reset it so ONNX (384)
156
+ // can index. A store with data is never touched.
157
+ if (!resetEmptyStore(projectDir)) {
158
+ console.log(" the vector store already has data at another dimension — not switching automatically");
159
+ return false;
160
+ }
161
+ const totals = await ragIndexCommand({ config: onnxConfig(config), logger, flags: { withSources: true } });
162
+ if ((totals?.indexed ?? 0) > 0) {
163
+ const p = await persistOnnxChoice(projectDir);
164
+ console.log(`✓ RAG indexed with the built-in ONNX embedder — persisted in ${p}`);
165
+ console.log(" To move to Ollama later: install it, remove the rag.embedder block from .karajan/kj.config.yml,");
166
+ console.log(" delete the store (~/.karajan/rag.db) and run: kj rag index --with-sources");
167
+ return true;
168
+ }
169
+ } catch (fallbackErr) {
170
+ console.log(` ONNX fallback also failed: ${fallbackErr.message}`);
171
+ }
172
+ return false;
173
+ }
174
+
175
+ /**
176
+ * ENV-E1: RAG-first — the playbook orders "query the RAG before coding",
177
+ * so installing the environment guarantees the index exists. KJC-TSK-0659
178
+ * stop-on-sudo: an index that cannot be built is a BLOCKING condition —
179
+ * "success" with 0 chunks would leave every future session running the
180
+ * method against an empty RAG (field-reproduced: 0/727 without Ollama).
181
+ * The playbook stays installed; the command exits 3 so the driving agent
182
+ * stops, shows the block, and waits for the user.
183
+ */
184
+ async function ensureRagIndex({ result, config, projectDir, logger }) {
185
+ const provider = config?.rag?.embedder?.provider || "ollama";
186
+ const blockRag = (why) => {
187
+ result.ragError = why;
188
+ const item = provider === "ollama"
189
+ ? { tool: "ollama", action: "needs-user", reason: `the RAG cannot index: ${why}` }
190
+ : { tool: `${provider} embedder`, action: "needs-user", reason: `the RAG cannot index: ${why} — check rag.embedder in kj config`, manualUrl: "kj config" };
191
+ pend(result, [item], { retry: "kj rag index --with-sources (then re-run: kj env install)" });
192
+ };
193
+ const fallbackOrBlock = async (why) => {
194
+ if (!(await tryOnnxFallback({ config, projectDir, logger, why }))) blockRag(why);
195
+ };
196
+ try {
197
+ if (hasRagIndex(config, projectDir)) {
198
+ console.log("✓ RAG index present");
199
+ return;
200
+ }
201
+ console.log("⏳ no RAG index for this project — building it (first time only)…");
202
+ const totals = await ragIndexCommand({ config, logger, flags: { withSources: true } });
203
+ if ((totals?.indexed ?? 0) === 0 && (totals?.files ?? 0) > 0) await fallbackOrBlock(`0 of ${totals.files} files indexed — is the embedder running?`);
204
+ } catch (err) {
205
+ await fallbackOrBlock(err.message);
206
+ }
207
+ }
208
+
209
+ export async function envInstallCommand({ config = null, logger = null, flags = {} }) {
210
+ const projectDir = config?.projectDir || process.cwd();
211
+ // KJC-BUG-0273: what is dirty NOW is the person's; kj commits only what it generates below.
212
+ const dirtyBefore = contractChanges(projectDir);
213
+ config = await withChosenBoard(config, projectDir);
74
214
 
75
215
  const result = await installPlaybook({
76
216
  projectDir, target: flags.target || "all",
@@ -90,145 +230,31 @@ export async function envInstallCommand({ config = null, logger = null, flags =
90
230
  console.log(`⚠ could not wire kj-rag-mcp into .mcp.json: ${err.message}`);
91
231
  }
92
232
 
93
- // KJC-TSK-0685 (user rule): Karajan does not run without a board — it is
94
- // what guarantees ordered, card-first work. Verify an OPERATIONAL access
95
- // path to the declared backend BEFORE anything else; no path → block
96
- // (exit 3) with the exact steps. The playbook stays installed.
97
- const access = verifyBoardAccess({ config, projectDir });
98
- if (!access.ok) {
99
- result.boardError = access.needed.join(" | ");
100
- result.exitCode = PENDING_EXIT_CODE;
101
- console.log(renderPendingBlock(
102
- [{ tool: `${access.name || access.backend} board`, action: "needs-user", reason: `Karajan does not run without a board. To use ${access.name || access.backend}: ${access.needed.join(" ")}` }],
103
- { retry: "kj env install" },
104
- ));
105
- return result;
106
- }
107
- if (access.backend !== "hu-board") console.log(`✓ board access verified: ${access.via}`);
108
-
109
- // KJC-BUG-0133 — installing IS activating. The setup flow used to order
110
- // harden + the verdict gate as TEXT steps the agent runs (or narrates);
111
- // a session that skipped them got a decorative method — field case
112
- // 2026-08-02: installed karajan, then wrote everything by hand with
113
- // nothing to stop it. env install now does them itself, idempotently.
114
- if (flags.enforce !== false) {
115
- if (!existsSync(join(projectDir, ".git"))) {
116
- result.exitCode = PENDING_EXIT_CODE;
117
- console.log(renderPendingBlock(
118
- [{ tool: "git", action: "needs-user", reason: "the enforcement gates live in git hooks — run `git init` (Karajan's guarantees do not exist without git)" }],
119
- { retry: "kj env install" },
120
- ));
121
- return result;
122
- }
123
- try {
124
- const h = await hardenCommand({ projectDir, logger: console });
125
- if (h?.ok === false) throw new Error(h.error || "kj harden failed");
126
- // KJC-BUG-0188: a headless harden never binds an identity by itself, and
127
- // it must not. Blocking here beats the Sentinel denying every git and gh
128
- // call later, with nothing on screen tying that back to this install.
129
- if (h?.identityPending) {
130
- result.exitCode = PENDING_EXIT_CODE;
131
- result.pendingBlock = renderPendingBlock(
132
- [{ tool: "identity", action: "needs-user", reason: h.identityPending, command: h.identityCommand }],
133
- { retry: "kj env install", why: "this clone has no declared identity, and kj never binds one on its own" },
134
- );
135
- console.log(result.pendingBlock);
136
- return result;
137
- }
138
- const gate = await reviewGateCommand({ config, flags: { installGate: true } });
139
- // KJC-BUG-0447: a gate no hook runs is decorative; never call it active.
140
- if (!gate?.enforced) throw new Error("the review gate is declared but no pre-commit hook runs it");
141
- console.log("✓ enforcement active: git hooks + cross-AI verdict gate — a commit outside the method is rejected, not narrated");
142
- } catch (err) {
143
- result.exitCode = PENDING_EXIT_CODE;
144
- console.log(renderPendingBlock(
145
- [{ tool: "enforcement", action: "needs-user", reason: `hooks/gate could not be installed: ${err.message} — without them the method is narratable` }],
146
- { retry: "kj env install" },
147
- ));
148
- return result;
149
- }
150
- }
151
-
152
- // ENV-E1: RAG-first — the playbook orders "query the RAG before coding",
153
- // so installing the environment guarantees the index exists. KJC-TSK-0659
154
- // stop-on-sudo: an index that cannot be built is a BLOCKING condition —
155
- // "success" with 0 chunks would leave every future session running the
156
- // method against an empty RAG (field-reproduced: 0/727 without Ollama).
157
- // The playbook stays installed; the command exits 3 so the driving agent
158
- // stops, shows the block, and waits for the user.
159
- if (flags.rag !== false) {
160
- const provider = config?.rag?.embedder?.provider || "ollama";
161
- const blockRag = (why) => {
162
- result.ragError = why;
163
- result.exitCode = PENDING_EXIT_CODE;
164
- const item = provider === "ollama"
165
- ? { tool: "ollama", action: "needs-user", reason: `the RAG cannot index: ${why}` }
166
- : { tool: `${provider} embedder`, action: "needs-user", reason: `the RAG cannot index: ${why} — check rag.embedder in kj config`, manualUrl: "kj config" };
167
- console.log(renderPendingBlock([item], { retry: "kj rag index --with-sources (then re-run: kj env install)" }));
168
- };
169
- // KJC-TSK-0683: on limited machines (no Ollama, no Docker) the DEFAULT
170
- // provider falls back to the built-in ONNX embedder instead of blocking.
171
- // Only for the first index and only when the user didn't pick a provider
172
- // explicitly — the election is persisted (dims differ, it must stick).
173
- const explicitProvider = Boolean(config?.rag?.embedder?.provider);
174
- const tryOnnxFallback = async (why) => {
175
- if (explicitProvider) return false;
176
- console.log(`⚠ default embedder unavailable (${why}) — trying the built-in ONNX embedder (no install needed)…`);
177
- try {
178
- // KJC-BUG-0128: an empty store may carry a vec table at the wrong
179
- // dim (the old probe created it at 768) — reset it so ONNX (384)
180
- // can index. A store with data is never touched.
181
- if (!resetEmptyStore(projectDir)) {
182
- console.log(" the vector store already has data at another dimension — not switching automatically");
183
- return false;
184
- }
185
- const totals = await ragIndexCommand({ config: onnxConfig(config), logger, flags: { withSources: true } });
186
- if ((totals?.indexed ?? 0) > 0) {
187
- const p = await persistOnnxChoice(projectDir);
188
- console.log(`✓ RAG indexed with the built-in ONNX embedder — persisted in ${p}`);
189
- console.log(" To move to Ollama later: install it, remove the rag.embedder block from .karajan/kj.config.yml,");
190
- console.log(" delete the store (~/.karajan/rag.db) and run: kj rag index --with-sources");
191
- return true;
192
- }
193
- } catch (fallbackErr) {
194
- console.log(` ONNX fallback also failed: ${fallbackErr.message}`);
195
- }
196
- return false;
197
- };
198
- try {
199
- if (hasRagIndex(config, projectDir)) {
200
- console.log("✓ RAG index present");
201
- } else {
202
- console.log("⏳ no RAG index for this project — building it (first time only)…");
203
- const totals = await ragIndexCommand({ config, logger, flags: { withSources: true } });
204
- if ((totals?.indexed ?? 0) === 0 && (totals?.files ?? 0) > 0) {
205
- const why = `0 of ${totals.files} files indexed — is the embedder running?`;
206
- if (!(await tryOnnxFallback(why))) blockRag(why);
207
- }
208
- }
209
- } catch (err) {
210
- if (!(await tryOnnxFallback(err.message))) blockRag(err.message);
211
- }
212
- }
213
- if (result.exitCode !== PENDING_EXIT_CODE) {
214
- // KJC-TSK-0707 (PV-D) — privacy onboarding rides the install: kj asks,
215
- // kj writes the denylist. Best-effort (stubbed in the SEA binary).
216
- try {
217
- const wizard = process.stdin.isTTY && process.stdout.isTTY ? createWizard() : null;
218
- await ensurePrivacyList({ wizard, logger: console, isTTY: Boolean(wizard) });
219
- wizard?.close();
220
- } catch { /* privacy onboarding is best-effort */ }
221
- console.log(" The host agent now follows the method: RAG first, TDD, cross-AI review before commit.");
222
- // KJC-BUG-0273: the contract is kj's to commit. Left to the agent, its own
223
- // PR-size rules forbid a 1200-line commit of generated files and it stops.
224
- const contract = commitContract({ projectDir, before: dirtyBefore, baseBranch: config?.base_branch || "main" });
225
- if (contract.committed) console.log(`✓ contract committed by kj: ${contract.files.length} generated file(s), nothing for the agent to commit`);
226
- else if (!contract.reason.startsWith("nothing")) console.log(`⚠ the contract kj generated is NOT committed (${contract.reason}): run \`kj env install\` again once that is fixed`);
227
- // KJC-BUG-0133 (part 2): a mid-session install lands the playbook in
228
- // CLAUDE.md, but the running session loaded its context BEFORE — nobody
229
- // re-reads it. Print the method so it enters THIS conversation now.
230
- console.log("\n— The Karajan method below is IN EFFECT from this very message. If your session started before this install, apply it from now on:\n");
231
- console.log(renderPlaybook({ stateBackend: config?.state_backend || "hu-board", boardName: config?.board?.name || null, config }));
232
- }
233
+ if (boardBlocked(result, config, projectDir)) return result;
234
+ if (flags.enforce !== false && await enforcementBlocked(result, config, projectDir)) return result;
235
+ if (flags.rag !== false) await ensureRagIndex({ result, config, projectDir, logger });
236
+ if (result.exitCode !== PENDING_EXIT_CODE) await finishInstall({ config, projectDir, dirtyBefore });
233
237
  return result;
234
238
  }
239
+
240
+ /** Privacy onboarding, the contract commit and the method printed into the running session. */
241
+ async function finishInstall({ config, projectDir, dirtyBefore }) {
242
+ // KJC-TSK-0707 (PV-D) — privacy onboarding rides the install: kj asks,
243
+ // kj writes the denylist. Best-effort (stubbed in the SEA binary).
244
+ try {
245
+ const wizard = process.stdin.isTTY && process.stdout.isTTY ? createWizard() : null;
246
+ await ensurePrivacyList({ wizard, logger: console, isTTY: Boolean(wizard) });
247
+ wizard?.close();
248
+ } catch { /* privacy onboarding is best-effort */ }
249
+ console.log(" The host agent now follows the method: RAG first, TDD, cross-AI review before commit.");
250
+ // KJC-BUG-0273: the contract is kj's to commit. Left to the agent, its own
251
+ // PR-size rules forbid a 1200-line commit of generated files and it stops.
252
+ const contract = commitContract({ projectDir, before: dirtyBefore, baseBranch: config?.base_branch || "main" });
253
+ if (contract.committed) console.log(`✓ contract committed by kj: ${contract.files.length} generated file(s), nothing for the agent to commit`);
254
+ else if (!contract.reason.startsWith("nothing")) console.log(`⚠ the contract kj generated is NOT committed (${contract.reason}): run \`kj env install\` again once that is fixed`);
255
+ // KJC-BUG-0133 (part 2): a mid-session install lands the playbook in
256
+ // CLAUDE.md, but the running session loaded its context BEFORE — nobody
257
+ // re-reads it. Print the method so it enters THIS conversation now.
258
+ console.log("\n— The Karajan method below is IN EFFECT from this very message. If your session started before this install, apply it from now on:\n");
259
+ console.log(renderPlaybook({ stateBackend: config?.state_backend || "hu-board", boardName: config?.board?.name || null, config }));
260
+ }
@@ -67,53 +67,83 @@ function defaultLaunch(agent, prompt) {
67
67
  });
68
68
  });
69
69
  }
70
+ const failed = () => {
71
+ process.exitCode = 1;
72
+ return 1;
73
+ };
74
+
75
+ // No agent ready: say what only the person can do (log in, or install one).
76
+ function explainNoAgent(agents, logger) {
77
+ const needLogin = agents.filter((a) => a.installed && !a.authenticated);
78
+ if (needLogin.length > 0) {
79
+ logger.error?.("Tienes el agente instalado pero falta iniciar sesión — eso solo puedes hacerlo tú (la cuenta es tuya; Karajan nunca toca tus credenciales):");
80
+ for (const a of needLogin) logger.error?.(` ${a.label}: ${a.login}`);
81
+ logger.error?.("Cuando hayas iniciado sesión, vuelve a escribir: kj go");
82
+ return;
83
+ }
84
+ logger.error?.("Karajan trabaja con un agente de IA que aún no tienes instalado. Elige uno, instálalo con su comando, e inicia sesión con TU cuenta (la cuenta es tuya):");
85
+ for (const a of agents) logger.error?.(` ${a.label}: ${a.install}`);
86
+ logger.error?.("Después, vuelve a escribir: kj go");
87
+ }
88
+
89
+ // At most one question, and only when more than one agent is ready.
90
+ async function chooseAgent(ready, { flags, deps }) {
91
+ if (ready.length === 1) return ready[0];
92
+ const ask = deps.ask ?? createCliAskQuestion({ flags });
93
+ const answer = await ask("¿Con cuál de tus agentes quieres trabajar?", { options: ready.map((a) => a.name), default: ready[0].name });
94
+ return ready.find((a) => a.name === String(answer).trim()) ?? ready[0];
95
+ }
96
+
97
+ // Prepare ONCE: decisions already taken are never re-asked. Resolves false
98
+ // when something is left for the person's hands (already said above).
99
+ async function prepareOnce({ projectDir, config, logger, deps }) {
100
+ if (existsSync(path.join(projectDir, ".karajan", "review-gate"))) return true;
101
+ logger.info?.("Preparando tu proyecto (solo la primera vez)…");
102
+ // BOOT-A paso 3 (KJC-TSK-0857): el orden vive en UN sitio, kj bootstrap.
103
+ // Antes esta puerta cableaba su propia secuencia (git init y env install),
104
+ // así que la puerta del maggle y la técnica podían separarse sin que nadie
105
+ // se diera cuenta. Ahora las dos hacen lo mismo.
106
+ const booted = await (deps.bootstrap ?? bootstrapCommand)({ config, logger, flags: {}, deps: {} });
107
+ if (!booted.ok) {
108
+ logger.error?.(`Falta una cosa que solo puedes hacer tú, la tienes justo arriba (${booted.pending}). Cuando la hagas, vuelve a escribir: kj go`);
109
+ return false;
110
+ }
111
+ const prepared = deps.prepare ? await deps.prepare({ config, logger }) : null;
112
+ // Algo depende de tus manos (arriba tienes el detalle). Sin eso el
113
+ // proyecto quedaría a medias, así que paramos aquí en vez de seguir.
114
+ if (prepared?.exitCode) {
115
+ logger.error?.("Falta una cosa que solo puedes hacer tú, la tienes justo arriba. Cuando la hagas, vuelve a escribir: kj go");
116
+ return false;
117
+ }
118
+ return true;
119
+ }
120
+
121
+ // The board is the muggle's window — unless the project turned it off
122
+ // (hu_board.enabled false is respected: KJC-BUG-0152). A board failure is
123
+ // said and never stops the conversation from starting. Resolves whether it opened.
124
+ async function openBoard({ config, logger, deps, windowMode }) {
125
+ if (config.hu_board?.enabled === false) return false;
126
+ const openPath = windowMode ? "/?maggle=1&window=1" : "/?maggle=1";
127
+ try {
128
+ await (deps.board ?? defaultBoard)({ config, logger, openPath });
129
+ return true;
130
+ } catch (err) {
131
+ logger.warn?.(`El tablero no pudo abrirse (${err.message}) — la conversación arranca igual.`);
132
+ return false;
133
+ }
134
+ }
135
+
70
136
  export async function goCommand({ config = {}, logger = console, flags = {}, deps = {} } = {}) {
71
137
  const projectDir = config.projectDir || process.cwd();
72
138
  const agents = await (deps.detect ?? detectMaggleAgents)();
73
139
  const ready = agents.filter((a) => a.installed && a.authenticated);
74
- const needLogin = agents.filter((a) => a.installed && !a.authenticated);
75
140
  if (ready.length === 0) {
76
- if (needLogin.length > 0) {
77
- logger.error?.("Tienes el agente instalado pero falta iniciar sesión — eso solo puedes hacerlo tú (la cuenta es tuya; Karajan nunca toca tus credenciales):");
78
- for (const a of needLogin) logger.error?.(` ${a.label}: ${a.login}`);
79
- logger.error?.("Cuando hayas iniciado sesión, vuelve a escribir: kj go");
80
- } else {
81
- logger.error?.("Karajan trabaja con un agente de IA que aún no tienes instalado. Elige uno, instálalo con su comando, e inicia sesión con TU cuenta (la cuenta es tuya):");
82
- for (const a of agents) logger.error?.(` ${a.label}: ${a.install}`);
83
- logger.error?.("Después, vuelve a escribir: kj go");
84
- }
85
- process.exitCode = 1;
86
- return 1;
87
- }
88
- let chosen = ready[0];
89
- if (ready.length > 1) {
90
- const ask = deps.ask ?? createCliAskQuestion({ flags });
91
- const answer = await ask("¿Con cuál de tus agentes quieres trabajar?", { options: ready.map((a) => a.name), default: ready[0].name });
92
- chosen = ready.find((a) => a.name === String(answer).trim()) ?? ready[0];
141
+ explainNoAgent(agents, logger);
142
+ return failed();
93
143
  }
144
+ const chosen = await chooseAgent(ready, { flags, deps });
94
145
  logger.info?.(`Trabajarás con ${chosen.label}.`);
95
- // Prepare ONCE: decisions already taken are never re-asked.
96
- if (!existsSync(path.join(projectDir, ".karajan", "review-gate"))) {
97
- logger.info?.("Preparando tu proyecto (solo la primera vez)…");
98
- // BOOT-A paso 3 (KJC-TSK-0857): el orden vive en UN sitio, kj bootstrap.
99
- // Antes esta puerta cableaba su propia secuencia (git init y env install),
100
- // así que la puerta del maggle y la técnica podían separarse sin que nadie
101
- // se diera cuenta. Ahora las dos hacen lo mismo.
102
- const booted = await (deps.bootstrap ?? bootstrapCommand)({ config, logger, flags: {}, deps: {} });
103
- if (!booted.ok) {
104
- logger.error?.(`Falta una cosa que solo puedes hacer tú, la tienes justo arriba (${booted.pending}). Cuando la hagas, vuelve a escribir: kj go`);
105
- process.exitCode = 1;
106
- return 1;
107
- }
108
- const prepared = deps.prepare ? await deps.prepare({ config, logger }) : null;
109
- // Algo depende de tus manos (arriba tienes el detalle). Sin eso el
110
- // proyecto quedaría a medias, así que paramos aquí en vez de seguir.
111
- if (prepared?.exitCode) {
112
- logger.error?.("Falta una cosa que solo puedes hacer tú, la tienes justo arriba. Cuando la hagas, vuelve a escribir: kj go");
113
- process.exitCode = 1;
114
- return 1;
115
- }
116
- }
146
+ if (!(await prepareOnce({ projectDir, config, logger, deps }))) return failed();
117
147
  const prompt = (deps.prompt ?? buildGoPrompt)();
118
148
  // --window (MGL-E, ADR 0008): la conversación vive DENTRO del board — el
119
149
  // daemon hereda por env (HU_BOARD_TERMINAL_CWD/AGENT/PROMPT) el cwd, el
@@ -126,19 +156,7 @@ export async function goCommand({ config = {}, logger = console, flags = {}, dep
126
156
  process.env.HU_BOARD_TERMINAL_AGENT = chosen.name;
127
157
  process.env.HU_BOARD_TERMINAL_PROMPT = prompt;
128
158
  }
129
- // The board is the muggle's window — unless the project turned it off
130
- // (hu_board.enabled false is respected: KJC-BUG-0152). A board failure is
131
- // said and never stops the conversation from starting.
132
- let boardOpened = false;
133
- if (config.hu_board?.enabled !== false) {
134
- const openPath = windowMode ? "/?maggle=1&window=1" : "/?maggle=1";
135
- try {
136
- await (deps.board ?? defaultBoard)({ config, logger, openPath });
137
- boardOpened = true;
138
- } catch (err) {
139
- logger.warn?.(`El tablero no pudo abrirse (${err.message}) — la conversación arranca igual.`);
140
- }
141
- }
159
+ const boardOpened = await openBoard({ config, logger, deps, windowMode });
142
160
  if (windowMode && boardOpened) {
143
161
  logger.info?.("Tu conversación vive en la ventana del navegador que se acaba de abrir. Si el tablero ya estaba arrancado de antes, reinícialo con kj board stop && kj go --window para que recoja este proyecto.");
144
162
  process.exitCode = 0;