karajan-code 4.70.3 → 5.0.0

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