karajan-code 4.70.4 → 5.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (390) hide show
  1. package/README.md +2 -2
  2. package/package.json +6 -14
  3. package/packages/hu-board/package.json +1 -1
  4. package/packages/hu-board/public/app.js +4 -18
  5. package/packages/hu-board/public/index.html +5 -2
  6. package/packages/hu-board/public/styles.css +26 -0
  7. package/packages/hu-board/public/utils/api.js +0 -74
  8. package/packages/hu-board/public/utils/board-tour.js +62 -0
  9. package/packages/hu-board/public/utils/board-view.js +49 -144
  10. package/packages/hu-board/public/utils/command-launcher.js +8 -70
  11. package/packages/hu-board/public/utils/formatters.js +10 -0
  12. package/packages/hu-board/public/utils/governance-view.js +13 -1
  13. package/packages/hu-board/public/utils/hu-actions.js +17 -68
  14. package/packages/hu-board/public/utils/init-listeners.js +7 -8
  15. package/packages/hu-board/public/utils/log-panel.js +3 -16
  16. package/packages/hu-board/public/utils/maggle-mode.js +2 -12
  17. package/packages/hu-board/public/utils/modals.js +7 -0
  18. package/packages/hu-board/public/utils/project-picker-view.js +1 -1
  19. package/packages/hu-board/public/utils/server-push.js +3 -22
  20. package/packages/hu-board/public/utils/shared-board-notice.js +33 -0
  21. package/packages/hu-board/public/utils/story-detail-view.js +5 -19
  22. package/packages/hu-board/src/cleanup-zombies.js +0 -1
  23. package/packages/hu-board/src/command-runner.js +36 -25
  24. package/packages/hu-board/src/db-connection.js +282 -0
  25. package/packages/hu-board/src/db-paths.js +113 -0
  26. package/packages/hu-board/src/db-queries.js +188 -0
  27. package/packages/hu-board/src/db-tombstones.js +51 -0
  28. package/packages/hu-board/src/db.js +28 -619
  29. package/packages/hu-board/src/ephemeral-cleaner.js +2 -3
  30. package/packages/hu-board/src/hu-zombie-reaper.js +5 -1
  31. package/packages/hu-board/src/plan-mutations.js +3 -153
  32. package/packages/hu-board/src/plans-service.js +55 -0
  33. package/packages/hu-board/src/routes/api/commands.js +122 -0
  34. package/packages/hu-board/src/routes/api/plans.js +55 -0
  35. package/packages/hu-board/src/routes/api/projects.js +267 -0
  36. package/packages/hu-board/src/routes/api/prompts.js +135 -0
  37. package/packages/hu-board/src/routes/api/sessions.js +95 -0
  38. package/packages/hu-board/src/routes/api/stories.js +183 -0
  39. package/packages/hu-board/src/routes/api/system.js +241 -0
  40. package/packages/hu-board/src/routes/api/tombstones.js +38 -0
  41. package/packages/hu-board/src/routes/api.js +24 -1371
  42. package/packages/hu-board/src/routes/governance.js +23 -1
  43. package/packages/hu-board/src/routes/lanes.js +40 -0
  44. package/packages/hu-board/src/routes/rag.js +4 -3
  45. package/packages/hu-board/src/server.js +20 -34
  46. package/packages/hu-board/src/sync-files.js +318 -0
  47. package/packages/hu-board/src/sync-project.js +99 -0
  48. package/packages/hu-board/src/sync.js +22 -566
  49. package/packages/hu-board/src/zombie-reaper.js +1 -1
  50. package/scripts/dual-publish.mjs +19 -3
  51. package/scripts/postinstall.js +15 -2
  52. package/scripts/verify-pack-mcp.mjs +11 -7
  53. package/scripts/verify-pack.mjs +4 -4
  54. package/src/audit/audit-history.js +3 -2
  55. package/src/audit/dead-exports.js +68 -59
  56. package/src/audit/deterministic-summary.js +33 -13
  57. package/src/audit/npm-signatures.js +41 -0
  58. package/src/audit/sbom.js +35 -0
  59. package/src/checks/agent-token.js +112 -0
  60. package/src/checks/dir-setup.js +34 -1
  61. package/src/checks/native-build.js +21 -39
  62. package/src/checks/runner.js +128 -142
  63. package/src/cli/advanced-commands.js +6 -6
  64. package/src/cli/register-meta.js +42 -82
  65. package/src/cli/register-pipeline.js +8 -143
  66. package/src/cli/register-plan.js +9 -100
  67. package/src/cli/register-roles-skills.js +1 -1
  68. package/src/cli/retired-commands.js +49 -0
  69. package/src/cli.js +18 -5
  70. package/src/commands/agent-run.js +40 -26
  71. package/src/commands/audit.js +127 -124
  72. package/src/commands/board.js +5 -9
  73. package/src/commands/check.js +29 -16
  74. package/src/commands/doctor.js +2 -0
  75. package/src/commands/env.js +176 -150
  76. package/src/commands/go.js +71 -53
  77. package/src/commands/harden.js +90 -81
  78. package/src/commands/hu-start.js +52 -0
  79. package/src/commands/hu.js +288 -221
  80. package/src/commands/identity.js +111 -78
  81. package/src/commands/init-atril.js +47 -0
  82. package/src/commands/init.js +11 -40
  83. package/src/commands/install-tools.js +70 -85
  84. package/src/commands/main-guard.js +78 -0
  85. package/src/commands/policy.js +226 -184
  86. package/src/commands/qmd.js +27 -21
  87. package/src/commands/rag.js +121 -55
  88. package/src/commands/review-gate.js +421 -341
  89. package/src/commands/skills.js +4 -1
  90. package/src/commands/start.js +4 -5
  91. package/src/commands/steward.js +96 -47
  92. package/src/commands/tournament.js +46 -37
  93. package/src/commands/worktree.js +107 -26
  94. package/src/config/defaults.js +1 -0
  95. package/src/config/loader.js +34 -17
  96. package/src/config/role-resolver.js +1 -0
  97. package/src/config/schema.js +1 -0
  98. package/src/config/secrets.js +36 -0
  99. package/src/config.js +1 -0
  100. package/src/environment/adr.js +16 -2
  101. package/src/git/karajan-label.js +20 -8
  102. package/src/guards/main-guard.js +54 -0
  103. package/src/harden/hook-templates.js +16 -1
  104. package/src/harden/sentinel/posttooluse.mjs +18 -6
  105. package/src/harden/sentinel/pretooluse-sentinel.mjs +64 -36
  106. package/src/harden/sentinel/sentinel-lib.mjs +129 -26
  107. package/src/harden/sentinel/sentinel-shell.mjs +5 -16
  108. package/src/harden/sentinel/sessionstart.mjs +3 -2
  109. package/src/harden/sentinel/stop.mjs +5 -4
  110. package/src/harden/workflow-templates.js +1 -1
  111. package/src/hu/board-lookup.js +29 -0
  112. package/src/hu/gh-identity.js +23 -0
  113. package/src/hu/github-board.js +4 -2
  114. package/src/hu/lane-prune.js +83 -13
  115. package/src/hu/lane-setup.js +65 -0
  116. package/src/hu/lanes.js +66 -0
  117. package/src/hu/team-board-mirror.js +3 -2
  118. package/src/infrastructure/command-runner.js +1 -1
  119. package/src/infrastructure/file-system-service.js +1 -1
  120. package/src/mcp/handlers/direct-handlers.js +2 -292
  121. package/src/mcp/handlers/hu-handlers.js +1 -35
  122. package/src/mcp/handlers/management-handlers.js +2 -42
  123. package/src/mcp/handlers/rag-handler.js +9 -1
  124. package/src/mcp/server-handlers.js +16 -22
  125. package/src/mcp/tools.js +1 -196
  126. package/src/plan/lane-plans.js +54 -0
  127. package/src/plan/plan-store.js +7 -4
  128. package/src/policy/add.js +26 -12
  129. package/src/policy/adr-number-check.js +31 -0
  130. package/src/prompts/audit.js +299 -339
  131. package/src/prompts/card-context.js +10 -5
  132. package/src/prompts/reviewer.js +5 -2
  133. package/src/rag/auto-update.js +71 -44
  134. package/src/rag/bootstrap-index.js +46 -54
  135. package/src/rag/indexer.js +9 -4
  136. package/src/rag/migrate.js +1 -1
  137. package/src/rag/onnx-fallback.js +2 -2
  138. package/src/rag/pid-lease.js +181 -0
  139. package/src/rag/project-store.js +2 -12
  140. package/src/review/card-first.js +57 -46
  141. package/src/review/gate-gitignore.js +23 -4
  142. package/src/review/gate-hook.js +12 -4
  143. package/src/review/one-shot-review.js +45 -35
  144. package/src/review/publish-pr.js +14 -2
  145. package/src/review/repo-docs.js +73 -0
  146. package/src/review/repo-stack.js +59 -0
  147. package/src/review/reviewer-fallback.js +19 -4
  148. package/src/review/seal-commit.js +29 -0
  149. package/src/review/sonar-pregate.js +59 -44
  150. package/src/review/third-party-review.js +90 -16
  151. package/src/review/third-party-size.js +43 -0
  152. package/src/review/third-party-skills.js +54 -0
  153. package/src/review/verify-pr.js +1 -24
  154. package/src/roles/audit-role.js +106 -125
  155. package/src/skills/atril-cli.js +19 -1
  156. package/src/skills/atril-skills.js +5 -4
  157. package/src/skills/skills-search.js +72 -0
  158. package/src/skills/translate.js +62 -0
  159. package/src/sonar/scanner.js +116 -101
  160. package/src/steward/invariants.js +5 -4
  161. package/src/steward/phantom-sweep.js +66 -0
  162. package/src/tournament/judge.js +60 -53
  163. package/src/utils/gh-account.js +37 -0
  164. package/src/utils/git.js +0 -13
  165. package/src/utils/home-migration.js +7 -1
  166. package/src/utils/main-checkout.js +53 -0
  167. package/src/utils/qmd-collection.js +3 -1
  168. package/src/utils/welcome.js +3 -3
  169. package/templates/{skills → atril/commands}/kj-architect.md +5 -0
  170. package/templates/{skills → atril/commands}/kj-audit.md +5 -0
  171. package/templates/{skills → atril/commands}/kj-board.md +5 -0
  172. package/templates/{skills → atril/commands}/kj-code.md +5 -0
  173. package/templates/{skills → atril/commands}/kj-csv-transform.md +5 -0
  174. package/templates/{skills → atril/commands}/kj-data-report.md +5 -0
  175. package/templates/{skills → atril/commands}/kj-discover.md +5 -0
  176. package/templates/{skills → atril/commands}/kj-rag-query.md +5 -0
  177. package/templates/{skills → atril/commands}/kj-review.md +5 -0
  178. package/templates/{skills → atril/commands}/kj-security.md +5 -0
  179. package/templates/{skills → atril/commands}/kj-sonar.md +5 -0
  180. package/templates/{skills → atril/commands}/kj-sql-analysis.md +5 -0
  181. package/templates/{skills → atril/commands}/kj-test.md +5 -0
  182. package/packages/hu-board/public/utils/run-launcher.js +0 -169
  183. package/packages/hu-board/src/run-tracker.js +0 -121
  184. package/src/activity-log.js +0 -59
  185. package/src/agents/host-agent.js +0 -38
  186. package/src/agents/model-registry.js +0 -142
  187. package/src/audit/plan-adherence.js +0 -224
  188. package/src/autonomy/arbiter-role.js +0 -71
  189. package/src/autonomy/arbiter.js +0 -22
  190. package/src/autonomy/decider.js +0 -18
  191. package/src/autonomy/decision-resolver.js +0 -58
  192. package/src/autonomy/outcome-report.js +0 -55
  193. package/src/autonomy/stage-ask.js +0 -30
  194. package/src/brain/decision-tracker.js +0 -125
  195. package/src/brain/decisor.js +0 -243
  196. package/src/brain/solomon-consult.js +0 -171
  197. package/src/brain/standby-scheduler.js +0 -8
  198. package/src/budget/comparison.js +0 -96
  199. package/src/canvas/parse-md.js +0 -238
  200. package/src/canvas/schema.js +0 -101
  201. package/src/canvas/to-flat.js +0 -85
  202. package/src/canvas/validate.js +0 -163
  203. package/src/ci/dispatch.js +0 -99
  204. package/src/ci/index.js +0 -3
  205. package/src/ci/pr-diff.js +0 -26
  206. package/src/cli/register-standby.js +0 -26
  207. package/src/commands/architect.js +0 -111
  208. package/src/commands/autorun.js +0 -49
  209. package/src/commands/researcher.js +0 -54
  210. package/src/commands/resume.js +0 -72
  211. package/src/commands/review.js +0 -89
  212. package/src/commands/run.js +0 -185
  213. package/src/commands/standby.js +0 -117
  214. package/src/commands/status.js +0 -36
  215. package/src/commands/triage.js +0 -78
  216. package/src/commands/undo.js +0 -62
  217. package/src/domains/domain-loader.js +0 -245
  218. package/src/domains/domain-registry.js +0 -140
  219. package/src/domains/domain-synthesizer.js +0 -129
  220. package/src/git/automation.js +0 -433
  221. package/src/git/coder-commit-guard.js +0 -40
  222. package/src/git/hu-automation.js +0 -243
  223. package/src/git/hu-snapshot.js +0 -10
  224. package/src/git/symlink-targets.js +0 -57
  225. package/src/golden/asserter.js +0 -92
  226. package/src/golden/loader.js +0 -52
  227. package/src/golden/runner.js +0 -92
  228. package/src/golden/schema.js +0 -41
  229. package/src/guards/duplicate-members.js +0 -86
  230. package/src/guards/intent-guard.js +0 -161
  231. package/src/guards/perf-guard.js +0 -126
  232. package/src/guards/policy-guard.js +0 -90
  233. package/src/guards/policy-resolver.js +0 -85
  234. package/src/guards/secret-redactor.js +0 -71
  235. package/src/hu/acceptance-runner.js +0 -138
  236. package/src/hu/lazy-planner.js +0 -109
  237. package/src/hu/parallel-executor.js +0 -94
  238. package/src/hu/routing-metrics.js +0 -17
  239. package/src/hu/split-validator.js +0 -143
  240. package/src/hu/splitting-detector.js +0 -158
  241. package/src/hu/splitting-generator.js +0 -175
  242. package/src/hu/store.js +0 -394
  243. package/src/infrastructure/mocks.js +0 -182
  244. package/src/mcp/handlers/run-handler.js +0 -318
  245. package/src/mcp/sovereignty-guard.js +0 -113
  246. package/src/mcp/tool-arg-normalizers.js +0 -16
  247. package/src/mutate/reviewer-signal.js +0 -76
  248. package/src/orchestrator/agent-fallback.js +0 -81
  249. package/src/orchestrator/brain-coordinator.js +0 -186
  250. package/src/orchestrator/ci-integration.js +0 -139
  251. package/src/orchestrator/config-init.js +0 -426
  252. package/src/orchestrator/direct-actions.js +0 -217
  253. package/src/orchestrator/drivers/error-recovery.js +0 -287
  254. package/src/orchestrator/drivers/init-context.js +0 -238
  255. package/src/orchestrator/drivers/iteration-loop.js +0 -382
  256. package/src/orchestrator/drivers/iteration-phases/coder-and-refactorer.js +0 -69
  257. package/src/orchestrator/drivers/iteration-phases/guards.js +0 -91
  258. package/src/orchestrator/drivers/iteration-phases/handle-approved.js +0 -29
  259. package/src/orchestrator/drivers/iteration-phases/quality-gates.js +0 -125
  260. package/src/orchestrator/drivers/iteration-phases/reviewer-gate.js +0 -62
  261. package/src/orchestrator/drivers/post-loop.js +0 -404
  262. package/src/orchestrator/drivers/pre-loop-phases/auto-hu-batch.js +0 -137
  263. package/src/orchestrator/drivers/pre-loop-phases/config-deprecations.js +0 -47
  264. package/src/orchestrator/drivers/pre-loop-phases/ensure-addyosmani-skills.js +0 -84
  265. package/src/orchestrator/drivers/pre-loop-phases/inject-loaded-plan.js +0 -393
  266. package/src/orchestrator/drivers/pre-loop.js +0 -524
  267. package/src/orchestrator/drivers/run-hu-batch.js +0 -468
  268. package/src/orchestrator/feedback-enrichment.js +0 -140
  269. package/src/orchestrator/feedback-queue.js +0 -151
  270. package/src/orchestrator/flow-control.js +0 -170
  271. package/src/orchestrator/flow-runner.js +0 -365
  272. package/src/orchestrator/fs-leak-detector.js +0 -228
  273. package/src/orchestrator/hu-scheduler.js +0 -114
  274. package/src/orchestrator/hu-sub-pipeline.js +0 -642
  275. package/src/orchestrator/iteration-gate.js +0 -73
  276. package/src/orchestrator/iteration-stages.js +0 -9
  277. package/src/orchestrator/parallel-limiter.js +0 -74
  278. package/src/orchestrator/pipeline-context.js +0 -72
  279. package/src/orchestrator/post-loop-stages.js +0 -468
  280. package/src/orchestrator/pre-loop-stages.js +0 -11
  281. package/src/orchestrator/preflight-checks.js +0 -487
  282. package/src/orchestrator/repair/repair-detector.js +0 -117
  283. package/src/orchestrator/repair/repair-runner.js +0 -142
  284. package/src/orchestrator/reviewer-fallback.js +0 -45
  285. package/src/orchestrator/role-output-compressor.js +0 -189
  286. package/src/orchestrator/session-journal.js +0 -308
  287. package/src/orchestrator/solomon-escalation.js +0 -331
  288. package/src/orchestrator/solomon-rules.js +0 -212
  289. package/src/orchestrator/stages/acceptance-coverage-stage.js +0 -47
  290. package/src/orchestrator/stages/architect-stage.js +0 -172
  291. package/src/orchestrator/stages/coder-stage.js +0 -506
  292. package/src/orchestrator/stages/domain-curator-stage.js +0 -82
  293. package/src/orchestrator/stages/format-stage.js +0 -61
  294. package/src/orchestrator/stages/hu-reviewer-phases/detect-and-apply-splits.js +0 -127
  295. package/src/orchestrator/stages/hu-reviewer-stage.js +0 -419
  296. package/src/orchestrator/stages/perf-stage.js +0 -140
  297. package/src/orchestrator/stages/planner-stage.js +0 -85
  298. package/src/orchestrator/stages/rag-context-stage.js +0 -80
  299. package/src/orchestrator/stages/rag-preload-decisor.js +0 -58
  300. package/src/orchestrator/stages/research-stage.js +0 -69
  301. package/src/orchestrator/stages/reviewer-stage.js +0 -431
  302. package/src/orchestrator/stages/sonar-stage.js +0 -439
  303. package/src/orchestrator/stages/stage-classes.js +0 -84
  304. package/src/orchestrator/stages/stage-executor.js +0 -155
  305. package/src/orchestrator/stages/suite-green-stage.js +0 -62
  306. package/src/orchestrator/stages/tdd-discipline-stage.js +0 -56
  307. package/src/orchestrator/stages/tool-judge-stage.js +0 -53
  308. package/src/orchestrator/stages/triage-stage.js +0 -172
  309. package/src/orchestrator/standby.js +0 -71
  310. package/src/orchestrator/verification-gate.js +0 -183
  311. package/src/orchestrator.js +0 -22
  312. package/src/plan/adr-generator.js +0 -170
  313. package/src/plan/adr-loader.js +0 -60
  314. package/src/plan/plan-executor.js +0 -100
  315. package/src/plan/plan-validation.js +0 -4
  316. package/src/prompts/acceptance-coverage.js +0 -32
  317. package/src/prompts/arbiter.js +0 -38
  318. package/src/prompts/architect.js +0 -103
  319. package/src/prompts/hu-reviewer.js +0 -244
  320. package/src/prompts/tool-correctness-judge.js +0 -31
  321. package/src/prompts/triage.js +0 -57
  322. package/src/rag/embedder.js +0 -2
  323. package/src/rag/embedders/_cloud-base.js +0 -2
  324. package/src/rag/embedders/cohere.js +0 -2
  325. package/src/rag/embedders/mistral.js +0 -2
  326. package/src/rag/embedders/onnx.js +0 -2
  327. package/src/rag/embedders/openai.js +0 -2
  328. package/src/rag/embedders/voyage.js +0 -2
  329. package/src/rag/rerank.js +0 -4
  330. package/src/rag/where-parser.js +0 -4
  331. package/src/repeat-detector.js +0 -88
  332. package/src/review/diff-generator.js +0 -112
  333. package/src/review/git-stash-helper.js +0 -104
  334. package/src/review/schema.js +0 -31
  335. package/src/review/scope-filter.js +0 -152
  336. package/src/review/snapshot-diff.js +0 -122
  337. package/src/review/tdd-discipline.js +0 -90
  338. package/src/review/tdd-policy.js +0 -69
  339. package/src/roles/acceptance-coverage-role.js +0 -78
  340. package/src/roles/architect-role.js +0 -77
  341. package/src/roles/coder-role.js +0 -113
  342. package/src/roles/commiter-role.js +0 -113
  343. package/src/roles/domain-curator-role.js +0 -134
  344. package/src/roles/hu-reviewer-role.js +0 -64
  345. package/src/roles/impeccable-role.js +0 -85
  346. package/src/roles/index.js +0 -18
  347. package/src/roles/karajan-brain-role.js +0 -134
  348. package/src/roles/perf-role.js +0 -122
  349. package/src/roles/planner-role.js +0 -151
  350. package/src/roles/refactorer-role.js +0 -30
  351. package/src/roles/repairer-role.js +0 -181
  352. package/src/roles/researcher-role.js +0 -70
  353. package/src/roles/reviewer-role.js +0 -115
  354. package/src/roles/security-role.js +0 -73
  355. package/src/roles/solomon-role.js +0 -271
  356. package/src/roles/sonar-role.js +0 -72
  357. package/src/roles/tester-role.js +0 -235
  358. package/src/roles/tool-correctness-judge-role.js +0 -78
  359. package/src/roles/triage-role.js +0 -99
  360. package/src/session/journal/decisions-writer.js +0 -200
  361. package/src/session/journal/iteration-logger.js +0 -194
  362. package/src/session/journal/summary-writer.js +0 -320
  363. package/src/session/journal/tree-writer.js +0 -164
  364. package/src/session/journal/write-all.js +0 -89
  365. package/src/session-cleanup.js +0 -116
  366. package/src/skills/semantic-detector.js +0 -167
  367. package/src/skills/skill-detector.js +0 -385
  368. package/src/sonar/cloud-scanner.js +0 -78
  369. package/src/tracker-bootstrap.js +0 -44
  370. package/src/types/index.js +0 -38
  371. package/src/types/policy.js +0 -43
  372. package/src/types/solomon.js +0 -87
  373. package/src/utils/async-lock.js +0 -31
  374. package/src/utils/budget.js +0 -229
  375. package/src/utils/cwd-confirm.js +0 -39
  376. package/src/utils/display/event-handlers.js +0 -403
  377. package/src/utils/display/formatters.js +0 -132
  378. package/src/utils/display/header.js +0 -47
  379. package/src/utils/display/session-summary.js +0 -118
  380. package/src/utils/display/solomon.js +0 -42
  381. package/src/utils/pricing.js +0 -29
  382. package/src/utils/quota-simulator.js +0 -56
  383. package/src/utils/role-assigner.js +0 -137
  384. package/src/utils/rtk-wrapper.js +0 -87
  385. package/src/utils/run-registry.js +0 -9
  386. package/src/utils/status-dashboard.js +0 -254
  387. package/src/utils/tool-call-extractor.js +0 -56
  388. package/src/webperf/devtools-detect.js +0 -112
  389. package/src/webperf/webperf-role-integration.js +0 -66
  390. package/templates/skills/kj-run.md +0 -73
@@ -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;