@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,196 @@
1
+ /**
2
+ * Ephemeral project cleaner for the HU Board (board-polish #3,
3
+ * KJC-TSK-0371 — dogfooding 2026-05-07).
4
+ *
5
+ * Why this exists
6
+ * ===============
7
+ *
8
+ * Every `kj run` against a throwaway directory (`/tmp/kj-test-1`,
9
+ * `/tmp/demo-foo`, ad-hoc scratch projects under `tmp_*`) produces
10
+ * a board project that hangs around forever, even after the user
11
+ * has long since rm-rf'd the source tree. After 12 days of
12
+ * dogfooding the board ended up with 30+ "kj-test-N" projects
13
+ * the user couldn't tell apart from real ones — exactly the
14
+ * "noise on the demo" risk the user flagged on 2026-05-07.
15
+ *
16
+ * Heuristic (per the user's spec on the same session, "solución
17
+ * c — heurística + flag visible"):
18
+ *
19
+ * 1. Project is ephemeral when:
20
+ * a) `is_test = 1` (user explicitly marked it test), OR
21
+ * b) project id matches /^(tmp_|test_|demo_|kj-test-)/i
22
+ * AND `is_test IS NULL` (no override).
23
+ * 2. AND last_activity is older than `ttlHours` (default 24h).
24
+ *
25
+ * 3. Override: when `is_test = 0` (user clicked "keep it"), the
26
+ * project is NEVER deleted, even if the heuristic would match.
27
+ *
28
+ * What "cleanup" means: DELETE the project row + cascade-delete its
29
+ * stories and sessions, ADD a project tombstone, and rm-rf the
30
+ * on-disk dirs (`hu-stories/<sid>/`, `sessions/<sid>/`,
31
+ * `~/.kj/plans/<projectId>/`). Without the tombstone + fs cleanup,
32
+ * the very next chokidar `add` event or `fullScan` would re-import
33
+ * those files as a brand-new project (KJC-BUG-0055).
34
+ */
35
+
36
+ import fs from "node:fs";
37
+ import path from "node:path";
38
+ import { homedir } from "node:os";
39
+ import { addTombstone, getKjHome } from "./db.js";
40
+
41
+ const DEFAULT_TTL_HOURS = 24;
42
+ const EPHEMERAL_ID_PATTERNS = [
43
+ /^tmp_/i,
44
+ /^test_/i,
45
+ /^demo_/i,
46
+ /^kj-test-/i,
47
+ /^auto-tmp_/i, // also covers auto-batch projects derived from /tmp/
48
+ /^auto-test_/i,
49
+ /^auto-demo_/i,
50
+ // KJC-TSK-0377: stray session/plan-prefixed projects accumulate during
51
+ // dogfooding when a kj run is invoked without a proper projectDir.
52
+ // The HU Board's sync_*File handlers create a placeholder project row
53
+ // with `id = session_id` or `id = plan_id`, which after 24h of
54
+ // inactivity is the same kind of zombi the other patterns target.
55
+ /^s_/i,
56
+ /^plan-/i,
57
+ ];
58
+
59
+ /**
60
+ * Decide whether a single project row qualifies as an ephemeral
61
+ * candidate ready for deletion at `now`. Pure function — no I/O.
62
+ *
63
+ * The matrix:
64
+ * is_test = 1 → ephemeral (user marked it)
65
+ * is_test = 0 → not ephemeral, EVER (user override wins)
66
+ * is_test = NULL/und. → fall back to id-pattern heuristic
67
+ * too recent → not ephemeral (still in use)
68
+ *
69
+ * @param {{ id: string, last_activity?: string|null, first_seen?: string|null,
70
+ * is_test?: number|null }} project
71
+ * @param {{ now: number, ttlHours: number }} ctx
72
+ * @returns {{ ephemeral: boolean, reason?: string }}
73
+ */
74
+ export function classifyEphemeral(project, ctx) {
75
+ if (!project || !project.id) return { ephemeral: false };
76
+ // Explicit user override always wins. is_test=0 means "keep it".
77
+ if (project.is_test === 0) return { ephemeral: false };
78
+
79
+ const idMatch = EPHEMERAL_ID_PATTERNS.some((re) => re.test(project.id));
80
+ const explicitTest = project.is_test === 1;
81
+ if (!idMatch && !explicitTest) return { ephemeral: false };
82
+
83
+ const refIso = project.last_activity || project.first_seen;
84
+ if (!refIso) return { ephemeral: false };
85
+ const refTs = new Date(refIso).getTime();
86
+ if (Number.isNaN(refTs)) return { ephemeral: false };
87
+ const ageHours = (ctx.now - refTs) / 3_600_000;
88
+ if (ageHours <= 0) return { ephemeral: false };
89
+ if (ageHours < ctx.ttlHours) return { ephemeral: false };
90
+
91
+ const why = explicitTest ? "is_test=1" : "id heuristic";
92
+ return {
93
+ ephemeral: true,
94
+ reason: `${why}, ${ageHours.toFixed(1)}h inactive (>= ${ctx.ttlHours}h)`,
95
+ };
96
+ }
97
+
98
+ /**
99
+ * Filter a list of project rows down to the ephemeral ones. Pure.
100
+ *
101
+ * @param {Array<object>} projects
102
+ * @param {{ now?: number, ttlHours?: number }} [opts]
103
+ * @returns {Array<{ project: object, reason: string }>}
104
+ */
105
+ export function findEphemeralProjects(projects, opts = {}) {
106
+ const ctx = {
107
+ now: opts.now ?? Date.now(),
108
+ ttlHours: opts.ttlHours ?? DEFAULT_TTL_HOURS,
109
+ };
110
+ const out = [];
111
+ for (const project of projects || []) {
112
+ const verdict = classifyEphemeral(project, ctx);
113
+ if (verdict.ephemeral) out.push({ project, reason: verdict.reason });
114
+ }
115
+ return out;
116
+ }
117
+
118
+ /**
119
+ * Cascade-delete every ephemeral project at `now`. Side-effecting.
120
+ * Returns the list of cleaned projects so the server can log them.
121
+ *
122
+ * Idempotent: cleanup deletes the rows outright, so running twice
123
+ * has no effect on the second pass.
124
+ *
125
+ * @param {object} deps
126
+ * @param {object} deps.db better-sqlite3 Database handle
127
+ * @param {object} [deps.opts] threshold overrides
128
+ * @param {Function} [deps.now] () => number, for tests
129
+ * @returns {Array<{ id: string, reason: string, stories_deleted: number, sessions_deleted: number }>}
130
+ */
131
+ export function cleanupEphemeralProjects({
132
+ db,
133
+ opts = {},
134
+ now = () => Date.now(),
135
+ // Deps are injectable so the legacy in-memory tests (which never
136
+ // call `initDb()`) can pass no-op stubs and still verify the
137
+ // cascade-delete logic. Production callers omit them and get the
138
+ // real persistence + fs side-effects.
139
+ tombstone = (type, id, o) => {
140
+ try { addTombstone(type, id, o); } catch { /* DB not initialised (test) */ }
141
+ },
142
+ kjHome = () => {
143
+ try { return getKjHome(); } catch { return null; }
144
+ },
145
+ } = {}) {
146
+ if (!db) return [];
147
+ const allProjects = db
148
+ .prepare("SELECT id, last_activity, first_seen, is_test FROM projects")
149
+ .all();
150
+ const candidates = findEphemeralProjects(allProjects, { ...opts, now: now() });
151
+ if (candidates.length === 0) return [];
152
+
153
+ const delStories = db.prepare("DELETE FROM stories WHERE project_id = ?");
154
+ const delSessions = db.prepare("DELETE FROM sessions WHERE project_id = ?");
155
+ const delProject = db.prepare("DELETE FROM projects WHERE id = ?");
156
+
157
+ const selStories = db.prepare("SELECT id FROM stories WHERE project_id = ?");
158
+ const selSessions = db.prepare("SELECT id FROM sessions WHERE project_id = ?");
159
+ const plansRoot = process.env.KJ_PLANS_DIR
160
+ || path.join(homedir(), ".kj", "plans");
161
+ const home = kjHome();
162
+ const cleaned = [];
163
+ for (const { project, reason } of candidates) {
164
+ const storyIds = selStories.all(project.id).map((r) => r.id);
165
+ const sessionIds = selSessions.all(project.id).map((r) => r.id);
166
+ const fsPaths = home
167
+ ? [
168
+ ...storyIds.map((sid) => path.join(home, "hu-stories", sid)),
169
+ ...sessionIds.map((sid) => path.join(home, "sessions", sid)),
170
+ path.join(plansRoot, project.id),
171
+ ]
172
+ : [];
173
+ db.transaction(() => {
174
+ delStories.run(project.id);
175
+ delSessions.run(project.id);
176
+ delProject.run(project.id);
177
+ })();
178
+ // Tombstone the lot + best-effort rm-rf. If the fs op fails (perm
179
+ // error, file in use) we still keep the tombstone — that alone is
180
+ // enough to stop sync.js from re-importing on the next chokidar
181
+ // event, and `fullScan` will retry the rm-rf at next boot.
182
+ tombstone("project", project.id, { source: "ephemeral-cleaner", fsPaths });
183
+ for (const sid of storyIds) tombstone("story", sid, { source: "ephemeral-cleaner" });
184
+ for (const sid of sessionIds) tombstone("session", sid, { source: "ephemeral-cleaner" });
185
+ for (const p of fsPaths) {
186
+ try { fs.rmSync(p, { recursive: true, force: true }); } catch { /* best effort */ }
187
+ }
188
+ cleaned.push({
189
+ id: project.id,
190
+ reason,
191
+ stories_deleted: storyIds.length,
192
+ sessions_deleted: sessionIds.length,
193
+ });
194
+ }
195
+ return cleaned;
196
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Tiny pub/sub used to push sync-related events to connected SSE
3
+ * clients. The chokidar watcher calls `publish(event)` whenever a plan
4
+ * / session / batch file changes; the SSE endpoint subscribes each
5
+ * connected response and writes the JSON-encoded event as a message.
6
+ *
7
+ * No external dependency on purpose — `events.EventEmitter` would work
8
+ * but would make testing the endpoint awkward (we need deterministic
9
+ * subscribe/publish semantics for the integration tests in
10
+ * sse.test.js). The contract is:
11
+ *
12
+ * subscribe(fn) → returns an unsubscribe function
13
+ * publish(event) → calls every current subscriber once
14
+ * subscriberCount → how many listeners are live (tests only)
15
+ *
16
+ * Events are plain JSON objects. Callers pick the shape:
17
+ *
18
+ * { type: 'plan', planId, projectId } — a plan JSON was synced
19
+ * { type: 'session', sessionId, projectId }— a session JSON was synced
20
+ * { type: 'story', storyId, projectId } — a single HU row mutated
21
+ *
22
+ * The board's client treats these as hints: the usual response is to
23
+ * re-fetch the affected project's stories and patch the kanban in
24
+ * place, so no full page reload is ever required.
25
+ */
26
+
27
+ const subscribers = new Set();
28
+
29
+ /**
30
+ * @param {(event: object) => void} fn
31
+ * @returns {() => void} unsubscribe
32
+ */
33
+ export function subscribe(fn) {
34
+ subscribers.add(fn);
35
+ return () => { subscribers.delete(fn); };
36
+ }
37
+
38
+ /**
39
+ * @param {object} event
40
+ */
41
+ export function publish(event) {
42
+ for (const fn of subscribers) {
43
+ try { fn(event); } catch (err) {
44
+ // A broken subscriber must not take the bus down. Log and move on.
45
+ console.error('[event-bus] subscriber threw:', err.message);
46
+ }
47
+ }
48
+ }
49
+
50
+ /** @returns {number} */
51
+ export function subscriberCount() {
52
+ return subscribers.size;
53
+ }
54
+
55
+ /** Test helper — drops every subscriber. */
56
+ export function _resetForTests() {
57
+ subscribers.clear();
58
+ }
@@ -0,0 +1,207 @@
1
+ /**
2
+ * Pure formatters used by the board UI to turn database rows into
3
+ * human-friendly labels. No DOM, no node APIs — easy to unit-test.
4
+ */
5
+
6
+ const TIME_RE_FALLBACK = "—";
7
+ const MAX_TASK_CHARS = 60;
8
+ const MAX_PROJECT_CHARS = 40;
9
+
10
+ /**
11
+ * Format an ISO 8601 timestamp as `HH:MM` (24h, local time of the
12
+ * server). Falls back to `—` when the input is missing or unparseable
13
+ * so the UI never renders `Invalid Date` or `null`.
14
+ *
15
+ * @param {string|null|undefined} iso
16
+ * @returns {string}
17
+ */
18
+ export function formatHHMM(iso) {
19
+ if (!iso) return TIME_RE_FALLBACK;
20
+ const d = new Date(iso);
21
+ if (Number.isNaN(d.getTime())) return TIME_RE_FALLBACK;
22
+ const hh = String(d.getHours()).padStart(2, "0");
23
+ const mm = String(d.getMinutes()).padStart(2, "0");
24
+ return `${hh}:${mm}`;
25
+ }
26
+
27
+ /**
28
+ * Truncate a task description to a max of N chars, collapsing internal
29
+ * whitespace, suffixed with "…" when truncated. Returns "" when the
30
+ * input is empty so the caller can decide whether to render the slot.
31
+ *
32
+ * @param {string|null|undefined} text
33
+ * @param {number} [max=MAX_TASK_CHARS]
34
+ * @returns {string}
35
+ */
36
+ export function shortTask(text, max = MAX_TASK_CHARS) {
37
+ if (!text) return "";
38
+ const collapsed = String(text).replace(/\s+/g, " ").trim();
39
+ if (collapsed.length <= max) return collapsed;
40
+ return collapsed.slice(0, max - 1).trimEnd() + "…";
41
+ }
42
+
43
+ /**
44
+ * Build the label tuple used by `renderSessionCard` and
45
+ * `showSessionDetail`. The board used to print the session.id verbatim
46
+ * (e.g. `s_2026-05-07T08-35-58-010Z`) which is technically correct but
47
+ * impossible to scan at a glance — the user explicitly flagged this
48
+ * during pre-talk testing on 2026-05-07.
49
+ *
50
+ * Returned shape:
51
+ * - `title`: bold-line title for the card (project + time).
52
+ * Always non-empty; falls back to the session id when no project
53
+ * is known so the card is never blank.
54
+ * - `subtitle`: short task summary (≤ 60 chars). Empty when the
55
+ * session has no task field — caller hides the slot.
56
+ * - `idChip`: the cryptic session id, kept available for the user
57
+ * who needs it for `kj resume <id>` and as a tooltip on the title.
58
+ *
59
+ * @param {{ id: string, project_name?: string|null, task?: string|null,
60
+ * created_at?: string|null }} session
61
+ * @returns {{ title: string, subtitle: string, idChip: string }}
62
+ */
63
+ /**
64
+ * Format a per-HU cost (USD) for the card badge. Cost F (KJC-TSK-0517).
65
+ *
66
+ * Two outputs in one call so the UI can render both at once:
67
+ * - `label`: 2-decimal "$X.XX" shown on the card (compact).
68
+ * - `tooltip`: 4-decimal "Estimated cost: $X.XXXX" shown on hover
69
+ * (matches the precision aggregated by Cost B/C/D, see
70
+ * `packages/core/src/cost/index.js`).
71
+ *
72
+ * Returns `null` for null/undefined/non-finite inputs so the caller
73
+ * skips the badge entirely (rendering "$0.00" for an unknown cost
74
+ * would be misleading — there is a real difference between "we know it
75
+ * was free" and "we haven't measured it yet").
76
+ *
77
+ * @param {number|null|undefined} costUsd
78
+ * @returns {{ label: string, tooltip: string } | null}
79
+ */
80
+ export function formatCost(costUsd) {
81
+ if (costUsd === null || costUsd === undefined) return null;
82
+ const n = Number(costUsd);
83
+ if (!Number.isFinite(n)) return null;
84
+ return {
85
+ label: `$${n.toFixed(2)}`,
86
+ tooltip: `Estimated cost: $${n.toFixed(4)}`,
87
+ };
88
+ }
89
+
90
+ /**
91
+ * Φ0-G (KJC-TSK-0525): per-HU and per-plan cached-tokens badge.
92
+ *
93
+ * Returns `{ label, tooltip }` for "🎯 23%" with a tooltip showing the
94
+ * exact numerator/denominator. Same "don't render 0% when there is no
95
+ * data" stance as formatCost — returns null when:
96
+ * - either arg is null/undefined/non-finite,
97
+ * - tokens_in is 0 (no input → ratio undefined, not zero),
98
+ * - cached is negative (would only happen from corrupted telemetry).
99
+ *
100
+ * The badge intentionally shows 1 decimal to keep narrow runs (e.g.
101
+ * a 73-token cache out of 1 200) readable without rounding to 0%.
102
+ *
103
+ * @param {number|null|undefined} cachedTokens
104
+ * @param {number|null|undefined} tokensIn
105
+ * @returns {{ label: string, tooltip: string } | null}
106
+ */
107
+ export function formatCacheRatio(cachedTokens, tokensIn) {
108
+ // Reject null/undefined explicitly — Number(null) coerces to 0,
109
+ // which would otherwise render an honest-looking "🎯 0%" badge for
110
+ // runs where we simply have no telemetry. Same stance as formatCost.
111
+ if (cachedTokens === null || cachedTokens === undefined) return null;
112
+ if (tokensIn === null || tokensIn === undefined) return null;
113
+ const c = Number(cachedTokens);
114
+ const t = Number(tokensIn);
115
+ if (!Number.isFinite(c) || !Number.isFinite(t)) return null;
116
+ if (t <= 0 || c < 0) return null;
117
+ const pct = Math.round((c / t) * 1000) / 10;
118
+ const num = c.toLocaleString("en-US");
119
+ const den = t.toLocaleString("en-US");
120
+ return {
121
+ label: `🎯 ${pct}%`,
122
+ tooltip: `Cache hits: ${num} / ${den} input tokens (${pct}%)`,
123
+ };
124
+ }
125
+
126
+ /**
127
+ * Format the aggregated project-level cost chip rendered in the board
128
+ * header. Cost G (KJC-TSK-0518). Consumes the shape returned by
129
+ * `GET /api/projects/:id/cost` (Cost E):
130
+ * { totalUsd, byPlan: [{planId, totalUsd, huCount}, …],
131
+ * unknownModelTokens: { tokensIn, tokensOut, models }, currency }
132
+ *
133
+ * Returns `{ label, tooltip }` where:
134
+ * - `label` is the compact "Total: $X.XX" shown in the header.
135
+ * - `tooltip` is a multi-line string with full precision, the
136
+ * per-plan breakdown, and a note when some tokens used a model we
137
+ * don't price (they aren't included in the total — surfacing the
138
+ * skip is more honest than silently swallowing it).
139
+ *
140
+ * Returns `null` when there is nothing to show (no input, or no
141
+ * measured cost yet and no plans). The chip is then hidden — same
142
+ * reasoning as `formatCost`: "$0.00" with no data is misleading.
143
+ *
144
+ * @param {{totalUsd:number, byPlan?:Array, unknownModelTokens?:object}|null|undefined} cost
145
+ * @returns {{ label: string, tooltip: string } | null}
146
+ */
147
+ export function formatProjectCostSummary(cost) {
148
+ if (!cost || typeof cost !== "object") return null;
149
+ const total = Number(cost.totalUsd);
150
+ if (!Number.isFinite(total)) return null;
151
+ const byPlan = Array.isArray(cost.byPlan) ? cost.byPlan : [];
152
+ if (total === 0 && byPlan.length === 0) return null;
153
+
154
+ const lines = [`Total: $${total.toFixed(4)}`];
155
+ // Φ0-G (KJC-TSK-0525): when the API includes project-level cache
156
+ // counters, show the aggregate cached% line right under the total
157
+ // so users can see at a glance how much was served from cache.
158
+ const projCache = formatCacheRatio(cost.cachedTokens, cost.tokensIn);
159
+ if (projCache) lines.push(`Cache: ${projCache.tooltip.split(": ")[1]}`);
160
+ if (byPlan.length > 0) {
161
+ lines.push("By plan:");
162
+ for (const p of byPlan) {
163
+ const planTotal = Number(p?.totalUsd);
164
+ if (!Number.isFinite(planTotal)) continue;
165
+ const huCount = Number.isFinite(p?.huCount) ? p.huCount : 0;
166
+ const planLabel = p?.planId || "unassigned";
167
+ const cachePct = p?.cachedRatioPct;
168
+ const cacheSuffix = (cachePct !== null && cachePct !== undefined && Number.isFinite(Number(cachePct)))
169
+ ? ` 🎯 ${cachePct}%`
170
+ : "";
171
+ lines.push(` ${planLabel}: $${planTotal.toFixed(2)} (${huCount} HU${huCount === 1 ? "" : "s"})${cacheSuffix}`);
172
+ }
173
+ }
174
+ const unk = cost.unknownModelTokens;
175
+ if (unk && Number.isFinite(unk.tokensIn) && Number.isFinite(unk.tokensOut)) {
176
+ const unkTotal = unk.tokensIn + unk.tokensOut;
177
+ if (unkTotal > 0) {
178
+ lines.push(`(${unkTotal} tokens with unknown pricing not included)`);
179
+ }
180
+ }
181
+ return {
182
+ label: `Total: $${total.toFixed(2)}`,
183
+ tooltip: lines.join("\n"),
184
+ };
185
+ }
186
+
187
+ export function formatSessionLabel(session) {
188
+ if (!session || !session.id) {
189
+ return { title: "(no session)", subtitle: "", idChip: "" };
190
+ }
191
+ const projectName = (session.project_name || "").trim();
192
+ const time = formatHHMM(session.created_at);
193
+ const projectPart = projectName
194
+ ? projectName.length > MAX_PROJECT_CHARS
195
+ ? projectName.slice(0, MAX_PROJECT_CHARS - 1) + "…"
196
+ : projectName
197
+ : session.id; // fall back so a card without a joined project never
198
+ // ends up blank — the cryptic id is better than empty
199
+ const title = time !== TIME_RE_FALLBACK
200
+ ? `${projectPart} · ${time}`
201
+ : projectPart;
202
+ return {
203
+ title,
204
+ subtitle: shortTask(session.task, MAX_TASK_CHARS),
205
+ idChip: session.id,
206
+ };
207
+ }
@@ -0,0 +1,103 @@
1
+ /**
2
+ * HU-level zombie reaper (KJC-TSK-0394 step 3).
3
+ *
4
+ * Counterpart to zombie-reaper.js (que opera sobre sesiones). Detecta
5
+ * HUs que el orquestador dejó stuck en `coding`/`reviewing` —síntoma
6
+ * de crash mid-iteration— y las resetea a `pending`.
7
+ *
8
+ * Threshold separado de las sesiones: una sesión legítima puede
9
+ * tardar horas (loop largo), una HU individual rara vez >30 min. Por
10
+ * eso este reaper trabaja en minutos, no en horas.
11
+ *
12
+ * Semántica del reap: status → `pending` (NO `failed`: el orquestador
13
+ * perdió contacto, no podemos afirmar el outcome). El campo `result`
14
+ * NO se toca — se conserva como historial. La persistencia pasa por
15
+ * plan-mutations::setHuStatus (escribe el plan JSON + resync DB). Si
16
+ * el JSON write falla cae al fallback DB-only.
17
+ */
18
+
19
+ const DEFAULT_RUNNING_MINUTES = 30;
20
+ // `coding` + `reviewing` son los nombres legacy; `running` es el
21
+ // canónico tras step 5. Aceptamos los tres durante la transición.
22
+ const RUNNING_STATUSES = new Set(["coding", "reviewing", "running"]);
23
+
24
+ /**
25
+ * Pure. Decide si un HU row es zombi a `now`.
26
+ */
27
+ export function classifyHuZombie(hu, ctx) {
28
+ if (!hu || !hu.status) return { zombie: false };
29
+ const status = String(hu.status).toLowerCase();
30
+ if (!RUNNING_STATUSES.has(status)) return { zombie: false };
31
+ const refIso = hu.updated_at;
32
+ if (!refIso) return { zombie: false };
33
+ const refTs = new Date(refIso).getTime();
34
+ if (Number.isNaN(refTs)) return { zombie: false };
35
+ const ageMinutes = (ctx.now - refTs) / 60_000;
36
+ if (ageMinutes < ctx.runningMinutes) return { zombie: false };
37
+ return {
38
+ zombie: true,
39
+ reason: `${status} ${ageMinutes.toFixed(0)}min inactive (>= ${ctx.runningMinutes}min)`,
40
+ };
41
+ }
42
+
43
+ /** Pure. Filtra solo los zombis. */
44
+ export function findZombieHus(hus, opts = {}) {
45
+ const ctx = {
46
+ now: opts.now ?? Date.now(),
47
+ runningMinutes: opts.runningMinutes ?? DEFAULT_RUNNING_MINUTES,
48
+ };
49
+ const out = [];
50
+ for (const hu of hus || []) {
51
+ const verdict = classifyHuZombie(hu, ctx);
52
+ if (verdict.zombie) out.push({ hu, reason: verdict.reason });
53
+ }
54
+ return out;
55
+ }
56
+
57
+ /**
58
+ * Resetea a `pending` cada HU zombi en la DB Y en el fichero del
59
+ * plan. `setHuStatus` se inyecta para que los tests no necesiten un
60
+ * plan-file real. Devuelve la lista de reapeados para el log.
61
+ */
62
+ export function reapZombieHus({ db, setHuStatus, setHuFailResult, opts = {}, now = () => Date.now() }) {
63
+ if (!db || typeof setHuStatus !== "function") return [];
64
+ const rows = db.prepare(
65
+ "SELECT id, project_id, plan_id, status, updated_at FROM stories WHERE status IN ('coding', 'reviewing', 'running')",
66
+ ).all();
67
+ const zombies = findZombieHus(rows, { ...opts, now: now() });
68
+ if (zombies.length === 0) return [];
69
+ const reaped = [];
70
+ for (const { hu, reason } of zombies) {
71
+ // El id compuesto del board es `${projectId}::${huId}`; setHuStatus
72
+ // quiere el huId local.
73
+ const localHuId = hu.id.includes("::") ? hu.id.split("::").slice(1).join("::") : hu.id;
74
+ let planPersisted = false;
75
+ if (hu.plan_id) {
76
+ try {
77
+ const result = setHuStatus({ planId: hu.plan_id, huId: localHuId, status: "pending", projectId: hu.project_id });
78
+ planPersisted = !!(result && result.ok);
79
+ } catch { /* swallow — never block startup */ }
80
+ // KJC-TSK-0404: además de resetear status=pending, marcar la HU con
81
+ // result=fail + blocker. El siguiente `kj run` ve el blocker y se
82
+ // lo inyecta al coder como "## PREVIOUS ATTEMPT FAILED" para que
83
+ // no repita el mismo error. No-op si la fn no se inyectó.
84
+ if (planPersisted && typeof setHuFailResult === "function") {
85
+ try {
86
+ setHuFailResult({
87
+ planId: hu.plan_id, huId: localHuId, projectId: hu.project_id,
88
+ blocker: `timeout: ${reason}`,
89
+ });
90
+ } catch { /* swallow — el status reset ya está hecho */ }
91
+ }
92
+ }
93
+ // Fallback DB-only para legacy rows (plan_id null) o si el write al
94
+ // plan falló. setHuStatus ya resincroniza en el caso happy.
95
+ if (!planPersisted) {
96
+ try {
97
+ db.prepare("UPDATE stories SET status = 'pending', updated_at = ? WHERE id = ?").run(new Date(now()).toISOString(), hu.id);
98
+ } catch { /* swallow */ }
99
+ }
100
+ reaped.push({ id: hu.id, status_before: hu.status, reason, plan_persisted: planPersisted });
101
+ }
102
+ return reaped;
103
+ }