@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,116 @@
1
+ /**
2
+ * Mapping of AI provider → required environment variable(s), plus helpers to
3
+ * determine which ones are actually needed by the current active config.
4
+ *
5
+ * The Check subsystem uses this to validate tokens LAZILY: we only demand an
6
+ * API key when a role is configured to use the provider behind it. Providers
7
+ * that run fully locally (e.g. opencode via a self-hosted proxy) are marked
8
+ * `optional: true` and only warn if their key is missing.
9
+ */
10
+
11
+ /**
12
+ * @typedef {Object} ProviderEnv
13
+ * @property {string} provider
14
+ * @property {string[]} envVars - env var names that would satisfy the check (first match wins)
15
+ * @property {string} howToGetUrl - user-facing URL to obtain a key
16
+ * @property {boolean} optional - warn vs fail when missing
17
+ */
18
+
19
+ /** @type {Record<string, ProviderEnv>} */
20
+ export const PROVIDER_ENV = Object.freeze({
21
+ anthropic: {
22
+ provider: "anthropic",
23
+ envVars: ["ANTHROPIC_API_KEY"],
24
+ howToGetUrl: "https://console.anthropic.com/settings/keys",
25
+ optional: false,
26
+ },
27
+ openai: {
28
+ provider: "openai",
29
+ envVars: ["OPENAI_API_KEY"],
30
+ howToGetUrl: "https://platform.openai.com/api-keys",
31
+ optional: false,
32
+ },
33
+ google: {
34
+ provider: "google",
35
+ envVars: ["GEMINI_API_KEY", "GOOGLE_API_KEY"],
36
+ howToGetUrl: "https://aistudio.google.com/app/apikey",
37
+ optional: false,
38
+ },
39
+ opencode: {
40
+ provider: "opencode",
41
+ envVars: ["OPENCODE_API_KEY"],
42
+ howToGetUrl: "https://opencode.ai/docs",
43
+ optional: true, // local/self-hosted — may not need an API key
44
+ },
45
+ aider: {
46
+ provider: "aider",
47
+ envVars: ["AIDER_API_KEY"],
48
+ howToGetUrl: "https://aider.chat/docs/usage.html",
49
+ optional: true,
50
+ },
51
+ });
52
+
53
+ /**
54
+ * Returns true if any of the env vars for a provider is set non-empty.
55
+ * @param {ProviderEnv} entry
56
+ */
57
+ export function hasEnvVar(entry) {
58
+ return entry.envVars.some((name) => {
59
+ const v = process.env[name];
60
+ return typeof v === "string" && v.trim().length > 0;
61
+ });
62
+ }
63
+
64
+ /**
65
+ * Walk the active config and collect the set of providers currently in use
66
+ * by at least one role.
67
+ *
68
+ * @param {Object} config - Karajan config (loaded)
69
+ * @returns {Set<string>} - set of provider slugs (e.g. "anthropic", "openai")
70
+ */
71
+ export function collectActiveProviders(config) {
72
+ const providers = new Set();
73
+ const roles = config?.roles || {};
74
+ for (const [, roleCfg] of Object.entries(roles)) {
75
+ const provider = roleCfg?.provider;
76
+ if (typeof provider === "string" && provider.trim().length > 0) {
77
+ providers.add(provider);
78
+ }
79
+ }
80
+ // Also include top-level default coder/reviewer if set (legacy shape).
81
+ if (config?.coder) providers.add(config.coder);
82
+ if (config?.reviewer) providers.add(config.reviewer);
83
+ return providers;
84
+ }
85
+
86
+ /**
87
+ * Map active providers to the env vars we must validate.
88
+ *
89
+ * @param {Object} config
90
+ * @returns {ProviderEnv[]}
91
+ */
92
+ export function getRequiredProviderEnvs(config) {
93
+ const active = collectActiveProviders(config);
94
+ const required = [];
95
+ for (const providerSlug of active) {
96
+ // Some configs store agent names (claude, codex, gemini) rather than
97
+ // canonical provider names. Map them.
98
+ const canonical = normalizeProvider(providerSlug);
99
+ const entry = PROVIDER_ENV[canonical];
100
+ if (entry) required.push(entry);
101
+ }
102
+ return required;
103
+ }
104
+
105
+ /**
106
+ * Normalize CLI/agent name to canonical provider slug.
107
+ */
108
+ export function normalizeProvider(name) {
109
+ const lc = String(name || "").toLowerCase();
110
+ if (lc === "claude" || lc === "anthropic") return "anthropic";
111
+ if (lc === "codex" || lc === "openai") return "openai";
112
+ if (lc === "gemini" || lc === "google") return "google";
113
+ if (lc === "opencode") return "opencode";
114
+ if (lc === "aider") return "aider";
115
+ return lc;
116
+ }
@@ -0,0 +1,72 @@
1
+ import path from "node:path";
2
+ import { existsSync, statSync } from "node:fs";
3
+ import { runCommand } from "./process.js";
4
+
5
+ export const QMD_DEFAULT_PATHS = ["docs", ".reviews", ".karajan/plans"];
6
+
7
+ export function projectSlug(projectDir) {
8
+ const base = path.basename(path.resolve(projectDir));
9
+ return base.toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "project";
10
+ }
11
+
12
+ export function planQmdCollections(projectDir) {
13
+ const slug = projectSlug(projectDir);
14
+ const result = [];
15
+ for (const rel of QMD_DEFAULT_PATHS) {
16
+ const abs = path.join(projectDir, rel);
17
+ if (!existsSync(abs)) continue;
18
+ try { if (!statSync(abs).isDirectory()) continue; } catch { continue; }
19
+ const suffix = rel.replace(/^\./, "").replace(/[/\\]+/g, "-");
20
+ result.push({ name: `${slug}-${suffix}`, path: abs });
21
+ }
22
+ return result;
23
+ }
24
+
25
+ async function listExistingCollections() {
26
+ try {
27
+ const res = await runCommand("qmd", ["collection", "list"]);
28
+ if (res.exitCode !== 0) return new Set();
29
+ const names = new Set();
30
+ for (const line of (res.stdout || "").split("\n")) {
31
+ const m = line.match(/^([\w.-]+)\s+\(qmd:\/\//);
32
+ if (m) names.add(m[1]);
33
+ }
34
+ return names;
35
+ } catch { return new Set(); }
36
+ }
37
+
38
+ /**
39
+ * Register all default QMD collections for the project. Idempotent: skips
40
+ * collections already present in `qmd collection list`. qmd's CLI only
41
+ * accepts one path per `collection add`, so we register up to 3 sibling
42
+ * collections (`<slug>-docs`, `<slug>-reviews`, `<slug>-karajan-plans`).
43
+ */
44
+ export async function registerQmdCollections(projectDir, logger) {
45
+ const planned = planQmdCollections(projectDir);
46
+ const result = { ok: true, added: [], skipped: [], errors: [] };
47
+ if (planned.length === 0) {
48
+ logger.info("QMD: no indexable folders yet (docs/, .reviews/, .karajan/plans/ all missing).");
49
+ return result;
50
+ }
51
+ const existing = await listExistingCollections();
52
+ for (const { name, path: abs } of planned) {
53
+ if (existing.has(name)) { result.skipped.push(name); continue; }
54
+ try {
55
+ const res = await runCommand("qmd", ["collection", "add", abs, "--name", name], { timeout: 60_000 });
56
+ if (res.exitCode === 0) {
57
+ result.added.push(name);
58
+ } else {
59
+ const err = (res.stderr || res.stdout || "").trim() || `exit code ${res.exitCode}`;
60
+ result.errors.push(`${name}: ${err}`);
61
+ result.ok = false;
62
+ }
63
+ } catch (err) {
64
+ result.errors.push(`${name}: ${err.message || String(err)}`);
65
+ result.ok = false;
66
+ }
67
+ }
68
+ if (result.added.length) logger.info(`QMD: registered ${result.added.length} collection(s): ${result.added.join(", ")}.`);
69
+ if (result.skipped.length) logger.info(`QMD: ${result.skipped.length} collection(s) already present, skipped.`);
70
+ for (const err of result.errors) logger.warn(`QMD: collection registration failed — ${err}`);
71
+ return result;
72
+ }
@@ -0,0 +1,18 @@
1
+ import { runCommand } from "./process.js";
2
+
3
+ /**
4
+ * Detect whether QMD is installed and available.
5
+ * @returns {Promise<{ available: boolean, version: string|null }>}
6
+ */
7
+ export async function detectQmd() {
8
+ try {
9
+ const result = await runCommand("qmd", ["--version"]);
10
+ if (result.exitCode === 0) {
11
+ const version = (result.stdout || "").trim() || null;
12
+ return { available: true, version };
13
+ }
14
+ return { available: false, version: null };
15
+ } catch { /* qmd binary not found */
16
+ return { available: false, version: null };
17
+ }
18
+ }
@@ -0,0 +1,37 @@
1
+ import { runCommand } from "./process.js";
2
+ import { getInstallCommand } from "./os-detect.js";
3
+ import { detectQmd } from "./qmd-detect.js";
4
+
5
+ /**
6
+ * Install QMD using the OS-appropriate command.
7
+ * @param {object} logger - Logger with info/warn methods
8
+ * @returns {Promise<{ ok: boolean, version: string|null, error: string|null }>}
9
+ */
10
+ export async function installQmd(logger) {
11
+ const command = getInstallCommand("qmd");
12
+
13
+ logger.info("Installing QMD...");
14
+
15
+ try {
16
+ const result = await runCommand("sh", ["-c", command], { timeout: 120_000 });
17
+
18
+ if (result.exitCode !== 0) {
19
+ const error = (result.stderr || "").trim() || `exit code ${result.exitCode}`;
20
+ logger.warn(`QMD install failed: ${error}`);
21
+ return { ok: false, version: null, error };
22
+ }
23
+
24
+ const check = await detectQmd();
25
+ if (check.available) {
26
+ logger.info(`QMD ${check.version || ""} installed successfully.`);
27
+ return { ok: true, version: check.version, error: null };
28
+ }
29
+
30
+ logger.warn("QMD install command succeeded but qmd binary not found in PATH.");
31
+ return { ok: false, version: null, error: "Binary not found after install" };
32
+ } catch (err) {
33
+ const error = err.message || String(err);
34
+ logger.warn(`QMD install failed: ${error}`);
35
+ return { ok: false, version: null, error };
36
+ }
37
+ }
@@ -0,0 +1,56 @@
1
+ // KJ_SIMULATE_QUOTA — testing hook for the standby flow. See
2
+ // docs/quota-simulation.md. KJC-TSK-0493.
3
+
4
+ let _warned = false;
5
+
6
+ export function parseQuotaSimulationConfig(env = process.env) {
7
+ const trigger = env.KJ_SIMULATE_QUOTA;
8
+ if (!trigger) return null;
9
+ const m = /^after-iter-(\d+)$/i.exec(String(trigger).trim());
10
+ if (!m) {
11
+ if (!_warned) {
12
+ // eslint-disable-next-line no-console
13
+ console.warn(`[KJ_SIMULATE_QUOTA] Ignoring malformed value "${trigger}". Expected "after-iter-<N>".`);
14
+ _warned = true;
15
+ }
16
+ return null;
17
+ }
18
+ return {
19
+ afterIter: Number.parseInt(m[1], 10),
20
+ resetIso: env.KJ_SIMULATE_QUOTA_RESET || new Date(Date.now() + 3600_000).toISOString(),
21
+ agent: String(env.KJ_SIMULATE_QUOTA_AGENT || "claude").toLowerCase(),
22
+ };
23
+ }
24
+
25
+ const SYNTHETIC_MSG = {
26
+ claude: (iso) => `[KJ_SIMULATE_QUOTA] Claude Pro usage limit reached. weekly limit. resets at ${iso}`,
27
+ codex: () => `[KJ_SIMULATE_QUOTA] You have exceeded your current quota. retry after 60 seconds`,
28
+ gemini: () => `[KJ_SIMULATE_QUOTA] Resource exhausted: quota exceeded. retry after 60 seconds`,
29
+ };
30
+
31
+ export function buildSyntheticQuotaMessage(agent, resetIso) {
32
+ return (SYNTHETIC_MSG[agent] || SYNTHETIC_MSG.claude)(resetIso);
33
+ }
34
+
35
+ export function shouldSimulateQuota({ iteration, agent }, env = process.env) {
36
+ const cfg = parseQuotaSimulationConfig(env);
37
+ if (!cfg) return null;
38
+ if (Number(iteration) < cfg.afterIter) return null;
39
+ const a = String(agent || "").toLowerCase();
40
+ if (cfg.agent !== "any" && cfg.agent !== a) return null;
41
+ return { resetIso: cfg.resetIso, agent: a || cfg.agent };
42
+ }
43
+
44
+ // Mutates execResult: forces ok=false and prepends the synthetic quota
45
+ // line to result.error so the existing detectRateLimit branch fires.
46
+ export function applyQuotaSimulation(execResult, { iteration, agent }, env = process.env) {
47
+ const fire = shouldSimulateQuota({ iteration, agent }, env);
48
+ if (!fire || !execResult || typeof execResult !== "object") return false;
49
+ if (!execResult.result || typeof execResult.result !== "object") execResult.result = {};
50
+ execResult.result.error = `${buildSyntheticQuotaMessage(fire.agent, fire.resetIso)}\n${execResult.result.error || ""}`;
51
+ execResult.ok = false;
52
+ return true;
53
+ }
54
+
55
+ // Test seam — reset the one-shot warning between unit tests.
56
+ export function _resetSimulatorWarning() { _warned = false; }
@@ -0,0 +1,181 @@
1
+ /**
2
+ * Detects rate limit / usage cap messages from CLI agent output.
3
+ * Returns { isRateLimit, agent, message, cooldownUntil, cooldownMs }
4
+ * where agent is the best guess of which CLI triggered it (or "unknown").
5
+ */
6
+
7
+ /**
8
+ * Extracts cooldown timing from a rate limit message string.
9
+ * Returns { cooldownUntil, cooldownMs } where cooldownUntil is an ISO string
10
+ * and cooldownMs is milliseconds to wait, or both null if not found.
11
+ */
12
+ export function parseCooldown(message) {
13
+ if (!message || typeof message !== "string") {
14
+ return { cooldownUntil: null, cooldownMs: null };
15
+ }
16
+
17
+ // 1. ISO timestamp: "try again after 2026-03-07T15:30:00Z"
18
+ // Also: "resets at 2026-03-07T15:30:00Z"
19
+ const isoMatch = /(?:after|at)\s+(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z)/i.exec(
20
+ message
21
+ );
22
+ if (isoMatch) {
23
+ const target = new Date(isoMatch[1]);
24
+ if (!Number.isNaN(target.getTime())) {
25
+ const ms = Math.max(0, target.getTime() - Date.now());
26
+ return { cooldownUntil: target.toISOString(), cooldownMs: ms };
27
+ }
28
+ }
29
+
30
+ // 4. Claude specific: "resets at 2026-03-07 15:30 UTC" (space-separated date/time)
31
+ const resetMatch = /resets?\s+at\s+(\d{4}-\d{2}-\d{2})\s+(\d{2}:\d{2})\s*UTC/i.exec(
32
+ message
33
+ );
34
+ if (resetMatch) {
35
+ const target = new Date(`${resetMatch[1]}T${resetMatch[2]}:00Z`);
36
+ if (!Number.isNaN(target.getTime())) {
37
+ const ms = Math.max(0, target.getTime() - Date.now());
38
+ return { cooldownUntil: target.toISOString(), cooldownMs: ms };
39
+ }
40
+ }
41
+
42
+ // 5. Claude Code session / weekly limit: "resets 10:10pm (Europe/Madrid)" / "resets 4am"
43
+ // KJC-BUG-0064: prefer the parenthesised IANA TZ when present so the
44
+ // same stderr resolves to the same instant regardless of where kj runs
45
+ // (CI in TZ=UTC was misclassifying Madrid 10:10pm as already past).
46
+ // No TZ literal → fall back to the host's local TZ.
47
+ const ampmMatch = /reset(?:s|ting)?\s+(?:at\s+)?(\d{1,2})(?::(\d{2}))?\s*([ap]m)\b/i.exec(message);
48
+ if (ampmMatch) {
49
+ let hour = Number.parseInt(ampmMatch[1], 10) % 12;
50
+ if (/pm/i.test(ampmMatch[3])) hour += 12;
51
+ const minute = ampmMatch[2] ? Number.parseInt(ampmMatch[2], 10) : 0;
52
+ if (hour < 24 && minute < 60) {
53
+ const tzMatch = /\(([A-Za-z]+\/[A-Za-z_/+\-0-9]+)\)/.exec(message);
54
+ const tz = tzMatch ? tzMatch[1] : null;
55
+ const target = tz ? nextOccurrenceInTZ(hour, minute, tz) : nextOccurrenceLocal(hour, minute);
56
+ if (target) return { cooldownUntil: target.toISOString(), cooldownMs: Math.max(0, target.getTime() - Date.now()) };
57
+ }
58
+ }
59
+
60
+ // 2. Relative seconds: "retry after 120 seconds" / "retry in 120s" / "Retry-After: 120"
61
+ const secMatch = /(?:retry[\s-]*after|retry\s+in|wait)\s*:?\s*(\d+)\s*(?:seconds?|secs?|s\b)/i.exec(
62
+ message
63
+ ) || /Retry-After:\s*(\d+)/i.exec(message);
64
+ if (secMatch) {
65
+ const seconds = Number.parseInt(secMatch[1], 10);
66
+ const ms = seconds * 1000;
67
+ const target = new Date(Date.now() + ms);
68
+ return { cooldownUntil: target.toISOString(), cooldownMs: ms };
69
+ }
70
+
71
+ // 3. Relative minutes: "retry in 5 minutes" / "wait 5 min"
72
+ const minMatch = /(?:retry\s+in|wait|after)\s+(\d+)\s*(?:minutes?|mins?)/i.exec(
73
+ message
74
+ );
75
+ if (minMatch) {
76
+ const minutes = Number.parseInt(minMatch[1], 10);
77
+ const ms = minutes * 60 * 1000;
78
+ const target = new Date(Date.now() + ms);
79
+ return { cooldownUntil: target.toISOString(), cooldownMs: ms };
80
+ }
81
+
82
+ return { cooldownUntil: null, cooldownMs: null };
83
+ }
84
+
85
+ // KJC-BUG-0064 — TZ-aware "next occurrence of hour:minute" resolver.
86
+ // Walks today + tomorrow in the given IANA TZ; returns the first instant
87
+ // > now. Uses Intl.DateTimeFormat as the source of truth so it works on
88
+ // any host TZ (CI runs UTC, dev runs Europe/Madrid, etc.).
89
+ function nextOccurrenceInTZ(hour, minute, tz) {
90
+ try {
91
+ const now = new Date();
92
+ for (let dayOffset = 0; dayOffset <= 1; dayOffset++) {
93
+ const probe = new Date(now.getTime() + dayOffset * 86400000);
94
+ const parts = new Intl.DateTimeFormat("en-CA", {
95
+ timeZone: tz, year: "numeric", month: "2-digit", day: "2-digit",
96
+ }).formatToParts(probe).reduce((acc, p) => (p.type !== "literal" ? { ...acc, [p.type]: p.value } : acc), {});
97
+ if (!parts.year) return null;
98
+ const candidate = zonedTimeToUTC(Number(parts.year), Number(parts.month), Number(parts.day), hour, minute, tz);
99
+ if (candidate && candidate.getTime() > now.getTime()) return candidate;
100
+ }
101
+ return null;
102
+ } catch { return null; }
103
+ }
104
+
105
+ // Converts a wall-clock date + time in `tz` to a UTC Date.
106
+ // Strategy: interpret year/month/day/hour/minute as naive UTC, see what
107
+ // wall-clock that maps to in `tz`, derive the offset (tz - UTC at that
108
+ // moment) and subtract it from the naive value to land on the true UTC.
109
+ // One step suffices except at DST transitions (within ±1h, acceptable
110
+ // for cooldown timing).
111
+ function zonedTimeToUTC(year, month, day, hour, minute, tz) {
112
+ const naiveUTC = Date.UTC(year, month - 1, day, hour, minute);
113
+ const parts = new Intl.DateTimeFormat("en-CA", {
114
+ timeZone: tz, year: "numeric", month: "2-digit", day: "2-digit",
115
+ hour: "2-digit", minute: "2-digit", hour12: false,
116
+ }).formatToParts(new Date(naiveUTC)).reduce((acc, p) => (p.type !== "literal" ? { ...acc, [p.type]: p.value } : acc), {});
117
+ const wallInTZ = Date.UTC(Number(parts.year), Number(parts.month) - 1, Number(parts.day), Number(parts.hour) % 24, Number(parts.minute));
118
+ const tzOffset = wallInTZ - naiveUTC; // positive when tz is ahead of UTC
119
+ return new Date(naiveUTC - tzOffset);
120
+ }
121
+
122
+ function nextOccurrenceLocal(hour, minute) {
123
+ const target = new Date();
124
+ target.setHours(hour, minute, 0, 0);
125
+ if (target.getTime() <= Date.now()) target.setDate(target.getDate() + 1);
126
+ return target;
127
+ }
128
+
129
+ const RATE_LIMIT_PATTERNS = [
130
+ // Claude CLI
131
+ { pattern: /usage limit/i, agent: "claude" },
132
+ { pattern: /plan's usage limit/i, agent: "claude" },
133
+ { pattern: /Claude Pro usage limit/i, agent: "claude" },
134
+ { pattern: /session limit/i, agent: "claude" },
135
+ { pattern: /weekly limit/i, agent: "claude" },
136
+
137
+ // OpenAI / Codex CLI
138
+ { pattern: /exceeded your current quota/i, agent: "codex" },
139
+
140
+ // Gemini CLI
141
+ { pattern: /resource exhausted/i, agent: "gemini" },
142
+ { pattern: /quota exceeded/i, agent: "gemini" },
143
+
144
+ // Generic rate limits (match any agent)
145
+ { pattern: /rate limit/i, agent: "unknown" },
146
+ { pattern: /token limit reached/i, agent: "unknown" },
147
+ { pattern: /\b429\b/, agent: "unknown" },
148
+ { pattern: /too many requests/i, agent: "unknown" },
149
+ { pattern: /throttl/i, agent: "unknown" },
150
+
151
+ // Provider outages / transient errors (treat like rate limits → retry)
152
+ { pattern: /\b500\b.*(?:internal server error|error)/i, agent: "unknown" },
153
+ { pattern: /\b502\b|bad gateway/i, agent: "unknown" },
154
+ { pattern: /\b503\b|service unavailable/i, agent: "unknown" },
155
+ { pattern: /\b504\b|gateway timeout/i, agent: "unknown" },
156
+ { pattern: /overloaded/i, agent: "unknown" },
157
+ { pattern: /ECONNREFUSED|ECONNRESET|ETIMEDOUT|socket hang up/i, agent: "unknown" },
158
+ { pattern: /network error|fetch failed/i, agent: "unknown" },
159
+ ];
160
+
161
+ export function detectRateLimit({ stderr = "", stdout = "" }) {
162
+ const combined = `${stderr}\n${stdout}`;
163
+
164
+ const PROVIDER_OUTAGE_PATTERNS = /\b50[0-4]\b|bad gateway|service unavailable|gateway timeout|overloaded|ECONNREFUSED|ECONNRESET|ETIMEDOUT|socket hang up|network error|fetch failed/i;
165
+
166
+ for (const { pattern, agent } of RATE_LIMIT_PATTERNS) {
167
+ if (pattern.test(combined)) {
168
+ const matchedLine = combined.split("\n").find((l) => pattern.test(l)) || combined.trim();
169
+ const isProviderOutage = PROVIDER_OUTAGE_PATTERNS.test(matchedLine);
170
+ return {
171
+ isRateLimit: true,
172
+ isProviderOutage,
173
+ agent,
174
+ message: matchedLine.trim(),
175
+ ...parseCooldown(matchedLine)
176
+ };
177
+ }
178
+ }
179
+
180
+ return { isRateLimit: false, isProviderOutage: false, agent: "", message: "", cooldownUntil: null, cooldownMs: null };
181
+ }
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Repo-level cleaner — KJC-TSK-0499 PR1. Read-only collector that
3
+ * surfaces stale local artifacts so the user can decide what to drop.
4
+ * Detects: branches merged into a base ref, branches with `[gone]`
5
+ * upstream, top-level `dist/` `coverage/` `logs/` older than N days,
6
+ * `*.tmp` / `*.bak` older than N days. Never spawns destructive ops.
7
+ */
8
+ import fs from "node:fs/promises";
9
+ import path from "node:path";
10
+ import { execa } from "execa";
11
+
12
+ const DAY_MS = 24 * 60 * 60 * 1000;
13
+ const STALE_DIRS = ["dist", "coverage", "logs"];
14
+ const STALE_FILE_RX = /\.(tmp|bak)$/;
15
+
16
+ async function safeGit(cwd, args) {
17
+ try {
18
+ const { stdout, exitCode } = await execa("git", args, { cwd, reject: false });
19
+ return exitCode === 0 ? stdout : "";
20
+ } catch { return ""; }
21
+ }
22
+
23
+ async function collectBranches(cwd, baseRef) {
24
+ const items = [];
25
+ const baseShort = baseRef.replace(/^origin\//, "");
26
+ const current = (await safeGit(cwd, ["symbolic-ref", "--short", "HEAD"])).trim();
27
+ const merged = await safeGit(cwd, ["branch", "--merged", baseRef, "--format=%(refname:short)"]);
28
+ for (const raw of merged.split("\n")) {
29
+ const name = raw.trim();
30
+ if (!name || name === current || name === baseShort) continue;
31
+ items.push({ kind: "branch", target: name, reason: `merged into ${baseRef}`, command: `git branch -d ${name}` });
32
+ }
33
+ for (const line of (await safeGit(cwd, ["branch", "-vv"])).split("\n")) {
34
+ const m = line.match(/^[\s*]+(\S+)\s+\S+\s+\[[^:]+: gone\]/);
35
+ if (m && m[1] !== current) items.push({ kind: "branch-gone", target: m[1], reason: "upstream gone", command: `git branch -D ${m[1]}` });
36
+ }
37
+ return items;
38
+ }
39
+
40
+ async function ageMs(p) {
41
+ try { return Date.now() - (await fs.stat(p)).mtimeMs; } catch { return -1; }
42
+ }
43
+
44
+ async function collectStale(cwd, maxAgeDays) {
45
+ const cutoff = maxAgeDays * DAY_MS;
46
+ const items = [];
47
+ for (const name of STALE_DIRS) {
48
+ const full = path.join(cwd, name);
49
+ if (await ageMs(full) > cutoff) {
50
+ items.push({ kind: "dir", target: full, reason: `${name}/ older than ${maxAgeDays}d`, command: `rm -rf ${full}` });
51
+ }
52
+ }
53
+ let entries = [];
54
+ try { entries = await fs.readdir(cwd, { withFileTypes: true }); } catch { /* cwd unreadable */ }
55
+ for (const e of entries) {
56
+ if (!e.isFile() || !STALE_FILE_RX.test(e.name)) continue;
57
+ const full = path.join(cwd, e.name);
58
+ if (await ageMs(full) > cutoff) {
59
+ items.push({ kind: "file", target: full, reason: `${path.extname(e.name)} older than ${maxAgeDays}d`, command: `rm -f ${full}` });
60
+ }
61
+ }
62
+ return items;
63
+ }
64
+
65
+ /**
66
+ * Discover repo-level cleanup candidates.
67
+ * @param {object} opts
68
+ * @param {string} [opts.cwd=process.cwd()]
69
+ * @param {number} [opts.maxAgeDays=7]
70
+ * @param {string} [opts.mergedTo="origin/main"]
71
+ * @returns {Promise<{candidates: Array, errors: Array}>}
72
+ */
73
+ export async function collectRepoCandidates({ cwd = process.cwd(), maxAgeDays = 7, mergedTo = "origin/main" } = {}) {
74
+ const candidates = [];
75
+ const errors = [];
76
+ try { candidates.push(...await collectBranches(cwd, mergedTo)); } catch (e) { errors.push({ stage: "branches", error: String(e?.message ?? e) }); }
77
+ try { candidates.push(...await collectStale(cwd, maxAgeDays)); } catch (e) { errors.push({ stage: "stale", error: String(e?.message ?? e) }); }
78
+ return { candidates, errors };
79
+ }
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Generic retry utility with exponential backoff and jitter.
3
+ * Handles transient errors (429, 502, 503, timeouts) automatically.
4
+ */
5
+
6
+ const TRANSIENT_HTTP_CODES = new Set([408, 429, 500, 502, 503, 504]);
7
+
8
+ const TRANSIENT_ERROR_PATTERNS = [
9
+ "ETIMEDOUT", "ECONNREFUSED", "ECONNRESET", "EPIPE",
10
+ "ENETUNREACH", "EAI_AGAIN", "EHOSTUNREACH",
11
+ "socket hang up", "network error", "fetch failed"
12
+ ];
13
+
14
+ const DEFAULT_OPTIONS = {
15
+ maxAttempts: 3,
16
+ initialBackoffMs: 1000,
17
+ maxBackoffMs: 30000,
18
+ backoffMultiplier: 2,
19
+ jitterFactor: 0.1,
20
+ onRetry: null
21
+ };
22
+
23
+ export function isTransientError(error) {
24
+ if (!error) return false;
25
+
26
+ if (error.httpStatus && TRANSIENT_HTTP_CODES.has(error.httpStatus)) return true;
27
+ if (error.status && TRANSIENT_HTTP_CODES.has(error.status)) return true;
28
+
29
+ const msg = (error.message || String(error)).toLowerCase();
30
+ return TRANSIENT_ERROR_PATTERNS.some((p) => msg.includes(p.toLowerCase()));
31
+ }
32
+
33
+ export function parseRetryAfter(headerValue) {
34
+ if (!headerValue) return null;
35
+ const seconds = Number(headerValue);
36
+ if (!Number.isNaN(seconds) && seconds > 0) return seconds * 1000;
37
+
38
+ const date = Date.parse(headerValue);
39
+ if (!Number.isNaN(date)) {
40
+ const delayMs = date - Date.now();
41
+ return delayMs > 0 ? delayMs : null;
42
+ }
43
+ return null;
44
+ }
45
+
46
+ export function calculateBackoff(attempt, options = {}) {
47
+ const { initialBackoffMs = 1000, maxBackoffMs = 30000, backoffMultiplier = 2, jitterFactor = 0.1 } = options;
48
+
49
+ const base = initialBackoffMs * Math.pow(backoffMultiplier, attempt);
50
+ const capped = Math.min(base, maxBackoffMs);
51
+ const jitter = capped * jitterFactor * (Math.random() * 2 - 1);
52
+ return Math.max(0, Math.round(capped + jitter));
53
+ }
54
+
55
+ function sleep(ms) {
56
+ return new Promise((resolve) => setTimeout(resolve, ms));
57
+ }
58
+
59
+ export async function withRetry(fn, options = {}) {
60
+ const opts = { ...DEFAULT_OPTIONS, ...options };
61
+ let lastError;
62
+
63
+ for (let attempt = 0; attempt < opts.maxAttempts; attempt++) {
64
+ try {
65
+ return await fn(attempt);
66
+ } catch (error) {
67
+ lastError = error;
68
+
69
+ if (attempt >= opts.maxAttempts - 1) break;
70
+ if (!isTransientError(error)) break;
71
+
72
+ let delayMs = calculateBackoff(attempt, opts);
73
+
74
+ const retryAfterMs = parseRetryAfter(error.retryAfter || error.headers?.get?.("retry-after"));
75
+ if (retryAfterMs) {
76
+ delayMs = Math.min(retryAfterMs, opts.maxBackoffMs);
77
+ }
78
+
79
+ if (opts.onRetry) {
80
+ opts.onRetry({ attempt, error, delayMs, maxAttempts: opts.maxAttempts });
81
+ }
82
+
83
+ await sleep(delayMs);
84
+ }
85
+ }
86
+
87
+ throw lastError;
88
+ }