@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
@@ -0,0 +1,49 @@
1
+ import { z } from "zod";
2
+ import { readCurrentSessionInfo } from "#session-memory/current-session.js";
3
+ import { createSummaryOutputSchema, createSummaryResult } from "./_shared/result.js";
4
+ const inputSchema = z
5
+ .object({
6
+ cwd: z.string().optional(),
7
+ })
8
+ .strict();
9
+ const outputSchema = createSummaryOutputSchema({
10
+ details: z.object({
11
+ sessionId: z.string(),
12
+ executionModels: z.array(z.string()),
13
+ }),
14
+ }).extend({
15
+ sessionId: z.string(),
16
+ executionModels: z.array(z.string()),
17
+ });
18
+ function effectiveCwd(cwd) {
19
+ return cwd ?? process.env.CLAUDE_PROJECT_DIR ?? process.cwd();
20
+ }
21
+ const tool = {
22
+ name: "wp_session_info",
23
+ description: "Report the current agent session id and every distinct observed execution model for the PR AI/model disclosure contract. Structured equivalent of `wp session-info` (prefers a live host session id, then falls back to stored session-memory capture for this worktree and its sibling worktrees). Feed `sessionId` straight into `wp_pr_upsert`'s `description.sessionId` — that tool also auto-resolves it via the same underlying lookup when omitted, so calling this tool first is optional. An empty `sessionId` means no live or captured session was found; use `Session-skip: SKIP <reason>` in the disclosure instead. Prefer `wp_session_id` when you only need the id, not the model history — it skips the cross-worktree scan this tool does.",
24
+ inputSchema,
25
+ outputSchema,
26
+ annotations: {
27
+ title: "Session info",
28
+ readOnlyHint: true,
29
+ destructiveHint: false,
30
+ idempotentHint: true,
31
+ openWorldHint: false,
32
+ },
33
+ handler: async (raw) => {
34
+ const input = inputSchema.parse(raw ?? {});
35
+ const info = readCurrentSessionInfo(effectiveCwd(input.cwd), process.env);
36
+ const executionModels = [...info.models];
37
+ const payload = {
38
+ passed: true,
39
+ summary: info.sessionId
40
+ ? `session id ${info.sessionId} with ${executionModels.length} observed model${executionModels.length === 1 ? "" : "s"}`
41
+ : "no live or captured session id found; use Session-skip in the PR disclosure",
42
+ sessionId: info.sessionId,
43
+ executionModels,
44
+ details: { sessionId: info.sessionId, executionModels },
45
+ };
46
+ return createSummaryResult(payload);
47
+ },
48
+ };
49
+ export default tool;
@@ -0,0 +1,23 @@
1
+ import type { TransformContext, TransformResult } from "./index.js";
2
+ /**
3
+ * `wp audit <kind>` writes the full `formatRepoAuditReport` output (title +
4
+ * one bullet per violation) into its CLI log. Every audit kind normalizes to
5
+ * the shared `"audit"` tool name (see `normalizeToolName`), so without a
6
+ * dedicated transform that output fell through to `genericTransform`, whose
7
+ * error/fail keyword heuristic drops any violation bullet that doesn't
8
+ * literally contain "error"/"fail"/"✗"/"✘" — silently discarding most
9
+ * violation detail even when the full report is a handful of short lines.
10
+ *
11
+ * This transform recognizes the bullet-list shape directly so violation text
12
+ * survives regardless of wording, then defers to the same size-based
13
+ * clip/elision policy every other transform uses (`createTransformResult` /
14
+ * `clipRawOutput`'s existing byte threshold) to decide whether the (now
15
+ * correctly extracted) violation list is short enough to print inline or
16
+ * long enough to still defer to `wp logs audit`.
17
+ *
18
+ * Audit output that isn't shaped like a violation-bullet report (for example
19
+ * raw subprocess output from `mutation`/`bundle-budget`) has no matching
20
+ * bullet lines, so this falls back to the previous generic keyword-based
21
+ * behavior unchanged.
22
+ */
23
+ export declare function auditTransform(rawOutput: string | undefined, context: TransformContext): TransformResult;
@@ -0,0 +1,47 @@
1
+ import { createTransformResult } from "./metadata.js";
2
+ import { genericTransform } from "./generic.js";
3
+ // `formatRepoAuditReport` (src/audit/repo-guardrails.ts) always renders one
4
+ // violation per line as `- <location><metadata><message><fixability>`. That
5
+ // bullet marker is the one structural signal every repo-audit report shares,
6
+ // regardless of which audit kind produced it or whether the violation text
7
+ // happens to contain the words "error"/"fail" that the generic fallback
8
+ // keys off of.
9
+ const VIOLATION_LINE_RE = /^-\s+(.+)$/u;
10
+ /**
11
+ * `wp audit <kind>` writes the full `formatRepoAuditReport` output (title +
12
+ * one bullet per violation) into its CLI log. Every audit kind normalizes to
13
+ * the shared `"audit"` tool name (see `normalizeToolName`), so without a
14
+ * dedicated transform that output fell through to `genericTransform`, whose
15
+ * error/fail keyword heuristic drops any violation bullet that doesn't
16
+ * literally contain "error"/"fail"/"✗"/"✘" — silently discarding most
17
+ * violation detail even when the full report is a handful of short lines.
18
+ *
19
+ * This transform recognizes the bullet-list shape directly so violation text
20
+ * survives regardless of wording, then defers to the same size-based
21
+ * clip/elision policy every other transform uses (`createTransformResult` /
22
+ * `clipRawOutput`'s existing byte threshold) to decide whether the (now
23
+ * correctly extracted) violation list is short enough to print inline or
24
+ * long enough to still defer to `wp logs audit`.
25
+ *
26
+ * Audit output that isn't shaped like a violation-bullet report (for example
27
+ * raw subprocess output from `mutation`/`bundle-budget`) has no matching
28
+ * bullet lines, so this falls back to the previous generic keyword-based
29
+ * behavior unchanged.
30
+ */
31
+ export function auditTransform(rawOutput, context) {
32
+ if (!rawOutput)
33
+ return {};
34
+ const failures = extractViolationBullets(rawOutput);
35
+ if (failures.length === 0)
36
+ return genericTransform(rawOutput, context);
37
+ return createTransformResult(rawOutput, failures.map((failure) => failure.message).join("\n"), context, { tier: 1, failures });
38
+ }
39
+ function extractViolationBullets(rawOutput) {
40
+ const failures = [];
41
+ for (const line of rawOutput.split(/\r?\n/u)) {
42
+ const message = VIOLATION_LINE_RE.exec(line)?.[1]?.trim();
43
+ if (message)
44
+ failures.push({ message });
45
+ }
46
+ return failures;
47
+ }
@@ -1,3 +1,4 @@
1
+ import { auditTransform } from "./audit.js";
1
2
  import { genericTransform } from "./generic.js";
2
3
  import { oxlintTransform } from "./oxlint.js";
3
4
  import { tscTransform } from "./tsc.js";
@@ -8,6 +9,7 @@ const builtInTransforms = new Map([
8
9
  ["lint-oxlint", oxlintTransform],
9
10
  ["typecheck", tscTransform],
10
11
  ["test", vitestTransform],
12
+ ["audit", auditTransform],
11
13
  ]);
12
14
  const transforms = new Map(builtInTransforms);
13
15
  export function registerTransform(toolName, transform) {
@@ -89,6 +89,8 @@
89
89
  "#compiler/*": "./compiler/*.js",
90
90
  "#review/*.js": "./review/*.js",
91
91
  "#review/*": "./review/*.js",
92
+ "#rust/*.js": "./rust/*.js",
93
+ "#rust/*": "./rust/*.js",
92
94
  "#telemetry/*.js": "./telemetry/*.js",
93
95
  "#telemetry/*": "./telemetry/*.js",
94
96
  "#codex/app-server/client": "./codex/app-server/client.js",
@@ -44,8 +44,14 @@ export interface PlatformResponse<T = unknown> {
44
44
  readonly status: number;
45
45
  readonly data: T;
46
46
  }
47
+ /**
48
+ * Injectable fetch call-signature used by platform transport and tests.
49
+ * Narrower than `typeof fetch` (which requires Bun/DOM statics like
50
+ * `preconnect`) so vitest mocks remain assignable without casts.
51
+ */
52
+ export type FetchLike = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
47
53
  export interface PlatformClientOptions {
48
- readonly fetchFn?: typeof globalThis.fetch;
54
+ readonly fetchFn?: FetchLike;
49
55
  readonly maxAttempts?: number;
50
56
  readonly baseRetryDelayMs?: number;
51
57
  readonly maxRetryDelayMs?: number;
@@ -1,9 +1,10 @@
1
1
  import { type AuthenticatedPlatformContractSnapshot, type PlatformContractAuthority, type PlatformContractAuthorityProbeResult, type PlatformContractSourceAdapter } from "./contract-provenance.js";
2
+ import { type FetchLike } from "./client.js";
2
3
  export interface GitHubContractSourceAdapterOptions {
3
4
  readonly token: string;
4
5
  readonly authority?: string;
5
6
  readonly apiUrl?: string;
6
- readonly fetchFn?: typeof globalThis.fetch;
7
+ readonly fetchFn?: FetchLike;
7
8
  readonly sleep?: (delayMs: number) => Promise<void>;
8
9
  }
9
10
  export declare class GitHubContractSourceAdapter implements PlatformContractSourceAdapter {
@@ -31,13 +31,33 @@ const SESSION_SKIP_PREFIX = /^Session-skip:/im;
31
31
  function trim(value) {
32
32
  return value?.trim() ?? "";
33
33
  }
34
+ /**
35
+ * Finds the value of a "<label>: <value>" list item at the head of a line,
36
+ * tolerating a leading "- " bullet and markdown emphasis wrapping the label
37
+ * (`**bold**`, `__bold__`, `*italic*`, `_italic_`) on either side of the
38
+ * colon — e.g. both "- Execution model(s): x" and
39
+ * "- **Execution model(s):** x" match. Still anchored to the start of the
40
+ * line (after stripping the bullet/emphasis decoration): a mention of the
41
+ * label mid-sentence never matches. Mirrors the CI `require_field` awk
42
+ * matcher in `.github/workflows/ci.agent-kit.yml` — keep the two in lockstep.
43
+ */
34
44
  function fieldValue(body, label) {
35
45
  const expected = label.toLowerCase();
36
46
  for (const line of body.split(/\r?\n/u)) {
37
- const normalized = line.toLowerCase();
38
- if (normalized.startsWith(`- ${expected}:`) || normalized.startsWith(`${expected}:`)) {
39
- return line.slice(line.indexOf(":") + 1).trim();
40
- }
47
+ const colonIndex = line.indexOf(":");
48
+ if (colonIndex === -1)
49
+ continue;
50
+ const before = line
51
+ .slice(0, colonIndex)
52
+ .replace(/^\s+/u, "")
53
+ .replace(/^-\s+/u, "")
54
+ .replace(/[*_]+/gu, "");
55
+ if (before.toLowerCase() !== expected)
56
+ continue;
57
+ return line
58
+ .slice(colonIndex + 1)
59
+ .replace(/^[*_]+/u, "")
60
+ .trim();
41
61
  }
42
62
  return undefined;
43
63
  }
@@ -9,7 +9,7 @@ import { createHash } from "node:crypto";
9
9
  import { existsSync, lstatSync, readFileSync } from "node:fs";
10
10
  import path from "node:path";
11
11
  import { evaluateReviewEvents, parseReviewEventLog } from "./events.js";
12
- import { createDeliverySubjectAtRef, createLegacyPlanSubjectAtRef, createPlanSubjectAtRef, resolveReviewCommit, } from "./subject.js";
12
+ import { createDeliverySubjectAtRef, createLegacyPlanSubjectAtRef, createPlanSubjectAtRef, createPreTagsStripPlanSubjectAtRef, resolveReviewCommit, } from "./subject.js";
13
13
  const GIT_TIMEOUT_MS = 5_000;
14
14
  const GIT_MAX_BUFFER_BYTES = 128 * 1024 * 1024;
15
15
  const REGULAR_MODE_PATTERN = /^100(?:644|755)$/u;
@@ -60,7 +60,7 @@ function assertStableSlug(slug) {
60
60
  }
61
61
  function locateBlueprint(entries, stableSlug) {
62
62
  assertStableSlug(stableSlug);
63
- const suffixPattern = new RegExp(`(?:^|/)${LIFECYCLE_SEGMENTS}/${stableSlug.replaceAll("/", "\\/")}/_overview\\.md$`, "u");
63
+ const suffixPattern = new RegExp(`(?:^|/)${LIFECYCLE_SEGMENTS}/${stableSlug.replaceAll("/", "\\/")}(?:/_overview\\.md|\\.md)$`, "u");
64
64
  const matches = entries.filter((entry) => suffixPattern.test(entry.path) &&
65
65
  REGULAR_MODE_PATTERN.test(entry.mode) &&
66
66
  entry.type === "blob");
@@ -69,6 +69,9 @@ function locateBlueprint(entries, stableSlug) {
69
69
  }
70
70
  return matches[0];
71
71
  }
72
+ function ledgerDirectoryForBlueprintPath(blueprintPath) {
73
+ return blueprintPath.endsWith("/_overview.md") ? path.posix.dirname(blueprintPath) : null;
74
+ }
72
75
  function readBlob(cwd, entry) {
73
76
  if (!REGULAR_MODE_PATTERN.test(entry.mode) || entry.type !== "blob") {
74
77
  throw new Error(`Review authority path is not a regular blob: ${entry.path}`);
@@ -115,14 +118,16 @@ export function readReviewAuthorityAtRef(cwd, ref, stableSlug, purpose) {
115
118
  const commit = resolveReviewCommit(cwd, ref);
116
119
  const entries = parseRefTree(cwd, commit);
117
120
  const overview = locateBlueprint(entries, stableSlug);
118
- const blueprintDirectory = path.posix.dirname(overview.path);
119
- const ledgerPath = path.posix.join(blueprintDirectory, "review-events.jsonl");
120
- const ledger = entries.find((entry) => entry.path === ledgerPath);
121
+ const blueprintDirectory = ledgerDirectoryForBlueprintPath(overview.path);
122
+ const ledgerPath = blueprintDirectory === null ? null : path.posix.join(blueprintDirectory, "review-events.jsonl");
123
+ const ledger = ledgerPath === null ? undefined : entries.find((entry) => entry.path === ledgerPath);
121
124
  const events = ledger ? parseReviewEventLog(readBlob(cwd, ledger).toString("utf8")) : [];
122
125
  if (events.some((event) => event.blueprintSlug !== stableSlug)) {
123
126
  throw new Error(`Review event ledger contains an event for a different blueprint: ${ledgerPath}`);
124
127
  }
125
- const { compromisedIds, issues } = markCompromisedArtifactEvents(cwd, entries, blueprintDirectory, events);
128
+ const { compromisedIds, issues } = blueprintDirectory === null
129
+ ? { compromisedIds: new Set(), issues: [] }
130
+ : markCompromisedArtifactEvents(cwd, entries, blueprintDirectory, events);
126
131
  const subject = purpose === "plan"
127
132
  ? createPlanSubjectAtRef(cwd, commit, stableSlug, overview.path)
128
133
  : createDeliverySubjectAtRef(cwd, commit, stableSlug, overview.path);
@@ -133,18 +138,23 @@ export function readReviewAuthorityAtRef(cwd, ref, stableSlug, purpose) {
133
138
  subjectDigest: subject.digest,
134
139
  }, compromisedIds);
135
140
  if (purpose === "plan" && evaluation.approvals.length === 0) {
136
- // Digest-compatibility fallback: events recorded before the lifecycle
137
- // stripped-key extension bound their subjectDigest to the legacy
138
- // normalization. Accept those for the same tracked content; new events
139
- // always record the current digest (see legacyNormalizePlanSubjectContent).
140
- const legacySubject = createLegacyPlanSubjectAtRef(cwd, commit, stableSlug, overview.path);
141
- if (legacySubject.digest !== subject.digest) {
141
+ // Digest-compatibility fallbacks (newest-first after current):
142
+ // 1) pre-tags-strip: lifecycle strip without advisory `tags` (approvals recorded
143
+ // after the stripped-key extension but before the tags strip)
144
+ // 2) legacy: status + completed_at only (pre-extension)
145
+ // New events always record the current digest (with tags strip).
146
+ for (const make of [createPreTagsStripPlanSubjectAtRef, createLegacyPlanSubjectAtRef]) {
147
+ const alt = make(cwd, commit, stableSlug, overview.path);
148
+ if (alt.digest === subject.digest)
149
+ continue;
142
150
  evaluation = evaluateReviewEvents(events, {
143
151
  purpose,
144
152
  blueprintSlug: stableSlug,
145
- subjectScheme: legacySubject.scheme,
146
- subjectDigest: legacySubject.digest,
153
+ subjectScheme: alt.scheme,
154
+ subjectDigest: alt.digest,
147
155
  }, compromisedIds);
156
+ if (evaluation.approvals.length > 0)
157
+ break;
148
158
  }
149
159
  }
150
160
  return {
@@ -160,6 +170,9 @@ export function readReviewAuthorityAtRef(cwd, ref, stableSlug, purpose) {
160
170
  };
161
171
  }
162
172
  export function readWorkingReviewEvents(blueprintPath, stableSlug) {
173
+ if (path.basename(blueprintPath) !== "_overview.md") {
174
+ return { events: [], compromisedIds: new Set(), issues: [] };
175
+ }
163
176
  const ledgerPath = path.join(path.dirname(blueprintPath), "review-events.jsonl");
164
177
  if (!existsSync(ledgerPath))
165
178
  return { events: [], compromisedIds: new Set(), issues: [] };
@@ -45,7 +45,40 @@ export declare function recordReviewAvailability(projectRoot: string, event: Rev
45
45
  export declare function recordReviewAvailabilityWithLock(projectRoot: string, event: ReviewAvailabilityEvent): Promise<ReviewAvailabilityState>;
46
46
  export declare function isProviderSuppressed(state: ReviewAvailabilityState, provider: ReviewProvider): boolean;
47
47
  export declare function suppressedReviewers(state: ReviewAvailabilityState): Set<string>;
48
+ /**
49
+ * Backstop age for suppressions recorded without a parseable retry window.
50
+ *
51
+ * Without this, a provider/account failure that never set `retryAfterMs`
52
+ * (unparsable limit text, incomplete event payload) stays suppressed forever
53
+ * and permanently removes that surface from rotation. 24h is long enough that
54
+ * a real multi-hour/day window still wins when present, and short enough that
55
+ * a sticky unknown-limit entry self-heals on the next calendar day instead of
56
+ * requiring a manual availability reset.
57
+ */
58
+ export declare const MAX_SUPPRESSION_AGE_MS: number;
48
59
  export declare function pruneExpiredAvailabilitySuppressions(state: ReviewAvailabilityState, nowMs?: number): {
49
60
  state: ReviewAvailabilityState;
50
61
  pruned: string[];
51
62
  };
63
+ export declare const OPENCODE_ACCOUNT_AVAILABILITY_SCHEMA_VERSION = 1;
64
+ export interface OpencodeAccountAvailabilityState {
65
+ readonly schemaVersion: 1;
66
+ readonly updatedAt: string;
67
+ readonly accounts: Record<string, ReviewAvailabilityRecord>;
68
+ }
69
+ export interface OpencodeAccountAvailabilityEvent {
70
+ readonly limitWindow?: ReviewLimitWindow;
71
+ readonly retryAfterMs?: number;
72
+ readonly failureCode?: string;
73
+ readonly terminalClassification?: string;
74
+ readonly at?: string;
75
+ }
76
+ export declare function opencodeAccountAvailabilityPath(): string;
77
+ export declare function loadOpencodeAccountAvailability(): OpencodeAccountAvailabilityState;
78
+ export declare function isOpencodeAccountSuppressed(state: OpencodeAccountAvailabilityState, canonicalId: string): boolean;
79
+ export declare function recordOpencodeAccountAvailability(canonicalId: string, event: OpencodeAccountAvailabilityEvent): OpencodeAccountAvailabilityState;
80
+ export declare function recordOpencodeAccountAvailabilityWithLock(canonicalId: string, event: OpencodeAccountAvailabilityEvent): Promise<OpencodeAccountAvailabilityState>;
81
+ export declare function pruneExpiredOpencodeAccountSuppressions(state: OpencodeAccountAvailabilityState, nowMs?: number): {
82
+ state: OpencodeAccountAvailabilityState;
83
+ pruned: string[];
84
+ };
@@ -1,6 +1,6 @@
1
1
  import { existsSync, mkdirSync, readFileSync } from "node:fs";
2
2
  import path from "node:path";
3
- import { NotInGitRepoError, withLock } from "#paths/state-root.js";
3
+ import { getStateRoot, NotInGitRepoError, withLock } from "#paths/state-root.js";
4
4
  import { writeJsonFileAtomic } from "#shared-utils/write-json-file.js";
5
5
  export const REVIEW_AVAILABILITY_SCHEMA_VERSION = 1;
6
6
  const FAILURE_CLASSIFICATIONS = new Set(["provider-limit-reached", "provider-unavailable"]);
@@ -121,10 +121,7 @@ function isAvailabilitySuccess(event) {
121
121
  return SUCCESS_CLASSIFICATIONS.has(event.terminalClassification);
122
122
  }
123
123
  function isProviderWideOpenCodeFailure(event) {
124
- return (event.provider === "opencode" &&
125
- (event.failureCode === "opencode-go-usage-limit" ||
126
- event.reviewer === undefined ||
127
- event.reviewer === "opencode"));
124
+ return (event.provider === "opencode" && (event.reviewer === undefined || event.reviewer === "opencode"));
128
125
  }
129
126
  function nextRecord(previous, event, input) {
130
127
  const at = event.at ?? new Date().toISOString();
@@ -250,26 +247,50 @@ export function suppressedReviewers(state) {
250
247
  .filter((record) => record.suppressed)
251
248
  .map((record) => record.canonicalId));
252
249
  }
253
- export function pruneExpiredAvailabilitySuppressions(state, nowMs = Date.now()) {
254
- const pruned = [];
255
- const pruneMap = (records) => Object.fromEntries(Object.entries(records).map(([key, record]) => {
256
- if (record.suppressed && !suppressedRecordStillApplies(record)) {
250
+ /**
251
+ * Backstop age for suppressions recorded without a parseable retry window.
252
+ *
253
+ * Without this, a provider/account failure that never set `retryAfterMs`
254
+ * (unparsable limit text, incomplete event payload) stays suppressed forever
255
+ * and permanently removes that surface from rotation. 24h is long enough that
256
+ * a real multi-hour/day window still wins when present, and short enough that
257
+ * a sticky unknown-limit entry self-heals on the next calendar day instead of
258
+ * requiring a manual availability reset.
259
+ */
260
+ export const MAX_SUPPRESSION_AGE_MS = 24 * 60 * 60 * 1000;
261
+ /**
262
+ * Shared expiry predicate for a suppressed availability record: applies to
263
+ * both the reviewer/provider store and the opencode-go account store below.
264
+ * A record clears when its terminal classification is no longer a failure
265
+ * classification, or when its retry window (or the max-age backstop) has
266
+ * elapsed.
267
+ */
268
+ function suppressionExpired(record, nowMs) {
269
+ if (!record.suppressed)
270
+ return false;
271
+ if (!suppressedRecordStillApplies(record))
272
+ return true;
273
+ if (record.lastFailureAt === undefined)
274
+ return false;
275
+ const failureAt = Date.parse(record.lastFailureAt);
276
+ if (!Number.isFinite(failureAt))
277
+ return false;
278
+ const windowMs = record.retryAfterMs === undefined ? MAX_SUPPRESSION_AGE_MS : record.retryAfterMs;
279
+ return failureAt + windowMs <= nowMs;
280
+ }
281
+ function pruneRecordMap(records, nowMs, pruned) {
282
+ return Object.fromEntries(Object.entries(records).map(([key, record]) => {
283
+ if (suppressionExpired(record, nowMs)) {
257
284
  pruned.push(key);
258
285
  return [key, { ...record, suppressed: false }];
259
286
  }
260
- if (record.suppressed &&
261
- record.retryAfterMs !== undefined &&
262
- record.lastFailureAt !== undefined) {
263
- const failureAt = Date.parse(record.lastFailureAt);
264
- if (Number.isFinite(failureAt) && failureAt + record.retryAfterMs <= nowMs) {
265
- pruned.push(key);
266
- return [key, { ...record, suppressed: false }];
267
- }
268
- }
269
287
  return [key, record];
270
288
  }));
271
- const providers = pruneMap(state.providers);
272
- const reviewers = pruneMap(state.reviewers);
289
+ }
290
+ export function pruneExpiredAvailabilitySuppressions(state, nowMs = Date.now()) {
291
+ const pruned = [];
292
+ const providers = pruneRecordMap(state.providers, nowMs, pruned);
293
+ const reviewers = pruneRecordMap(state.reviewers, nowMs, pruned);
273
294
  if (pruned.length === 0)
274
295
  return { state, pruned };
275
296
  return {
@@ -282,3 +303,84 @@ export function pruneExpiredAvailabilitySuppressions(state, nowMs = Date.now())
282
303
  pruned,
283
304
  };
284
305
  }
306
+ // --- opencode-go multi-account cooldown store (user-scoped, cross-repo) ---
307
+ export const OPENCODE_ACCOUNT_AVAILABILITY_SCHEMA_VERSION = 1;
308
+ export function opencodeAccountAvailabilityPath() {
309
+ return path.join(getStateRoot(), "opencode-account-availability.json");
310
+ }
311
+ function emptyOpencodeAccountAvailability(now = new Date().toISOString()) {
312
+ return {
313
+ schemaVersion: OPENCODE_ACCOUNT_AVAILABILITY_SCHEMA_VERSION,
314
+ updatedAt: now,
315
+ accounts: {},
316
+ };
317
+ }
318
+ export function loadOpencodeAccountAvailability() {
319
+ const file = opencodeAccountAvailabilityPath();
320
+ if (!existsSync(file))
321
+ return emptyOpencodeAccountAvailability();
322
+ try {
323
+ const parsed = JSON.parse(readFileSync(file, "utf8"));
324
+ if (!isRecord(parsed) ||
325
+ parsed.schemaVersion !== OPENCODE_ACCOUNT_AVAILABILITY_SCHEMA_VERSION) {
326
+ return emptyOpencodeAccountAvailability();
327
+ }
328
+ return {
329
+ schemaVersion: OPENCODE_ACCOUNT_AVAILABILITY_SCHEMA_VERSION,
330
+ updatedAt: typeof parsed.updatedAt === "string" ? parsed.updatedAt : new Date().toISOString(),
331
+ accounts: normalizeRecordMap(parsed.accounts),
332
+ };
333
+ }
334
+ catch {
335
+ return emptyOpencodeAccountAvailability();
336
+ }
337
+ }
338
+ function saveOpencodeAccountAvailability(state) {
339
+ const file = opencodeAccountAvailabilityPath();
340
+ mkdirSync(path.dirname(file), { recursive: true });
341
+ writeJsonFileAtomic(file, state);
342
+ }
343
+ export function isOpencodeAccountSuppressed(state, canonicalId) {
344
+ return state.accounts[canonicalId]?.suppressed === true;
345
+ }
346
+ export function recordOpencodeAccountAvailability(canonicalId, event) {
347
+ const at = event.at ?? new Date().toISOString();
348
+ const current = loadOpencodeAccountAvailability();
349
+ const accounts = { ...current.accounts };
350
+ accounts[canonicalId] = {
351
+ canonicalId,
352
+ provider: "opencode",
353
+ terminalClassification: event.terminalClassification ?? "provider-limit-reached",
354
+ ...(event.failureCode ? { failureCode: event.failureCode } : {}),
355
+ ...(event.limitWindow ? { limitWindow: event.limitWindow } : {}),
356
+ ...(event.retryAfterMs === undefined ? {} : { retryAfterMs: event.retryAfterMs }),
357
+ lastFailureAt: at,
358
+ suppressed: true,
359
+ suppressionScope: "provider",
360
+ suppressedAt: at,
361
+ };
362
+ const next = {
363
+ schemaVersion: OPENCODE_ACCOUNT_AVAILABILITY_SCHEMA_VERSION,
364
+ updatedAt: at,
365
+ accounts,
366
+ };
367
+ saveOpencodeAccountAvailability(next);
368
+ return next;
369
+ }
370
+ export function recordOpencodeAccountAvailabilityWithLock(canonicalId, event) {
371
+ return withLock("user", () => recordOpencodeAccountAvailability(canonicalId, event));
372
+ }
373
+ export function pruneExpiredOpencodeAccountSuppressions(state, nowMs = Date.now()) {
374
+ const pruned = [];
375
+ const accounts = pruneRecordMap(state.accounts, nowMs, pruned);
376
+ if (pruned.length === 0)
377
+ return { state, pruned };
378
+ return {
379
+ state: {
380
+ ...state,
381
+ updatedAt: new Date(nowMs).toISOString(),
382
+ accounts,
383
+ },
384
+ pruned,
385
+ };
386
+ }
@@ -0,0 +1,27 @@
1
+ import { executeAllowedWpCommand } from "#trust/command-runner.js";
2
+ import type { ReviewTargetCheckout } from "#review/execution/review-checkout.js";
3
+ export interface ReviewGateVerificationCommand {
4
+ readonly command: string;
5
+ readonly outcome: "passed" | "failed" | "timed-out" | "deferred";
6
+ readonly durationMs: number;
7
+ readonly exitCode: number | null;
8
+ readonly signal?: string;
9
+ }
10
+ export interface ReviewGateVerification {
11
+ readonly status: "passed" | "failed";
12
+ readonly commands: readonly ReviewGateVerificationCommand[];
13
+ readonly failureCode?: string;
14
+ readonly logPath?: string;
15
+ }
16
+ export interface VerifyDeliveryGatesInput {
17
+ readonly purpose: "plan" | "delivery";
18
+ readonly blueprintContent: string;
19
+ readonly projectRoot: string;
20
+ readonly gateId: string;
21
+ readonly checkout: ReviewTargetCheckout;
22
+ readonly signal?: AbortSignal;
23
+ readonly executeCommand?: typeof executeAllowedWpCommand;
24
+ }
25
+ export declare function verifyDeliveryPromotionGates(input: VerifyDeliveryGatesInput): ReviewGateVerification | undefined;
26
+ export declare function verificationPromptSummary(verification: ReviewGateVerification | undefined): string | undefined;
27
+ export declare function verificationArtifactSection(verification: ReviewGateVerification | undefined): string;
@@ -0,0 +1,115 @@
1
+ import path from "node:path";
2
+ import { executeAllowedWpCommand, relativeCommandLogPath, } from "#trust/command-runner.js";
3
+ import { parseTrustDossier } from "#trust/dossier.js";
4
+ import { parseAllowedWpCommand } from "#trust/gates.js";
5
+ export function verifyDeliveryPromotionGates(input) {
6
+ if (input.purpose !== "delivery")
7
+ return undefined;
8
+ const parsed = parseTrustDossier(input.blueprintContent);
9
+ if (parsed.violations.length > 0) {
10
+ if (parsed.violations.length === 1 &&
11
+ parsed.violations[0]?.section === "Trust Dossier" &&
12
+ parsed.violations[0]?.message === "missing Trust Dossier section") {
13
+ return undefined;
14
+ }
15
+ return {
16
+ status: "failed",
17
+ commands: [],
18
+ failureCode: "malformed-trust-dossier",
19
+ };
20
+ }
21
+ const gates = parsed.dossier?.gates ?? [];
22
+ if (gates.length === 0)
23
+ return undefined;
24
+ const commands = [];
25
+ const executeCommand = input.executeCommand ?? executeAllowedWpCommand;
26
+ const logRoot = path.join(input.projectRoot, ".webpresso", "logs", "delivery-verification", input.gateId);
27
+ for (const gate of gates) {
28
+ if (input.signal?.aborted) {
29
+ return {
30
+ status: "failed",
31
+ commands,
32
+ failureCode: "verification-cancelled",
33
+ };
34
+ }
35
+ if (isApprovalDependentLifecycleGate(gate.command)) {
36
+ commands.push({
37
+ command: gate.command,
38
+ outcome: "deferred",
39
+ durationMs: 0,
40
+ exitCode: null,
41
+ });
42
+ continue;
43
+ }
44
+ const result = executeCommand({
45
+ command: gate.command,
46
+ executionRoot: input.checkout.cwd,
47
+ logRoot,
48
+ env: { CI: "1", NO_COLOR: "1", WP_FORCE_SOURCE: "1" },
49
+ });
50
+ commands.push(publicCommandResult(result));
51
+ if (result.outcome !== "passed") {
52
+ return {
53
+ status: "failed",
54
+ commands,
55
+ failureCode: result.failureCode ?? "verification-command-failed",
56
+ logPath: relativeCommandLogPath(input.projectRoot, result.logPath),
57
+ };
58
+ }
59
+ const changes = input.checkout.changes();
60
+ if (changes.length > 0) {
61
+ return {
62
+ status: "failed",
63
+ commands,
64
+ failureCode: "verification-worktree-contaminated",
65
+ logPath: relativeCommandLogPath(input.projectRoot, result.logPath),
66
+ };
67
+ }
68
+ }
69
+ return {
70
+ status: "passed",
71
+ commands,
72
+ };
73
+ }
74
+ export function verificationPromptSummary(verification) {
75
+ if (!verification)
76
+ return undefined;
77
+ if (verification.status !== "passed")
78
+ return undefined;
79
+ return [
80
+ "Host verification before outside review: passed.",
81
+ ...verification.commands.map((command, index) => `${index + 1}. ${command.command}: ${command.outcome === "deferred" ? "deferred until review publication" : "passed"}`),
82
+ ].join("\n");
83
+ }
84
+ function isApprovalDependentLifecycleGate(command) {
85
+ try {
86
+ const [, subcommand, kind] = parseAllowedWpCommand(command);
87
+ return subcommand === "audit" && kind === "blueprint-lifecycle";
88
+ }
89
+ catch {
90
+ return false;
91
+ }
92
+ }
93
+ export function verificationArtifactSection(verification) {
94
+ if (!verification)
95
+ return "";
96
+ const lines = [
97
+ "## Host Verification",
98
+ "",
99
+ `Status: ${verification.status}`,
100
+ ...verification.commands.map((command, index) => `${index + 1}. ${command.command} -> ${command.outcome} (${command.durationMs}ms, exit ${command.exitCode ?? "null"})`),
101
+ ...(verification.failureCode ? [`Failure code: ${verification.failureCode}`] : []),
102
+ ...(verification.logPath ? [`Log: ${verification.logPath}`] : []),
103
+ "",
104
+ ];
105
+ return `${lines.join("\n")}\n`;
106
+ }
107
+ function publicCommandResult(result) {
108
+ return {
109
+ command: result.command,
110
+ outcome: result.outcome,
111
+ durationMs: result.durationMs,
112
+ exitCode: result.exitCode,
113
+ ...(result.signal ? { signal: result.signal } : {}),
114
+ };
115
+ }