@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,57 @@
1
+ export declare const WEBPRESSO_MCP_SERVER_NAME = "webpresso";
2
+ /**
3
+ * The single canonical launch spec for the webpresso MCP server. `name` is the
4
+ * server alias every host registers; `args` are appended after the resolved
5
+ * `wp` launcher; `env` is forwarded to the spawned process by every
6
+ * env-capable channel. `WP_SKIP_UPDATE_CHECK=1` keeps MCP startup from doing a
7
+ * network update probe on every host session.
8
+ */
9
+ export declare const WEBPRESSO_MCP_SPEC: {
10
+ readonly name: "webpresso";
11
+ readonly args: readonly ["mcp"];
12
+ readonly env: {
13
+ readonly WP_SKIP_UPDATE_CHECK: "1";
14
+ };
15
+ };
16
+ export type WebpressoMcpSpec = typeof WEBPRESSO_MCP_SPEC;
17
+ /**
18
+ * Resolve `binary` against PATH, returning the first executable candidate.
19
+ * Returns the candidate as an absolute path: relative PATH entries (`bin`,
20
+ * `.`) resolve against the current working directory — persisting them
21
+ * verbatim into host configs would resolve differently in later sessions.
22
+ * No realpath is taken, so generated configs stay anchored to a stable
23
+ * global-bin symlink rather than a version-pinned store path.
24
+ */
25
+ export declare function resolveExecutableOnPath(binary: string, env: NodeJS.ProcessEnv): string | null;
26
+ export interface WebpressoInstallProbe {
27
+ /** Test seam — override the candidate roots. Default: probe in fixed order. */
28
+ candidates?: readonly string[];
29
+ /** Test seam — resolve the stable global `wp` shim available on PATH. */
30
+ stableEntry?: () => string | null;
31
+ /** Test seam — verify that a PATH entry is the Webpresso command. */
32
+ stableEntryIsWebpresso?: (entryPath: string) => boolean;
33
+ }
34
+ /**
35
+ * Resolve the absolute path to webpresso's global `bin/wp` launcher on this
36
+ * machine. Probes the locations consumers use to install webpresso, in order
37
+ * of stability:
38
+ *
39
+ * 1. the stable global `wp` shim available on PATH
40
+ * 2. the currently executing `@webpresso/agent-kit` package
41
+ * 3. Claude plugin install — `~/.claude/plugins/cache/.../agent-kit/`
42
+ * 4. bun global — `~/.bun/install/global/node_modules/@webpresso/agent-kit/`
43
+ * Returns `null` when none of the candidates contain a usable `bin/wp`.
44
+ */
45
+ export declare function findWebpressoMcpEntry(probe?: WebpressoInstallProbe): string | null;
46
+ export declare function resolveWebpressoMcpEntry(probe?: WebpressoInstallProbe): {
47
+ entryPath: string | null;
48
+ checked: readonly string[];
49
+ };
50
+ export declare function isWebpressoWpEntry(entryPath: string): boolean;
51
+ /** Run a discovery probe with bounded output and SIGKILL escalation. */
52
+ export declare function runQuiet(cmd: string, args: readonly string[], timeoutMs?: number): string | null;
53
+ /** Launch tuple for the webpresso MCP server given a resolved `wp` launcher. */
54
+ export declare function agentKitMcpLaunchCommand(entryPath: string): {
55
+ command: string;
56
+ args: string[];
57
+ };
@@ -0,0 +1,273 @@
1
+ /**
2
+ * Canonical Webpresso MCP server spec + shared `wp` binary resolver.
3
+ *
4
+ * One source of truth for how every supported host launches the webpresso MCP
5
+ * server. Each host channel (Claude plugin manifest, Codex plugin manifest,
6
+ * Codex `config.toml`, OpenCode `opencode.json`, Grok `.grok/config.toml`)
7
+ * projects this spec into its own config dialect. The two committed plugin
8
+ * manifests reference the launcher via `${CLAUDE_PLUGIN_ROOT}`/`${PLUGIN_ROOT}`;
9
+ * the setup-time writers resolve an absolute `bin/wp` path via
10
+ * {@link findWebpressoMcpEntry}. Compare channels *semantically* (command
11
+ * basename, args, env) — never byte-for-byte.
12
+ */
13
+ import { accessSync, constants, existsSync, readFileSync, realpathSync, statSync } from "node:fs";
14
+ import { homedir } from "node:os";
15
+ import { dirname, isAbsolute, join, relative, resolve } from "node:path";
16
+ import { execFileSync } from "node:child_process";
17
+ import { fileURLToPath } from "node:url";
18
+ import { pathCandidates } from "#runtime/command-exists.js";
19
+ export const WEBPRESSO_MCP_SERVER_NAME = "webpresso";
20
+ /**
21
+ * The single canonical launch spec for the webpresso MCP server. `name` is the
22
+ * server alias every host registers; `args` are appended after the resolved
23
+ * `wp` launcher; `env` is forwarded to the spawned process by every
24
+ * env-capable channel. `WP_SKIP_UPDATE_CHECK=1` keeps MCP startup from doing a
25
+ * network update probe on every host session.
26
+ */
27
+ export const WEBPRESSO_MCP_SPEC = {
28
+ name: WEBPRESSO_MCP_SERVER_NAME,
29
+ args: ["mcp"],
30
+ env: { WP_SKIP_UPDATE_CHECK: "1" },
31
+ };
32
+ /**
33
+ * Resolve `binary` against PATH, returning the first executable candidate.
34
+ * Returns the candidate as an absolute path: relative PATH entries (`bin`,
35
+ * `.`) resolve against the current working directory — persisting them
36
+ * verbatim into host configs would resolve differently in later sessions.
37
+ * No realpath is taken, so generated configs stay anchored to a stable
38
+ * global-bin symlink rather than a version-pinned store path.
39
+ */
40
+ export function resolveExecutableOnPath(binary, env) {
41
+ for (const candidate of pathCandidates(binary, {
42
+ pathEnv: env.PATH,
43
+ pathExtEnv: env.PATHEXT,
44
+ })) {
45
+ try {
46
+ if (!statSync(candidate).isFile())
47
+ continue;
48
+ if (process.platform !== "win32")
49
+ accessSync(candidate, constants.X_OK);
50
+ return isAbsolute(candidate) ? candidate : resolve(candidate);
51
+ }
52
+ catch {
53
+ // Continue scanning PATH.
54
+ }
55
+ }
56
+ return null;
57
+ }
58
+ const WP_BIN_RELATIVE = join("bin", "wp");
59
+ const WP_LAUNCHER_REQUIRED_RELATIVES = [
60
+ WP_BIN_RELATIVE,
61
+ join("bin", "_run.js"),
62
+ join("bin", "runtime-lanes.js"),
63
+ join("bin", "runtime-manifest.json"),
64
+ ];
65
+ /**
66
+ * Resolve the absolute path to webpresso's global `bin/wp` launcher on this
67
+ * machine. Probes the locations consumers use to install webpresso, in order
68
+ * of stability:
69
+ *
70
+ * 1. the stable global `wp` shim available on PATH
71
+ * 2. the currently executing `@webpresso/agent-kit` package
72
+ * 3. Claude plugin install — `~/.claude/plugins/cache/.../agent-kit/`
73
+ * 4. bun global — `~/.bun/install/global/node_modules/@webpresso/agent-kit/`
74
+ * Returns `null` when none of the candidates contain a usable `bin/wp`.
75
+ */
76
+ export function findWebpressoMcpEntry(probe = {}) {
77
+ return resolveWebpressoMcpEntry(probe).entryPath;
78
+ }
79
+ function isUsableWebpressoMcpRoot(root) {
80
+ return WP_LAUNCHER_REQUIRED_RELATIVES.every((relativePath) => existsSync(join(root, relativePath)));
81
+ }
82
+ export function resolveWebpressoMcpEntry(probe = {}) {
83
+ const checked = [];
84
+ if (probe.candidates === undefined) {
85
+ const stableEntry = probe.stableEntry !== undefined
86
+ ? probe.stableEntry()
87
+ : resolveExecutableOnPath("wp", process.env);
88
+ if (stableEntry) {
89
+ checked.push(stableEntry);
90
+ const packageWp = packageWpFromAdjacentNpmCmdShim(stableEntry);
91
+ if (packageWp !== null)
92
+ return { entryPath: packageWp, checked };
93
+ const verifierAllows = probe.stableEntryIsWebpresso?.(stableEntry) ?? true;
94
+ if (verifierAllows && isWebpressoWpEntry(stableEntry)) {
95
+ return { entryPath: stableEntry, checked };
96
+ }
97
+ }
98
+ }
99
+ const candidateGroups = probe.candidates !== undefined ? [probe.candidates] : [defaultFixedCandidates()];
100
+ for (const candidates of candidateGroups) {
101
+ for (const root of candidates) {
102
+ if (!root)
103
+ continue;
104
+ checked.push(root);
105
+ const wpBin = join(root, WP_BIN_RELATIVE);
106
+ if (isTrustedWebpressoPackageRoot(root))
107
+ return { entryPath: wpBin, checked };
108
+ }
109
+ }
110
+ return { entryPath: null, checked };
111
+ }
112
+ function defaultFixedCandidates() {
113
+ const home = process.env.HOME || homedir();
114
+ const packageRoot = currentAgentKitPackageRoot();
115
+ const claudePlugin = join(home, ".claude", "plugins", "cache", "webpresso", "agent-kit");
116
+ const bunGlobal = join(home, ".bun", "install", "global", "node_modules", "@webpresso", "agent-kit");
117
+ return [packageRoot ?? "", claudePlugin, bunGlobal];
118
+ }
119
+ function currentAgentKitPackageRoot() {
120
+ let current = dirname(fileURLToPath(import.meta.url));
121
+ for (let depth = 0; depth < 12; depth += 1) {
122
+ const packagePath = join(current, "package.json");
123
+ if (existsSync(packagePath)) {
124
+ try {
125
+ const pkg = JSON.parse(readFileSync(packagePath, "utf8"));
126
+ if (pkg.name === "@webpresso/agent-kit")
127
+ return current;
128
+ }
129
+ catch {
130
+ return null;
131
+ }
132
+ }
133
+ const parent = dirname(current);
134
+ if (parent === current)
135
+ break;
136
+ current = parent;
137
+ }
138
+ return null;
139
+ }
140
+ function isUsableWebpressoPackageRoot(packageRoot) {
141
+ try {
142
+ const pkg = JSON.parse(readFileSync(join(packageRoot, "package.json"), "utf8"));
143
+ return pkg.name === "@webpresso/agent-kit" && isUsableWebpressoMcpRoot(packageRoot);
144
+ }
145
+ catch {
146
+ return false;
147
+ }
148
+ }
149
+ function isPathInsideOrEqual(parent, candidate) {
150
+ const parentRoot = realpathSync(parent);
151
+ const candidateRoot = realpathSync(candidate);
152
+ const rel = relative(resolve(parentRoot), resolve(candidateRoot));
153
+ return rel === "" || (!rel.startsWith("..") && !isAbsolute(rel));
154
+ }
155
+ function isTrustedWebpressoPackageRoot(packageRoot) {
156
+ try {
157
+ if (!isUsableWebpressoPackageRoot(packageRoot))
158
+ return false;
159
+ const realPackageRoot = realpathSync(packageRoot);
160
+ const sourceRoot = currentAgentKitPackageRoot();
161
+ if (sourceRoot !== null && realPackageRoot === realpathSync(sourceRoot))
162
+ return true;
163
+ return trustedHomeInstallPackageRoots().some((anchor) => existsSync(anchor) && isPathInsideOrEqual(anchor, realPackageRoot));
164
+ }
165
+ catch {
166
+ return false;
167
+ }
168
+ }
169
+ function trustedHomeInstallPackageRoots() {
170
+ const home = process.env.HOME || homedir();
171
+ return [
172
+ join(home, ".claude", "plugins", "cache", "webpresso", "agent-kit"),
173
+ join(home, ".bun", "install", "global", "node_modules", "@webpresso", "agent-kit"),
174
+ ];
175
+ }
176
+ function packageWpFromAdjacentNpmCmdShim(entryPath) {
177
+ if (!entryPath.toLowerCase().endsWith(".cmd"))
178
+ return null;
179
+ const packageRoot = join(dirname(entryPath), "node_modules", "@webpresso", "agent-kit");
180
+ return isTrustedWebpressoPackageRoot(packageRoot) ? join(packageRoot, "bin", "wp") : null;
181
+ }
182
+ export function isWebpressoWpEntry(entryPath) {
183
+ try {
184
+ if (entryPath.toLowerCase().endsWith(".cmd"))
185
+ return false;
186
+ // Validate package ownership from the symlink target. Executing a binary
187
+ // and trusting its self-reported version lets any same-named PATH entry
188
+ // impersonate Webpresso.
189
+ const resolvedEntry = realpathSync(entryPath);
190
+ const packageRoot = dirname(dirname(resolvedEntry));
191
+ if (resolvedEntry !== join(packageRoot, WP_BIN_RELATIVE))
192
+ return false;
193
+ return isTrustedWebpressoPackageRoot(packageRoot);
194
+ }
195
+ catch {
196
+ return false;
197
+ }
198
+ }
199
+ const PROBE_SUPERVISOR = String.raw `
200
+ const { spawn } = require("node:child_process");
201
+ const input = JSON.parse(process.argv[1]);
202
+ const child = spawn(input.cmd, input.args, {
203
+ detached: process.platform !== "win32",
204
+ env: { ...process.env, WP_SKIP_UPDATE_CHECK: "1" },
205
+ stdio: ["ignore", "pipe", "ignore"],
206
+ windowsHide: true,
207
+ });
208
+ let output = "";
209
+ let timedOut = false;
210
+ let finished = false;
211
+ const signal = (name) => {
212
+ try {
213
+ if (process.platform !== "win32" && child.pid) process.kill(-child.pid, name);
214
+ else child.kill(name);
215
+ } catch {}
216
+ };
217
+ const finish = (value) => {
218
+ if (finished) return;
219
+ finished = true;
220
+ clearTimeout(deadline);
221
+ clearTimeout(forceKill);
222
+ process.stdout.write(JSON.stringify(value));
223
+ };
224
+ let forceKill;
225
+ const deadline = setTimeout(() => {
226
+ timedOut = true;
227
+ signal("SIGTERM");
228
+ forceKill = setTimeout(() => signal("SIGKILL"), input.killGraceMs);
229
+ }, input.timeoutMs);
230
+ child.stdout.setEncoding("utf8");
231
+ child.stdout.on("data", (chunk) => {
232
+ output += chunk;
233
+ if (Buffer.byteLength(output) > input.maxBytes) {
234
+ timedOut = true;
235
+ output = "";
236
+ child.stdout.destroy();
237
+ signal("SIGKILL");
238
+ }
239
+ });
240
+ child.on("error", () => finish(null));
241
+ child.on("exit", (code) => finish(!timedOut && code === 0 ? output.trim() || null : null));
242
+ `;
243
+ /** Run a discovery probe with bounded output and SIGKILL escalation. */
244
+ export function runQuiet(cmd, args, timeoutMs = 1_500) {
245
+ try {
246
+ const output = execFileSync(process.execPath, [
247
+ "-e",
248
+ PROBE_SUPERVISOR,
249
+ JSON.stringify({
250
+ cmd,
251
+ args: [...args],
252
+ timeoutMs,
253
+ killGraceMs: 100,
254
+ maxBytes: 64 * 1024,
255
+ }),
256
+ ], {
257
+ encoding: "utf8",
258
+ env: { ...process.env, WP_SKIP_UPDATE_CHECK: "1" },
259
+ stdio: ["ignore", "pipe", "ignore"],
260
+ timeout: timeoutMs + 1_000,
261
+ // JSON escaping can expand the supervisor's already-bounded 64 KiB payload.
262
+ maxBuffer: 512 * 1024,
263
+ });
264
+ return JSON.parse(output);
265
+ }
266
+ catch {
267
+ return null;
268
+ }
269
+ }
270
+ /** Launch tuple for the webpresso MCP server given a resolved `wp` launcher. */
271
+ export function agentKitMcpLaunchCommand(entryPath) {
272
+ return { command: entryPath, args: [...WEBPRESSO_MCP_SPEC.args] };
273
+ }
@@ -4,6 +4,7 @@ export interface MergeOptions {
4
4
  overwrite?: boolean;
5
5
  dryRun?: boolean;
6
6
  ownership?: MergeOwnership;
7
+ repoRoot?: string;
7
8
  }
8
9
  export interface MergeResult {
9
10
  targetPath: string;
@@ -7,7 +7,33 @@
7
7
  * only.
8
8
  */
9
9
  import { existsSync, mkdirSync, readFileSync, writeFileSync, statSync, readdirSync } from "node:fs";
10
- import { dirname, join } from "node:path";
10
+ import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
11
+ import { convergeUnitFromTarget } from "./convergence.js";
12
+ import { resolveUntrackedCollision } from "./untracked-collision.js";
13
+ function guardUntrackedOverwrite(targetPath, incoming, opts) {
14
+ if (!opts.repoRoot)
15
+ return;
16
+ const repoRoot = resolve(opts.repoRoot);
17
+ const target = resolve(targetPath);
18
+ const repoRelative = relative(repoRoot, target);
19
+ if (repoRelative.length === 0 ||
20
+ repoRelative === ".." ||
21
+ repoRelative.startsWith(`..${sep}`) ||
22
+ isAbsolute(repoRelative)) {
23
+ return;
24
+ }
25
+ const result = resolveUntrackedCollision({
26
+ unit: convergeUnitFromTarget(repoRoot, target, {
27
+ action: "modify",
28
+ scope: "repo-committed",
29
+ reason: "setup merge target",
30
+ }),
31
+ desiredContent: incoming,
32
+ force: opts.overwrite,
33
+ });
34
+ if (!result.ok)
35
+ throw new Error(result.error);
36
+ }
11
37
  export function writeFileMerged(targetPath, incoming, opts = {}) {
12
38
  const exists = existsSync(targetPath);
13
39
  const existingContent = exists ? readFileSync(targetPath, "utf8") : null;
@@ -25,6 +51,7 @@ export function writeFileMerged(targetPath, incoming, opts = {}) {
25
51
  if (shouldOverwrite) {
26
52
  if (opts.dryRun)
27
53
  return { targetPath, action: "skipped-dry" };
54
+ guardUntrackedOverwrite(targetPath, incoming, opts);
28
55
  writeFileSync(targetPath, incoming);
29
56
  return { targetPath, action: "overwritten" };
30
57
  }
@@ -92,6 +119,8 @@ export function patchJsonFile(targetPath, patcher, opts = {}) {
92
119
  if (opts.dryRun) {
93
120
  return { targetPath, action: "skipped-dry" };
94
121
  }
122
+ if (exists)
123
+ guardUntrackedOverwrite(targetPath, incoming, opts);
95
124
  mkdirSync(dirname(targetPath), { recursive: true });
96
125
  writeFileSync(targetPath, incoming);
97
126
  return { targetPath, action: exists ? "overwritten" : "created" };
@@ -8,6 +8,13 @@ export interface ResolveAgentKitPackageRootOptions {
8
8
  readonly pathExtEnv?: string;
9
9
  readonly platform?: NodeJS.Platform;
10
10
  readonly requireCatalog?: boolean;
11
+ /**
12
+ * Optional cwd anchor used as a last-resort walk start when module/argv/PATH
13
+ * anchors cannot locate a root (virtual bunfs/vite moduleUrl, vitest entry).
14
+ * Not defaulted to process.cwd() so hermetic tests stay isolated from the
15
+ * monorepo checkout the test process runs in.
16
+ */
17
+ readonly cwd?: string;
11
18
  }
12
19
  export declare function isAgentKitPackageRoot(dir: string, options?: {
13
20
  readonly requireCatalog?: boolean;
@@ -84,13 +84,15 @@ export function isAgentKitPackageRoot(dir, options = {}) {
84
84
  return false;
85
85
  return hasStableAgentKitRootMarker(dir);
86
86
  }
87
+ /** Max parent-walk hops from a start path. Deep pnpm/vitest/store layouts exceed 10. */
88
+ const PACKAGE_ROOT_WALK_MAX_DEPTH = 32;
87
89
  export function findAgentKitPackageRoot(startPath, options = {}) {
88
90
  if (!isRunnablePath(startPath))
89
91
  return null;
90
92
  const platform = options.platform ?? process.platform;
91
93
  const pathModule = pathModuleForPlatform(platform);
92
94
  let dir = pathModule.dirname(startPath);
93
- for (let depth = 0; depth < 10; depth++) {
95
+ for (let depth = 0; depth < PACKAGE_ROOT_WALK_MAX_DEPTH; depth++) {
94
96
  if (isAgentKitPackageRoot(dir, options))
95
97
  return dir;
96
98
  const parent = pathModule.dirname(dir);
@@ -113,7 +115,13 @@ export function resolveAgentKitPackageRoot(options = {}) {
113
115
  platform,
114
116
  });
115
117
  const requireCatalog = options.requireCatalog;
116
- for (const startPath of [modulePath, argv1, execPath, pathResolvedBin, argv0]) {
118
+ // Opt-in cwd last: monorepo/test hosts often run from the package root when
119
+ // moduleUrl is virtual (bunfs/vite) or argv points outside the tree (vitest).
120
+ // Callers that want the fallback pass `cwd` explicitly (see hook launchers).
121
+ const cwdStart = typeof options.cwd === "string" && options.cwd.length > 0
122
+ ? join(options.cwd, "package.json")
123
+ : undefined;
124
+ for (const startPath of [modulePath, argv1, execPath, pathResolvedBin, argv0, cwdStart]) {
117
125
  const root = findAgentKitPackageRoot(startPath ?? undefined, { requireCatalog, platform });
118
126
  if (root)
119
127
  return root;
@@ -10,9 +10,6 @@ export declare const PLUGIN_CACHE_TARGETS: readonly [{
10
10
  }, {
11
11
  readonly host: "opencode";
12
12
  readonly rootParts: readonly [".config", "opencode", "plugins", "cache"];
13
- }, {
14
- readonly host: "windsurf";
15
- readonly rootParts: readonly [".windsurf", "plugins", "cache"];
16
13
  }, {
17
14
  readonly host: "agents";
18
15
  readonly rootParts: readonly [".agents", "plugins", "cache"];
@@ -6,7 +6,6 @@ export const PLUGIN_CACHE_TARGETS = [
6
6
  { host: "codex", rootParts: [".codex", "plugins", "cache"] },
7
7
  { host: "opencode", rootParts: [".opencode", "plugins", "cache"] },
8
8
  { host: "opencode", rootParts: [".config", "opencode", "plugins", "cache"] },
9
- { host: "windsurf", rootParts: [".windsurf", "plugins", "cache"] },
10
9
  { host: "agents", rootParts: [".agents", "plugins", "cache"] },
11
10
  { host: "factory", rootParts: [".factory", "plugins", "cache"] },
12
11
  ];
@@ -1,7 +1,7 @@
1
1
  import type { AgentkitConfig } from "./config.js";
2
2
  import type { ConsumerContext } from "./detect-consumer.js";
3
3
  import { type MergeOptions, type MergeResult } from "./merge.js";
4
- export declare const AGENTS_MD_MAX_BYTES = 8100;
4
+ export declare const AGENTS_MD_MAX_BYTES = 8180;
5
5
  export declare function renderRepositoryMap(consumer: ConsumerContext): string;
6
6
  export declare function renderTechStack(consumer: ConsumerContext): string;
7
7
  export interface ScaffoldAgentsMdInput {
@@ -29,9 +29,15 @@ const TECH_STACK_RULES = [
29
29
  { dep: /^zod$/, label: "Zod" },
30
30
  { dep: /^typescript$/, label: "TypeScript" },
31
31
  ];
32
- // Kept under 8 KB (8192 bytes); headroom above 8_000 absorbs the longer
33
- // `wp_ultragoal_*` native tool names in the Codex routing surface.
34
- export const AGENTS_MD_MAX_BYTES = 8_100;
32
+ // Kept under the 8 KB (8192-byte) hard ceiling. The rendered template embeds
33
+ // the full `native_tool_names` registry list, so this soft target tracks the
34
+ // tool count: adding an MCP tool grows the list by ~15-17 bytes. Bumped
35
+ // 8_100 -> 8_180 for the four-tool batch (wp_ci_preflight, wp_session_id,
36
+ // wp_session_info, wp_review_run), which renders ~8_160 — still 30+ bytes under
37
+ // 8_192. The 8_192 ceiling is the real guardrail; the next addition that would
38
+ // breach it should trigger a conscious restructure (or exclude the
39
+ // auto-generated tool list from the prose budget), not another reflexive bump.
40
+ export const AGENTS_MD_MAX_BYTES = 8_180;
35
41
  export function renderRepositoryMap(consumer) {
36
42
  const packages = consumer.workspacePackages;
37
43
  if (packages.length === 0) {
@@ -14,6 +14,26 @@ interface PackageJsonLike {
14
14
  [key: string]: unknown;
15
15
  }
16
16
  export declare function readSetupWpActionRef(metaUrl: string): string;
17
+ /**
18
+ * Idempotent, inject-by-content-absence repair for the managed `wp audit
19
+ * guardrails` CI step. On every `wp setup`, re-inserts the step into any
20
+ * wp-enabled quality workflow whose content lacks a guardrails invocation, so a
21
+ * consumer who deleted the step (with or without its comments) gets it back —
22
+ * and therefore keeps inheriting new guardrails (e.g. typescript-version) on
23
+ * their next agent-kit bump. Returns the content unchanged when the step is
24
+ * already present, the workflow is not a wp quality pipeline, no test step
25
+ * anchor exists, or `repoAlreadyWired` is true (guardrails already run in a
26
+ * sibling workflow of the same repo — see `migrateConsumerWorkflows`, and
27
+ * tech-debt h-017: without this check a consumer who deliberately hosts
28
+ * guardrails in a dedicated `guardrails.yml` got a duplicate step injected
29
+ * into `ci.yml` on every `wp setup`).
30
+ *
31
+ * `hasGuardrailsInvocation`/`findLiveTestRunAnchor` come from the shared
32
+ * `ci-guardrails-detection` module also used by `auditCiGuardrailsWiring`, so
33
+ * the audit and this injector can never disagree about what counts as a wired
34
+ * guardrails step or a live test-run anchor (tech-debt h-016).
35
+ */
36
+ export declare function ensureGuardrailsCiStep(content: string, repoAlreadyWired?: boolean): string;
17
37
  export declare function readAgentConfigVersion(metaUrl: string): string;
18
38
  export declare function collectRuntimeContractGuidance(packageJson: PackageJsonLike | null | undefined): RuntimeContractGuidance;
19
39
  export declare const BASE_KIT_QUALITY_TARGETS: string[];
@@ -3,6 +3,7 @@ import { dirname, join } from "node:path";
3
3
  import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
4
4
  import { writeFileMerged } from "./merge.js";
5
5
  import { resolveAgentKitPackageRoot } from "./package-root.js";
6
+ import { findLiveTestRunAnchor, hasGuardrailsInvocation } from "#audit/ci-guardrails-detection.js";
6
7
  import { readPackageVersion } from "#cli/utils";
7
8
  import { SETUP_WP_ACTION_REF } from "#build/release-action-ref.js";
8
9
  const AUTHORING_TIME_DEPENDENCIES = [
@@ -162,16 +163,71 @@ function migrateWorkflowContent(content, actionRef, version) {
162
163
  })
163
164
  .join("\n");
164
165
  }
166
+ // A workflow is a wp-enabled quality pipeline when it both installs the `wp`
167
+ // runtime and runs the default test suite. Gating on a wp-runtime anchor keeps
168
+ // injection off non-wp workflows (a reusable-workflow release.yml, or
169
+ // agent-kit's own source-built CI) that must never get a consumer step. Two
170
+ // install shapes count as the anchor: the `setup-wp` GitHub Action, and a
171
+ // global npm/pnpm/vp install of `@webpresso/agent-kit` — the documented
172
+ // third-party quick start for consumers that don't use the action (see
173
+ // `package-conventions.md`; e.g. ozby/edge-matte's ci.yml).
174
+ const SETUP_WP_MARKER = "setup-wp@";
175
+ const GLOBAL_AGENT_KIT_INSTALL = /^(?=.*\b(?:npm|pnpm|vp)\s+(?:install|i|add)\b)(?=.*(?:-g\b|--global\b))(?=.*@webpresso\/agent-kit\b).*$/imu;
176
+ function hasWpRuntimeAnchor(content) {
177
+ return content.includes(SETUP_WP_MARKER) || GLOBAL_AGENT_KIT_INSTALL.test(content);
178
+ }
179
+ /**
180
+ * Idempotent, inject-by-content-absence repair for the managed `wp audit
181
+ * guardrails` CI step. On every `wp setup`, re-inserts the step into any
182
+ * wp-enabled quality workflow whose content lacks a guardrails invocation, so a
183
+ * consumer who deleted the step (with or without its comments) gets it back —
184
+ * and therefore keeps inheriting new guardrails (e.g. typescript-version) on
185
+ * their next agent-kit bump. Returns the content unchanged when the step is
186
+ * already present, the workflow is not a wp quality pipeline, no test step
187
+ * anchor exists, or `repoAlreadyWired` is true (guardrails already run in a
188
+ * sibling workflow of the same repo — see `migrateConsumerWorkflows`, and
189
+ * tech-debt h-017: without this check a consumer who deliberately hosts
190
+ * guardrails in a dedicated `guardrails.yml` got a duplicate step injected
191
+ * into `ci.yml` on every `wp setup`).
192
+ *
193
+ * `hasGuardrailsInvocation`/`findLiveTestRunAnchor` come from the shared
194
+ * `ci-guardrails-detection` module also used by `auditCiGuardrailsWiring`, so
195
+ * the audit and this injector can never disagree about what counts as a wired
196
+ * guardrails step or a live test-run anchor (tech-debt h-016).
197
+ */
198
+ export function ensureGuardrailsCiStep(content, repoAlreadyWired = false) {
199
+ if (hasGuardrailsInvocation(content))
200
+ return content;
201
+ if (repoAlreadyWired)
202
+ return content;
203
+ if (!hasWpRuntimeAnchor(content))
204
+ return content;
205
+ const anchorStepStart = findLiveTestRunAnchor(content);
206
+ if (anchorStepStart === -1)
207
+ return content;
208
+ const lines = content.split("\n");
209
+ // Safe: anchorStepStart is an index returned by findLiveTestRunAnchor over
210
+ // this same content.split("\n"), so it is always within lines' bounds here.
211
+ const indent = lines[anchorStepStart].match(/^(\s*)/u)?.[1] ?? "";
212
+ lines.splice(anchorStepStart, 0, `${indent}- name: Run wp audit guardrails`, `${indent} run: wp audit guardrails`);
213
+ return lines.join("\n");
214
+ }
165
215
  function migrateConsumerWorkflows(repoRoot, actionRef, version, options) {
166
216
  const workflowDir = join(repoRoot, ".github", "workflows");
167
217
  if (!existsSync(workflowDir))
168
218
  return [];
169
- return readdirSync(workflowDir)
170
- .filter((name) => name.endsWith(".yml") || name.endsWith(".yaml"))
171
- .map((name) => {
219
+ const names = readdirSync(workflowDir).filter((name) => name.endsWith(".yml") || name.endsWith(".yaml"));
220
+ // Read every workflow's original content once, up front, so per-file
221
+ // injection below can see the REPO-LEVEL wiring state (mirrors how
222
+ // `auditCiGuardrailsWiring` reasons across all workflows) instead of only
223
+ // the file it happens to be looking at. Without this, a consumer who
224
+ // deliberately hosts guardrails in a dedicated `guardrails.yml` gets a
225
+ // duplicate step injected into `ci.yml` on every `wp setup` (h-017).
226
+ const entries = names.map((name) => [name, readFileSync(join(workflowDir, name), "utf8")]);
227
+ const repoAlreadyWired = entries.some(([, content]) => hasGuardrailsInvocation(content));
228
+ return entries.map(([name, existing]) => {
172
229
  const targetPath = join(workflowDir, name);
173
- const existing = readFileSync(targetPath, "utf8");
174
- const migrated = migrateWorkflowContent(existing, actionRef, version);
230
+ const migrated = ensureGuardrailsCiStep(migrateWorkflowContent(existing, actionRef, version), repoAlreadyWired);
175
231
  if (migrated === existing)
176
232
  return { targetPath, action: "identical" };
177
233
  if (options.dryRun)
@@ -1,14 +1,20 @@
1
1
  /**
2
2
  * Grok Build native project hooks emitter.
3
3
  *
4
- * Writes a project-local `.grok/hooks.json` surface with managed commands
5
- * labeled `WP_HOOK_HOST=grok`. Claude freeload remains the fallback delivery
6
- * path; this is the first-class native project surface for doctor/setup.
4
+ * Grok reads project hook configs from `.grok/hooks/*.json` a near-clone of
5
+ * the Claude Code hook contract (stdin event JSON incl. hookEventName/cwd,
6
+ * regex matchers, exit code 2 = deny, JSON stdout for decisions). We write one
7
+ * managed file, `.grok/hooks/webpresso.json`, with commands labeled
8
+ * `WP_HOOK_HOST=grok`. Hooks are trust-gated: the user must run `/hooks-trust`
9
+ * once in Grok before they fire (fail-open on timeout).
10
+ *
11
+ * Managed events are the intersection of WP_HOOK_SPECS with Grok's documented
12
+ * event set — notably Grok has no PermissionRequest event (it has
13
+ * PermissionDenied instead), so that spec is skipped.
7
14
  */
8
- export declare const GROK_HOOKS_RELATIVE_PATH = ".grok/hooks.json";
15
+ export declare const GROK_HOOKS_DIR_RELATIVE_PATH = ".grok/hooks";
16
+ export declare const GROK_HOOKS_RELATIVE_PATH = ".grok/hooks/webpresso.json";
9
17
  export type GrokHooksDocument = {
10
- readonly version: 1;
11
- readonly host: "grok";
12
18
  readonly hooks: Readonly<Record<string, readonly {
13
19
  readonly matcher?: string;
14
20
  readonly hooks: readonly {
@@ -19,8 +25,8 @@ export type GrokHooksDocument = {
19
25
  }[]>>;
20
26
  };
21
27
  /**
22
- * Build managed Grok hook groups. PreToolUse uses a broad Bash|Write|Edit matcher;
23
- * PermissionRequest and SessionStart are included without matchers.
28
+ * Build the managed Grok hook document (Claude-contract wrapped shape:
29
+ * `{ "hooks": { Event: [ { matcher?, hooks: [...] } ] } }`).
24
30
  */
25
31
  export declare function buildGrokHookDocument(input: {
26
32
  readonly resolveBin: (name: string) => string;