@webpresso/agent-kit 3.1.30 → 3.3.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 (300) hide show
  1. package/README.md +53 -15
  2. package/bin/_run.js +183 -20
  3. package/catalog/AGENTS.md.tpl +3 -3
  4. package/catalog/agent/agents/implementer.md +45 -0
  5. package/catalog/agent/rules/parallel-orchestration-quota.md +100 -0
  6. package/catalog/agent/rules/pre-implementation.md +28 -1
  7. package/catalog/agent/rules/supported-agent-clis.md +9 -0
  8. package/catalog/agent/rules/typescript-version.md +50 -0
  9. package/catalog/agent/skills/codex/SKILL.md +25 -1
  10. package/catalog/agent/skills/verify/SKILL.md +4 -2
  11. package/catalog/docs/templates/blueprint.md +1 -1
  12. package/dist/esm/audit/agents.js +1 -0
  13. package/dist/esm/audit/blueprint-lifecycle-sql.d.ts +12 -0
  14. package/dist/esm/audit/blueprint-lifecycle-sql.js +23 -2
  15. package/dist/esm/audit/blueprint-pr-coverage.js +4 -0
  16. package/dist/esm/audit/blueprint-trust.js +14 -10
  17. package/dist/esm/audit/changeset-evidence.d.ts +23 -0
  18. package/dist/esm/audit/changeset-evidence.js +30 -0
  19. package/dist/esm/audit/changeset-required.d.ts +10 -0
  20. package/dist/esm/audit/changeset-required.js +73 -0
  21. package/dist/esm/audit/ci-guardrails-detection.d.ts +27 -0
  22. package/dist/esm/audit/ci-guardrails-detection.js +68 -0
  23. package/dist/esm/audit/ci-guardrails-wiring.d.ts +22 -0
  24. package/dist/esm/audit/ci-guardrails-wiring.js +80 -0
  25. package/dist/esm/audit/ci-test-perf.js +30 -0
  26. package/dist/esm/audit/command-surface.js +4 -2
  27. package/dist/esm/audit/host-substitution-risk.d.ts +2 -0
  28. package/dist/esm/audit/host-substitution-risk.js +256 -0
  29. package/dist/esm/audit/registry.d.ts +4 -4
  30. package/dist/esm/audit/registry.js +13 -1
  31. package/dist/esm/audit/toolchain-isolation.js +1 -2
  32. package/dist/esm/audit/typescript-version.d.ts +2 -0
  33. package/dist/esm/audit/typescript-version.js +123 -0
  34. package/dist/esm/blueprint/core/parser.js +101 -2
  35. package/dist/esm/blueprint/core/schema.d.ts +1 -0
  36. package/dist/esm/blueprint/core/schema.js +26 -0
  37. package/dist/esm/blueprint/core/validation/criteria.d.ts +14 -0
  38. package/dist/esm/blueprint/core/validation/criteria.js +41 -2
  39. package/dist/esm/blueprint/core/validation/state.js +25 -14
  40. package/dist/esm/blueprint/db/migrations/run.js +4 -2
  41. package/dist/esm/blueprint/lifecycle/audit.d.ts +20 -0
  42. package/dist/esm/blueprint/lifecycle/audit.js +87 -20
  43. package/dist/esm/blueprint/lifecycle/canonicalize.d.ts +6 -0
  44. package/dist/esm/blueprint/lifecycle/canonicalize.js +135 -0
  45. package/dist/esm/blueprint/lifecycle/local.d.ts +2 -1
  46. package/dist/esm/blueprint/lifecycle/local.js +19 -3
  47. package/dist/esm/blueprint/lifecycle/review-provenance.d.ts +6 -0
  48. package/dist/esm/blueprint/lifecycle/review-provenance.js +68 -12
  49. package/dist/esm/blueprint/lifecycle/rollout-anchor.d.ts +97 -0
  50. package/dist/esm/blueprint/lifecycle/rollout-anchor.js +161 -0
  51. package/dist/esm/blueprint/service/BlueprintCreationService.d.ts +1 -0
  52. package/dist/esm/blueprint/service/BlueprintCreationService.js +53 -4
  53. package/dist/esm/blueprint/service/blueprint-tech-debt-links.js +16 -2
  54. package/dist/esm/blueprint/sync/client.d.ts +2 -1
  55. package/dist/esm/blueprint/sync/client.js +1 -1
  56. package/dist/esm/blueprint/trust/command-runner.d.ts +26 -0
  57. package/dist/esm/blueprint/trust/command-runner.js +148 -0
  58. package/dist/esm/blueprint/trust/dossier.d.ts +1 -0
  59. package/dist/esm/blueprint/trust/dossier.js +40 -6
  60. package/dist/esm/blueprint/trust/gates.d.ts +7 -0
  61. package/dist/esm/blueprint/trust/gates.js +20 -0
  62. package/dist/esm/blueprint/trust/promotion.d.ts +8 -0
  63. package/dist/esm/blueprint/trust/promotion.js +74 -112
  64. package/dist/esm/blueprint/trust/scaffold.d.ts +19 -0
  65. package/dist/esm/blueprint/trust/scaffold.js +64 -0
  66. package/dist/esm/blueprint/trust/validator.d.ts +1 -0
  67. package/dist/esm/blueprint/trust/validator.js +7 -1
  68. package/dist/esm/blueprint/utils/archive.js +27 -5
  69. package/dist/esm/blueprint/utils/document-paths.d.ts +18 -0
  70. package/dist/esm/blueprint/utils/document-paths.js +45 -12
  71. package/dist/esm/build/cli-mcp-parity.js +15 -0
  72. package/dist/esm/cli/bundle/commands/blueprint.d.ts +1 -0
  73. package/dist/esm/cli/bundle/commands/blueprint.js +1 -0
  74. package/dist/esm/cli/cli.d.ts +1 -1
  75. package/dist/esm/cli/cli.js +43 -0
  76. package/dist/esm/cli/commands/agent-launch.d.ts +8 -1
  77. package/dist/esm/cli/commands/agent-launch.js +93 -4
  78. package/dist/esm/cli/commands/audit-core.d.ts +1 -1
  79. package/dist/esm/cli/commands/audit-core.js +8 -1
  80. package/dist/esm/cli/commands/audit.js +21 -5
  81. package/dist/esm/cli/commands/blueprint/execution.js +18 -6
  82. package/dist/esm/cli/commands/blueprint/mutations.js +59 -40
  83. package/dist/esm/cli/commands/blueprint/router-dispatch.d.ts +2 -1
  84. package/dist/esm/cli/commands/blueprint/router-dispatch.js +20 -1
  85. package/dist/esm/cli/commands/blueprint/router-output.d.ts +21 -0
  86. package/dist/esm/cli/commands/blueprint/router-output.js +48 -17
  87. package/dist/esm/cli/commands/blueprint/router.d.ts +7 -0
  88. package/dist/esm/cli/commands/blueprint/router.js +13 -1
  89. package/dist/esm/cli/commands/ci-preflight.d.ts +13 -2
  90. package/dist/esm/cli/commands/ci-preflight.js +30 -6
  91. package/dist/esm/cli/commands/compile.js +5 -31
  92. package/dist/esm/cli/commands/dash/delivery-status.d.ts +1 -0
  93. package/dist/esm/cli/commands/dash/delivery-status.js +11 -4
  94. package/dist/esm/cli/commands/doctor.js +5 -0
  95. package/dist/esm/cli/commands/hook.d.ts +5 -0
  96. package/dist/esm/cli/commands/hook.js +15 -9
  97. package/dist/esm/cli/commands/init/config.js +2 -2
  98. package/dist/esm/cli/commands/init/convergence-apply.d.ts +88 -0
  99. package/dist/esm/cli/commands/init/convergence-apply.js +325 -0
  100. package/dist/esm/cli/commands/init/convergence-state.d.ts +13 -0
  101. package/dist/esm/cli/commands/init/convergence-state.js +54 -0
  102. package/dist/esm/cli/commands/init/convergence.d.ts +23 -0
  103. package/dist/esm/cli/commands/init/convergence.js +57 -0
  104. package/dist/esm/cli/commands/init/git-convergence.d.ts +19 -0
  105. package/dist/esm/cli/commands/init/git-convergence.js +168 -0
  106. package/dist/esm/cli/commands/init/gitignore-patcher.js +2 -0
  107. package/dist/esm/cli/commands/init/grok-privacy-smoke.d.ts +52 -0
  108. package/dist/esm/cli/commands/init/grok-privacy-smoke.js +91 -0
  109. package/dist/esm/cli/commands/init/host-native-proof.d.ts +18 -0
  110. package/dist/esm/cli/commands/init/host-native-proof.js +88 -0
  111. package/dist/esm/cli/commands/init/host-visibility.d.ts +2 -2
  112. package/dist/esm/cli/commands/init/host-visibility.js +94 -5
  113. package/dist/esm/cli/commands/init/index.d.ts +3 -0
  114. package/dist/esm/cli/commands/init/index.js +326 -173
  115. package/dist/esm/cli/commands/init/mcp-spec.d.ts +57 -0
  116. package/dist/esm/cli/commands/init/mcp-spec.js +273 -0
  117. package/dist/esm/cli/commands/init/merge.d.ts +1 -0
  118. package/dist/esm/cli/commands/init/merge.js +30 -1
  119. package/dist/esm/cli/commands/init/package-root.d.ts +7 -0
  120. package/dist/esm/cli/commands/init/package-root.js +10 -2
  121. package/dist/esm/cli/commands/init/plugin-cache-prune.d.ts +0 -3
  122. package/dist/esm/cli/commands/init/plugin-cache-prune.js +0 -1
  123. package/dist/esm/cli/commands/init/scaffold-agents-md.d.ts +1 -1
  124. package/dist/esm/cli/commands/init/scaffold-agents-md.js +9 -3
  125. package/dist/esm/cli/commands/init/scaffold-base-kit.d.ts +20 -0
  126. package/dist/esm/cli/commands/init/scaffold-base-kit.js +61 -5
  127. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/emitters/grok.d.ts +14 -8
  128. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/emitters/grok.js +41 -24
  129. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/index.d.ts +2 -0
  130. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/index.js +54 -24
  131. package/dist/esm/cli/commands/init/scaffolders/codex-mcp/index.d.ts +12 -31
  132. package/dist/esm/cli/commands/init/scaffolders/codex-mcp/index.js +288 -202
  133. package/dist/esm/cli/commands/init/scaffolders/file-transaction.d.ts +22 -0
  134. package/dist/esm/cli/commands/init/scaffolders/file-transaction.js +230 -0
  135. package/dist/esm/cli/commands/init/scaffolders/grok-mcp/index.d.ts +38 -0
  136. package/dist/esm/cli/commands/init/scaffolders/grok-mcp/index.js +73 -0
  137. package/dist/esm/cli/commands/init/scaffolders/opencode-plugin/index.d.ts +21 -0
  138. package/dist/esm/cli/commands/init/scaffolders/opencode-plugin/index.js +559 -63
  139. package/dist/esm/cli/commands/init/untracked-collision.d.ts +14 -0
  140. package/dist/esm/cli/commands/init/untracked-collision.js +42 -0
  141. package/dist/esm/cli/commands/opencode-probe.d.ts +107 -0
  142. package/dist/esm/cli/commands/opencode-probe.js +323 -0
  143. package/dist/esm/cli/commands/opencode-rotate.d.ts +72 -0
  144. package/dist/esm/cli/commands/opencode-rotate.js +147 -0
  145. package/dist/esm/cli/commands/public-readiness.d.ts +11 -0
  146. package/dist/esm/cli/commands/public-readiness.js +42 -0
  147. package/dist/esm/cli/commands/quality-runner.js +3 -3
  148. package/dist/esm/cli/commands/review.d.ts +35 -3
  149. package/dist/esm/cli/commands/review.js +751 -209
  150. package/dist/esm/cli/commands/run-wait.d.ts +2 -0
  151. package/dist/esm/cli/commands/run-wait.js +122 -0
  152. package/dist/esm/cli/commands/rust-check.d.ts +24 -0
  153. package/dist/esm/cli/commands/rust-check.js +110 -0
  154. package/dist/esm/cli/commands/setup-help.js +1 -1
  155. package/dist/esm/cli/commands/setup-repair-help.js +1 -1
  156. package/dist/esm/cli/commands/typecheck.d.ts +1 -0
  157. package/dist/esm/cli/commands/typecheck.js +30 -1
  158. package/dist/esm/cli/commands/worktree/router-dispatch.d.ts +6 -0
  159. package/dist/esm/cli/commands/worktree/router-dispatch.js +126 -9
  160. package/dist/esm/cli/commands/worktree/router.js +10 -0
  161. package/dist/esm/cli/direct-provider-launch.js +9 -5
  162. package/dist/esm/cli/optional-tool-freshness.d.ts +27 -0
  163. package/dist/esm/cli/optional-tool-freshness.js +219 -9
  164. package/dist/esm/daemon/domains/git-actions.d.ts +29 -0
  165. package/dist/esm/daemon/domains/git-actions.js +136 -0
  166. package/dist/esm/daemon/domains/run-registry-interop.d.ts +16 -0
  167. package/dist/esm/daemon/domains/run-registry-interop.js +45 -0
  168. package/dist/esm/daemon/domains/sessions-search.d.ts +11 -0
  169. package/dist/esm/daemon/domains/sessions-search.js +29 -0
  170. package/dist/esm/daemon/handlers/blueprints-handlers.d.ts +5 -0
  171. package/dist/esm/daemon/handlers/blueprints-handlers.js +36 -0
  172. package/dist/esm/daemon/handlers/deploy-handlers.d.ts +6 -0
  173. package/dist/esm/daemon/handlers/deploy-handlers.js +5 -0
  174. package/dist/esm/daemon/handlers/fleet-handlers.d.ts +15 -0
  175. package/dist/esm/daemon/handlers/fleet-handlers.js +91 -0
  176. package/dist/esm/daemon/handlers/git-handlers.d.ts +6 -0
  177. package/dist/esm/daemon/handlers/git-handlers.js +27 -0
  178. package/dist/esm/daemon/handlers/product.d.ts +7 -0
  179. package/dist/esm/daemon/handlers/product.js +10 -119
  180. package/dist/esm/daemon/handlers/sessions-handlers.d.ts +12 -0
  181. package/dist/esm/daemon/handlers/sessions-handlers.js +31 -0
  182. package/dist/esm/daemon/handlers/shared.d.ts +5 -0
  183. package/dist/esm/daemon/handlers/shared.js +16 -0
  184. package/dist/esm/daemon/methods.d.ts +8 -1
  185. package/dist/esm/daemon/methods.js +7 -0
  186. package/dist/esm/daemon/protocol/contract.d.ts +43 -0
  187. package/dist/esm/daemon/protocol/contract.js +26 -0
  188. package/dist/esm/daemon/protocol/index.d.ts +1 -1
  189. package/dist/esm/daemon/protocol/index.js +1 -1
  190. package/dist/esm/hooks/__conformance__/matrix.d.ts +6 -0
  191. package/dist/esm/hooks/__conformance__/matrix.js +137 -0
  192. package/dist/esm/hooks/conformance/batch.js +5 -0
  193. package/dist/esm/hooks/doctor.d.ts +5 -3
  194. package/dist/esm/hooks/doctor.js +67 -16
  195. package/dist/esm/hooks/permission-request/index.js +9 -3
  196. package/dist/esm/hooks/permission-request/policy.d.ts +9 -0
  197. package/dist/esm/hooks/permission-request/policy.js +13 -0
  198. package/dist/esm/hooks/pretool-guard/dev-routing.d.ts +9 -0
  199. package/dist/esm/hooks/pretool-guard/dev-routing.js +41 -5
  200. package/dist/esm/hooks/pretool-guard/logger.d.ts +7 -0
  201. package/dist/esm/hooks/pretool-guard/logger.js +16 -0
  202. package/dist/esm/hooks/pretool-guard/runner.d.ts +6 -0
  203. package/dist/esm/hooks/pretool-guard/runner.js +68 -16
  204. package/dist/esm/hooks/pretool-guard/validators/blueprint.js +30 -10
  205. package/dist/esm/hooks/pretool-guard/validators/forbidden-commands.js +54 -0
  206. package/dist/esm/hooks/pretool-guard/validators/index.d.ts +7 -0
  207. package/dist/esm/hooks/pretool-guard/validators/index.js +21 -0
  208. package/dist/esm/hooks/pretool-guard/validators/path-contract.d.ts +19 -0
  209. package/dist/esm/hooks/pretool-guard/validators/path-contract.js +25 -8
  210. package/dist/esm/hooks/pretool-guard/validators/worktree-discipline.js +6 -1
  211. package/dist/esm/hooks/shared/guard-outcome.d.ts +149 -0
  212. package/dist/esm/hooks/shared/guard-outcome.js +215 -0
  213. package/dist/esm/hooks/shared/hook-bootstrap.d.ts +26 -0
  214. package/dist/esm/hooks/shared/hook-bootstrap.js +64 -1
  215. package/dist/esm/hooks/shared/types.d.ts +8 -5
  216. package/dist/esm/hooks/shared/types.js +50 -9
  217. package/dist/esm/hooks/stop/qa-changed-files.d.ts +11 -0
  218. package/dist/esm/hooks/stop/qa-changed-files.js +92 -0
  219. package/dist/esm/mcp/blueprint/_shared/lifecycle.js +14 -1
  220. package/dist/esm/mcp/blueprint/handlers/document-mutations.d.ts +5 -0
  221. package/dist/esm/mcp/blueprint/handlers/document-mutations.js +109 -11
  222. package/dist/esm/mcp/blueprint/handlers/review-log.js +8 -1
  223. package/dist/esm/mcp/blueprint/handlers/task-advance.js +113 -54
  224. package/dist/esm/mcp/blueprint/handlers/task-verify.js +39 -19
  225. package/dist/esm/mcp/blueprint/registration.js +3 -2
  226. package/dist/esm/mcp/server.d.ts +1 -0
  227. package/dist/esm/mcp/server.js +27 -0
  228. package/dist/esm/mcp/tools/_names.d.ts +1 -1
  229. package/dist/esm/mcp/tools/_names.js +5 -0
  230. package/dist/esm/mcp/tools/_registry.js +10 -0
  231. package/dist/esm/mcp/tools/_shared/audit-kinds.d.ts +1 -1
  232. package/dist/esm/mcp/tools/_shared/result.js +44 -1
  233. package/dist/esm/mcp/tools/audit.d.ts +5 -1
  234. package/dist/esm/mcp/tools/audit.js +42 -0
  235. package/dist/esm/mcp/tools/audits.d.ts +4 -0
  236. package/dist/esm/mcp/tools/ci-preflight.d.ts +50 -0
  237. package/dist/esm/mcp/tools/ci-preflight.js +183 -0
  238. package/dist/esm/mcp/tools/pr-upsert.js +39 -1
  239. package/dist/esm/mcp/tools/review-run.d.ts +28 -0
  240. package/dist/esm/mcp/tools/review-run.js +216 -0
  241. package/dist/esm/mcp/tools/run-wait.d.ts +86 -0
  242. package/dist/esm/mcp/tools/run-wait.js +593 -0
  243. package/dist/esm/mcp/tools/session-fetch-and-index.d.ts +1 -1
  244. package/dist/esm/mcp/tools/session-id.d.ts +3 -0
  245. package/dist/esm/mcp/tools/session-id.js +45 -0
  246. package/dist/esm/mcp/tools/session-info.d.ts +3 -0
  247. package/dist/esm/mcp/tools/session-info.js +49 -0
  248. package/dist/esm/output-transforms/audit.d.ts +23 -0
  249. package/dist/esm/output-transforms/audit.js +47 -0
  250. package/dist/esm/output-transforms/index.js +2 -0
  251. package/dist/esm/package.json +2 -0
  252. package/dist/esm/platform/client.d.ts +7 -1
  253. package/dist/esm/platform/github-contract-source.d.ts +2 -1
  254. package/dist/esm/pr-description/contract.js +24 -4
  255. package/dist/esm/review/authority.js +27 -14
  256. package/dist/esm/review/availability.d.ts +33 -0
  257. package/dist/esm/review/availability.js +122 -20
  258. package/dist/esm/review/delivery-verifier.d.ts +27 -0
  259. package/dist/esm/review/delivery-verifier.js +115 -0
  260. package/dist/esm/review/events.d.ts +3 -2
  261. package/dist/esm/review/events.js +23 -3
  262. package/dist/esm/review/execution/adapters.d.ts +11 -1
  263. package/dist/esm/review/execution/adapters.js +36 -10
  264. package/dist/esm/review/execution/artifacts.d.ts +1 -1
  265. package/dist/esm/review/execution/output-policy.d.ts +2 -0
  266. package/dist/esm/review/execution/output-policy.js +12 -0
  267. package/dist/esm/review/execution/review-checkout.js +121 -21
  268. package/dist/esm/review/execution/supervisor.js +36 -4
  269. package/dist/esm/review/execution/types.d.ts +9 -0
  270. package/dist/esm/review/opencode-account.d.ts +24 -0
  271. package/dist/esm/review/opencode-account.js +68 -0
  272. package/dist/esm/review/opencode-auth.d.ts +33 -0
  273. package/dist/esm/review/opencode-auth.js +79 -0
  274. package/dist/esm/review/subject.d.ts +25 -0
  275. package/dist/esm/review/subject.js +116 -26
  276. package/dist/esm/runtime/opencode-account-materializer.d.ts +34 -0
  277. package/dist/esm/runtime/opencode-account-materializer.js +33 -0
  278. package/dist/esm/rust/affected-manifests.d.ts +38 -0
  279. package/dist/esm/rust/affected-manifests.js +69 -0
  280. package/dist/esm/session-memory/fetch-index.d.ts +1 -1
  281. package/dist/esm/status/snapshot.js +32 -25
  282. package/dist/esm/test/duration-sequencer.d.ts +20 -0
  283. package/dist/esm/test/duration-sequencer.js +54 -0
  284. package/dist/esm/test/shard-durations.json +922 -0
  285. package/dist/esm/test/worker-budget.d.ts +6 -0
  286. package/dist/esm/test/worker-budget.js +59 -1
  287. package/dist/esm/test-helpers/global-setup.d.ts +1 -2
  288. package/dist/esm/test-helpers/global-setup.js +79 -32
  289. package/dist/esm/typecheck/planner.d.ts +9 -1
  290. package/dist/esm/typecheck/planner.js +31 -0
  291. package/dist/esm/ultragoal/service.d.ts +7 -0
  292. package/dist/esm/ultragoal/service.js +42 -2
  293. package/dist/esm/utils/write-json-file.js +3 -1
  294. package/dist/esm/worktrees/codex-project-trust.d.ts +15 -0
  295. package/dist/esm/worktrees/codex-project-trust.js +56 -0
  296. package/dist/esm/worktrees/orphan-scan.d.ts +50 -0
  297. package/dist/esm/worktrees/orphan-scan.js +211 -0
  298. package/dist/esm/worktrees/registry.d.ts +8 -0
  299. package/dist/esm/worktrees/registry.js +18 -3
  300. package/package.json +18 -13
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ export declare const MAINTAINER_OVERRIDE_REVIEWER = "maintainer-override";
2
3
  export declare const reviewEventSchema: z.ZodObject<{
3
4
  schemaVersion: z.ZodLiteral<2>;
4
5
  id: z.ZodString;
@@ -16,12 +17,12 @@ export declare const reviewEventSchema: z.ZodObject<{
16
17
  gateId: z.ZodString;
17
18
  promptHash: z.ZodString;
18
19
  reviewer: z.ZodString;
19
- provider: z.ZodEnum<{
20
+ provider: z.ZodOptional<z.ZodEnum<{
20
21
  claude: "claude";
21
22
  codex: "codex";
22
23
  grok: "grok";
23
24
  opencode: "opencode";
24
- }>;
25
+ }>>;
25
26
  model: z.ZodOptional<z.ZodString>;
26
27
  effort: z.ZodOptional<z.ZodEnum<{
27
28
  high: "high";
@@ -9,6 +9,14 @@ const SHA256_PATTERN = /^[a-f0-9]{64}$/u;
9
9
  const COMMIT_PATTERN = /^(?:[a-f0-9]{40}|[a-f0-9]{64})$/u;
10
10
  const BLUEPRINT_SLUG_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*(?:\/[a-z0-9]+(?:-[a-z0-9]+)*)*$/u;
11
11
  const ARTIFACT_PATH_PATTERN = /^review-artifacts\/[a-zA-Z0-9._-]+\.md$/u;
12
+ // The honest, auditable escape hatch reviewer id. A maintainer override is a
13
+ // HUMAN adjudication (not a machine backend), recorded only by `wp review
14
+ // override` after a real strong-reviewer rejection of the same subject. It
15
+ // satisfies the promotion gate DISTINCTLY (never folded into a strong or
16
+ // OpenCode-Go approval). Defined here — the lowest review-core module — so both
17
+ // the ledger read-time policy below and the lifecycle approval gate share one
18
+ // source of truth without an import cycle.
19
+ export const MAINTAINER_OVERRIDE_REVIEWER = "maintainer-override";
12
20
  export const reviewEventSchema = z
13
21
  .object({
14
22
  schemaVersion: z.literal(2),
@@ -21,7 +29,10 @@ export const reviewEventSchema = z
21
29
  gateId: z.string().min(1).max(120),
22
30
  promptHash: z.string().regex(SHA256_PATTERN),
23
31
  reviewer: z.string().min(1).max(120),
24
- provider: z.enum(["claude", "codex", "opencode", "grok"]),
32
+ // Optional: present for machine-backend reviews (always set by `wp review
33
+ // gate`/`log`); absent for a human maintainer-override, which has no AI
34
+ // backend. Descriptive metadata only — no policy keys off `provider`.
35
+ provider: z.enum(["claude", "codex", "opencode", "grok"]).optional(),
25
36
  model: z.string().min(1).max(240).optional(),
26
37
  effort: z.enum(["medium", "high"]).optional(),
27
38
  stage: z.string().min(1).max(120),
@@ -81,7 +92,7 @@ function orderedEvent(event) {
81
92
  gateId: event.gateId,
82
93
  promptHash: event.promptHash,
83
94
  reviewer: event.reviewer,
84
- provider: event.provider,
95
+ ...(event.provider ? { provider: event.provider } : {}),
85
96
  ...(event.model ? { model: event.model } : {}),
86
97
  ...(event.effort ? { effort: event.effort } : {}),
87
98
  stage: event.stage,
@@ -164,10 +175,19 @@ function isManualMarker(value) {
164
175
  // dodge the policy while real gate ids (content hashes) can never collide.
165
176
  return value.trim().toLowerCase() === "manual";
166
177
  }
178
+ // Reviewers whose manual-stage approvals are legitimate governance decisions,
179
+ // not disguised machine approvals: a plain `human` review and a
180
+ // `maintainer-override` (a maintainer's hand-recorded adjudication). Both are
181
+ // human authority by construction, so a manual marker on their events is
182
+ // expected rather than a policy dodge.
183
+ const HUMAN_LIKE_REVIEWERS = new Set(["human", MAINTAINER_OVERRIDE_REVIEWER]);
184
+ function isHumanLikeReviewer(reviewer) {
185
+ return HUMAN_LIKE_REVIEWERS.has(canonicalReviewer(reviewer));
186
+ }
167
187
  function isManualMachineApproval(event) {
168
188
  return (isPolicyApprovalVerdict(event.verdict) &&
169
189
  (isManualMarker(event.gateId) || isManualMarker(event.stage)) &&
170
- canonicalReviewer(event.reviewer) !== "human");
190
+ !isHumanLikeReviewer(event.reviewer));
171
191
  }
172
192
  // `compromisedIds` marks events whose artifact evidence failed integrity
173
193
  // verification. They still PARTICIPATE in reviewer supersession — a later
@@ -1,4 +1,14 @@
1
- import type { ReviewRunInput, ReviewStreamAdapter } from "./types.js";
1
+ import type { ReviewAdapterEvent, ReviewRunInput, ReviewStreamAdapter } from "./types.js";
2
+ /**
3
+ * Classify an OpenCode Go usage-limit message and derive its real reset window.
4
+ *
5
+ * Exported because the `wp opencode` launch-time health probe
6
+ * (`#cli/commands/opencode-probe.js`) runs the same provider text through the
7
+ * same classifier. A second copy of this wording/duration math would drift the
8
+ * moment the provider changes a phrase, and the two consumers would then
9
+ * disagree about whether an account is capped.
10
+ */
11
+ export declare function opencodeUsageLimit(message: string): ReviewAdapterEvent | null;
2
12
  export declare const claudeReviewAdapter: ReviewStreamAdapter;
3
13
  export declare const codexReviewAdapter: ReviewStreamAdapter;
4
14
  export declare const opencodeReviewAdapter: ReviewStreamAdapter;
@@ -3,7 +3,7 @@ import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
3
3
  import { tmpdir } from "node:os";
4
4
  import { join } from "node:path";
5
5
  import { CLAUDE_DEFAULT_REVIEW_MODEL, CODEX_DEFAULT_REVIEW_MODEL, GROK_DEFAULT_REVIEW_MODEL, } from "./types.js";
6
- import { canonicalizeProviderFailureCode } from "./output-policy.js";
6
+ import { canonicalizeProviderFailureCode, sanitizeProviderErrorDetail } from "./output-policy.js";
7
7
  function objectValue(value) {
8
8
  return value !== null && typeof value === "object" && !Array.isArray(value)
9
9
  ? value
@@ -61,6 +61,18 @@ function providerErrorMessage(value) {
61
61
  ];
62
62
  return candidates.find((candidate) => typeof candidate === "string") ?? null;
63
63
  }
64
+ // Builds a "failure" event, attaching the provider's raw diagnostic text
65
+ // (sanitized and length-bounded) alongside the canonical short `code` when
66
+ // one is available. `code` stays the only field the retry/classification
67
+ // control flow reads; `message` exists purely to be surfaced to a caller who
68
+ // is stuck debugging a generic failure code.
69
+ function providerFailureEvent(value, code, message = providerErrorMessage(value)) {
70
+ return {
71
+ kind: "failure",
72
+ code,
73
+ ...(message ? { message: sanitizeProviderErrorDetail(message) } : {}),
74
+ };
75
+ }
64
76
  function parseResetDurationMs(message) {
65
77
  const reset = message.match(/\bresets?\s+in\s+([^.'"]+)/iu)?.[1];
66
78
  if (!reset)
@@ -80,7 +92,16 @@ function parseResetDurationMs(message) {
80
92
  }
81
93
  return duration > 0 ? duration : undefined;
82
94
  }
83
- function opencodeUsageLimit(message) {
95
+ /**
96
+ * Classify an OpenCode Go usage-limit message and derive its real reset window.
97
+ *
98
+ * Exported because the `wp opencode` launch-time health probe
99
+ * (`#cli/commands/opencode-probe.js`) runs the same provider text through the
100
+ * same classifier. A second copy of this wording/duration math would drift the
101
+ * moment the provider changes a phrase, and the two consumers would then
102
+ * disagree about whether an account is capped.
103
+ */
104
+ export function opencodeUsageLimit(message) {
84
105
  if (!/\busage limit reached\b/iu.test(message))
85
106
  return null;
86
107
  const limitWindow = /\bweekly usage limit reached\b/iu.test(message)
@@ -94,6 +115,7 @@ function opencodeUsageLimit(message) {
94
115
  return {
95
116
  kind: "failure",
96
117
  code: "opencode-go-usage-limit",
118
+ message: sanitizeProviderErrorDetail(message),
97
119
  limitWindow,
98
120
  ...(retryAfterMs === undefined ? {} : { retryAfterMs }),
99
121
  };
@@ -104,7 +126,7 @@ function claudeEvent(value) {
104
126
  return { kind: "unsupported" };
105
127
  if (record.type === "result") {
106
128
  if (record.is_error === true || record.subtype === "error") {
107
- return { kind: "failure", code: failureCode(record) };
129
+ return providerFailureEvent(record, failureCode(record));
108
130
  }
109
131
  return {
110
132
  kind: "terminal",
@@ -112,7 +134,7 @@ function claudeEvent(value) {
112
134
  };
113
135
  }
114
136
  if (record.type === "error")
115
- return { kind: "failure", code: failureCode(record) };
137
+ return providerFailureEvent(record, failureCode(record));
116
138
  if (record.type === "rate_limit_event") {
117
139
  const rateLimit = objectValue(record.rate_limit_info);
118
140
  return rateLimit?.status === "rejected"
@@ -127,7 +149,7 @@ function claudeEvent(value) {
127
149
  }
128
150
  if (record.type === "assistant") {
129
151
  return typeof record.error === "string"
130
- ? { kind: "failure", code: failureCode(record) }
152
+ ? providerFailureEvent(record, failureCode(record), record.error)
131
153
  : { kind: "progress", frontier: claudeEnvelopeFrontier(record) };
132
154
  }
133
155
  if (record.type !== "stream_event")
@@ -151,7 +173,7 @@ function claudeEvent(value) {
151
173
  return { kind: "progress", frontier: stableFrontier(event) };
152
174
  }
153
175
  if (event.type === "error")
154
- return { kind: "failure", code: failureCode(event) };
176
+ return providerFailureEvent(event, failureCode(event));
155
177
  return { kind: "unsupported" };
156
178
  }
157
179
  function opencodeText(record) {
@@ -216,7 +238,7 @@ function opencodeEvent(value) {
216
238
  if (record.type === "error") {
217
239
  const message = providerErrorMessage(record);
218
240
  const usageLimit = message === null ? null : opencodeUsageLimit(message);
219
- return usageLimit ?? { kind: "failure", code: failureCode(record) };
241
+ return usageLimit ?? providerFailureEvent(record, failureCode(record), message);
220
242
  }
221
243
  // OpenCode's non-interactive JSON protocol closes a completed run with
222
244
  // session.idle or step_finish(reason: "stop"); recorded Kimi and DeepSeek
@@ -293,10 +315,10 @@ function codexEvent(value) {
293
315
  : message && /rate.?limit|quota/iu.test(message)
294
316
  ? "rate-limit-rejected"
295
317
  : "provider-error");
296
- return { kind: "failure", code };
318
+ return providerFailureEvent(record, code, message);
297
319
  }
298
320
  if (type.includes("rate_limit") && /reject|limit|quota/iu.test(message ?? "")) {
299
- return { kind: "failure", code: "rate-limit-rejected" };
321
+ return providerFailureEvent(record, "rate-limit-rejected", message);
300
322
  }
301
323
  if (type === "result" ||
302
324
  type === "final" ||
@@ -399,6 +421,10 @@ export const opencodeReviewAdapter = {
399
421
  OPENCODE_CONFIG_CONTENT: OPENCODE_REVIEW_CONFIG,
400
422
  OPENCODE_DISABLE_AUTOUPDATE: "true",
401
423
  OPENCODE_DISABLE_PRUNE: "true",
424
+ // Isolate the credential store per account: the spawn reads
425
+ // `<authDataDir>/opencode/auth.json` instead of the user's global file,
426
+ // so mid-flight account failover never races a live interactive TUI.
427
+ ...(input.authDataDir ? { XDG_DATA_HOME: input.authDataDir } : {}),
402
428
  },
403
429
  cleanup: attachment.cleanup,
404
430
  };
@@ -506,7 +532,7 @@ export const grokReviewAdapter = {
506
532
  return {
507
533
  kind: "failure",
508
534
  code: "provider-failed",
509
- message,
535
+ message: sanitizeProviderErrorDetail(message),
510
536
  };
511
537
  }
512
538
  return { kind: "progress", frontier: type || "event" };
@@ -14,4 +14,4 @@ export interface ReviewArtifactWriter {
14
14
  export declare function reviewRunStatePath(root: string, runId: string): string;
15
15
  export type ReviewRunStateCleanupResult = "missing" | "not-running" | "removed" | "refused";
16
16
  export declare function removeOwnedRunningReviewState(root: string, runId: string, reservationToken: string): ReviewRunStateCleanupResult;
17
- export declare function createReviewArtifactWriter(root: string, runId: string, startedAt: string, reservationToken?: `${string}-${string}-${string}-${string}-${string}`): ReviewArtifactWriter;
17
+ export declare function createReviewArtifactWriter(root: string, runId: string, startedAt: string, reservationToken?: string): ReviewArtifactWriter;
@@ -11,4 +11,6 @@ export type ReviewOutputPolicyResult = {
11
11
  };
12
12
  export declare function sanitizeReviewOutput(value: string): string;
13
13
  export declare function applyReviewOutputPolicy(value: string, consumedBytes?: number): ReviewOutputPolicyResult;
14
+ export declare const REVIEW_ERROR_DETAIL_MAX_CHARS = 400;
15
+ export declare function sanitizeProviderErrorDetail(value: string): string;
14
16
  export declare function canonicalizeProviderFailureCode(value: unknown): string;
@@ -23,6 +23,18 @@ export function applyReviewOutputPolicy(value, consumedBytes = 0) {
23
23
  }
24
24
  return { ok: true, value: sanitized, bytes, totalBytes: consumedBytes + bytes };
25
25
  }
26
+ export const REVIEW_ERROR_DETAIL_MAX_CHARS = 400;
27
+ // Bounds and redacts a provider's raw diagnostic text (a JSON envelope's
28
+ // error.message field, or a raw stderr line) before it is retained anywhere.
29
+ // This feeds only the ephemeral gate state file and CLI stdout -- never the
30
+ // durable, git-committed review ledger -- but the same secret redaction that
31
+ // already applies to terminal/progress "result" text applies here too.
32
+ export function sanitizeProviderErrorDetail(value) {
33
+ const sanitized = sanitizeReviewOutput(value).replace(/\s+/gu, " ").trim();
34
+ return sanitized.length > REVIEW_ERROR_DETAIL_MAX_CHARS
35
+ ? `${sanitized.slice(0, REVIEW_ERROR_DETAIL_MAX_CHARS)}…`
36
+ : sanitized;
37
+ }
26
38
  export function canonicalizeProviderFailureCode(value) {
27
39
  if (typeof value !== "string")
28
40
  return "provider-error";
@@ -1,8 +1,8 @@
1
1
  import { execFileSync } from "node:child_process";
2
2
  import { createHash } from "node:crypto";
3
- import { lstatSync, mkdtempSync, readFileSync, readdirSync, readlinkSync, rmSync, unlinkSync, writeFileSync, } from "node:fs";
3
+ import { lstatSync, mkdtempSync, readFileSync, readdirSync, readlinkSync, rmSync, symlinkSync, unlinkSync, writeFileSync, } from "node:fs";
4
4
  import { tmpdir } from "node:os";
5
- import { join } from "node:path";
5
+ import { basename, join } from "node:path";
6
6
  function git(cwd, args, input) {
7
7
  return execFileSync("git", [...args], {
8
8
  cwd,
@@ -23,24 +23,34 @@ function frame(hash, label, value) {
23
23
  hash.update(`${label}\0${bytes.byteLength}\0`, "utf8");
24
24
  hash.update(bytes);
25
25
  }
26
- function worktreeFingerprint(cwd) {
26
+ // Fingerprint over the filtered line set `status()` produces below, minus
27
+ // pure gitignored (`!!`) entries, so the two contamination checks
28
+ // (`changes()`'s status() diff and the caller worktree fingerprint) cannot
29
+ // drift apart on what counts as "source" content while still tolerating
30
+ // expected runtime bookkeeping.
31
+ //
32
+ // Before the status()-alignment fix, the fingerprint hashed the raw
33
+ // unfiltered `git status` output (without `--ignored`), so anything status()
34
+ // already tolerates (the self-hosting bootstrap's idempotent `package.json`
35
+ // trailing-whitespace normalization, and non-source ignored paths under
36
+ // dist/.tshy/node_modules) could still flip the fingerprint and false-flag a
37
+ // clean caller worktree as contaminated.
38
+ //
39
+ // Reusing status() alone re-introduces a different false positive: the
40
+ // supervisor writes process records under gitignored `.webpresso/` (and similar
41
+ // runtime roots) during the run. Those appear as `!!` lines only when
42
+ // `--ignored` is on. Dropping `!!` here restores the prior fingerprint
43
+ // contract for ignored paths while keeping the package.json / non-`!!` filters
44
+ // shared with status(). Checkout-side status() still reports non-derived
45
+ // gitignored writes (e.g. a stray `.env`) on the disposable review tree.
46
+ function worktreeFingerprint(cwd, ignoredPaths = new Set()) {
27
47
  const hash = createHash("sha256");
28
- const porcelain = git(cwd, ["status", "--porcelain=v1", "-z", "--untracked-files=all"]);
29
- frame(hash, "porcelain", porcelain);
48
+ const lines = status(cwd, ignoredPaths).filter((line) => !line.startsWith("!!"));
49
+ frame(hash, "status-lines", lines.join("\n"));
30
50
  frame(hash, "staged", gitBytes(cwd, ["diff", "--cached", "--binary", "--no-ext-diff", "HEAD", "--"]));
31
- const records = porcelain.split("\0").filter(Boolean);
32
51
  const dirtyPaths = new Set();
33
- for (let index = 0; index < records.length; index += 1) {
34
- const record = records[index] ?? "";
35
- dirtyPaths.add(record.slice(3));
36
- if (record.slice(0, 2).match(/[RC]/u)) {
37
- const pairedPath = records[index + 1];
38
- if (pairedPath) {
39
- dirtyPaths.add(pairedPath);
40
- index += 1;
41
- }
42
- }
43
- }
52
+ for (const line of lines)
53
+ dirtyPaths.add(statusEntryPath(line));
44
54
  for (const relativePath of [...dirtyPaths].filter(Boolean).toSorted()) {
45
55
  const absolutePath = join(cwd, relativePath);
46
56
  frame(hash, "dirty-path", relativePath);
@@ -60,12 +70,78 @@ function worktreeFingerprint(cwd) {
60
70
  }
61
71
  return hash.digest("hex");
62
72
  }
63
- function status(cwd) {
73
+ // Path segments that mark gitignored content as NON-source: derived build
74
+ // outputs a Promotion Gate command (`./bin/wp typecheck`/`test`/etc.)
75
+ // regenerates while self-hosting this repo (`dist/`, `.tshy/`) and the
76
+ // reconstructed dependency tree (`node_modules`, already separately allowlisted
77
+ // via `ignoredPaths` when symlinked — kept here too for depth-safety, e.g. a
78
+ // nested per-workspace-package `node_modules`). A gitignored (`!!`) path whose
79
+ // ANY segment is one of these — at any workspace depth (e.g. `packages/x/dist`,
80
+ // `.tshy/build.log`) — is exempt from the contamination check. Confirmed
81
+ // empirically that the gate commands emit gitignored output exclusively under
82
+ // these roots.
83
+ //
84
+ // SCOPE (deliberate, documented): this verifier enforces the review TARGET's
85
+ // *source* integrity, not its build artifacts. It does not flag writes under a
86
+ // derived/dependency root, because such output can reach neither the caller
87
+ // nor the reviewed source (the checkout is a disposable tmpdir). Everything
88
+ // else stays strict: a tracked mutation (` M `/` D `), a new untracked source
89
+ // file (`?? `), and any NON-derived gitignored write (e.g. a stray `.env`) all
90
+ // still surface.
91
+ const NON_SOURCE_IGNORED_SEGMENTS = new Set(["dist", ".tshy", "node_modules"]);
92
+ function isNonSourceIgnoredPath(relativePath) {
93
+ return relativePath.split("/").some((segment) => NON_SOURCE_IGNORED_SEGMENTS.has(segment));
94
+ }
95
+ // This repo self-hosts: a Promotion Gate command's first `./bin/wp` invocation
96
+ // triggers wp's own bootstrap build, which runs tshy and then
97
+ // `./bin/wp format --file package.json` — an IDEMPOTENT normalization (e.g.
98
+ // adding a missing trailing newline). Re-running it a second time produces
99
+ // byte-identical output modulo trailing whitespace, so it is not a source
100
+ // mutation. Exempt ONLY that exact shape: a plain, unstaged content
101
+ // modification (` M `) of a file literally named `package.json` whose
102
+ // tracked-vs-working-tree bytes differ solely in trailing whitespace. Any
103
+ // other package.json change (dependency/script/field edits, staged changes,
104
+ // adds, deletes) still fails closed — this check never inspects JSON
105
+ // semantics, only trailing-whitespace-normalized byte equality.
106
+ function isNormalizedPackageJsonModification(cwd, line) {
107
+ if (!line.startsWith(" M "))
108
+ return false;
109
+ const relativePath = line.slice(3);
110
+ if (basename(relativePath) !== "package.json")
111
+ return false;
112
+ try {
113
+ const working = readFileSync(join(cwd, relativePath), "utf8");
114
+ const tracked = git(cwd, ["show", `HEAD:${relativePath}`]);
115
+ return working.replace(/\s+$/u, "") === tracked.replace(/\s+$/u, "");
116
+ }
117
+ catch {
118
+ return false;
119
+ }
120
+ }
121
+ // Single source of truth for turning a `git status --porcelain=v1` line into
122
+ // its relative path, shared by status()'s filter predicate and
123
+ // worktreeFingerprint()'s dirty-path hashing so both stay aligned by
124
+ // construction. Does not special-case the rename arrow (`old -> new`); a
125
+ // rename line's raw tail is used as-is, matching this function's prior
126
+ // (pre-refactor) inline behavior.
127
+ function statusEntryPath(line) {
128
+ return line.slice(3).replace(/\/$/u, "");
129
+ }
130
+ function status(cwd, ignoredPaths = new Set()) {
64
131
  const output = git(cwd, ["status", "--porcelain=v1", "--untracked-files=all", "--ignored"]);
65
132
  return output
66
133
  .split(/\r?\n/u)
67
- .map((line) => line.trimEnd())
68
- .filter((line) => line.length > 0);
134
+ .filter((line) => line.length > 0)
135
+ .filter((line) => {
136
+ const relativePath = statusEntryPath(line);
137
+ if (ignoredPaths.has(relativePath))
138
+ return false;
139
+ if (line.startsWith("!!") && isNonSourceIgnoredPath(relativePath))
140
+ return false;
141
+ if (isNormalizedPackageJsonModification(cwd, line))
142
+ return false;
143
+ return true;
144
+ });
69
145
  }
70
146
  function neutralizeSymlinks(directory) {
71
147
  for (const entry of readdirSync(directory, { withFileTypes: true })) {
@@ -111,6 +187,7 @@ export function createReviewTargetCheckout(repositoryRoot, targetSha) {
111
187
  git(cwd, ["update-ref", "HEAD", baselineHead]);
112
188
  if (status(cwd).length > 0)
113
189
  throw new Error("review target checkout was not clean at start");
190
+ const injectedPaths = projectCallerDependencies(repositoryRoot, cwd);
114
191
  let cleaned = false;
115
192
  return {
116
193
  cwd,
@@ -119,7 +196,7 @@ export function createReviewTargetCheckout(repositoryRoot, targetSha) {
119
196
  },
120
197
  changes: () => {
121
198
  try {
122
- const changes = [...status(cwd)];
199
+ const changes = [...status(cwd, injectedPaths)];
123
200
  if (git(cwd, ["rev-parse", "HEAD"]).trim() !== baselineHead) {
124
201
  changes.push("review checkout HEAD changed");
125
202
  }
@@ -145,3 +222,26 @@ export function createReviewTargetCheckout(repositoryRoot, targetSha) {
145
222
  throw error;
146
223
  }
147
224
  }
225
+ function projectCallerDependencies(repositoryRoot, checkoutRoot) {
226
+ const projected = new Set();
227
+ const dependencyPath = "node_modules";
228
+ const callerPath = join(repositoryRoot, dependencyPath);
229
+ const targetPath = join(checkoutRoot, dependencyPath);
230
+ try {
231
+ lstatSync(callerPath);
232
+ }
233
+ catch {
234
+ return projected;
235
+ }
236
+ try {
237
+ lstatSync(targetPath);
238
+ return projected;
239
+ }
240
+ catch {
241
+ // Missing in the target archive means the caller dependency cache may be
242
+ // projected for local tooling without overwriting target-owned files.
243
+ }
244
+ symlinkSync(callerPath, targetPath, "dir");
245
+ projected.add(dependencyPath);
246
+ return projected;
247
+ }
@@ -107,6 +107,7 @@ export async function superviseReviewExecution(input, adapter, deps = {}) {
107
107
  });
108
108
  let status = null;
109
109
  let failureCode;
110
+ let providerErrorDetail;
110
111
  let limitWindow;
111
112
  let retryAfterMs;
112
113
  let result;
@@ -122,6 +123,10 @@ export async function superviseReviewExecution(input, adapter, deps = {}) {
122
123
  let fallbackSelectionTimer;
123
124
  let pendingFallback;
124
125
  const seenFrontiers = new Set();
126
+ const droppedEventWarnings = [];
127
+ const recordDroppedProviderEvent = (reason) => {
128
+ droppedEventWarnings.push(reason);
129
+ };
125
130
  const decoder = new StringDecoder("utf8");
126
131
  const diagnosticDecoder = new StringDecoder("utf8");
127
132
  let buffered = "";
@@ -191,6 +196,7 @@ export async function superviseReviewExecution(input, adapter, deps = {}) {
191
196
  failureCode = code;
192
197
  limitWindow = metadata.limitWindow;
193
198
  retryAfterMs = metadata.retryAfterMs;
199
+ providerErrorDetail = metadata.providerErrorDetail;
194
200
  requestTermination();
195
201
  finalize();
196
202
  };
@@ -228,19 +234,41 @@ export async function superviseReviewExecution(input, adapter, deps = {}) {
228
234
  const processLine = (line) => {
229
235
  if (line.trim().length === 0 || status !== null)
230
236
  return;
231
- if (Buffer.byteLength(line, "utf8") > MAX_PROVIDER_EVENT_BYTES) {
232
- fail("protocol-unsupported", "provider-event-too-large");
233
- return;
234
- }
237
+ const oversized = Buffer.byteLength(line, "utf8") > MAX_PROVIDER_EVENT_BYTES;
235
238
  let value;
236
239
  try {
237
240
  value = JSON.parse(line);
238
241
  }
239
242
  catch {
243
+ // An oversized frame that also fails to parse carries no recoverable
244
+ // signal (a genuinely truncated/malformed line looks the same either
245
+ // way) -- drop it with a warning instead of failing a run that may
246
+ // still receive a complete, well-formed terminal verdict later. A
247
+ // normal-sized malformed line is still a protocol violation.
248
+ if (oversized) {
249
+ recordDroppedProviderEvent("provider-event-too-large-unparseable");
250
+ return;
251
+ }
240
252
  fail("protocol-unsupported", "invalid-json-event");
241
253
  return;
242
254
  }
243
255
  const event = adapter.parseEvent(value);
256
+ if (oversized && (event.kind === "terminal" || event.kind === "failure")) {
257
+ // Terminal/verdict and failure frames are load-bearing: an oversized
258
+ // one is treated as a protocol violation, same as before this fix.
259
+ fail("protocol-unsupported", "provider-event-too-large");
260
+ return;
261
+ }
262
+ if (oversized) {
263
+ // Non-terminal (progress/neutral/unsupported) oversized frames are
264
+ // dropped rather than failing the whole run -- a provider that echoes
265
+ // e.g. a large lockfile read in a progress frame must not discard an
266
+ // otherwise-complete review verdict. See catalog/agent/rules
267
+ // /no-timeout-as-fix.md: the byte bound stays in place, we just stop
268
+ // treating "one big frame" as fatal to the whole run.
269
+ recordDroppedProviderEvent(`provider-event-too-large-${event.kind}`);
270
+ return;
271
+ }
244
272
  if (event.kind === "unsupported") {
245
273
  fail("protocol-unsupported", "unknown-provider-event");
246
274
  return;
@@ -249,6 +277,7 @@ export async function superviseReviewExecution(input, adapter, deps = {}) {
249
277
  fail("provider-failure", canonicalizeProviderFailureCode(event.code), {
250
278
  ...(event.limitWindow === undefined ? {} : { limitWindow: event.limitWindow }),
251
279
  ...(event.retryAfterMs === undefined ? {} : { retryAfterMs: event.retryAfterMs }),
280
+ ...(event.message === undefined ? {} : { providerErrorDetail: event.message }),
252
281
  });
253
282
  return;
254
283
  }
@@ -324,6 +353,7 @@ export async function superviseReviewExecution(input, adapter, deps = {}) {
324
353
  fail("provider-failure", canonicalizeProviderFailureCode(event.code), {
325
354
  ...(event.limitWindow === undefined ? {} : { limitWindow: event.limitWindow }),
326
355
  ...(event.retryAfterMs === undefined ? {} : { retryAfterMs: event.retryAfterMs }),
356
+ ...(event.message === undefined ? {} : { providerErrorDetail: event.message }),
327
357
  });
328
358
  }
329
359
  };
@@ -425,9 +455,11 @@ export async function superviseReviewExecution(input, adapter, deps = {}) {
425
455
  markerMatched,
426
456
  ...(result === undefined ? {} : { result }),
427
457
  ...(terminalFailureCode === undefined ? {} : { failureCode: terminalFailureCode }),
458
+ ...(providerErrorDetail === undefined ? {} : { providerErrorDetail }),
428
459
  ...(limitWindow === undefined ? {} : { limitWindow }),
429
460
  ...(retryAfterMs === undefined ? {} : { retryAfterMs }),
430
461
  summaryPath: artifacts.summaryPath,
462
+ ...(droppedEventWarnings.length > 0 ? { warnings: [...droppedEventWarnings] } : {}),
431
463
  };
432
464
  }
433
465
  function recordReviewProcessStart(reviewChild, processGroupId) {
@@ -19,6 +19,12 @@ export interface ReviewRunInput {
19
19
  readonly cwd?: string;
20
20
  readonly registryRoot?: string;
21
21
  readonly signal?: AbortSignal;
22
+ /**
23
+ * Isolated opencode auth store directory for this spawn. When set, the
24
+ * opencode adapter injects it as `XDG_DATA_HOME` so the child reads a
25
+ * per-account `auth.json` instead of the user's global credential store.
26
+ */
27
+ readonly authDataDir?: string;
22
28
  }
23
29
  export interface ReviewInvocation {
24
30
  readonly command: string;
@@ -41,6 +47,7 @@ export type ReviewAdapterEvent = {
41
47
  } | {
42
48
  readonly kind: "failure";
43
49
  readonly code: string;
50
+ readonly message?: string;
44
51
  readonly limitWindow?: ReviewLimitWindow;
45
52
  readonly retryAfterMs?: number;
46
53
  } | {
@@ -70,7 +77,9 @@ export interface ReviewRunSummary {
70
77
  readonly markerMatched: boolean | null;
71
78
  readonly result?: string;
72
79
  readonly failureCode?: string;
80
+ readonly providerErrorDetail?: string;
73
81
  readonly limitWindow?: ReviewLimitWindow;
74
82
  readonly retryAfterMs?: number;
75
83
  readonly summaryPath: string;
84
+ readonly warnings?: readonly string[];
76
85
  }
@@ -0,0 +1,24 @@
1
+ import { type OpencodeAccountAvailabilityState } from "./availability.js";
2
+ export interface OpencodeAccount {
3
+ readonly id: string;
4
+ readonly secretName: string;
5
+ readonly key: string;
6
+ }
7
+ export interface OpencodeAccountSelection {
8
+ readonly account: OpencodeAccount;
9
+ readonly cooled: boolean;
10
+ readonly waitMs?: number;
11
+ }
12
+ /**
13
+ * Discover opencode-go accounts from Doppler-sourced secrets shaped
14
+ * `OPENCODE_GO_KEY_<ID>`. Sorted by id ascending, de-duplicated by id.
15
+ */
16
+ export declare function resolveOpencodeAccounts(secrets: Readonly<Record<string, string>>): readonly OpencodeAccount[];
17
+ export declare function opencodeAccountCanonicalId(id: string): string;
18
+ export declare function opencodeAccountDataDir(id: string, stateRoot?: string): string;
19
+ /**
20
+ * Select the account to use for the next opencode-go invocation. Callers must
21
+ * prune expired suppressions in `state` before calling this (via
22
+ * `pruneExpiredOpencodeAccountSuppressions`).
23
+ */
24
+ export declare function selectActiveOpencodeAccount(accounts: readonly OpencodeAccount[], state: OpencodeAccountAvailabilityState, nowMs?: number): OpencodeAccountSelection | undefined;