@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,271 @@
1
+ // KJC-TSK-0501 step 8c/8 — Story edit form (inline editor for the HU modal).
2
+ //
3
+ // Classic script (no exports). Loaded by index.html before app.js so the
4
+ // top-level `function` declarations are hoisted into the shared realm and
5
+ // `showStoryDetail` (story-detail-view.js) can call `renderStoryEditForm`
6
+ // when the user hits ✎ on a story.
7
+ //
8
+ // Globals consumed from earlier scripts / app.js:
9
+ // - esc() (app.js)
10
+ // - shortStoryId() (utils/formatters.js)
11
+ // - projectInitialsCache (mutable in app.js — assignment crosses
12
+ // scripts transparently)
13
+ // - showStoryDetail() (utils/story-detail-view.js)
14
+ // - showError() (utils/modals.js)
15
+ // - closeModal() (app.js → window.closeModal, used by
16
+ // inline onclick on the modal close button)
17
+ // - renderBoard() (app.js)
18
+
19
+ /**
20
+ * Swap the story modal into an inline edit form. Accepts the full story
21
+ * record that `showStoryDetail` just rendered so we don't re-fetch —
22
+ * the user's intent is "edit what I'm looking at".
23
+ *
24
+ * Cancel restores the read-only view via `showStoryDetail`; Save posts a
25
+ * PATCH and, on success, re-opens in read-only with the refreshed row.
26
+ *
27
+ * @param {object} story
28
+ */
29
+ function renderStoryEditForm(story) {
30
+ const content = document.getElementById('modal-content');
31
+ const initials = projectInitialsCache[story.project_id] || 'kj';
32
+ const shortId = shortStoryId(story, initials);
33
+
34
+ const rawAc = story.acceptance_criteria ? JSON.parse(story.acceptance_criteria) : [];
35
+ // Represent AC as text: plain strings go verbatim, Gherkin objects
36
+ // collapse to "Given … | When … | Then …". On save we parse back —
37
+ // if the line matches the pattern we treat it as Gherkin, otherwise
38
+ // a free-form string.
39
+ const acInitial = rawAc
40
+ .map((c) => typeof c === 'string' ? c : (c.given ? `Given ${c.given} | When ${c.when} | Then ${c.then}` : JSON.stringify(c)))
41
+ .join('\n');
42
+
43
+ // Tests-first editor: one block per test with a type selector
44
+ // (shell | gherkin), the content textarea, optional file path, and
45
+ // a remove button. Save collects them into the structured v2.7.5
46
+ // array shape. Plain strings in the existing data are treated as
47
+ // legacy shell tests and auto-upgraded to the structured form.
48
+ const rawTests = story.acceptance_tests ? JSON.parse(story.acceptance_tests) : [];
49
+ const testsInitial = rawTests.map((t) => {
50
+ if (typeof t === 'string') return { type: 'shell', content: t, file: '' };
51
+ if (t && typeof t === 'object') {
52
+ return {
53
+ type: t.type === 'gherkin' ? 'gherkin' : 'shell',
54
+ content: typeof t.content === 'string' ? t.content : JSON.stringify(t),
55
+ file: typeof t.file === 'string' ? t.file : '',
56
+ };
57
+ }
58
+ return { type: 'shell', content: String(t ?? ''), file: '' };
59
+ });
60
+
61
+ const scopeInitial = story.original_text || story.certified_want || '';
62
+ const TASK_TYPES = ['sw', 'infra', 'doc', 'add-tests', 'refactor'];
63
+
64
+ content.innerHTML = `
65
+ <div class="modal__header">
66
+ <div>
67
+ <div class="modal__title" title="${esc(story.id)}">${esc(shortId)} <span style="font-size:0.75rem;color:var(--text-muted);font-weight:normal">(editing)</span></div>
68
+ <div class="modal__subtitle" style="font-size:0.75rem;color:var(--text-muted);font-family:monospace;margin-top:2px">${esc(story.id)}</div>
69
+ </div>
70
+ <button class="modal__close" onclick="closeModal()">&times;</button>
71
+ </div>
72
+
73
+ <form id="hu-edit-form" onsubmit="return false" style="display:flex;flex-direction:column;gap:14px;padding:8px 0">
74
+ <label style="display:flex;flex-direction:column;gap:4px;font-size:0.85rem">
75
+ <span style="color:var(--text-muted)">Title</span>
76
+ <input type="text" id="edit-title" value="${esc(story.title || '')}"
77
+ style="padding:8px;border:1px solid var(--border);background:var(--bg-primary);color:var(--text);border-radius:var(--radius-sm);font-size:0.95rem"
78
+ maxlength="200" required>
79
+ </label>
80
+
81
+ <label style="display:flex;flex-direction:column;gap:4px;font-size:0.85rem">
82
+ <span style="color:var(--text-muted)">Scope</span>
83
+ <textarea id="edit-scope" rows="4"
84
+ style="padding:8px;border:1px solid var(--border);background:var(--bg-primary);color:var(--text);border-radius:var(--radius-sm);font-family:inherit;font-size:0.9rem;line-height:1.5;resize:vertical">${esc(scopeInitial)}</textarea>
85
+ </label>
86
+
87
+ <label style="display:flex;flex-direction:column;gap:4px;font-size:0.85rem">
88
+ <span style="color:var(--text-muted)">Task type</span>
89
+ <select id="edit-task-type"
90
+ style="padding:8px;border:1px solid var(--border);background:var(--bg-primary);color:var(--text);border-radius:var(--radius-sm);font-size:0.9rem">
91
+ ${TASK_TYPES.map((t) => `<option value="${t}">${t}</option>`).join('')}
92
+ </select>
93
+ </label>
94
+
95
+ <label style="display:flex;flex-direction:column;gap:4px;font-size:0.85rem">
96
+ <span style="color:var(--text-muted)">
97
+ Acceptance criteria — one per line.
98
+ Gherkin: <code>Given X | When Y | Then Z</code>
99
+ </span>
100
+ <textarea id="edit-ac" rows="6"
101
+ style="padding:8px;border:1px solid var(--border);background:var(--bg-primary);color:var(--text);border-radius:var(--radius-sm);font-family:var(--font-mono, monospace);font-size:0.85rem;line-height:1.5;resize:vertical">${esc(acInitial)}</textarea>
102
+ </label>
103
+
104
+ <fieldset style="border:1px solid var(--border);border-radius:var(--radius-sm);padding:10px 12px;margin:0">
105
+ <legend style="color:var(--text-muted);font-size:0.85rem;padding:0 6px">
106
+ Acceptance tests — the contract the coder must satisfy
107
+ </legend>
108
+ <div id="edit-tests-list" style="display:flex;flex-direction:column;gap:8px"></div>
109
+ <button type="button" id="edit-tests-add"
110
+ style="margin-top:8px;padding:4px 10px;font-size:0.8rem;background:var(--bg-primary);color:var(--text);border:1px solid var(--border);border-radius:var(--radius-sm);cursor:pointer">
111
+ + Add test
112
+ </button>
113
+ </fieldset>
114
+
115
+ <div style="display:flex;justify-content:flex-end;gap:8px;margin-top:8px;padding-top:12px;border-top:1px solid var(--border)">
116
+ <button type="button" id="edit-cancel" class="control-btn"
117
+ style="padding:6px 14px;border:1px solid var(--border);background:var(--bg-primary);color:var(--text);border-radius:var(--radius-sm);cursor:pointer">
118
+ Cancel
119
+ </button>
120
+ <button type="button" id="edit-save" class="control-btn"
121
+ style="padding:6px 14px;border:none;background:var(--color-green);color:#fff;border-radius:var(--radius-sm);cursor:pointer;font-weight:600">
122
+ Save
123
+ </button>
124
+ </div>
125
+ </form>
126
+ `;
127
+
128
+ // Tests editor state + renderers. Kept on the function scope (not
129
+ // on window) so reopening the form gives a fresh copy.
130
+ let testRows = [...testsInitial];
131
+ const listEl = document.getElementById('edit-tests-list');
132
+
133
+ function renderTestsEditor() {
134
+ listEl.innerHTML = testRows.map((t, i) => `
135
+ <div class="edit-test-row" data-idx="${i}"
136
+ style="display:grid;grid-template-columns:auto 1fr auto;gap:6px;align-items:start;padding:8px;border:1px solid var(--border);border-radius:var(--radius-sm);background:var(--bg-primary)">
137
+ <select data-field="type"
138
+ style="padding:4px 6px;border:1px solid var(--border);background:var(--bg-secondary);color:var(--text);border-radius:var(--radius-sm);font-size:0.8rem">
139
+ <option value="shell"${t.type === 'shell' ? ' selected' : ''}>shell</option>
140
+ <option value="gherkin"${t.type === 'gherkin' ? ' selected' : ''}>gherkin</option>
141
+ </select>
142
+ <div style="display:flex;flex-direction:column;gap:4px">
143
+ <textarea data-field="content" rows="${t.type === 'gherkin' ? 3 : 2}"
144
+ placeholder="${t.type === 'gherkin' ? 'Given …\\nWhen …\\nThen …' : 'npx vitest run test/foo.test.js'}"
145
+ style="padding:6px 8px;border:1px solid var(--border);background:var(--bg-secondary);color:var(--text);border-radius:var(--radius-sm);font-family:var(--font-mono, monospace);font-size:0.82rem;line-height:1.4;resize:vertical">${esc(t.content)}</textarea>
146
+ <input type="text" data-field="file" placeholder="Optional: file path (e.g. tests/login.test.ts)" value="${esc(t.file || '')}"
147
+ style="padding:4px 8px;border:1px solid var(--border);background:var(--bg-secondary);color:var(--text);border-radius:var(--radius-sm);font-family:var(--font-mono, monospace);font-size:0.75rem">
148
+ </div>
149
+ <button type="button" data-field="remove" title="Remove this test"
150
+ style="align-self:start;padding:4px 8px;background:transparent;border:1px solid var(--border);color:var(--text-muted);border-radius:var(--radius-sm);cursor:pointer;font-size:0.8rem">✕</button>
151
+ </div>
152
+ `).join('');
153
+
154
+ // Bind the edit events. We re-bind on every render — cheap, and
155
+ // keeps the state → DOM mapping simple.
156
+ listEl.querySelectorAll('.edit-test-row').forEach((row) => {
157
+ const idx = Number(row.dataset.idx);
158
+ row.querySelector('[data-field="type"]').addEventListener('change', (e) => {
159
+ testRows[idx].type = e.target.value;
160
+ renderTestsEditor(); // re-render so placeholder updates
161
+ });
162
+ row.querySelector('[data-field="content"]').addEventListener('input', (e) => {
163
+ testRows[idx].content = e.target.value;
164
+ });
165
+ row.querySelector('[data-field="file"]').addEventListener('input', (e) => {
166
+ testRows[idx].file = e.target.value;
167
+ });
168
+ row.querySelector('[data-field="remove"]').addEventListener('click', () => {
169
+ testRows.splice(idx, 1);
170
+ renderTestsEditor();
171
+ });
172
+ });
173
+ }
174
+
175
+ renderTestsEditor();
176
+
177
+ document.getElementById('edit-tests-add').addEventListener('click', () => {
178
+ testRows.push({ type: 'shell', content: '', file: '' });
179
+ renderTestsEditor();
180
+ });
181
+
182
+ document.getElementById('edit-cancel').addEventListener('click', () => showStoryDetail(story.id));
183
+ document.getElementById('edit-save').addEventListener('click', () => saveStoryEdits(story, () => testRows));
184
+ }
185
+
186
+ /**
187
+ * Collect the form values, compute the diff against the story as it
188
+ * was on modal open, and PATCH only the changed fields. Empty diff is
189
+ * a no-op (close out of edit mode without an API call).
190
+ * @param {object} story
191
+ */
192
+ async function saveStoryEdits(story, getTestRows) {
193
+ const title = document.getElementById('edit-title').value.trim();
194
+ const scope = document.getElementById('edit-scope').value;
195
+ const taskType = document.getElementById('edit-task-type').value;
196
+ const acRaw = document.getElementById('edit-ac').value;
197
+
198
+ if (!title) {
199
+ await showError('Title cannot be empty.', { title: 'Invalid input' });
200
+ return;
201
+ }
202
+
203
+ // Parse AC: one per line; a line with "Given X | When Y | Then Z"
204
+ // (case-insensitive) becomes Gherkin; everything else stays a string.
205
+ // Blank lines are dropped.
206
+ const acceptance_criteria = acRaw.split('\n')
207
+ .map((line) => line.trim())
208
+ .filter(Boolean)
209
+ .map((line) => {
210
+ const m = /^Given\s+(.*?)\s*\|\s*When\s+(.*?)\s*\|\s*Then\s+(.*)$/i.exec(line);
211
+ if (m) return { given: m[1].trim(), when: m[2].trim(), then: m[3].trim() };
212
+ return line;
213
+ });
214
+
215
+ // Collect the structured acceptance_tests from the editor state,
216
+ // dropping rows the user left empty.
217
+ const testRows = typeof getTestRows === 'function' ? getTestRows() : [];
218
+ const acceptance_tests = testRows
219
+ .map((t) => ({
220
+ type: t.type === 'gherkin' ? 'gherkin' : 'shell',
221
+ content: (t.content || '').trim(),
222
+ file: (t.file || '').trim(),
223
+ }))
224
+ .filter((t) => t.content.length > 0)
225
+ .map((t) => (t.file ? t : { type: t.type, content: t.content }));
226
+
227
+ // Diff against the original so we don't send untouched fields and
228
+ // let the server's COALESCE keep existing values.
229
+ const patch = {};
230
+ const originalScope = story.original_text || story.certified_want || '';
231
+ if (title !== (story.title || '')) patch.title = title;
232
+ if (scope !== originalScope) patch.scope = scope;
233
+ if (taskType && taskType !== 'sw') patch.task_type = taskType;
234
+ const prevAcStr = story.acceptance_criteria || '[]';
235
+ const nextAcStr = JSON.stringify(acceptance_criteria);
236
+ if (nextAcStr !== prevAcStr) patch.acceptance_criteria = acceptance_criteria;
237
+ const prevTestsStr = story.acceptance_tests || '[]';
238
+ const nextTestsStr = JSON.stringify(acceptance_tests);
239
+ if (nextTestsStr !== prevTestsStr) patch.acceptance_tests = acceptance_tests;
240
+
241
+ if (Object.keys(patch).length === 0) {
242
+ showStoryDetail(story.id);
243
+ return;
244
+ }
245
+
246
+ try {
247
+ const res = await fetch(`/api/stories/${encodeURIComponent(story.id)}`, {
248
+ method: 'PATCH',
249
+ headers: { 'Content-Type': 'application/json' },
250
+ body: JSON.stringify(patch),
251
+ });
252
+ if (!res.ok) {
253
+ if (res.status === 404) {
254
+ await showError(
255
+ 'The board server does not recognise the extended PATCH endpoint.\n\n'
256
+ + 'Restart it to pick up v2.7.5+:\n\n'
257
+ + ' kj board stop && kj board start',
258
+ { title: 'Board out of date' }
259
+ );
260
+ return;
261
+ }
262
+ const msg = (await res.json().catch(() => ({}))).error || `HTTP ${res.status}`;
263
+ await showError(msg, { title: 'Could not save HU' });
264
+ return;
265
+ }
266
+ await renderBoard(); // card shows new title/counts
267
+ await showStoryDetail(story.id); // reopen modal with fresh data
268
+ } catch (err) {
269
+ await showError(err.message, { title: 'Could not save HU' });
270
+ }
271
+ }
@@ -0,0 +1,32 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Karajan Wiki · QMD semantic search</title>
7
+ <link rel="stylesheet" href="/styles.css">
8
+ </head>
9
+ <body>
10
+ <header class="header">
11
+ <div class="header__logo"><div><div class="header__title">Karajan Wiki</div><div class="header__subtitle">QMD semantic search</div></div></div>
12
+ <nav class="header__nav">
13
+ <a class="nav-btn" href="/">← Board</a>
14
+ <a class="nav-btn" href="/rag.html">RAG</a>
15
+ <button class="nav-btn active">Wiki</button>
16
+ </nav>
17
+ </header>
18
+ <main class="main" id="wiki-app">
19
+ <section class="wiki-search">
20
+ <form id="wiki-form" class="wiki-form" autocomplete="off">
21
+ <input id="wiki-q" type="search" placeholder="Search the project wiki…" aria-label="Query" required>
22
+ <input id="wiki-collection" type="text" placeholder="collection (optional)" aria-label="Collection">
23
+ <label class="wiki-fast"><input id="wiki-fast" type="checkbox"> fast (no rerank)</label>
24
+ <button type="submit" class="control-btn" id="wiki-go">Search</button>
25
+ </form>
26
+ <p class="wiki-hint">QMD runs locally — 0 tokens, ~60s with rerank, ~5s in fast mode.</p>
27
+ </section>
28
+ <section id="wiki-results" class="wiki-results"></section>
29
+ </main>
30
+ <script src="/wiki.js"></script>
31
+ </body>
32
+ </html>
@@ -0,0 +1,53 @@
1
+ // KJC-TSK-0508 — HU Board Wiki panel frontend. Calls POST /api/wiki/query
2
+ // (server-side spawns `qmd query --format json`) and renders the hits.
3
+ // A 503 with `installable:true` triggers an "Install QMD" CTA instead of
4
+ // a generic error so the user can act on the missing binary.
5
+ const TOKEN = new URLSearchParams(location.search).get("token") || localStorage.getItem("kj_board_token") || "";
6
+ const HEADERS = { "Content-Type": "application/json", ...(TOKEN ? { Authorization: `Bearer ${TOKEN}` } : {}) };
7
+ const ESC = (s) => String(s).replace(/[&<>"']/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" }[c]));
8
+
9
+ function renderInstallCTA(root) {
10
+ root.innerHTML = `<div class="rag-card rag-card--error">
11
+ <h3>QMD not installed</h3>
12
+ <p>The semantic wiki engine is not available on this host. Install it once:</p>
13
+ <pre class="wiki-code">npm install -g @tobilu/qmd</pre>
14
+ <p class="rag-stat-sub">Then re-run <code>kj init</code> in this project to register the wiki collection.</p>
15
+ </div>`;
16
+ }
17
+
18
+ function renderHits(root, results) {
19
+ if (!Array.isArray(results) || results.length === 0) {
20
+ root.innerHTML = '<p class="rag-empty">no hits — try a broader query or run <code>qmd update</code>.</p>';
21
+ return;
22
+ }
23
+ root.innerHTML = results.map((r) => {
24
+ const src = r.source || r.path || r.file || "(unknown source)";
25
+ const snip = r.snippet || r.text || r.content || "";
26
+ const score = typeof r.score === "number" ? r.score.toFixed(3) : "";
27
+ return `<article class="wiki-hit">
28
+ <header class="wiki-hit__head"><span class="wiki-hit__src">${ESC(src)}</span>${score ? `<span class="wiki-hit__score">${ESC(score)}</span>` : ""}</header>
29
+ <pre class="wiki-hit__snip">${ESC(snip)}</pre>
30
+ </article>`;
31
+ }).join("");
32
+ }
33
+
34
+ async function runSearch(ev) {
35
+ ev?.preventDefault();
36
+ const root = document.getElementById("wiki-results");
37
+ const query = document.getElementById("wiki-q").value.trim();
38
+ if (!query) return;
39
+ const collection = document.getElementById("wiki-collection").value.trim() || undefined;
40
+ const fast = document.getElementById("wiki-fast").checked;
41
+ root.innerHTML = '<div class="loading"><div class="loading__spinner"></div><p>Searching the wiki…</p></div>';
42
+ try {
43
+ const res = await fetch("/api/wiki/query", { method: "POST", headers: HEADERS, body: JSON.stringify({ query, collection, fast }) });
44
+ const data = await res.json().catch(() => ({}));
45
+ if (res.status === 503 && data.installable) { renderInstallCTA(root); return; }
46
+ if (!res.ok) throw new Error(data.error || `HTTP ${res.status}`);
47
+ renderHits(root, data.results);
48
+ } catch (err) {
49
+ root.innerHTML = `<div class="rag-card rag-card--error"><strong>Error:</strong> ${ESC(err.message || String(err))}</div>`;
50
+ }
51
+ }
52
+
53
+ document.getElementById("wiki-form").addEventListener("submit", runSearch);
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Bearer-token authentication middleware for HU Board.
3
+ *
4
+ * Threat model — KJC-TSK-0355:
5
+ * The board is a localhost dev tool. The "shared dev machine" risk
6
+ * only kicks in when the server binds beyond loopback (e.g.
7
+ * `--bind 0.0.0.0` to access from another laptop on LAN). For pure
8
+ * loopback traffic, the OS already enforces "only the local user
9
+ * can connect", so a token would be UX friction with zero benefit
10
+ * (the browser opens `http://localhost:4000` and would stop working
11
+ * without manual `?token=...` injection on every link).
12
+ *
13
+ * Behaviour:
14
+ * - When `HU_BOARD_TOKEN` is set AND the request is from a non-
15
+ * loopback address → require the token in `Authorization: Bearer`
16
+ * header, `?token=` query param, or `kj_board_token` cookie.
17
+ * - When the request comes from loopback (127.0.0.1 / ::1 / ::ffff:127.0.0.1)
18
+ * → skip auth. The browser running on the same machine just works.
19
+ * - When `HU_BOARD_TOKEN` is unset → skip auth entirely. Tests and
20
+ * legacy callers keep working.
21
+ */
22
+
23
+ /** Loopback addresses we trust without auth, regardless of token state. */
24
+ const LOOPBACK_ADDRESSES = new Set([
25
+ "127.0.0.1",
26
+ "::1",
27
+ "::ffff:127.0.0.1",
28
+ ]);
29
+
30
+ /**
31
+ * Returns an Express middleware that enforces token auth for non-
32
+ * loopback requests when HU_BOARD_TOKEN is defined.
33
+ *
34
+ * @returns {import('express').RequestHandler}
35
+ */
36
+ export function authMiddleware() {
37
+ return (req, res, next) => {
38
+ const expected = process.env.HU_BOARD_TOKEN;
39
+ if (!expected) return next();
40
+
41
+ if (isLoopback(req)) return next();
42
+
43
+ const token = extractToken(req);
44
+ if (token === expected) return next();
45
+
46
+ return res.status(401).json({
47
+ error: "Unauthorized",
48
+ message:
49
+ "HU Board is bound to a non-loopback interface. Pass the token from " +
50
+ "~/.karajan/hu-board/token via Authorization: Bearer <token>, " +
51
+ "?token=<token>, or the kj_board_token cookie.",
52
+ });
53
+ };
54
+ }
55
+
56
+ /**
57
+ * @param {import('express').Request} req
58
+ * @returns {boolean}
59
+ */
60
+ function isLoopback(req) {
61
+ // Express populates req.ip from req.socket.remoteAddress unless
62
+ // `trust proxy` is set (we don't, by design — the board never sits
63
+ // behind a proxy in its supported deployment). req.ip is therefore
64
+ // the actual peer address.
65
+ const ip = req.ip || req.socket?.remoteAddress || "";
66
+ return LOOPBACK_ADDRESSES.has(ip);
67
+ }
68
+
69
+ /**
70
+ * @param {import('express').Request} req
71
+ * @returns {string|undefined}
72
+ */
73
+ function extractToken(req) {
74
+ const authHeader = req.headers.authorization;
75
+ if (authHeader && authHeader.startsWith("Bearer ")) {
76
+ return authHeader.slice(7);
77
+ }
78
+ if (req.query && typeof req.query.token === "string") {
79
+ return req.query.token;
80
+ }
81
+ // Cookie parsing: we don't pull cookie-parser as a dep just for one
82
+ // header. Express stores the raw header in req.headers.cookie; the
83
+ // grammar is `name=value; name2=value2`. Good enough for one key.
84
+ const cookieHeader = req.headers.cookie;
85
+ if (cookieHeader) {
86
+ for (const part of cookieHeader.split(";")) {
87
+ const [name, ...rest] = part.trim().split("=");
88
+ if (name === "kj_board_token") return rest.join("=");
89
+ }
90
+ }
91
+ return undefined;
92
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Process-scoped boot info (KJC-TSK-0379).
3
+ *
4
+ * BOOT_TIME is captured at module load and exposed via /api/version so
5
+ * the client can detect server restarts and trigger a reload. Lives in
6
+ * its own module to avoid the server.js ↔ routes/api.js import cycle.
7
+ */
8
+
9
+ import { createRequire } from 'node:module';
10
+
11
+ export const BOOT_TIME = new Date().toISOString();
12
+
13
+ const require = createRequire(import.meta.url);
14
+ export const PKG_VERSION = (() => {
15
+ try { return require('../package.json').version; } catch { return 'unknown'; }
16
+ })();
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Cleanup zombi resources of the HU Board (KJC-TSK-0380 PR C).
3
+ *
4
+ * Detects and removes orphan / abandoned entries that survive across
5
+ * dogfooding sessions and would otherwise reappear on every fullScan:
6
+ *
7
+ * 1. Ephemeral projects (`tmp_*`, `test_*`, `demo_*`, `kj-test-*`,
8
+ * `s_*`, `plan-*`) older than `staleProjectDays` (default 7).
9
+ * 2. Stale prompts (no `.answer.json` and file mtime older than
10
+ * `stalePromptHours`, default 24).
11
+ * 3. Orphan session directories (`~/.karajan/sessions/<id>/`) older
12
+ * than `staleProjectDays` with no matching DB row.
13
+ *
14
+ * Each removal goes through the same path the API endpoints use:
15
+ * tombstone + fs cleanup. So nothing resurrects on the next sync.
16
+ *
17
+ * Usage:
18
+ * import { cleanupZombies } from './cleanup-zombies.js';
19
+ * const report = cleanupZombies({ dryRun: false });
20
+ *
21
+ * Pure module; no HTTP. Safe to call with the server down.
22
+ */
23
+
24
+ import fs from 'node:fs';
25
+ import path from 'node:path';
26
+ import {
27
+ getDb,
28
+ getKjHome,
29
+ addTombstone,
30
+ isTombstoned,
31
+ deleteProject,
32
+ deleteSession,
33
+ initDb,
34
+ closeDb,
35
+ } from './db.js';
36
+
37
+ // Re-export the db lifecycle helpers so callers (e.g. `kj board cleanup`)
38
+ // can do a single `await import('cleanup-zombies.js')` and stay within
39
+ // the project's dynamic-import budget. The script otherwise needs both
40
+ // initDb/closeDb (lifecycle) and cleanupZombies (logic).
41
+ export { initDb, closeDb };
42
+
43
+ const EPHEMERAL_PREFIXES = ['tmp_', 'test_', 'demo_', 'kj-test-', 's_', 'plan-'];
44
+
45
+ function isEphemeralId(id) {
46
+ return EPHEMERAL_PREFIXES.some((p) => id.startsWith(p));
47
+ }
48
+
49
+ // KJC-BUG-0059 (v2.19.3): cleanup walks all known plan roots,
50
+ // canonical first then legacy. Pre-fix hard-coded `~/.kj/plans/`
51
+ // missed every plan post-v2.19.0 → zombies never garbage-collected.
52
+ import { getHuBoardPlansDirs } from './db.js';
53
+ function plansRoots() {
54
+ return getHuBoardPlansDirs();
55
+ }
56
+
57
+ function olderThanDays(isoStr, days) {
58
+ if (!isoStr) return true;
59
+ const t = Date.parse(isoStr);
60
+ if (Number.isNaN(t)) return true;
61
+ return (Date.now() - t) > days * 24 * 60 * 60 * 1000;
62
+ }
63
+
64
+ function olderThanHours(mtimeMs, hours) {
65
+ return (Date.now() - mtimeMs) > hours * 60 * 60 * 1000;
66
+ }
67
+
68
+ /**
69
+ * @param {object} opts
70
+ * @param {boolean} [opts.dryRun=false] - Compute the report without writing.
71
+ * @param {number} [opts.staleProjectDays=7]
72
+ * @param {number} [opts.stalePromptHours=24]
73
+ * @returns {{ projects: string[], prompts: string[], sessions: string[], dryRun: boolean }}
74
+ */
75
+ export function cleanupZombies(opts = {}) {
76
+ const dryRun = !!opts.dryRun;
77
+ const staleProjectDays = opts.staleProjectDays ?? 7;
78
+ const stalePromptHours = opts.stalePromptHours ?? 24;
79
+
80
+ const report = { projects: [], prompts: [], sessions: [], dryRun };
81
+ const db = getDb();
82
+
83
+ // (1) Ephemeral projects -------------------------------------------------
84
+ const allProjects = db.prepare('SELECT id, last_activity FROM projects').all();
85
+ for (const proj of allProjects) {
86
+ if (!isEphemeralId(proj.id)) continue;
87
+ if (!olderThanDays(proj.last_activity, staleProjectDays)) continue;
88
+ report.projects.push(proj.id);
89
+ if (dryRun) continue;
90
+
91
+ const storyIds = db.prepare('SELECT id FROM stories WHERE project_id = ?').all(proj.id).map((r) => r.id);
92
+ const sessionIds = db.prepare('SELECT id FROM sessions WHERE project_id = ?').all(proj.id).map((r) => r.id);
93
+ const fsPaths = [
94
+ ...storyIds.map((sid) => path.join(getKjHome(), 'hu-stories', sid)),
95
+ ...sessionIds.map((sid) => path.join(getKjHome(), 'sessions', sid)),
96
+ ...plansRoots().map((root) => path.join(root, proj.id)),
97
+ ];
98
+ deleteProject(proj.id);
99
+ addTombstone('project', proj.id, { source: 'auto-cleanup', fsPaths });
100
+ for (const sid of storyIds) addTombstone('story', sid, { source: 'auto-cleanup' });
101
+ for (const sid of sessionIds) addTombstone('session', sid, { source: 'auto-cleanup' });
102
+ for (const p of fsPaths) { try { fs.rmSync(p, { recursive: true, force: true }); } catch { /* */ } }
103
+ }
104
+
105
+ // (2) Stale prompts ------------------------------------------------------
106
+ const promptsDir = path.join(getKjHome(), 'prompts');
107
+ if (fs.existsSync(promptsDir)) {
108
+ for (const name of fs.readdirSync(promptsDir)) {
109
+ if (!name.endsWith('.json') || name.endsWith('.answer.json')) continue;
110
+ const promptId = name.replace(/\.json$/, '');
111
+ if (isTombstoned('prompt', promptId)) continue;
112
+ const fullPath = path.join(promptsDir, name);
113
+ const answerPath = path.join(promptsDir, `${promptId}.answer.json`);
114
+ if (fs.existsSync(answerPath)) continue; // already resolved
115
+ const stat = fs.statSync(fullPath);
116
+ if (!olderThanHours(stat.mtimeMs, stalePromptHours)) continue;
117
+ report.prompts.push(promptId);
118
+ if (dryRun) continue;
119
+ addTombstone('prompt', promptId, { source: 'auto-cleanup', fsPaths: [fullPath, answerPath] });
120
+ try { fs.unlinkSync(fullPath); } catch { /* */ }
121
+ }
122
+ }
123
+
124
+ // (3) Orphan session directories ----------------------------------------
125
+ const sessionsDir = path.join(getKjHome(), 'sessions');
126
+ if (fs.existsSync(sessionsDir)) {
127
+ for (const dir of fs.readdirSync(sessionsDir)) {
128
+ if (isTombstoned('session', dir)) continue;
129
+ const fullPath = path.join(sessionsDir, dir);
130
+ const stat = fs.statSync(fullPath);
131
+ if (!stat.isDirectory()) continue;
132
+ const inDb = db.prepare('SELECT id FROM sessions WHERE id = ?').get(dir);
133
+ if (inDb) continue; // active session, not orphan
134
+ const mtimeIso = new Date(stat.mtimeMs).toISOString();
135
+ if (!olderThanDays(mtimeIso, staleProjectDays)) continue;
136
+ report.sessions.push(dir);
137
+ if (dryRun) continue;
138
+ deleteSession(dir);
139
+ addTombstone('session', dir, { source: 'auto-cleanup', fsPaths: [fullPath] });
140
+ try { fs.rmSync(fullPath, { recursive: true, force: true }); } catch { /* */ }
141
+ }
142
+ }
143
+
144
+ return report;
145
+ }