@webpresso/agent-kit 3.1.30 → 3.2.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.

Potentially problematic release.


This version of @webpresso/agent-kit might be problematic. Click here for more details.

Files changed (257) hide show
  1. package/README.md +53 -15
  2. package/bin/_run.js +179 -19
  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/ci-guardrails-wiring.d.ts +22 -0
  17. package/dist/esm/audit/ci-guardrails-wiring.js +90 -0
  18. package/dist/esm/audit/ci-test-perf.js +30 -0
  19. package/dist/esm/audit/command-surface.js +4 -2
  20. package/dist/esm/audit/host-substitution-risk.d.ts +2 -0
  21. package/dist/esm/audit/host-substitution-risk.js +256 -0
  22. package/dist/esm/audit/registry.d.ts +4 -4
  23. package/dist/esm/audit/registry.js +8 -1
  24. package/dist/esm/audit/toolchain-isolation.js +1 -2
  25. package/dist/esm/audit/typescript-version.d.ts +2 -0
  26. package/dist/esm/audit/typescript-version.js +123 -0
  27. package/dist/esm/blueprint/core/parser.js +43 -1
  28. package/dist/esm/blueprint/core/validation/criteria.d.ts +14 -0
  29. package/dist/esm/blueprint/core/validation/criteria.js +41 -2
  30. package/dist/esm/blueprint/core/validation/state.js +25 -14
  31. package/dist/esm/blueprint/db/migrations/run.js +4 -2
  32. package/dist/esm/blueprint/lifecycle/audit.d.ts +20 -0
  33. package/dist/esm/blueprint/lifecycle/audit.js +65 -10
  34. package/dist/esm/blueprint/lifecycle/canonicalize.d.ts +6 -0
  35. package/dist/esm/blueprint/lifecycle/canonicalize.js +135 -0
  36. package/dist/esm/blueprint/lifecycle/local.d.ts +2 -1
  37. package/dist/esm/blueprint/lifecycle/local.js +19 -3
  38. package/dist/esm/blueprint/lifecycle/review-provenance.js +36 -11
  39. package/dist/esm/blueprint/service/BlueprintCreationService.d.ts +1 -0
  40. package/dist/esm/blueprint/service/BlueprintCreationService.js +53 -4
  41. package/dist/esm/blueprint/service/blueprint-tech-debt-links.js +16 -2
  42. package/dist/esm/blueprint/trust/command-runner.d.ts +26 -0
  43. package/dist/esm/blueprint/trust/command-runner.js +148 -0
  44. package/dist/esm/blueprint/trust/gates.d.ts +7 -0
  45. package/dist/esm/blueprint/trust/gates.js +20 -0
  46. package/dist/esm/blueprint/trust/promotion.d.ts +8 -0
  47. package/dist/esm/blueprint/trust/promotion.js +62 -112
  48. package/dist/esm/blueprint/trust/scaffold.d.ts +19 -0
  49. package/dist/esm/blueprint/trust/scaffold.js +64 -0
  50. package/dist/esm/blueprint/trust/validator.d.ts +1 -0
  51. package/dist/esm/blueprint/trust/validator.js +7 -1
  52. package/dist/esm/blueprint/utils/archive.js +27 -5
  53. package/dist/esm/blueprint/utils/document-paths.d.ts +18 -0
  54. package/dist/esm/blueprint/utils/document-paths.js +45 -12
  55. package/dist/esm/build/cli-mcp-parity.js +10 -0
  56. package/dist/esm/cli/bundle/commands/blueprint.d.ts +1 -0
  57. package/dist/esm/cli/bundle/commands/blueprint.js +1 -0
  58. package/dist/esm/cli/cli.d.ts +1 -1
  59. package/dist/esm/cli/cli.js +34 -0
  60. package/dist/esm/cli/commands/agent-launch.d.ts +6 -1
  61. package/dist/esm/cli/commands/agent-launch.js +82 -4
  62. package/dist/esm/cli/commands/audit-core.d.ts +1 -1
  63. package/dist/esm/cli/commands/audit-core.js +8 -1
  64. package/dist/esm/cli/commands/audit.js +17 -5
  65. package/dist/esm/cli/commands/blueprint/execution.js +18 -6
  66. package/dist/esm/cli/commands/blueprint/mutations.js +59 -40
  67. package/dist/esm/cli/commands/blueprint/router-dispatch.d.ts +2 -1
  68. package/dist/esm/cli/commands/blueprint/router-dispatch.js +20 -1
  69. package/dist/esm/cli/commands/blueprint/router-output.d.ts +21 -0
  70. package/dist/esm/cli/commands/blueprint/router-output.js +48 -17
  71. package/dist/esm/cli/commands/blueprint/router.d.ts +7 -0
  72. package/dist/esm/cli/commands/blueprint/router.js +13 -1
  73. package/dist/esm/cli/commands/ci-preflight.d.ts +13 -2
  74. package/dist/esm/cli/commands/ci-preflight.js +6 -3
  75. package/dist/esm/cli/commands/compile.js +5 -31
  76. package/dist/esm/cli/commands/dash/delivery-status.d.ts +1 -0
  77. package/dist/esm/cli/commands/dash/delivery-status.js +11 -4
  78. package/dist/esm/cli/commands/doctor.js +5 -0
  79. package/dist/esm/cli/commands/hook.d.ts +5 -0
  80. package/dist/esm/cli/commands/hook.js +15 -9
  81. package/dist/esm/cli/commands/init/config.js +2 -2
  82. package/dist/esm/cli/commands/init/convergence-apply.d.ts +24 -0
  83. package/dist/esm/cli/commands/init/convergence-apply.js +221 -0
  84. package/dist/esm/cli/commands/init/convergence-state.d.ts +13 -0
  85. package/dist/esm/cli/commands/init/convergence-state.js +54 -0
  86. package/dist/esm/cli/commands/init/convergence.d.ts +23 -0
  87. package/dist/esm/cli/commands/init/convergence.js +57 -0
  88. package/dist/esm/cli/commands/init/git-convergence.d.ts +19 -0
  89. package/dist/esm/cli/commands/init/git-convergence.js +168 -0
  90. package/dist/esm/cli/commands/init/gitignore-patcher.js +2 -0
  91. package/dist/esm/cli/commands/init/grok-privacy-smoke.d.ts +52 -0
  92. package/dist/esm/cli/commands/init/grok-privacy-smoke.js +91 -0
  93. package/dist/esm/cli/commands/init/host-native-proof.d.ts +18 -0
  94. package/dist/esm/cli/commands/init/host-native-proof.js +88 -0
  95. package/dist/esm/cli/commands/init/host-visibility.d.ts +2 -2
  96. package/dist/esm/cli/commands/init/host-visibility.js +94 -5
  97. package/dist/esm/cli/commands/init/index.d.ts +1 -0
  98. package/dist/esm/cli/commands/init/index.js +326 -173
  99. package/dist/esm/cli/commands/init/mcp-spec.d.ts +57 -0
  100. package/dist/esm/cli/commands/init/mcp-spec.js +273 -0
  101. package/dist/esm/cli/commands/init/merge.d.ts +1 -0
  102. package/dist/esm/cli/commands/init/merge.js +30 -1
  103. package/dist/esm/cli/commands/init/plugin-cache-prune.d.ts +0 -3
  104. package/dist/esm/cli/commands/init/plugin-cache-prune.js +0 -1
  105. package/dist/esm/cli/commands/init/scaffold-agents-md.d.ts +1 -1
  106. package/dist/esm/cli/commands/init/scaffold-agents-md.js +9 -3
  107. package/dist/esm/cli/commands/init/scaffold-base-kit.d.ts +11 -0
  108. package/dist/esm/cli/commands/init/scaffold-base-kit.js +56 -1
  109. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/emitters/grok.d.ts +14 -8
  110. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/emitters/grok.js +41 -24
  111. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/index.d.ts +2 -0
  112. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/index.js +51 -23
  113. package/dist/esm/cli/commands/init/scaffolders/codex-mcp/index.d.ts +12 -31
  114. package/dist/esm/cli/commands/init/scaffolders/codex-mcp/index.js +288 -202
  115. package/dist/esm/cli/commands/init/scaffolders/file-transaction.d.ts +7 -0
  116. package/dist/esm/cli/commands/init/scaffolders/file-transaction.js +58 -0
  117. package/dist/esm/cli/commands/init/scaffolders/grok-mcp/index.d.ts +38 -0
  118. package/dist/esm/cli/commands/init/scaffolders/grok-mcp/index.js +73 -0
  119. package/dist/esm/cli/commands/init/scaffolders/opencode-plugin/index.d.ts +21 -0
  120. package/dist/esm/cli/commands/init/scaffolders/opencode-plugin/index.js +559 -63
  121. package/dist/esm/cli/commands/init/untracked-collision.d.ts +14 -0
  122. package/dist/esm/cli/commands/init/untracked-collision.js +42 -0
  123. package/dist/esm/cli/commands/opencode-rotate.d.ts +49 -0
  124. package/dist/esm/cli/commands/opencode-rotate.js +135 -0
  125. package/dist/esm/cli/commands/public-readiness.d.ts +11 -0
  126. package/dist/esm/cli/commands/public-readiness.js +42 -0
  127. package/dist/esm/cli/commands/review.d.ts +35 -3
  128. package/dist/esm/cli/commands/review.js +729 -209
  129. package/dist/esm/cli/commands/run-wait.d.ts +2 -0
  130. package/dist/esm/cli/commands/run-wait.js +122 -0
  131. package/dist/esm/cli/commands/setup-help.js +1 -1
  132. package/dist/esm/cli/commands/setup-repair-help.js +1 -1
  133. package/dist/esm/cli/commands/worktree/router-dispatch.js +13 -8
  134. package/dist/esm/cli/direct-provider-launch.js +9 -5
  135. package/dist/esm/cli/optional-tool-freshness.d.ts +27 -0
  136. package/dist/esm/cli/optional-tool-freshness.js +219 -9
  137. package/dist/esm/daemon/domains/git-actions.d.ts +29 -0
  138. package/dist/esm/daemon/domains/git-actions.js +136 -0
  139. package/dist/esm/daemon/domains/run-registry-interop.d.ts +16 -0
  140. package/dist/esm/daemon/domains/run-registry-interop.js +45 -0
  141. package/dist/esm/daemon/domains/sessions-search.d.ts +11 -0
  142. package/dist/esm/daemon/domains/sessions-search.js +29 -0
  143. package/dist/esm/daemon/handlers/blueprints-handlers.d.ts +5 -0
  144. package/dist/esm/daemon/handlers/blueprints-handlers.js +36 -0
  145. package/dist/esm/daemon/handlers/deploy-handlers.d.ts +6 -0
  146. package/dist/esm/daemon/handlers/deploy-handlers.js +5 -0
  147. package/dist/esm/daemon/handlers/fleet-handlers.d.ts +15 -0
  148. package/dist/esm/daemon/handlers/fleet-handlers.js +91 -0
  149. package/dist/esm/daemon/handlers/git-handlers.d.ts +6 -0
  150. package/dist/esm/daemon/handlers/git-handlers.js +27 -0
  151. package/dist/esm/daemon/handlers/product.d.ts +7 -0
  152. package/dist/esm/daemon/handlers/product.js +10 -119
  153. package/dist/esm/daemon/handlers/sessions-handlers.d.ts +12 -0
  154. package/dist/esm/daemon/handlers/sessions-handlers.js +31 -0
  155. package/dist/esm/daemon/handlers/shared.d.ts +5 -0
  156. package/dist/esm/daemon/handlers/shared.js +16 -0
  157. package/dist/esm/daemon/methods.d.ts +8 -1
  158. package/dist/esm/daemon/methods.js +7 -0
  159. package/dist/esm/daemon/protocol/contract.d.ts +43 -0
  160. package/dist/esm/daemon/protocol/contract.js +26 -0
  161. package/dist/esm/daemon/protocol/index.d.ts +1 -1
  162. package/dist/esm/daemon/protocol/index.js +1 -1
  163. package/dist/esm/hooks/__conformance__/matrix.d.ts +6 -0
  164. package/dist/esm/hooks/__conformance__/matrix.js +137 -0
  165. package/dist/esm/hooks/conformance/batch.js +5 -0
  166. package/dist/esm/hooks/doctor.d.ts +3 -3
  167. package/dist/esm/hooks/doctor.js +30 -15
  168. package/dist/esm/hooks/permission-request/index.js +9 -3
  169. package/dist/esm/hooks/permission-request/policy.d.ts +9 -0
  170. package/dist/esm/hooks/permission-request/policy.js +13 -0
  171. package/dist/esm/hooks/pretool-guard/dev-routing.d.ts +9 -0
  172. package/dist/esm/hooks/pretool-guard/dev-routing.js +41 -5
  173. package/dist/esm/hooks/pretool-guard/logger.d.ts +7 -0
  174. package/dist/esm/hooks/pretool-guard/logger.js +16 -0
  175. package/dist/esm/hooks/pretool-guard/runner.d.ts +6 -0
  176. package/dist/esm/hooks/pretool-guard/runner.js +68 -16
  177. package/dist/esm/hooks/pretool-guard/validators/blueprint.js +30 -10
  178. package/dist/esm/hooks/pretool-guard/validators/forbidden-commands.js +54 -0
  179. package/dist/esm/hooks/pretool-guard/validators/index.d.ts +7 -0
  180. package/dist/esm/hooks/pretool-guard/validators/index.js +21 -0
  181. package/dist/esm/hooks/pretool-guard/validators/path-contract.d.ts +19 -0
  182. package/dist/esm/hooks/pretool-guard/validators/path-contract.js +25 -8
  183. package/dist/esm/hooks/shared/guard-outcome.d.ts +149 -0
  184. package/dist/esm/hooks/shared/guard-outcome.js +215 -0
  185. package/dist/esm/hooks/shared/hook-bootstrap.d.ts +26 -0
  186. package/dist/esm/hooks/shared/hook-bootstrap.js +64 -1
  187. package/dist/esm/hooks/shared/types.d.ts +8 -5
  188. package/dist/esm/hooks/shared/types.js +23 -8
  189. package/dist/esm/hooks/stop/qa-changed-files.d.ts +11 -0
  190. package/dist/esm/hooks/stop/qa-changed-files.js +74 -0
  191. package/dist/esm/mcp/blueprint/_shared/lifecycle.js +14 -1
  192. package/dist/esm/mcp/blueprint/handlers/document-mutations.js +78 -4
  193. package/dist/esm/mcp/blueprint/handlers/review-log.js +8 -1
  194. package/dist/esm/mcp/blueprint/handlers/task-advance.js +113 -54
  195. package/dist/esm/mcp/blueprint/handlers/task-verify.js +39 -19
  196. package/dist/esm/mcp/blueprint/registration.js +3 -2
  197. package/dist/esm/mcp/server.d.ts +1 -0
  198. package/dist/esm/mcp/server.js +27 -0
  199. package/dist/esm/mcp/tools/_names.d.ts +1 -1
  200. package/dist/esm/mcp/tools/_names.js +5 -0
  201. package/dist/esm/mcp/tools/_registry.js +10 -0
  202. package/dist/esm/mcp/tools/_shared/audit-kinds.d.ts +1 -1
  203. package/dist/esm/mcp/tools/_shared/result.js +44 -1
  204. package/dist/esm/mcp/tools/audit.d.ts +4 -1
  205. package/dist/esm/mcp/tools/audit.js +30 -0
  206. package/dist/esm/mcp/tools/audits.d.ts +3 -0
  207. package/dist/esm/mcp/tools/ci-preflight.d.ts +50 -0
  208. package/dist/esm/mcp/tools/ci-preflight.js +183 -0
  209. package/dist/esm/mcp/tools/pr-upsert.js +39 -1
  210. package/dist/esm/mcp/tools/review-run.d.ts +28 -0
  211. package/dist/esm/mcp/tools/review-run.js +216 -0
  212. package/dist/esm/mcp/tools/run-wait.d.ts +86 -0
  213. package/dist/esm/mcp/tools/run-wait.js +593 -0
  214. package/dist/esm/mcp/tools/session-id.d.ts +3 -0
  215. package/dist/esm/mcp/tools/session-id.js +45 -0
  216. package/dist/esm/mcp/tools/session-info.d.ts +3 -0
  217. package/dist/esm/mcp/tools/session-info.js +49 -0
  218. package/dist/esm/output-transforms/audit.d.ts +23 -0
  219. package/dist/esm/output-transforms/audit.js +47 -0
  220. package/dist/esm/output-transforms/index.js +2 -0
  221. package/dist/esm/review/authority.js +13 -5
  222. package/dist/esm/review/availability.d.ts +22 -0
  223. package/dist/esm/review/availability.js +107 -20
  224. package/dist/esm/review/delivery-verifier.d.ts +27 -0
  225. package/dist/esm/review/delivery-verifier.js +115 -0
  226. package/dist/esm/review/events.d.ts +3 -2
  227. package/dist/esm/review/events.js +23 -3
  228. package/dist/esm/review/execution/adapters.js +26 -9
  229. package/dist/esm/review/execution/output-policy.d.ts +2 -0
  230. package/dist/esm/review/execution/output-policy.js +12 -0
  231. package/dist/esm/review/execution/review-checkout.js +87 -6
  232. package/dist/esm/review/execution/supervisor.js +5 -0
  233. package/dist/esm/review/execution/types.d.ts +8 -0
  234. package/dist/esm/review/opencode-account.d.ts +24 -0
  235. package/dist/esm/review/opencode-account.js +68 -0
  236. package/dist/esm/review/opencode-auth.d.ts +33 -0
  237. package/dist/esm/review/opencode-auth.js +79 -0
  238. package/dist/esm/review/subject.d.ts +12 -0
  239. package/dist/esm/review/subject.js +74 -22
  240. package/dist/esm/runtime/opencode-account-materializer.d.ts +34 -0
  241. package/dist/esm/runtime/opencode-account-materializer.js +33 -0
  242. package/dist/esm/status/snapshot.js +30 -19
  243. package/dist/esm/test/duration-sequencer.d.ts +20 -0
  244. package/dist/esm/test/duration-sequencer.js +54 -0
  245. package/dist/esm/test/shard-durations.json +922 -0
  246. package/dist/esm/test/worker-budget.d.ts +6 -0
  247. package/dist/esm/test/worker-budget.js +59 -1
  248. package/dist/esm/test-helpers/global-setup.d.ts +1 -1
  249. package/dist/esm/test-helpers/global-setup.js +68 -24
  250. package/dist/esm/ultragoal/service.d.ts +7 -0
  251. package/dist/esm/ultragoal/service.js +42 -2
  252. package/dist/esm/utils/write-json-file.js +3 -1
  253. package/dist/esm/worktrees/codex-project-trust.d.ts +15 -0
  254. package/dist/esm/worktrees/codex-project-trust.js +56 -0
  255. package/dist/esm/worktrees/registry.d.ts +8 -0
  256. package/dist/esm/worktrees/registry.js +18 -3
  257. package/package.json +16 -13
@@ -0,0 +1,14 @@
1
+ import type { ConvergeUnit } from "./convergence.js";
2
+ export type CollisionResolution = "none" | "tracked" | "removed-identical" | "removed-owned";
3
+ export interface CollisionResult {
4
+ readonly ok: boolean;
5
+ readonly resolution?: CollisionResolution;
6
+ readonly error?: string;
7
+ }
8
+ export interface ResolveCollisionOptions {
9
+ readonly unit: ConvergeUnit;
10
+ readonly desiredContent: string | Uint8Array;
11
+ /** Compatibility input only. Unknown content remains protected when true. */
12
+ readonly force?: boolean;
13
+ }
14
+ export declare function resolveUntrackedCollision(options: ResolveCollisionOptions): CollisionResult;
@@ -0,0 +1,42 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { existsSync, lstatSync, readFileSync, rmSync } from "node:fs";
3
+ const GIT_TIMEOUT_MS = 15_000;
4
+ const OWNERSHIP_MARKER = /^\uFEFF?[ \t]*(?:#|\/\/|\/\*|<!--|")?[ \t]*(?:>>> managed by webpresso\b|Generated by webpresso\b)/iu;
5
+ function git(repoRoot, args) {
6
+ const result = spawnSync("git", [...args], {
7
+ cwd: repoRoot,
8
+ encoding: "utf8",
9
+ timeout: GIT_TIMEOUT_MS,
10
+ maxBuffer: 1024 * 1024,
11
+ stdio: ["ignore", "pipe", "pipe"],
12
+ });
13
+ return { ok: result.status === 0 && !result.error };
14
+ }
15
+ export function resolveUntrackedCollision(options) {
16
+ const { unit } = options;
17
+ if (!existsSync(unit.targetPath))
18
+ return { ok: true, resolution: "none" };
19
+ if (git(unit.repoRoot, ["ls-files", "--error-unmatch", "--", unit.gitPath]).ok) {
20
+ return { ok: true, resolution: "tracked" };
21
+ }
22
+ if (!lstatSync(unit.targetPath).isFile()) {
23
+ return {
24
+ ok: false,
25
+ error: `Untracked collision at ${unit.gitPath} is not a regular file; move it aside and rerun wp setup.`,
26
+ };
27
+ }
28
+ const current = readFileSync(unit.targetPath);
29
+ if (current.equals(Buffer.from(options.desiredContent))) {
30
+ rmSync(unit.targetPath);
31
+ return { ok: true, resolution: "removed-identical" };
32
+ }
33
+ if (OWNERSHIP_MARKER.test(current.toString("utf8"))) {
34
+ rmSync(unit.targetPath);
35
+ return { ok: true, resolution: "removed-owned" };
36
+ }
37
+ return {
38
+ ok: false,
39
+ error: `Unknown untracked content blocks ${unit.gitPath}; it was preserved${options.force ? " despite --force" : ""}. ` +
40
+ "Move or commit the file, then rerun wp setup.",
41
+ };
42
+ }
@@ -0,0 +1,49 @@
1
+ import { resolveOpencodeAccountsGracefully } from "#runtime/opencode-account-materializer.js";
2
+ /** The `--wp-rotate` flag, namespaced like `--wp-permission-mode`. */
3
+ export declare const OPENCODE_ROTATE_FLAG = "--wp-rotate";
4
+ export type OpencodeRotateOutcome =
5
+ /** The previously-active account was cooled and a different one is now active. */
6
+ {
7
+ readonly kind: "rotated";
8
+ readonly cooledAccountId: string;
9
+ readonly activeAccountId: string;
10
+ readonly activeIsCooled: boolean;
11
+ readonly waitMs?: number;
12
+ }
13
+ /** The on-disk key matched no known account, so nothing was cooled. */
14
+ | {
15
+ readonly kind: "reselected";
16
+ readonly activeAccountId: string;
17
+ readonly activeIsCooled: boolean;
18
+ }
19
+ /** Fewer than two accounts are provisioned — there is nowhere to rotate to. */
20
+ | {
21
+ readonly kind: "single-account";
22
+ }
23
+ /** Secrets could not be resolved; the caller must still launch normally. */
24
+ | {
25
+ readonly kind: "unavailable";
26
+ readonly reason: string;
27
+ };
28
+ export interface OpencodeRotateDeps {
29
+ readonly resolveOpencodeAccounts?: typeof resolveOpencodeAccountsGracefully;
30
+ readonly readActiveKey?: () => string | undefined;
31
+ readonly materializeGlobalOpencodeAuth?: (key: string) => void;
32
+ readonly recordCooldown?: (canonicalId: string) => Promise<unknown>;
33
+ }
34
+ /**
35
+ * Cool the currently-materialized opencode-go account and materialize the next
36
+ * usable one into the global `auth.json`.
37
+ *
38
+ * The active account is identified by reading the global store back and matching
39
+ * its key against the resolved accounts — `wp` wrote that value itself, so this
40
+ * stays correct without persisting a separate "last launched" record. When the
41
+ * key matches nothing (a hand-edited store, or a fresh machine), the rotation
42
+ * degrades to a plain reselect and cools nothing, because cooling an account we
43
+ * cannot identify would suppress the wrong one.
44
+ *
45
+ * Never throws: the caller launches the TUI regardless of the outcome.
46
+ */
47
+ export declare function rotateActiveOpencodeAccount(cwd: string, secretProfile: string, deps?: OpencodeRotateDeps): Promise<OpencodeRotateOutcome>;
48
+ /** Human-readable lines describing a rotation outcome, in print order. */
49
+ export declare function describeOpencodeRotate(outcome: OpencodeRotateOutcome): readonly string[];
@@ -0,0 +1,135 @@
1
+ import { loadOpencodeAccountAvailability, pruneExpiredOpencodeAccountSuppressions, recordOpencodeAccountAvailabilityWithLock, } from "#review/availability.js";
2
+ import { opencodeAccountCanonicalId, selectActiveOpencodeAccount, } from "#review/opencode-account.js";
3
+ import { materializeOpencodeGoAuth, readOpencodeGoKey } from "#review/opencode-auth.js";
4
+ import { resolveOpencodeAccountsGracefully } from "#runtime/opencode-account-materializer.js";
5
+ /** The `--wp-rotate` flag, namespaced like `--wp-permission-mode`. */
6
+ export const OPENCODE_ROTATE_FLAG = "--wp-rotate";
7
+ /**
8
+ * A manual rotation has no provider response to parse a reset window from, so it
9
+ * assumes the shortest real OpenCode Go window (5 hours). Recording no window at
10
+ * all would leave the record permanently suppressed — `suppressionExpired` only
11
+ * clears a record that carries `retryAfterMs` — so every rotated account would
12
+ * be cooled forever and selection would degrade to "first account" again. A
13
+ * too-short guess is self-correcting: the account is retried, and if it is still
14
+ * capped the gate (or the next rotation) simply cools it again.
15
+ */
16
+ const MANUAL_ROTATION_COOLDOWN_MS = 5 * 60 * 60 * 1_000;
17
+ function coolActiveAccount(canonicalId, deps) {
18
+ if (deps.recordCooldown)
19
+ return deps.recordCooldown(canonicalId);
20
+ // Same event shape the review gate records on an in-flight usage limit, so
21
+ // both writers produce identical, mutually-readable cooldown records.
22
+ return recordOpencodeAccountAvailabilityWithLock(canonicalId, {
23
+ failureCode: "opencode-go-usage-limit",
24
+ terminalClassification: "provider-limit-reached",
25
+ limitWindow: "unknown",
26
+ retryAfterMs: MANUAL_ROTATION_COOLDOWN_MS,
27
+ });
28
+ }
29
+ /**
30
+ * Pick the account to make active next.
31
+ *
32
+ * `excludeId` is the account we just cooled. It is filtered out here rather
33
+ * than relied upon coming back suppressed from the store: that keeps rotation
34
+ * correct even if the cooldown write is slow, contended, or fails outright, and
35
+ * removes a read-after-write dependency from the hot path.
36
+ */
37
+ function selectNextAccount(accounts, excludeId) {
38
+ const pruned = pruneExpiredOpencodeAccountSuppressions(loadOpencodeAccountAvailability());
39
+ const candidates = excludeId === undefined ? accounts : accounts.filter((account) => account.id !== excludeId);
40
+ // Callers guarantee at least one candidate: rotation needs >= 2 accounts, and
41
+ // the no-exclude path is only reached with a non-empty list.
42
+ const pool = candidates.length > 0 ? candidates : accounts;
43
+ const selection = selectActiveOpencodeAccount(pool, pruned.state);
44
+ return selection ?? { account: pool[0], cooled: false };
45
+ }
46
+ /**
47
+ * Cool the currently-materialized opencode-go account and materialize the next
48
+ * usable one into the global `auth.json`.
49
+ *
50
+ * The active account is identified by reading the global store back and matching
51
+ * its key against the resolved accounts — `wp` wrote that value itself, so this
52
+ * stays correct without persisting a separate "last launched" record. When the
53
+ * key matches nothing (a hand-edited store, or a fresh machine), the rotation
54
+ * degrades to a plain reselect and cools nothing, because cooling an account we
55
+ * cannot identify would suppress the wrong one.
56
+ *
57
+ * Never throws: the caller launches the TUI regardless of the outcome.
58
+ */
59
+ export async function rotateActiveOpencodeAccount(cwd, secretProfile, deps = {}) {
60
+ try {
61
+ return await rotateOrThrow(cwd, secretProfile, deps);
62
+ }
63
+ catch (error) {
64
+ // The cooldown write takes the user-scope lock the review gate also holds
65
+ // (contention rejects after a few short retries) and the auth write can fail
66
+ // with EACCES/ENOSPC. Neither may abort the launch, so both degrade to the
67
+ // same "carry on and launch" outcome the sibling select path uses.
68
+ return { kind: "unavailable", reason: error instanceof Error ? error.message : String(error) };
69
+ }
70
+ }
71
+ async function rotateOrThrow(cwd, secretProfile, deps) {
72
+ const resolution = (deps.resolveOpencodeAccounts ?? resolveOpencodeAccountsGracefully)({
73
+ cwd,
74
+ secretProfile,
75
+ });
76
+ if (!resolution.ok)
77
+ return { kind: "unavailable", reason: resolution.reason };
78
+ const accounts = resolution.accounts;
79
+ if (accounts.length < 2)
80
+ return { kind: "single-account" };
81
+ const activeKey = (deps.readActiveKey ?? (() => readOpencodeGoKey()))();
82
+ const current = accounts.find((account) => account.key === activeKey);
83
+ const materialize = deps.materializeGlobalOpencodeAuth ?? ((key) => void materializeOpencodeGoAuth(key));
84
+ if (!current) {
85
+ const next = selectNextAccount(accounts);
86
+ materialize(next.account.key);
87
+ return { kind: "reselected", activeAccountId: next.account.id, activeIsCooled: next.cooled };
88
+ }
89
+ await coolActiveAccount(opencodeAccountCanonicalId(current.id), deps);
90
+ const next = selectNextAccount(accounts, current.id);
91
+ materialize(next.account.key);
92
+ return {
93
+ kind: "rotated",
94
+ cooledAccountId: current.id,
95
+ activeAccountId: next.account.id,
96
+ activeIsCooled: next.cooled,
97
+ ...(next.waitMs === undefined ? {} : { waitMs: next.waitMs }),
98
+ };
99
+ }
100
+ function formatWait(waitMs) {
101
+ if (waitMs === undefined)
102
+ return "";
103
+ const hours = waitMs / 3_600_000;
104
+ if (hours >= 24)
105
+ return ` (resets in ~${Math.ceil(hours / 24)}d)`;
106
+ if (hours >= 1)
107
+ return ` (resets in ~${Math.ceil(hours)}h)`;
108
+ return ` (resets in ~${Math.max(1, Math.ceil(waitMs / 60_000))}m)`;
109
+ }
110
+ /** Human-readable lines describing a rotation outcome, in print order. */
111
+ export function describeOpencodeRotate(outcome) {
112
+ switch (outcome.kind) {
113
+ case "unavailable":
114
+ return [`wp opencode: could not resolve OpenCode Go accounts (${outcome.reason}).`];
115
+ case "single-account":
116
+ return [
117
+ "wp opencode: only one OpenCode Go account is configured; nothing to rotate to. Add OPENCODE_GO_KEY_<ID> secrets to enable rotation.",
118
+ ];
119
+ case "reselected":
120
+ return [
121
+ "wp opencode: the active key matches no configured account, so nothing was cooled.",
122
+ `wp opencode: now active -> opencode-go:${outcome.activeAccountId}`,
123
+ ];
124
+ case "rotated": {
125
+ const lines = [
126
+ `wp opencode: cooled opencode-go:${outcome.cooledAccountId} (usage limit)`,
127
+ `wp opencode: now active -> opencode-go:${outcome.activeAccountId}`,
128
+ ];
129
+ if (outcome.activeIsCooled) {
130
+ lines.push(`wp opencode: every account is within its cooldown; using the soonest-expiring one${formatWait(outcome.waitMs)}. Run again after it resets, or add another OPENCODE_GO_KEY_<ID>.`);
131
+ }
132
+ return lines;
133
+ }
134
+ }
135
+ }
@@ -0,0 +1,11 @@
1
+ import type { CAC } from "cac";
2
+ export declare const PUBLIC_READINESS_COMMAND_HELP: string;
3
+ export interface PublicReadinessDeps {
4
+ readonly cwd?: string;
5
+ readonly resolveRepoRoot?: (cwd: string) => string | null;
6
+ readonly hasReadinessScript?: (repoRoot: string) => boolean;
7
+ readonly run?: (repoRoot: string) => number;
8
+ readonly stderr?: Pick<typeof process.stderr, "write">;
9
+ }
10
+ export declare function runPublicReadiness(deps?: PublicReadinessDeps): number;
11
+ export declare function registerPublicReadinessCommand(cli: CAC): void;
@@ -0,0 +1,42 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { existsSync } from "node:fs";
3
+ import path from "node:path";
4
+ import { getGitTopLevel } from "#git/changed-files";
5
+ export const PUBLIC_READINESS_COMMAND_HELP = [
6
+ "Run the public package readiness gate.",
7
+ "",
8
+ "Delegates to scripts/public-readiness.ts, preserving that script as the",
9
+ "single owner of package and repository visibility readiness checks.",
10
+ "",
11
+ "Examples:",
12
+ " wp public:readiness",
13
+ ].join("\n");
14
+ function defaultRunPublicReadiness(repoRoot) {
15
+ const result = spawnSync("bun", ["scripts/public-readiness.ts"], {
16
+ cwd: repoRoot,
17
+ stdio: "inherit",
18
+ env: process.env,
19
+ });
20
+ if (result.error) {
21
+ process.stderr.write(`${result.error.message}\n`);
22
+ return 1;
23
+ }
24
+ return typeof result.status === "number" ? result.status : 1;
25
+ }
26
+ export function runPublicReadiness(deps = {}) {
27
+ const cwd = deps.cwd ?? process.cwd();
28
+ const stderr = deps.stderr ?? process.stderr;
29
+ const resolveRepoRoot = deps.resolveRepoRoot ?? ((current) => getGitTopLevel(current));
30
+ const hasReadinessScript = deps.hasReadinessScript ??
31
+ ((repoRoot) => existsSync(path.join(repoRoot, "scripts", "public-readiness.ts")));
32
+ const run = deps.run ?? defaultRunPublicReadiness;
33
+ const repoRoot = resolveRepoRoot(cwd) ?? cwd;
34
+ if (!hasReadinessScript(repoRoot)) {
35
+ stderr.write(`wp public:readiness: scripts/public-readiness.ts is missing at ${repoRoot}.\n`);
36
+ return 1;
37
+ }
38
+ return run(repoRoot);
39
+ }
40
+ export function registerPublicReadinessCommand(cli) {
41
+ cli.command("public:readiness", PUBLIC_READINESS_COMMAND_HELP).action(() => runPublicReadiness());
42
+ }
@@ -2,8 +2,11 @@ import type { CAC } from "cac";
2
2
  import { runReviewExecution, type ReviewEffort, type ReviewLimitWindow, type ReviewProvider, type ReviewRunSummary, type ReviewStage } from "#review/execution/index.js";
3
3
  import { createReviewTargetCheckout, type ReviewTargetCheckout } from "#review/execution/review-checkout.js";
4
4
  import { type ReviewProcessRecord } from "#review/execution/process-registry.js";
5
+ import { type OpencodeAccount } from "#review/opencode-account.js";
6
+ import { resolveOpencodeAccountsGracefully } from "#runtime/opencode-account-materializer.js";
5
7
  import { evaluateApprovalGate } from "#lifecycle/audit";
6
8
  import { type ReviewEvent } from "#review/events.js";
9
+ import { type ReviewGateVerification } from "#review/delivery-verifier.js";
7
10
  import { runCommand } from "#mcp/tools/_shared/run-command.js";
8
11
  export type ReviewVerdict = "approve" | "approve-with-nits" | "reject" | "no-verdict";
9
12
  export type ReviewStatus = "complete" | "timed-out" | "incomplete" | "unavailable" | "failed";
@@ -77,6 +80,8 @@ export interface ReviewCommandOptions {
77
80
  artifactRoot?: string;
78
81
  idleSeconds?: string;
79
82
  maxProgressEvents?: string;
83
+ reason?: string;
84
+ confirm?: boolean;
80
85
  }
81
86
  interface ReviewGateAttempt {
82
87
  readonly provider: ReviewProvider;
@@ -90,6 +95,7 @@ interface ReviewGateAttempt {
90
95
  readonly verdict: ReviewVerdict;
91
96
  readonly terminalClassification: string;
92
97
  readonly failureCode?: string;
98
+ readonly providerErrorDetail?: string;
93
99
  readonly limitWindow?: ReviewLimitWindow;
94
100
  readonly retryAfterMs?: number;
95
101
  readonly artifact?: string;
@@ -105,7 +111,7 @@ interface ReviewGateResult {
105
111
  readonly targetSha: string;
106
112
  readonly baseSha: string;
107
113
  readonly promptHash: string;
108
- readonly status: "approved" | "needs-review" | "rejected" | "recorded-untracked";
114
+ readonly status: "approved" | "needs-review" | "rejected" | "scope-exhausted" | "recorded-untracked" | "verification-failed";
109
115
  readonly approvalGate: ReturnType<typeof evaluateApprovalGate>;
110
116
  readonly statePath: string;
111
117
  readonly attempts: readonly ReviewGateAttempt[];
@@ -123,6 +129,7 @@ interface ReviewGateResult {
123
129
  readonly rev?: string;
124
130
  }[];
125
131
  readonly issues?: readonly string[];
132
+ readonly verification?: ReviewGateVerification;
126
133
  readonly message: string;
127
134
  }
128
135
  interface ReviewLedger {
@@ -164,6 +171,22 @@ export declare function runReviewRunCommand(projectRoot: string, options: Review
164
171
  export declare function readReviewLedger(projectRoot: string, slug: string): Promise<ReviewLedger>;
165
172
  export declare function logReviewEntry(projectRoot: string, slug: string, input: ReviewCommandOptions): Promise<ReviewEntry>;
166
173
  export declare function logReviewEvent(projectRoot: string, slug: string, input: ReviewCommandOptions): Promise<ReviewEvent>;
174
+ /**
175
+ * `wp review override` — the gated, auditable human escape hatch for the
176
+ * promotion approval gate. It records a `maintainer-override` approval that
177
+ * satisfies the gate distinctly (never as a strong/OpenCode-Go approval).
178
+ *
179
+ * Trust properties:
180
+ * - REFUSES unless a formal strong-reviewer gate has already REJECTED the exact
181
+ * current subject (precondition + target binding are the same lookup).
182
+ * - Requires an explicit `--reason` and `--confirm` — no accidental invocation.
183
+ * - Reviewer id `maintainer-override` is not in the `wp review log` reviewer
184
+ * enum, so neither `wp review log` nor `wp_blueprint_review_log` can mint it;
185
+ * this command is the only writer. The read-time policy in
186
+ * `#review/events` treats it as human-like, so its manual-stage record is a
187
+ * legitimate governance approval rather than a disguised machine approval.
188
+ */
189
+ export declare function runReviewOverride(projectRoot: string, slug: string, input: ReviewCommandOptions): Promise<ReviewEvent>;
167
190
  export declare function classifyProviderOutput(output: string): Pick<ReviewGateAttempt, "status" | "verdict" | "terminalClassification">;
168
191
  export declare function reviewerFamily(model: string): ReviewReviewerId;
169
192
  type GateProviderResultStage = ReviewStage | "approval-gate";
@@ -176,18 +199,26 @@ export declare function invokeProvider(input: {
176
199
  readonly provider: ReviewProvider;
177
200
  readonly targetSha: string;
178
201
  readonly signal?: AbortSignal;
179
- }, executeReview?: typeof runReviewExecution, runCatalogCommand?: typeof runCommand, createTargetCheckout?: typeof createReviewTargetCheckout, existingCheckout?: ReviewTargetCheckout): Promise<{
202
+ readonly authDataDir?: string;
203
+ }, executeReview?: typeof runReviewExecution, runCatalogCommand?: typeof runCommand, createTargetCheckout?: typeof createReviewTargetCheckout, getSharedCheckout?: () => ReviewTargetCheckout | undefined): Promise<{
180
204
  output: string;
181
205
  model: string;
182
206
  reviewer: string;
183
207
  stage?: GateProviderResultStage;
184
- } & Pick<ReviewGateAttempt, "status" | "verdict" | "terminalClassification" | "failureCode" | "limitWindow" | "retryAfterMs">>;
208
+ } & Pick<ReviewGateAttempt, "status" | "verdict" | "terminalClassification" | "failureCode" | "providerErrorDetail" | "limitWindow" | "retryAfterMs">>;
185
209
  export interface ReviewGateDependencies {
186
210
  readonly createTargetCheckout?: typeof createReviewTargetCheckout;
187
211
  readonly invokeProvider?: typeof invokeProvider;
188
212
  readonly runCatalogCommand?: typeof runCommand;
189
213
  readonly runReviewExecution?: typeof runReviewExecution;
190
214
  readonly signal?: AbortSignal;
215
+ /** Test seam: resolve opencode-go accounts from the secret provider. */
216
+ readonly resolveOpencodeAccounts?: typeof resolveOpencodeAccountsGracefully;
217
+ /**
218
+ * Test seam: materialize an account's isolated auth store and return the
219
+ * `XDG_DATA_HOME` directory to inject for that account's spawns.
220
+ */
221
+ readonly prepareOpencodeCredential?: (account: OpencodeAccount) => string;
191
222
  }
192
223
  /**
193
224
  * Sanctioned fallback when `wp review gate` does not reach "approved" — points
@@ -198,6 +229,7 @@ export interface ReviewGateDependencies {
198
229
  export declare function reviewGateNonConvergenceHint(slug: string, context?: {
199
230
  readonly gateId?: string;
200
231
  readonly failureCode?: string;
232
+ readonly status?: ReviewGateResult["status"];
201
233
  }): string;
202
234
  export declare function runReviewGate(projectRoot: string, slug: string, input: ReviewCommandOptions, dependencies?: ReviewGateDependencies): Promise<ReviewGateResult>;
203
235
  export declare function buildReviewScoreboard(entries: readonly ReviewEntry[]): readonly ReviewScoreboardRow[];