@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
@@ -55,9 +55,11 @@ const SUPPORTED_COMMANDS = [
55
55
  "e2e",
56
56
  "ci",
57
57
  "ci-preflight",
58
+ "public:readiness",
58
59
  "typecheck",
59
60
  "lint",
60
61
  "format",
62
+ "rust-check",
61
63
  "logs",
62
64
  "session-id",
63
65
  "session-info",
@@ -70,6 +72,7 @@ const SUPPORTED_COMMANDS = [
70
72
  "hook",
71
73
  "hooks",
72
74
  "self-install-guard",
75
+ "optional-tool-refresh",
73
76
  "gain",
74
77
  "bench",
75
78
  "install",
@@ -101,6 +104,10 @@ const ROOT_HELP_CORE_ENTRIES = [
101
104
  command: "format",
102
105
  description: "Format through the portable wp surface (--check for CI/husky)",
103
106
  },
107
+ {
108
+ command: "rust-check",
109
+ description: "Prove affected tracked Rust crates compile and pass tests (cargo check + test)",
110
+ },
104
111
  {
105
112
  command: "e2e",
106
113
  description: "Build and run E2E commands through the portable webpresso surface",
@@ -111,6 +118,10 @@ const ROOT_HELP_CORE_ENTRIES = [
111
118
  command: "ci-preflight",
112
119
  description: "Run the locally-runnable required CI checks fail-fast (the pre-push gate)",
113
120
  },
121
+ {
122
+ command: "public:readiness",
123
+ description: "Run the public package readiness gate through the portable wp surface",
124
+ },
114
125
  { command: "logs", description: "Print persisted raw output from recent quality runs" },
115
126
  { command: "dev", description: "Run a manifest-backed development target" },
116
127
  { command: "status", description: "Show unified local readiness and next action" },
@@ -364,6 +375,10 @@ const COMMAND_REGISTRARS = {
364
375
  const { registerCiPreflightCommand } = await import("./commands/ci-preflight.js");
365
376
  registerCiPreflightCommand(cli);
366
377
  },
378
+ "public:readiness": async (cli) => {
379
+ const { registerPublicReadinessCommand } = await import("./commands/public-readiness.js");
380
+ registerPublicReadinessCommand(cli);
381
+ },
367
382
  typecheck: async (cli) => {
368
383
  const { registerTypecheckCommand } = await import("./commands/typecheck.js");
369
384
  registerTypecheckCommand(cli);
@@ -376,6 +391,10 @@ const COMMAND_REGISTRARS = {
376
391
  const { registerFormatCommand } = await import("./commands/format.js");
377
392
  registerFormatCommand(cli);
378
393
  },
394
+ "rust-check": async (cli) => {
395
+ const { registerRustCheckCommand } = await import("./commands/rust-check.js");
396
+ registerRustCheckCommand(cli);
397
+ },
379
398
  logs: async (cli) => {
380
399
  const { registerLogsCommand } = await import("./commands/logs.js");
381
400
  registerLogsCommand(cli);
@@ -422,6 +441,15 @@ const COMMAND_REGISTRARS = {
422
441
  const { registerSelfInstallGuardCommand } = await import("./commands/self-install-guard.js");
423
442
  registerSelfInstallGuardCommand(cli);
424
443
  },
444
+ "optional-tool-refresh": async (cli) => {
445
+ cli
446
+ .command("optional-tool-refresh <tool> [...args]", "Internal detached optional-tool freshness worker")
447
+ .allowUnknownOptions()
448
+ .action(async (tool, args = []) => {
449
+ const { runOptionalToolRefreshWorker } = await import("./optional-tool-freshness.js");
450
+ process.exitCode = runOptionalToolRefreshWorker([tool, ...args]);
451
+ });
452
+ },
425
453
  gain: async (cli) => {
426
454
  const { registerGainCommand } = await import("./commands/gain/index.js");
427
455
  registerGainCommand(cli);
@@ -636,6 +664,21 @@ export async function main() {
636
664
  console.error(formatUnknownCommandError(command, SUPPORTED_COMMANDS));
637
665
  return 1;
638
666
  }
667
+ // `wp run wait` is the read-only workflow-run waiter, intercepted before the
668
+ // package-manager `run` facade (which maps `wp run <script>` to `vp run
669
+ // <script>`). Without this, `wait` would be treated as a package script name.
670
+ // Errors resolve to an exit code here, mirroring main()'s catch below, so a
671
+ // bad flag never rejects the main() promise.
672
+ if (resolvedCommand === "run" && argv[3] === "wait") {
673
+ const { runRunWaitCommand } = await import("./commands/run-wait.js");
674
+ try {
675
+ return await runRunWaitCommand(argv.slice(4));
676
+ }
677
+ catch (error) {
678
+ console.error(error instanceof Error ? error.message : String(error));
679
+ return 1;
680
+ }
681
+ }
639
682
  await COMMAND_REGISTRARS[resolvedCommand](cli, resolvedCommand);
640
683
  if (resolvedCommand === "session") {
641
684
  const { runSessionCommand } = await import("./commands/session.js");
@@ -2,6 +2,8 @@ import type { CAC } from "cac";
2
2
  import { type DashPermissionMode, type DashProvider } from "#cli/commands/dash/provider-capabilities.js";
3
3
  import { type RuntimeCommandOptions } from "#runtime/executor.js";
4
4
  import { type SecretsConfig } from "#runtime/secrets-config.js";
5
+ import { resolveOpencodeAccountsGracefully } from "#runtime/opencode-account-materializer.js";
6
+ import { resolveHealthyOpencodeAccount } from "#cli/commands/opencode-probe.js";
5
7
  export declare const AGENT_LAUNCH_COMMANDS: readonly ["claude", "codex", "grok", "opencode"];
6
8
  export type AgentLaunchCommand = DashProvider;
7
9
  export declare const DEFAULT_AGENT_SECRET_PROFILE = "preview";
@@ -18,6 +20,11 @@ export interface AgentLaunchDeps {
18
20
  error?: Error;
19
21
  };
20
22
  readonly ensureCodexSkillSurface?: (cwd: string, env?: NodeJS.ProcessEnv) => string | null;
23
+ readonly resolveOpencodeAccounts?: typeof resolveOpencodeAccountsGracefully;
24
+ readonly materializeGlobalOpencodeAuth?: (key: string) => void;
25
+ readonly readActiveKey?: () => string | undefined;
26
+ readonly recordCooldown?: (canonicalId: string) => Promise<unknown>;
27
+ readonly resolveHealthyOpencodeAccount?: typeof resolveHealthyOpencodeAccount;
21
28
  }
22
29
  export declare function buildDefaultAgentArgs(command: AgentLaunchCommand, args?: readonly string[], permissionMode?: DashPermissionMode): readonly string[];
23
30
  export declare function isAgentLaunchCommand(command: string | undefined): command is AgentLaunchCommand;
@@ -29,5 +36,5 @@ export declare function buildAgentRuntimeCommandOptions(input: {
29
36
  readonly env?: NodeJS.ProcessEnv;
30
37
  readonly secretsConfig: SecretsConfig | null;
31
38
  }): RuntimeCommandOptions;
32
- export declare function runAgentLaunchCommand(command: AgentLaunchCommand, args?: readonly string[], options?: AgentLaunchOptions, deps?: AgentLaunchDeps): number;
39
+ export declare function runAgentLaunchCommand(command: AgentLaunchCommand, args?: readonly string[], options?: AgentLaunchOptions, deps?: AgentLaunchDeps): Promise<number>;
33
40
  export declare function registerAgentLaunchCommand(cli: CAC, command: AgentLaunchCommand): void;
@@ -7,6 +7,12 @@ import { WP_AGENT_LAUNCHED_VIA_WP_ENV, WP_AGENT_LAUNCHED_VIA_WP_VALUE, } from "#
7
7
  import { readSecretsConfig } from "#runtime/secrets-config.js";
8
8
  import { resolveFreshAgentKitPackageRoot } from "#cli/commands/init/package-root";
9
9
  import { ensureCodexUserPlugin } from "#cli/commands/init/scaffolders/codex-plugin/index.js";
10
+ import { resolveOpencodeAccountsGracefully } from "#runtime/opencode-account-materializer.js";
11
+ import { loadOpencodeAccountAvailability, pruneExpiredOpencodeAccountSuppressions, } from "#review/availability.js";
12
+ import { selectActiveOpencodeAccount } from "#review/opencode-account.js";
13
+ import { materializeOpencodeGoAuth } from "#review/opencode-auth.js";
14
+ import { describeOpencodeRotate, OPENCODE_ROTATE_FLAG, rotateActiveOpencodeAccount, } from "#cli/commands/opencode-rotate.js";
15
+ import { OPENCODE_SKIP_PROBE_ENV, resolveHealthyOpencodeAccount, } from "#cli/commands/opencode-probe.js";
10
16
  export const AGENT_LAUNCH_COMMANDS = DASH_PROVIDERS;
11
17
  export const DEFAULT_AGENT_SECRET_PROFILE = "preview";
12
18
  export const AGENT_SECRET_PROFILE_ENV = "WP_AGENT_SECRET_PROFILE";
@@ -83,7 +89,77 @@ function ensureCodexSkillSurface(cwd, env = process.env) {
83
89
  return `Codex plugin refresh failed (${result.kind}). Run \`wp install codex\` and retry.`;
84
90
  }
85
91
  }
86
- export function runAgentLaunchCommand(command, args = [], options = {}, deps = {}) {
92
+ /**
93
+ * Choose the active opencode-go account, verify it against the real provider,
94
+ * and materialize its key into the global opencode `auth.json` before the TUI
95
+ * launches.
96
+ *
97
+ * A launch-time health probe (`resolveHealthyOpencodeAccount`) spawns a bounded
98
+ * `opencode run` against the candidate's isolated credential store; a detected
99
+ * usage limit cools that account with the provider's real reset window and the
100
+ * next account is probed instead. So a plain relaunch now *does* rotate past an
101
+ * exhausted account, and `wp opencode --wp-rotate` is only needed to force a
102
+ * swap off an account that is still answering.
103
+ *
104
+ * Honest limitation: interactive opencode is a long-lived TUI — the account is
105
+ * chosen once at launch, so a cap hit *mid-session* is still NOT auto-swapped.
106
+ * Only the review gate does in-flight failover, because only it observes every
107
+ * provider response for the life of the run.
108
+ *
109
+ * Never throws: a Doppler outage, a failed probe, or a materialize failure must
110
+ * not block the TUI from launching, so every failure is logged and swallowed.
111
+ */
112
+ async function selectAndMaterializeOpencodeAccount(cwd, deps, secretProfile, env) {
113
+ try {
114
+ const resolution = (deps.resolveOpencodeAccounts ?? resolveOpencodeAccountsGracefully)({
115
+ cwd,
116
+ secretProfile,
117
+ });
118
+ if (!resolution.ok)
119
+ return;
120
+ const pruned = pruneExpiredOpencodeAccountSuppressions(loadOpencodeAccountAvailability());
121
+ const initial = selectActiveOpencodeAccount(resolution.accounts, pruned.state);
122
+ if (!initial)
123
+ return;
124
+ const probed = await (deps.resolveHealthyOpencodeAccount ?? resolveHealthyOpencodeAccount)(resolution.accounts, initial, { env });
125
+ for (const line of probed.lines)
126
+ console.error(line);
127
+ const selection = probed.selection;
128
+ (deps.materializeGlobalOpencodeAuth ?? ((key) => materializeOpencodeGoAuth(key)))(selection.account.key);
129
+ if (selection.cooled && probed.lines.length === 0) {
130
+ console.error(`wp opencode: all OpenCode Go accounts are within their usage cooldown; launching on the soonest-expiring account (${selection.account.id}).`);
131
+ }
132
+ }
133
+ catch (error) {
134
+ const message = error instanceof Error ? error.message : String(error);
135
+ console.error(`wp opencode: account selection failed, launching without it (${message}).`);
136
+ }
137
+ }
138
+ /**
139
+ * Run the opencode-only pre-launch credential step and return the args to
140
+ * forward to the binary. `--wp-rotate` is consumed here and must never reach
141
+ * opencode, which would treat it as a prompt.
142
+ */
143
+ async function prepareOpencodeLaunch(cwd, args, secretsConfig, deps, secretProfile, env) {
144
+ if (!args.includes(OPENCODE_ROTATE_FLAG)) {
145
+ if (secretsConfig !== null) {
146
+ await selectAndMaterializeOpencodeAccount(cwd, deps, secretProfile, env);
147
+ }
148
+ return args;
149
+ }
150
+ const outcome = await rotateActiveOpencodeAccount(cwd, secretProfile, deps);
151
+ for (const line of describeOpencodeRotate(outcome))
152
+ console.error(line);
153
+ // A successful rotation already materialized the next account, so the normal
154
+ // launch-time selection is skipped to avoid a second, redundant write. When
155
+ // rotation could not run, fall through to that selection so the launch still
156
+ // gets the best account it can rather than whatever the store happens to hold.
157
+ if (outcome.kind === "unavailable" && secretsConfig !== null) {
158
+ await selectAndMaterializeOpencodeAccount(cwd, deps, secretProfile, env);
159
+ }
160
+ return args.filter((arg) => arg !== OPENCODE_ROTATE_FLAG);
161
+ }
162
+ export async function runAgentLaunchCommand(command, args = [], options = {}, deps = {}) {
87
163
  const cwd = options.cwd ?? process.cwd();
88
164
  if (command === "codex") {
89
165
  const skillSurfaceError = (deps.ensureCodexSkillSurface ?? ensureCodexSkillSurface)(cwd, options.env ?? process.env);
@@ -93,9 +169,19 @@ export function runAgentLaunchCommand(command, args = [], options = {}, deps = {
93
169
  }
94
170
  }
95
171
  const secretsConfig = (deps.readSecretsConfig ?? readSecretsConfig)(cwd);
172
+ // Account discovery must resolve secrets through the same profile the launch
173
+ // itself uses (below, via buildAgentRuntimeCommandOptions). Without it no
174
+ // environment selector reaches the secret manager, which then falls back to
175
+ // its own ambient, path-scoped config — that resolves only in a checkout that
176
+ // happens to sit under the scoped directory and fails in every managed
177
+ // worktree, CI clone, and fresh checkout.
178
+ const secretProfile = resolveAgentSecretProfile(options.env ?? process.env);
179
+ const launchArgs = command === "opencode"
180
+ ? await prepareOpencodeLaunch(cwd, args, secretsConfig, deps, secretProfile, options.env ?? process.env)
181
+ : args;
96
182
  const runtimeOptions = buildAgentRuntimeCommandOptions({
97
183
  command,
98
- args,
184
+ args: launchArgs,
99
185
  cwd,
100
186
  env: options.env ?? process.env,
101
187
  secretsConfig,
@@ -108,8 +194,11 @@ export function runAgentLaunchCommand(command, args = [], options = {}, deps = {
108
194
  return result.status ?? 1;
109
195
  }
110
196
  export function registerAgentLaunchCommand(cli, command) {
197
+ const rotateHint = command === "opencode"
198
+ ? ` (the selected OpenCode Go account is health-probed first and a capped one is rotated past automatically; pass \`${OPENCODE_ROTATE_FLAG}\` to force a rotation, or set \`${OPENCODE_SKIP_PROBE_ENV}=1\` to skip the probe)`
199
+ : "";
111
200
  cli
112
- .command(`${command} [...args]`, `Launch ${command} with provider-backed agent secrets and safe-by-default permissions (run \`wp yolo on\` for persistent full-auto)`)
201
+ .command(`${command} [...args]`, `Launch ${command} with provider-backed agent secrets and safe-by-default permissions (run \`wp yolo on\` for persistent full-auto)${rotateHint}`)
113
202
  .allowUnknownOptions()
114
- .action((args = []) => runAgentLaunchCommand(command, args, { cwd: process.cwd() }));
203
+ .action(async (args = []) => await runAgentLaunchCommand(command, args, { cwd: process.cwd() }));
115
204
  }
@@ -1,4 +1,4 @@
1
- import type { AuditKindId } from "#audit/registry";
1
+ import { type AuditKindId } from "#audit/registry";
2
2
  import type { RepoAuditResult } from "#audit/repo-guardrails";
3
3
  export type AuditKind = AuditKindId;
4
4
  export type AuditOutcome = {
@@ -1,3 +1,4 @@
1
+ import { getAuditKindDescriptor } from "#audit/registry";
1
2
  function optionsForCompositeRepoAudit(name, options) {
2
3
  if (name !== "reference-parity-matrix" || options.strict !== true)
3
4
  return options;
@@ -94,7 +95,13 @@ export async function runAuditDispatch(auditKind, targets, options, deps) {
94
95
  let allOk = true;
95
96
  for (const name of deps.knownRepoKinds) {
96
97
  const result = await deps.runRepoAudit(name, root, optionsForCompositeRepoAudit(name, options));
97
- if (!result.ok)
98
+ // Signal-class kinds (registry.ts gateClass) still run and still
99
+ // report their findings via `results` below, but a failing signal
100
+ // kind must not flip the umbrella — only blocking-class failures do.
101
+ // Unclassified kinds default to blocking, matching the registry's
102
+ // own descriptor() default.
103
+ const gateClass = getAuditKindDescriptor(name)?.gateClass ?? "blocking";
104
+ if (!result.ok && gateClass !== "signal")
98
105
  allOk = false;
99
106
  results.push({ name, result });
100
107
  }
@@ -15,6 +15,7 @@ import { createCliLogSink } from "#cli/commands/quality-log-store.js";
15
15
  import { emitCliCommandOutput, runLoggedChildCommand } from "#cli/commands/quality-runner.js";
16
16
  export const REPO_AUDIT_REGISTRY = {
17
17
  "catalog-drift": async (root) => (await import("#audit/repo-guardrails")).auditCatalogDrift(root),
18
+ "typescript-version": async (root) => (await import("#audit/typescript-version")).auditTypescriptVersion(root),
18
19
  "package-surface": async (root) => (await import("#audit/package-surface")).auditPackageSurface(root),
19
20
  "command-surface": async (root) => (await import("#audit/command-surface")).auditCommandSurface(root),
20
21
  "reference-parity-matrix": async (root, options) => (await import("#audit/reference-parity-matrix")).auditReferenceParityMatrix(root, undefined, {
@@ -123,6 +124,7 @@ export const REPO_AUDIT_REGISTRY = {
123
124
  "secrets-config": async (root) => (await import("#audit/secrets-config")).auditSecretsConfig(root),
124
125
  "consumer-agent-kit-dependency": async (root) => (await import("#audit/consumer-agent-kit-dependency")).auditConsumerAgentKitDependency(root),
125
126
  "ci-test-perf": async (root) => (await import("#audit/ci-test-perf")).auditCiTestPerf(root),
127
+ "ci-guardrails-wiring": async (root) => (await import("#audit/ci-guardrails-wiring")).auditCiGuardrailsWiring(root),
126
128
  "test-scan-perf": async (root, options) => (await import("#audit/test-scan-perf")).auditTestScanPerf(root, {
127
129
  affectedFiles: options.affectedFiles,
128
130
  }),
@@ -140,9 +142,14 @@ export const REPO_AUDIT_REGISTRY = {
140
142
  draftTechDebt: options.draftTechDebt,
141
143
  }),
142
144
  "harness-overlay-evidence": async (root) => (await import("#audit/harness-overlay-evidence")).auditHarnessOverlayEvidence(root),
145
+ "host-substitution-risk": async (root) => (await import("#audit/host-substitution-risk")).auditHostSubstitutionRisk(root),
143
146
  "legacy-workflow-identity": async (root) => (await import("#audit/legacy-workflow-identity")).auditLegacyWorkflowIdentity(root),
144
147
  rules: async (root) => runContentAudit(root, "rule"),
145
148
  skills: async (root) => runContentAudit(root, "skill"),
149
+ "changeset-required": async (root, options) => (await import("#audit/changeset-required")).auditChangesetRequired(root, {
150
+ baseRef: options.baseRef ?? options.base,
151
+ changedFiles: options.affectedFiles,
152
+ }),
146
153
  };
147
154
  async function runContentAudit(root, kind) {
148
155
  const { auditContent } = await import("../../content/audit.js");
@@ -467,22 +474,31 @@ export function registerAuditCommand(cli) {
467
474
  }
468
475
  case "aggregate-result": {
469
476
  const { formatRepoAuditReport } = await import("#audit/repo-guardrails");
477
+ const isSignal = (name) => getAuditKindDescriptor(name)?.gateClass === "signal";
470
478
  for (const { name, result } of outcome.results) {
471
479
  if (result.ok)
472
480
  continue;
473
- sink.write(`\n[${name}]\n${formatRepoAuditReport(result)}\n`);
481
+ const label = isSignal(name) ? `${name} (signal, non-blocking)` : name;
482
+ sink.write(`\n[${label}]\n${formatRepoAuditReport(result)}\n`);
474
483
  }
475
484
  const failed = outcome.results.filter(({ result }) => !result.ok);
485
+ const blockingFailed = failed.filter(({ name }) => !isSignal(name));
486
+ const signalFailed = failed.filter(({ name }) => isSignal(name));
476
487
  if (failed.length > 0) {
477
- sink.write(`\nguardrails: ${failed.length}/${outcome.results.length} audits failed: ${failed
478
- .map(({ name }) => name)
479
- .join(", ")}\n`);
488
+ const parts = [];
489
+ if (blockingFailed.length > 0) {
490
+ parts.push(`${blockingFailed.length} blocking: ${blockingFailed.map(({ name }) => name).join(", ")}`);
491
+ }
492
+ if (signalFailed.length > 0) {
493
+ parts.push(`${signalFailed.length} signal/non-blocking: ${signalFailed.map(({ name }) => name).join(", ")}`);
494
+ }
495
+ sink.write(`\nguardrails: ${failed.length}/${outcome.results.length} audits reported findings (${parts.join("; ")})\n`);
480
496
  }
481
497
  exitCode = outcome.code;
482
498
  summary =
483
499
  outcome.code === 0
484
500
  ? "audit guardrails passed"
485
- : `audit guardrails failed (${failed.length}/${outcome.results.length})`;
501
+ : `audit guardrails failed (${blockingFailed.length}/${outcome.results.length})`;
486
502
  break;
487
503
  }
488
504
  case "quality-exit": {
@@ -9,6 +9,7 @@ import { setTimeout as delay } from "node:timers/promises";
9
9
  import { applyRuntimeProgressSnapshot, buildBlueprintProgressBridgeState, normalizeRuntimeTaskSnapshot, projectBlueprintLifecycleFromRuntime, readBlueprintExecutionArtifacts, runtimeStateSnapshotSchema, writeBlueprintExecutionArtifacts, writeBlueprintExecutionMetadata, } from "#index";
10
10
  import { applyBlueprintLifecycleToFile, parseBlueprint } from "#local";
11
11
  import { resolveBlueprintRoot } from "#utils/blueprint-root";
12
+ import { resolveBlueprintDocumentIdentity } from "#utils/document-paths.js";
12
13
  import { clearBlueprintExecutionState, persistBlueprintExecutionArtifacts, persistBlueprintExecutionMetadata, persistBlueprintProgressBridgeState, readBlueprintExecutionArtifactsState, readBlueprintExecutionState, readBlueprintProgressBridgeState, readBlueprintRuntimeSnapshot, writeBlueprintRuntimeSnapshot, } from "./execution-io.js";
13
14
  import { assertCompletionEvidence, mergeExecutionArtifacts, } from "./execution-state.js";
14
15
  import { buildBlueprintExecutionControlCommand, buildBlueprintExecutionLaunchCommand, buildBlueprintExecutionRuntimePaths, buildBlueprintLaunchSpec, buildListTasksVerificationCommand, isMissingFileError, nowIsoTimestamp, parseRuntimeApiResponse, parseTeamExecutionId, resolveControlStatus, toProjectRelativePath, uniqueStrings, } from "./execution-spec.js";
@@ -114,22 +115,33 @@ export async function reconcileBlueprintRuntimeSnapshot(projectRoot, blueprintPa
114
115
  const currentStatus = currentDir.split(blueprintsRoot + path.sep)[1]?.split(path.sep)[0];
115
116
  const relativeSlug = slug.replace(/^[^/]+\//u, "");
116
117
  const targetDir = path.join(blueprintsRoot, nextStatus, relativeSlug);
117
- const targetPath = path.join(targetDir, "_overview.md");
118
+ // A flat document owns only its own file; renaming its parent directory
119
+ // would move the entire lifecycle state directory. Lifecycle moves are
120
+ // shape-preserving: flat stays flat, folder moves as a folder.
121
+ const isFlatDocument = resolveBlueprintDocumentIdentity(blueprintsRoot, blueprintPath)?.shape === "flat";
122
+ const targetPath = isFlatDocument ? `${targetDir}.md` : path.join(targetDir, "_overview.md");
123
+ const documentMoved = blueprintPath !== targetPath;
118
124
  const nextMarkdown = completionEvidence
119
125
  ? writeBlueprintExecutionArtifacts(result.markdown, completionEvidence)
120
126
  : result.markdown;
121
- if (currentDir !== targetDir && currentStatus && currentStatus !== nextStatus) {
127
+ if (documentMoved && currentStatus && currentStatus !== nextStatus) {
122
128
  const { mkdir, rename } = await import("node:fs/promises");
123
- await mkdir(path.dirname(targetDir), { recursive: true });
124
- await rename(currentDir, targetDir);
129
+ if (isFlatDocument) {
130
+ await mkdir(path.dirname(targetPath), { recursive: true });
131
+ await rename(blueprintPath, targetPath);
132
+ }
133
+ else {
134
+ await mkdir(path.dirname(targetDir), { recursive: true });
135
+ await rename(currentDir, targetDir);
136
+ }
125
137
  await writeFile(targetPath, nextMarkdown, "utf-8");
126
138
  }
127
139
  else {
128
140
  await writeFile(blueprintPath, nextMarkdown, "utf-8");
129
141
  }
130
142
  return {
131
- moved: currentDir !== targetDir,
132
- path: currentDir !== targetDir ? targetPath : blueprintPath,
143
+ moved: documentMoved,
144
+ path: documentMoved ? targetPath : blueprintPath,
133
145
  status: result.execution.status,
134
146
  };
135
147
  }
@@ -24,9 +24,9 @@ import { getBlueprintDocumentPaths } from "#utils/document-paths.js";
24
24
  import { resolveBlueprintRoot } from "#utils/blueprint-root.js";
25
25
  import { reIngestProjection } from "#projection-ready.js";
26
26
  import { assertAllTasksHaveCanonicalPassingEvidence } from "#verification.js";
27
- import { applyPromotionTrustGate } from "#trust/promotion.js";
27
+ import { assertPromotionTrustProofCurrent, preparePromotionTrustGate, } from "#trust/promotion.js";
28
28
  import matter from "gray-matter";
29
- import { evaluateApprovalGate, formatApprovalGateRequirement } from "#lifecycle/audit";
29
+ import { assertCompletedApprovalGate, evaluateApprovalGate, formatApprovalGateRequirement, } from "#lifecycle/audit";
30
30
  /**
31
31
  * Module-level factory. `null` = use the production default (loadSyncCredentials
32
32
  * from auth.ts + BlueprintSyncClient + ReplicaManager — lazy-imported so that
@@ -321,24 +321,38 @@ async function advanceTaskLocked(cwd, blueprintSlug, taskId, toStatus) {
321
321
  * - Re-ingests into DB
322
322
  */
323
323
  export async function promoteBlueprint(cwd, slug, toState) {
324
+ const adapter = await resolveSyncAdapterForCli(cwd);
325
+ // Pull platform state under the mutation lock, then release it before any
326
+ // Trust Dossier command spawns a child `wp`. Child startup/ingestion can
327
+ // legitimately need the same repo-scoped lock.
328
+ await withMarkdownWriteLock(cwd, () => runCliPlatformMutationSync(adapter, {
329
+ label: "wp_blueprint_promote",
330
+ ensureFreshSlug: slug,
331
+ }));
332
+ const found = findBlueprintDocument(resolveBlueprintRoot(cwd), slug);
333
+ if (!found) {
334
+ throw new Error(`Blueprint "${slug}" not found in any state directory under ${resolveBlueprintRoot(cwd)}`);
335
+ }
336
+ const crossesDraftApprovalBoundary = found.state === "draft" && (toState === "planned" || toState === "in-progress");
337
+ const trustProof = crossesDraftApprovalBoundary
338
+ ? preparePromotionTrustGate({
339
+ repoRoot: cwd,
340
+ file: found.documentPath,
341
+ markdown: readFileSync(found.documentPath, "utf8"),
342
+ })
343
+ : null;
324
344
  // F9/R7: cross-worktree markdown writes serialize via the repo-scoped lock.
325
- return withMarkdownWriteLock(cwd, () => promoteBlueprintLocked(cwd, slug, toState));
345
+ // The proof is revalidated inside this lock before any lifecycle write.
346
+ return withMarkdownWriteLock(cwd, () => promoteBlueprintLocked(cwd, slug, toState, adapter, trustProof));
326
347
  }
327
- async function promoteBlueprintLocked(cwd, slug, toState) {
348
+ async function promoteBlueprintLocked(cwd, slug, toState, adapter, trustProof) {
328
349
  const blueprintRoot = resolveBlueprintRoot(cwd);
329
350
  const found = findBlueprintDocument(blueprintRoot, slug);
330
351
  if (!found) {
331
352
  throw new Error(`Blueprint "${slug}" not found in any state directory under ${blueprintRoot}`);
332
353
  }
333
354
  const { dir: currentDir, documentPath: currentDocumentPath, shape, state: currentState } = found;
334
- // Guard: refuse to complete if any tasks are not done/dropped.
335
- // Markdown `_overview.md` is SSOT for task status + verification evidence.
336
- // The projection DB is derived and can lag when agents edit task Status
337
- // blocks without going through `task complete` (hash-skipping reingest then
338
- // leaves stale todo rows). Do not fail completion on projection lag —
339
- // reingest after the markdown gate so consumers see fresh rows.
340
- if (toState === "completed") {
341
- const markdown = readFileSync(currentDocumentPath, "utf8");
355
+ const assertCompletionReady = (markdown) => {
342
356
  const blueprint = parseBlueprint(markdown, slug);
343
357
  if (blueprint.tasks.length === 0) {
344
358
  throw new Error(`Cannot promote "${slug}" to completed: zero-task blueprints cannot complete through the public lifecycle surface`);
@@ -349,30 +363,30 @@ async function promoteBlueprintLocked(cwd, slug, toState) {
349
363
  throw new Error(`Cannot promote "${slug}" to completed: the following tasks are not done/dropped: ${list}`);
350
364
  }
351
365
  assertAllTasksHaveCanonicalPassingEvidence(markdown, blueprint.tasks.filter((task) => task.status === "done").map((task) => task.id));
352
- // Best-effort projection refresh. Clear content hash first so a lagging
353
- // tasks table is rewritten even when a prior corrupt UPDATE left the
354
- // stored hash equal to current markdown.
355
- await forceReIngestBlueprint(cwd, slug, currentDocumentPath);
356
- }
357
- // Platform freshness must be resolved before trust validation so the proof
358
- // applies to the current replica, not stale local markdown.
359
- // Iron rule: resolveSyncAdapterForCli() returns null when WP_BLUEPRINT_PLATFORM_DISABLED=1.
360
- const adapter = await resolveSyncAdapterForCli(cwd);
361
- await runCliPlatformMutationSync(adapter, {
362
- label: "wp_blueprint_promote",
363
- ensureFreshSlug: slug,
364
- });
366
+ // Completion parity with CI: the CLI completion owner uses the same
367
+ // audit-owned gate as the MCP funnel and execution engine, so all normal
368
+ // paths into `completed` enforce the same plan-purpose approval.
369
+ try {
370
+ assertCompletedApprovalGate(currentDocumentPath, markdown);
371
+ }
372
+ catch (error) {
373
+ throw new Error(`Cannot complete "${slug}": ${error instanceof Error ? error.message : String(error)}`);
374
+ }
375
+ };
365
376
  // Trust must be proven before any platform status_changed event is published.
366
377
  let content = readFileSync(currentDocumentPath, "utf8");
367
378
  let trustedSource = content;
379
+ if (toState === "completed") {
380
+ assertCompletionReady(content);
381
+ }
368
382
  const crossesDraftApprovalBoundary = currentState === "draft" && (toState === "planned" || toState === "in-progress");
369
383
  if (crossesDraftApprovalBoundary) {
370
- content = applyPromotionTrustGate({
371
- repoRoot: cwd,
372
- file: currentDocumentPath,
373
- markdown: content,
374
- });
375
- trustedSource = readFileSync(currentDocumentPath, "utf8");
384
+ if (trustProof === null) {
385
+ throw new Error("Blueprint trust gate invalidated: no external gate proof is available");
386
+ }
387
+ assertPromotionTrustProofCurrent({ repoRoot: cwd, file: currentDocumentPath, markdown: content }, trustProof);
388
+ content = trustProof.trustedMarkdown;
389
+ trustedSource = trustProof.sourceMarkdown;
376
390
  // Governance Piece 1 — HARD gate at the promotion boundary. One
377
391
  // provenance-backed Claude/Codex/Grok or OpenCode-Go model approval can
378
392
  // promote. Check AFTER the trust gate so trust failures surface first.
@@ -402,18 +416,23 @@ async function promoteBlueprintLocked(cwd, slug, toState) {
402
416
  if (crossesDraftApprovalBoundary) {
403
417
  const refreshedSource = readFileSync(currentDocumentPath, "utf8");
404
418
  if (refreshedSource !== trustedSource) {
405
- content = applyPromotionTrustGate({
406
- repoRoot: cwd,
407
- file: currentDocumentPath,
408
- markdown: refreshedSource,
409
- });
410
- const approvalEvaluation = evaluateApprovalGate(currentDocumentPath, matter(content).data.approvals);
411
- if (!approvalEvaluation.satisfied) {
412
- throw new Error(`Cannot promote "${slug}" to ${toState}: frontmatter \`approvals:\` does not satisfy the promotion reviewer mix (${formatApprovalGateRequirement(approvalEvaluation)}). Record approvals, commit the matching review ledger (e.g. reviews.md), and include tracked review transcript/artifact files referenced by each structured review entry — see catalog/agent/rules/pre-implementation.md.`);
413
- }
419
+ throw new Error(`Cannot promote "${slug}" to ${toState}: blueprint changed after external trust gates ran; retry promotion`);
414
420
  }
421
+ assertPromotionTrustProofCurrent({ repoRoot: cwd, file: currentDocumentPath, markdown: refreshedSource }, trustProof);
422
+ }
423
+ if (toState === "completed") {
424
+ // The platform pull can replace the document after the first completion
425
+ // check. Re-read and re-assert against the exact content written below.
426
+ content = readFileSync(currentDocumentPath, "utf8");
427
+ assertCompletionReady(content);
415
428
  }
416
429
  }
430
+ if (toState === "completed") {
431
+ // Best-effort projection refresh. Clear content hash first so a lagging
432
+ // tasks table is rewritten even when a prior corrupt UPDATE left the
433
+ // stored hash equal to current markdown.
434
+ await forceReIngestBlueprint(cwd, slug, currentDocumentPath);
435
+ }
417
436
  // Always update local markdown + SQLite.
418
437
  // Platform-first: these become derived artifacts; disabled: these are canonical.
419
438
  // Update frontmatter in the current location first, then move
@@ -1,6 +1,6 @@
1
1
  import type { BlueprintAuditResult, BlueprintSummary } from "#local";
2
2
  import type { BlueprintTemplateEntry } from "#sync/types.js";
3
- import type { AdvanceTaskResult, BlueprintCommandOptions, BlueprintLifecycleMutationResult, CreateBlueprintResult, ExportBlueprintResult, ExecuteBlueprintResult, MoveBlueprintResult, PromoteBlueprintResult, ShowBlueprintResult } from "./router.js";
3
+ import type { AdvanceTaskResult, BlueprintCommandOptions, BlueprintCanonicalizeResult, BlueprintLifecycleMutationResult, CreateBlueprintResult, ExportBlueprintResult, ExecuteBlueprintResult, MoveBlueprintResult, PromoteBlueprintResult, ShowBlueprintResult } from "./router.js";
4
4
  /**
5
5
  * Override the platform template fetcher — for tests only.
6
6
  * Pass `null` to restore the production default.
@@ -21,6 +21,7 @@ export declare class BlueprintAuditFailedError extends Error {
21
21
  interface BlueprintCommandDependencies {
22
22
  advanceBlueprintTask: (slug: string, taskId: string, toStatus: string, options: BlueprintCommandOptions) => Promise<AdvanceTaskResult>;
23
23
  auditBlueprints: (options: BlueprintCommandOptions) => Promise<BlueprintAuditResult>;
24
+ canonicalizeBlueprintBySlug: (slug: string, options: BlueprintCommandOptions) => Promise<BlueprintCanonicalizeResult>;
24
25
  controlBlueprintExec: (action: "status" | "resume" | "stop", slug: string, options: BlueprintCommandOptions) => Promise<ExecuteBlueprintResult>;
25
26
  readBlueprintExecutionLogs: (slug: string, options: BlueprintCommandOptions) => Promise<ExecuteBlueprintResult>;
26
27
  createBlueprint: (goal: string, options: BlueprintCommandOptions) => Promise<CreateBlueprintResult>;
@@ -3,7 +3,9 @@ import { tmpdir } from "node:os";
3
3
  import { randomUUID } from "node:crypto";
4
4
  import path from "node:path";
5
5
  import { executeBlueprintDbSubcommand } from "./db-commands.js";
6
+ import { getBlueprintSubcommandNames } from "./router-output.js";
6
7
  import { listTemplates, resolveTemplate } from "./template-resolver.js";
8
+ import { TRUST_DOSSIER_SCAFFOLD } from "#trust/scaffold.js";
7
9
  // ---------------------------------------------------------------------------
8
10
  // Platform template fetcher (injectable for tests)
9
11
  // ---------------------------------------------------------------------------
@@ -232,6 +234,15 @@ export async function executeBlueprintSubcommand(subcommand, args, options, deps
232
234
  deps.printBlueprintOutput(options.json ? result : result.message, options.json);
233
235
  return;
234
236
  }
237
+ case "canonicalize": {
238
+ const slug = args[0];
239
+ if (!slug) {
240
+ throw new Error("Usage: wp blueprint canonicalize <slug>");
241
+ }
242
+ const result = await deps.canonicalizeBlueprintBySlug(slug, options);
243
+ deps.printBlueprintOutput(options.json ? { from: result.from, to: result.to, moved: result.moved } : result.message, options.json);
244
+ return;
245
+ }
235
246
  case "start": {
236
247
  const slug = args[0];
237
248
  if (!slug) {
@@ -350,8 +361,16 @@ export async function executeBlueprintSubcommand(subcommand, args, options, deps
350
361
  deps.printBlueprintOutput(options.json ? result : result.message, options.json);
351
362
  return;
352
363
  }
364
+ case "trust-dossier": {
365
+ // Read-only: prints an already-valid "## Trust Dossier" starter block
366
+ // (src/blueprint/trust/scaffold.ts). No file writes, no worktree/lock
367
+ // interaction — an author pastes the output into their overview and
368
+ // edits the example values.
369
+ deps.printBlueprintOutput(options.json ? { markdown: TRUST_DOSSIER_SCAFFOLD } : TRUST_DOSSIER_SCAFFOLD, options.json);
370
+ return;
371
+ }
353
372
  default: {
354
- throw new Error(`Unknown blueprint subcommand: ${subcommand}\n\nUse one of: list, new, show, exec, start, park, task, finalize, promote, audit, move, control, logs, db, export`);
373
+ throw new Error(`Unknown blueprint subcommand: ${subcommand}\n\nUse one of: ${getBlueprintSubcommandNames().join(", ")}`);
355
374
  }
356
375
  }
357
376
  }
@@ -1,7 +1,28 @@
1
1
  import type { Blueprint, BlueprintAuditResult, BlueprintSummary } from "#local";
2
2
  import type { CreateBlueprintResult, ExecuteBlueprintResult, ShowBlueprintResult } from "./router.js";
3
+ /**
4
+ * Canonical registry of every subcommand `router-dispatch.ts` actually
5
+ * dispatches. This is the single source of truth for both the `--help`
6
+ * `Commands:` block (see `BLUEPRINT_HELP` below) and the unknown-subcommand
7
+ * error list — so a subcommand can't ship dispatched-but-undocumented
8
+ * again. `router-dispatch.test.ts` asserts this list stays equal to the
9
+ * live `case "…":` labels in the dispatch switch.
10
+ */
11
+ export interface BlueprintSubcommandSpec {
12
+ /** Matches a `case "<name>":` label in router-dispatch.ts exactly. */
13
+ readonly name: string;
14
+ /** One or more `Commands:` lines (without the leading indent). */
15
+ readonly usage: readonly string[];
16
+ }
17
+ export declare const BLUEPRINT_SUBCOMMANDS: readonly BlueprintSubcommandSpec[];
3
18
  export declare function formatTaskLine(task: Blueprint["tasks"][number]): string;
4
19
  export declare function getBlueprintHelpText(): string;
20
+ /**
21
+ * Subcommand names in registry order, for the unknown-subcommand error
22
+ * message. Kept as a function (rather than exporting a derived constant)
23
+ * so the source of truth stays singular: `BLUEPRINT_SUBCOMMANDS`.
24
+ */
25
+ export declare function getBlueprintSubcommandNames(): readonly string[];
5
26
  export interface BlueprintInventorySummary {
6
27
  total: number;
7
28
  byStatus: Record<string, number>;