@webpresso/agent-kit 3.1.30 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (300) hide show
  1. package/README.md +53 -15
  2. package/bin/_run.js +183 -20
  3. package/catalog/AGENTS.md.tpl +3 -3
  4. package/catalog/agent/agents/implementer.md +45 -0
  5. package/catalog/agent/rules/parallel-orchestration-quota.md +100 -0
  6. package/catalog/agent/rules/pre-implementation.md +28 -1
  7. package/catalog/agent/rules/supported-agent-clis.md +9 -0
  8. package/catalog/agent/rules/typescript-version.md +50 -0
  9. package/catalog/agent/skills/codex/SKILL.md +25 -1
  10. package/catalog/agent/skills/verify/SKILL.md +4 -2
  11. package/catalog/docs/templates/blueprint.md +1 -1
  12. package/dist/esm/audit/agents.js +1 -0
  13. package/dist/esm/audit/blueprint-lifecycle-sql.d.ts +12 -0
  14. package/dist/esm/audit/blueprint-lifecycle-sql.js +23 -2
  15. package/dist/esm/audit/blueprint-pr-coverage.js +4 -0
  16. package/dist/esm/audit/blueprint-trust.js +14 -10
  17. package/dist/esm/audit/changeset-evidence.d.ts +23 -0
  18. package/dist/esm/audit/changeset-evidence.js +30 -0
  19. package/dist/esm/audit/changeset-required.d.ts +10 -0
  20. package/dist/esm/audit/changeset-required.js +73 -0
  21. package/dist/esm/audit/ci-guardrails-detection.d.ts +27 -0
  22. package/dist/esm/audit/ci-guardrails-detection.js +68 -0
  23. package/dist/esm/audit/ci-guardrails-wiring.d.ts +22 -0
  24. package/dist/esm/audit/ci-guardrails-wiring.js +80 -0
  25. package/dist/esm/audit/ci-test-perf.js +30 -0
  26. package/dist/esm/audit/command-surface.js +4 -2
  27. package/dist/esm/audit/host-substitution-risk.d.ts +2 -0
  28. package/dist/esm/audit/host-substitution-risk.js +256 -0
  29. package/dist/esm/audit/registry.d.ts +4 -4
  30. package/dist/esm/audit/registry.js +13 -1
  31. package/dist/esm/audit/toolchain-isolation.js +1 -2
  32. package/dist/esm/audit/typescript-version.d.ts +2 -0
  33. package/dist/esm/audit/typescript-version.js +123 -0
  34. package/dist/esm/blueprint/core/parser.js +101 -2
  35. package/dist/esm/blueprint/core/schema.d.ts +1 -0
  36. package/dist/esm/blueprint/core/schema.js +26 -0
  37. package/dist/esm/blueprint/core/validation/criteria.d.ts +14 -0
  38. package/dist/esm/blueprint/core/validation/criteria.js +41 -2
  39. package/dist/esm/blueprint/core/validation/state.js +25 -14
  40. package/dist/esm/blueprint/db/migrations/run.js +4 -2
  41. package/dist/esm/blueprint/lifecycle/audit.d.ts +20 -0
  42. package/dist/esm/blueprint/lifecycle/audit.js +87 -20
  43. package/dist/esm/blueprint/lifecycle/canonicalize.d.ts +6 -0
  44. package/dist/esm/blueprint/lifecycle/canonicalize.js +135 -0
  45. package/dist/esm/blueprint/lifecycle/local.d.ts +2 -1
  46. package/dist/esm/blueprint/lifecycle/local.js +19 -3
  47. package/dist/esm/blueprint/lifecycle/review-provenance.d.ts +6 -0
  48. package/dist/esm/blueprint/lifecycle/review-provenance.js +68 -12
  49. package/dist/esm/blueprint/lifecycle/rollout-anchor.d.ts +97 -0
  50. package/dist/esm/blueprint/lifecycle/rollout-anchor.js +161 -0
  51. package/dist/esm/blueprint/service/BlueprintCreationService.d.ts +1 -0
  52. package/dist/esm/blueprint/service/BlueprintCreationService.js +53 -4
  53. package/dist/esm/blueprint/service/blueprint-tech-debt-links.js +16 -2
  54. package/dist/esm/blueprint/sync/client.d.ts +2 -1
  55. package/dist/esm/blueprint/sync/client.js +1 -1
  56. package/dist/esm/blueprint/trust/command-runner.d.ts +26 -0
  57. package/dist/esm/blueprint/trust/command-runner.js +148 -0
  58. package/dist/esm/blueprint/trust/dossier.d.ts +1 -0
  59. package/dist/esm/blueprint/trust/dossier.js +40 -6
  60. package/dist/esm/blueprint/trust/gates.d.ts +7 -0
  61. package/dist/esm/blueprint/trust/gates.js +20 -0
  62. package/dist/esm/blueprint/trust/promotion.d.ts +8 -0
  63. package/dist/esm/blueprint/trust/promotion.js +74 -112
  64. package/dist/esm/blueprint/trust/scaffold.d.ts +19 -0
  65. package/dist/esm/blueprint/trust/scaffold.js +64 -0
  66. package/dist/esm/blueprint/trust/validator.d.ts +1 -0
  67. package/dist/esm/blueprint/trust/validator.js +7 -1
  68. package/dist/esm/blueprint/utils/archive.js +27 -5
  69. package/dist/esm/blueprint/utils/document-paths.d.ts +18 -0
  70. package/dist/esm/blueprint/utils/document-paths.js +45 -12
  71. package/dist/esm/build/cli-mcp-parity.js +15 -0
  72. package/dist/esm/cli/bundle/commands/blueprint.d.ts +1 -0
  73. package/dist/esm/cli/bundle/commands/blueprint.js +1 -0
  74. package/dist/esm/cli/cli.d.ts +1 -1
  75. package/dist/esm/cli/cli.js +43 -0
  76. package/dist/esm/cli/commands/agent-launch.d.ts +8 -1
  77. package/dist/esm/cli/commands/agent-launch.js +93 -4
  78. package/dist/esm/cli/commands/audit-core.d.ts +1 -1
  79. package/dist/esm/cli/commands/audit-core.js +8 -1
  80. package/dist/esm/cli/commands/audit.js +21 -5
  81. package/dist/esm/cli/commands/blueprint/execution.js +18 -6
  82. package/dist/esm/cli/commands/blueprint/mutations.js +59 -40
  83. package/dist/esm/cli/commands/blueprint/router-dispatch.d.ts +2 -1
  84. package/dist/esm/cli/commands/blueprint/router-dispatch.js +20 -1
  85. package/dist/esm/cli/commands/blueprint/router-output.d.ts +21 -0
  86. package/dist/esm/cli/commands/blueprint/router-output.js +48 -17
  87. package/dist/esm/cli/commands/blueprint/router.d.ts +7 -0
  88. package/dist/esm/cli/commands/blueprint/router.js +13 -1
  89. package/dist/esm/cli/commands/ci-preflight.d.ts +13 -2
  90. package/dist/esm/cli/commands/ci-preflight.js +30 -6
  91. package/dist/esm/cli/commands/compile.js +5 -31
  92. package/dist/esm/cli/commands/dash/delivery-status.d.ts +1 -0
  93. package/dist/esm/cli/commands/dash/delivery-status.js +11 -4
  94. package/dist/esm/cli/commands/doctor.js +5 -0
  95. package/dist/esm/cli/commands/hook.d.ts +5 -0
  96. package/dist/esm/cli/commands/hook.js +15 -9
  97. package/dist/esm/cli/commands/init/config.js +2 -2
  98. package/dist/esm/cli/commands/init/convergence-apply.d.ts +88 -0
  99. package/dist/esm/cli/commands/init/convergence-apply.js +325 -0
  100. package/dist/esm/cli/commands/init/convergence-state.d.ts +13 -0
  101. package/dist/esm/cli/commands/init/convergence-state.js +54 -0
  102. package/dist/esm/cli/commands/init/convergence.d.ts +23 -0
  103. package/dist/esm/cli/commands/init/convergence.js +57 -0
  104. package/dist/esm/cli/commands/init/git-convergence.d.ts +19 -0
  105. package/dist/esm/cli/commands/init/git-convergence.js +168 -0
  106. package/dist/esm/cli/commands/init/gitignore-patcher.js +2 -0
  107. package/dist/esm/cli/commands/init/grok-privacy-smoke.d.ts +52 -0
  108. package/dist/esm/cli/commands/init/grok-privacy-smoke.js +91 -0
  109. package/dist/esm/cli/commands/init/host-native-proof.d.ts +18 -0
  110. package/dist/esm/cli/commands/init/host-native-proof.js +88 -0
  111. package/dist/esm/cli/commands/init/host-visibility.d.ts +2 -2
  112. package/dist/esm/cli/commands/init/host-visibility.js +94 -5
  113. package/dist/esm/cli/commands/init/index.d.ts +3 -0
  114. package/dist/esm/cli/commands/init/index.js +326 -173
  115. package/dist/esm/cli/commands/init/mcp-spec.d.ts +57 -0
  116. package/dist/esm/cli/commands/init/mcp-spec.js +273 -0
  117. package/dist/esm/cli/commands/init/merge.d.ts +1 -0
  118. package/dist/esm/cli/commands/init/merge.js +30 -1
  119. package/dist/esm/cli/commands/init/package-root.d.ts +7 -0
  120. package/dist/esm/cli/commands/init/package-root.js +10 -2
  121. package/dist/esm/cli/commands/init/plugin-cache-prune.d.ts +0 -3
  122. package/dist/esm/cli/commands/init/plugin-cache-prune.js +0 -1
  123. package/dist/esm/cli/commands/init/scaffold-agents-md.d.ts +1 -1
  124. package/dist/esm/cli/commands/init/scaffold-agents-md.js +9 -3
  125. package/dist/esm/cli/commands/init/scaffold-base-kit.d.ts +20 -0
  126. package/dist/esm/cli/commands/init/scaffold-base-kit.js +61 -5
  127. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/emitters/grok.d.ts +14 -8
  128. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/emitters/grok.js +41 -24
  129. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/index.d.ts +2 -0
  130. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/index.js +54 -24
  131. package/dist/esm/cli/commands/init/scaffolders/codex-mcp/index.d.ts +12 -31
  132. package/dist/esm/cli/commands/init/scaffolders/codex-mcp/index.js +288 -202
  133. package/dist/esm/cli/commands/init/scaffolders/file-transaction.d.ts +22 -0
  134. package/dist/esm/cli/commands/init/scaffolders/file-transaction.js +230 -0
  135. package/dist/esm/cli/commands/init/scaffolders/grok-mcp/index.d.ts +38 -0
  136. package/dist/esm/cli/commands/init/scaffolders/grok-mcp/index.js +73 -0
  137. package/dist/esm/cli/commands/init/scaffolders/opencode-plugin/index.d.ts +21 -0
  138. package/dist/esm/cli/commands/init/scaffolders/opencode-plugin/index.js +559 -63
  139. package/dist/esm/cli/commands/init/untracked-collision.d.ts +14 -0
  140. package/dist/esm/cli/commands/init/untracked-collision.js +42 -0
  141. package/dist/esm/cli/commands/opencode-probe.d.ts +107 -0
  142. package/dist/esm/cli/commands/opencode-probe.js +323 -0
  143. package/dist/esm/cli/commands/opencode-rotate.d.ts +72 -0
  144. package/dist/esm/cli/commands/opencode-rotate.js +147 -0
  145. package/dist/esm/cli/commands/public-readiness.d.ts +11 -0
  146. package/dist/esm/cli/commands/public-readiness.js +42 -0
  147. package/dist/esm/cli/commands/quality-runner.js +3 -3
  148. package/dist/esm/cli/commands/review.d.ts +35 -3
  149. package/dist/esm/cli/commands/review.js +751 -209
  150. package/dist/esm/cli/commands/run-wait.d.ts +2 -0
  151. package/dist/esm/cli/commands/run-wait.js +122 -0
  152. package/dist/esm/cli/commands/rust-check.d.ts +24 -0
  153. package/dist/esm/cli/commands/rust-check.js +110 -0
  154. package/dist/esm/cli/commands/setup-help.js +1 -1
  155. package/dist/esm/cli/commands/setup-repair-help.js +1 -1
  156. package/dist/esm/cli/commands/typecheck.d.ts +1 -0
  157. package/dist/esm/cli/commands/typecheck.js +30 -1
  158. package/dist/esm/cli/commands/worktree/router-dispatch.d.ts +6 -0
  159. package/dist/esm/cli/commands/worktree/router-dispatch.js +126 -9
  160. package/dist/esm/cli/commands/worktree/router.js +10 -0
  161. package/dist/esm/cli/direct-provider-launch.js +9 -5
  162. package/dist/esm/cli/optional-tool-freshness.d.ts +27 -0
  163. package/dist/esm/cli/optional-tool-freshness.js +219 -9
  164. package/dist/esm/daemon/domains/git-actions.d.ts +29 -0
  165. package/dist/esm/daemon/domains/git-actions.js +136 -0
  166. package/dist/esm/daemon/domains/run-registry-interop.d.ts +16 -0
  167. package/dist/esm/daemon/domains/run-registry-interop.js +45 -0
  168. package/dist/esm/daemon/domains/sessions-search.d.ts +11 -0
  169. package/dist/esm/daemon/domains/sessions-search.js +29 -0
  170. package/dist/esm/daemon/handlers/blueprints-handlers.d.ts +5 -0
  171. package/dist/esm/daemon/handlers/blueprints-handlers.js +36 -0
  172. package/dist/esm/daemon/handlers/deploy-handlers.d.ts +6 -0
  173. package/dist/esm/daemon/handlers/deploy-handlers.js +5 -0
  174. package/dist/esm/daemon/handlers/fleet-handlers.d.ts +15 -0
  175. package/dist/esm/daemon/handlers/fleet-handlers.js +91 -0
  176. package/dist/esm/daemon/handlers/git-handlers.d.ts +6 -0
  177. package/dist/esm/daemon/handlers/git-handlers.js +27 -0
  178. package/dist/esm/daemon/handlers/product.d.ts +7 -0
  179. package/dist/esm/daemon/handlers/product.js +10 -119
  180. package/dist/esm/daemon/handlers/sessions-handlers.d.ts +12 -0
  181. package/dist/esm/daemon/handlers/sessions-handlers.js +31 -0
  182. package/dist/esm/daemon/handlers/shared.d.ts +5 -0
  183. package/dist/esm/daemon/handlers/shared.js +16 -0
  184. package/dist/esm/daemon/methods.d.ts +8 -1
  185. package/dist/esm/daemon/methods.js +7 -0
  186. package/dist/esm/daemon/protocol/contract.d.ts +43 -0
  187. package/dist/esm/daemon/protocol/contract.js +26 -0
  188. package/dist/esm/daemon/protocol/index.d.ts +1 -1
  189. package/dist/esm/daemon/protocol/index.js +1 -1
  190. package/dist/esm/hooks/__conformance__/matrix.d.ts +6 -0
  191. package/dist/esm/hooks/__conformance__/matrix.js +137 -0
  192. package/dist/esm/hooks/conformance/batch.js +5 -0
  193. package/dist/esm/hooks/doctor.d.ts +5 -3
  194. package/dist/esm/hooks/doctor.js +67 -16
  195. package/dist/esm/hooks/permission-request/index.js +9 -3
  196. package/dist/esm/hooks/permission-request/policy.d.ts +9 -0
  197. package/dist/esm/hooks/permission-request/policy.js +13 -0
  198. package/dist/esm/hooks/pretool-guard/dev-routing.d.ts +9 -0
  199. package/dist/esm/hooks/pretool-guard/dev-routing.js +41 -5
  200. package/dist/esm/hooks/pretool-guard/logger.d.ts +7 -0
  201. package/dist/esm/hooks/pretool-guard/logger.js +16 -0
  202. package/dist/esm/hooks/pretool-guard/runner.d.ts +6 -0
  203. package/dist/esm/hooks/pretool-guard/runner.js +68 -16
  204. package/dist/esm/hooks/pretool-guard/validators/blueprint.js +30 -10
  205. package/dist/esm/hooks/pretool-guard/validators/forbidden-commands.js +54 -0
  206. package/dist/esm/hooks/pretool-guard/validators/index.d.ts +7 -0
  207. package/dist/esm/hooks/pretool-guard/validators/index.js +21 -0
  208. package/dist/esm/hooks/pretool-guard/validators/path-contract.d.ts +19 -0
  209. package/dist/esm/hooks/pretool-guard/validators/path-contract.js +25 -8
  210. package/dist/esm/hooks/pretool-guard/validators/worktree-discipline.js +6 -1
  211. package/dist/esm/hooks/shared/guard-outcome.d.ts +149 -0
  212. package/dist/esm/hooks/shared/guard-outcome.js +215 -0
  213. package/dist/esm/hooks/shared/hook-bootstrap.d.ts +26 -0
  214. package/dist/esm/hooks/shared/hook-bootstrap.js +64 -1
  215. package/dist/esm/hooks/shared/types.d.ts +8 -5
  216. package/dist/esm/hooks/shared/types.js +50 -9
  217. package/dist/esm/hooks/stop/qa-changed-files.d.ts +11 -0
  218. package/dist/esm/hooks/stop/qa-changed-files.js +92 -0
  219. package/dist/esm/mcp/blueprint/_shared/lifecycle.js +14 -1
  220. package/dist/esm/mcp/blueprint/handlers/document-mutations.d.ts +5 -0
  221. package/dist/esm/mcp/blueprint/handlers/document-mutations.js +109 -11
  222. package/dist/esm/mcp/blueprint/handlers/review-log.js +8 -1
  223. package/dist/esm/mcp/blueprint/handlers/task-advance.js +113 -54
  224. package/dist/esm/mcp/blueprint/handlers/task-verify.js +39 -19
  225. package/dist/esm/mcp/blueprint/registration.js +3 -2
  226. package/dist/esm/mcp/server.d.ts +1 -0
  227. package/dist/esm/mcp/server.js +27 -0
  228. package/dist/esm/mcp/tools/_names.d.ts +1 -1
  229. package/dist/esm/mcp/tools/_names.js +5 -0
  230. package/dist/esm/mcp/tools/_registry.js +10 -0
  231. package/dist/esm/mcp/tools/_shared/audit-kinds.d.ts +1 -1
  232. package/dist/esm/mcp/tools/_shared/result.js +44 -1
  233. package/dist/esm/mcp/tools/audit.d.ts +5 -1
  234. package/dist/esm/mcp/tools/audit.js +42 -0
  235. package/dist/esm/mcp/tools/audits.d.ts +4 -0
  236. package/dist/esm/mcp/tools/ci-preflight.d.ts +50 -0
  237. package/dist/esm/mcp/tools/ci-preflight.js +183 -0
  238. package/dist/esm/mcp/tools/pr-upsert.js +39 -1
  239. package/dist/esm/mcp/tools/review-run.d.ts +28 -0
  240. package/dist/esm/mcp/tools/review-run.js +216 -0
  241. package/dist/esm/mcp/tools/run-wait.d.ts +86 -0
  242. package/dist/esm/mcp/tools/run-wait.js +593 -0
  243. package/dist/esm/mcp/tools/session-fetch-and-index.d.ts +1 -1
  244. package/dist/esm/mcp/tools/session-id.d.ts +3 -0
  245. package/dist/esm/mcp/tools/session-id.js +45 -0
  246. package/dist/esm/mcp/tools/session-info.d.ts +3 -0
  247. package/dist/esm/mcp/tools/session-info.js +49 -0
  248. package/dist/esm/output-transforms/audit.d.ts +23 -0
  249. package/dist/esm/output-transforms/audit.js +47 -0
  250. package/dist/esm/output-transforms/index.js +2 -0
  251. package/dist/esm/package.json +2 -0
  252. package/dist/esm/platform/client.d.ts +7 -1
  253. package/dist/esm/platform/github-contract-source.d.ts +2 -1
  254. package/dist/esm/pr-description/contract.js +24 -4
  255. package/dist/esm/review/authority.js +27 -14
  256. package/dist/esm/review/availability.d.ts +33 -0
  257. package/dist/esm/review/availability.js +122 -20
  258. package/dist/esm/review/delivery-verifier.d.ts +27 -0
  259. package/dist/esm/review/delivery-verifier.js +115 -0
  260. package/dist/esm/review/events.d.ts +3 -2
  261. package/dist/esm/review/events.js +23 -3
  262. package/dist/esm/review/execution/adapters.d.ts +11 -1
  263. package/dist/esm/review/execution/adapters.js +36 -10
  264. package/dist/esm/review/execution/artifacts.d.ts +1 -1
  265. package/dist/esm/review/execution/output-policy.d.ts +2 -0
  266. package/dist/esm/review/execution/output-policy.js +12 -0
  267. package/dist/esm/review/execution/review-checkout.js +121 -21
  268. package/dist/esm/review/execution/supervisor.js +36 -4
  269. package/dist/esm/review/execution/types.d.ts +9 -0
  270. package/dist/esm/review/opencode-account.d.ts +24 -0
  271. package/dist/esm/review/opencode-account.js +68 -0
  272. package/dist/esm/review/opencode-auth.d.ts +33 -0
  273. package/dist/esm/review/opencode-auth.js +79 -0
  274. package/dist/esm/review/subject.d.ts +25 -0
  275. package/dist/esm/review/subject.js +116 -26
  276. package/dist/esm/runtime/opencode-account-materializer.d.ts +34 -0
  277. package/dist/esm/runtime/opencode-account-materializer.js +33 -0
  278. package/dist/esm/rust/affected-manifests.d.ts +38 -0
  279. package/dist/esm/rust/affected-manifests.js +69 -0
  280. package/dist/esm/session-memory/fetch-index.d.ts +1 -1
  281. package/dist/esm/status/snapshot.js +32 -25
  282. package/dist/esm/test/duration-sequencer.d.ts +20 -0
  283. package/dist/esm/test/duration-sequencer.js +54 -0
  284. package/dist/esm/test/shard-durations.json +922 -0
  285. package/dist/esm/test/worker-budget.d.ts +6 -0
  286. package/dist/esm/test/worker-budget.js +59 -1
  287. package/dist/esm/test-helpers/global-setup.d.ts +1 -2
  288. package/dist/esm/test-helpers/global-setup.js +79 -32
  289. package/dist/esm/typecheck/planner.d.ts +9 -1
  290. package/dist/esm/typecheck/planner.js +31 -0
  291. package/dist/esm/ultragoal/service.d.ts +7 -0
  292. package/dist/esm/ultragoal/service.js +42 -2
  293. package/dist/esm/utils/write-json-file.js +3 -1
  294. package/dist/esm/worktrees/codex-project-trust.d.ts +15 -0
  295. package/dist/esm/worktrees/codex-project-trust.js +56 -0
  296. package/dist/esm/worktrees/orphan-scan.d.ts +50 -0
  297. package/dist/esm/worktrees/orphan-scan.js +211 -0
  298. package/dist/esm/worktrees/registry.d.ts +8 -0
  299. package/dist/esm/worktrees/registry.js +18 -3
  300. package/package.json +18 -13
@@ -0,0 +1,50 @@
1
+ ---
2
+ type: rule
3
+ slug: typescript-version
4
+ title: TypeScript 7 required at consumers
5
+ status: active
6
+ scope: repo
7
+ applies_to: [agents]
8
+ related:
9
+ - package-conventions
10
+ - ci-test-perf
11
+ created: "2026-07-24"
12
+ last_reviewed: "2026-07-24"
13
+ paths:
14
+ - "pnpm-workspace.yaml"
15
+ - "**/package.json"
16
+ ---
17
+
18
+ # TypeScript 7 required at consumers
19
+
20
+ The Webpresso toolchain runs on **TypeScript 7** — the native (Go) compiler that
21
+ `tsc` resolves to since the 7.0 GA. Consumer repos must stay on the 7 major so
22
+ their typecheck, build, and shipped `.d.ts` match the compiler agent-kit ships
23
+ and tests against.
24
+
25
+ ## What is enforced
26
+
27
+ `wp audit typescript-version` (bundled in `wp audit guardrails`, so it runs in
28
+ every consumer CI that calls guardrails, and updates automatically on the next
29
+ `wp` / agent-kit bump — no CI edits) fails when the `typescript` compiler
30
+ dependency is pinned below major 7, in either:
31
+
32
+ - the `catalog.typescript` pin in `pnpm-workspace.yaml`, or
33
+ - a direct `typescript` dependency/devDependency in any `package.json`
34
+ (`catalog:` / `workspace:` references are skipped — they resolve through the
35
+ catalog, which is checked directly).
36
+
37
+ ## `@typescript/typescript6` is allowed — it is not legacy
38
+
39
+ TypeScript 7's **programmatic** compiler API is not stable until 7.1, so tooling
40
+ that calls the compiler API (e.g. `wp typecheck`) bridges through
41
+ `@typescript/typescript6`, the Microsoft-published TS6 compiler-API
42
+ compatibility package. That is a distinct dependency from the `typescript`
43
+ compiler and is **never** flagged by this audit. Keep `typescript` on `^7` and
44
+ `@typescript/typescript6` on `^6` — do not downgrade the `typescript` compiler.
45
+
46
+ ## Temporary exception
47
+
48
+ A consumer mid-migration may add a `typescript-version-allow` comment in
49
+ `pnpm-workspace.yaml` (or the manifest) to suppress the check. Treat it as
50
+ temporary and remove it once the repo is on TypeScript 7.
@@ -8,13 +8,37 @@ license: MIT
8
8
 
9
9
  Use this skill from Claude or another non-Codex host when the user wants Codex to independently review a diff, challenge a plan, or answer a repo question. Keep Codex read-only by default and treat its answer as external advice until independently verified.
10
10
 
11
+ ## Primary path: the `wp_review_run` MCP tool
12
+
13
+ When the webpresso MCP server is available, call `wp_review_run` directly instead of any bash block below — it is the same `wp review run` typed runtime, called in-process, with two ergonomic wins: it takes a `prompt` string (no `--prompt-file` bookkeeping) and it runs the probe stage and the review stage in **one call** by default (the review stage is skipped automatically if the probe fails, so you never waste a full review invocation on a cold/broken provider).
14
+
15
+ ```jsonc
16
+ // wp_review_run MCP tool call
17
+ {
18
+ "prompt": "<diff summary + what to look for>",
19
+ "provider": "codex",
20
+ // model, effort, artifactRoot, idleSeconds are all optional and default
21
+ // the same way the `wp review run` CLI does. Omit `stage` for automatic
22
+ // probe-then-review; pass stage: "probe" or stage: "review" only for
23
+ // manual single-stage control.
24
+ }
25
+ ```
26
+
27
+ **Provider fallback lives in this one call.** If your primary provider (e.g. Codex) is unavailable — out of credits, auth expired — switch the `provider` parameter to another supported value (`claude`, `opencode`, `grok`) in the SAME `wp_review_run` call. Never shell into a different provider's CLI directly; that bypasses artifact capture under `.webpresso/reviews`, availability tracking, and the probe-then-review handshake this tool exists to provide. The pretool guard hook actively blocks shelling straight into a provider CLI's review-launching subcommand and redirects here.
28
+
29
+ The bash blocks in this skill (below) are the **MCP-unavailable fallback only** — use them when the webpresso MCP server itself is not reachable in the current host, not as a provider-fallback mechanism.
30
+
11
31
  ## Single-shot budget (anti-stampede)
12
32
 
13
33
  - Default: **one** review invocation per request.
14
34
  - Do **not** fan out parallel multi-host review matrices unless the user set `review_budget`/`N` > 1.
15
- - Prefer `wp review run` / `wp review gate` over spawn/wait agent loops for review.
35
+ - Prefer `wp_review_run` (MCP) or `wp review run` / `wp review gate` (CLI fallback) over spawn/wait agent loops for review.
16
36
  - Keep prompts bounded; no whole-repo paste.
17
37
 
38
+ ## MCP-unavailable fallback (raw CLI)
39
+
40
+ Everything from here down is the manual `wp review run` CLI path documented for hosts or sessions where the webpresso MCP server is not reachable. Prefer `wp_review_run` above whenever MCP is available.
41
+
18
42
  ## Auth check
19
43
 
20
44
  ```bash
@@ -235,8 +235,10 @@ Reviewer preference (pick **one** path):
235
235
 
236
236
  Approval evidence requirements:
237
237
 
238
- - Blueprint lifecycle: `wp review gate <blueprint-slug> --target HEAD --json` first.
239
- Use `wp review log` / `wp review read` for inspection.
238
+ - Blueprint lifecycle: `wp review gate <blueprint-slug> --purpose delivery --target HEAD --json`
239
+ first. Delivery gates automatically run declared Promotion Gates from the
240
+ Trust Dossier before outside-review provider probes. Use `wp review log` /
241
+ `wp review read` for inspection.
240
242
  - Each reviewer must return clear `APPROVED`/`BLOCKED` with enough evidence.
241
243
  - Record as PR comments when a PR exists (model/tool, verdict, reviewed commit).
242
244
  - Unavailable reviewer → try next preferred; if still short, report gap and
@@ -22,7 +22,7 @@ approvals: [] # Legacy approval mirror; authority may also come from v2 review-e
22
22
  - Draft slug: `{{slug}}`
23
23
  - Output path: `{{output_path}}`
24
24
  - Generated command: `wp blueprint new "{{description}}" --complexity {{complexity}}`
25
- - Default shape: folder (`blueprints/<status>/<slug>/_overview.md`, with sibling `reviews.md` for approvals); flat `blueprints/<status>/<slug>.md` is legacy-valid
25
+ - Default shape: folder (`blueprints/<status>/<slug>/_overview.md`, with sibling `reviews.md` for approvals); flat `blueprints/<status>/<slug>.md` stays readable but cannot hold review evidence, ledgers, or approvals — run `wp blueprint canonicalize <slug>` first
26
26
  - Validation scope: parser compliance before write
27
27
 
28
28
  ## Architecture Overview
@@ -13,6 +13,7 @@ const REQUIRED_SUBAGENTS = [
13
13
  "security-auditor.md",
14
14
  "doc-writer.md",
15
15
  "explorer.md",
16
+ "implementer.md",
16
17
  ];
17
18
  const EXPLORER_CONTRACT_MARKERS = [
18
19
  "3–5 tool calls",
@@ -35,4 +35,16 @@ export interface BlueprintLifecycleAuditOptions {
35
35
  */
36
36
  readonly affectedFiles?: readonly string[];
37
37
  }
38
+ /**
39
+ * Decide the file scope for the per-file `runBlueprintAudit` pass. In
40
+ * changed-only mode with a resolved changed scope, audit only the changed
41
+ * blueprint files (bounded, pre-commit-fast); otherwise audit the whole corpus
42
+ * (`all: true`, the CI-grade pass). Extracted + exported so the scoping contract
43
+ * is unit-testable without standing up the full corpus.
44
+ */
45
+ export declare function resolveRunBlueprintAuditScope(changedOnly: boolean, changedFiles: ReadonlySet<string> | null): {
46
+ readonly stagedFiles: string[];
47
+ } | {
48
+ readonly all: true;
49
+ };
38
50
  export declare function auditBlueprintLifecycleSql(cwd?: string, options?: BlueprintLifecycleAuditOptions): Promise<RepoAuditResult>;
@@ -357,17 +357,20 @@ function listTrackedBlueprintPaths(cwd) {
357
357
  }
358
358
  function changedBlueprintScope(candidates) {
359
359
  const slugs = new Set();
360
+ const files = new Set();
360
361
  for (const [currentPath, candidate] of candidates) {
362
+ files.add(currentPath);
361
363
  const currentSlug = blueprintSlugFromGitPath(currentPath);
362
364
  if (currentSlug)
363
365
  slugs.add(currentSlug);
364
366
  for (const previousPath of candidate.previousPaths) {
367
+ files.add(previousPath);
365
368
  const previousSlug = blueprintSlugFromGitPath(previousPath);
366
369
  if (previousSlug)
367
370
  slugs.add(previousSlug);
368
371
  }
369
372
  }
370
- return { slugs };
373
+ return { slugs, files };
371
374
  }
372
375
  /**
373
376
  * When `--affected` supplies an explicit file set (pre-commit staged files or
@@ -435,6 +438,19 @@ function readPreviousLifecycleStatusFromCandidate(cwd, row, candidate, trackedPa
435
438
  }
436
439
  return null;
437
440
  }
441
+ /**
442
+ * Decide the file scope for the per-file `runBlueprintAudit` pass. In
443
+ * changed-only mode with a resolved changed scope, audit only the changed
444
+ * blueprint files (bounded, pre-commit-fast); otherwise audit the whole corpus
445
+ * (`all: true`, the CI-grade pass). Extracted + exported so the scoping contract
446
+ * is unit-testable without standing up the full corpus.
447
+ */
448
+ export function resolveRunBlueprintAuditScope(changedOnly, changedFiles) {
449
+ if (changedOnly && changedFiles !== null) {
450
+ return { stagedFiles: [...changedFiles] };
451
+ }
452
+ return { all: true };
453
+ }
438
454
  export async function auditBlueprintLifecycleSql(cwd = process.cwd(), options = {}) {
439
455
  const budgets = loadBudgets(cwd);
440
456
  const wipInProgressMax = budgets["blueprint-wip-in-progress-max"].max ?? 3;
@@ -522,10 +538,15 @@ export async function auditBlueprintLifecycleSql(cwd = process.cwd(), options =
522
538
  if (options.changedOnly && changedScope) {
523
539
  titleNotices.push(`changed-only: ${changedScope.slugs.size} blueprint(s)`);
524
540
  }
541
+ // In changed-only mode, scope the per-file blueprint audit to the changed
542
+ // blueprints. Auditing all blueprints (`all: true`) parses and validates the
543
+ // entire corpus (~1300 files, ~70s) even for a one-blueprint change — far too
544
+ // slow for the pre-commit hot path. The whole-corpus pass is CI's job; the
545
+ // structural + SQL projection checks below still run over every blueprint.
525
546
  const { runBlueprintAudit } = await import("../blueprint/lifecycle/audit.js");
526
547
  const lifecycle = await runBlueprintAudit({
527
548
  projectRoot: cwd,
528
- all: true,
549
+ ...resolveRunBlueprintAuditScope(options.changedOnly === true, changedScope?.files ?? null),
529
550
  completedApprovalGate: options.changedOnly === true,
530
551
  inProgressApprovalGate: options.changedOnly === true,
531
552
  strict: true,
@@ -262,6 +262,10 @@ function isDependabotDependencyFile(filePath) {
262
262
  normalized === "bun.lockb" ||
263
263
  normalized === "deno.lock" ||
264
264
  normalized === "pnpm-workspace.yaml" ||
265
+ normalized === "Cargo.toml" ||
266
+ normalized.endsWith("/Cargo.toml") ||
267
+ normalized === "Cargo.lock" ||
268
+ normalized.endsWith("/Cargo.lock") ||
265
269
  (normalized.startsWith(".github/workflows/") && /\.ya?ml$/u.test(basename)));
266
270
  }
267
271
  function normalizeHeadRef(value) {
@@ -4,15 +4,16 @@ import matter from "gray-matter";
4
4
  import { parseTrustDossier } from "#trust/dossier.js";
5
5
  import { validateBlueprintTrust } from "#trust/validator.js";
6
6
  import { parseBlueprintDocumentRelativePath } from "#utils/document-paths.js";
7
+ import { stableSlugForBlueprintFile } from "#lifecycle/review-provenance.js";
8
+ import { isBlueprintExemptFromRollout, ROLLOUT_DATE } from "#lifecycle/rollout-anchor.js";
7
9
  const EXECUTABLE_DIRS = ["planned", "in-progress", "completed"];
8
- const TRUST_DOSSIER_ROLLOUT_DATE = "2026-07-12";
9
10
  export function auditBlueprintTrust(rootDirectory = process.cwd()) {
10
11
  const violations = [];
11
12
  let checked = 0;
12
13
  for (const file of findExecutableBlueprints(rootDirectory)) {
13
14
  checked += 1;
14
15
  const markdown = readFileSync(path.join(rootDirectory, file), "utf8");
15
- const grandfatherMissingDossier = isPreContractBlueprintMissingDossier(markdown);
16
+ const grandfatherMissingDossier = isPreContractBlueprintMissingDossier(markdown, rootDirectory, file);
16
17
  const status = readStatus(markdown);
17
18
  const normalizedStatus = isBlueprintTrustStatus(status) ? status : "planned";
18
19
  if (!isBlueprintTrustStatus(status)) {
@@ -40,20 +41,23 @@ export function auditBlueprintTrust(rootDirectory = process.cwd()) {
40
41
  }
41
42
  return { ok: violations.length === 0, title: "Blueprint trust", checked, violations };
42
43
  }
43
- function isPreContractBlueprintMissingDossier(markdown) {
44
+ function isPreContractBlueprintMissingDossier(markdown, repoRoot, file) {
44
45
  const parsedDossier = parseTrustDossier(markdown);
45
46
  if (parsedDossier.violations.length !== 1 ||
46
47
  parsedDossier.violations[0]?.section !== "Trust Dossier" ||
47
48
  parsedDossier.violations[0]?.message !== "missing Trust Dossier section") {
48
49
  return false;
49
50
  }
50
- const created = matter(markdown).data["created"];
51
- const createdDate = created instanceof Date
52
- ? created.toISOString().slice(0, 10)
53
- : typeof created === "string" && /^\d{4}-\d{2}-\d{2}$/u.test(created)
54
- ? created
55
- : null;
56
- return createdDate !== null && createdDate < TRUST_DOSSIER_ROLLOUT_DATE;
51
+ // Was a compare against frontmatter `created`, which the blueprint own
52
+ // author controls -- writing an old date suppressed the missing-dossier
53
+ // violation outright. Presence at the rollout anchor commit is the
54
+ // unforgeable equivalent; see `#lifecycle/rollout-anchor.js`. Fails closed.
55
+ return isBlueprintExemptFromRollout({
56
+ repoRoot,
57
+ slug: stableSlugForBlueprintFile(file),
58
+ created: matter(markdown).data["created"],
59
+ rolloutDate: ROLLOUT_DATE,
60
+ });
57
61
  }
58
62
  export function findExecutableBlueprints(rootDirectory) {
59
63
  const root = path.join(rootDirectory, "blueprints");
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Shared "does this change carry release evidence" predicate.
3
+ *
4
+ * Single source of truth for the `changeset-missing` rule described in
5
+ * `catalog/agent/rules/changeset-release.md`: release-visible changes need a
6
+ * `.changeset/*.md` entry (or an all-docs change, or a `Changeset-exempt:`
7
+ * trailer). Consumed by both the live-PR readiness evaluator
8
+ * (`#status/snapshot.js`) and the local branch-scoped audit
9
+ * (`#audit/changeset-required.js`) so the two verdicts can never diverge.
10
+ */
11
+ export declare const CHANGESET_EXEMPT_PATTERN: RegExp;
12
+ /**
13
+ * True when `files` carries release evidence for its own change: every
14
+ * changed file is documentation, one of the changed files is itself a
15
+ * `.changeset/*.md` entry, or one of `exemptionTexts` (PR body, commit
16
+ * messages, ...) carries a `Changeset-exempt: <reason>` trailer.
17
+ *
18
+ * Mirrors the original `wp_pr_status` behavior exactly: an empty `files` list
19
+ * is NOT treated as evidence (fails closed) — callers that consider "no
20
+ * changed files" a legitimate, non-suspicious state (e.g. a freshly rebased
21
+ * local branch) must short-circuit before calling this function.
22
+ */
23
+ export declare function hasChangesetEvidence(files: readonly string[], exemptionTexts: readonly string[]): boolean;
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Shared "does this change carry release evidence" predicate.
3
+ *
4
+ * Single source of truth for the `changeset-missing` rule described in
5
+ * `catalog/agent/rules/changeset-release.md`: release-visible changes need a
6
+ * `.changeset/*.md` entry (or an all-docs change, or a `Changeset-exempt:`
7
+ * trailer). Consumed by both the live-PR readiness evaluator
8
+ * (`#status/snapshot.js`) and the local branch-scoped audit
9
+ * (`#audit/changeset-required.js`) so the two verdicts can never diverge.
10
+ */
11
+ const CHANGESET_FILE_PATTERN = /^\.changeset\/[^/]+\.md$/u;
12
+ export const CHANGESET_EXEMPT_PATTERN = /Changeset-exempt:\s*\S/iu;
13
+ /**
14
+ * True when `files` carries release evidence for its own change: every
15
+ * changed file is documentation, one of the changed files is itself a
16
+ * `.changeset/*.md` entry, or one of `exemptionTexts` (PR body, commit
17
+ * messages, ...) carries a `Changeset-exempt: <reason>` trailer.
18
+ *
19
+ * Mirrors the original `wp_pr_status` behavior exactly: an empty `files` list
20
+ * is NOT treated as evidence (fails closed) — callers that consider "no
21
+ * changed files" a legitimate, non-suspicious state (e.g. a freshly rebased
22
+ * local branch) must short-circuit before calling this function.
23
+ */
24
+ export function hasChangesetEvidence(files, exemptionTexts) {
25
+ if (files.length > 0 && files.every((file) => file.endsWith(".md")))
26
+ return true;
27
+ if (files.some((file) => CHANGESET_FILE_PATTERN.test(file)))
28
+ return true;
29
+ return exemptionTexts.some((text) => CHANGESET_EXEMPT_PATTERN.test(text));
30
+ }
@@ -0,0 +1,10 @@
1
+ import type { RepoAuditResult } from "./repo-guardrails.js";
2
+ export interface ChangesetRequiredOptions {
3
+ /** Base ref for the branch diff. Defaults to `origin/${GITHUB_BASE_REF:-main}`. */
4
+ readonly baseRef?: string;
5
+ /** Test/adapter seam for an already-resolved changed-file list. */
6
+ readonly changedFiles?: readonly string[];
7
+ /** Test/adapter seam for already-resolved commit messages. */
8
+ readonly commitMessages?: readonly string[];
9
+ }
10
+ export declare function auditChangesetRequired(rootDirectory?: string, options?: ChangesetRequiredOptions): RepoAuditResult;
@@ -0,0 +1,73 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { defaultBranchBaseRef, getBranchChangedFiles } from "#git/changed-files";
3
+ import { hasChangesetEvidence } from "./changeset-evidence.js";
4
+ /**
5
+ * `wp audit changeset-required` — the local, branch-scoped mirror of the
6
+ * `changeset-missing` blocker `wp_pr_status` computes against a live PR (see
7
+ * `#status/snapshot.js`). Both call the same `hasChangesetEvidence` predicate
8
+ * so a locally-green `wp ci-preflight` can never diverge from the landing
9
+ * verdict (`catalog/agent/rules/ci-cost-local-first.md`).
10
+ *
11
+ * Unlike the PR-based check (which reads `pr.files`/`pr.body` from the GitHub
12
+ * API), this audit resolves its own branch diff and scans local commit
13
+ * messages for a `Changeset-exempt: <reason>` trailer, so it works fully
14
+ * offline pre-push.
15
+ */
16
+ const TITLE = "Changeset required";
17
+ const GIT_LOG_PROBE_OPTIONS = { timeout: 1_500, maxBuffer: 1024 * 1024 };
18
+ export function auditChangesetRequired(rootDirectory = process.cwd(), options = {}) {
19
+ const baseRef = options.baseRef?.trim() || defaultBranchBaseRef();
20
+ let changedFiles;
21
+ if (options.changedFiles) {
22
+ changedFiles = options.changedFiles;
23
+ }
24
+ else {
25
+ const resolved = resolveChangedFiles(rootDirectory, baseRef);
26
+ if (resolved.error)
27
+ return fail(resolved.error, 0);
28
+ changedFiles = resolved.files;
29
+ }
30
+ if (changedFiles.length === 0) {
31
+ return passWithWarning(`[warn] changeset-required: no changed files vs ${baseRef} — skipping`, 0);
32
+ }
33
+ const exemptionMessages = options.commitMessages ?? readCommitMessages(rootDirectory, baseRef);
34
+ if (hasChangesetEvidence(changedFiles, exemptionMessages)) {
35
+ return pass(changedFiles.length);
36
+ }
37
+ return fail("release-visible changes require a .changeset/*.md entry (`wp run changeset`) " +
38
+ "or a `Changeset-exempt: <reason>` commit trailer", changedFiles.length);
39
+ }
40
+ function pass(checked) {
41
+ return { ok: true, title: TITLE, checked, violations: [] };
42
+ }
43
+ function passWithWarning(message, checked) {
44
+ return { ok: true, title: TITLE, checked, violations: [{ message }] };
45
+ }
46
+ function fail(message, checked) {
47
+ return { ok: false, title: TITLE, checked, violations: [{ message }] };
48
+ }
49
+ function resolveChangedFiles(cwd, baseRef) {
50
+ const result = getBranchChangedFiles(cwd, baseRef);
51
+ if (result.degraded) {
52
+ return {
53
+ files: [],
54
+ error: `unable to resolve changed files vs ${baseRef} (${result.reason}); ` +
55
+ "refusing to skip changeset enforcement",
56
+ };
57
+ }
58
+ return { files: result.files };
59
+ }
60
+ function readCommitMessages(cwd, baseRef) {
61
+ const result = spawnSync("git", ["log", "--format=%B%x00", `${baseRef}...HEAD`], {
62
+ cwd,
63
+ encoding: "utf8",
64
+ stdio: ["ignore", "pipe", "pipe"],
65
+ ...GIT_LOG_PROBE_OPTIONS,
66
+ });
67
+ if (result.status !== 0 || result.error)
68
+ return [];
69
+ return result.stdout
70
+ .split("\0")
71
+ .map((entry) => entry.trim())
72
+ .filter(Boolean);
73
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Shared content-absence detection for the `wp audit guardrails` CI wiring
3
+ * contract. Both the audit (`ci-guardrails-wiring.ts`) and the self-healing
4
+ * injector (`scaffold-base-kit.ts`'s `ensureGuardrailsCiStep`) must agree on
5
+ * what counts as a "live" test-run step to anchor guardrails after — keeping
6
+ * that detection in one place is what keeps the two from silently diverging.
7
+ *
8
+ * Prior to this module, the audit matched the test-run pattern against whole
9
+ * file content (including inside `#`-commented lines) while the injector's
10
+ * anchor scan already skipped comment lines. A workflow whose only test-run
11
+ * mention was commented out therefore tripped the audit red with no anchor
12
+ * for the injector to repair — an unrepairable-red state (tech-debt h-016).
13
+ */
14
+ /** True when `content` invokes `wp audit guardrails` anywhere (including
15
+ * inside comments — see the pattern's doc comment above). */
16
+ export declare function hasGuardrailsInvocation(content: string): boolean;
17
+ /**
18
+ * Scans `content` line by line, skipping commented-out lines, tracking the
19
+ * most recent step-start line (`- ...`), and returning the index of the step
20
+ * that owns the first non-commented line matching the live test-run pattern.
21
+ * Returns -1 when no such line exists.
22
+ *
23
+ * This is the single source of truth for "is there a live test-run step to
24
+ * anchor a guardrails step before" — used by the audit (as a boolean) and by
25
+ * the injector (as an insertion index).
26
+ */
27
+ export declare function findLiveTestRunAnchor(content: string): number;
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Shared content-absence detection for the `wp audit guardrails` CI wiring
3
+ * contract. Both the audit (`ci-guardrails-wiring.ts`) and the self-healing
4
+ * injector (`scaffold-base-kit.ts`'s `ensureGuardrailsCiStep`) must agree on
5
+ * what counts as a "live" test-run step to anchor guardrails after — keeping
6
+ * that detection in one place is what keeps the two from silently diverging.
7
+ *
8
+ * Prior to this module, the audit matched the test-run pattern against whole
9
+ * file content (including inside `#`-commented lines) while the injector's
10
+ * anchor scan already skipped comment lines. A workflow whose only test-run
11
+ * mention was commented out therefore tripped the audit red with no anchor
12
+ * for the injector to repair — an unrepairable-red state (tech-debt h-016).
13
+ */
14
+ /**
15
+ * The guardrails invocation, keyed on the command itself (not the `wp`
16
+ * prefix nor a marker comment) so `wp audit guardrails`,
17
+ * `./bin/wp audit guardrails`, and the `--affected`/`--full` variants all
18
+ * count as wired.
19
+ *
20
+ * NOTE: this intentionally matches inside comments too — it is a
21
+ * whole-content test, not line-scanned. A commented-out `wp audit
22
+ * guardrails` line therefore still counts as "wired" by both the audit and
23
+ * the injector. This is an inherent limitation of content-absence
24
+ * pattern-matching (neither side parses YAML as executable steps) rather
25
+ * than a bug unique to either detector — see tech-debt h-016. Do not "fix"
26
+ * this by line-scanning here without first solving real YAML-step parsing;
27
+ * a half-parsed heuristic would be worse than the documented limitation.
28
+ */
29
+ const GUARDRAILS_INVOCATION_PATTERN = /\baudit guardrails\b/u;
30
+ /**
31
+ * Lines that run the default test suite — the signal that a workflow is a
32
+ * quality-CI pipeline that owes a guardrails step. Comment lines are
33
+ * excluded: a commented-out test-run mention is not a live test run.
34
+ */
35
+ const TEST_RUN_PATTERN = /(?:\bvp\s+run\s+test\b|\bpnpm\s+(?:run\s+)?test\b|\bnpm\s+(?:run\s+)?test\b|\bbun\s+(?:run\s+)?test\b|\bwp\s+test\b|\bvitest\s+run\b)/iu;
36
+ function isCommentLine(line) {
37
+ return line.trim().startsWith("#");
38
+ }
39
+ /** True when `content` invokes `wp audit guardrails` anywhere (including
40
+ * inside comments — see the pattern's doc comment above). */
41
+ export function hasGuardrailsInvocation(content) {
42
+ return GUARDRAILS_INVOCATION_PATTERN.test(content);
43
+ }
44
+ /**
45
+ * Scans `content` line by line, skipping commented-out lines, tracking the
46
+ * most recent step-start line (`- ...`), and returning the index of the step
47
+ * that owns the first non-commented line matching the live test-run pattern.
48
+ * Returns -1 when no such line exists.
49
+ *
50
+ * This is the single source of truth for "is there a live test-run step to
51
+ * anchor a guardrails step before" — used by the audit (as a boolean) and by
52
+ * the injector (as an insertion index).
53
+ */
54
+ export function findLiveTestRunAnchor(content) {
55
+ const lines = content.split("\n");
56
+ let lastStepStart = -1;
57
+ for (let index = 0; index < lines.length; index += 1) {
58
+ // Safe: index is bounded by the loop condition against lines.length.
59
+ const line = lines[index];
60
+ if (isCommentLine(line))
61
+ continue;
62
+ if (/^\s*-\s/u.test(line))
63
+ lastStepStart = index;
64
+ if (TEST_RUN_PATTERN.test(line))
65
+ return lastStepStart;
66
+ }
67
+ return -1;
68
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Guardrail: keep `wp audit guardrails` wired into a repo's own CI.
3
+ *
4
+ * `wp audit guardrails` is the delivery vehicle for every guardrail that ships
5
+ * inside the suite (e.g. `typescript-version` from #810). A consumer inherits a
6
+ * new guardrail automatically on their next agent-kit bump ONLY IF their CI
7
+ * still invokes `wp audit guardrails`. If that step is deleted, the whole suite
8
+ * silently stops running in CI and new guardrails never reach the consumer.
9
+ *
10
+ * This audit is dogfood / defense-in-depth — NOT the delivery guarantee. It
11
+ * cannot run if the very invariant it checks is already broken (a CI with no
12
+ * guardrails step never executes this audit in CI). The actual self-healing
13
+ * guarantee is the managed inject-by-content-absence repair in
14
+ * `scaffold-base-kit.ts` (`ensureGuardrailsCiStep`), which re-inserts the step
15
+ * on every `wp setup`. This audit's value is LOCAL: it is registered as
16
+ * affected-safe + MCP-visible, so `wp audit guardrails --affected` (pre-commit /
17
+ * IDE) catches the drift on the developer's machine even when CI has drifted.
18
+ *
19
+ * Modeled on the self-CI checks in `ci-test-perf.ts`.
20
+ */
21
+ import type { RepoAuditResult } from "./repo-guardrails.js";
22
+ export declare function auditCiGuardrailsWiring(rootDirectory?: string): RepoAuditResult;
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Guardrail: keep `wp audit guardrails` wired into a repo's own CI.
3
+ *
4
+ * `wp audit guardrails` is the delivery vehicle for every guardrail that ships
5
+ * inside the suite (e.g. `typescript-version` from #810). A consumer inherits a
6
+ * new guardrail automatically on their next agent-kit bump ONLY IF their CI
7
+ * still invokes `wp audit guardrails`. If that step is deleted, the whole suite
8
+ * silently stops running in CI and new guardrails never reach the consumer.
9
+ *
10
+ * This audit is dogfood / defense-in-depth — NOT the delivery guarantee. It
11
+ * cannot run if the very invariant it checks is already broken (a CI with no
12
+ * guardrails step never executes this audit in CI). The actual self-healing
13
+ * guarantee is the managed inject-by-content-absence repair in
14
+ * `scaffold-base-kit.ts` (`ensureGuardrailsCiStep`), which re-inserts the step
15
+ * on every `wp setup`. This audit's value is LOCAL: it is registered as
16
+ * affected-safe + MCP-visible, so `wp audit guardrails --affected` (pre-commit /
17
+ * IDE) catches the drift on the developer's machine even when CI has drifted.
18
+ *
19
+ * Modeled on the self-CI checks in `ci-test-perf.ts`.
20
+ */
21
+ import { existsSync, readdirSync, readFileSync } from "node:fs";
22
+ import { join, relative } from "node:path";
23
+ import { findLiveTestRunAnchor, hasGuardrailsInvocation } from "./ci-guardrails-detection.js";
24
+ const WORKFLOW_DIR = ".github/workflows";
25
+ const WORKFLOW_FILE_PATTERN = /\.(ya?ml)$/iu;
26
+ function walkWorkflowFiles(dir) {
27
+ if (!existsSync(dir))
28
+ return [];
29
+ const files = [];
30
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
31
+ const fullPath = join(dir, entry.name);
32
+ if (entry.isDirectory()) {
33
+ files.push(...walkWorkflowFiles(fullPath));
34
+ continue;
35
+ }
36
+ if (entry.isFile() && WORKFLOW_FILE_PATTERN.test(entry.name)) {
37
+ files.push(fullPath);
38
+ }
39
+ }
40
+ return files;
41
+ }
42
+ export function auditCiGuardrailsWiring(rootDirectory = process.cwd()) {
43
+ const workflowsRoot = join(rootDirectory, WORKFLOW_DIR);
44
+ const files = walkWorkflowFiles(workflowsRoot);
45
+ const testWorkflows = [];
46
+ let anyWorkflowWiresGuardrails = false;
47
+ for (const file of files) {
48
+ const content = readFileSync(file, "utf8");
49
+ if (hasGuardrailsInvocation(content))
50
+ anyWorkflowWiresGuardrails = true;
51
+ if (findLiveTestRunAnchor(content) !== -1) {
52
+ testWorkflows.push(relative(rootDirectory, file).replace(/\\/gu, "/"));
53
+ }
54
+ }
55
+ // No quality-CI pipeline (no workflow runs the default test suite) → nothing
56
+ // to enforce; a repo without agent-kit CI is not "drifted".
57
+ const violations = [];
58
+ if (testWorkflows.length > 0 && !anyWorkflowWiresGuardrails) {
59
+ for (const workflow of testWorkflows) {
60
+ violations.push({
61
+ file: workflow,
62
+ severity: "error",
63
+ ruleId: "ci-guardrails-wiring",
64
+ message: `${workflow} runs the test suite but no workflow invokes \`wp audit guardrails\`. ` +
65
+ `Guardrails (e.g. typescript-version) reach this repo only while CI runs ` +
66
+ `\`wp audit guardrails\`. Re-run \`wp setup\` to restore the managed CI step.`,
67
+ });
68
+ }
69
+ }
70
+ return {
71
+ ok: violations.length === 0,
72
+ title: "CI guardrails wiring",
73
+ checked: files.length,
74
+ sources: files.map((file) => ({
75
+ path: relative(rootDirectory, file).replace(/\\/gu, "/"),
76
+ kind: "workflow",
77
+ })),
78
+ violations,
79
+ };
80
+ }