@oscharko-dev/keiko 0.1.0-beta.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 (450) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +7 -0
  3. package/README.md +621 -0
  4. package/TRADEMARKS.md +41 -0
  5. package/dist/audit/aggregate.d.ts +5 -0
  6. package/dist/audit/aggregate.js +25 -0
  7. package/dist/audit/build.d.ts +2 -0
  8. package/dist/audit/build.js +224 -0
  9. package/dist/audit/errors.d.ts +25 -0
  10. package/dist/audit/errors.js +39 -0
  11. package/dist/audit/index-api.d.ts +14 -0
  12. package/dist/audit/index-api.js +131 -0
  13. package/dist/audit/index.d.ts +12 -0
  14. package/dist/audit/index.js +17 -0
  15. package/dist/audit/persist.d.ts +8 -0
  16. package/dist/audit/persist.js +40 -0
  17. package/dist/audit/redaction.d.ts +3 -0
  18. package/dist/audit/redaction.js +61 -0
  19. package/dist/audit/report.d.ts +18 -0
  20. package/dist/audit/report.js +50 -0
  21. package/dist/audit/retention.d.ts +3 -0
  22. package/dist/audit/retention.js +95 -0
  23. package/dist/audit/runid.d.ts +1 -0
  24. package/dist/audit/runid.js +29 -0
  25. package/dist/audit/side-file.d.ts +12 -0
  26. package/dist/audit/side-file.js +82 -0
  27. package/dist/audit/store.d.ts +12 -0
  28. package/dist/audit/store.js +198 -0
  29. package/dist/audit/types.d.ts +188 -0
  30. package/dist/audit/types.js +8 -0
  31. package/dist/audit/workflow-evidence.d.ts +27 -0
  32. package/dist/audit/workflow-evidence.js +145 -0
  33. package/dist/cli/context.d.ts +2 -0
  34. package/dist/cli/context.js +102 -0
  35. package/dist/cli/evaluate.d.ts +7 -0
  36. package/dist/cli/evaluate.js +207 -0
  37. package/dist/cli/evidence.d.ts +8 -0
  38. package/dist/cli/evidence.js +88 -0
  39. package/dist/cli/gateway-config.d.ts +10 -0
  40. package/dist/cli/gateway-config.js +12 -0
  41. package/dist/cli/gen-tests.d.ts +7 -0
  42. package/dist/cli/gen-tests.js +208 -0
  43. package/dist/cli/index.d.ts +2 -0
  44. package/dist/cli/index.js +14 -0
  45. package/dist/cli/investigate.d.ts +8 -0
  46. package/dist/cli/investigate.js +242 -0
  47. package/dist/cli/models.d.ts +3 -0
  48. package/dist/cli/models.js +64 -0
  49. package/dist/cli/run.d.ts +7 -0
  50. package/dist/cli/run.js +187 -0
  51. package/dist/cli/runner.d.ts +6 -0
  52. package/dist/cli/runner.js +83 -0
  53. package/dist/cli/ui.d.ts +31 -0
  54. package/dist/cli/ui.js +240 -0
  55. package/dist/cli/verify.d.ts +2 -0
  56. package/dist/cli/verify.js +103 -0
  57. package/dist/evaluations/fixtures/bug-investigation/happy-path.d.ts +2 -0
  58. package/dist/evaluations/fixtures/bug-investigation/happy-path.js +66 -0
  59. package/dist/evaluations/fixtures/bug-investigation/investigation-only.d.ts +2 -0
  60. package/dist/evaluations/fixtures/bug-investigation/investigation-only.js +39 -0
  61. package/dist/evaluations/fixtures/bug-investigation/unsafe-action.d.ts +2 -0
  62. package/dist/evaluations/fixtures/bug-investigation/unsafe-action.js +37 -0
  63. package/dist/evaluations/fixtures/index.d.ts +7 -0
  64. package/dist/evaluations/fixtures/index.js +35 -0
  65. package/dist/evaluations/fixtures/support.d.ts +5 -0
  66. package/dist/evaluations/fixtures/support.js +42 -0
  67. package/dist/evaluations/fixtures/unit-tests/happy-path.d.ts +2 -0
  68. package/dist/evaluations/fixtures/unit-tests/happy-path.js +40 -0
  69. package/dist/evaluations/fixtures/unit-tests/retry-then-accept.d.ts +2 -0
  70. package/dist/evaluations/fixtures/unit-tests/retry-then-accept.js +39 -0
  71. package/dist/evaluations/fixtures/unit-tests/unsafe-action.d.ts +2 -0
  72. package/dist/evaluations/fixtures/unit-tests/unsafe-action.js +32 -0
  73. package/dist/evaluations/index.d.ts +12 -0
  74. package/dist/evaluations/index.js +12 -0
  75. package/dist/evaluations/manifest-check.d.ts +1 -0
  76. package/dist/evaluations/manifest-check.js +48 -0
  77. package/dist/evaluations/model-provider.d.ts +12 -0
  78. package/dist/evaluations/model-provider.js +26 -0
  79. package/dist/evaluations/render.d.ts +2 -0
  80. package/dist/evaluations/render.js +59 -0
  81. package/dist/evaluations/runner-support.d.ts +27 -0
  82. package/dist/evaluations/runner-support.js +163 -0
  83. package/dist/evaluations/runner.d.ts +20 -0
  84. package/dist/evaluations/runner.js +174 -0
  85. package/dist/evaluations/scorer.d.ts +14 -0
  86. package/dist/evaluations/scorer.js +131 -0
  87. package/dist/evaluations/scripted-model.d.ts +6 -0
  88. package/dist/evaluations/scripted-model.js +26 -0
  89. package/dist/evaluations/surface-parity.d.ts +2 -0
  90. package/dist/evaluations/surface-parity.js +184 -0
  91. package/dist/evaluations/types.d.ts +74 -0
  92. package/dist/evaluations/types.js +16 -0
  93. package/dist/gateway/capabilities.d.ts +11 -0
  94. package/dist/gateway/capabilities.data.d.ts +2 -0
  95. package/dist/gateway/capabilities.data.js +203 -0
  96. package/dist/gateway/capabilities.js +41 -0
  97. package/dist/gateway/config.d.ts +15 -0
  98. package/dist/gateway/config.js +154 -0
  99. package/dist/gateway/errors.d.ts +72 -0
  100. package/dist/gateway/errors.js +82 -0
  101. package/dist/gateway/gateway.d.ts +19 -0
  102. package/dist/gateway/gateway.js +94 -0
  103. package/dist/gateway/index.d.ts +10 -0
  104. package/dist/gateway/index.js +11 -0
  105. package/dist/gateway/model-selection.d.ts +9 -0
  106. package/dist/gateway/model-selection.js +36 -0
  107. package/dist/gateway/normalize.d.ts +7 -0
  108. package/dist/gateway/normalize.js +93 -0
  109. package/dist/gateway/openai-adapter.d.ts +20 -0
  110. package/dist/gateway/openai-adapter.js +263 -0
  111. package/dist/gateway/redaction.d.ts +1 -0
  112. package/dist/gateway/redaction.js +51 -0
  113. package/dist/gateway/resilience.d.ts +24 -0
  114. package/dist/gateway/resilience.js +166 -0
  115. package/dist/gateway/types.d.ts +108 -0
  116. package/dist/gateway/types.js +2 -0
  117. package/dist/harness/adapters.d.ts +23 -0
  118. package/dist/harness/adapters.js +38 -0
  119. package/dist/harness/context.d.ts +33 -0
  120. package/dist/harness/context.js +21 -0
  121. package/dist/harness/emitter.d.ts +15 -0
  122. package/dist/harness/emitter.js +72 -0
  123. package/dist/harness/errors.d.ts +21 -0
  124. package/dist/harness/errors.js +39 -0
  125. package/dist/harness/executor.d.ts +3 -0
  126. package/dist/harness/executor.js +211 -0
  127. package/dist/harness/fingerprint.d.ts +6 -0
  128. package/dist/harness/fingerprint.js +43 -0
  129. package/dist/harness/index.d.ts +9 -0
  130. package/dist/harness/index.js +13 -0
  131. package/dist/harness/loop.d.ts +3 -0
  132. package/dist/harness/loop.js +159 -0
  133. package/dist/harness/patcher.d.ts +4 -0
  134. package/dist/harness/patcher.js +49 -0
  135. package/dist/harness/planner.d.ts +3 -0
  136. package/dist/harness/planner.js +21 -0
  137. package/dist/harness/ports.d.ts +61 -0
  138. package/dist/harness/ports.js +4 -0
  139. package/dist/harness/session.d.ts +25 -0
  140. package/dist/harness/session.js +116 -0
  141. package/dist/harness/sinks.d.ts +30 -0
  142. package/dist/harness/sinks.js +72 -0
  143. package/dist/harness/tasks/explain-plan.d.ts +3 -0
  144. package/dist/harness/tasks/explain-plan.js +29 -0
  145. package/dist/harness/tasks/generate-unit-tests.d.ts +3 -0
  146. package/dist/harness/tasks/generate-unit-tests.js +28 -0
  147. package/dist/harness/tasks/investigate-bug.d.ts +3 -0
  148. package/dist/harness/tasks/investigate-bug.js +31 -0
  149. package/dist/harness/tasks/policy.d.ts +11 -0
  150. package/dist/harness/tasks/policy.js +22 -0
  151. package/dist/harness/tasks/verify.d.ts +3 -0
  152. package/dist/harness/tasks/verify.js +16 -0
  153. package/dist/harness/types.d.ts +270 -0
  154. package/dist/harness/types.js +33 -0
  155. package/dist/index.d.ts +11 -0
  156. package/dist/index.js +36 -0
  157. package/dist/sdk/index.d.ts +9 -0
  158. package/dist/sdk/index.js +37 -0
  159. package/dist/sdk/run-agent.d.ts +16 -0
  160. package/dist/sdk/run-agent.js +56 -0
  161. package/dist/tools/browser/cdp-client.d.ts +35 -0
  162. package/dist/tools/browser/cdp-client.js +218 -0
  163. package/dist/tools/browser/errors.d.ts +25 -0
  164. package/dist/tools/browser/errors.js +55 -0
  165. package/dist/tools/browser/index.d.ts +5 -0
  166. package/dist/tools/browser/index.js +6 -0
  167. package/dist/tools/browser/session.d.ts +44 -0
  168. package/dist/tools/browser/session.js +748 -0
  169. package/dist/tools/browser/types.d.ts +48 -0
  170. package/dist/tools/browser/types.js +2 -0
  171. package/dist/tools/browser/validators.d.ts +5 -0
  172. package/dist/tools/browser/validators.js +97 -0
  173. package/dist/tools/errors.d.ts +59 -0
  174. package/dist/tools/errors.js +94 -0
  175. package/dist/tools/exec.d.ts +42 -0
  176. package/dist/tools/exec.js +327 -0
  177. package/dist/tools/index.d.ts +11 -0
  178. package/dist/tools/index.js +14 -0
  179. package/dist/tools/patch-content.d.ts +10 -0
  180. package/dist/tools/patch-content.js +126 -0
  181. package/dist/tools/patch-normalize.d.ts +1 -0
  182. package/dist/tools/patch-normalize.js +80 -0
  183. package/dist/tools/patch-parse.d.ts +8 -0
  184. package/dist/tools/patch-parse.js +201 -0
  185. package/dist/tools/patch.d.ts +18 -0
  186. package/dist/tools/patch.js +403 -0
  187. package/dist/tools/registry.d.ts +36 -0
  188. package/dist/tools/registry.js +231 -0
  189. package/dist/tools/sandbox.d.ts +8 -0
  190. package/dist/tools/sandbox.js +121 -0
  191. package/dist/tools/schemas.d.ts +2 -0
  192. package/dist/tools/schemas.js +51 -0
  193. package/dist/tools/terminal-policy.d.ts +9 -0
  194. package/dist/tools/terminal-policy.js +313 -0
  195. package/dist/tools/types.d.ts +99 -0
  196. package/dist/tools/types.js +103 -0
  197. package/dist/tools/writer.d.ts +7 -0
  198. package/dist/tools/writer.js +20 -0
  199. package/dist/ui/browser.d.ts +10 -0
  200. package/dist/ui/browser.js +231 -0
  201. package/dist/ui/chat-handlers.d.ts +4 -0
  202. package/dist/ui/chat-handlers.js +281 -0
  203. package/dist/ui/csp-hashes.json +17 -0
  204. package/dist/ui/csp.d.ts +2 -0
  205. package/dist/ui/csp.js +66 -0
  206. package/dist/ui/deps.d.ts +34 -0
  207. package/dist/ui/deps.js +137 -0
  208. package/dist/ui/evidence.d.ts +27 -0
  209. package/dist/ui/evidence.js +142 -0
  210. package/dist/ui/files-deny.d.ts +2 -0
  211. package/dist/ui/files-deny.js +12 -0
  212. package/dist/ui/files.d.ts +65 -0
  213. package/dist/ui/files.js +492 -0
  214. package/dist/ui/headers.d.ts +2 -0
  215. package/dist/ui/headers.js +21 -0
  216. package/dist/ui/host-check.d.ts +2 -0
  217. package/dist/ui/host-check.js +58 -0
  218. package/dist/ui/index.d.ts +20 -0
  219. package/dist/ui/index.js +23 -0
  220. package/dist/ui/load-csp.d.ts +1 -0
  221. package/dist/ui/load-csp.js +28 -0
  222. package/dist/ui/read-handlers.d.ts +8 -0
  223. package/dist/ui/read-handlers.js +247 -0
  224. package/dist/ui/routes.d.ts +36 -0
  225. package/dist/ui/routes.js +129 -0
  226. package/dist/ui/run-engine.d.ts +20 -0
  227. package/dist/ui/run-engine.js +345 -0
  228. package/dist/ui/run-handlers.d.ts +8 -0
  229. package/dist/ui/run-handlers.js +431 -0
  230. package/dist/ui/run-request.d.ts +13 -0
  231. package/dist/ui/run-request.js +219 -0
  232. package/dist/ui/runs.d.ts +43 -0
  233. package/dist/ui/runs.js +92 -0
  234. package/dist/ui/server.d.ts +11 -0
  235. package/dist/ui/server.js +143 -0
  236. package/dist/ui/sink.d.ts +27 -0
  237. package/dist/ui/sink.js +80 -0
  238. package/dist/ui/sse.d.ts +7 -0
  239. package/dist/ui/sse.js +27 -0
  240. package/dist/ui/static/404.html +1 -0
  241. package/dist/ui/static/_next/static/ca-A01hy9W98aRvMZKdAw/_buildManifest.js +1 -0
  242. package/dist/ui/static/_next/static/ca-A01hy9W98aRvMZKdAw/_ssgManifest.js +1 -0
  243. package/dist/ui/static/_next/static/chunks/255-d47fd57964443afe.js +1 -0
  244. package/dist/ui/static/_next/static/chunks/4-be1fef693af8e088.js +1 -0
  245. package/dist/ui/static/_next/static/chunks/4bd1b696-c023c6e3521b1417.js +1 -0
  246. package/dist/ui/static/_next/static/chunks/app/_not-found/page-75825b09bcecad97.js +1 -0
  247. package/dist/ui/static/_next/static/chunks/app/launch/page-9c86a13c29884245.js +1 -0
  248. package/dist/ui/static/_next/static/chunks/app/layout-bdea63fe87947d50.js +1 -0
  249. package/dist/ui/static/_next/static/chunks/app/page-4168c12c68b7a853.js +1 -0
  250. package/dist/ui/static/_next/static/chunks/framework-a6e0b7e30f98059a.js +1 -0
  251. package/dist/ui/static/_next/static/chunks/main-778a50aebff02192.js +1 -0
  252. package/dist/ui/static/_next/static/chunks/main-app-30679af7240d63e9.js +1 -0
  253. package/dist/ui/static/_next/static/chunks/pages/_app-7d307437aca18ad4.js +1 -0
  254. package/dist/ui/static/_next/static/chunks/pages/_error-cb2a52f75f2162e2.js +1 -0
  255. package/dist/ui/static/_next/static/chunks/polyfills-42372ed130431b0a.js +1 -0
  256. package/dist/ui/static/_next/static/chunks/webpack-4a462cecab786e93.js +1 -0
  257. package/dist/ui/static/_next/static/css/be7cb54d5c5673b6.css +1 -0
  258. package/dist/ui/static/assets/editors/goland.svg +35 -0
  259. package/dist/ui/static/assets/editors/intellij.svg +39 -0
  260. package/dist/ui/static/assets/editors/pycharm.svg +58 -0
  261. package/dist/ui/static/assets/editors/rustrover.svg +19 -0
  262. package/dist/ui/static/assets/editors/vscode.svg +1 -0
  263. package/dist/ui/static/assets/editors/webstorm.svg +21 -0
  264. package/dist/ui/static/assets/icons/anthropic.svg +1 -0
  265. package/dist/ui/static/assets/icons/brave.svg +1 -0
  266. package/dist/ui/static/assets/icons/css3.svg +1 -0
  267. package/dist/ui/static/assets/icons/docker.svg +1 -0
  268. package/dist/ui/static/assets/icons/git.svg +1 -0
  269. package/dist/ui/static/assets/icons/github.svg +1 -0
  270. package/dist/ui/static/assets/icons/go.svg +1 -0
  271. package/dist/ui/static/assets/icons/gradle.svg +1 -0
  272. package/dist/ui/static/assets/icons/grafana.svg +1 -0
  273. package/dist/ui/static/assets/icons/graphql.svg +1 -0
  274. package/dist/ui/static/assets/icons/html5.svg +1 -0
  275. package/dist/ui/static/assets/icons/image.svg +1 -0
  276. package/dist/ui/static/assets/icons/java.svg +1 -0
  277. package/dist/ui/static/assets/icons/javascript.svg +1 -0
  278. package/dist/ui/static/assets/icons/json.svg +1 -0
  279. package/dist/ui/static/assets/icons/kafka.svg +1 -0
  280. package/dist/ui/static/assets/icons/kubernetes.svg +1 -0
  281. package/dist/ui/static/assets/icons/linear.svg +1 -0
  282. package/dist/ui/static/assets/icons/markdown.svg +1 -0
  283. package/dist/ui/static/assets/icons/nginx.svg +1 -0
  284. package/dist/ui/static/assets/icons/nodejs.svg +1 -0
  285. package/dist/ui/static/assets/icons/notion.svg +1 -0
  286. package/dist/ui/static/assets/icons/openai.svg +1 -0
  287. package/dist/ui/static/assets/icons/playwright.svg +1 -0
  288. package/dist/ui/static/assets/icons/postgresql.svg +1 -0
  289. package/dist/ui/static/assets/icons/prometheus.svg +1 -0
  290. package/dist/ui/static/assets/icons/properties.svg +1 -0
  291. package/dist/ui/static/assets/icons/puppeteer.svg +1 -0
  292. package/dist/ui/static/assets/icons/python.svg +1 -0
  293. package/dist/ui/static/assets/icons/react.svg +1 -0
  294. package/dist/ui/static/assets/icons/redis.svg +1 -0
  295. package/dist/ui/static/assets/icons/rust.svg +1 -0
  296. package/dist/ui/static/assets/icons/sentry.svg +1 -0
  297. package/dist/ui/static/assets/icons/slack.svg +1 -0
  298. package/dist/ui/static/assets/icons/spring.svg +1 -0
  299. package/dist/ui/static/assets/icons/typescript.svg +1 -0
  300. package/dist/ui/static/assets/icons/upstash.svg +1 -0
  301. package/dist/ui/static/assets/icons/yaml.svg +1 -0
  302. package/dist/ui/static/assets/keiko-logo.svg +10 -0
  303. package/dist/ui/static/index.html +1 -0
  304. package/dist/ui/static/index.txt +19 -0
  305. package/dist/ui/static/keiko-logo.svg +10 -0
  306. package/dist/ui/static/launch.html +1 -0
  307. package/dist/ui/static/launch.txt +19 -0
  308. package/dist/ui/static.d.ts +3 -0
  309. package/dist/ui/static.js +72 -0
  310. package/dist/ui/store/chats.d.ts +14 -0
  311. package/dist/ui/store/chats.js +110 -0
  312. package/dist/ui/store/db.d.ts +6 -0
  313. package/dist/ui/store/db.js +182 -0
  314. package/dist/ui/store/errors.d.ts +12 -0
  315. package/dist/ui/store/errors.js +30 -0
  316. package/dist/ui/store/index.d.ts +6 -0
  317. package/dist/ui/store/index.js +6 -0
  318. package/dist/ui/store/messages.d.ts +5 -0
  319. package/dist/ui/store/messages.js +137 -0
  320. package/dist/ui/store/paths.d.ts +4 -0
  321. package/dist/ui/store/paths.js +69 -0
  322. package/dist/ui/store/projects.d.ts +7 -0
  323. package/dist/ui/store/projects.js +61 -0
  324. package/dist/ui/store/schema.d.ts +3 -0
  325. package/dist/ui/store/schema.js +77 -0
  326. package/dist/ui/store/types.d.ts +80 -0
  327. package/dist/ui/store/types.js +3 -0
  328. package/dist/ui/store/validation.d.ts +4 -0
  329. package/dist/ui/store/validation.js +72 -0
  330. package/dist/ui/store-handlers.d.ts +16 -0
  331. package/dist/ui/store-handlers.js +465 -0
  332. package/dist/ui/terminal-errors.d.ts +21 -0
  333. package/dist/ui/terminal-errors.js +45 -0
  334. package/dist/ui/terminal-evidence.d.ts +20 -0
  335. package/dist/ui/terminal-evidence.js +65 -0
  336. package/dist/ui/terminal-routes.d.ts +9 -0
  337. package/dist/ui/terminal-routes.js +219 -0
  338. package/dist/ui/terminal.d.ts +67 -0
  339. package/dist/ui/terminal.js +835 -0
  340. package/dist/verification/classify.d.ts +10 -0
  341. package/dist/verification/classify.js +53 -0
  342. package/dist/verification/detect.d.ts +4 -0
  343. package/dist/verification/detect.js +81 -0
  344. package/dist/verification/errors.d.ts +11 -0
  345. package/dist/verification/errors.js +21 -0
  346. package/dist/verification/index.d.ts +17 -0
  347. package/dist/verification/index.js +13 -0
  348. package/dist/verification/limits.d.ts +3 -0
  349. package/dist/verification/limits.js +40 -0
  350. package/dist/verification/monitor.d.ts +4 -0
  351. package/dist/verification/monitor.js +58 -0
  352. package/dist/verification/orchestrator.d.ts +16 -0
  353. package/dist/verification/orchestrator.js +363 -0
  354. package/dist/verification/plan.d.ts +9 -0
  355. package/dist/verification/plan.js +125 -0
  356. package/dist/verification/summary.d.ts +40 -0
  357. package/dist/verification/summary.js +67 -0
  358. package/dist/verification/types.d.ts +63 -0
  359. package/dist/verification/types.js +13 -0
  360. package/dist/workflows/bug-investigation/context.d.ts +7 -0
  361. package/dist/workflows/bug-investigation/context.js +119 -0
  362. package/dist/workflows/bug-investigation/descriptor.d.ts +3 -0
  363. package/dist/workflows/bug-investigation/descriptor.js +46 -0
  364. package/dist/workflows/bug-investigation/emit.d.ts +12 -0
  365. package/dist/workflows/bug-investigation/emit.js +35 -0
  366. package/dist/workflows/bug-investigation/events.d.ts +81 -0
  367. package/dist/workflows/bug-investigation/events.js +9 -0
  368. package/dist/workflows/bug-investigation/failure-parse.d.ts +3 -0
  369. package/dist/workflows/bug-investigation/failure-parse.js +154 -0
  370. package/dist/workflows/bug-investigation/guard.d.ts +2 -0
  371. package/dist/workflows/bug-investigation/guard.js +69 -0
  372. package/dist/workflows/bug-investigation/index.d.ts +7 -0
  373. package/dist/workflows/bug-investigation/index.js +13 -0
  374. package/dist/workflows/bug-investigation/internal.d.ts +37 -0
  375. package/dist/workflows/bug-investigation/internal.js +64 -0
  376. package/dist/workflows/bug-investigation/model-loop.d.ts +4 -0
  377. package/dist/workflows/bug-investigation/model-loop.js +223 -0
  378. package/dist/workflows/bug-investigation/parse.d.ts +3 -0
  379. package/dist/workflows/bug-investigation/parse.js +123 -0
  380. package/dist/workflows/bug-investigation/prompt.d.ts +4 -0
  381. package/dist/workflows/bug-investigation/prompt.js +107 -0
  382. package/dist/workflows/bug-investigation/report.d.ts +23 -0
  383. package/dist/workflows/bug-investigation/report.js +151 -0
  384. package/dist/workflows/bug-investigation/stages.d.ts +13 -0
  385. package/dist/workflows/bug-investigation/stages.js +242 -0
  386. package/dist/workflows/bug-investigation/types.d.ts +91 -0
  387. package/dist/workflows/bug-investigation/types.js +14 -0
  388. package/dist/workflows/bug-investigation/verify-stage.d.ts +10 -0
  389. package/dist/workflows/bug-investigation/verify-stage.js +91 -0
  390. package/dist/workflows/bug-investigation/workflow.d.ts +2 -0
  391. package/dist/workflows/bug-investigation/workflow.js +74 -0
  392. package/dist/workflows/descriptor.d.ts +20 -0
  393. package/dist/workflows/descriptor.js +8 -0
  394. package/dist/workflows/index.d.ts +3 -0
  395. package/dist/workflows/index.js +2 -0
  396. package/dist/workflows/unit-tests/context.d.ts +7 -0
  397. package/dist/workflows/unit-tests/context.js +129 -0
  398. package/dist/workflows/unit-tests/conventions.d.ts +4 -0
  399. package/dist/workflows/unit-tests/conventions.js +87 -0
  400. package/dist/workflows/unit-tests/descriptor.d.ts +4 -0
  401. package/dist/workflows/unit-tests/descriptor.js +43 -0
  402. package/dist/workflows/unit-tests/emit.d.ts +12 -0
  403. package/dist/workflows/unit-tests/emit.js +35 -0
  404. package/dist/workflows/unit-tests/events.d.ts +78 -0
  405. package/dist/workflows/unit-tests/events.js +7 -0
  406. package/dist/workflows/unit-tests/index.d.ts +6 -0
  407. package/dist/workflows/unit-tests/index.js +10 -0
  408. package/dist/workflows/unit-tests/internal.d.ts +35 -0
  409. package/dist/workflows/unit-tests/internal.js +43 -0
  410. package/dist/workflows/unit-tests/model-loop.d.ts +4 -0
  411. package/dist/workflows/unit-tests/model-loop.js +95 -0
  412. package/dist/workflows/unit-tests/parse.d.ts +6 -0
  413. package/dist/workflows/unit-tests/parse.js +68 -0
  414. package/dist/workflows/unit-tests/prompt.d.ts +4 -0
  415. package/dist/workflows/unit-tests/prompt.js +71 -0
  416. package/dist/workflows/unit-tests/report.d.ts +21 -0
  417. package/dist/workflows/unit-tests/report.js +90 -0
  418. package/dist/workflows/unit-tests/stages.d.ts +9 -0
  419. package/dist/workflows/unit-tests/stages.js +155 -0
  420. package/dist/workflows/unit-tests/types.d.ts +70 -0
  421. package/dist/workflows/unit-tests/types.js +11 -0
  422. package/dist/workflows/unit-tests/verify-stage.d.ts +9 -0
  423. package/dist/workflows/unit-tests/verify-stage.js +56 -0
  424. package/dist/workflows/unit-tests/workflow.d.ts +2 -0
  425. package/dist/workflows/unit-tests/workflow.js +58 -0
  426. package/dist/workspace/contextPack.d.ts +9 -0
  427. package/dist/workspace/contextPack.js +94 -0
  428. package/dist/workspace/detect.d.ts +3 -0
  429. package/dist/workspace/detect.js +135 -0
  430. package/dist/workspace/discovery.d.ts +9 -0
  431. package/dist/workspace/discovery.js +167 -0
  432. package/dist/workspace/errors.d.ts +39 -0
  433. package/dist/workspace/errors.js +66 -0
  434. package/dist/workspace/fs.d.ts +21 -0
  435. package/dist/workspace/fs.js +36 -0
  436. package/dist/workspace/ignore.d.ts +14 -0
  437. package/dist/workspace/ignore.js +176 -0
  438. package/dist/workspace/index.d.ts +11 -0
  439. package/dist/workspace/index.js +13 -0
  440. package/dist/workspace/paths.d.ts +2 -0
  441. package/dist/workspace/paths.js +38 -0
  442. package/dist/workspace/realpath.d.ts +7 -0
  443. package/dist/workspace/realpath.js +72 -0
  444. package/dist/workspace/retrieval.d.ts +9 -0
  445. package/dist/workspace/retrieval.js +74 -0
  446. package/dist/workspace/summary.d.ts +3 -0
  447. package/dist/workspace/summary.js +54 -0
  448. package/dist/workspace/types.d.ts +103 -0
  449. package/dist/workspace/types.js +27 -0
  450. package/package.json +58 -0
@@ -0,0 +1,431 @@
1
+ // The five run-engine BFF endpoints (ADR-0011 D5 routes 5–9). POST /api/runs starts a dry-run-first
2
+ // run in the background and returns 202 {runId, fingerprint}; the SSE route replays the bounded ring
3
+ // buffer (respecting Last-Event-ID) then streams live redacted events, closing after the terminal
4
+ // event; cancel propagates to the underlying harness/workflow AbortController; GET returns the
5
+ // redacted final report projection (or status:"running"); apply is the ONLY write path, re-invoking
6
+ // the same workflow with apply:true through the existing gated path. No model is ever called
7
+ // directly; no guard is reimplemented; no secret reaches any response (live payloads are redacted).
8
+ import { randomUUID } from "node:crypto";
9
+ import { parseRunRequest } from "./run-request.js";
10
+ import { startRun, applyRun } from "./run-engine.js";
11
+ import { ActiveRunLimitError } from "./runs.js";
12
+ import { SSE_HEADERS, writeEvent, readyMessage } from "./sse.js";
13
+ import { errorBody, STREAMING } from "./routes.js";
14
+ import { UiStoreError } from "./store/index.js";
15
+ const MAX_BODY_BYTES = 1_000_000;
16
+ const VERIFY_NOOP_MODEL = {
17
+ call: () => Promise.reject(new Error("verify runs must not call the model")),
18
+ };
19
+ // Sentinel thrown (and caught in handleCreateRun) when the body exceeds MAX_BODY_BYTES. Using a
20
+ // typed class avoids fragile string matching and clearly separates this case from I/O errors.
21
+ class BodyTooLargeError extends Error {
22
+ constructor() {
23
+ super("request body too large");
24
+ this.name = "BodyTooLargeError";
25
+ }
26
+ }
27
+ // Reads the request body up to a byte cap (a bounded read protects the loopback BFF from an
28
+ // oversized body). Resolves the decoded UTF-8 text, or rejects with BodyTooLargeError past the cap.
29
+ // When the cap is exceeded the stream is switched to flowing/drain mode (req.resume) so Node.js
30
+ // continues consuming the socket data and the HTTP server can still write the 413 response over
31
+ // the same connection (FIX H). The chunks array is cleared at that point to free accumulated memory.
32
+ function readBody(req) {
33
+ return new Promise((resolve, reject) => {
34
+ const chunks = [];
35
+ let total = 0;
36
+ let capped = false;
37
+ req.on("data", (chunk) => {
38
+ total += chunk.length;
39
+ if (total > MAX_BODY_BYTES) {
40
+ if (!capped) {
41
+ capped = true;
42
+ chunks.length = 0; // release accumulated buffers before draining
43
+ reject(new BodyTooLargeError());
44
+ req.resume(); // drain without buffering; lets the server write the 413 response
45
+ }
46
+ return;
47
+ }
48
+ chunks.push(chunk);
49
+ });
50
+ req.on("end", () => {
51
+ if (!capped) {
52
+ resolve(Buffer.concat(chunks).toString("utf8"));
53
+ }
54
+ });
55
+ req.on("error", reject);
56
+ });
57
+ }
58
+ // Composer-launched runs operate on the host filesystem. Reject workspaceRoot paths not registered
59
+ // in the local project store so a CSRF-equipped local client cannot trigger workflows in arbitrary
60
+ // directories. Returns a RouteResult to return, or null when the check passes.
61
+ function rejectUnregisteredWorkspace(parsed, deps) {
62
+ const root = typeof parsed.input.workspaceRoot === "string" ? parsed.input.workspaceRoot : "";
63
+ const registered = deps.store.listProjects().some((p) => p.path === root);
64
+ return registered
65
+ ? null
66
+ : { status: 403, body: errorBody("WORKSPACE_NOT_REGISTERED", "The workspaceRoot is not a registered project.") };
67
+ }
68
+ function resolveRunModel(parsed, deps) {
69
+ return parsed.kind === "verify" ? VERIFY_NOOP_MODEL : deps.modelPortFactory(parsed.modelId);
70
+ }
71
+ function isRecord(value) {
72
+ return typeof value === "object" && value !== null && !Array.isArray(value);
73
+ }
74
+ function requireBodyString(body, name) {
75
+ const value = body[name];
76
+ if (typeof value !== "string" || value.length === 0) {
77
+ return { status: 400, body: errorBody("BAD_REQUEST", `Field "${name}" is required.`) };
78
+ }
79
+ return value;
80
+ }
81
+ function requireBodyNumber(body, name) {
82
+ const value = body[name];
83
+ if (typeof value !== "number" || !Number.isFinite(value)) {
84
+ return {
85
+ status: 400,
86
+ body: errorBody("BAD_REQUEST", `Field "${name}" must be a finite number.`),
87
+ };
88
+ }
89
+ return value;
90
+ }
91
+ function requireBodyRecord(body, name) {
92
+ const value = body[name];
93
+ if (!isRecord(value)) {
94
+ return { status: 400, body: errorBody("BAD_REQUEST", `Field "${name}" must be an object.`) };
95
+ }
96
+ return value;
97
+ }
98
+ function parseJsonRecord(raw) {
99
+ let parsed;
100
+ try {
101
+ parsed = JSON.parse(raw);
102
+ }
103
+ catch {
104
+ return { status: 400, body: errorBody("BAD_REQUEST", "Request body is not valid JSON.") };
105
+ }
106
+ if (!isRecord(parsed)) {
107
+ return { status: 400, body: errorBody("BAD_REQUEST", "Request body must be a JSON object.") };
108
+ }
109
+ return parsed;
110
+ }
111
+ function isRouteResult(value) {
112
+ return isRecord(value) && typeof value.status === "number" && "body" in value;
113
+ }
114
+ function chatBelongsToProject(deps, projectPath, chatId) {
115
+ return deps.store.listChats(projectPath).some((chat) => chat.id === chatId);
116
+ }
117
+ function runSummaryDiscriminator(request) {
118
+ if (request.kind === "unit-tests") {
119
+ return { workflowId: "unit-test-generation", taskType: undefined };
120
+ }
121
+ if (request.kind === "bug-investigation") {
122
+ return { workflowId: "bug-investigation", taskType: undefined };
123
+ }
124
+ if (request.kind === "explain-plan") {
125
+ return { workflowId: undefined, taskType: "explain-plan" };
126
+ }
127
+ return { workflowId: undefined, taskType: "verify" };
128
+ }
129
+ function buildChatRunMessages(body, request, chatId, runId) {
130
+ const user = requireBodyRecord(body, "user");
131
+ if (isRouteResult(user))
132
+ return user;
133
+ const summary = requireBodyRecord(body, "summary");
134
+ if (isRouteResult(summary))
135
+ return summary;
136
+ const userContent = requireBodyString(user, "content");
137
+ if (typeof userContent !== "string")
138
+ return userContent;
139
+ const userTimestamp = requireBodyNumber(user, "timestamp");
140
+ if (typeof userTimestamp !== "number")
141
+ return userTimestamp;
142
+ const summaryContent = requireBodyString(summary, "content");
143
+ if (typeof summaryContent !== "string")
144
+ return summaryContent;
145
+ const summaryTimestamp = requireBodyNumber(summary, "timestamp");
146
+ if (typeof summaryTimestamp !== "number")
147
+ return summaryTimestamp;
148
+ const discriminator = runSummaryDiscriminator(request);
149
+ return [
150
+ {
151
+ chatId,
152
+ role: "user",
153
+ content: userContent,
154
+ timestamp: userTimestamp,
155
+ runId: undefined,
156
+ workflowId: undefined,
157
+ workflowStatus: undefined,
158
+ shortResult: undefined,
159
+ taskType: undefined,
160
+ },
161
+ {
162
+ chatId,
163
+ role: "system",
164
+ content: summaryContent,
165
+ timestamp: summaryTimestamp,
166
+ runId,
167
+ workflowId: discriminator.workflowId,
168
+ workflowStatus: "running",
169
+ shortResult: undefined,
170
+ taskType: discriminator.taskType,
171
+ },
172
+ ];
173
+ }
174
+ function storeErrorResult(error) {
175
+ return { status: error.status, body: errorBody(error.code, error.message) };
176
+ }
177
+ function markSummaryFailed(deps, message, shortResult) {
178
+ try {
179
+ deps.store.updateMessage(message.id, { workflowStatus: "failed", shortResult });
180
+ }
181
+ catch {
182
+ // Best-effort compensation only. The original start error remains the response source.
183
+ }
184
+ }
185
+ function parseChatRunEnvelope(raw, deps) {
186
+ const body = parseJsonRecord(raw);
187
+ if (isRouteResult(body))
188
+ return body;
189
+ const chatId = requireBodyString(body, "chatId");
190
+ if (isRouteResult(chatId))
191
+ return chatId;
192
+ const projectPath = requireBodyString(body, "projectPath");
193
+ if (isRouteResult(projectPath))
194
+ return projectPath;
195
+ if (!chatBelongsToProject(deps, projectPath, chatId)) {
196
+ return { status: 404, body: errorBody("NOT_FOUND", "Chat not found.") };
197
+ }
198
+ const runBody = requireBodyRecord(body, "run");
199
+ if (isRouteResult(runBody))
200
+ return runBody;
201
+ return { body, chatId, projectPath, runBody };
202
+ }
203
+ function validateChatRunRequest(runBody, deps) {
204
+ const parsed = parseRunRequest(JSON.stringify(runBody));
205
+ if ("code" in parsed) {
206
+ return { status: 400, body: errorBody(parsed.code, parsed.message) };
207
+ }
208
+ const unregistered = rejectUnregisteredWorkspace(parsed, deps);
209
+ if (unregistered !== null)
210
+ return unregistered;
211
+ const model = resolveRunModel(parsed, deps);
212
+ return model === undefined
213
+ ? { status: 400, body: errorBody("NO_MODEL", "No model provider is configured.") }
214
+ : { request: parsed, model };
215
+ }
216
+ function engineContextFor(deps, request, model) {
217
+ return {
218
+ request,
219
+ model,
220
+ registry: deps.registry,
221
+ evidence: {
222
+ store: deps.evidenceStore,
223
+ env: deps.env,
224
+ additionalSecrets: deps.redactionSecrets ?? [],
225
+ },
226
+ };
227
+ }
228
+ function persistChatRunMessages(deps, envelope, request, runId) {
229
+ const messagesInput = buildChatRunMessages(envelope.body, request, envelope.chatId, runId);
230
+ if (isRouteResult(messagesInput))
231
+ return messagesInput;
232
+ try {
233
+ return deps.store.createMessages(messagesInput);
234
+ }
235
+ catch (error) {
236
+ if (error instanceof UiStoreError)
237
+ return storeErrorResult(error);
238
+ throw error;
239
+ }
240
+ }
241
+ function startPersistedChatRun(deps, request, model, runId, messages) {
242
+ try {
243
+ const run = startRun(engineContextFor(deps, request, model), deps.redactor, { runId });
244
+ return { status: 202, body: { run, messages } };
245
+ }
246
+ catch (error) {
247
+ const summary = messages[1];
248
+ if (summary !== undefined) {
249
+ markSummaryFailed(deps, summary, "Run could not be started.");
250
+ }
251
+ if (error instanceof ActiveRunLimitError) {
252
+ return { status: 429, body: errorBody("TOO_MANY_RUNS", "The active run limit is reached.") };
253
+ }
254
+ throw error;
255
+ }
256
+ }
257
+ // Route 5 — POST /api/runs. Validates the body, resolves the ModelPort, starts the run, returns 202.
258
+ export async function handleCreateRun(ctx, deps) {
259
+ let raw;
260
+ try {
261
+ raw = await readBody(ctx.req);
262
+ }
263
+ catch (error) {
264
+ if (error instanceof BodyTooLargeError) {
265
+ return {
266
+ status: 413,
267
+ body: errorBody("PAYLOAD_TOO_LARGE", "Request body exceeds the size limit."),
268
+ };
269
+ }
270
+ throw error;
271
+ }
272
+ const parsed = parseRunRequest(raw);
273
+ if ("code" in parsed) {
274
+ return { status: 400, body: errorBody(parsed.code, parsed.message) };
275
+ }
276
+ const unregistered = rejectUnregisteredWorkspace(parsed, deps);
277
+ if (unregistered !== null) {
278
+ return unregistered;
279
+ }
280
+ const model = resolveRunModel(parsed, deps);
281
+ if (model === undefined) {
282
+ return { status: 400, body: errorBody("NO_MODEL", "No model provider is configured.") };
283
+ }
284
+ const engineCtx = {
285
+ request: parsed,
286
+ model,
287
+ registry: deps.registry,
288
+ evidence: {
289
+ store: deps.evidenceStore,
290
+ env: deps.env,
291
+ additionalSecrets: deps.redactionSecrets ?? [],
292
+ },
293
+ };
294
+ try {
295
+ const started = startRun(engineCtx, deps.redactor);
296
+ return { status: 202, body: { runId: started.runId, fingerprint: started.fingerprint } };
297
+ }
298
+ catch (error) {
299
+ if (error instanceof ActiveRunLimitError) {
300
+ return { status: 429, body: errorBody("TOO_MANY_RUNS", "The active run limit is reached.") };
301
+ }
302
+ throw error;
303
+ }
304
+ }
305
+ // Route — POST /api/chats/runs. Composer-specific path that makes Issue #66's chat invariant
306
+ // explicit: a successful workflow launch first reserves a runId and persists exactly one user
307
+ // message plus one system run summary, then starts the run with that reserved runId. If persistence
308
+ // fails, no run is started; if the start is refused, the summary is terminalized as failed.
309
+ export async function handleCreateChatRun(ctx, deps) {
310
+ let raw;
311
+ try {
312
+ raw = await readBody(ctx.req);
313
+ }
314
+ catch (error) {
315
+ if (error instanceof BodyTooLargeError) {
316
+ return {
317
+ status: 413,
318
+ body: errorBody("PAYLOAD_TOO_LARGE", "Request body exceeds the size limit."),
319
+ };
320
+ }
321
+ throw error;
322
+ }
323
+ const envelope = parseChatRunEnvelope(raw, deps);
324
+ if (isRouteResult(envelope))
325
+ return envelope;
326
+ const validated = validateChatRunRequest(envelope.runBody, deps);
327
+ if (isRouteResult(validated))
328
+ return validated;
329
+ const runId = randomUUID();
330
+ const messages = persistChatRunMessages(deps, envelope, validated.request, runId);
331
+ if (isRouteResult(messages))
332
+ return messages;
333
+ return startPersistedChatRun(deps, validated.request, validated.model, runId, messages);
334
+ }
335
+ function lastEventId(req) {
336
+ const header = req.headers["last-event-id"];
337
+ const value = Array.isArray(header) ? header[0] : header;
338
+ if (value === undefined) {
339
+ return -1;
340
+ }
341
+ const parsed = Number.parseInt(value, 10);
342
+ return Number.isFinite(parsed) ? parsed : -1;
343
+ }
344
+ // Route 6 — GET /api/runs/:runId/events (SSE). Replays the ring buffer (after Last-Event-ID), sends
345
+ // `ready`, then streams live events, closing after the run terminates. The writer is detached on
346
+ // client disconnect to avoid leaks and unbounded fan-out.
347
+ export function handleRunEvents(ctx, deps) {
348
+ const record = deps.registry.get(ctx.params.runId ?? "");
349
+ if (record === undefined) {
350
+ return { status: 404, body: errorBody("NOT_FOUND", "Unknown run.") };
351
+ }
352
+ openSseStream(ctx.res, record, lastEventId(ctx.req), deps.redactor);
353
+ ctx.req.on("close", () => {
354
+ ctx.res.end();
355
+ });
356
+ return STREAMING;
357
+ }
358
+ function openSseStream(res, record, afterSeq, redactor) {
359
+ res.writeHead(200, SSE_HEADERS);
360
+ const writer = {
361
+ write: (event) => {
362
+ const accepted = writeEvent(res, event, redactor);
363
+ if (!accepted) {
364
+ res.destroy();
365
+ }
366
+ return accepted;
367
+ },
368
+ close: () => {
369
+ res.end();
370
+ },
371
+ };
372
+ const detach = record.sink.attach(writer, afterSeq);
373
+ res.write(readyMessage());
374
+ res.on("close", detach);
375
+ if (record.sink.isTerminated()) {
376
+ detach();
377
+ res.end();
378
+ }
379
+ }
380
+ // Route 7 — POST /api/runs/:runId/cancel. Idempotent; 404 unknown.
381
+ export function handleCancelRun(ctx, deps) {
382
+ const record = deps.registry.get(ctx.params.runId ?? "");
383
+ if (record === undefined) {
384
+ return { status: 404, body: errorBody("NOT_FOUND", "Unknown run.") };
385
+ }
386
+ record.cancel("cancelled via UI");
387
+ return { status: 200, body: { ok: true } };
388
+ }
389
+ // Route 8 — GET /api/runs/:runId. Final redacted report projection, or status:"running".
390
+ export function handleGetRun(ctx, deps) {
391
+ const record = deps.registry.get(ctx.params.runId ?? "");
392
+ if (record === undefined) {
393
+ return { status: 404, body: errorBody("NOT_FOUND", "Unknown run.") };
394
+ }
395
+ if (record.status === "running") {
396
+ return { status: 200, body: { report: { status: "running" } } };
397
+ }
398
+ return { status: 200, body: { report: reportWithApply(record.report, record.applyReport, record.appliedAt) } };
399
+ }
400
+ function reportWithApply(report, applyReport, appliedAt) {
401
+ if (applyReport === undefined || appliedAt === undefined) {
402
+ return report;
403
+ }
404
+ if (!isRecord(report)) {
405
+ return { report, applyReport, appliedAt };
406
+ }
407
+ return { ...report, applyReport, appliedAt };
408
+ }
409
+ // Route 9 — POST /api/runs/:runId/apply. The ONLY write path. 404 unknown; 409 when not in an
410
+ // appliable (dry-run-success) state; otherwise re-invokes the gated workflow with apply:true.
411
+ export async function handleApplyRun(ctx, deps) {
412
+ const record = deps.registry.get(ctx.params.runId ?? "");
413
+ if (record === undefined) {
414
+ return { status: 404, body: errorBody("NOT_FOUND", "Unknown run.") };
415
+ }
416
+ if (record.appliable === undefined) {
417
+ return {
418
+ status: 409,
419
+ body: errorBody("NOT_APPLIABLE", "The run is not in an appliable state."),
420
+ };
421
+ }
422
+ const model = deps.modelPortFactory(record.modelId);
423
+ if (model === undefined) {
424
+ return { status: 400, body: errorBody("NO_MODEL", "No model provider is configured.") };
425
+ }
426
+ const report = await applyRun(record.appliable, model, record.modelId, deps.redactor);
427
+ record.appliable = undefined;
428
+ record.applyReport = report;
429
+ record.appliedAt = Date.now();
430
+ return { status: 200, body: { report: reportWithApply(record.report, record.applyReport, record.appliedAt) } };
431
+ }
@@ -0,0 +1,13 @@
1
+ export type RunKind = "unit-tests" | "bug-investigation" | "explain-plan" | "verify";
2
+ export interface RunRequest {
3
+ readonly kind: RunKind;
4
+ readonly modelId: string;
5
+ readonly apply: boolean;
6
+ readonly input: Record<string, unknown>;
7
+ readonly limits: Record<string, unknown> | undefined;
8
+ }
9
+ export interface RunRequestError {
10
+ readonly code: "BAD_REQUEST";
11
+ readonly message: string;
12
+ }
13
+ export declare function parseRunRequest(raw: string): RunRequest | RunRequestError;
@@ -0,0 +1,219 @@
1
+ // Parsing + validation of the `POST /api/runs` request body (ADR-0011 D5 route 5). The body arrives
2
+ // as untyped JSON; this module narrows it (no `any`) into a typed `RunRequest` or a typed validation
3
+ // error. It performs SHAPE validation only — exactly one of workflowId/taskType, a present input
4
+ // object, a non-empty modelId, and a selected project workspaceRoot. The create route is ALWAYS
5
+ // dry-run: `apply` is forced false here regardless of the body, so applying is reachable only via
6
+ // the gated apply route (D8). The deeper guards (`isSensitivePath`, patch limits, target validation)
7
+ // are enforced by the workflow/harness entry points the engine calls; the BFF never reimplements
8
+ // them.
9
+ function isRecord(value) {
10
+ return typeof value === "object" && value !== null && !Array.isArray(value);
11
+ }
12
+ function resolveKind(body) {
13
+ const workflowId = body.workflowId;
14
+ const taskType = body.taskType;
15
+ const hasWorkflow = workflowId !== undefined;
16
+ const hasTask = taskType !== undefined;
17
+ if (hasWorkflow === hasTask) {
18
+ return { code: "BAD_REQUEST", message: "Provide exactly one of workflowId or taskType." };
19
+ }
20
+ if (hasWorkflow) {
21
+ if (workflowId === "unit-test-generation") {
22
+ return "unit-tests";
23
+ }
24
+ if (workflowId === "bug-investigation") {
25
+ return "bug-investigation";
26
+ }
27
+ return { code: "BAD_REQUEST", message: "Unknown workflowId." };
28
+ }
29
+ if (taskType === "explain-plan") {
30
+ return "explain-plan";
31
+ }
32
+ if (taskType === "verify") {
33
+ return "verify";
34
+ }
35
+ return { code: "BAD_REQUEST", message: "Unsupported taskType." };
36
+ }
37
+ function validateWorkspaceRoot(input) {
38
+ const workspaceRoot = input.workspaceRoot;
39
+ if (typeof workspaceRoot !== "string" || workspaceRoot.length === 0) {
40
+ return { code: "BAD_REQUEST", message: "A non-empty workspaceRoot is required." };
41
+ }
42
+ return null;
43
+ }
44
+ function validateStringField(input, name, label) {
45
+ const value = input[name];
46
+ if (typeof value !== "string" || value.length === 0) {
47
+ return { code: "BAD_REQUEST", message: `${label} must be a non-empty string.` };
48
+ }
49
+ return null;
50
+ }
51
+ function validateOptionalStringField(input, name, label) {
52
+ const value = input[name];
53
+ if (value === undefined) {
54
+ return null;
55
+ }
56
+ if (typeof value !== "string") {
57
+ return { code: "BAD_REQUEST", message: `${label} must be a string.` };
58
+ }
59
+ return null;
60
+ }
61
+ function validateStringArray(value, label, options = {
62
+ required: false,
63
+ allowEmpty: true,
64
+ }) {
65
+ if (value === undefined) {
66
+ return options.required
67
+ ? { code: "BAD_REQUEST", message: `${label} must be a string array.` }
68
+ : null;
69
+ }
70
+ if (!Array.isArray(value)) {
71
+ return { code: "BAD_REQUEST", message: `${label} must be a string array.` };
72
+ }
73
+ if (!options.allowEmpty && value.length === 0) {
74
+ return { code: "BAD_REQUEST", message: `${label} must contain at least one entry.` };
75
+ }
76
+ for (const entry of value) {
77
+ if (typeof entry !== "string" || entry.length === 0) {
78
+ return { code: "BAD_REQUEST", message: `${label} must contain non-empty strings.` };
79
+ }
80
+ }
81
+ return null;
82
+ }
83
+ // Verify shape: targetFiles (when present) must be a string[] of non-empty entries. The deeper
84
+ // guards (path containment, script detection) run inside the verification orchestrator; the BFF
85
+ // only validates shape here.
86
+ function validateVerifyInput(input) {
87
+ return validateStringArray(input.targetFiles, "verify targetFiles");
88
+ }
89
+ function validateExplainPlanInput(input) {
90
+ return (validateStringField(input, "filePath", "explain-plan filePath") ??
91
+ validateOptionalStringField(input, "question", "explain-plan question"));
92
+ }
93
+ function validateUnitTestTarget(target) {
94
+ const kind = target.kind;
95
+ if (kind === "file") {
96
+ return (validateStringField(target, "filePath", "unit-test target.filePath") ??
97
+ validateOptionalStringField(target, "targetFunction", "unit-test target.targetFunction"));
98
+ }
99
+ if (kind === "module") {
100
+ return validateStringField(target, "moduleDir", "unit-test target.moduleDir");
101
+ }
102
+ if (kind === "changedFiles") {
103
+ return validateStringArray(target.filePaths, "unit-test target.filePaths", {
104
+ required: true,
105
+ allowEmpty: false,
106
+ });
107
+ }
108
+ return {
109
+ code: "BAD_REQUEST",
110
+ message: "unit-test target.kind must be one of file, module, changedFiles.",
111
+ };
112
+ }
113
+ function validateUnitTestsInput(input) {
114
+ const target = input.target;
115
+ if (!isRecord(target)) {
116
+ return { code: "BAD_REQUEST", message: "unit-test target must be an object." };
117
+ }
118
+ return validateUnitTestTarget(target);
119
+ }
120
+ function validateBugReport(report) {
121
+ const descriptionError = validateOptionalStringField(report, "description", "bug report.description");
122
+ if (descriptionError !== null) {
123
+ return descriptionError;
124
+ }
125
+ const failingOutputError = validateOptionalStringField(report, "failingOutput", "bug report.failingOutput");
126
+ if (failingOutputError !== null) {
127
+ return failingOutputError;
128
+ }
129
+ const stackTraceError = validateOptionalStringField(report, "stackTrace", "bug report.stackTrace");
130
+ if (stackTraceError !== null) {
131
+ return stackTraceError;
132
+ }
133
+ const targetFilesError = validateStringArray(report.targetFiles, "bug report.targetFiles");
134
+ if (targetFilesError !== null) {
135
+ return targetFilesError;
136
+ }
137
+ return hasBugEvidence(report)
138
+ ? null
139
+ : {
140
+ code: "BAD_REQUEST",
141
+ message: "bug report requires at least one of description, failingOutput, stackTrace, or targetFiles.",
142
+ };
143
+ }
144
+ function hasNonEmptyString(value) {
145
+ return typeof value === "string" && value.trim().length > 0;
146
+ }
147
+ function hasNonEmptyStringEntry(value) {
148
+ return Array.isArray(value) && value.some(hasNonEmptyString);
149
+ }
150
+ function hasBugEvidence(report) {
151
+ return (hasNonEmptyString(report.description) ||
152
+ hasNonEmptyString(report.failingOutput) ||
153
+ hasNonEmptyString(report.stackTrace) ||
154
+ hasNonEmptyStringEntry(report.targetFiles));
155
+ }
156
+ function validateBugInvestigationInput(input) {
157
+ const report = input.report;
158
+ if (!isRecord(report)) {
159
+ return { code: "BAD_REQUEST", message: "bug report must be an object." };
160
+ }
161
+ return validateBugReport(report);
162
+ }
163
+ function validateRunInput(kind, input) {
164
+ const workspaceRootError = validateWorkspaceRoot(input);
165
+ if (workspaceRootError !== null) {
166
+ return workspaceRootError;
167
+ }
168
+ if (kind === "verify") {
169
+ return validateVerifyInput(input);
170
+ }
171
+ if (kind === "explain-plan") {
172
+ return validateExplainPlanInput(input);
173
+ }
174
+ if (kind === "unit-tests") {
175
+ return validateUnitTestsInput(input);
176
+ }
177
+ return validateBugInvestigationInput(input);
178
+ }
179
+ // Parses the raw JSON text into a validated RunRequest, or a typed BAD_REQUEST error.
180
+ export function parseRunRequest(raw) {
181
+ let parsed;
182
+ try {
183
+ parsed = JSON.parse(raw);
184
+ }
185
+ catch {
186
+ return { code: "BAD_REQUEST", message: "Request body is not valid JSON." };
187
+ }
188
+ if (!isRecord(parsed)) {
189
+ return { code: "BAD_REQUEST", message: "Request body must be a JSON object." };
190
+ }
191
+ const kind = resolveKind(parsed);
192
+ if (typeof kind !== "string") {
193
+ return kind;
194
+ }
195
+ const modelId = parsed.modelId;
196
+ if (typeof modelId !== "string" || modelId.length === 0) {
197
+ return { code: "BAD_REQUEST", message: "A non-empty modelId is required." };
198
+ }
199
+ const input = parsed.input;
200
+ if (!isRecord(input)) {
201
+ return { code: "BAD_REQUEST", message: "An input object is required." };
202
+ }
203
+ const inputError = validateRunInput(kind, input);
204
+ if (inputError !== null) {
205
+ return inputError;
206
+ }
207
+ const limits = parsed.limits;
208
+ return {
209
+ kind,
210
+ modelId,
211
+ // Dry-run-first (ADR-0011 D8 / security M1): the create route NEVER applies, even if the client
212
+ // body carries `apply:true`. Applying is the sole responsibility of POST /api/runs/:runId/apply
213
+ // (route 9), which re-invokes the workflow through the gated path. A one-shot create-with-apply
214
+ // would bypass the explicit review→apply step, so the body flag is deliberately ignored here.
215
+ apply: false,
216
+ input,
217
+ ...(isRecord(limits) ? { limits } : { limits: undefined }),
218
+ };
219
+ }