@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,458 @@
1
+ import fs from "node:fs";
2
+ import http from "node:http";
3
+ import path from "node:path";
4
+ import net from "node:net";
5
+ import { spawn } from "node:child_process";
6
+ import { getKarajanHome } from "../utils/paths.js";
7
+ import { getPortOccupant } from "../utils/port-occupant.js";
8
+
9
+ const BOARD_DIR = path.resolve(import.meta.dirname, "../../packages/hu-board");
10
+ const PID_FILE = path.join(getKarajanHome(), "hu-board.pid");
11
+
12
+ function readPid() {
13
+ try {
14
+ const pid = Number.parseInt(fs.readFileSync(PID_FILE, "utf8").trim(), 10);
15
+ return Number.isFinite(pid) ? pid : null;
16
+ } catch { /* PID file does not exist */
17
+ return null;
18
+ }
19
+ }
20
+
21
+ function isProcessAlive(pid) {
22
+ try {
23
+ process.kill(pid, 0);
24
+ return true;
25
+ } catch { /* process does not exist */
26
+ return false;
27
+ }
28
+ }
29
+
30
+ /**
31
+ * HTTP-probe the board's /api/dashboard. Returns true when it
32
+ * responds with 2xx within ~750ms. Used as a fallback when the PID
33
+ * file is missing/stale to detect "board is up, just untracked".
34
+ */
35
+ async function isBoardReachable(port, timeoutMs = 750) {
36
+ return new Promise((resolve) => {
37
+ const req = http.request(
38
+ { host: '127.0.0.1', port, path: '/api/dashboard', method: 'GET', timeout: timeoutMs },
39
+ (res) => {
40
+ const ok = res.statusCode >= 200 && res.statusCode < 500;
41
+ res.resume(); // drain
42
+ resolve(ok);
43
+ }
44
+ );
45
+ req.on('error', () => resolve(false));
46
+ req.on('timeout', () => { req.destroy(); resolve(false); });
47
+ req.end();
48
+ });
49
+ }
50
+
51
+ /**
52
+ * Check if a TCP port is available on localhost.
53
+ * Returns true when we can successfully bind (port is free).
54
+ */
55
+ function isPortAvailable(port) {
56
+ return new Promise((resolve) => {
57
+ const server = net.createServer();
58
+ server.once("error", () => resolve(false));
59
+ server.once("listening", () => {
60
+ server.close(() => resolve(true));
61
+ });
62
+ server.listen(port, "127.0.0.1");
63
+ });
64
+ }
65
+
66
+ /**
67
+ * Starting from `desiredPort`, return the first free port within `maxTries`.
68
+ * Returns null if none available.
69
+ */
70
+ async function findAvailablePort(desiredPort, maxTries = 10) {
71
+ for (let i = 0; i < maxTries; i++) {
72
+ const port = desiredPort + i;
73
+ if (await isPortAvailable(port)) return port;
74
+ }
75
+ return null;
76
+ }
77
+
78
+ /**
79
+ * Build the board URL. When `projectSlug` is provided, the URL points at
80
+ * the per-project view via the SPA hash route (`#board/<slug>`) so the
81
+ * caller's project shows up pre-filtered instead of "All projects". The
82
+ * frontend router is hash-based (see public/utils/init-listeners.js); a
83
+ * real `/p/<slug>` path is not a route and silently falls back to the
84
+ * global dashboard. KJC-BUG-0093.
85
+ * @param {number} port
86
+ * @param {string|null} [projectSlug]
87
+ * @returns {string}
88
+ */
89
+ export function buildBoardUrl(port, projectSlug) {
90
+ const base = `http://localhost:${port}`;
91
+ return projectSlug ? `${base}/#board/${projectSlug}` : base;
92
+ }
93
+
94
+ /**
95
+ * After spawning the board daemon, watch for an early exit. Resolves to
96
+ * an error string when the child dies within the window — a broken
97
+ * native module, a crash on boot, or the silent entry-point mismatch
98
+ * that made the daemon exit 0 without a trace — and to `null` when it
99
+ * survives. This is what stops `startBoard` from reporting `ok` for a
100
+ * daemon that died milliseconds after spawn. The window is overridable
101
+ * via KJ_BOARD_START_WINDOW_MS (tests set it low to stay fast).
102
+ * @param {import('node:child_process').ChildProcess} child
103
+ * @param {string} logPath
104
+ * @returns {Promise<string|null>}
105
+ */
106
+ export function waitForEarlyExit(child, logPath) {
107
+ const envWindow = Number(process.env.KJ_BOARD_START_WINDOW_MS);
108
+ const windowMs = Number.isFinite(envWindow) && envWindow >= 0 ? envWindow : 1500;
109
+ return new Promise((resolve) => {
110
+ let settled = false;
111
+ let timer = null;
112
+ const onExit = (code, signal) => {
113
+ const how = signal ? `signal ${signal}` : `exit code ${code}`;
114
+ finish(`HU Board daemon died on startup (${how}). See ${logPath} for the cause.`);
115
+ };
116
+ function finish(value) {
117
+ if (settled) return;
118
+ settled = true;
119
+ if (timer) clearTimeout(timer);
120
+ child.removeListener("exit", onExit);
121
+ resolve(value);
122
+ }
123
+ child.once("exit", onExit);
124
+ timer = setTimeout(() => finish(null), windowMs);
125
+ });
126
+ }
127
+
128
+ // KJC-BUG-0080: waitForEarlyExit only catches an exit, not a hung
129
+ // daemon that survived the window without ever bind()ing the port.
130
+ // Poll /api/dashboard until it answers or the readiness budget runs
131
+ // out, so we never announce "HU Board started" for an unreachable
132
+ // server. Overridable with KJ_BOARD_READY_TIMEOUT_MS (tests set it
133
+ // low to stay fast).
134
+ async function waitForBoardReachable(port, totalTimeoutMs) {
135
+ const envTimeout = Number(process.env.KJ_BOARD_READY_TIMEOUT_MS);
136
+ const budget = Number.isFinite(envTimeout) && envTimeout >= 0
137
+ ? envTimeout
138
+ : (Number.isFinite(totalTimeoutMs) ? totalTimeoutMs : 5000);
139
+ const intervalMs = budget <= 200 ? Math.max(10, Math.floor(budget / 5)) : 200;
140
+ const deadline = Date.now() + budget;
141
+ while (Date.now() < deadline) {
142
+ const remaining = deadline - Date.now();
143
+ const probeTimeout = Math.min(750, Math.max(50, remaining));
144
+ if (await isBoardReachable(port, probeTimeout)) return true;
145
+ const wait = Math.min(intervalMs, deadline - Date.now());
146
+ if (wait > 0) await new Promise((r) => setTimeout(r, wait));
147
+ }
148
+ return false;
149
+ }
150
+
151
+ export async function startBoard(desiredPort = 4000, opts = {}) {
152
+ const { projectSlug = null, bind = "127.0.0.1" } = opts;
153
+ const existingPid = readPid();
154
+ if (existingPid && isProcessAlive(existingPid)) {
155
+ // Trust the saved PID — port info comes from the PID file if present
156
+ return {
157
+ ok: true,
158
+ alreadyRunning: true,
159
+ pid: existingPid,
160
+ url: buildBoardUrl(desiredPort, projectSlug),
161
+ };
162
+ }
163
+
164
+ // Belt-and-suspenders (v2.7.5 fix): even if the PID file is missing
165
+ // or stale, HTTP-probe the configured port before spawning. If
166
+ // /api/dashboard responds, a board is already there — reuse it. The
167
+ // pre-fix scenario: 🔁 self-restart didn't update the PID file
168
+ // (now fixed), so a `kj plan` afterwards saw a stale PID, spawned
169
+ // a duplicate board on port 4001, and confused the user.
170
+ if (await isBoardReachable(desiredPort)) {
171
+ return {
172
+ ok: true,
173
+ alreadyRunning: true,
174
+ pid: existingPid || null,
175
+ url: buildBoardUrl(desiredPort, projectSlug),
176
+ };
177
+ }
178
+
179
+ // Find a free port starting from desiredPort
180
+ const port = await findAvailablePort(desiredPort, 10);
181
+ if (port === null) {
182
+ throw new Error(`No free port in range ${desiredPort}-${desiredPort + 9}`);
183
+ }
184
+
185
+ const serverPath = path.join(BOARD_DIR, "src/server.js");
186
+ const karajanHome = getKarajanHome();
187
+ fs.mkdirSync(karajanHome, { recursive: true });
188
+
189
+ // Open a log file for the daemon. Pre-fix the daemon was spawned
190
+ // with `stdio: "ignore"`, so every `console.log` from server.js
191
+ // (including the new [zombie-reaper] messages) went straight to
192
+ // /dev/null and the user had no way to see what was happening.
193
+ // The file is opened in append-only mode so successive starts
194
+ // don't truncate the history.
195
+ const logPath = path.join(karajanHome, "hu-board.log");
196
+ const logFd = fs.openSync(logPath, "a");
197
+ fs.writeSync(
198
+ logFd,
199
+ `\n--- kj board start at ${new Date().toISOString()} (port ${port}, bind ${bind}) ---\n`,
200
+ );
201
+
202
+ // Use process.execPath (absolute path to current node binary) instead of "node".
203
+ // Fixes ENOENT on nvm setups where `node` is not in the spawned process's PATH.
204
+ // KJ_BOARD_DAEMON=1 tells server.js to run main() regardless of how
205
+ // its entry-point path compares — the symlink/Windows/space-in-path
206
+ // mismatch that used to make the daemon exit 0 in total silence.
207
+ const child = spawn(process.execPath, [serverPath], {
208
+ env: { ...process.env, PORT: String(port), BIND_HOST: bind, KJ_BOARD_DAEMON: "1" },
209
+ detached: true,
210
+ stdio: ["ignore", logFd, logFd],
211
+ cwd: BOARD_DIR
212
+ });
213
+ // The parent doesn't need the fd anymore — the child inherited it.
214
+ fs.closeSync(logFd);
215
+ // Swallow spawn errors so an HU Board failure never crashes the pipeline.
216
+ child.on("error", () => { /* non-blocking — caller logs via tryAutoStartBoard catch */ });
217
+ if (!child.pid) {
218
+ throw new Error("Failed to spawn HU Board server");
219
+ }
220
+ // Don't report success blindly: a crash on boot (e.g. broken
221
+ // better-sqlite3, or the silent entry-point mismatch) used to be
222
+ // reported as ok:true, leaving the user with a dead board and a log
223
+ // showing only the start line. Catch the early exit here.
224
+ const failure = await waitForEarlyExit(child, logPath);
225
+ if (failure) {
226
+ throw new Error(failure);
227
+ }
228
+ // KJC-BUG-0080: even after a clean window, the server may have
229
+ // failed to bind (e.g. port race vs the parent's findAvailablePort)
230
+ // or be still booting. Probe /api/dashboard before announcing
231
+ // success — without this the CLI used to print "HU Board started
232
+ // at http://localhost:4000" while :4000 actually returned nothing.
233
+ const reachable = await waitForBoardReachable(port);
234
+ if (!reachable) {
235
+ throw new Error(
236
+ `HU Board daemon spawned (PID ${child.pid}) but did not answer on http://localhost:${port} in time. See ${logPath} for the cause.`
237
+ );
238
+ }
239
+ fs.writeFileSync(PID_FILE, String(child.pid));
240
+ // Detach for real: `detached: true` only sets up the new session;
241
+ // without `unref()` the parent's event loop still holds a reference
242
+ // to the child handle and `kj board start` hangs at the prompt
243
+ // instead of returning.
244
+ child.unref();
245
+ return {
246
+ ok: true,
247
+ alreadyRunning: false,
248
+ pid: child.pid,
249
+ url: buildBoardUrl(port, projectSlug),
250
+ port,
251
+ logPath,
252
+ };
253
+ }
254
+
255
+ export async function stopBoard(opts = {}) {
256
+ const { port = 4000 } = opts;
257
+ // Path 1: live PID in the file → straight SIGTERM. Pre-v2.7.5
258
+ // this was the only path; if the file was missing or stale (e.g.
259
+ // user ran a board built before the server-writes-own-PID fix in
260
+ // PR #508) we'd return "wasn't running" while the ghost server
261
+ // happily kept serving requests. The fallbacks below close that gap.
262
+ const pid = readPid();
263
+ if (pid && isProcessAlive(pid)) {
264
+ try { process.kill(pid, "SIGTERM"); } catch { /* race with dying process */ }
265
+ fs.rmSync(PID_FILE, { force: true });
266
+ return { ok: true, wasRunning: true, pid, via: "pidfile" };
267
+ }
268
+ fs.rmSync(PID_FILE, { force: true });
269
+
270
+ // Path 2: HTTP-ask the server to exit cleanly. Works for any
271
+ // running server that has the /api/board/shutdown endpoint —
272
+ // even when there's no PID file, no process tree to walk, etc.
273
+ const shutdownUrl = `/api/board/shutdown`;
274
+ const shutdownOk = await postBoardShutdown(port, shutdownUrl);
275
+ if (shutdownOk) {
276
+ return { ok: true, wasRunning: true, pid: null, via: "api" };
277
+ }
278
+
279
+ // Path 3: there's something on the port and the API didn't take it
280
+ // down. Find its PID via lsof / ss / netstat and SIGTERM it.
281
+ // Works against truly old servers that don't have the shutdown
282
+ // endpoint, with the cost of one extra subprocess call.
283
+ try {
284
+ const occupant = await getPortOccupant(port);
285
+ if (occupant?.pid) {
286
+ try { process.kill(occupant.pid, "SIGTERM"); } catch { /* gone */ }
287
+ return { ok: true, wasRunning: true, pid: occupant.pid, via: "port-occupant" };
288
+ }
289
+ } catch { /* lsof not installed etc — fall through */ }
290
+
291
+ return { ok: true, wasRunning: false };
292
+ }
293
+
294
+ /**
295
+ * Best-effort POST to the board's shutdown endpoint. Returns true
296
+ * when the server acks (and is presumably exiting); false on any
297
+ * error so the caller falls through to the next strategy.
298
+ */
299
+ function postBoardShutdown(port, urlPath, timeoutMs = 750) {
300
+ return new Promise((resolve) => {
301
+ const req = http.request(
302
+ { host: '127.0.0.1', port, path: urlPath, method: 'POST', timeout: timeoutMs,
303
+ headers: { 'Content-Type': 'application/json', 'Content-Length': '2' } },
304
+ (res) => {
305
+ const ok = res.statusCode >= 200 && res.statusCode < 400;
306
+ res.resume();
307
+ resolve(ok);
308
+ }
309
+ );
310
+ req.on('error', () => resolve(false));
311
+ req.on('timeout', () => { req.destroy(); resolve(false); });
312
+ req.write('{}');
313
+ req.end();
314
+ });
315
+ }
316
+
317
+ export async function boardStatus(port = 4000) {
318
+ const pid = readPid();
319
+ const running = pid !== null && isProcessAlive(pid);
320
+
321
+ return {
322
+ ok: true,
323
+ running,
324
+ pid: running ? pid : null,
325
+ url: running ? `http://localhost:${port}` : null
326
+ };
327
+ }
328
+
329
+ // Strip ANSI escapes so we can measure the actual on-screen width of a
330
+ // styled string. Used to size the rules around the banner content.
331
+ const ANSI_RE = /\[[0-9;]*m/g;
332
+ const visibleWidth = (s) => s.replace(ANSI_RE, "").length;
333
+
334
+ /**
335
+ * Render a HU Board announcement framed by two horizontal rules of EQUAL
336
+ * length. Pre-v2.7.5 we used a fixed 50-char rounded box drawn with
337
+ * corners + verticals — fine when the URL fit, but a project slug
338
+ * pushed past the right wall and the box looked broken. The user's
339
+ * verdict: "looks worse than no box at all when it overflows".
340
+ *
341
+ * New rule: just an over-and-under rule sized to the longest visible
342
+ * content line. No left/right verticals, no fixed width. Always tidy.
343
+ *
344
+ * @param {Object} params
345
+ * @param {string} params.url - "http://localhost:4000"
346
+ * @param {string} params.status - "started" | "already running"
347
+ * @param {string} [params.projectName] - optional project label
348
+ * @returns {string} ANSI-coloured banner ready for console.log
349
+ */
350
+ export function renderBoardBanner({ url, status, projectName }) {
351
+ const cyan = (s) => `${s}`;
352
+ const bold = (s) => `${s}`;
353
+ const underline = (s) => `${s}`;
354
+
355
+ const heading = ` ${bold("HU Board")} · ${status} → ${underline(url)}`;
356
+ const content = [heading];
357
+ if (projectName) content.push(` Project: ${bold(projectName)}`);
358
+
359
+ // Width = widest visible content line + 2-cell padding. Cap at the
360
+ // terminal width so a silly-long URL doesn't draw a 200-char rule.
361
+ const termWidth = (process.stdout.columns && process.stdout.columns > 20)
362
+ ? process.stdout.columns
363
+ : 100;
364
+ const longest = Math.max(...content.map(visibleWidth));
365
+ const ruleWidth = Math.min(longest + 2, termWidth);
366
+ const rule = cyan("─".repeat(ruleWidth));
367
+
368
+ return ["", rule, ...content, rule, ""].join("\n");
369
+ }
370
+
371
+ export async function boardCommand({ action = "start", port = 4000, bind = "127.0.0.1", logger }) {
372
+ switch (action) {
373
+ case "start": {
374
+ let result;
375
+ try {
376
+ result = await startBoard(port, { bind });
377
+ } catch (err) {
378
+ // The daemon died on boot (broken native module, crash) or no
379
+ // free port was found. Surface it cleanly — the whole point of
380
+ // this fix is that `kj board start` never reports a phantom
381
+ // success while leaving a dead board behind.
382
+ logger.error(`HU Board failed to start: ${err.message}`);
383
+ process.exitCode = 1;
384
+ return { ok: false, error: err.message };
385
+ }
386
+ if (result.alreadyRunning) {
387
+ logger.info(`HU Board already running (PID ${result.pid}) at ${result.url}`);
388
+ } else {
389
+ logger.info(`HU Board started (PID ${result.pid}) at ${result.url}`);
390
+ if (result.logPath) {
391
+ logger.info(`Daemon logs: tail -f ${result.logPath}`);
392
+ }
393
+ if (bind !== "127.0.0.1" && bind !== "localhost") {
394
+ logger.warn(`HU Board bound to ${bind} — token auth enforced for non-loopback peers. Token: ~/.karajan/hu-board/token`);
395
+ }
396
+ }
397
+ return result;
398
+ }
399
+ case "stop": {
400
+ const result = await stopBoard({ port });
401
+ if (result.wasRunning) {
402
+ const pidPart = result.pid ? ` (PID ${result.pid})` : "";
403
+ const viaPart = result.via && result.via !== "pidfile" ? ` [via ${result.via}]` : "";
404
+ logger.info(`HU Board stopped${pidPart}${viaPart}`);
405
+ } else {
406
+ logger.info("HU Board was not running");
407
+ }
408
+ return result;
409
+ }
410
+ case "status": {
411
+ const result = await boardStatus(port);
412
+ if (result.running) {
413
+ logger.info(`HU Board is running (PID ${result.pid}) at ${result.url}`);
414
+ } else {
415
+ logger.info("HU Board is not running");
416
+ }
417
+ return result;
418
+ }
419
+ case "open": {
420
+ const status = await boardStatus(port);
421
+ if (!status.running) {
422
+ logger.info("HU Board is not running. Starting it first...");
423
+ await startBoard(port);
424
+ }
425
+ const url = `http://localhost:${port}`;
426
+ // eslint-disable-next-line import-x/no-unresolved -- optional peer; the .catch handles its absence
427
+ const { default: open } = await import("open").catch(() => ({ default: null }));
428
+ if (open) {
429
+ await open(url);
430
+ logger.info(`Opened ${url}`);
431
+ } else {
432
+ logger.info(`Open in browser: ${url}`);
433
+ }
434
+ return { ok: true, url };
435
+ }
436
+ case "cleanup": {
437
+ const { initDb, closeDb, cleanupZombies } = await import("../../packages/hu-board/src/cleanup-zombies.js");
438
+ initDb();
439
+ try {
440
+ const dryRun = process.argv.includes("--dry-run");
441
+ const report = cleanupZombies({ dryRun });
442
+ const total = report.projects.length + report.prompts.length + report.sessions.length;
443
+ const prefix = dryRun ? "[dry-run] Would clean" : "Cleaned";
444
+ logger.info(`${prefix}: ${report.projects.length} projects, ${report.prompts.length} prompts, ${report.sessions.length} sessions (total ${total})`);
445
+ if (report.projects.length) logger.info(` projects: ${report.projects.join(", ")}`);
446
+ if (report.prompts.length) logger.info(` prompts: ${report.prompts.join(", ")}`);
447
+ if (report.sessions.length) logger.info(` sessions: ${report.sessions.join(", ")}`);
448
+ if (total === 0) logger.info(" Board is already clean.");
449
+ return { ok: true, ...report };
450
+ } finally {
451
+ closeDb();
452
+ }
453
+ }
454
+ default:
455
+ logger.error(`Unknown board action: ${action}. Use start|stop|status|open|cleanup`);
456
+ return { ok: false, error: `Unknown action: ${action}` };
457
+ }
458
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * `kj check` — verify the quality harness installed by `kj harden`
3
+ * (KJC-TSK-0558). Prints a per-category table and returns an exit code so it
4
+ * works both locally and as a CI drift gate. `--json` for machine output.
5
+ */
6
+
7
+ import { checkHarden } from "../harden/check.js";
8
+ import { collectMethodStats, formatMethodStats } from "../checks/method.js";
9
+ import { checkAiSurface, formatAiSurface } from "../checks/ai-surface.js";
10
+ import { detectObservedAgents } from "../utils/agent-detect.js";
11
+
12
+ export async function checkCommand({ projectDir = process.cwd(), profile = "standard", json = false, logger = console } = {}) {
13
+ const result = await checkHarden({ projectDir, profile });
14
+ // KJC-TSK-0689 (MG-D): method adherence is VISIBILITY, not a gate — it
15
+ // rides along in check output but never affects the exit code.
16
+ const method = await collectMethodStats({ projectDir }).catch(() => null);
17
+ // KJC-TSK-0694: same deal for the MCP inventory — a nudge, never a gate.
18
+ // KJC-TSK-0728: observed agent CLIs ride the same snapshot as "(cli)"
19
+ // entries, so a newly-appeared agent binary trips the same drift question.
20
+ let aiSurface = null;
21
+ try {
22
+ const clis = (await detectObservedAgents().catch(() => []))
23
+ .filter((a) => a.available)
24
+ .map((a) => `${a.name} (cli)`);
25
+ aiSurface = checkAiSurface({ projectDir, extraSurface: clis });
26
+ } catch { /* inventory is best-effort */ }
27
+
28
+ if (json) {
29
+ logger.info?.(JSON.stringify({ ...result, method, aiSurface }));
30
+ return result.ok ? 0 : 1;
31
+ }
32
+
33
+ logger.info?.(`kj check (${profile})`);
34
+ for (const c of result.checks) logger.info?.(` ${c.ok ? "✓" : "✗"} ${c.id}: ${c.detail}`);
35
+ if (method) logger.info?.(` method: ${formatMethodStats(method)}`);
36
+ if (aiSurface) logger.info?.(` ${formatAiSurface(aiSurface)}`);
37
+ if (!result.ok) logger.info?.("Harness drift detected — run `kj harden` to repair.");
38
+ else logger.info?.("Harness OK.");
39
+ return result.ok ? 0 : 1;
40
+ }
@@ -0,0 +1,145 @@
1
+ /**
2
+ * `kj clean` — manual garbage collector.
3
+ *
4
+ * Shows or deletes accumulated state under `~/.kj/plans/` and
5
+ * `~/.karajan/` (sessions, HU story batches). Dry-run by default so
6
+ * the user can eyeball the plan before committing to it.
7
+ *
8
+ * Usage:
9
+ * kj clean # dry-run, shows what would be removed
10
+ * kj clean --yes # actually delete
11
+ * kj clean --plan-days 60 # keep finalised plans for 60 days instead of 30
12
+ * kj clean --draft-days 90 # keep drafts for 90 days instead of 60
13
+ * kj clean --session-days 14 # keep finalised sessions for 14 days instead of 7
14
+ * kj clean --hu-days 30 # keep HU batches for 30 days instead of 14
15
+ *
16
+ * The auto-GC that runs at the start of every `kj run` / `kj plan`
17
+ * uses the same retention defaults; tweaking them here lets the user
18
+ * sweep more or less aggressively without editing config.
19
+ */
20
+
21
+ import { runManualGC, summarizeGC, nukeBoardDb } from "../utils/garbage-collector.js";
22
+ import { collectRepoCandidates } from "../utils/repo-cleaner.js";
23
+ import { collectVectorStoreOrphans } from "../utils/vector-store-orphans.js";
24
+
25
+ /**
26
+ * @param {Object} opts
27
+ * @param {boolean} [opts.yes=false] - when false, dry-run only
28
+ * @param {boolean} [opts.nuke=false] - retention=0 for every bucket + wipe board DB
29
+ * @param {boolean} [opts.repo=false] - also report repo-level candidates (branches, dist, *.tmp, *.bak)
30
+ * @param {number} [opts.repoDays=7] - age threshold for repo artifacts (default 7d)
31
+ * @param {string} [opts.repoBase="origin/main"]
32
+ * @param {boolean} [opts.vectorStores=false] - also report orphan RAG vector-store entries
33
+ * @param {string[]} [opts.projectRoots] - directories scanned to resolve slugs (default: ~/ws_*, ~/projects, ~/code)
34
+ * @param {number} [opts.planDays=30]
35
+ * @param {number} [opts.draftDays=60]
36
+ * @param {number} [opts.sessionDays=7]
37
+ * @param {number} [opts.huDays=14]
38
+ * @param {Object} [opts.logger]
39
+ */
40
+ export async function cleanCommand(opts = {}) {
41
+ const dryRun = !opts.yes;
42
+ // --nuke forces retention=0 everywhere so drafts / sessions / plans of
43
+ // any age are swept. Combined with wipeBoardDb this is the "I want it
44
+ // all gone right now" button.
45
+ const retention = opts.nuke
46
+ ? { planRetentionDays: 0, draftRetentionDays: 0, sessionRetentionDays: 0, huRetentionDays: 0 }
47
+ : {
48
+ planRetentionDays: opts.planDays,
49
+ draftRetentionDays: opts.draftDays,
50
+ sessionRetentionDays: opts.sessionDays,
51
+ huRetentionDays: opts.huDays,
52
+ };
53
+ const result = await runManualGC({ ...retention, dryRun });
54
+
55
+ if (opts.nuke) {
56
+ const boardResult = await nukeBoardDb({ dryRun });
57
+ result.removed.push(...boardResult.removed);
58
+ result.bytesFreed += boardResult.bytesFreed;
59
+ result.errors.push(...boardResult.errors);
60
+ }
61
+
62
+ if (!result.removed.length) {
63
+ console.log("[clean] nothing to clean — ~/.kj/ and ~/.karajan/ are tidy.");
64
+ return { ok: true, removed: 0 };
65
+ }
66
+
67
+ const verb = dryRun ? "would remove" : "removed";
68
+ console.log(`[clean] ${verb} ${result.removed.length} item(s):`);
69
+ for (const r of result.removed) {
70
+ console.log(` - ${r.kind.padEnd(10)} ${r.path}`);
71
+ console.log(` ${r.reason}`);
72
+ }
73
+
74
+ const kb = Math.round(result.bytesFreed / 1024);
75
+ if (kb) console.log(`[clean] ${verb} ${kb} KiB total`);
76
+
77
+ if (result.errors.length) {
78
+ console.log(`[clean] ${result.errors.length} error(s) (non-blocking):`);
79
+ for (const e of result.errors) console.log(` - ${e.path}: ${e.error}`);
80
+ }
81
+
82
+ if (dryRun) {
83
+ console.log("");
84
+ console.log("[clean] DRY-RUN — no files touched. Re-run with --yes to actually delete.");
85
+ } else {
86
+ console.log(summarizeGC(result));
87
+ }
88
+
89
+ if (opts.repo) await printRepoReport(opts);
90
+ if (opts.vectorStores) await printVectorStoreReport(opts);
91
+
92
+ return { ok: true, removed: result.removed.length, bytesFreed: result.bytesFreed };
93
+ }
94
+
95
+ async function printRepoReport(opts) {
96
+ const { candidates, errors } = await collectRepoCandidates({
97
+ maxAgeDays: opts.repoDays ?? 7,
98
+ mergedTo: opts.repoBase ?? "origin/main",
99
+ });
100
+ console.log("");
101
+ if (!candidates.length) {
102
+ console.log("[clean:repo] no repo-level candidates — branches/dist/tmp are tidy.");
103
+ return;
104
+ }
105
+ console.log(`[clean:repo] ${candidates.length} candidate(s) — review and run the command to remove:`);
106
+ for (const c of candidates) {
107
+ console.log(` - ${c.kind.padEnd(11)} ${c.target}`);
108
+ console.log(` ${c.reason}`);
109
+ console.log(` $ ${c.command}`);
110
+ }
111
+ if (errors.length) {
112
+ console.log(`[clean:repo] ${errors.length} non-blocking error(s):`);
113
+ for (const e of errors) console.log(` - ${e.stage}: ${e.error}`);
114
+ }
115
+ console.log("[clean:repo] read-only — commands are printed, never executed.");
116
+ }
117
+
118
+ async function printVectorStoreReport(opts) {
119
+ const { rows, orphans, errors } = await collectVectorStoreOrphans({
120
+ projectRoots: opts.projectRoots,
121
+ });
122
+ console.log("");
123
+ if (errors.length && !rows.length) {
124
+ console.log("[clean:rag] could not read rag.db — RAG may not be initialised yet.");
125
+ for (const e of errors) console.log(` - ${e.stage}: ${e.error}`);
126
+ return;
127
+ }
128
+ console.log(`[clean:rag] ${rows.length} project_slug(s) indexed in rag.db:`);
129
+ for (const r of rows) {
130
+ const tag = r.orphan ? " ORPHAN" : "";
131
+ const when = r.lastAt || "n/a";
132
+ console.log(` - ${r.slug.padEnd(28)} chunks=${String(r.chunkCount).padStart(5)} last=${when}${tag}`);
133
+ }
134
+ if (!orphans.length) {
135
+ console.log("[clean:rag] no orphans — every indexed slug resolves to a live project directory.");
136
+ return;
137
+ }
138
+ console.log("");
139
+ console.log(`[clean:rag] ${orphans.length} orphan(s) — review and run to purge:`);
140
+ for (const o of orphans) {
141
+ console.log(` - ${o.slug}`);
142
+ console.log(` $ ${o.command}`);
143
+ }
144
+ console.log("[clean:rag] read-only — commands are printed, never executed.");
145
+ }
@@ -0,0 +1,45 @@
1
+ import fs from "node:fs/promises";
2
+ import { createAgent } from "../agents/index.js";
3
+ import { assertAgentsAvailable } from "../agents/availability.js";
4
+ import { buildCoderPrompt } from "../prompts/coder.js";
5
+ import { resolveRole } from "../config.js";
6
+ import { withCliRunLog } from "../utils/cli-run-log.js";
7
+ import { createCliProgressReporter } from "../utils/cli-progress.js";
8
+
9
+ export async function codeCommand({ task, config, logger }) {
10
+ return withCliRunLog("code", { projectDir: config?.projectDir, logger }, async ({ runLog }) => {
11
+ const coderRole = resolveRole(config, "coder");
12
+ await assertAgentsAvailable([coderRole.provider]);
13
+ logger.info(`Coder (${coderRole.provider}) starting...`);
14
+ runLog.logText(`[coder] provider=${coderRole.provider}`);
15
+ const coder = createAgent(coderRole.provider, config, logger);
16
+ let coderRules = null;
17
+ if (config.coder_rules) {
18
+ try {
19
+ coderRules = await fs.readFile(config.coder_rules, "utf8");
20
+ } catch { /* configured coder_rules path not found — try fallback */
21
+ try { coderRules = await fs.readFile("coder-rules.md", "utf8"); } catch { /* no coder rules file */ }
22
+ }
23
+ }
24
+ const prompt = await buildCoderPrompt({ task, coderRules, methodology: config.development?.methodology || "tdd" });
25
+ const progress = createCliProgressReporter({ role: "coder" });
26
+ let result;
27
+ try {
28
+ result = await coder.runTask({ prompt, onOutput: progress.onOutput, role: "coder" });
29
+ progress.finish(result.ok ? "done" : "failed");
30
+ } catch (err) { progress.finish("failed"); throw err; }
31
+ if (!result.ok) {
32
+ if (result.error) logger.error(result.error);
33
+ throw new Error(result.error || result.output || `Coder failed (exit ${result.exitCode})`);
34
+ }
35
+ if (result.output) {
36
+ console.log(result.output);
37
+ }
38
+ if (result.error) {
39
+ logger.warn(result.error);
40
+ }
41
+ logger.info(`Coder completed (exit ${result.exitCode})`);
42
+ runLog.logText(`[coder] finished (exit=${result.exitCode})`);
43
+ return { ok: true };
44
+ });
45
+ }