@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
@@ -1,14 +1,12 @@
1
1
  import { execFileSync, spawnSync } from "node:child_process";
2
- import { existsSync, mkdirSync, writeFileSync } from "node:fs";
2
+ import { existsSync } from "node:fs";
3
3
  import { join } from "node:path";
4
4
  import { validateBlueprintTrust } from "./validator.js";
5
5
  import { parseTrustDossier } from "./dossier.js";
6
- import { parseAllowedWpCommand } from "./gates.js";
7
- import { resolvePackageAssetPreferred } from "#utils/package-assets.js";
6
+ import { parseAllowedWpCommand, wpTestFileTargets } from "./gates.js";
7
+ import { ALLOWED_WP_COMMAND_TIMEOUT_MS, executeAllowedWpCommand, tailBounded, } from "./command-runner.js";
8
8
  export { parseAllowedWpCommand };
9
- const PROMOTION_GATE_TIMEOUT_MS = 30_000;
10
- const PROMOTION_GATE_STDIO_TAIL_LIMIT = 500;
11
- export function applyPromotionTrustGate(input) {
9
+ export function preparePromotionTrustGate(input) {
12
10
  const now = (input.now ?? new Date()).toISOString();
13
11
  const syntacticMarkdown = upsertReadinessValue(upsertReadinessValue(input.markdown, "verified-at", now), "verified-head", "0123456789abcdef0123456789abcdef01234567");
14
12
  const parsedBeforeHead = parseTrustDossier(syntacticMarkdown);
@@ -30,6 +28,16 @@ export function applyPromotionTrustGate(input) {
30
28
  throw new Error(`Blueprint trust gate failed: ${preflight.violations.map((v) => `${v.section}: ${v.message}`).join("; ")}`);
31
29
  const parsed = parseTrustDossier(markdown);
32
30
  for (const gate of parsed.dossier?.gates ?? []) {
31
+ const deferredTarget = firstMissingTestTarget(input.repoRoot, gate.command);
32
+ if (deferredTarget !== undefined) {
33
+ // A `wp test --file <path>` gate whose target file does not exist yet is
34
+ // recorded as an honest non-`pass` result instead of aborting the whole
35
+ // promotion. `deferred` is a distinct failing state: it is only tolerated
36
+ // by the post-run validation below (allowDeferredGates), and the strict
37
+ // whole-repo completion audit still rejects it, so it can never merge.
38
+ markdown = updateGateLastResult(markdown, gate.gate, `deferred: test file not yet created (${deferredTarget})`);
39
+ continue;
40
+ }
33
41
  runPromotionCommand(input.repoRoot, gate.command);
34
42
  markdown = updateGateLastResult(markdown, gate.gate, `pass at ${now}`);
35
43
  }
@@ -40,10 +48,27 @@ export function applyPromotionTrustGate(input) {
40
48
  markdown,
41
49
  promotionCandidate: true,
42
50
  scanTaskAmbiguity: true,
51
+ allowDeferredGates: true,
43
52
  });
44
53
  if (!validated.ok)
45
54
  throw new Error(`Blueprint trust gate failed: ${validated.violations.map((v) => `${v.section}: ${v.message}`).join("; ")}`);
46
- return markdown;
55
+ return {
56
+ file: input.file,
57
+ sourceMarkdown: input.markdown,
58
+ verifiedHead: head,
59
+ trustedMarkdown: markdown,
60
+ };
61
+ }
62
+ export function applyPromotionTrustGate(input) {
63
+ return preparePromotionTrustGate(input).trustedMarkdown;
64
+ }
65
+ export function assertPromotionTrustProofCurrent(input, proof) {
66
+ if (input.file !== proof.file || input.markdown !== proof.sourceMarkdown) {
67
+ throw new Error("Blueprint trust gate invalidated: the blueprint changed after external gates ran; retry promotion");
68
+ }
69
+ if (readHead(input.repoRoot) !== proof.verifiedHead) {
70
+ throw new Error("Blueprint trust gate invalidated: git HEAD changed after external gates ran; retry promotion");
71
+ }
47
72
  }
48
73
  function readHead(repoRoot) {
49
74
  const testHead = process.env["WP_BLUEPRINT_TRUST_GATE_TEST_HEAD"];
@@ -61,123 +86,48 @@ function readHead(repoRoot) {
61
86
  throw new Error("Blueprint trust gate failed: git HEAD is unavailable");
62
87
  }
63
88
  }
89
+ /**
90
+ * Returns the first `wp test --file` target of `command` that does not exist on
91
+ * disk (resolved against `repoRoot`), or undefined when the gate is not a
92
+ * `wp test --file` gate or all its targets exist. A missing target is
93
+ * indistinguishable at `existsSync` time from "renamed / wrong path / deleted",
94
+ * so callers must treat it as a non-`pass` deferral, never a success.
95
+ */
96
+ function firstMissingTestTarget(repoRoot, command) {
97
+ const targets = wpTestFileTargets(parseAllowedWpCommand(command));
98
+ return targets.find((target) => !existsSync(join(repoRoot, target)));
99
+ }
64
100
  export function runPromotionCommand(repoRoot, command, deps = {}) {
65
- const argv = parseAllowedWpCommand(command);
66
- const [binary, ...args] = argv;
67
- if (binary === undefined)
68
- throw new Error(`Promotion gate command is empty: ${command}`);
69
- const invocation = resolvePromotionGateInvocation(repoRoot, binary, args);
70
- const spawn = deps.spawn ?? spawnSync;
71
- const result = spawn(invocation.command, invocation.args, {
72
- cwd: repoRoot,
73
- encoding: "utf8",
74
- stdio: ["ignore", "pipe", "pipe"],
75
- timeout: PROMOTION_GATE_TIMEOUT_MS,
76
- env: invocation.env,
101
+ const result = executeAllowedWpCommand({
102
+ command,
103
+ executionRoot: repoRoot,
104
+ logRoot: join(repoRoot, ".webpresso", "logs", "promotion-gates"),
105
+ ...(deps.spawn ? { spawn: deps.spawn } : {}),
77
106
  });
78
- if (result.status === 0 && !result.error && result.signal === null)
107
+ if (result.outcome === "passed")
79
108
  return;
80
- const stdout = typeof result.stdout === "string" ? result.stdout : "";
81
- const stderr = typeof result.stderr === "string" ? result.stderr : "";
82
- const logPath = writePromotionGateLog(repoRoot, command, invocation, stdout, stderr, result);
83
- throw new Error(formatPromotionGateFailure(command, stdout, stderr, result, logPath));
109
+ throw new Error(formatPromotionGateFailure(result));
84
110
  }
85
- function resolvePromotionGateInvocation(repoRoot, binary, args) {
86
- if (binary === "./bin/wp" || binary === "wp") {
87
- const localLauncher = resolveRepoWpLauncher(repoRoot);
88
- if (localLauncher !== null) {
89
- return {
90
- command: localLauncher,
91
- args: [...args],
92
- env: { ...process.env, PATH: `${repoRoot}/bin:${process.env["PATH"] ?? ""}` },
93
- };
94
- }
95
- if (binary === "./bin/wp") {
96
- return {
97
- command: join(repoRoot, "bin", process.platform === "win32" ? "wp.cmd" : "wp"),
98
- args: [...args],
99
- env: { ...process.env, PATH: `${repoRoot}/bin:${process.env["PATH"] ?? ""}` },
100
- };
101
- }
102
- const packagedLauncher = resolvePackagedWpLauncher();
103
- return {
104
- command: process.execPath,
105
- args: [packagedLauncher, ...args],
106
- env: { ...process.env },
107
- };
108
- }
109
- return {
110
- command: binary,
111
- args: [...args],
112
- env: { ...process.env, PATH: `${repoRoot}/bin:${process.env["PATH"] ?? ""}` },
113
- };
114
- }
115
- function resolveRepoWpLauncher(repoRoot) {
116
- const launcher = join(repoRoot, "bin", process.platform === "win32" ? "wp.cmd" : "wp");
117
- return existsSync(launcher) ? launcher : null;
118
- }
119
- function resolvePackagedWpLauncher() {
120
- const launcher = resolvePackageAssetPreferred([
121
- process.platform === "win32" ? "bin/wp.cmd" : "bin/wp",
122
- "bin/wp",
123
- ]);
124
- if (!existsSync(launcher)) {
125
- throw new Error(`Promotion gate failed: packaged wp launcher is unavailable at ${launcher}`);
126
- }
127
- return launcher;
128
- }
129
- function formatPromotionGateFailure(command, stdout, stderr, result, logPath) {
111
+ function formatPromotionGateFailure(result) {
130
112
  const details = [];
131
- if (typeof result.status === "number")
132
- details.push(`exit=${result.status}`);
113
+ if (typeof result.exitCode === "number")
114
+ details.push(`exit=${result.exitCode}`);
133
115
  if (result.signal)
134
116
  details.push(`signal=${result.signal}`);
135
- if (result.error) {
136
- const code = typeof result.error === "object" &&
137
- result.error !== null &&
138
- "code" in result.error &&
139
- typeof result.error.code === "string"
140
- ? result.error.code
141
- : null;
142
- details.push(code ? `spawn_error=${code}` : `spawn_error=${result.error.message}`);
143
- if (code === "ETIMEDOUT")
144
- details.push(`timeout=${PROMOTION_GATE_TIMEOUT_MS}ms`);
117
+ if (result.errorMessage) {
118
+ details.push(`spawn_error=${result.failureCode ?? result.errorMessage}`);
119
+ if (result.outcome === "timed-out")
120
+ details.push(`timeout=${ALLOWED_WP_COMMAND_TIMEOUT_MS}ms`);
145
121
  }
146
- const stderrTail = tailBounded(stderr);
147
- const stdoutTail = tailBounded(stdout);
122
+ const stderrTail = tailBounded(result.stderr);
123
+ const stdoutTail = tailBounded(result.stdout);
148
124
  if (stderrTail.length > 0)
149
125
  details.push(`stderr_tail=${JSON.stringify(stderrTail)}`);
150
126
  if (stdoutTail.length > 0)
151
127
  details.push(`stdout_tail=${JSON.stringify(stdoutTail)}`);
152
- details.push(`log=${logPath}`);
153
- return `Promotion gate failed (${command}): ${details.join("; ")}`;
154
- }
155
- function tailBounded(text) {
156
- if (text.length <= PROMOTION_GATE_STDIO_TAIL_LIMIT)
157
- return text.trim();
158
- return text.slice(-PROMOTION_GATE_STDIO_TAIL_LIMIT).trim();
159
- }
160
- function writePromotionGateLog(repoRoot, command, invocation, stdout, stderr, result) {
161
- const logsDir = join(repoRoot, ".webpresso", "logs", "promotion-gates");
162
- mkdirSync(logsDir, { recursive: true });
163
- const logPath = join(logsDir, `${Date.now()}-promotion-gate.log`);
164
- const errorBlock = result.error
165
- ? `error: ${result.error.name}: ${result.error.message}\n`
166
- : "error: none\n";
167
- writeFileSync(logPath, [
168
- `command: ${command}`,
169
- `invocation: ${invocation.command} ${invocation.args.join(" ")}`,
170
- `exit: ${result.status ?? "null"}`,
171
- `signal: ${result.signal ?? "null"}`,
172
- `timeout_ms: ${PROMOTION_GATE_TIMEOUT_MS}`,
173
- errorBlock.trimEnd(),
174
- "--- stderr ---",
175
- stderr,
176
- "--- stdout ---",
177
- stdout,
178
- "",
179
- ].join("\n"), "utf8");
180
- return logPath;
128
+ if (result.logPath)
129
+ details.push(`log=${result.logPath}`);
130
+ return `Promotion gate failed (${result.command}): ${details.join("; ")}`;
181
131
  }
182
132
  function upsertReadinessValue(markdown, key, value) {
183
133
  const re = new RegExp(`^- ${key}: .*$`, "mu");
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Trust Dossier authoring scaffold.
3
+ *
4
+ * `dossier.ts` / `evidence.ts` / `gates.ts` / `ambiguity.ts` define strict
5
+ * validation for a blueprint's `## Trust Dossier` section, but nothing
6
+ * generates a starting template. `TRUST_DOSSIER_SCAFFOLD` is a complete,
7
+ * already-valid worked example — proven by `scaffold.test.ts` to pass
8
+ * `validateBlueprintTrust` under both the strict configuration
9
+ * `wp audit blueprint-trust` uses and the draft-preflight configuration
10
+ * `wp_blueprint_put` / `preparePromotionTrustGate` use — so an author can
11
+ * paste it in and edit the example values instead of hand-deriving the
12
+ * contract from source.
13
+ *
14
+ * `verified-at` / `verified-head` stay as inert, obviously-fake placeholders:
15
+ * `preparePromotionTrustGate` (./promotion.ts) overwrites both with the real
16
+ * timestamp/HEAD on every promotion attempt, so there is no need for this
17
+ * pure, dependency-free module to shell out to git.
18
+ */
19
+ export declare const TRUST_DOSSIER_SCAFFOLD: string;
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Trust Dossier authoring scaffold.
3
+ *
4
+ * `dossier.ts` / `evidence.ts` / `gates.ts` / `ambiguity.ts` define strict
5
+ * validation for a blueprint's `## Trust Dossier` section, but nothing
6
+ * generates a starting template. `TRUST_DOSSIER_SCAFFOLD` is a complete,
7
+ * already-valid worked example — proven by `scaffold.test.ts` to pass
8
+ * `validateBlueprintTrust` under both the strict configuration
9
+ * `wp audit blueprint-trust` uses and the draft-preflight configuration
10
+ * `wp_blueprint_put` / `preparePromotionTrustGate` use — so an author can
11
+ * paste it in and edit the example values instead of hand-deriving the
12
+ * contract from source.
13
+ *
14
+ * `verified-at` / `verified-head` stay as inert, obviously-fake placeholders:
15
+ * `preparePromotionTrustGate` (./promotion.ts) overwrites both with the real
16
+ * timestamp/HEAD on every promotion attempt, so there is no need for this
17
+ * pure, dependency-free module to shell out to git.
18
+ */
19
+ const PLACEHOLDER_HEAD = "0".repeat(40);
20
+ export const TRUST_DOSSIER_SCAFFOLD = `## Trust Dossier
21
+
22
+ <!-- Trust Dossier scaffold — generated by \`wp blueprint trust-dossier\`. -->
23
+ <!-- Replace every example value below, then delete these comment lines before promotion. -->
24
+ <!-- Constraints enforced by \`wp audit blueprint-trust\` / \`wp_blueprint_put\`: -->
25
+ <!-- - All 5 "###" subsections below are required, verbatim, exactly once each. -->
26
+ <!-- - Material Claims "repo:<path>" evidence must be a bare, existing repo-relative path -->
27
+ <!-- (no ":line" suffix, no leading "/", no "../"). -->
28
+ <!-- - "Residual Unknowns" must render exactly \`None.\` when nothing is outstanding. -->
29
+ <!-- - Promotion Gates "Command" must be a \`wp\` facade call: \`wp typecheck\`, \`wp lint\` -->
30
+ <!-- (no extra args), \`wp test --file <path>\` (repeatable), or -->
31
+ <!-- \`wp audit <kind> [--json|--affected|--branch|--base <val>]\`. -->
32
+ <!-- - No TBD, TODO, "decide during implementation", "open question", or <placeholder> tokens -->
33
+ <!-- anywhere in the 5 subsections below. -->
34
+
35
+ ### Readiness Verdict
36
+
37
+ - promotion-ready: true
38
+ - unresolved-count: 0
39
+ - verified-at: 1970-01-01T00:00:00.000Z
40
+ - verified-head: ${PLACEHOLDER_HEAD}
41
+ - trust-gate-version: v1
42
+
43
+ ### Material Claims
44
+
45
+ | ID | Claim | Evidence |
46
+ | --- | --- | --- |
47
+ | C1 | Example material claim — replace with a real, falsifiable claim this blueprint depends on. | repo:README.md |
48
+
49
+ ### Material Decisions
50
+
51
+ | ID | Decision | Chosen option | Rejected alternatives | Rationale |
52
+ | --- | --- | --- | --- | --- |
53
+ | D1 | Example material decision — replace with a real decision this blueprint locks in. | Example chosen option. | Example rejected alternative and why it lost. | Example rationale for the chosen option. |
54
+
55
+ ### Promotion Gates
56
+
57
+ | Gate | Command | Expected outcome | Last result |
58
+ | --- | --- | --- | --- |
59
+ | typecheck | wp typecheck | Typecheck passes with zero errors. | pass (scaffold example — rerun and record the real result before promotion) |
60
+
61
+ ### Residual Unknowns
62
+
63
+ None.
64
+ `;
@@ -8,6 +8,7 @@ export type ValidateBlueprintTrustInput = {
8
8
  promotionCandidate?: boolean;
9
9
  requirePassingGates?: boolean;
10
10
  scanTaskAmbiguity?: boolean;
11
+ allowDeferredGates?: boolean;
11
12
  };
12
13
  export type BlueprintTrustViolation = TrustDossierViolation & {
13
14
  file: string;
@@ -51,7 +51,8 @@ export function validateBlueprintTrust(input) {
51
51
  message: error instanceof Error ? error.message : String(error),
52
52
  });
53
53
  }
54
- if (input.requirePassingGates !== false && !/^pass\b/iu.test(gate.lastResult))
54
+ if (input.requirePassingGates !== false &&
55
+ !isAcceptableGateResult(gate.lastResult, input.allowDeferredGates))
55
56
  violations.push({
56
57
  section: "Promotion Gates",
57
58
  claimId: gate.gate,
@@ -64,3 +65,8 @@ export function validateBlueprintTrust(input) {
64
65
  violations: violations.map((v) => ({ ...v, file: input.file })),
65
66
  };
66
67
  }
68
+ function isAcceptableGateResult(lastResult, allowDeferredGates) {
69
+ if (/^pass\b/iu.test(lastResult))
70
+ return true;
71
+ return allowDeferredGates === true && /^deferred:/iu.test(lastResult);
72
+ }
@@ -6,6 +6,7 @@
6
6
  import { access, readFile, writeFile } from "node:fs/promises";
7
7
  import path from "node:path";
8
8
  import { parseBlueprint } from "#core/parser";
9
+ import { getBlueprintDocumentCandidates } from "./document-paths.js";
9
10
  import { resolveBlueprintRoot } from "./blueprint-root.js";
10
11
  /**
11
12
  * Validates that all tasks in a plan are complete.
@@ -111,7 +112,13 @@ export async function archiveBlueprint(slug, projectPath) {
111
112
  if (isAlreadyCompleted(slug)) {
112
113
  return { success: false, error: "Plan is already completed" };
113
114
  }
114
- const paths = buildPlanPaths(projectPath, slug);
115
+ const paths = await buildPlanPaths(projectPath, slug);
116
+ if (paths.ambiguousPaths) {
117
+ return {
118
+ success: false,
119
+ error: `Plan ${slug} exists in both flat and folder forms; remove one before archiving: ${paths.ambiguousPaths.join(", ")}`,
120
+ };
121
+ }
115
122
  // Check if plan exists
116
123
  const planExists = await checkPlanExists(paths.sourcePath);
117
124
  if (!planExists) {
@@ -132,16 +139,31 @@ export async function archiveBlueprint(slug, projectPath) {
132
139
  /**
133
140
  * Builds source and target paths for archival.
134
141
  *
142
+ * Resolves the existing flat or folder document via the shared candidates;
143
+ * when neither shape exists, falls back to the canonical folder path so the
144
+ * caller reports the same "Plan not found" error as before.
145
+ *
135
146
  * @param projectPath - Root path of the project
136
147
  * @param slug - Plan slug
137
148
  * @returns Path information
138
149
  */
139
- function buildPlanPaths(projectPath, slug) {
150
+ async function buildPlanPaths(projectPath, slug) {
140
151
  const plansDir = resolveBlueprintRoot(projectPath);
141
- const sourcePath = path.join(plansDir, slug, "_overview.md");
152
+ const candidates = getBlueprintDocumentCandidates(plansDir, slug);
153
+ const existing = [];
154
+ for (const candidate of candidates) {
155
+ if (await checkPlanExists(candidate))
156
+ existing.push(candidate);
157
+ }
158
+ const sourcePath = existing[0] ?? candidates.at(-1) ?? path.join(plansDir, slug, "_overview.md");
142
159
  const sourceDir = path.dirname(sourcePath);
143
- const targetDir = sourceDir;
144
- return { sourcePath, sourceDir, targetDir, planName: slug };
160
+ return {
161
+ sourcePath,
162
+ sourceDir,
163
+ targetDir: sourceDir,
164
+ planName: slug,
165
+ ...(existing.length > 1 ? { ambiguousPaths: existing } : {}),
166
+ };
145
167
  }
146
168
  /**
147
169
  * Extracts plan name from slug (removes status prefix).
@@ -8,6 +8,13 @@ export interface BlueprintDocumentPath {
8
8
  slug: string;
9
9
  state: BlueprintStatus;
10
10
  }
11
+ export interface BlueprintDocumentIdentity extends BlueprintDocumentPath {
12
+ documentPath: string;
13
+ folderDirectory: string;
14
+ folderPath: string;
15
+ lifecycleSlug: string;
16
+ stableSlug: string;
17
+ }
11
18
  export declare function normalizeBlueprintPath(filePath: string): string;
12
19
  export declare function isBlueprintStatus(value: string | undefined): value is BlueprintStatus;
13
20
  export declare function isBlueprintSlugSegment(value: string | undefined): value is string;
@@ -18,5 +25,16 @@ export declare function getBlueprintDocumentPaths(root: string, state: Blueprint
18
25
  flat: string;
19
26
  folder: string;
20
27
  };
28
+ export declare function resolveBlueprintDocumentIdentity(root: string, documentPath: string): BlueprintDocumentIdentity | null;
29
+ export declare function folderBlueprintRequiredMessage(identity: {
30
+ readonly documentPath: string;
31
+ readonly lifecycleSlug: string;
32
+ readonly shape: BlueprintShape;
33
+ }): string;
34
+ export declare function assertFolderBlueprintIdentity(identity: {
35
+ readonly documentPath: string;
36
+ readonly lifecycleSlug: string;
37
+ readonly shape: BlueprintShape;
38
+ }): void;
21
39
  export declare function getBlueprintDocumentCandidates(root: string, slug: string): string[];
22
40
  export declare function getBlueprintAlternateDocumentPath(root: string, filePath: string): string | null;
@@ -22,7 +22,7 @@ export function isBlueprintSlugSegment(value) {
22
22
  export function parseBlueprintDocumentRelativePath(filePath) {
23
23
  const normalized = normalizeBlueprintPath(filePath);
24
24
  const parts = normalized.split("/").filter((segment) => segment.length > 0);
25
- const [state, second, third] = parts;
25
+ const [state, second] = parts;
26
26
  if (!isBlueprintStatus(state)) {
27
27
  return null;
28
28
  }
@@ -38,13 +38,16 @@ export function parseBlueprintDocumentRelativePath(filePath) {
38
38
  state,
39
39
  };
40
40
  }
41
- if (parts.length === 3 &&
42
- isBlueprintSlugSegment(second) &&
43
- third === BLUEPRINT_OVERVIEW_FILENAME) {
41
+ if (parts.length >= 3 && parts.at(-1) === BLUEPRINT_OVERVIEW_FILENAME) {
42
+ const slugSegments = parts.slice(1, -1);
43
+ if (!slugSegments.every((segment) => isBlueprintSlugSegment(segment))) {
44
+ return null;
45
+ }
46
+ const slug = slugSegments.join("/");
44
47
  return {
45
- relativePath: `${state}/${second}/${BLUEPRINT_OVERVIEW_FILENAME}`,
48
+ relativePath: `${state}/${slug}/${BLUEPRINT_OVERVIEW_FILENAME}`,
46
49
  shape: "folder",
47
- slug: second,
50
+ slug,
48
51
  state,
49
52
  };
50
53
  }
@@ -53,13 +56,13 @@ export function parseBlueprintDocumentRelativePath(filePath) {
53
56
  export function isBlueprintSupportingMarkdownRelativePath(filePath) {
54
57
  const normalized = normalizeBlueprintPath(filePath);
55
58
  const parts = normalized.split("/").filter((segment) => segment.length > 0);
56
- const [state, slug, doc] = parts;
57
- return (parts.length === 3 &&
59
+ const [state] = parts;
60
+ return (parts.length >= 3 &&
58
61
  isBlueprintStatus(state) &&
59
- isBlueprintSlugSegment(slug) &&
60
- typeof doc === "string" &&
61
- doc.endsWith(".md") &&
62
- doc !== BLUEPRINT_OVERVIEW_FILENAME);
62
+ parts.slice(1, -1).every((segment) => isBlueprintSlugSegment(segment)) &&
63
+ typeof parts.at(-1) === "string" &&
64
+ parts.at(-1).endsWith(".md") &&
65
+ parts.at(-1) !== BLUEPRINT_OVERVIEW_FILENAME);
63
66
  }
64
67
  export function getBlueprintDocumentPaths(root, state, slug) {
65
68
  const directory = path.join(root, state, slug);
@@ -69,6 +72,36 @@ export function getBlueprintDocumentPaths(root, state, slug) {
69
72
  folder: path.join(directory, BLUEPRINT_OVERVIEW_FILENAME),
70
73
  };
71
74
  }
75
+ export function resolveBlueprintDocumentIdentity(root, documentPath) {
76
+ const relativePath = normalizeBlueprintPath(path.relative(root, documentPath));
77
+ const parsed = parseBlueprintDocumentRelativePath(relativePath);
78
+ if (!parsed)
79
+ return null;
80
+ const paths = getBlueprintDocumentPaths(root, parsed.state, parsed.slug);
81
+ return {
82
+ ...parsed,
83
+ documentPath: parsed.shape === "flat" ? paths.flat : paths.folder,
84
+ folderDirectory: paths.directory,
85
+ folderPath: paths.folder,
86
+ lifecycleSlug: `${parsed.state}/${parsed.slug}`,
87
+ stableSlug: parsed.slug,
88
+ };
89
+ }
90
+ export function folderBlueprintRequiredMessage(identity) {
91
+ if (identity.shape === "folder") {
92
+ return "";
93
+ }
94
+ return [
95
+ `Blueprint evidence requires folder shape; ${identity.documentPath} is a flat blueprint.`,
96
+ `Run: wp blueprint canonicalize ${identity.lifecycleSlug}`,
97
+ "Then commit the canonicalized path and rerun the evidence command.",
98
+ ].join(" ");
99
+ }
100
+ export function assertFolderBlueprintIdentity(identity) {
101
+ const message = folderBlueprintRequiredMessage(identity);
102
+ if (message)
103
+ throw new Error(message);
104
+ }
72
105
  export function getBlueprintDocumentCandidates(root, slug) {
73
106
  const normalized = normalizeBlueprintPath(slug);
74
107
  const segments = normalized.split("/").filter((segment) => segment.length > 0);
@@ -187,6 +187,11 @@ export const CLI_MCP_PARITY_CLASSIFICATION = {
187
187
  classification: "direct-fallback-by-design",
188
188
  reason: "internal serialization wrapper spawned by the auto-update, wp update, and setup lanes; never agent-facing",
189
189
  },
190
+ "optional-tool-refresh": {
191
+ command: "optional-tool-refresh",
192
+ classification: "direct-fallback-by-design",
193
+ reason: "internal detached worker spawned by provider launch freshness; never agent-facing",
194
+ },
190
195
  init: {
191
196
  command: "init",
192
197
  classification: "unsafe-credentialed-destructive",
@@ -229,6 +234,11 @@ export const CLI_MCP_PARITY_CLASSIFICATION = {
229
234
  classification: "direct-fallback-by-design",
230
235
  reason: "long-running local preview server remains a direct CLI workflow",
231
236
  },
237
+ "public:readiness": {
238
+ command: "public:readiness",
239
+ classification: "direct-fallback-by-design",
240
+ reason: "public package readiness performs local package/tarball checks and is intentionally not a read-only MCP workflow",
241
+ },
232
242
  qa: {
233
243
  command: "qa",
234
244
  classification: "existing-mcp",
@@ -1,6 +1,7 @@
1
1
  import type { CliCommand } from "@webpresso/cli-contract";
2
2
  export declare const blueprintCommands: {
3
3
  audit: CliCommand<Record<string, unknown>>;
4
+ canonicalize: CliCommand<Record<string, unknown>>;
4
5
  exec: CliCommand<Record<string, unknown>>;
5
6
  finalize: CliCommand<Record<string, unknown>>;
6
7
  list: CliCommand<Record<string, unknown>>;
@@ -35,6 +35,7 @@ function createBlueprintAuditCommand() {
35
35
  }
36
36
  export const blueprintCommands = {
37
37
  audit: createBlueprintAuditCommand(),
38
+ canonicalize: placeholderCommand(SCOPE, "canonicalize", "Canonicalize a flat blueprint to folder shape"),
38
39
  exec: placeholderGroup(SCOPE, "exec", "Execute a blueprint", {
39
40
  logs: placeholderCommand(SCOPE, "logs", "Show blueprint execution logs"),
40
41
  resume: placeholderCommand(SCOPE, "resume", "Resume blueprint execution"),
@@ -5,7 +5,7 @@
5
5
  * Lazy-loads subcommand modules based on the first argv to keep startup
6
6
  * cheap. Modeled on apps/cli-wp/src/cli.ts.
7
7
  */
8
- declare const SUPPORTED_COMMANDS: readonly ["blueprint", "browser", "dash", "dash-helper-start", "dash-helper-complete", "claude", "codex", "grok", "opencode", "yolo", "config", "secrets", "roadmap", "status", "review", "sync", "audit", "qa", "ultragoal", "compile", "rule", "skill", "skills", "docs", "setup", "init", "dev", "deploy", "preview", "cleanup", "migrate", "doctor", "err", "test", "e2e", "ci", "ci-preflight", "typecheck", "lint", "format", "logs", "session-id", "session-info", "session", "tech-debt", "pr", "fleet", "worktree", "mcp", "hook", "hooks", "self-install-guard", "gain", "bench", "install", "add", "remove", "update", "exec", "run"];
8
+ declare const SUPPORTED_COMMANDS: readonly ["blueprint", "browser", "dash", "dash-helper-start", "dash-helper-complete", "claude", "codex", "grok", "opencode", "yolo", "config", "secrets", "roadmap", "status", "review", "sync", "audit", "qa", "ultragoal", "compile", "rule", "skill", "skills", "docs", "setup", "init", "dev", "deploy", "preview", "cleanup", "migrate", "doctor", "err", "test", "e2e", "ci", "ci-preflight", "public:readiness", "typecheck", "lint", "format", "logs", "session-id", "session-info", "session", "tech-debt", "pr", "fleet", "worktree", "mcp", "hook", "hooks", "self-install-guard", "optional-tool-refresh", "gain", "bench", "install", "add", "remove", "update", "exec", "run"];
9
9
  export type SupportedCommand = (typeof SUPPORTED_COMMANDS)[number];
10
10
  type RootHelpEntry = {
11
11
  readonly command: string;
@@ -55,6 +55,7 @@ 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",
@@ -70,6 +71,7 @@ const SUPPORTED_COMMANDS = [
70
71
  "hook",
71
72
  "hooks",
72
73
  "self-install-guard",
74
+ "optional-tool-refresh",
73
75
  "gain",
74
76
  "bench",
75
77
  "install",
@@ -111,6 +113,10 @@ const ROOT_HELP_CORE_ENTRIES = [
111
113
  command: "ci-preflight",
112
114
  description: "Run the locally-runnable required CI checks fail-fast (the pre-push gate)",
113
115
  },
116
+ {
117
+ command: "public:readiness",
118
+ description: "Run the public package readiness gate through the portable wp surface",
119
+ },
114
120
  { command: "logs", description: "Print persisted raw output from recent quality runs" },
115
121
  { command: "dev", description: "Run a manifest-backed development target" },
116
122
  { command: "status", description: "Show unified local readiness and next action" },
@@ -364,6 +370,10 @@ const COMMAND_REGISTRARS = {
364
370
  const { registerCiPreflightCommand } = await import("./commands/ci-preflight.js");
365
371
  registerCiPreflightCommand(cli);
366
372
  },
373
+ "public:readiness": async (cli) => {
374
+ const { registerPublicReadinessCommand } = await import("./commands/public-readiness.js");
375
+ registerPublicReadinessCommand(cli);
376
+ },
367
377
  typecheck: async (cli) => {
368
378
  const { registerTypecheckCommand } = await import("./commands/typecheck.js");
369
379
  registerTypecheckCommand(cli);
@@ -422,6 +432,15 @@ const COMMAND_REGISTRARS = {
422
432
  const { registerSelfInstallGuardCommand } = await import("./commands/self-install-guard.js");
423
433
  registerSelfInstallGuardCommand(cli);
424
434
  },
435
+ "optional-tool-refresh": async (cli) => {
436
+ cli
437
+ .command("optional-tool-refresh <tool> [...args]", "Internal detached optional-tool freshness worker")
438
+ .allowUnknownOptions()
439
+ .action(async (tool, args = []) => {
440
+ const { runOptionalToolRefreshWorker } = await import("./optional-tool-freshness.js");
441
+ process.exitCode = runOptionalToolRefreshWorker([tool, ...args]);
442
+ });
443
+ },
425
444
  gain: async (cli) => {
426
445
  const { registerGainCommand } = await import("./commands/gain/index.js");
427
446
  registerGainCommand(cli);
@@ -636,6 +655,21 @@ export async function main() {
636
655
  console.error(formatUnknownCommandError(command, SUPPORTED_COMMANDS));
637
656
  return 1;
638
657
  }
658
+ // `wp run wait` is the read-only workflow-run waiter, intercepted before the
659
+ // package-manager `run` facade (which maps `wp run <script>` to `vp run
660
+ // <script>`). Without this, `wait` would be treated as a package script name.
661
+ // Errors resolve to an exit code here, mirroring main()'s catch below, so a
662
+ // bad flag never rejects the main() promise.
663
+ if (resolvedCommand === "run" && argv[3] === "wait") {
664
+ const { runRunWaitCommand } = await import("./commands/run-wait.js");
665
+ try {
666
+ return await runRunWaitCommand(argv.slice(4));
667
+ }
668
+ catch (error) {
669
+ console.error(error instanceof Error ? error.message : String(error));
670
+ return 1;
671
+ }
672
+ }
639
673
  await COMMAND_REGISTRARS[resolvedCommand](cli, resolvedCommand);
640
674
  if (resolvedCommand === "session") {
641
675
  const { runSessionCommand } = await import("./commands/session.js");