@webpresso/agent-kit 3.1.30 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

Files changed (257) hide show
  1. package/README.md +53 -15
  2. package/bin/_run.js +179 -19
  3. package/catalog/AGENTS.md.tpl +3 -3
  4. package/catalog/agent/agents/implementer.md +45 -0
  5. package/catalog/agent/rules/parallel-orchestration-quota.md +100 -0
  6. package/catalog/agent/rules/pre-implementation.md +28 -1
  7. package/catalog/agent/rules/supported-agent-clis.md +9 -0
  8. package/catalog/agent/rules/typescript-version.md +50 -0
  9. package/catalog/agent/skills/codex/SKILL.md +25 -1
  10. package/catalog/agent/skills/verify/SKILL.md +4 -2
  11. package/catalog/docs/templates/blueprint.md +1 -1
  12. package/dist/esm/audit/agents.js +1 -0
  13. package/dist/esm/audit/blueprint-lifecycle-sql.d.ts +12 -0
  14. package/dist/esm/audit/blueprint-lifecycle-sql.js +23 -2
  15. package/dist/esm/audit/blueprint-pr-coverage.js +4 -0
  16. package/dist/esm/audit/ci-guardrails-wiring.d.ts +22 -0
  17. package/dist/esm/audit/ci-guardrails-wiring.js +90 -0
  18. package/dist/esm/audit/ci-test-perf.js +30 -0
  19. package/dist/esm/audit/command-surface.js +4 -2
  20. package/dist/esm/audit/host-substitution-risk.d.ts +2 -0
  21. package/dist/esm/audit/host-substitution-risk.js +256 -0
  22. package/dist/esm/audit/registry.d.ts +4 -4
  23. package/dist/esm/audit/registry.js +8 -1
  24. package/dist/esm/audit/toolchain-isolation.js +1 -2
  25. package/dist/esm/audit/typescript-version.d.ts +2 -0
  26. package/dist/esm/audit/typescript-version.js +123 -0
  27. package/dist/esm/blueprint/core/parser.js +43 -1
  28. package/dist/esm/blueprint/core/validation/criteria.d.ts +14 -0
  29. package/dist/esm/blueprint/core/validation/criteria.js +41 -2
  30. package/dist/esm/blueprint/core/validation/state.js +25 -14
  31. package/dist/esm/blueprint/db/migrations/run.js +4 -2
  32. package/dist/esm/blueprint/lifecycle/audit.d.ts +20 -0
  33. package/dist/esm/blueprint/lifecycle/audit.js +65 -10
  34. package/dist/esm/blueprint/lifecycle/canonicalize.d.ts +6 -0
  35. package/dist/esm/blueprint/lifecycle/canonicalize.js +135 -0
  36. package/dist/esm/blueprint/lifecycle/local.d.ts +2 -1
  37. package/dist/esm/blueprint/lifecycle/local.js +19 -3
  38. package/dist/esm/blueprint/lifecycle/review-provenance.js +36 -11
  39. package/dist/esm/blueprint/service/BlueprintCreationService.d.ts +1 -0
  40. package/dist/esm/blueprint/service/BlueprintCreationService.js +53 -4
  41. package/dist/esm/blueprint/service/blueprint-tech-debt-links.js +16 -2
  42. package/dist/esm/blueprint/trust/command-runner.d.ts +26 -0
  43. package/dist/esm/blueprint/trust/command-runner.js +148 -0
  44. package/dist/esm/blueprint/trust/gates.d.ts +7 -0
  45. package/dist/esm/blueprint/trust/gates.js +20 -0
  46. package/dist/esm/blueprint/trust/promotion.d.ts +8 -0
  47. package/dist/esm/blueprint/trust/promotion.js +62 -112
  48. package/dist/esm/blueprint/trust/scaffold.d.ts +19 -0
  49. package/dist/esm/blueprint/trust/scaffold.js +64 -0
  50. package/dist/esm/blueprint/trust/validator.d.ts +1 -0
  51. package/dist/esm/blueprint/trust/validator.js +7 -1
  52. package/dist/esm/blueprint/utils/archive.js +27 -5
  53. package/dist/esm/blueprint/utils/document-paths.d.ts +18 -0
  54. package/dist/esm/blueprint/utils/document-paths.js +45 -12
  55. package/dist/esm/build/cli-mcp-parity.js +10 -0
  56. package/dist/esm/cli/bundle/commands/blueprint.d.ts +1 -0
  57. package/dist/esm/cli/bundle/commands/blueprint.js +1 -0
  58. package/dist/esm/cli/cli.d.ts +1 -1
  59. package/dist/esm/cli/cli.js +34 -0
  60. package/dist/esm/cli/commands/agent-launch.d.ts +6 -1
  61. package/dist/esm/cli/commands/agent-launch.js +82 -4
  62. package/dist/esm/cli/commands/audit-core.d.ts +1 -1
  63. package/dist/esm/cli/commands/audit-core.js +8 -1
  64. package/dist/esm/cli/commands/audit.js +17 -5
  65. package/dist/esm/cli/commands/blueprint/execution.js +18 -6
  66. package/dist/esm/cli/commands/blueprint/mutations.js +59 -40
  67. package/dist/esm/cli/commands/blueprint/router-dispatch.d.ts +2 -1
  68. package/dist/esm/cli/commands/blueprint/router-dispatch.js +20 -1
  69. package/dist/esm/cli/commands/blueprint/router-output.d.ts +21 -0
  70. package/dist/esm/cli/commands/blueprint/router-output.js +48 -17
  71. package/dist/esm/cli/commands/blueprint/router.d.ts +7 -0
  72. package/dist/esm/cli/commands/blueprint/router.js +13 -1
  73. package/dist/esm/cli/commands/ci-preflight.d.ts +13 -2
  74. package/dist/esm/cli/commands/ci-preflight.js +6 -3
  75. package/dist/esm/cli/commands/compile.js +5 -31
  76. package/dist/esm/cli/commands/dash/delivery-status.d.ts +1 -0
  77. package/dist/esm/cli/commands/dash/delivery-status.js +11 -4
  78. package/dist/esm/cli/commands/doctor.js +5 -0
  79. package/dist/esm/cli/commands/hook.d.ts +5 -0
  80. package/dist/esm/cli/commands/hook.js +15 -9
  81. package/dist/esm/cli/commands/init/config.js +2 -2
  82. package/dist/esm/cli/commands/init/convergence-apply.d.ts +24 -0
  83. package/dist/esm/cli/commands/init/convergence-apply.js +221 -0
  84. package/dist/esm/cli/commands/init/convergence-state.d.ts +13 -0
  85. package/dist/esm/cli/commands/init/convergence-state.js +54 -0
  86. package/dist/esm/cli/commands/init/convergence.d.ts +23 -0
  87. package/dist/esm/cli/commands/init/convergence.js +57 -0
  88. package/dist/esm/cli/commands/init/git-convergence.d.ts +19 -0
  89. package/dist/esm/cli/commands/init/git-convergence.js +168 -0
  90. package/dist/esm/cli/commands/init/gitignore-patcher.js +2 -0
  91. package/dist/esm/cli/commands/init/grok-privacy-smoke.d.ts +52 -0
  92. package/dist/esm/cli/commands/init/grok-privacy-smoke.js +91 -0
  93. package/dist/esm/cli/commands/init/host-native-proof.d.ts +18 -0
  94. package/dist/esm/cli/commands/init/host-native-proof.js +88 -0
  95. package/dist/esm/cli/commands/init/host-visibility.d.ts +2 -2
  96. package/dist/esm/cli/commands/init/host-visibility.js +94 -5
  97. package/dist/esm/cli/commands/init/index.d.ts +1 -0
  98. package/dist/esm/cli/commands/init/index.js +326 -173
  99. package/dist/esm/cli/commands/init/mcp-spec.d.ts +57 -0
  100. package/dist/esm/cli/commands/init/mcp-spec.js +273 -0
  101. package/dist/esm/cli/commands/init/merge.d.ts +1 -0
  102. package/dist/esm/cli/commands/init/merge.js +30 -1
  103. package/dist/esm/cli/commands/init/plugin-cache-prune.d.ts +0 -3
  104. package/dist/esm/cli/commands/init/plugin-cache-prune.js +0 -1
  105. package/dist/esm/cli/commands/init/scaffold-agents-md.d.ts +1 -1
  106. package/dist/esm/cli/commands/init/scaffold-agents-md.js +9 -3
  107. package/dist/esm/cli/commands/init/scaffold-base-kit.d.ts +11 -0
  108. package/dist/esm/cli/commands/init/scaffold-base-kit.js +56 -1
  109. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/emitters/grok.d.ts +14 -8
  110. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/emitters/grok.js +41 -24
  111. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/index.d.ts +2 -0
  112. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/index.js +51 -23
  113. package/dist/esm/cli/commands/init/scaffolders/codex-mcp/index.d.ts +12 -31
  114. package/dist/esm/cli/commands/init/scaffolders/codex-mcp/index.js +288 -202
  115. package/dist/esm/cli/commands/init/scaffolders/file-transaction.d.ts +7 -0
  116. package/dist/esm/cli/commands/init/scaffolders/file-transaction.js +58 -0
  117. package/dist/esm/cli/commands/init/scaffolders/grok-mcp/index.d.ts +38 -0
  118. package/dist/esm/cli/commands/init/scaffolders/grok-mcp/index.js +73 -0
  119. package/dist/esm/cli/commands/init/scaffolders/opencode-plugin/index.d.ts +21 -0
  120. package/dist/esm/cli/commands/init/scaffolders/opencode-plugin/index.js +559 -63
  121. package/dist/esm/cli/commands/init/untracked-collision.d.ts +14 -0
  122. package/dist/esm/cli/commands/init/untracked-collision.js +42 -0
  123. package/dist/esm/cli/commands/opencode-rotate.d.ts +49 -0
  124. package/dist/esm/cli/commands/opencode-rotate.js +135 -0
  125. package/dist/esm/cli/commands/public-readiness.d.ts +11 -0
  126. package/dist/esm/cli/commands/public-readiness.js +42 -0
  127. package/dist/esm/cli/commands/review.d.ts +35 -3
  128. package/dist/esm/cli/commands/review.js +729 -209
  129. package/dist/esm/cli/commands/run-wait.d.ts +2 -0
  130. package/dist/esm/cli/commands/run-wait.js +122 -0
  131. package/dist/esm/cli/commands/setup-help.js +1 -1
  132. package/dist/esm/cli/commands/setup-repair-help.js +1 -1
  133. package/dist/esm/cli/commands/worktree/router-dispatch.js +13 -8
  134. package/dist/esm/cli/direct-provider-launch.js +9 -5
  135. package/dist/esm/cli/optional-tool-freshness.d.ts +27 -0
  136. package/dist/esm/cli/optional-tool-freshness.js +219 -9
  137. package/dist/esm/daemon/domains/git-actions.d.ts +29 -0
  138. package/dist/esm/daemon/domains/git-actions.js +136 -0
  139. package/dist/esm/daemon/domains/run-registry-interop.d.ts +16 -0
  140. package/dist/esm/daemon/domains/run-registry-interop.js +45 -0
  141. package/dist/esm/daemon/domains/sessions-search.d.ts +11 -0
  142. package/dist/esm/daemon/domains/sessions-search.js +29 -0
  143. package/dist/esm/daemon/handlers/blueprints-handlers.d.ts +5 -0
  144. package/dist/esm/daemon/handlers/blueprints-handlers.js +36 -0
  145. package/dist/esm/daemon/handlers/deploy-handlers.d.ts +6 -0
  146. package/dist/esm/daemon/handlers/deploy-handlers.js +5 -0
  147. package/dist/esm/daemon/handlers/fleet-handlers.d.ts +15 -0
  148. package/dist/esm/daemon/handlers/fleet-handlers.js +91 -0
  149. package/dist/esm/daemon/handlers/git-handlers.d.ts +6 -0
  150. package/dist/esm/daemon/handlers/git-handlers.js +27 -0
  151. package/dist/esm/daemon/handlers/product.d.ts +7 -0
  152. package/dist/esm/daemon/handlers/product.js +10 -119
  153. package/dist/esm/daemon/handlers/sessions-handlers.d.ts +12 -0
  154. package/dist/esm/daemon/handlers/sessions-handlers.js +31 -0
  155. package/dist/esm/daemon/handlers/shared.d.ts +5 -0
  156. package/dist/esm/daemon/handlers/shared.js +16 -0
  157. package/dist/esm/daemon/methods.d.ts +8 -1
  158. package/dist/esm/daemon/methods.js +7 -0
  159. package/dist/esm/daemon/protocol/contract.d.ts +43 -0
  160. package/dist/esm/daemon/protocol/contract.js +26 -0
  161. package/dist/esm/daemon/protocol/index.d.ts +1 -1
  162. package/dist/esm/daemon/protocol/index.js +1 -1
  163. package/dist/esm/hooks/__conformance__/matrix.d.ts +6 -0
  164. package/dist/esm/hooks/__conformance__/matrix.js +137 -0
  165. package/dist/esm/hooks/conformance/batch.js +5 -0
  166. package/dist/esm/hooks/doctor.d.ts +3 -3
  167. package/dist/esm/hooks/doctor.js +30 -15
  168. package/dist/esm/hooks/permission-request/index.js +9 -3
  169. package/dist/esm/hooks/permission-request/policy.d.ts +9 -0
  170. package/dist/esm/hooks/permission-request/policy.js +13 -0
  171. package/dist/esm/hooks/pretool-guard/dev-routing.d.ts +9 -0
  172. package/dist/esm/hooks/pretool-guard/dev-routing.js +41 -5
  173. package/dist/esm/hooks/pretool-guard/logger.d.ts +7 -0
  174. package/dist/esm/hooks/pretool-guard/logger.js +16 -0
  175. package/dist/esm/hooks/pretool-guard/runner.d.ts +6 -0
  176. package/dist/esm/hooks/pretool-guard/runner.js +68 -16
  177. package/dist/esm/hooks/pretool-guard/validators/blueprint.js +30 -10
  178. package/dist/esm/hooks/pretool-guard/validators/forbidden-commands.js +54 -0
  179. package/dist/esm/hooks/pretool-guard/validators/index.d.ts +7 -0
  180. package/dist/esm/hooks/pretool-guard/validators/index.js +21 -0
  181. package/dist/esm/hooks/pretool-guard/validators/path-contract.d.ts +19 -0
  182. package/dist/esm/hooks/pretool-guard/validators/path-contract.js +25 -8
  183. package/dist/esm/hooks/shared/guard-outcome.d.ts +149 -0
  184. package/dist/esm/hooks/shared/guard-outcome.js +215 -0
  185. package/dist/esm/hooks/shared/hook-bootstrap.d.ts +26 -0
  186. package/dist/esm/hooks/shared/hook-bootstrap.js +64 -1
  187. package/dist/esm/hooks/shared/types.d.ts +8 -5
  188. package/dist/esm/hooks/shared/types.js +23 -8
  189. package/dist/esm/hooks/stop/qa-changed-files.d.ts +11 -0
  190. package/dist/esm/hooks/stop/qa-changed-files.js +74 -0
  191. package/dist/esm/mcp/blueprint/_shared/lifecycle.js +14 -1
  192. package/dist/esm/mcp/blueprint/handlers/document-mutations.js +78 -4
  193. package/dist/esm/mcp/blueprint/handlers/review-log.js +8 -1
  194. package/dist/esm/mcp/blueprint/handlers/task-advance.js +113 -54
  195. package/dist/esm/mcp/blueprint/handlers/task-verify.js +39 -19
  196. package/dist/esm/mcp/blueprint/registration.js +3 -2
  197. package/dist/esm/mcp/server.d.ts +1 -0
  198. package/dist/esm/mcp/server.js +27 -0
  199. package/dist/esm/mcp/tools/_names.d.ts +1 -1
  200. package/dist/esm/mcp/tools/_names.js +5 -0
  201. package/dist/esm/mcp/tools/_registry.js +10 -0
  202. package/dist/esm/mcp/tools/_shared/audit-kinds.d.ts +1 -1
  203. package/dist/esm/mcp/tools/_shared/result.js +44 -1
  204. package/dist/esm/mcp/tools/audit.d.ts +4 -1
  205. package/dist/esm/mcp/tools/audit.js +30 -0
  206. package/dist/esm/mcp/tools/audits.d.ts +3 -0
  207. package/dist/esm/mcp/tools/ci-preflight.d.ts +50 -0
  208. package/dist/esm/mcp/tools/ci-preflight.js +183 -0
  209. package/dist/esm/mcp/tools/pr-upsert.js +39 -1
  210. package/dist/esm/mcp/tools/review-run.d.ts +28 -0
  211. package/dist/esm/mcp/tools/review-run.js +216 -0
  212. package/dist/esm/mcp/tools/run-wait.d.ts +86 -0
  213. package/dist/esm/mcp/tools/run-wait.js +593 -0
  214. package/dist/esm/mcp/tools/session-id.d.ts +3 -0
  215. package/dist/esm/mcp/tools/session-id.js +45 -0
  216. package/dist/esm/mcp/tools/session-info.d.ts +3 -0
  217. package/dist/esm/mcp/tools/session-info.js +49 -0
  218. package/dist/esm/output-transforms/audit.d.ts +23 -0
  219. package/dist/esm/output-transforms/audit.js +47 -0
  220. package/dist/esm/output-transforms/index.js +2 -0
  221. package/dist/esm/review/authority.js +13 -5
  222. package/dist/esm/review/availability.d.ts +22 -0
  223. package/dist/esm/review/availability.js +107 -20
  224. package/dist/esm/review/delivery-verifier.d.ts +27 -0
  225. package/dist/esm/review/delivery-verifier.js +115 -0
  226. package/dist/esm/review/events.d.ts +3 -2
  227. package/dist/esm/review/events.js +23 -3
  228. package/dist/esm/review/execution/adapters.js +26 -9
  229. package/dist/esm/review/execution/output-policy.d.ts +2 -0
  230. package/dist/esm/review/execution/output-policy.js +12 -0
  231. package/dist/esm/review/execution/review-checkout.js +87 -6
  232. package/dist/esm/review/execution/supervisor.js +5 -0
  233. package/dist/esm/review/execution/types.d.ts +8 -0
  234. package/dist/esm/review/opencode-account.d.ts +24 -0
  235. package/dist/esm/review/opencode-account.js +68 -0
  236. package/dist/esm/review/opencode-auth.d.ts +33 -0
  237. package/dist/esm/review/opencode-auth.js +79 -0
  238. package/dist/esm/review/subject.d.ts +12 -0
  239. package/dist/esm/review/subject.js +74 -22
  240. package/dist/esm/runtime/opencode-account-materializer.d.ts +34 -0
  241. package/dist/esm/runtime/opencode-account-materializer.js +33 -0
  242. package/dist/esm/status/snapshot.js +30 -19
  243. package/dist/esm/test/duration-sequencer.d.ts +20 -0
  244. package/dist/esm/test/duration-sequencer.js +54 -0
  245. package/dist/esm/test/shard-durations.json +922 -0
  246. package/dist/esm/test/worker-budget.d.ts +6 -0
  247. package/dist/esm/test/worker-budget.js +59 -1
  248. package/dist/esm/test-helpers/global-setup.d.ts +1 -1
  249. package/dist/esm/test-helpers/global-setup.js +68 -24
  250. package/dist/esm/ultragoal/service.d.ts +7 -0
  251. package/dist/esm/ultragoal/service.js +42 -2
  252. package/dist/esm/utils/write-json-file.js +3 -1
  253. package/dist/esm/worktrees/codex-project-trust.d.ts +15 -0
  254. package/dist/esm/worktrees/codex-project-trust.js +56 -0
  255. package/dist/esm/worktrees/registry.d.ts +8 -0
  256. package/dist/esm/worktrees/registry.js +18 -3
  257. package/package.json +16 -13
@@ -0,0 +1,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) {
@@ -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,90 @@
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
+ const WORKFLOW_DIR = ".github/workflows";
24
+ const WORKFLOW_FILE_PATTERN = /\.(ya?ml)$/iu;
25
+ /** The guardrails invocation, keyed on the command itself (not the `wp` prefix
26
+ * nor a marker comment) so `wp audit guardrails`, `./bin/wp audit guardrails`,
27
+ * and the `--affected`/`--full` variants all count as wired. */
28
+ const GUARDRAILS_INVOCATION_PATTERN = /\baudit guardrails\b/u;
29
+ /**
30
+ * Lines that run the default test suite — the signal that a workflow is a
31
+ * quality-CI pipeline that owes a guardrails step. Mirrors the notion of a
32
+ * test-running workflow used by `ci-test-perf.ts`; kept local so the two audits
33
+ * stay independent.
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 walkWorkflowFiles(dir) {
37
+ if (!existsSync(dir))
38
+ return [];
39
+ const files = [];
40
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
41
+ const fullPath = join(dir, entry.name);
42
+ if (entry.isDirectory()) {
43
+ files.push(...walkWorkflowFiles(fullPath));
44
+ continue;
45
+ }
46
+ if (entry.isFile() && WORKFLOW_FILE_PATTERN.test(entry.name)) {
47
+ files.push(fullPath);
48
+ }
49
+ }
50
+ return files;
51
+ }
52
+ export function auditCiGuardrailsWiring(rootDirectory = process.cwd()) {
53
+ const workflowsRoot = join(rootDirectory, WORKFLOW_DIR);
54
+ const files = walkWorkflowFiles(workflowsRoot);
55
+ const testWorkflows = [];
56
+ let anyWorkflowWiresGuardrails = false;
57
+ for (const file of files) {
58
+ const content = readFileSync(file, "utf8");
59
+ if (GUARDRAILS_INVOCATION_PATTERN.test(content))
60
+ anyWorkflowWiresGuardrails = true;
61
+ if (TEST_RUN_PATTERN.test(content)) {
62
+ testWorkflows.push(relative(rootDirectory, file).replace(/\\/gu, "/"));
63
+ }
64
+ }
65
+ // No quality-CI pipeline (no workflow runs the default test suite) → nothing
66
+ // to enforce; a repo without agent-kit CI is not "drifted".
67
+ const violations = [];
68
+ if (testWorkflows.length > 0 && !anyWorkflowWiresGuardrails) {
69
+ for (const workflow of testWorkflows) {
70
+ violations.push({
71
+ file: workflow,
72
+ severity: "error",
73
+ ruleId: "ci-guardrails-wiring",
74
+ message: `${workflow} runs the test suite but no workflow invokes \`wp audit guardrails\`. ` +
75
+ `Guardrails (e.g. typescript-version) reach this repo only while CI runs ` +
76
+ `\`wp audit guardrails\`. Re-run \`wp setup\` to restore the managed CI step.`,
77
+ });
78
+ }
79
+ }
80
+ return {
81
+ ok: violations.length === 0,
82
+ title: "CI guardrails wiring",
83
+ checked: files.length,
84
+ sources: files.map((file) => ({
85
+ path: relative(rootDirectory, file).replace(/\\/gu, "/"),
86
+ kind: "workflow",
87
+ })),
88
+ violations,
89
+ };
90
+ }
@@ -11,6 +11,7 @@
11
11
  */
12
12
  import { existsSync, readdirSync, readFileSync } from "node:fs";
13
13
  import { join, relative } from "node:path";
14
+ import { parse as parseYaml } from "yaml";
14
15
  const WORKFLOW_DIR = ".github/workflows";
15
16
  const WORKFLOW_FILE_PATTERN = /\.(ya?ml)$/iu;
16
17
  /** Steps/jobs that intentionally serialize workers (mutation, Stryker, etc.). */
@@ -26,6 +27,16 @@ const SETUP_VP_CACHE_TRUE_PATTERN = /^\s*cache:\s*(?:true|pnpm|npm)\b/mu;
26
27
  const PACKAGE_INSTALL_PATTERN = /(?:\bvp\s+install\b|\bpnpm\s+install\b|\bnpm\s+ci\b|\bnpm\s+install\b|\bbun\s+install\b)/u;
27
28
  // Opt-out: put this comment on the same line or the line immediately above a serial force.
28
29
  const ALLOW_COMMENT_PATTERN = /ci-test-perf-allow(?:-serial)?\b/iu;
30
+ const AGENT_KIT_SELF_CI_PATH = ".github/workflows/ci.agent-kit.yml";
31
+ function readWorkflowJob(content, jobName) {
32
+ try {
33
+ const parsed = parseYaml(content);
34
+ return parsed.jobs?.[jobName];
35
+ }
36
+ catch {
37
+ return;
38
+ }
39
+ }
29
40
  function walkWorkflowFiles(dir) {
30
41
  if (!existsSync(dir))
31
42
  return [];
@@ -112,6 +123,25 @@ function findViolations(root, file) {
112
123
  `Cold installs inflate test job wall-clock without adding coverage.`,
113
124
  });
114
125
  }
126
+ if (relPath === AGENT_KIT_SELF_CI_PATH) {
127
+ const testJob = readWorkflowJob(content, "test");
128
+ const supervisorSteps = testJob?.steps?.filter((step) => typeof step.run === "string" && step.run.includes("bun scripts/run-vitest-projects.ts"));
129
+ const hasConcurrentLane = content.includes("WP_VITEST_PROJECT_MODE: concurrent");
130
+ const hasSerialLane = content.includes("WP_VITEST_PROJECT_MODE: serial");
131
+ if ((supervisorSteps?.length ?? 0) < 2 || !hasConcurrentLane || !hasSerialLane) {
132
+ violations.push({
133
+ file: relPath,
134
+ message: `${relPath}: agent-kit self-CI must run concurrent Test shards and the isolated serial lane ` +
135
+ `through scripts/run-vitest-projects.ts so project concurrency and owned-process cleanup stay enforced.`,
136
+ });
137
+ }
138
+ if (testJob?.["timeout-minutes"] !== 5) {
139
+ violations.push({
140
+ file: relPath,
141
+ message: `${relPath}: agent-kit self-CI Test job must declare timeout-minutes: 5.`,
142
+ });
143
+ }
144
+ }
115
145
  return violations;
116
146
  }
117
147
  export function auditCiTestPerf(rootDirectory = process.cwd()) {
@@ -23,6 +23,7 @@ export function auditCommandSurface(root) {
23
23
  const verifySkillPath = path.join(root, "catalog/agent/skills/verify/SKILL.md");
24
24
  const codexVerifyPath = path.join(root, "packages/codex-plugin/skills/verify/SKILL.md");
25
25
  const claudeVerifyPath = path.join(root, "packages/claude-plugin/skills/verify/SKILL.md");
26
+ const opencodeVerifyPath = path.join(root, "packages/opencode-plugin/skills/verify/SKILL.md");
26
27
  const hookGuidancePath = path.join(root, "src/hooks/pretool-guard/dev-routing.ts");
27
28
  const requiredFiles = [
28
29
  cliPath,
@@ -35,6 +36,7 @@ export function auditCommandSurface(root) {
35
36
  verifySkillPath,
36
37
  codexVerifyPath,
37
38
  claudeVerifyPath,
39
+ opencodeVerifyPath,
38
40
  hookGuidancePath,
39
41
  ];
40
42
  for (const file of requiredFiles) {
@@ -65,8 +67,8 @@ export function auditCommandSurface(root) {
65
67
  if (!contains(mcpPrWaitPath, "buildStatusSnapshot")) {
66
68
  findings.push(violation("src/mcp/tools/pr-wait.ts", "MCP ready wait does not use the shared snapshot evaluator"));
67
69
  }
68
- for (const file of [verifySkillPath, codexVerifyPath, claudeVerifyPath]) {
69
- if (!contains(file, "wp review gate <blueprint-slug> --target HEAD --json")) {
70
+ for (const file of [verifySkillPath, codexVerifyPath, claudeVerifyPath, opencodeVerifyPath]) {
71
+ if (!contains(file, "wp review gate <blueprint-slug> --purpose delivery --target HEAD --json")) {
70
72
  findings.push(violation(path.relative(root, file), "missing executable review-gate lifecycle guidance"));
71
73
  }
72
74
  }
@@ -0,0 +1,2 @@
1
+ import type { RepoAuditResult } from "./repo-guardrails.js";
2
+ export declare function auditHostSubstitutionRisk(rootDirectory?: string): RepoAuditResult;
@@ -0,0 +1,256 @@
1
+ import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
2
+ import path from "node:path";
3
+ import { parse as parseYaml } from "yaml";
4
+ import { readTrustedJsonFile } from "#shared-utils/read-json-file.js";
5
+ const AGENT_KIT_PACKAGE = "@webpresso/agent-kit";
6
+ const SCORECARD_REL = "docs/host-substitution-scorecard.md";
7
+ const BASELINES_REL = "maintainers/host-baselines";
8
+ const SCORECARD_HEADERS = [
9
+ "capability",
10
+ "class",
11
+ "substitution risk",
12
+ "observed native overlap",
13
+ "repo-owned invariant",
14
+ "independent verifier",
15
+ "blueprints",
16
+ "investment",
17
+ ];
18
+ const CLASS_VALUES = new Set(["core", "bridge", "utility"]);
19
+ const RISK_VALUES = new Set(["low", "medium", "high"]);
20
+ const INVESTMENT_VALUES = new Set(["invest", "maintain", "minimize"]);
21
+ const SURFACE_STATUS_VALUES = new Set(["full", "partial", "none", "in-progress"]);
22
+ function violation(file, message) {
23
+ return { file, message };
24
+ }
25
+ function isRecord(value) {
26
+ return typeof value === "object" && value !== null && !Array.isArray(value);
27
+ }
28
+ function packageName(root) {
29
+ try {
30
+ const parsed = readTrustedJsonFile(path.join(root, "package.json"));
31
+ return typeof parsed.name === "string" ? parsed.name : null;
32
+ }
33
+ catch {
34
+ return null;
35
+ }
36
+ }
37
+ function splitMarkdownRow(line) {
38
+ return line
39
+ .trim()
40
+ .replace(/^\|/u, "")
41
+ .replace(/\|$/u, "")
42
+ .split("|")
43
+ .map((cell) => cell.trim());
44
+ }
45
+ function parseScorecard(markdown) {
46
+ const lines = markdown.split(/\r?\n/u);
47
+ const rows = [];
48
+ const errors = [];
49
+ for (let index = 0; index < lines.length - 1; index += 1) {
50
+ const header = splitMarkdownRow(lines[index] ?? "").map((cell) => cell.toLowerCase());
51
+ if (header.join("|") !== SCORECARD_HEADERS.join("|"))
52
+ continue;
53
+ const separator = lines[index + 1] ?? "";
54
+ if (!/^\s*\|[\s:|-]+\|\s*$/u.test(separator)) {
55
+ errors.push("scorecard table header must be followed by a markdown separator row");
56
+ continue;
57
+ }
58
+ for (let rowIndex = index + 2; rowIndex < lines.length; rowIndex += 1) {
59
+ const line = lines[rowIndex] ?? "";
60
+ if (!line.trimStart().startsWith("|"))
61
+ break;
62
+ const cells = splitMarkdownRow(line);
63
+ if (cells.length !== SCORECARD_HEADERS.length) {
64
+ errors.push(`scorecard row ${rowIndex + 1} has ${cells.length} cells`);
65
+ continue;
66
+ }
67
+ rows.push(Object.fromEntries(SCORECARD_HEADERS.map((key, i) => [key, cells[i] ?? ""])));
68
+ }
69
+ return { rows, errors };
70
+ }
71
+ errors.push(`missing scorecard table with headers: ${SCORECARD_HEADERS.join(" | ")}`);
72
+ return { rows, errors };
73
+ }
74
+ function cleanToken(value) {
75
+ return value.replace(/^`|`$/gu, "").trim();
76
+ }
77
+ function splitCellList(value) {
78
+ return value
79
+ .split(/(?:<br\s*\/?>|,)/iu)
80
+ .map((item) => cleanToken(item))
81
+ .filter((item) => item.length > 0 && item !== "--" && item.toLowerCase() !== "none");
82
+ }
83
+ function scanBlueprintIds(root) {
84
+ const ids = new Set();
85
+ const base = path.join(root, "blueprints");
86
+ if (!existsSync(base))
87
+ return ids;
88
+ for (const status of ["draft", "planned", "in-progress", "completed", "archived", "parked"]) {
89
+ const statusDir = path.join(base, status);
90
+ if (!existsSync(statusDir))
91
+ continue;
92
+ for (const entry of readdirSync(statusDir)) {
93
+ if (entry.startsWith("."))
94
+ continue;
95
+ const absolute = path.join(statusDir, entry);
96
+ if (entry.endsWith(".md")) {
97
+ ids.add(`${status}/${entry.replace(/\.md$/u, "")}`);
98
+ continue;
99
+ }
100
+ if (statSync(absolute).isDirectory() && existsSync(path.join(absolute, "_overview.md"))) {
101
+ ids.add(`${status}/${entry}`);
102
+ }
103
+ }
104
+ }
105
+ return ids;
106
+ }
107
+ function activeBlueprintIds(root) {
108
+ const ids = new Set();
109
+ for (const id of scanBlueprintIds(root)) {
110
+ if (id.startsWith("planned/") || id.startsWith("in-progress/"))
111
+ ids.add(id);
112
+ }
113
+ return ids;
114
+ }
115
+ function validateScorecard(root) {
116
+ const scorecardPath = path.join(root, SCORECARD_REL);
117
+ if (!existsSync(scorecardPath)) {
118
+ return { checked: 1, violations: [violation(SCORECARD_REL, "scorecard file is missing")] };
119
+ }
120
+ const parsed = parseScorecard(readFileSync(scorecardPath, "utf8"));
121
+ const violations = parsed.errors.map((message) => violation(SCORECARD_REL, message));
122
+ const seenCapabilities = new Set();
123
+ const allBlueprintIds = scanBlueprintIds(root);
124
+ const active = activeBlueprintIds(root);
125
+ const activeCoverage = new Map();
126
+ for (const id of active)
127
+ activeCoverage.set(id, 0);
128
+ for (const [index, row] of parsed.rows.entries()) {
129
+ const rowLabel = `row ${index + 1}`;
130
+ const capability = cleanToken(row.capability ?? "");
131
+ if (!/^[a-z][a-z0-9-]*$/u.test(capability)) {
132
+ violations.push(violation(SCORECARD_REL, `${rowLabel}: capability must be a stable kebab-case id`));
133
+ }
134
+ else if (seenCapabilities.has(capability)) {
135
+ violations.push(violation(SCORECARD_REL, `${rowLabel}: duplicate capability ${capability}`));
136
+ }
137
+ seenCapabilities.add(capability);
138
+ const kind = cleanToken(row.class ?? "");
139
+ if (!CLASS_VALUES.has(kind)) {
140
+ violations.push(violation(SCORECARD_REL, `${rowLabel}: class must be core, bridge, or utility`));
141
+ }
142
+ const risk = cleanToken(row["substitution risk"] ?? "");
143
+ if (!RISK_VALUES.has(risk)) {
144
+ violations.push(violation(SCORECARD_REL, `${rowLabel}: substitution risk must be low, medium, or high`));
145
+ }
146
+ const investment = cleanToken(row.investment ?? "");
147
+ if (!INVESTMENT_VALUES.has(investment)) {
148
+ violations.push(violation(SCORECARD_REL, `${rowLabel}: investment must be invest, maintain, or minimize`));
149
+ }
150
+ for (const verifier of splitCellList(row["independent verifier"] ?? "")) {
151
+ if (!verifier.startsWith("repo:"))
152
+ continue;
153
+ const verifierPath = verifier.slice("repo:".length);
154
+ if (!existsSync(path.join(root, verifierPath))) {
155
+ violations.push(violation(SCORECARD_REL, `${rowLabel}: verifier path missing: ${verifier}`));
156
+ }
157
+ }
158
+ for (const blueprint of splitCellList(row.blueprints ?? "")) {
159
+ if (!/^(?:draft|planned|in-progress|completed|archived|parked)\/[a-z0-9][a-z0-9-]*$/u.test(blueprint)) {
160
+ violations.push(violation(SCORECARD_REL, `${rowLabel}: invalid blueprint reference ${blueprint}`));
161
+ continue;
162
+ }
163
+ if (!allBlueprintIds.has(blueprint)) {
164
+ violations.push(violation(SCORECARD_REL, `${rowLabel}: blueprint reference does not exist: ${blueprint}`));
165
+ }
166
+ if (activeCoverage.has(blueprint)) {
167
+ activeCoverage.set(blueprint, (activeCoverage.get(blueprint) ?? 0) + 1);
168
+ }
169
+ }
170
+ }
171
+ for (const [id, count] of activeCoverage) {
172
+ if (count !== 1) {
173
+ violations.push(violation(SCORECARD_REL, `active blueprint ${id} must be covered exactly once in scorecard blueprints column; found ${count}`));
174
+ }
175
+ }
176
+ return { checked: parsed.rows.length + activeCoverage.size, violations };
177
+ }
178
+ function validateHostBaseline(root, relPath) {
179
+ const violations = [];
180
+ const value = parseYaml(readFileSync(path.join(root, relPath), "utf8"));
181
+ if (!isRecord(value))
182
+ return [violation(relPath, "host baseline must be a YAML object")];
183
+ for (const key of ["host", "cli"]) {
184
+ if (typeof value[key] !== "string" || value[key].trim() === "") {
185
+ violations.push(violation(relPath, `${key} must be a non-empty string`));
186
+ }
187
+ }
188
+ for (const key of ["last_implemented", "last_observed"]) {
189
+ const record = value[key];
190
+ if (!isRecord(record)) {
191
+ violations.push(violation(relPath, `${key} must be an object`));
192
+ continue;
193
+ }
194
+ for (const child of ["version", "date"]) {
195
+ if (typeof record[child] !== "string" || record[child].trim() === "") {
196
+ violations.push(violation(relPath, `${key}.${child} must be a non-empty string`));
197
+ }
198
+ }
199
+ }
200
+ const sources = value.sources;
201
+ const docs = isRecord(sources) ? sources.docs : undefined;
202
+ if (!Array.isArray(docs) ||
203
+ docs.some((item) => typeof item !== "string" || !item.startsWith("https://"))) {
204
+ violations.push(violation(relPath, "sources.docs must be an array of https URLs"));
205
+ }
206
+ const surfaces = value.surfaces;
207
+ if (!isRecord(surfaces)) {
208
+ violations.push(violation(relPath, "surfaces must be an object"));
209
+ }
210
+ else {
211
+ for (const [name, raw] of Object.entries(surfaces)) {
212
+ if (!isRecord(raw)) {
213
+ violations.push(violation(relPath, `surfaces.${name} must be an object`));
214
+ continue;
215
+ }
216
+ if (typeof raw.status !== "string" || !SURFACE_STATUS_VALUES.has(raw.status)) {
217
+ violations.push(violation(relPath, `surfaces.${name}.status must be full, partial, none, or in-progress`));
218
+ }
219
+ if (typeof raw.notes !== "string" || raw.notes.trim() === "") {
220
+ violations.push(violation(relPath, `surfaces.${name}.notes must be a non-empty string`));
221
+ }
222
+ }
223
+ }
224
+ return violations;
225
+ }
226
+ function validateHostBaselines(root) {
227
+ const dir = path.join(root, BASELINES_REL);
228
+ if (!existsSync(dir)) {
229
+ return {
230
+ checked: 1,
231
+ violations: [violation(BASELINES_REL, "host baselines directory missing")],
232
+ };
233
+ }
234
+ const files = readdirSync(dir)
235
+ .filter((entry) => entry.endsWith(".yaml"))
236
+ .map((entry) => path.join(BASELINES_REL, entry));
237
+ return {
238
+ checked: files.length,
239
+ violations: files.flatMap((file) => validateHostBaseline(root, file)),
240
+ };
241
+ }
242
+ export function auditHostSubstitutionRisk(rootDirectory = process.cwd()) {
243
+ const root = path.resolve(rootDirectory);
244
+ const title = "Host substitution risk";
245
+ if (packageName(root) !== AGENT_KIT_PACKAGE)
246
+ return { ok: true, title, checked: 0, violations: [] };
247
+ const scorecard = validateScorecard(root);
248
+ const baselines = validateHostBaselines(root);
249
+ const violations = [...scorecard.violations, ...baselines.violations];
250
+ return {
251
+ ok: violations.length === 0,
252
+ title,
253
+ checked: scorecard.checked + baselines.checked,
254
+ violations,
255
+ };
256
+ }