@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,56 @@
1
+ // The verification stage (ADR-0008 D5, steering note C). Runs ONLY after a successful apply: it
2
+ // resolves a verification plan by deriving the just-created test from the target SOURCE file(s) via
3
+ // #7 resolveTargetedTests, falling back to the full `test` script, and records an explicit skip
4
+ // reason when neither resolves or the framework is unknown. Verification reuses the #7 orchestrator
5
+ // unchanged; this stage only wires the plan and projects an output-text-free audit summary.
6
+ import { nodeSpawnFn } from "../../tools/index.js";
7
+ import {} from "../../workspace/index.js";
8
+ import { buildVerificationPlan, detectScripts, resolveTargetedTests, runVerification, summarizeForAudit, DEFAULT_VERIFICATION_LIMITS, } from "../../verification/index.js";
9
+ export const SKIP_UNRESOLVED = "verification skipped: framework unknown or no test files resolved";
10
+ // The source files the target points at — passed to resolveTargetedTests so it can find the
11
+ // just-created sibling/mirrored test (steering note C). Test files are NOT passed here.
12
+ function targetSourceFiles(target) {
13
+ if (target.kind === "file") {
14
+ return [target.filePath];
15
+ }
16
+ if (target.kind === "changedFiles") {
17
+ return target.filePaths;
18
+ }
19
+ return [target.moduleDir];
20
+ }
21
+ function buildPlanFallback(workspace, fs) {
22
+ const catalog = detectScripts(workspace, fs);
23
+ return buildVerificationPlan(workspace, catalog, { only: ["test"] }, fs);
24
+ }
25
+ function resolveVerificationPlan(workspace, target, fs) {
26
+ const targeted = resolveTargetedTests(workspace, targetSourceFiles(target), fs, DEFAULT_VERIFICATION_LIMITS);
27
+ if (targeted.length > 0) {
28
+ return { workspaceRoot: workspace.root, steps: targeted };
29
+ }
30
+ const fallback = buildPlanFallback(workspace, fs);
31
+ const runnable = fallback.steps.filter((step) => step.skipReason === undefined);
32
+ return runnable.length > 0 ? { workspaceRoot: workspace.root, steps: runnable } : undefined;
33
+ }
34
+ export async function runWorkflowVerification(state, workspace, fs) {
35
+ const plan = resolveVerificationPlan(workspace, state.input.target, fs);
36
+ if (plan === undefined) {
37
+ return { summary: undefined, skipReason: SKIP_UNRESOLVED };
38
+ }
39
+ const report = await runVerification(plan, {
40
+ workspace,
41
+ signal: state.signal,
42
+ spawn: state.deps.spawn ?? nodeSpawnFn,
43
+ processEnv: state.deps.processEnv ?? process.env,
44
+ now: state.now,
45
+ fs,
46
+ });
47
+ const summary = summarizeForAudit(report);
48
+ state.emitter.emit({
49
+ type: "workflow:verification:result",
50
+ overallStatus: summary.overallStatus,
51
+ stepCount: summary.results.length,
52
+ passedCount: summary.results.filter((r) => r.status === "passed").length,
53
+ durationMs: summary.durationMs,
54
+ });
55
+ return { summary, skipReason: undefined };
56
+ }
@@ -0,0 +1,2 @@
1
+ import type { UnitTestWorkflowDeps, UnitTestWorkflowInput, UnitTestWorkflowReport } from "./types.js";
2
+ export declare function generateUnitTests(input: UnitTestWorkflowInput, deps: UnitTestWorkflowDeps): Promise<UnitTestWorkflowReport>;
@@ -0,0 +1,58 @@
1
+ // The single public entry: generateUnitTests (ADR-0008 D2/D5/D6). A deterministic linear pipeline
2
+ // (NOT the harness loop): intake -> detect -> context -> conventions -> [prompt -> model -> parse ->
3
+ // validate -> production-guard] (bounded retries) -> [dry-run | apply -> verify] -> report. It
4
+ // composes #3-#7 UNCHANGED and emits redacted progress events. The model loop, verify stage, and
5
+ // report stages live in sibling files to keep each under the LOC limit; this file owns only the
6
+ // stage sequencing and the single top-level catch boundary that maps an unexpected IO failure to a
7
+ // redacted "failed" report (and a CancelledError to a "cancelled" report).
8
+ import { CancelledError } from "../../gateway/errors.js";
9
+ import { detectWorkspace } from "../../workspace/index.js";
10
+ import { nodeWorkspaceFs } from "../../workspace/fs.js";
11
+ import { buildTestGenContext } from "./context.js";
12
+ import { detectConventions } from "./conventions.js";
13
+ import { computeFingerprint } from "./emit.js";
14
+ import { runModelLoop } from "./model-loop.js";
15
+ import { cancelledReport, emitCompleted, failedReport, finishPipeline } from "./stages.js";
16
+ import { buildRunState, EMPTY_LOOP } from "./internal.js";
17
+ async function runPipeline(state) {
18
+ const fs = state.deps.fs ?? nodeWorkspaceFs;
19
+ const workspace = detectWorkspace(state.input.workspaceRoot, fs);
20
+ state.emitter.emit({
21
+ type: "workflow:started",
22
+ workflowId: "unit-test-generation",
23
+ modelId: state.input.modelId,
24
+ applyEnabled: state.input.apply === true,
25
+ limits: state.limits,
26
+ });
27
+ const pack = buildTestGenContext(workspace, state.input, state.limits, { fs });
28
+ const conventions = detectConventions(workspace, pack);
29
+ state.emitter.emit({
30
+ type: "conventions:detected",
31
+ framework: conventions.framework,
32
+ testDirs: conventions.testDirs,
33
+ fileNamingStyle: conventions.fileNamingStyle,
34
+ });
35
+ state.emitter.emit({
36
+ type: "context:selected",
37
+ entryCount: pack.selected.length,
38
+ usedBytes: pack.usedBytes,
39
+ budgetBytes: pack.budgetBytes,
40
+ droppedForBudget: pack.droppedForBudget,
41
+ });
42
+ const loop = await runModelLoop(state, workspace, conventions, pack);
43
+ return finishPipeline(state, workspace, loop);
44
+ }
45
+ export async function generateUnitTests(input, deps) {
46
+ const state = buildRunState(input, deps, computeFingerprint(input.target, input.modelId));
47
+ let report;
48
+ try {
49
+ report = await runPipeline(state);
50
+ }
51
+ catch (error) {
52
+ report =
53
+ error instanceof CancelledError
54
+ ? cancelledReport(state, EMPTY_LOOP, undefined)
55
+ : failedReport(state, error);
56
+ }
57
+ return emitCompleted(state, report);
58
+ }
@@ -0,0 +1,9 @@
1
+ import { type WorkspaceFs } from "./fs.js";
2
+ import { type RetrievalStrategy } from "./retrieval.js";
3
+ import { type ContextPack, type ContextRequest, type DiscoveredFile, type WorkspaceInfo } from "./types.js";
4
+ export interface ContextPackDeps {
5
+ readonly fs: WorkspaceFs;
6
+ readonly strategy: RetrievalStrategy;
7
+ }
8
+ export declare function buildContextPack(workspace: WorkspaceInfo, request: ContextRequest, deps?: ContextPackDeps): ContextPack;
9
+ export declare function buildContextPackFromFiles(workspace: WorkspaceInfo, request: ContextRequest, candidates: readonly DiscoveredFile[], deps?: ContextPackDeps): ContextPack;
@@ -0,0 +1,94 @@
1
+ // Deterministic, explainable context-pack assembly (ADR-0005 D4). Resolution order:
2
+ // discover -> filter (deny/ignore/boundary, already applied by discovery) -> rank by an
3
+ // explainable category heuristic (selectionReason, stable path tie-break) -> greedily add
4
+ // excerpts until the byte budget is exhausted -> truncate each to maxBytesPerFile and
5
+ // redact() it -> record per-entry metadata.
6
+ // No clock, no RNG: the same workspace + request always yields the same pack.
7
+ import { nodeWorkspaceFs } from "./fs.js";
8
+ import { discoverFiles, readWorkspaceFile } from "./discovery.js";
9
+ import { lexicalRetrievalStrategy } from "./retrieval.js";
10
+ import { DEFAULT_READ_OPTIONS, } from "./types.js";
11
+ const DEFAULT_DEPS = {
12
+ fs: nodeWorkspaceFs,
13
+ strategy: lexicalRetrievalStrategy,
14
+ };
15
+ function utf8Bytes(value) {
16
+ return Buffer.byteLength(value, "utf8");
17
+ }
18
+ // Clamps a string to at most `maxBytes` UTF-8 bytes without splitting a multi-byte char.
19
+ function clampToBytes(text, maxBytes) {
20
+ if (maxBytes <= 0) {
21
+ return { excerpt: "", truncated: true };
22
+ }
23
+ if (utf8Bytes(text) <= maxBytes) {
24
+ return { excerpt: text, truncated: false };
25
+ }
26
+ const buffer = Buffer.from(text, "utf8").subarray(0, maxBytes);
27
+ const excerpt = new TextDecoder("utf-8", { fatal: false }).decode(buffer).replace(/�+$/u, "");
28
+ return { excerpt, truncated: true };
29
+ }
30
+ // The read cap is the file-size safety ceiling, NOT the per-file excerpt budget: a large
31
+ // file is read up to the ceiling and then excerpted by clampToBytes. Using the small
32
+ // per-file budget here would wrongly reject any file bigger than the excerpt size.
33
+ function readEntry(workspace, relPath, request, deps) {
34
+ const readCap = Math.max(request.maxBytesPerFile, DEFAULT_READ_OPTIONS.maxBytes);
35
+ try {
36
+ const content = readWorkspaceFile(workspace, relPath, { maxBytes: readCap }, deps.fs);
37
+ return { text: content.text, sizeBytes: content.sizeBytes, truncated: content.truncated };
38
+ }
39
+ catch {
40
+ // A file that is too large, denied, or unreadable is simply not packed.
41
+ return null;
42
+ }
43
+ }
44
+ function buildEntry(workspace, ranked, request, deps, remaining) {
45
+ const content = readEntry(workspace, ranked.file.relativePath, request, deps);
46
+ if (content === null) {
47
+ return null;
48
+ }
49
+ const perFileCap = Math.min(request.maxBytesPerFile, remaining);
50
+ const { excerpt, truncated } = clampToBytes(content.text, perFileCap);
51
+ if (excerpt.length === 0) {
52
+ return null;
53
+ }
54
+ return {
55
+ path: ranked.file.relativePath,
56
+ sizeBytes: content.sizeBytes,
57
+ excerptBytes: utf8Bytes(excerpt),
58
+ selectionReason: ranked.selectionReason,
59
+ truncated: truncated || content.truncated,
60
+ excerpt,
61
+ };
62
+ }
63
+ function tryAddEntry(workspace, ranked, request, deps, state) {
64
+ const remaining = request.budgetBytes - state.usedBytes;
65
+ if (remaining <= 0) {
66
+ state.droppedForBudget += 1;
67
+ return;
68
+ }
69
+ const entry = buildEntry(workspace, ranked, request, deps, remaining);
70
+ if (entry === null) {
71
+ return;
72
+ }
73
+ state.entries.push(entry);
74
+ state.usedBytes += entry.excerptBytes;
75
+ }
76
+ export function buildContextPack(workspace, request, deps = DEFAULT_DEPS) {
77
+ const candidates = discoverFiles(workspace, request.discovery, deps.fs);
78
+ return buildContextPackFromFiles(workspace, request, candidates, deps);
79
+ }
80
+ export function buildContextPackFromFiles(workspace, request, candidates, deps = DEFAULT_DEPS) {
81
+ const ranked = deps.strategy.rank(candidates, request.task);
82
+ const state = { entries: [], usedBytes: 0, droppedForBudget: 0 };
83
+ for (const item of ranked) {
84
+ tryAddEntry(workspace, item, request, deps, state);
85
+ }
86
+ return {
87
+ workspaceRoot: workspace.root,
88
+ totalCandidates: candidates.length,
89
+ selected: state.entries,
90
+ usedBytes: state.usedBytes,
91
+ budgetBytes: request.budgetBytes,
92
+ droppedForBudget: state.droppedForBudget,
93
+ };
94
+ }
@@ -0,0 +1,3 @@
1
+ import { type WorkspaceFs } from "./fs.js";
2
+ import type { WorkspaceInfo } from "./types.js";
3
+ export declare function detectWorkspace(startDir: string, fs?: WorkspaceFs): WorkspaceInfo;
@@ -0,0 +1,135 @@
1
+ // Workspace detection: walk up from a start directory to the nearest root containing a
2
+ // `.git` entry or a `package.json`, then read safe metadata. The only JSON parse in the
3
+ // module is wrapped in a single try/catch at this IO boundary (ADR-0005). No clock, no RNG.
4
+ import { dirname, join, relative, resolve } from "node:path";
5
+ import { nodeWorkspaceFs } from "./fs.js";
6
+ import { WorkspaceNotFoundError } from "./errors.js";
7
+ import { isDenied } from "./ignore.js";
8
+ import { assertContainedRealPath } from "./realpath.js";
9
+ const MARKERS = [".git", "package.json"];
10
+ function isRoot(dir, fs) {
11
+ return MARKERS.some((marker) => fs.exists(join(dir, marker)));
12
+ }
13
+ function findRoot(startDir, fs) {
14
+ let current = resolve(startDir);
15
+ // Bounded by the filesystem: dirname() reaches a fixed point at the volume root.
16
+ for (;;) {
17
+ if (isRoot(current, fs)) {
18
+ return current;
19
+ }
20
+ const parent = dirname(current);
21
+ if (parent === current) {
22
+ throw new WorkspaceNotFoundError(`no workspace root (.git or package.json) found above ${startDir}`, startDir);
23
+ }
24
+ current = parent;
25
+ }
26
+ }
27
+ function asString(value) {
28
+ return typeof value === "string" ? value : undefined;
29
+ }
30
+ function isRecord(value) {
31
+ return typeof value === "object" && value !== null && !Array.isArray(value);
32
+ }
33
+ function depKeys(value) {
34
+ return isRecord(value) ? Object.keys(value) : [];
35
+ }
36
+ function detectFramework(record) {
37
+ const names = new Set([
38
+ ...depKeys(record.devDependencies),
39
+ ...depKeys(record.dependencies),
40
+ ]);
41
+ if (names.has("vitest")) {
42
+ return "vitest";
43
+ }
44
+ if (names.has("jest")) {
45
+ return "jest";
46
+ }
47
+ if (names.has("mocha")) {
48
+ return "mocha";
49
+ }
50
+ return "unknown";
51
+ }
52
+ const EMPTY_META = { name: undefined, version: undefined, testFramework: "unknown" };
53
+ function toRelative(root, absolutePath) {
54
+ return relative(root, absolutePath).split("\\").join("/");
55
+ }
56
+ function toRealRelative(root, fs, absolutePath) {
57
+ try {
58
+ return toRelative(fs.realPath(root), absolutePath);
59
+ }
60
+ catch {
61
+ return toRelative(root, absolutePath);
62
+ }
63
+ }
64
+ function readContainedText(root, path, fs) {
65
+ if (!fs.exists(path)) {
66
+ return undefined;
67
+ }
68
+ try {
69
+ const containedPath = assertContainedRealPath(fs, root, path, path);
70
+ if (isDenied(toRealRelative(root, fs, containedPath))) {
71
+ return undefined;
72
+ }
73
+ return fs.readFileUtf8(containedPath);
74
+ }
75
+ catch {
76
+ return undefined;
77
+ }
78
+ }
79
+ function readPackageMeta(root, fs) {
80
+ const path = join(root, "package.json");
81
+ try {
82
+ const raw = readContainedText(root, path, fs);
83
+ if (raw === undefined) {
84
+ return EMPTY_META;
85
+ }
86
+ const parsed = JSON.parse(raw);
87
+ if (!isRecord(parsed)) {
88
+ return EMPTY_META;
89
+ }
90
+ return {
91
+ name: asString(parsed.name),
92
+ version: asString(parsed.version),
93
+ testFramework: detectFramework(parsed),
94
+ };
95
+ }
96
+ catch {
97
+ return EMPTY_META;
98
+ }
99
+ }
100
+ function readIgnoreLines(root, fs) {
101
+ const path = join(root, ".gitignore");
102
+ const raw = readContainedText(root, path, fs);
103
+ if (raw === undefined) {
104
+ return [];
105
+ }
106
+ return raw.split(/\r?\n/);
107
+ }
108
+ function isExistingDir(absolutePath, fs) {
109
+ return fs.exists(absolutePath) && fs.stat(absolutePath).isDirectory;
110
+ }
111
+ function detectDirs(root, fs, candidates) {
112
+ return candidates.filter((dir) => isExistingDir(join(root, dir), fs));
113
+ }
114
+ function detectLanguages(root, fs) {
115
+ const languages = [];
116
+ if (fs.exists(join(root, "tsconfig.json"))) {
117
+ languages.push("typescript");
118
+ }
119
+ languages.push("javascript");
120
+ return languages;
121
+ }
122
+ export function detectWorkspace(startDir, fs = nodeWorkspaceFs) {
123
+ const root = findRoot(startDir, fs);
124
+ const meta = readPackageMeta(root, fs);
125
+ return {
126
+ root,
127
+ name: meta.name,
128
+ version: meta.version,
129
+ testFramework: meta.testFramework,
130
+ sourceDirs: detectDirs(root, fs, ["src"]),
131
+ testDirs: detectDirs(root, fs, ["tests", "test", "__tests__"]),
132
+ languages: detectLanguages(root, fs),
133
+ ignoreLines: readIgnoreLines(root, fs),
134
+ };
135
+ }
@@ -0,0 +1,9 @@
1
+ import { type WorkspaceFs } from "./fs.js";
2
+ import { type DiscoveredFile, type DiscoveryOptions, type DiscoveryStats, type FileContent, type ReadOptions, type WorkspaceInfo } from "./types.js";
3
+ export interface DiscoveryResult {
4
+ readonly files: readonly DiscoveredFile[];
5
+ readonly stats: DiscoveryStats;
6
+ }
7
+ export declare function discoverFiles(workspace: WorkspaceInfo, opts: DiscoveryOptions, fs?: WorkspaceFs): readonly DiscoveredFile[];
8
+ export declare function discoverWithStats(workspace: WorkspaceInfo, opts: DiscoveryOptions, fs?: WorkspaceFs): DiscoveryResult;
9
+ export declare function readWorkspaceFile(workspace: WorkspaceInfo, relPath: string, opts?: ReadOptions, fs?: WorkspaceFs): FileContent;
@@ -0,0 +1,167 @@
1
+ // Recursive, bounded, deterministic file discovery and a single boundary-checked read path.
2
+ // Security invariants (ADR-0005 D2/D3):
3
+ // - every directory descent and every read goes through resolveWithinWorkspace first;
4
+ // - always-on DENY patterns are applied before the optional .gitignore subset;
5
+ // - a symlink whose realpath escapes the root is skipped (never followed);
6
+ // - recursion is capped by maxDepth and total results by maxFiles.
7
+ import { relative } from "node:path";
8
+ import { nodeWorkspaceFs, } from "./fs.js";
9
+ import { compileIgnore, isDenied, isIgnored } from "./ignore.js";
10
+ import { resolveWithinWorkspace } from "./paths.js";
11
+ import { assertContainedRealPath } from "./realpath.js";
12
+ import { FileTooLargeError, PathDeniedError, WorkspaceReadError } from "./errors.js";
13
+ import { redact } from "../gateway/redaction.js";
14
+ import { DEFAULT_READ_OPTIONS, } from "./types.js";
15
+ function toRelative(root, absolutePath) {
16
+ return relative(root, absolutePath).split("\\").join("/");
17
+ }
18
+ function toRealRelative(fs, root, absolutePath) {
19
+ try {
20
+ return toRelative(fs.realPath(root), absolutePath);
21
+ }
22
+ catch {
23
+ return toRelative(root, absolutePath);
24
+ }
25
+ }
26
+ // Returns false when the entry must be skipped for any security or noise reason, recording
27
+ // which tier rejected it for the discovery stats.
28
+ function isAllowed(walk, relPath, isDir) {
29
+ if (isDenied(relPath)) {
30
+ walk.denied += 1;
31
+ return false;
32
+ }
33
+ if (walk.applyGitignore && isIgnored(walk.matcher, relPath, isDir)) {
34
+ walk.ignored += 1;
35
+ return false;
36
+ }
37
+ return true;
38
+ }
39
+ function childRelative(root, absoluteDir, name) {
40
+ const dirRel = toRelative(root, absoluteDir);
41
+ return dirRel === "" ? name : `${dirRel}/${name}`;
42
+ }
43
+ function readDirSafe(walk, absoluteDir) {
44
+ try {
45
+ return walk.fs.readDir(absoluteDir);
46
+ }
47
+ catch {
48
+ return [];
49
+ }
50
+ }
51
+ function statSize(walk, absolutePath) {
52
+ try {
53
+ return walk.fs.stat(absolutePath).size;
54
+ }
55
+ catch {
56
+ return 0;
57
+ }
58
+ }
59
+ function handleEntry(walk, absoluteDir, entry, depth) {
60
+ const childAbs = resolveWithinWorkspace(walk.root, childRelative(walk.root, absoluteDir, entry.name));
61
+ const relPath = toRelative(walk.root, childAbs);
62
+ if (!isAllowed(walk, relPath, entry.isDirectory)) {
63
+ return;
64
+ }
65
+ // Symlinks are skipped unconditionally (for safety/simplicity). A non-symlink entry that
66
+ // reports neither isFile nor isDirectory is likewise treated as non-traversable noise.
67
+ // Only genuine files and directories are walked.
68
+ if (entry.isSymbolicLink) {
69
+ return;
70
+ }
71
+ if (entry.isDirectory) {
72
+ descend(walk, childAbs, depth + 1);
73
+ return;
74
+ }
75
+ if (entry.isFile) {
76
+ walk.out.push({ relativePath: relPath, sizeBytes: statSize(walk, childAbs) });
77
+ }
78
+ }
79
+ function descend(walk, absoluteDir, depth) {
80
+ if (depth > walk.opts.maxDepth || walk.out.length >= walk.opts.maxFiles) {
81
+ return;
82
+ }
83
+ const entries = [...readDirSafe(walk, absoluteDir)].sort((a, b) => (a.name < b.name ? -1 : 1));
84
+ for (const entry of entries) {
85
+ if (walk.out.length >= walk.opts.maxFiles) {
86
+ return;
87
+ }
88
+ handleEntry(walk, absoluteDir, entry, depth);
89
+ }
90
+ }
91
+ function runWalk(workspace, opts, fs) {
92
+ const walk = {
93
+ fs,
94
+ root: workspace.root,
95
+ matcher: compileIgnore(workspace.ignoreLines),
96
+ opts,
97
+ applyGitignore: opts.applyGitignore,
98
+ out: [],
99
+ denied: 0,
100
+ ignored: 0,
101
+ };
102
+ descend(walk, resolveWithinWorkspace(workspace.root, "."), 0);
103
+ return walk;
104
+ }
105
+ export function discoverFiles(workspace, opts, fs = nodeWorkspaceFs) {
106
+ return runWalk(workspace, opts, fs).out;
107
+ }
108
+ export function discoverWithStats(workspace, opts, fs = nodeWorkspaceFs) {
109
+ const walk = runWalk(workspace, opts, fs);
110
+ return {
111
+ files: walk.out,
112
+ stats: { discovered: walk.out.length, denied: walk.denied, ignored: walk.ignored },
113
+ };
114
+ }
115
+ function describe(error) {
116
+ return error instanceof Error ? error.message : "unknown error";
117
+ }
118
+ function statFile(fs, absolutePath, relPath) {
119
+ try {
120
+ return fs.stat(absolutePath);
121
+ }
122
+ catch (error) {
123
+ throw new WorkspaceReadError(`cannot stat file: ${relPath} (${describe(error)})`, relPath);
124
+ }
125
+ }
126
+ function assertNoHardLinkAlias(stats, relPath) {
127
+ if (stats.hardLinkCount !== undefined && stats.hardLinkCount > 1) {
128
+ throw new PathDeniedError(`refusing to read a hard-linked workspace alias: ${relPath}`, relPath);
129
+ }
130
+ }
131
+ function readContent(fs, absolutePath, relPath, opts) {
132
+ let raw;
133
+ try {
134
+ raw = fs.readFileUtf8(absolutePath);
135
+ }
136
+ catch (error) {
137
+ throw new WorkspaceReadError(`cannot read file: ${relPath} (${describe(error)})`, relPath);
138
+ }
139
+ const rawBytes = Buffer.byteLength(raw, "utf8");
140
+ const truncated = rawBytes > opts.maxBytes;
141
+ const text = truncated
142
+ ? Buffer.from(raw, "utf8").subarray(0, opts.maxBytes).toString("utf8")
143
+ : raw;
144
+ return { relativePath: relPath, sizeBytes: rawBytes, text: redact(text), truncated };
145
+ }
146
+ // The single read path. Order: boundary -> deny -> realpath containment -> size cap -> read -> redact.
147
+ // Realpath containment is shared with the write/cwd paths via assertContainedRealPath: when the
148
+ // path does not exist, it validates the nearest existing parent and returns absolutePath, so a
149
+ // missing in-root file still surfaces as a WorkspaceReadError (not a false PathEscapeError).
150
+ export function readWorkspaceFile(workspace, relPath, opts = DEFAULT_READ_OPTIONS, fs = nodeWorkspaceFs) {
151
+ const absolutePath = resolveWithinWorkspace(workspace.root, relPath);
152
+ const normalizedRel = toRelative(workspace.root, absolutePath);
153
+ if (isDenied(normalizedRel)) {
154
+ throw new PathDeniedError(`refusing to read a denied path: ${normalizedRel}`, normalizedRel);
155
+ }
156
+ const resolvedPath = assertContainedRealPath(fs, workspace.root, absolutePath, normalizedRel);
157
+ const resolvedRel = toRealRelative(fs, workspace.root, resolvedPath);
158
+ if (isDenied(resolvedRel)) {
159
+ throw new PathDeniedError(`refusing to read a denied path: ${normalizedRel}`, normalizedRel);
160
+ }
161
+ const stats = statFile(fs, resolvedPath, normalizedRel);
162
+ assertNoHardLinkAlias(stats, normalizedRel);
163
+ if (stats.size > opts.maxBytes) {
164
+ throw new FileTooLargeError(`file exceeds the read cap: ${normalizedRel}`, normalizedRel, stats.size, opts.maxBytes);
165
+ }
166
+ return readContent(fs, resolvedPath, normalizedRel, opts);
167
+ }
@@ -0,0 +1,39 @@
1
+ export declare const WORKSPACE_CODES: {
2
+ readonly PATH_ESCAPE: "WORKSPACE_PATH_ESCAPE";
3
+ readonly PATH_DENIED: "WORKSPACE_PATH_DENIED";
4
+ readonly NOT_FOUND: "WORKSPACE_NOT_FOUND";
5
+ readonly FILE_TOO_LARGE: "WORKSPACE_FILE_TOO_LARGE";
6
+ readonly READ_FAILED: "WORKSPACE_READ_FAILED";
7
+ };
8
+ export type WorkspaceCode = (typeof WORKSPACE_CODES)[keyof typeof WORKSPACE_CODES];
9
+ export declare abstract class WorkspaceError extends Error {
10
+ abstract readonly code: WorkspaceCode;
11
+ constructor(message: string, secrets?: readonly string[]);
12
+ }
13
+ export declare class PathEscapeError extends WorkspaceError {
14
+ readonly code: "WORKSPACE_PATH_ESCAPE";
15
+ readonly requestedPath: string;
16
+ constructor(message: string, requestedPath: string, secrets?: readonly string[]);
17
+ }
18
+ export declare class PathDeniedError extends WorkspaceError {
19
+ readonly code: "WORKSPACE_PATH_DENIED";
20
+ readonly requestedPath: string;
21
+ constructor(message: string, requestedPath: string, secrets?: readonly string[]);
22
+ }
23
+ export declare class WorkspaceNotFoundError extends WorkspaceError {
24
+ readonly code: "WORKSPACE_NOT_FOUND";
25
+ readonly startDir: string;
26
+ constructor(message: string, startDir: string, secrets?: readonly string[]);
27
+ }
28
+ export declare class FileTooLargeError extends WorkspaceError {
29
+ readonly code: "WORKSPACE_FILE_TOO_LARGE";
30
+ readonly requestedPath: string;
31
+ readonly sizeBytes: number;
32
+ readonly limitBytes: number;
33
+ constructor(message: string, requestedPath: string, sizeBytes: number, limitBytes: number, secrets?: readonly string[]);
34
+ }
35
+ export declare class WorkspaceReadError extends WorkspaceError {
36
+ readonly code: "WORKSPACE_READ_FAILED";
37
+ readonly requestedPath: string;
38
+ constructor(message: string, requestedPath: string, secrets?: readonly string[]);
39
+ }
@@ -0,0 +1,66 @@
1
+ // Workspace error taxonomy, mirroring the gateway/harness pattern (ADR-0003, ADR-0004).
2
+ // Errors carry a stable `code` discriminant; callers switch on `code`, never parse
3
+ // `message`. Every message is redacted at construction so errors are always safe to log.
4
+ import { redact } from "../gateway/redaction.js";
5
+ export const WORKSPACE_CODES = {
6
+ PATH_ESCAPE: "WORKSPACE_PATH_ESCAPE",
7
+ PATH_DENIED: "WORKSPACE_PATH_DENIED",
8
+ NOT_FOUND: "WORKSPACE_NOT_FOUND",
9
+ FILE_TOO_LARGE: "WORKSPACE_FILE_TOO_LARGE",
10
+ READ_FAILED: "WORKSPACE_READ_FAILED",
11
+ };
12
+ export class WorkspaceError extends Error {
13
+ constructor(message, secrets = []) {
14
+ super(redact(message, secrets));
15
+ this.name = new.target.name;
16
+ }
17
+ }
18
+ // Raised when a candidate path escapes the workspace root (NUL, `..`, or absolute escape).
19
+ export class PathEscapeError extends WorkspaceError {
20
+ code = WORKSPACE_CODES.PATH_ESCAPE;
21
+ requestedPath;
22
+ constructor(message, requestedPath, secrets = []) {
23
+ super(message, secrets);
24
+ this.requestedPath = requestedPath;
25
+ }
26
+ }
27
+ // Raised when a path matches an always-on deny pattern (secrets, deps, build, vcs).
28
+ export class PathDeniedError extends WorkspaceError {
29
+ code = WORKSPACE_CODES.PATH_DENIED;
30
+ requestedPath;
31
+ constructor(message, requestedPath, secrets = []) {
32
+ super(message, secrets);
33
+ this.requestedPath = requestedPath;
34
+ }
35
+ }
36
+ // Raised when no workspace root (`.git` or `package.json`) is found above startDir.
37
+ export class WorkspaceNotFoundError extends WorkspaceError {
38
+ code = WORKSPACE_CODES.NOT_FOUND;
39
+ startDir;
40
+ constructor(message, startDir, secrets = []) {
41
+ super(message, secrets);
42
+ this.startDir = startDir;
43
+ }
44
+ }
45
+ // Raised when a file exceeds the configured read size cap.
46
+ export class FileTooLargeError extends WorkspaceError {
47
+ code = WORKSPACE_CODES.FILE_TOO_LARGE;
48
+ requestedPath;
49
+ sizeBytes;
50
+ limitBytes;
51
+ constructor(message, requestedPath, sizeBytes, limitBytes, secrets = []) {
52
+ super(message, secrets);
53
+ this.requestedPath = requestedPath;
54
+ this.sizeBytes = sizeBytes;
55
+ this.limitBytes = limitBytes;
56
+ }
57
+ }
58
+ // Raised for an underlying filesystem read failure at the IO boundary.
59
+ export class WorkspaceReadError extends WorkspaceError {
60
+ code = WORKSPACE_CODES.READ_FAILED;
61
+ requestedPath;
62
+ constructor(message, requestedPath, secrets = []) {
63
+ super(message, secrets);
64
+ this.requestedPath = requestedPath;
65
+ }
66
+ }
@@ -0,0 +1,21 @@
1
+ export interface WorkspaceStat {
2
+ readonly size: number;
3
+ readonly isFile: boolean;
4
+ readonly isDirectory: boolean;
5
+ readonly isSymbolicLink: boolean;
6
+ readonly hardLinkCount?: number | undefined;
7
+ }
8
+ export interface WorkspaceDirEntry {
9
+ readonly name: string;
10
+ readonly isDirectory: boolean;
11
+ readonly isFile: boolean;
12
+ readonly isSymbolicLink: boolean;
13
+ }
14
+ export interface WorkspaceFs {
15
+ readonly readFileUtf8: (absolutePath: string) => string;
16
+ readonly stat: (absolutePath: string) => WorkspaceStat;
17
+ readonly readDir: (absolutePath: string) => readonly WorkspaceDirEntry[];
18
+ readonly realPath: (absolutePath: string) => string;
19
+ readonly exists: (absolutePath: string) => boolean;
20
+ }
21
+ export declare const nodeWorkspaceFs: WorkspaceFs;