@karajan-family/code 4.21.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 (746) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +98 -0
  3. package/bin/karajan-mcp.js +2 -0
  4. package/bin/kj-rag-mcp.js +75 -0
  5. package/bin/kj-tail +361 -0
  6. package/bin/kj.js +2 -0
  7. package/docs/README.es.md +80 -0
  8. package/docs/karajan-code-logo-small.png +0 -0
  9. package/library/logical-clocks-lamport.md +40 -0
  10. package/package.json +140 -0
  11. package/packages/ai-trash/README.md +37 -0
  12. package/packages/ai-trash/bin/kj-trash.js +10 -0
  13. package/packages/ai-trash/package.json +43 -0
  14. package/packages/ai-trash/src/cli.js +200 -0
  15. package/packages/ai-trash/src/destructive-parser.js +74 -0
  16. package/packages/ai-trash/src/git-snapshot.js +128 -0
  17. package/packages/ai-trash/src/hook.js +137 -0
  18. package/packages/ai-trash/src/index.js +14 -0
  19. package/packages/ai-trash/src/install.js +73 -0
  20. package/packages/ai-trash/src/logger.js +60 -0
  21. package/packages/ai-trash/src/manifest.js +91 -0
  22. package/packages/ai-trash/src/permissions.js +26 -0
  23. package/packages/ai-trash/src/snapshot.js +66 -0
  24. package/packages/ai-trash/src/ulid.js +66 -0
  25. package/packages/hu-board/README.md +69 -0
  26. package/packages/hu-board/package.json +28 -0
  27. package/packages/hu-board/public/app.js +259 -0
  28. package/packages/hu-board/public/index.html +77 -0
  29. package/packages/hu-board/public/pipeline.css +132 -0
  30. package/packages/hu-board/public/pipeline.html +48 -0
  31. package/packages/hu-board/public/pipeline.js +200 -0
  32. package/packages/hu-board/public/rag.html +21 -0
  33. package/packages/hu-board/public/rag.js +39 -0
  34. package/packages/hu-board/public/styles.css +973 -0
  35. package/packages/hu-board/public/utils/api.js +139 -0
  36. package/packages/hu-board/public/utils/board-view.js +473 -0
  37. package/packages/hu-board/public/utils/command-launcher.js +213 -0
  38. package/packages/hu-board/public/utils/config-editor.js +181 -0
  39. package/packages/hu-board/public/utils/dashboard-view.js +103 -0
  40. package/packages/hu-board/public/utils/formatters.js +300 -0
  41. package/packages/hu-board/public/utils/graph-view.js +216 -0
  42. package/packages/hu-board/public/utils/hu-actions.js +224 -0
  43. package/packages/hu-board/public/utils/init-listeners.js +180 -0
  44. package/packages/hu-board/public/utils/log-panel.js +222 -0
  45. package/packages/hu-board/public/utils/modals.js +309 -0
  46. package/packages/hu-board/public/utils/plan-rollup-view.js +53 -0
  47. package/packages/hu-board/public/utils/preflight-view.js +89 -0
  48. package/packages/hu-board/public/utils/project-actions.js +143 -0
  49. package/packages/hu-board/public/utils/project-picker-view.js +139 -0
  50. package/packages/hu-board/public/utils/run-launcher.js +169 -0
  51. package/packages/hu-board/public/utils/server-push.js +252 -0
  52. package/packages/hu-board/public/utils/sessions-view.js +199 -0
  53. package/packages/hu-board/public/utils/story-detail-view.js +324 -0
  54. package/packages/hu-board/public/utils/story-edit-form.js +271 -0
  55. package/packages/hu-board/public/wiki.html +32 -0
  56. package/packages/hu-board/public/wiki.js +53 -0
  57. package/packages/hu-board/src/auth.js +92 -0
  58. package/packages/hu-board/src/boot-info.js +16 -0
  59. package/packages/hu-board/src/cleanup-zombies.js +145 -0
  60. package/packages/hu-board/src/command-runner.js +133 -0
  61. package/packages/hu-board/src/config-yaml.js +531 -0
  62. package/packages/hu-board/src/db.js +963 -0
  63. package/packages/hu-board/src/ephemeral-cleaner.js +196 -0
  64. package/packages/hu-board/src/event-bus.js +58 -0
  65. package/packages/hu-board/src/format.js +207 -0
  66. package/packages/hu-board/src/hu-zombie-reaper.js +103 -0
  67. package/packages/hu-board/src/journal-parser.js +180 -0
  68. package/packages/hu-board/src/plan-mutations.js +424 -0
  69. package/packages/hu-board/src/preflight.js +286 -0
  70. package/packages/hu-board/src/routes/api.js +1374 -0
  71. package/packages/hu-board/src/routes/pipeline.js +45 -0
  72. package/packages/hu-board/src/routes/rag.js +42 -0
  73. package/packages/hu-board/src/routes/wiki.js +43 -0
  74. package/packages/hu-board/src/run-tracker.js +121 -0
  75. package/packages/hu-board/src/server.js +438 -0
  76. package/packages/hu-board/src/sync.js +732 -0
  77. package/packages/hu-board/src/token-store.js +56 -0
  78. package/packages/hu-board/src/zombie-reaper.js +206 -0
  79. package/scripts/ai-attribution-guard.yml +84 -0
  80. package/scripts/audit-test-diet.mjs +151 -0
  81. package/scripts/build-sea.mjs +150 -0
  82. package/scripts/diet-baseline.mjs +170 -0
  83. package/scripts/dual-publish.mjs +67 -0
  84. package/scripts/esbuild-sea.config.mjs +273 -0
  85. package/scripts/git-hooks/post-merge +18 -0
  86. package/scripts/install-binary.ps1 +174 -0
  87. package/scripts/install-binary.sh +159 -0
  88. package/scripts/install-guard-all-repos.sh +85 -0
  89. package/scripts/install-kj.sh +193 -0
  90. package/scripts/install.js +901 -0
  91. package/scripts/install.sh +7 -0
  92. package/scripts/postinstall.js +117 -0
  93. package/scripts/protect-all-repos.sh +88 -0
  94. package/scripts/quickstart-gate.mjs +97 -0
  95. package/scripts/regen-arch-stats.sh +71 -0
  96. package/scripts/setup-multi-instance.sh +150 -0
  97. package/scripts/verify-pack.mjs +278 -0
  98. package/src/activity-log.js +59 -0
  99. package/src/agents/agy-agent.js +62 -0
  100. package/src/agents/aider-agent.js +78 -0
  101. package/src/agents/availability.js +52 -0
  102. package/src/agents/base-agent.js +134 -0
  103. package/src/agents/claude-agent.js +413 -0
  104. package/src/agents/codex-agent.js +112 -0
  105. package/src/agents/copilot-agent.js +65 -0
  106. package/src/agents/gemini-agent.js +85 -0
  107. package/src/agents/host-agent.js +38 -0
  108. package/src/agents/index.js +59 -0
  109. package/src/agents/kimi-agent.js +45 -0
  110. package/src/agents/model-registry.js +137 -0
  111. package/src/agents/opencode-agent.js +72 -0
  112. package/src/agents/qwen-agent.js +20 -0
  113. package/src/agents/resolve-bin.js +73 -0
  114. package/src/audit/agent-readiness.js +259 -0
  115. package/src/audit/ai-slop-findings.js +102 -0
  116. package/src/audit/audit-fallback.js +66 -0
  117. package/src/audit/audit-history-display.js +61 -0
  118. package/src/audit/audit-history.js +102 -0
  119. package/src/audit/basal-cost.js +281 -0
  120. package/src/audit/circular-deps.js +144 -0
  121. package/src/audit/dead-exports.js +186 -0
  122. package/src/audit/deterministic-summary.js +327 -0
  123. package/src/audit/harness-scorecard.js +100 -0
  124. package/src/audit/harness-section.js +81 -0
  125. package/src/audit/infra-findings.js +100 -0
  126. package/src/audit/injection-findings.js +114 -0
  127. package/src/audit/issue-filter.js +133 -0
  128. package/src/audit/osv-findings.js +167 -0
  129. package/src/audit/plan-adherence.js +224 -0
  130. package/src/audit/semgrep-findings.js +129 -0
  131. package/src/audit/sonar-findings.js +93 -0
  132. package/src/audit/webperf-input.js +133 -0
  133. package/src/autonomy/arbiter-role.js +71 -0
  134. package/src/autonomy/arbiter.js +22 -0
  135. package/src/autonomy/decider.js +18 -0
  136. package/src/autonomy/decision-resolver.js +58 -0
  137. package/src/autonomy/outcome-report.js +55 -0
  138. package/src/autonomy/policy.js +41 -0
  139. package/src/autonomy/stage-ask.js +30 -0
  140. package/src/bootstrap.js +276 -0
  141. package/src/brain/agent-error-classifier.js +116 -0
  142. package/src/brain/decision-tracker.js +125 -0
  143. package/src/brain/decisor.js +243 -0
  144. package/src/brain/solomon-consult.js +171 -0
  145. package/src/brain/standby-scheduler.js +8 -0
  146. package/src/brain/standby-store.js +12 -0
  147. package/src/brain/with-brain-recovery.js +235 -0
  148. package/src/budget/comparison.js +96 -0
  149. package/src/canvas/parse-md.js +238 -0
  150. package/src/canvas/schema.js +101 -0
  151. package/src/canvas/to-flat.js +85 -0
  152. package/src/canvas/validate.js +163 -0
  153. package/src/checks/ai-surface.js +79 -0
  154. package/src/checks/ai-trash.js +34 -0
  155. package/src/checks/binaries.js +207 -0
  156. package/src/checks/ci.js +105 -0
  157. package/src/checks/config-files.js +217 -0
  158. package/src/checks/dir-setup.js +103 -0
  159. package/src/checks/guarantee-level.js +38 -0
  160. package/src/checks/hardware.js +150 -0
  161. package/src/checks/harness-scorecard.js +41 -0
  162. package/src/checks/mcp-health.js +169 -0
  163. package/src/checks/method.js +73 -0
  164. package/src/checks/native-build.js +67 -0
  165. package/src/checks/node.js +70 -0
  166. package/src/checks/ollama.js +41 -0
  167. package/src/checks/orphans.js +77 -0
  168. package/src/checks/ports.js +188 -0
  169. package/src/checks/project-checks.js +245 -0
  170. package/src/checks/qmd.js +41 -0
  171. package/src/checks/rag-hooks.js +54 -0
  172. package/src/checks/release-check.js +102 -0
  173. package/src/checks/rtk.js +36 -0
  174. package/src/checks/runner.js +326 -0
  175. package/src/checks/skills.js +53 -0
  176. package/src/checks/sonar.js +67 -0
  177. package/src/checks/squeezr.js +36 -0
  178. package/src/checks/system.js +53 -0
  179. package/src/checks/tokens.js +172 -0
  180. package/src/checks/types.js +103 -0
  181. package/src/ci/dispatch.js +99 -0
  182. package/src/ci/index.js +3 -0
  183. package/src/ci/pr-diff.js +26 -0
  184. package/src/ci/repo.js +45 -0
  185. package/src/cli/_shared.js +30 -0
  186. package/src/cli/advanced-commands.js +61 -0
  187. package/src/cli/register-meta.js +772 -0
  188. package/src/cli/register-pipeline.js +335 -0
  189. package/src/cli/register-plan.js +224 -0
  190. package/src/cli/register-roles-skills.js +54 -0
  191. package/src/cli/register-sonar.js +28 -0
  192. package/src/cli/register-standby.js +26 -0
  193. package/src/cli.js +155 -0
  194. package/src/commands/advanced.js +31 -0
  195. package/src/commands/agent-run.js +63 -0
  196. package/src/commands/agents.js +105 -0
  197. package/src/commands/architect.js +123 -0
  198. package/src/commands/audit.js +430 -0
  199. package/src/commands/autorun.js +49 -0
  200. package/src/commands/board.js +458 -0
  201. package/src/commands/check.js +40 -0
  202. package/src/commands/clean.js +145 -0
  203. package/src/commands/code.js +45 -0
  204. package/src/commands/config.js +74 -0
  205. package/src/commands/discover.js +105 -0
  206. package/src/commands/doctor.js +251 -0
  207. package/src/commands/env.js +207 -0
  208. package/src/commands/harden.js +203 -0
  209. package/src/commands/hu.js +210 -0
  210. package/src/commands/identity.js +60 -0
  211. package/src/commands/init.js +950 -0
  212. package/src/commands/install-tools.js +432 -0
  213. package/src/commands/mutate.js +94 -0
  214. package/src/commands/ollama.js +44 -0
  215. package/src/commands/onboard.js +45 -0
  216. package/src/commands/plan/_shared.js +55 -0
  217. package/src/commands/plan/add-hu.js +23 -0
  218. package/src/commands/plan/delete.js +9 -0
  219. package/src/commands/plan/fix.js +99 -0
  220. package/src/commands/plan/generate.js +532 -0
  221. package/src/commands/plan/index.js +13 -0
  222. package/src/commands/plan/list.js +21 -0
  223. package/src/commands/plan/migrate-result.js +41 -0
  224. package/src/commands/plan/ready.js +46 -0
  225. package/src/commands/plan/remove-hu.js +17 -0
  226. package/src/commands/plan/share.js +85 -0
  227. package/src/commands/plan/show.js +81 -0
  228. package/src/commands/plan/unshare.js +41 -0
  229. package/src/commands/plan/validate.js +20 -0
  230. package/src/commands/plan.js +17 -0
  231. package/src/commands/policy.js +164 -0
  232. package/src/commands/privacy.js +45 -0
  233. package/src/commands/qmd.js +86 -0
  234. package/src/commands/rag-mcp.js +18 -0
  235. package/src/commands/rag.js +173 -0
  236. package/src/commands/report-issue.js +77 -0
  237. package/src/commands/report.js +469 -0
  238. package/src/commands/researcher.js +54 -0
  239. package/src/commands/resume.js +68 -0
  240. package/src/commands/review-gate.js +355 -0
  241. package/src/commands/review.js +89 -0
  242. package/src/commands/roles.js +116 -0
  243. package/src/commands/run.js +185 -0
  244. package/src/commands/scan.js +18 -0
  245. package/src/commands/skills.js +86 -0
  246. package/src/commands/sonar.js +53 -0
  247. package/src/commands/standby.js +117 -0
  248. package/src/commands/start.js +121 -0
  249. package/src/commands/status.js +36 -0
  250. package/src/commands/sync.js +103 -0
  251. package/src/commands/telemetry.js +70 -0
  252. package/src/commands/tournament.js +85 -0
  253. package/src/commands/triage.js +78 -0
  254. package/src/commands/undo.js +62 -0
  255. package/src/commands/watch.js +46 -0
  256. package/src/commands/webperf.js +162 -0
  257. package/src/commands/worktree.js +76 -0
  258. package/src/config/defaults.js +270 -0
  259. package/src/config/loader.js +204 -0
  260. package/src/config/overrides.js +220 -0
  261. package/src/config/role-resolver.js +171 -0
  262. package/src/config/schema.js +299 -0
  263. package/src/config/test-harness.js +78 -0
  264. package/src/config.js +34 -0
  265. package/src/domains/domain-loader.js +245 -0
  266. package/src/domains/domain-registry.js +140 -0
  267. package/src/domains/domain-synthesizer.js +129 -0
  268. package/src/environment/adr.js +45 -0
  269. package/src/environment/board-access.js +97 -0
  270. package/src/environment/board-select.js +59 -0
  271. package/src/environment/briefs.js +129 -0
  272. package/src/environment/issue-report.js +72 -0
  273. package/src/environment/mcp-wiring.js +44 -0
  274. package/src/environment/playbook.js +127 -0
  275. package/src/git/automation.js +334 -0
  276. package/src/git/hu-automation.js +171 -0
  277. package/src/git/hu-snapshot.js +10 -0
  278. package/src/golden/asserter.js +92 -0
  279. package/src/golden/loader.js +52 -0
  280. package/src/golden/runner.js +92 -0
  281. package/src/golden/schema.js +41 -0
  282. package/src/guards/intent-guard.js +161 -0
  283. package/src/guards/output-guard.js +149 -0
  284. package/src/guards/perf-guard.js +126 -0
  285. package/src/guards/policy-guard.js +90 -0
  286. package/src/guards/policy-resolver.js +85 -0
  287. package/src/guards/secret-redactor.js +71 -0
  288. package/src/harden/advisory.js +203 -0
  289. package/src/harden/alternatives.js +50 -0
  290. package/src/harden/check.js +81 -0
  291. package/src/harden/config-engine.js +128 -0
  292. package/src/harden/config-templates.js +122 -0
  293. package/src/harden/guidelines-engine.js +48 -0
  294. package/src/harden/guidelines-templates.js +36 -0
  295. package/src/harden/harden-engine.js +80 -0
  296. package/src/harden/harness-hooks.js +107 -0
  297. package/src/harden/hook-commands.js +25 -0
  298. package/src/harden/hook-templates.js +183 -0
  299. package/src/harden/interactive.js +76 -0
  300. package/src/harden/sentinel-hooks.js +721 -0
  301. package/src/harden/stack-roots.js +105 -0
  302. package/src/harden/test-on-push.js +38 -0
  303. package/src/harden/workflow-engine.js +116 -0
  304. package/src/harden/workflow-templates.js +285 -0
  305. package/src/hu/acceptance-runner.js +138 -0
  306. package/src/hu/auto-generator.js +358 -0
  307. package/src/hu/graph.js +71 -0
  308. package/src/hu/lazy-planner.js +109 -0
  309. package/src/hu/model-router.js +108 -0
  310. package/src/hu/parallel-executor.js +94 -0
  311. package/src/hu/split-validator.js +143 -0
  312. package/src/hu/splitting-detector.js +158 -0
  313. package/src/hu/splitting-generator.js +175 -0
  314. package/src/hu/store.js +392 -0
  315. package/src/hu/worktree-bootstrap.js +59 -0
  316. package/src/identity/bootstrap.js +48 -0
  317. package/src/identity/compare.js +38 -0
  318. package/src/identity/detect.js +51 -0
  319. package/src/identity/store.js +66 -0
  320. package/src/infrastructure/command-runner.js +44 -0
  321. package/src/infrastructure/environment.js +38 -0
  322. package/src/infrastructure/file-system-service.js +63 -0
  323. package/src/infrastructure/mocks.js +182 -0
  324. package/src/lang/chunk-go.js +94 -0
  325. package/src/lang/chunk-java.js +90 -0
  326. package/src/lang/chunk-python.js +88 -0
  327. package/src/lang/chunk-rust.js +102 -0
  328. package/src/lang/go.js +14 -0
  329. package/src/lang/java.js +16 -0
  330. package/src/lang/js.js +8 -0
  331. package/src/lang/python.js +14 -0
  332. package/src/lang/registry.js +79 -0
  333. package/src/lang/rust.js +14 -0
  334. package/src/lang/tree-sitter-loader.js +45 -0
  335. package/src/mcp/direct-role-runner.js +112 -0
  336. package/src/mcp/error-classifiers.js +66 -0
  337. package/src/mcp/handlers/direct-handlers.js +486 -0
  338. package/src/mcp/handlers/hu-handlers.js +93 -0
  339. package/src/mcp/handlers/management-handlers.js +265 -0
  340. package/src/mcp/handlers/rag-handler.js +52 -0
  341. package/src/mcp/handlers/run-handler.js +313 -0
  342. package/src/mcp/orphan-guard.js +131 -0
  343. package/src/mcp/preflight.js +47 -0
  344. package/src/mcp/progress.js +149 -0
  345. package/src/mcp/response-compressor.js +81 -0
  346. package/src/mcp/run-kj.js +135 -0
  347. package/src/mcp/server-handlers.js +79 -0
  348. package/src/mcp/server.js +66 -0
  349. package/src/mcp/shared-helpers.js +297 -0
  350. package/src/mcp/sovereignty-guard.js +112 -0
  351. package/src/mcp/suggest-handler.js +92 -0
  352. package/src/mcp/tool-arg-normalizers.js +16 -0
  353. package/src/mcp/tools.js +432 -0
  354. package/src/mutate/diff-scope.js +105 -0
  355. package/src/mutate/reviewer-signal.js +76 -0
  356. package/src/mutate/runner.js +72 -0
  357. package/src/mutate/tool-registry.js +90 -0
  358. package/src/onboarder/cache.js +24 -0
  359. package/src/onboarder/collectors/framework-multilang.js +64 -0
  360. package/src/onboarder/collectors/index.js +136 -0
  361. package/src/orchestrator/agent-fallback.js +81 -0
  362. package/src/orchestrator/brain-coordinator.js +186 -0
  363. package/src/orchestrator/ci-integration.js +139 -0
  364. package/src/orchestrator/config-init.js +510 -0
  365. package/src/orchestrator/direct-actions.js +217 -0
  366. package/src/orchestrator/drivers/error-recovery.js +287 -0
  367. package/src/orchestrator/drivers/init-context.js +227 -0
  368. package/src/orchestrator/drivers/iteration-loop.js +380 -0
  369. package/src/orchestrator/drivers/iteration-phases/coder-and-refactorer.js +69 -0
  370. package/src/orchestrator/drivers/iteration-phases/guards.js +84 -0
  371. package/src/orchestrator/drivers/iteration-phases/handle-approved.js +29 -0
  372. package/src/orchestrator/drivers/iteration-phases/quality-gates.js +114 -0
  373. package/src/orchestrator/drivers/iteration-phases/reviewer-gate.js +60 -0
  374. package/src/orchestrator/drivers/post-loop.js +383 -0
  375. package/src/orchestrator/drivers/pre-loop-phases/auto-hu-batch.js +136 -0
  376. package/src/orchestrator/drivers/pre-loop-phases/config-deprecations.js +47 -0
  377. package/src/orchestrator/drivers/pre-loop-phases/ensure-addyosmani-skills.js +84 -0
  378. package/src/orchestrator/drivers/pre-loop-phases/inject-loaded-plan.js +391 -0
  379. package/src/orchestrator/drivers/pre-loop.js +507 -0
  380. package/src/orchestrator/drivers/run-hu-batch.js +427 -0
  381. package/src/orchestrator/feedback-enrichment.js +140 -0
  382. package/src/orchestrator/feedback-queue.js +151 -0
  383. package/src/orchestrator/flow-control.js +170 -0
  384. package/src/orchestrator/flow-runner.js +364 -0
  385. package/src/orchestrator/fs-leak-detector.js +210 -0
  386. package/src/orchestrator/hu-scheduler.js +114 -0
  387. package/src/orchestrator/hu-sub-pipeline.js +616 -0
  388. package/src/orchestrator/integrations.js +81 -0
  389. package/src/orchestrator/iteration-gate.js +73 -0
  390. package/src/orchestrator/iteration-stages.js +9 -0
  391. package/src/orchestrator/parallel-limiter.js +74 -0
  392. package/src/orchestrator/pipeline-context.js +72 -0
  393. package/src/orchestrator/post-loop-stages.js +468 -0
  394. package/src/orchestrator/pre-loop-stages.js +11 -0
  395. package/src/orchestrator/preflight-checks.js +462 -0
  396. package/src/orchestrator/repair/repair-detector.js +117 -0
  397. package/src/orchestrator/repair/repair-runner.js +142 -0
  398. package/src/orchestrator/reviewer-fallback.js +45 -0
  399. package/src/orchestrator/role-output-compressor.js +189 -0
  400. package/src/orchestrator/session-journal.js +308 -0
  401. package/src/orchestrator/solomon-escalation.js +305 -0
  402. package/src/orchestrator/solomon-rules.js +212 -0
  403. package/src/orchestrator/stages/architect-stage.js +172 -0
  404. package/src/orchestrator/stages/coder-stage.js +480 -0
  405. package/src/orchestrator/stages/domain-curator-stage.js +82 -0
  406. package/src/orchestrator/stages/hu-reviewer-phases/detect-and-apply-splits.js +127 -0
  407. package/src/orchestrator/stages/hu-reviewer-stage.js +394 -0
  408. package/src/orchestrator/stages/perf-stage.js +140 -0
  409. package/src/orchestrator/stages/planner-stage.js +85 -0
  410. package/src/orchestrator/stages/rag-context-stage.js +77 -0
  411. package/src/orchestrator/stages/rag-preload-decisor.js +58 -0
  412. package/src/orchestrator/stages/research-stage.js +69 -0
  413. package/src/orchestrator/stages/reviewer-stage.js +422 -0
  414. package/src/orchestrator/stages/sonar-stage.js +421 -0
  415. package/src/orchestrator/stages/stage-classes.js +84 -0
  416. package/src/orchestrator/stages/stage-executor.js +155 -0
  417. package/src/orchestrator/stages/tdd-discipline-stage.js +56 -0
  418. package/src/orchestrator/stages/tool-judge-stage.js +53 -0
  419. package/src/orchestrator/stages/triage-stage.js +172 -0
  420. package/src/orchestrator/standby.js +71 -0
  421. package/src/orchestrator/verification-gate.js +183 -0
  422. package/src/orchestrator.js +22 -0
  423. package/src/plan/adr-generator.js +170 -0
  424. package/src/plan/adr-loader.js +60 -0
  425. package/src/plan/plan-executor.js +100 -0
  426. package/src/plan/plan-fixer.js +192 -0
  427. package/src/plan/plan-hu-ops.js +16 -0
  428. package/src/plan/plan-id.js +4 -0
  429. package/src/plan/plan-migrate-result.js +24 -0
  430. package/src/plan/plan-reviewer.js +236 -0
  431. package/src/plan/plan-schema.js +334 -0
  432. package/src/plan/plan-store.js +260 -0
  433. package/src/plan/plan-structural-pass.js +122 -0
  434. package/src/plan/plan-validation.js +4 -0
  435. package/src/plan/preflight-hu.js +105 -0
  436. package/src/plan/spec-detector.js +61 -0
  437. package/src/plan/tests-synthesizer.js +131 -0
  438. package/src/planning-game/adapter.js +108 -0
  439. package/src/planning-game/architect-adrs.js +45 -0
  440. package/src/planning-game/client.js +108 -0
  441. package/src/planning-game/decomposition.js +87 -0
  442. package/src/planning-game/pipeline-adapter.js +251 -0
  443. package/src/plugins/loader.js +67 -0
  444. package/src/policy/add.js +111 -0
  445. package/src/policy/decisions.js +42 -0
  446. package/src/policy/engine.js +82 -0
  447. package/src/policy/exceptions.js +60 -0
  448. package/src/privacy/onboarding.js +35 -0
  449. package/src/privacy/scan.js +120 -0
  450. package/src/prompts/arbiter.js +38 -0
  451. package/src/prompts/architect.js +103 -0
  452. package/src/prompts/audit.js +474 -0
  453. package/src/prompts/coder.js +287 -0
  454. package/src/prompts/diff-clip.js +27 -0
  455. package/src/prompts/discover.js +222 -0
  456. package/src/prompts/hu-reviewer.js +219 -0
  457. package/src/prompts/planner.js +312 -0
  458. package/src/prompts/prompt-layout.js +80 -0
  459. package/src/prompts/prompt-resolver.js +120 -0
  460. package/src/prompts/reviewer.js +75 -0
  461. package/src/prompts/rtk-snippet.js +25 -0
  462. package/src/prompts/spec-reviewer.js +66 -0
  463. package/src/prompts/split-signal.js +82 -0
  464. package/src/prompts/start-decision.js +52 -0
  465. package/src/prompts/tool-correctness-judge.js +31 -0
  466. package/src/prompts/triage.js +57 -0
  467. package/src/rag/auto-update.js +72 -0
  468. package/src/rag/chunker-ast.js +56 -0
  469. package/src/rag/chunker.js +132 -0
  470. package/src/rag/embedder.js +2 -0
  471. package/src/rag/embedders/_cloud-base.js +2 -0
  472. package/src/rag/embedders/cohere.js +2 -0
  473. package/src/rag/embedders/factory.js +2 -0
  474. package/src/rag/embedders/mistral.js +2 -0
  475. package/src/rag/embedders/onnx.js +2 -0
  476. package/src/rag/embedders/openai.js +2 -0
  477. package/src/rag/embedders/voyage.js +2 -0
  478. package/src/rag/eval.js +61 -0
  479. package/src/rag/governed-embedder.js +39 -0
  480. package/src/rag/indexer.js +199 -0
  481. package/src/rag/library.js +62 -0
  482. package/src/rag/ollama-capability.js +59 -0
  483. package/src/rag/ollama-manager.js +98 -0
  484. package/src/rag/onnx-fallback.js +69 -0
  485. package/src/rag/rerank.js +4 -0
  486. package/src/rag/retriever.js +2 -0
  487. package/src/rag/vec-store.js +17 -0
  488. package/src/rag/watcher.js +62 -0
  489. package/src/rag/where-parser.js +4 -0
  490. package/src/repeat-detector.js +88 -0
  491. package/src/review/card-first.js +99 -0
  492. package/src/review/card-verify.js +83 -0
  493. package/src/review/diff-generator.js +112 -0
  494. package/src/review/gate-gitignore.js +77 -0
  495. package/src/review/git-stash-helper.js +104 -0
  496. package/src/review/mutation-pregate.js +48 -0
  497. package/src/review/one-shot-review.js +122 -0
  498. package/src/review/parser.js +80 -0
  499. package/src/review/policy-gate.js +26 -0
  500. package/src/review/profiles.js +66 -0
  501. package/src/review/reviewer-fallback.js +125 -0
  502. package/src/review/schema.js +31 -0
  503. package/src/review/scope-filter.js +152 -0
  504. package/src/review/snapshot-diff.js +122 -0
  505. package/src/review/solomon-arbitration.js +113 -0
  506. package/src/review/sonar-pregate.js +63 -0
  507. package/src/review/tdd-discipline.js +90 -0
  508. package/src/review/tdd-policy.js +69 -0
  509. package/src/review/tests-with-code.js +33 -0
  510. package/src/review/verdict-store.js +87 -0
  511. package/src/review/workspace.js +21 -0
  512. package/src/roles/agent-role.js +194 -0
  513. package/src/roles/architect-role.js +77 -0
  514. package/src/roles/audit-role.js +258 -0
  515. package/src/roles/base-role.js +165 -0
  516. package/src/roles/coder-role.js +107 -0
  517. package/src/roles/commiter-role.js +110 -0
  518. package/src/roles/discover-role.js +81 -0
  519. package/src/roles/domain-curator-role.js +141 -0
  520. package/src/roles/hu-reviewer-role.js +64 -0
  521. package/src/roles/impeccable-role.js +85 -0
  522. package/src/roles/index.js +18 -0
  523. package/src/roles/karajan-brain-role.js +134 -0
  524. package/src/roles/onboarder-role.js +61 -0
  525. package/src/roles/perf-role.js +122 -0
  526. package/src/roles/planner-role.js +151 -0
  527. package/src/roles/refactorer-role.js +30 -0
  528. package/src/roles/repairer-role.js +181 -0
  529. package/src/roles/researcher-role.js +70 -0
  530. package/src/roles/reviewer-role.js +104 -0
  531. package/src/roles/security-role.js +73 -0
  532. package/src/roles/solomon-role.js +265 -0
  533. package/src/roles/sonar-role.js +70 -0
  534. package/src/roles/spec-reviewer-role.js +106 -0
  535. package/src/roles/tester-role.js +235 -0
  536. package/src/roles/tool-correctness-judge-role.js +78 -0
  537. package/src/roles/triage-role.js +99 -0
  538. package/src/session/journal/decisions-writer.js +200 -0
  539. package/src/session/journal/iteration-logger.js +194 -0
  540. package/src/session/journal/summary-writer.js +320 -0
  541. package/src/session/journal/tree-writer.js +164 -0
  542. package/src/session/journal/write-all.js +89 -0
  543. package/src/session/mutators.js +339 -0
  544. package/src/session/runtime-overrides.js +59 -0
  545. package/src/session/store.js +106 -0
  546. package/src/session-cleanup.js +116 -0
  547. package/src/skills/addyosmani-catalog.js +315 -0
  548. package/src/skills/addyosmani-role-map.js +139 -0
  549. package/src/skills/openskills-client.js +157 -0
  550. package/src/skills/semantic-detector.js +167 -0
  551. package/src/skills/skill-detector.js +385 -0
  552. package/src/skills/skill-loader.js +219 -0
  553. package/src/skills/skills-cache.js +128 -0
  554. package/src/sonar/api.js +128 -0
  555. package/src/sonar/cloud-scanner.js +78 -0
  556. package/src/sonar/config-resolver.js +88 -0
  557. package/src/sonar/credentials.js +60 -0
  558. package/src/sonar/discovery.js +124 -0
  559. package/src/sonar/enforcer.js +19 -0
  560. package/src/sonar/issue-filter.js +12 -0
  561. package/src/sonar/manager.js +181 -0
  562. package/src/sonar/project-key.js +120 -0
  563. package/src/sonar/scanner.js +307 -0
  564. package/src/sonar/token-bootstrap.js +205 -0
  565. package/src/sonar/token-recovery.js +67 -0
  566. package/src/spec-review/refine-loop.js +58 -0
  567. package/src/spec-review/run-spec-review.js +119 -0
  568. package/src/start/assessment.js +74 -0
  569. package/src/start/maturity.js +73 -0
  570. package/src/start/start-decider-role.js +79 -0
  571. package/src/start/sweep.js +157 -0
  572. package/src/sync/apply-canvas-patch.js +55 -0
  573. package/src/sync/detect-drift.js +113 -0
  574. package/src/tournament/crown.js +88 -0
  575. package/src/tournament/judge.js +150 -0
  576. package/src/tournament/run.js +142 -0
  577. package/src/tournament/scoreboard.js +124 -0
  578. package/src/tracker-bootstrap.js +44 -0
  579. package/src/types/agent.js +55 -0
  580. package/src/types/config.js +114 -0
  581. package/src/types/hu.js +47 -0
  582. package/src/types/index.js +38 -0
  583. package/src/types/policy.js +43 -0
  584. package/src/types/session.js +75 -0
  585. package/src/types/solomon.js +87 -0
  586. package/src/types/stage.js +68 -0
  587. package/src/utils/agent-detect.js +93 -0
  588. package/src/utils/ai-trash.js +48 -0
  589. package/src/utils/async-lock.js +31 -0
  590. package/src/utils/atomic-write.js +6 -0
  591. package/src/utils/banner.js +66 -0
  592. package/src/utils/binary-sources.js +70 -0
  593. package/src/utils/board-prompt-bridge.js +108 -0
  594. package/src/utils/budget.js +229 -0
  595. package/src/utils/cli-ask-question.js +93 -0
  596. package/src/utils/cli-progress.js +220 -0
  597. package/src/utils/cli-run-log.js +83 -0
  598. package/src/utils/cwd-confirm.js +39 -0
  599. package/src/utils/derive-project-name-from-cwd.js +119 -0
  600. package/src/utils/display/event-handlers.js +403 -0
  601. package/src/utils/display/formatters.js +132 -0
  602. package/src/utils/display/header.js +47 -0
  603. package/src/utils/display/resume-hint.js +41 -0
  604. package/src/utils/display/session-summary.js +118 -0
  605. package/src/utils/display/solomon.js +42 -0
  606. package/src/utils/display/spec-findings.js +44 -0
  607. package/src/utils/doc-links.js +29 -0
  608. package/src/utils/docker-install.js +34 -0
  609. package/src/utils/escape-regexp.js +15 -0
  610. package/src/utils/events.js +35 -0
  611. package/src/utils/fs.js +19 -0
  612. package/src/utils/garbage-collector.js +481 -0
  613. package/src/utils/git.js +279 -0
  614. package/src/utils/home-migration.js +87 -0
  615. package/src/utils/injection-guard.js +186 -0
  616. package/src/utils/install-hints.js +180 -0
  617. package/src/utils/json-extract.js +64 -0
  618. package/src/utils/locale.js +59 -0
  619. package/src/utils/logger.js +96 -0
  620. package/src/utils/managed-markers.js +93 -0
  621. package/src/utils/manifest-sniff.js +82 -0
  622. package/src/utils/messages.js +61 -0
  623. package/src/utils/model-selector.js +107 -0
  624. package/src/utils/os-detect.js +79 -0
  625. package/src/utils/paths.js +13 -0
  626. package/src/utils/pending-user-action.js +95 -0
  627. package/src/utils/port-check.js +6 -0
  628. package/src/utils/port-occupant.js +96 -0
  629. package/src/utils/pricing.js +29 -0
  630. package/src/utils/process.js +4 -0
  631. package/src/utils/project-detect.js +160 -0
  632. package/src/utils/prompt-project-name.js +35 -0
  633. package/src/utils/provider-env.js +116 -0
  634. package/src/utils/qmd-collection.js +72 -0
  635. package/src/utils/qmd-detect.js +18 -0
  636. package/src/utils/qmd-install.js +37 -0
  637. package/src/utils/quota-simulator.js +56 -0
  638. package/src/utils/rate-limit-detector.js +181 -0
  639. package/src/utils/repo-cleaner.js +79 -0
  640. package/src/utils/retry.js +88 -0
  641. package/src/utils/role-assigner.js +137 -0
  642. package/src/utils/role-env.js +58 -0
  643. package/src/utils/rtk-detect.js +18 -0
  644. package/src/utils/rtk-install.js +38 -0
  645. package/src/utils/rtk-wrapper.js +87 -0
  646. package/src/utils/run-context.js +75 -0
  647. package/src/utils/run-log.js +217 -0
  648. package/src/utils/run-registry.js +9 -0
  649. package/src/utils/shared-paths.js +12 -0
  650. package/src/utils/squeezr-detect.js +21 -0
  651. package/src/utils/squeezr-install.js +37 -0
  652. package/src/utils/stack-detect.js +135 -0
  653. package/src/utils/stall-detector.js +159 -0
  654. package/src/utils/status-dashboard.js +254 -0
  655. package/src/utils/task-file.js +109 -0
  656. package/src/utils/telemetry.js +124 -0
  657. package/src/utils/templates-root.js +61 -0
  658. package/src/utils/tool-call-extractor.js +56 -0
  659. package/src/utils/tool-governor.js +116 -0
  660. package/src/utils/tool-installer.js +93 -0
  661. package/src/utils/update-check.js +280 -0
  662. package/src/utils/vector-store-orphans.js +89 -0
  663. package/src/utils/welcome.js +47 -0
  664. package/src/utils/with-timeout.js +42 -0
  665. package/src/utils/wizard.js +47 -0
  666. package/src/webperf/cwv-gate.js +94 -0
  667. package/src/webperf/devtools-detect.js +112 -0
  668. package/src/webperf/scanner.js +209 -0
  669. package/src/webperf/webperf-role-integration.js +66 -0
  670. package/templates/coder-rules.md +45 -0
  671. package/templates/docker-compose.sonar.yml +60 -0
  672. package/templates/domains/example-dental.md +57 -0
  673. package/templates/kj.config.yml +128 -0
  674. package/templates/review-rules.md +17 -0
  675. package/templates/roles/architect/anthropic.md +74 -0
  676. package/templates/roles/architect/default.md +72 -0
  677. package/templates/roles/architect/google.md +61 -0
  678. package/templates/roles/architect/openai.md +60 -0
  679. package/templates/roles/architect/opencode.md +59 -0
  680. package/templates/roles/architect.md +76 -0
  681. package/templates/roles/audit.md +68 -0
  682. package/templates/roles/coder/anthropic.md +81 -0
  683. package/templates/roles/coder/default.md +78 -0
  684. package/templates/roles/coder/google.md +87 -0
  685. package/templates/roles/coder/openai.md +68 -0
  686. package/templates/roles/coder/opencode.md +58 -0
  687. package/templates/roles/coder.md +152 -0
  688. package/templates/roles/commiter.md +44 -0
  689. package/templates/roles/discover.md +167 -0
  690. package/templates/roles/domain-curator.md +52 -0
  691. package/templates/roles/hu-reviewer.md +236 -0
  692. package/templates/roles/impeccable-design.md +87 -0
  693. package/templates/roles/impeccable.md +125 -0
  694. package/templates/roles/karajan-brain.md +95 -0
  695. package/templates/roles/onboarder.md +45 -0
  696. package/templates/roles/planner/anthropic.md +59 -0
  697. package/templates/roles/planner/default.md +57 -0
  698. package/templates/roles/planner/google.md +75 -0
  699. package/templates/roles/planner/openai.md +55 -0
  700. package/templates/roles/planner/opencode.md +62 -0
  701. package/templates/roles/planner.md +61 -0
  702. package/templates/roles/refactorer.md +48 -0
  703. package/templates/roles/repairer.md +103 -0
  704. package/templates/roles/researcher.md +41 -0
  705. package/templates/roles/reviewer/anthropic.md +61 -0
  706. package/templates/roles/reviewer/default.md +62 -0
  707. package/templates/roles/reviewer/google.md +63 -0
  708. package/templates/roles/reviewer/openai.md +57 -0
  709. package/templates/roles/reviewer/opencode.md +42 -0
  710. package/templates/roles/reviewer-paranoid.md +38 -0
  711. package/templates/roles/reviewer-relaxed.md +34 -0
  712. package/templates/roles/reviewer-strict.md +37 -0
  713. package/templates/roles/reviewer.md +62 -0
  714. package/templates/roles/security.md +54 -0
  715. package/templates/roles/solomon/anthropic.md +71 -0
  716. package/templates/roles/solomon/default.md +83 -0
  717. package/templates/roles/solomon/google.md +61 -0
  718. package/templates/roles/solomon/openai.md +53 -0
  719. package/templates/roles/solomon/opencode.md +55 -0
  720. package/templates/roles/solomon.md +83 -0
  721. package/templates/roles/sonar.md +49 -0
  722. package/templates/roles/spec-reviewer.md +47 -0
  723. package/templates/roles/tester.md +50 -0
  724. package/templates/roles/triage.md +69 -0
  725. package/templates/skills/kj-architect.md +45 -0
  726. package/templates/skills/kj-audit.md +63 -0
  727. package/templates/skills/kj-board.md +16 -0
  728. package/templates/skills/kj-code.md +51 -0
  729. package/templates/skills/kj-csv-transform.md +40 -0
  730. package/templates/skills/kj-data-report.md +44 -0
  731. package/templates/skills/kj-discover.md +24 -0
  732. package/templates/skills/kj-rag-query.md +50 -0
  733. package/templates/skills/kj-review.md +47 -0
  734. package/templates/skills/kj-run.md +73 -0
  735. package/templates/skills/kj-security.md +49 -0
  736. package/templates/skills/kj-sonar.md +41 -0
  737. package/templates/skills/kj-sql-analysis.md +40 -0
  738. package/templates/skills/kj-test.md +40 -0
  739. package/templates/workflows/automerge.yml +30 -0
  740. package/templates/workflows/houston-override.yml +46 -0
  741. package/templates/workflows/kj-ci-gateway.yml +58 -0
  742. package/vendor/tree-sitter-grammars/README.md +21 -0
  743. package/vendor/tree-sitter-grammars/go.wasm +0 -0
  744. package/vendor/tree-sitter-grammars/java.wasm +0 -0
  745. package/vendor/tree-sitter-grammars/python.wasm +0 -0
  746. package/vendor/tree-sitter-grammars/rust.wasm +0 -0
@@ -0,0 +1,243 @@
1
+ /**
2
+ * Brain decisor — intent-driven pipeline routing.
3
+ *
4
+ * Takes the triage output + task text + config and produces a Decision that
5
+ * the orchestrator applies as pipelineFlags overrides. The existing stage
6
+ * if/else in runFlow continues to drive execution; Brain just tells it which
7
+ * roles are relevant for this particular task.
8
+ *
9
+ * This is an MVP that keeps the current architecture. A future iteration
10
+ * (Pipeline Sovereignty / KJC-PCS-0022) can promote the Decision into a
11
+ * true DAG with parallel stage execution.
12
+ */
13
+
14
+ /**
15
+ * @typedef {Object} TriageOutput
16
+ * @property {"trivial"|"simple"|"medium"|"complex"} level
17
+ * @property {string[]} roles - roles triage suggests running
18
+ * @property {string} [taskType] - sw | infra | doc | add-tests | refactor | no-code | audit
19
+ * @property {string} [reasoning]
20
+ * @property {number} [confidence] - 0..1, often absent
21
+ * @property {string[]} [domainHints]
22
+ */
23
+
24
+ /**
25
+ * @typedef {Object} Decision
26
+ * @property {string[]} rolesOn - roles to enable in pipelineFlags
27
+ * @property {string[]} rolesOff - roles to explicitly disable
28
+ * @property {"low"|"medium"|"high"} confidence
29
+ * @property {string} rationale - human-readable reasoning
30
+ * @property {boolean} consultSolomon - true if the Brain is unsure
31
+ * @property {string} [solomonReason] - why Solomon is needed
32
+ * @property {string[]} appliedOverrides - names of manual overrides respected
33
+ */
34
+
35
+ const VALID_ROLES = new Set([
36
+ "planner", "researcher", "architect", "coder", "reviewer", "refactorer",
37
+ "tester", "security", "impeccable", "triage", "discover", "hu_reviewer", "solomon",
38
+ ]);
39
+
40
+ /**
41
+ * Translate triage level → base set of roles. Conservative baseline that the
42
+ * decisor then adjusts via taskType and domain hints.
43
+ */
44
+ function baselineForLevel(level) {
45
+ switch (level) {
46
+ case "trivial": return new Set(["coder"]);
47
+ case "simple": return new Set(["coder", "reviewer"]);
48
+ case "medium": return new Set(["coder", "reviewer", "tester"]);
49
+ case "complex": return new Set(["researcher", "architect", "planner", "coder", "reviewer", "tester", "security"]);
50
+ default: return new Set(["coder", "reviewer"]);
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Apply taskType adjustments on top of the baseline set (mutates in place).
56
+ */
57
+ function applyTaskTypeAdjustments(roles, taskType) {
58
+ switch (taskType) {
59
+ case "doc":
60
+ // Documentation tasks: skip testing and security, keep reviewer for tone/clarity.
61
+ roles.delete("tester");
62
+ roles.delete("security");
63
+ roles.delete("refactorer");
64
+ break;
65
+ case "add-tests":
66
+ // Adding tests: tester is primary, security less relevant.
67
+ roles.add("tester");
68
+ roles.delete("security");
69
+ break;
70
+ case "refactor":
71
+ // Refactor: no new behavior; tester verifies nothing broke; reviewer checks structure.
72
+ roles.add("refactorer");
73
+ roles.add("tester");
74
+ break;
75
+ case "infra":
76
+ // Infra: architect helps; security critical.
77
+ roles.add("architect");
78
+ roles.add("security");
79
+ break;
80
+ case "audit":
81
+ // Audit: no coder; reviewer + security.
82
+ roles.delete("coder");
83
+ roles.add("reviewer");
84
+ roles.add("security");
85
+ break;
86
+ case "no-code":
87
+ // No-code tasks (e.g. running SQL queries): drop coder+reviewer+tester.
88
+ roles.delete("coder");
89
+ roles.delete("reviewer");
90
+ roles.delete("tester");
91
+ break;
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Apply explicit overrides (CLI flags / config) on top of the decision.
97
+ * force = add; skip = remove. Tracks what was applied.
98
+ *
99
+ * @param {Set<string>} roles
100
+ * @param {{ forceRoles?: string[], skipRoles?: string[] }} overrides
101
+ * @returns {string[]} applied override names
102
+ */
103
+ function applyOverrides(roles, overrides) {
104
+ const applied = [];
105
+ for (const r of overrides.forceRoles || []) {
106
+ if (!VALID_ROLES.has(r)) continue;
107
+ roles.add(r);
108
+ applied.push(`force:${r}`);
109
+ }
110
+ for (const r of overrides.skipRoles || []) {
111
+ if (!VALID_ROLES.has(r)) continue;
112
+ roles.delete(r);
113
+ applied.push(`skip:${r}`);
114
+ }
115
+ return applied;
116
+ }
117
+
118
+ /**
119
+ * Build a routing Decision from triage output + task text + config.
120
+ *
121
+ * @param {Object} args
122
+ * @param {TriageOutput} args.triage
123
+ * @param {string} [args.task]
124
+ * @param {Object} [args.config] - resolved config (for rolesEnabled in pipeline)
125
+ * @param {{ forceRoles?: string[], skipRoles?: string[] }} [args.overrides]
126
+ * @returns {Decision}
127
+ */
128
+ export function buildDecision({ triage, task: _task, config = {}, overrides = {} } = {}) {
129
+ const level = triage?.level || "simple";
130
+ const taskType = triage?.taskType || "sw";
131
+
132
+ // 1. Baseline by complexity.
133
+ const roles = baselineForLevel(level);
134
+
135
+ // 2. Include triage's own suggested roles (triage already factors domain hints).
136
+ if (Array.isArray(triage?.roles)) {
137
+ for (const r of triage.roles) {
138
+ if (VALID_ROLES.has(r)) roles.add(r);
139
+ }
140
+ }
141
+
142
+ // 3. Task-type-specific adjustments.
143
+ applyTaskTypeAdjustments(roles, taskType);
144
+
145
+ // 4. Config pipeline.X.enabled=false forces a role off (respect user config).
146
+ const pipeline = config?.pipeline || {};
147
+ for (const r of Array.from(roles)) {
148
+ if (pipeline[r]?.enabled === false) {
149
+ roles.delete(r);
150
+ }
151
+ }
152
+
153
+ // 5. Manual CLI/flag overrides (highest priority).
154
+ const appliedOverrides = applyOverrides(roles, overrides);
155
+
156
+ // 6. Invariants: coder is always on unless explicitly audit/no-code AND not force-added.
157
+ if (taskType !== "audit" && taskType !== "no-code" && !overrides.skipRoles?.includes("coder")) {
158
+ roles.add("coder");
159
+ }
160
+
161
+ // 7. Confidence + Solomon consultation.
162
+ const explicitConfidence = typeof triage?.confidence === "number" ? triage.confidence : null;
163
+ let confidence = "medium";
164
+ let consultSolomon = false;
165
+ let solomonReason;
166
+ if (explicitConfidence !== null) {
167
+ if (explicitConfidence < 0.6) {
168
+ confidence = "low";
169
+ consultSolomon = true;
170
+ solomonReason = `triage confidence ${explicitConfidence.toFixed(2)} < 0.6`;
171
+ } else if (explicitConfidence >= 0.85) {
172
+ confidence = "high";
173
+ }
174
+ }
175
+ // Ambiguous level (absent or off-vocabulary) → low confidence regardless.
176
+ if (!triage?.level) {
177
+ confidence = "low";
178
+ consultSolomon = true;
179
+ solomonReason = "triage returned no level";
180
+ }
181
+
182
+ const rolesOn = Array.from(roles).sort((a, b) => a.localeCompare(b));
183
+ const rolesOff = Array.from(VALID_ROLES).filter((r) => !roles.has(r)).sort((a, b) => a.localeCompare(b));
184
+
185
+ const rationale = buildRationale({ level, taskType, rolesOn, appliedOverrides, triage });
186
+
187
+ return {
188
+ rolesOn,
189
+ rolesOff,
190
+ confidence,
191
+ rationale,
192
+ consultSolomon,
193
+ solomonReason,
194
+ appliedOverrides,
195
+ };
196
+ }
197
+
198
+ function buildRationale({ level, taskType, rolesOn, appliedOverrides, triage }) {
199
+ const parts = [
200
+ `level=${level}`,
201
+ `taskType=${taskType}`,
202
+ `roles=[${rolesOn.join(",")}]`,
203
+ ];
204
+ if (triage?.domainHints?.length) {
205
+ parts.push(`hints=[${triage.domainHints.slice(0, 3).join(",")}]`);
206
+ }
207
+ if (appliedOverrides.length) {
208
+ parts.push(`overrides=[${appliedOverrides.join(",")}]`);
209
+ }
210
+ return parts.join(" | ");
211
+ }
212
+
213
+ /**
214
+ * Apply a Decision onto an existing pipelineFlags object. Returns a NEW
215
+ * object — the input is not mutated. Setting roleEnabled flags via a
216
+ * consistent naming pattern: `${role}Enabled`.
217
+ *
218
+ * Unknown roles are silently ignored; the orchestrator only consumes a
219
+ * fixed set of *Enabled flags.
220
+ *
221
+ * @param {Decision} decision
222
+ * @param {Object} pipelineFlags
223
+ * @returns {Object} new flags object
224
+ */
225
+ export function applyDecisionToFlags(decision, pipelineFlags = {}) {
226
+ const next = { ...pipelineFlags };
227
+ const flagFor = (role) => {
228
+ if (role === "hu_reviewer") return "huReviewerEnabled";
229
+ return `${role}Enabled`;
230
+ };
231
+ for (const role of decision.rolesOn) {
232
+ next[flagFor(role)] = true;
233
+ }
234
+ for (const role of decision.rolesOff) {
235
+ next[flagFor(role)] = false;
236
+ }
237
+ // Coder/security/reviewer aren't always toggled via flags in the current
238
+ // orchestrator (coder is implicit). Don't touch fields the orchestrator
239
+ // doesn't know; next simply carries them.
240
+ return next;
241
+ }
242
+
243
+ export { VALID_ROLES };
@@ -0,0 +1,171 @@
1
+ /**
2
+ * Brain → Solomon consultation for routing decisions.
3
+ *
4
+ * When the Brain's Decision is low confidence, ambiguous, or the triage+config
5
+ * combination yields conflicting signals, the Brain consults Solomon. Solomon
6
+ * ruling is treated as *advisory*: if it returns a concrete role adjustment
7
+ * (e.g. "add security"), Brain applies it; otherwise Brain keeps the baseline.
8
+ *
9
+ * This is intentionally conservative. A future iteration can make Solomon a
10
+ * binding arbiter for routing (like it is for pipeline-control decisions).
11
+ *
12
+ * Architectural invariant: this module is the ONLY sanctioned path from
13
+ * Brain to Solomon for routing concerns. Stages / rules / agents must not
14
+ * bypass Brain to reach Solomon directly — see the note on `invokeSolomon`
15
+ * in `src/orchestrator/solomon-escalation.js` and docs/ARCHITECTURE.md.
16
+ *
17
+ * @typedef {import("../types/solomon.js").Conflict} Conflict
18
+ * @typedef {import("../types/solomon.js").SolomonResult} SolomonResult
19
+ */
20
+
21
+ /**
22
+ * Build a synthetic conflict payload suitable for invokeSolomon().
23
+ *
24
+ * @param {Object} args
25
+ * @param {import("./decisor.js").Decision} args.decision
26
+ * @param {Object} args.triage
27
+ * @param {string} args.task
28
+ * @returns {Conflict} conflict
29
+ */
30
+ export function buildRoutingConflict({ decision, triage, task }) {
31
+ return {
32
+ task,
33
+ stage: "brain-routing",
34
+ reason: decision.solomonReason || "low-confidence routing decision",
35
+ iterationCount: 0,
36
+ maxIterations: 1,
37
+ history: [],
38
+ summary: `Brain rationale: ${decision.rationale}. Roles on: ${decision.rolesOn.join(", ")}. Confidence: ${decision.confidence}.`,
39
+ // Extra data Solomon's prompt can introspect via conflict.detail
40
+ detail: {
41
+ triageLevel: triage?.level,
42
+ triageTaskType: triage?.taskType,
43
+ triageConfidence: triage?.confidence,
44
+ rolesOn: decision.rolesOn,
45
+ rolesOff: decision.rolesOff,
46
+ reason: decision.solomonReason,
47
+ },
48
+ };
49
+ }
50
+
51
+ /**
52
+ * Invoke Solomon with a routing question and translate its ruling back into
53
+ * adjustments to the Decision's rolesOn/rolesOff lists.
54
+ *
55
+ * If anything goes wrong (Solomon disabled, invocation throws, ruling
56
+ * malformed), the original decision is returned unchanged with a diagnostic
57
+ * appended to its rationale.
58
+ *
59
+ * @param {Object} args
60
+ * @param {import("./decisor.js").Decision} args.decision
61
+ * @param {Object} args.triage
62
+ * @param {string} args.task
63
+ * @param {Object} args.config
64
+ * @param {Object} args.logger
65
+ * @param {Object} [args.emitter]
66
+ * @param {Object} [args.eventBase]
67
+ * @param {Object} [args.session]
68
+ * @param {Function} [args.askQuestion]
69
+ * @returns {Promise<import("./decisor.js").Decision>}
70
+ */
71
+ export async function consultSolomonForRouting({
72
+ decision,
73
+ triage,
74
+ task,
75
+ config,
76
+ logger,
77
+ emitter = null,
78
+ eventBase = {},
79
+ session = {},
80
+ askQuestion = null,
81
+ }) {
82
+ if (!decision?.consultSolomon) return decision;
83
+ // Route through the existing infrastructure.
84
+ let invokeSolomon;
85
+ try {
86
+ ({ invokeSolomon } = await import("../orchestrator/solomon-escalation.js"));
87
+ } catch (err) {
88
+ logger?.warn?.(`brain/solomon-consult: escalation module unavailable (${err.message})`);
89
+ return decision;
90
+ }
91
+
92
+ const conflict = buildRoutingConflict({ decision, triage, task });
93
+
94
+ let ruling;
95
+ try {
96
+ ruling = await invokeSolomon({
97
+ config,
98
+ logger,
99
+ emitter,
100
+ eventBase,
101
+ stage: "brain-routing",
102
+ conflict,
103
+ askQuestion,
104
+ session,
105
+ iteration: 0,
106
+ });
107
+ } catch (err) {
108
+ logger?.warn?.(`brain/solomon-consult: invokeSolomon threw (${err.message}) — keeping baseline`);
109
+ return {
110
+ ...decision,
111
+ rationale: `${decision.rationale} | solomon=failed(${err.message})`,
112
+ };
113
+ }
114
+
115
+ if (!ruling || typeof ruling !== "object") {
116
+ return {
117
+ ...decision,
118
+ rationale: `${decision.rationale} | solomon=no-ruling`,
119
+ };
120
+ }
121
+
122
+ return applyRulingToDecision(decision, ruling, logger);
123
+ }
124
+
125
+ /**
126
+ * Apply a Solomon ruling to a decision. Known ruling fields Solomon may
127
+ * return that we understand in routing context:
128
+ * - action: "approve" / "continue" / "retry_different_approach" / "escalate"
129
+ * - suggestedActions: list of strings that may include "add X" / "remove X"
130
+ * - conditions: list of strings
131
+ *
132
+ * For routing we only look at suggestedActions (text-based) and parse simple
133
+ * "add X" / "skip X" / "remove X" hints against the valid role set. Everything
134
+ * else is logged for the rationale and ignored.
135
+ */
136
+ export function applyRulingToDecision(decision, ruling, logger) {
137
+ const rolesOn = new Set(decision.rolesOn);
138
+ const rolesOff = new Set(decision.rolesOff);
139
+ const applied = [];
140
+
141
+ const suggestions = Array.isArray(ruling.suggestedActions) ? ruling.suggestedActions : [];
142
+ for (const raw of suggestions) {
143
+ const action = String(raw || "").toLowerCase();
144
+ const addMatch = action.match(/\b(?:add|enable|include)\s+(\w+)/);
145
+ const skipMatch = action.match(/\b(?:skip|disable|remove|drop)\s+(\w+)/);
146
+ if (addMatch) {
147
+ const role = addMatch[1];
148
+ rolesOn.add(role);
149
+ rolesOff.delete(role);
150
+ applied.push(`solomon-add:${role}`);
151
+ } else if (skipMatch) {
152
+ const role = skipMatch[1];
153
+ rolesOff.add(role);
154
+ rolesOn.delete(role);
155
+ applied.push(`solomon-skip:${role}`);
156
+ }
157
+ }
158
+
159
+ const ruleVerdict = ruling.action || ruling.verdict || "unknown";
160
+ logger?.info?.(`brain/solomon-consult: Solomon verdict=${ruleVerdict} applied=[${applied.join(",")}]`);
161
+
162
+ return {
163
+ ...decision,
164
+ rolesOn: Array.from(rolesOn).sort((a, b) => a.localeCompare(b)),
165
+ rolesOff: Array.from(rolesOff).sort((a, b) => a.localeCompare(b)),
166
+ appliedOverrides: [...decision.appliedOverrides, ...applied],
167
+ confidence: applied.length > 0 ? "high" : decision.confidence,
168
+ consultSolomon: false,
169
+ rationale: `${decision.rationale} | solomon=${ruleVerdict}${applied.length ? ` applied=${applied.join(",")}` : ""}`,
170
+ };
171
+ }
@@ -0,0 +1,8 @@
1
+ // Shim de compatibilidad: standby-scheduler vive ahora en karajan-core.
2
+ // Mantiene la ruta para callers internos del CLI.
3
+ export {
4
+ _resetScheduledForTests,
5
+ scheduleResume,
6
+ cancelScheduled,
7
+ reconcileAll,
8
+ } from "karajan-core/standby-scheduler";
@@ -0,0 +1,12 @@
1
+ // Shim de compatibilidad: standby-store vive ahora en karajan-core.
2
+ // Mantiene la ruta para callers internos del CLI.
3
+ export {
4
+ standbyDir,
5
+ standbyDoneDir,
6
+ persistStandby,
7
+ buildStandbyState,
8
+ loadStandby,
9
+ listPendingStandby,
10
+ markStandbyDone,
11
+ acquireStandbyLock,
12
+ } from "karajan-core/standby-store";
@@ -0,0 +1,235 @@
1
+ // KJC-TSK-0412: wrapper central de recovery. Cualquier stage que invoque
2
+ // un agente IA pasa por aquí. Brain consume classifyAgentError (TSK-0411)
3
+ // y decide: standby corto, backoff exponencial, hibernar, o abortar.
4
+ //
5
+ // Hibernar al disco (acción persistente) es TSK-0414 — esta PR devuelve
6
+ // { ok: false, action: "hibernate", retryUntil } cuando aplica; el caller
7
+ // puede ignorarlo (fallback a long standby capped) hasta que llegue 0414.
8
+
9
+ import { classifyAgentError, ERROR_CLASS } from "./agent-error-classifier.js";
10
+ import { persistStandby, markStandbyDone } from "./standby-store.js";
11
+
12
+ const ONE_MIN = 60 * 1000;
13
+
14
+ const ONE_HOUR = 60 * 60 * 1000;
15
+ const DEFAULT_FALLBACK_WAIT_HOURS = 12;
16
+
17
+ export const DEFAULT_RECOVERY_POLICY = Object.freeze({
18
+ hibernateThresholdMs: 5 * ONE_MIN, // > 5 min de espera → hibernar
19
+ longStandbyCapMs: 10 * ONE_MIN, // si el caller no hiberna, espera máx 10 min
20
+ // KJC-TSK-0415: threshold de fallback. Si retryAfter > esto Y el role
21
+ // tiene fallback configurado → switch provider en vez de hibernar.
22
+ fallbackWaitHoursDefault: DEFAULT_FALLBACK_WAIT_HOURS,
23
+ // Standby-in-process: when the cooldown is short enough we keep the
24
+ // process alive and `await sleepFn(retryAfter)`, then retry the
25
+ // agent. The user sees nothing terminal — kj just pauses. A SIGINT /
26
+ // SIGTERM during the wait prints `kj standby resume <id>` and exits
27
+ // cleanly. For waits longer than this the process exits straight
28
+ // away (a multi-day weekly cap is not worth keeping kj running).
29
+ standbyWaitHoursMax: DEFAULT_FALLBACK_WAIT_HOURS,
30
+ classes: {
31
+ [ERROR_CLASS.RATE_LIMIT_SHORT]: { mode: "standby", maxRetries: 3 },
32
+ [ERROR_CLASS.QUOTA_EXHAUSTED_DAILY]: { mode: "hibernate", maxRetries: 1, fallbackEligible: true },
33
+ [ERROR_CLASS.QUOTA_EXHAUSTED_MONTHLY]: { mode: "hibernate", maxRetries: 1, fallbackEligible: true },
34
+ [ERROR_CLASS.API_DOWN]: { mode: "backoff", maxRetries: 3, baseMs: 5_000, factor: 3, jitterPct: 0.2 },
35
+ [ERROR_CLASS.NETWORK_TIMEOUT]: { mode: "backoff", maxRetries: 3, baseMs: 5_000, factor: 3, jitterPct: 0.2 },
36
+ [ERROR_CLASS.SILENCED]: { mode: "backoff", maxRetries: 2, baseMs: 30_000, factor: 2, jitterPct: 0.15 },
37
+ [ERROR_CLASS.AUTH_FAILED]: { mode: "abort", maxRetries: 0 },
38
+ [ERROR_CLASS.UNKNOWN_FATAL]: { mode: "abort", maxRetries: 0 },
39
+ },
40
+ });
41
+
42
+ function backoffDelay(policyForClass, attempt) {
43
+ const { baseMs = 5_000, factor = 2, jitterPct = 0.1 } = policyForClass;
44
+ const ms = baseMs * factor ** attempt;
45
+ const jitter = ms * jitterPct * (Math.random() * 2 - 1);
46
+ return Math.max(0, Math.round(ms + jitter));
47
+ }
48
+
49
+ function sleep(ms) {
50
+ // En tests (VITEST / NODE_ENV=test) saltamos sleeps reales — los tests que
51
+ // necesitan timing controlado inyectan sleepFn explícito. Esto evita que
52
+ // tests de stages downstream que llaman a withBrainRecovery se cuelguen
53
+ // esperando standby cuando el agente fake devuelve un error recoverable.
54
+ if (process.env.VITEST || process.env.NODE_ENV === "test") return Promise.resolve();
55
+ return new Promise((resolve) => setTimeout(resolve, ms));
56
+ }
57
+
58
+ function emit(emitter, type, eventBase, detail) {
59
+ if (!emitter || typeof emitter.emit !== "function") return;
60
+ try {
61
+ emitter.emit("progress", { ...(eventBase || {}), type, detail });
62
+ } catch { /* nunca bloquees un retry por un emitter mal */ }
63
+ }
64
+
65
+ /**
66
+ * @param {object} args
67
+ * @param {object} args.agent - Agent con runTask({...})
68
+ * @param {object} args.taskArgs - args pasados a agent.runTask
69
+ * @param {string} args.role - "planner" | "coder" | "reviewer" | ...
70
+ * @param {string} [args.provider] - claude|codex|gemini|opencode (default: agent.provider)
71
+ * @param {object} [args.policy] - override DEFAULT_RECOVERY_POLICY
72
+ * @param {object} [args.emitter] - bus de eventos
73
+ * @param {object} [args.eventBase]
74
+ * @param {object} [args.logger]
75
+ * @param {Function} [args.sleepFn] - inyectable para tests
76
+ * @returns {Promise<{ ok: boolean, output?: string, action?: string, recovery?: object, error?: string }>}
77
+ */
78
+ export async function withBrainRecovery({
79
+ agent, taskArgs, role, provider,
80
+ policy = DEFAULT_RECOVERY_POLICY,
81
+ emitter, eventBase, logger,
82
+ sleepFn = sleep,
83
+ // TSK-0414 PR2: si presente, en hibernate persiste { sessionId, planId?, huId?,
84
+ // argv?, ... } a ~/.kj/standby/<sessionId>.json y devuelve standbyFile en
85
+ // el resultado.
86
+ sessionState,
87
+ // KJC-TSK-0415: fallback chain. Si QUOTA_EXHAUSTED_* con retryAfter
88
+ // > maxWaitHours (default 12h) y hay fallback, switch en vez de hibernar.
89
+ fallback = null,
90
+ }) {
91
+ let effectiveAgent = agent;
92
+ let effectiveProvider = provider || agent?.provider || "unknown";
93
+ let effectiveFallback = fallback;
94
+ const attemptsByClass = {};
95
+
96
+ while (true) {
97
+ const result = await effectiveAgent.runTask(taskArgs);
98
+ if (result?.ok) return result;
99
+
100
+ const cls = classifyAgentError({
101
+ provider: effectiveProvider,
102
+ stdout: result?.output || "",
103
+ stderr: result?.error || "",
104
+ exitCode: result?.exitCode ?? null,
105
+ });
106
+ const classPolicy = policy.classes[cls.class] || { mode: "abort", maxRetries: 0 };
107
+ attemptsByClass[cls.class] = (attemptsByClass[cls.class] || 0) + 1;
108
+ const attempt = attemptsByClass[cls.class];
109
+
110
+ emit(emitter, "brain:agent-error", eventBase, { role, class: cls.class, attempt, message: cls.message, provider: effectiveProvider });
111
+ logger?.warn?.(`[brain] ${role} (${effectiveProvider}) → ${cls.class} attempt ${attempt}/${classPolicy.maxRetries}: ${cls.message}`);
112
+
113
+ // ABORT: no recuperable.
114
+ if (classPolicy.mode === "abort" || attempt > classPolicy.maxRetries) {
115
+ emit(emitter, "brain:fatal", eventBase, { role, class: cls.class, message: cls.message });
116
+ return { ok: false, action: "abort", recovery: cls, error: `Brain aborted: ${cls.class} — ${cls.message}` };
117
+ }
118
+
119
+ // KJC-TSK-0415: ¿switch a fallback antes de hibernar?
120
+ // Aplica si:
121
+ // - classPolicy.fallbackEligible (QUOTA_EXHAUSTED_*)
122
+ // - hay fallback configurado
123
+ // - retryAfter excede maxWaitHours del fallback (default 12h)
124
+ if (classPolicy.fallbackEligible && effectiveFallback?.agent && cls.retryAfter) {
125
+ const maxWaitMs = (effectiveFallback.maxWaitHours ?? policy.fallbackWaitHoursDefault ?? DEFAULT_FALLBACK_WAIT_HOURS) * ONE_HOUR;
126
+ if (cls.retryAfter > maxWaitMs) {
127
+ const from = effectiveProvider;
128
+ const to = effectiveFallback.provider || effectiveFallback.agent.provider || "unknown";
129
+ emit(emitter, "brain:fallback-switched", eventBase, { role, from, to, class: cls.class, retryAfter: cls.retryAfter, maxWaitMs });
130
+ logger?.info?.(`[brain] ${role}: fallback ${from} → ${to} (retryAfter ${Math.round(cls.retryAfter/3600000)}h > maxWait ${maxWaitMs/3600000}h)`);
131
+ // Switch al fallback. Reset attempts para que el nuevo provider
132
+ // pueda fallar/retry independientemente. Fallback puede tener su
133
+ // propio fallback en cadena.
134
+ effectiveAgent = effectiveFallback.agent;
135
+ effectiveProvider = to;
136
+ effectiveFallback = effectiveFallback.fallback || null;
137
+ for (const k of Object.keys(attemptsByClass)) attemptsByClass[k] = 0;
138
+ continue;
139
+ }
140
+ }
141
+
142
+ // HIBERNATE: the cooldown is too long for a short standby. Persist
143
+ // the snapshot first so a SIGINT / SIGTERM during the wait leaves a
144
+ // recoverable session on disk. Then either:
145
+ // (a) wait in-process until cooldownUntil and retry (the common
146
+ // case — daily quotas reset in a few hours), or
147
+ // (b) when the wait would exceed `standbyWaitHoursMax`, return
148
+ // action="hibernate" so the caller exits and the user
149
+ // resumes later with `kj standby resume <id>` (weekly /
150
+ // monthly caps).
151
+ if (classPolicy.mode === "hibernate" && cls.retryAfter && cls.retryAfter > policy.hibernateThresholdMs) {
152
+ let standbyFile = null;
153
+ if (sessionState && sessionState.sessionId) {
154
+ try {
155
+ standbyFile = persistStandby({
156
+ ...sessionState,
157
+ cooldownUntil: cls.retryUntil,
158
+ reason: cls.class,
159
+ role, provider: effectiveProvider,
160
+ classifierMessage: cls.message,
161
+ });
162
+ } catch (err) {
163
+ logger?.warn?.(`[brain] failed to persist standby: ${err.message}`);
164
+ }
165
+ }
166
+
167
+ const maxWaitMs = (policy.standbyWaitHoursMax ?? DEFAULT_FALLBACK_WAIT_HOURS) * ONE_HOUR;
168
+ const tooLongToWait = cls.retryAfter > maxWaitMs;
169
+
170
+ if (tooLongToWait) {
171
+ logger?.info?.(`[brain] hibernated session ${sessionState?.sessionId ?? "(no id)"} → ${standbyFile} (resume at ${cls.retryUntil}; wait > ${Math.round(maxWaitMs/ONE_HOUR)}h, exiting)`);
172
+ emit(emitter, "brain:hibernate-request", eventBase, { role, class: cls.class, retryUntil: cls.retryUntil, retryAfter: cls.retryAfter, standbyFile });
173
+ return { ok: false, action: "hibernate", recovery: cls, standbyFile };
174
+ }
175
+
176
+ // Standby-in-process: keep kj alive, sleep until the cooldown,
177
+ // then retry. A SIGINT / SIGTERM during the wait prints the
178
+ // resume command and exits — the standby JSON is already on
179
+ // disk so the user can pick it up later.
180
+ const sid = sessionState?.sessionId ?? null;
181
+ const onSignal = (signal) => {
182
+ // eslint-disable-next-line no-console
183
+ console.log(`\n⏸ Standby interrupted (${signal}). Resume with:`);
184
+ // eslint-disable-next-line no-console
185
+ if (sid) console.log(` kj standby resume ${sid}`);
186
+ // eslint-disable-next-line no-console
187
+ else console.log(` kj standby list (no session id was assigned)`);
188
+ process.exit(signal === "SIGINT" ? 130 : 143);
189
+ };
190
+ const detach = () => {
191
+ process.removeListener("SIGINT", onSignal);
192
+ process.removeListener("SIGTERM", onSignal);
193
+ };
194
+ process.once("SIGINT", onSignal);
195
+ process.once("SIGTERM", onSignal);
196
+
197
+ const hours = Math.round((cls.retryAfter / ONE_HOUR) * 10) / 10;
198
+ logger?.info?.(`[brain] ${role}: standby for ~${hours}h until ${cls.retryUntil} (Ctrl+C to detach; will resume with 'kj standby resume ${sid}').`);
199
+ emit(emitter, "brain:standby-wait", eventBase, { role, class: cls.class, waitMs: cls.retryAfter, retryUntil: cls.retryUntil, standbyFile });
200
+
201
+ try {
202
+ await sleepFn(cls.retryAfter);
203
+ } finally {
204
+ detach();
205
+ }
206
+
207
+ // Wait finished without an interrupt — mark the standby JSON
208
+ // done so it does not auto-resume from disk later.
209
+ if (sid) {
210
+ try { markStandbyDone(sid); } catch { /* ignore */ }
211
+ }
212
+ emit(emitter, "brain:standby-resumed", eventBase, { role, class: cls.class });
213
+ continue;
214
+ }
215
+
216
+ // STANDBY: espera hasta cooldownUntil (capado por longStandbyCapMs).
217
+ if (classPolicy.mode === "standby") {
218
+ const waitMs = Math.min(cls.retryAfter || ONE_MIN, policy.longStandbyCapMs);
219
+ emit(emitter, "brain:standby", eventBase, { role, class: cls.class, waitMs, retryUntil: cls.retryUntil });
220
+ await sleepFn(waitMs);
221
+ continue;
222
+ }
223
+
224
+ // BACKOFF exponencial con jitter.
225
+ if (classPolicy.mode === "backoff") {
226
+ const waitMs = backoffDelay(classPolicy, attempt - 1);
227
+ emit(emitter, "brain:backoff", eventBase, { role, class: cls.class, attempt, waitMs });
228
+ await sleepFn(waitMs);
229
+ continue;
230
+ }
231
+
232
+ // Modo desconocido — defensive abort.
233
+ return { ok: false, action: "abort", recovery: cls, error: `Brain: unknown mode '${classPolicy.mode}' for class ${cls.class}` };
234
+ }
235
+ }