@webpresso/agent-kit 3.3.2 → 3.3.4

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 (167) hide show
  1. package/THIRD-PARTY-NOTICES.md +12 -10
  2. package/catalog/AGENTS.md.tpl +3 -5
  3. package/catalog/agent/rules/changeset-release.md +15 -4
  4. package/catalog/agent/rules/managed-tool-binaries.md +67 -0
  5. package/catalog/agent/rules/parallel-orchestration-quota.md +99 -1
  6. package/catalog/agent/rules/pre-implementation.md +36 -20
  7. package/catalog/agent/rules/rtk-routing.md +22 -16
  8. package/catalog/agent/skills/autopilot/SKILL.md +4 -3
  9. package/catalog/agent/skills/ultragoal/SKILL.md +11 -9
  10. package/catalog/agent/skills/verify/SKILL.md +12 -12
  11. package/catalog/compose/admin-blocks-registry.json +659 -0
  12. package/dist/esm/audit/hook-surface.js +5 -1
  13. package/dist/esm/audit/repo-guardrails.d.ts +8 -0
  14. package/dist/esm/audit/repo-guardrails.js +71 -1
  15. package/dist/esm/blueprint/core/parser.js +40 -16
  16. package/dist/esm/blueprint/core/validation/criteria.js +2 -1
  17. package/dist/esm/blueprint/execution/artifacts.js +8 -4
  18. package/dist/esm/blueprint/execution/frontmatter-keys.d.ts +17 -0
  19. package/dist/esm/blueprint/execution/frontmatter-keys.js +24 -0
  20. package/dist/esm/blueprint/execution/metadata.js +8 -4
  21. package/dist/esm/blueprint/lifecycle/audit.d.ts +21 -0
  22. package/dist/esm/blueprint/lifecycle/audit.js +89 -3
  23. package/dist/esm/blueprint/lifecycle/review-provenance.js +148 -40
  24. package/dist/esm/blueprint/lifecycle/rollout-anchor.d.ts +22 -0
  25. package/dist/esm/blueprint/lifecycle/rollout-anchor.js +37 -1
  26. package/dist/esm/blueprint/markdown/blocked-line.d.ts +49 -0
  27. package/dist/esm/blueprint/markdown/blocked-line.js +63 -0
  28. package/dist/esm/blueprint/markdown/helpers.js +10 -4
  29. package/dist/esm/blueprint/tracked-document/parser.js +3 -3
  30. package/dist/esm/blueprint/trust/gate-row.d.ts +55 -0
  31. package/dist/esm/blueprint/trust/gate-row.js +113 -0
  32. package/dist/esm/blueprint/trust/promotion.d.ts +17 -0
  33. package/dist/esm/blueprint/trust/promotion.js +19 -19
  34. package/dist/esm/build/atomic-file-copy.d.ts +32 -0
  35. package/dist/esm/build/atomic-file-copy.js +44 -0
  36. package/dist/esm/build/cli-mcp-parity.js +3 -2
  37. package/dist/esm/build/package-manifest.js +7 -2
  38. package/dist/esm/build/vendor-oxlint-plugins.d.ts +8 -0
  39. package/dist/esm/build/vendor-oxlint-plugins.js +53 -0
  40. package/dist/esm/cli/commands/audit-core.js +9 -1
  41. package/dist/esm/cli/commands/audit.js +16 -5
  42. package/dist/esm/cli/commands/blueprint/mutations.d.ts +15 -0
  43. package/dist/esm/cli/commands/blueprint/mutations.js +8 -2
  44. package/dist/esm/cli/commands/compile.d.ts +13 -0
  45. package/dist/esm/cli/commands/compile.js +182 -62
  46. package/dist/esm/cli/commands/format.js +17 -1
  47. package/dist/esm/cli/commands/gain/index.js +10 -3
  48. package/dist/esm/cli/commands/init/gitignore-patcher.js +6 -0
  49. package/dist/esm/cli/commands/init/index.js +29 -48
  50. package/dist/esm/cli/commands/init/mcp-spec.d.ts +54 -4
  51. package/dist/esm/cli/commands/init/mcp-spec.js +126 -22
  52. package/dist/esm/cli/commands/init/merge.js +30 -10
  53. package/dist/esm/cli/commands/init/scaffold-agents-md.js +5 -7
  54. package/dist/esm/cli/commands/init/scaffolders/codex-mcp/index.d.ts +56 -31
  55. package/dist/esm/cli/commands/init/scaffolders/codex-mcp/index.js +180 -43
  56. package/dist/esm/cli/commands/init/scaffolders/rtk/index.d.ts +17 -5
  57. package/dist/esm/cli/commands/init/scaffolders/rtk/index.js +56 -75
  58. package/dist/esm/cli/commands/init/scaffolders/runtime-check/index.js +7 -6
  59. package/dist/esm/cli/commands/init/untracked-collision.d.ts +12 -1
  60. package/dist/esm/cli/commands/init/untracked-collision.js +23 -1
  61. package/dist/esm/cli/commands/package-manager.js +8 -2
  62. package/dist/esm/cli/commands/pr-help.js +1 -1
  63. package/dist/esm/cli/commands/pr.js +18 -4
  64. package/dist/esm/cli/commands/review.js +60 -6
  65. package/dist/esm/cli/commands/worktree/router-dispatch.d.ts +24 -2
  66. package/dist/esm/cli/commands/worktree/router-dispatch.js +80 -48
  67. package/dist/esm/cli/commands/worktree/router.js +5 -2
  68. package/dist/esm/cli/direct-provider-launch.js +12 -4
  69. package/dist/esm/compiler/flatten.d.ts +10 -1
  70. package/dist/esm/compiler/flatten.js +14 -4
  71. package/dist/esm/compose/admin-blocks-registry-integrity.d.ts +22 -0
  72. package/dist/esm/compose/admin-blocks-registry-integrity.js +90 -0
  73. package/dist/esm/compose/compose.d.ts +46 -0
  74. package/dist/esm/compose/compose.js +250 -0
  75. package/dist/esm/compose/escape-html.d.ts +5 -0
  76. package/dist/esm/compose/escape-html.js +12 -0
  77. package/dist/esm/compose/ops-report-render.d.ts +5 -0
  78. package/dist/esm/compose/ops-report-render.js +71 -0
  79. package/dist/esm/compose/ops-report-schema.d.ts +42 -0
  80. package/dist/esm/compose/ops-report-schema.js +60 -0
  81. package/dist/esm/compose/preview.d.ts +14 -0
  82. package/dist/esm/compose/preview.js +32 -0
  83. package/dist/esm/compose/registry-resolve.d.ts +64 -0
  84. package/dist/esm/compose/registry-resolve.js +200 -0
  85. package/dist/esm/config/oxlint/graphql-conventions.js +2 -5
  86. package/dist/esm/config/oxlint/import-hygiene.js +21 -8
  87. package/dist/esm/config/oxlint/oxlint-config.fixture.d.ts +1 -1
  88. package/dist/esm/config/oxlint/oxlint-config.fixture.js +2 -3
  89. package/dist/esm/config/oxlint/oxlintrc.d.ts +5 -7
  90. package/dist/esm/config/oxlint/oxlintrc.js +18 -21
  91. package/dist/esm/config/oxlint/path-roles.js +53 -0
  92. package/dist/esm/config/oxlint/query-patterns.js +2 -5
  93. package/dist/esm/daemon/domains/blueprint-exec-ledger.d.ts +86 -0
  94. package/dist/esm/daemon/domains/blueprint-exec-ledger.js +134 -0
  95. package/dist/esm/daemon/domains/blueprint-exec.d.ts +106 -0
  96. package/dist/esm/daemon/domains/blueprint-exec.js +161 -0
  97. package/dist/esm/daemon/domains/launch.d.ts +25 -0
  98. package/dist/esm/daemon/domains/launch.js +30 -1
  99. package/dist/esm/daemon/handlers/blueprints-handlers.d.ts +12 -1
  100. package/dist/esm/daemon/handlers/blueprints-handlers.js +148 -6
  101. package/dist/esm/daemon/handlers/product.d.ts +10 -0
  102. package/dist/esm/daemon/handlers/product.js +10 -1
  103. package/dist/esm/daemon/methods.d.ts +21 -1
  104. package/dist/esm/daemon/methods.js +20 -0
  105. package/dist/esm/daemon/protocol/contract.d.ts +58 -0
  106. package/dist/esm/daemon/protocol/contract.js +38 -0
  107. package/dist/esm/daemon/server.js +7 -0
  108. package/dist/esm/docs-linter/blueprint-plan.js +2 -2
  109. package/dist/esm/errors/wp-error.js +21 -2
  110. package/dist/esm/git/changed-files.d.ts +20 -0
  111. package/dist/esm/git/changed-files.js +63 -0
  112. package/dist/esm/hooks/doctor.d.ts +11 -0
  113. package/dist/esm/hooks/doctor.js +114 -45
  114. package/dist/esm/hooks/pretool-guard/dev-routing.js +1 -1
  115. package/dist/esm/hooks/pretool-guard/rtk-rewrite.d.ts +24 -0
  116. package/dist/esm/hooks/pretool-guard/rtk-rewrite.js +50 -0
  117. package/dist/esm/hooks/pretool-guard/runner.js +6 -0
  118. package/dist/esm/hooks/status/index.js +1 -1
  119. package/dist/esm/hooks/stop/qa-changed-files.js +6 -1
  120. package/dist/esm/mcp/blueprint/_shared/lifecycle.js +39 -11
  121. package/dist/esm/mcp/blueprint/handlers/review-log.d.ts +14 -7
  122. package/dist/esm/mcp/blueprint/handlers/review-log.js +46 -22
  123. package/dist/esm/mcp/blueprint/handlers/validate.js +6 -0
  124. package/dist/esm/mcp/blueprint/registration.js +1 -1
  125. package/dist/esm/mcp/tools/_names.d.ts +1 -1
  126. package/dist/esm/mcp/tools/_names.js +4 -0
  127. package/dist/esm/mcp/tools/_registry.js +8 -0
  128. package/dist/esm/mcp/tools/_shared/redact.d.ts +22 -0
  129. package/dist/esm/mcp/tools/_shared/redact.js +114 -8
  130. package/dist/esm/mcp/tools/gain.js +28 -2
  131. package/dist/esm/mcp/tools/release-progress.d.ts +9 -0
  132. package/dist/esm/mcp/tools/release-progress.js +110 -0
  133. package/dist/esm/mcp/tools/worktree.js +65 -3
  134. package/dist/esm/mcp/tools/wp-ui-blocks-list.d.ts +3 -0
  135. package/dist/esm/mcp/tools/wp-ui-blocks-list.js +43 -0
  136. package/dist/esm/mcp/tools/wp-ui-compose.d.ts +3 -0
  137. package/dist/esm/mcp/tools/wp-ui-compose.js +96 -0
  138. package/dist/esm/mcp/tools/wp-ui-preview.d.ts +3 -0
  139. package/dist/esm/mcp/tools/wp-ui-preview.js +59 -0
  140. package/dist/esm/package.json +2 -0
  141. package/dist/esm/review/lifecycle-writers.d.ts +76 -0
  142. package/dist/esm/review/lifecycle-writers.js +330 -0
  143. package/dist/esm/review/subject.js +66 -32
  144. package/dist/esm/status/snapshot.d.ts +5 -0
  145. package/dist/esm/status/snapshot.js +27 -2
  146. package/dist/esm/test/shard-durations.json +0 -1
  147. package/dist/esm/test-helpers/global-setup.js +38 -3
  148. package/dist/esm/tool-runtime/managed-rtk.d.ts +44 -0
  149. package/dist/esm/tool-runtime/managed-rtk.js +275 -0
  150. package/dist/esm/tool-runtime/resolve-runner.js +18 -194
  151. package/dist/esm/utils/package-root.d.ts +32 -0
  152. package/dist/esm/utils/package-root.js +82 -0
  153. package/dist/esm/worktrees/git-metadata.d.ts +41 -0
  154. package/dist/esm/worktrees/git-metadata.js +48 -0
  155. package/package.json +27 -21
  156. package/dist/esm/config/oxlint/code-safety.d.ts +0 -18
  157. package/dist/esm/config/oxlint/foundation-purity.d.ts +0 -21
  158. package/dist/esm/config/oxlint/graphql-conventions.d.ts +0 -23
  159. package/dist/esm/config/oxlint/import-hygiene.d.ts +0 -34
  160. package/dist/esm/config/oxlint/index.d.ts +0 -22
  161. package/dist/esm/config/oxlint/index.js +0 -28
  162. package/dist/esm/config/oxlint/monorepo-paths.d.ts +0 -22
  163. package/dist/esm/config/oxlint/query-patterns.d.ts +0 -24
  164. package/dist/esm/config/oxlint/testing-quality.d.ts +0 -35
  165. package/dist/esm/config/oxlint/tier-boundaries.d.ts +0 -35
  166. package/dist/esm/output-transforms/rulesync.d.ts +0 -2
  167. package/dist/esm/output-transforms/rulesync.js +0 -79
@@ -4,18 +4,26 @@
4
4
  * One source of truth for how every supported host launches the webpresso MCP
5
5
  * server. Each host channel (Claude plugin manifest, Codex plugin manifest,
6
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
7
+ * projects this spec into its own config dialect. The Claude plugin manifest
8
+ * references the launcher via `${CLAUDE_PLUGIN_ROOT}`, which Claude Code
9
+ * expands. Codex does **not** expand `${PLUGIN_ROOT}` in a bundled `.mcp.json`
10
+ * that variable reaches hook commands only — and it spawns bundled MCP
11
+ * servers with the *project* cwd and no plugin env, so the Codex manifest can
12
+ * anchor on neither the plugin root nor a relative path and uses the bare
13
+ * `wp` name resolved from PATH at spawn time. The remaining setup-time writers
14
+ * (Grok, OpenCode) resolve an absolute `bin/wp` path via
15
+ * {@link resolveWebpressoMcpEntry}. Compare channels *semantically* (command
11
16
  * basename, args, env) — never byte-for-byte.
12
17
  */
13
18
  import { accessSync, constants, existsSync, readFileSync, realpathSync, statSync } from "node:fs";
14
19
  import { homedir } from "node:os";
15
- import { dirname, isAbsolute, join, relative, resolve } from "node:path";
20
+ import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
16
21
  import { execFileSync } from "node:child_process";
17
22
  import { fileURLToPath } from "node:url";
23
+ import { isVersionedAgentKitPayloadPath } from "#cli/auto-update/self-invocation.js";
18
24
  import { pathCandidates } from "#runtime/command-exists.js";
25
+ import { isManagedWorktreePath, isPathContained } from "#worktrees/identity.js";
26
+ import { resolveKnownManagedWorktreeRoots } from "#worktrees/location.js";
19
27
  export const WEBPRESSO_MCP_SERVER_NAME = "webpresso";
20
28
  /**
21
29
  * The single canonical launch spec for the webpresso MCP server. `name` is the
@@ -62,23 +70,57 @@ const WP_LAUNCHER_REQUIRED_RELATIVES = [
62
70
  join("bin", "runtime-lanes.js"),
63
71
  join("bin", "runtime-manifest.json"),
64
72
  ];
73
+ function isUsableWebpressoMcpRoot(root) {
74
+ return WP_LAUNCHER_REQUIRED_RELATIVES.every((relativePath) => existsSync(join(root, relativePath)));
75
+ }
76
+ /**
77
+ * True when `entryPath` is the stable vite-plus multi-package `wp` shim that
78
+ * dispatches to `@webpresso/agent-kit` via `~/.vite-plus/bins/wp.json`.
79
+ *
80
+ * Unlike {@link isWebpressoWpEntry}, this does **not** require realpath to land
81
+ * inside a package `bin/wp` tree — vite-plus's `wp` is a symlink to the `vp`
82
+ * binary, so package-root ownership is proven via the bins metadata instead.
83
+ */
84
+ export function isVitePlusManagedWebpressoWp(entryPath) {
85
+ try {
86
+ if (isVersionedAgentKitPayloadPath(entryPath))
87
+ return false;
88
+ const binDir = dirname(entryPath);
89
+ if (basename(binDir) !== "bin")
90
+ return false;
91
+ if (basename(dirname(binDir)) !== ".vite-plus")
92
+ return false;
93
+ const base = basename(entryPath).toLowerCase();
94
+ if (base !== "wp" && base !== "wp.exe")
95
+ return false;
96
+ const metaPath = join(dirname(binDir), "bins", "wp.json");
97
+ const meta = JSON.parse(readFileSync(metaPath, "utf8"));
98
+ return meta.package === "@webpresso/agent-kit";
99
+ }
100
+ catch {
101
+ return false;
102
+ }
103
+ }
104
+ function isDurableMcpEntryPath(entryPath) {
105
+ return !isVersionedAgentKitPayloadPath(entryPath);
106
+ }
65
107
  /**
66
108
  * Resolve the absolute path to webpresso's global `bin/wp` launcher on this
67
109
  * machine. Probes the locations consumers use to install webpresso, in order
68
110
  * of stability:
69
111
  *
70
- * 1. the stable global `wp` shim available on PATH
71
- * 2. the currently executing `@webpresso/agent-kit` package
112
+ * 1. the stable global `wp` shim available on PATH (including the
113
+ * vite-plus multi-package shim at `~/.vite-plus/bin/wp`)
114
+ * 2. the currently executing `@webpresso/agent-kit` package **only when it
115
+ * is not a vite-plus `agent-kit#<installId>` payload path**
72
116
  * 3. Claude plugin install — `~/.claude/plugins/cache/.../agent-kit/`
73
117
  * 4. bun global — `~/.bun/install/global/node_modules/@webpresso/agent-kit/`
74
- * Returns `null` when none of the candidates contain a usable `bin/wp`.
118
+ *
119
+ * Host MCP configs (Grok/OpenCode) must never be pinned to a versioned
120
+ * installId tree: the next `wp update` / `vp update -g` deletes that path and
121
+ * leaves every host with a dead launcher. `entryPath` is `null` when none of
122
+ * the candidates yield a durable `wp`.
75
123
  */
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
124
  export function resolveWebpressoMcpEntry(probe = {}) {
83
125
  const checked = [];
84
126
  if (probe.candidates === undefined) {
@@ -88,10 +130,17 @@ export function resolveWebpressoMcpEntry(probe = {}) {
88
130
  if (stableEntry) {
89
131
  checked.push(stableEntry);
90
132
  const packageWp = packageWpFromAdjacentNpmCmdShim(stableEntry);
91
- if (packageWp !== null)
133
+ if (packageWp !== null && isDurableMcpEntryPath(packageWp)) {
92
134
  return { entryPath: packageWp, checked };
135
+ }
93
136
  const verifierAllows = probe.stableEntryIsWebpresso?.(stableEntry) ?? true;
94
- if (verifierAllows && isWebpressoWpEntry(stableEntry)) {
137
+ // Prefer the vite-plus multi-package shim before package-root ownership:
138
+ // it survives installId swaps and is what PATH `wp` resolves to in the
139
+ // default consumer install.
140
+ if (verifierAllows && isVitePlusManagedWebpressoWp(stableEntry)) {
141
+ return { entryPath: stableEntry, checked };
142
+ }
143
+ if (verifierAllows && isWebpressoWpEntry(stableEntry) && isDurableMcpEntryPath(stableEntry)) {
95
144
  return { entryPath: stableEntry, checked };
96
145
  }
97
146
  }
@@ -103,8 +152,12 @@ export function resolveWebpressoMcpEntry(probe = {}) {
103
152
  continue;
104
153
  checked.push(root);
105
154
  const wpBin = join(root, WP_BIN_RELATIVE);
106
- if (isTrustedWebpressoPackageRoot(root))
155
+ // Never materialize host MCP configs against a vite-plus installId tree.
156
+ if (!isDurableMcpEntryPath(wpBin))
157
+ continue;
158
+ if (isTrustedWebpressoPackageRoot(root, probe.sourceRoot)) {
107
159
  return { entryPath: wpBin, checked };
160
+ }
108
161
  }
109
162
  }
110
163
  return { entryPath: null, checked };
@@ -152,27 +205,78 @@ function isPathInsideOrEqual(parent, candidate) {
152
205
  const rel = relative(resolve(parentRoot), resolve(candidateRoot));
153
206
  return rel === "" || (!rel.startsWith("..") && !isAbsolute(rel));
154
207
  }
155
- function isTrustedWebpressoPackageRoot(packageRoot) {
208
+ function isTrustedWebpressoPackageRoot(packageRoot, resolveSourceRoot = currentAgentKitPackageRoot) {
156
209
  try {
157
210
  if (!isUsableWebpressoPackageRoot(packageRoot))
158
211
  return false;
159
212
  const realPackageRoot = realpathSync(packageRoot);
160
- const sourceRoot = currentAgentKitPackageRoot();
161
- if (sourceRoot !== null && realPackageRoot === realpathSync(sourceRoot))
213
+ const sourceRoot = resolveSourceRoot();
214
+ // A managed blueprint worktree is deleted by `wp worktree merge-cleanup`
215
+ // once its PR lands, so self-trusting it here would persist a path into
216
+ // host MCP configs that later resolves to nothing. Only the durable
217
+ // primary checkout may earn self-trust.
218
+ const home = process.env.HOME || homedir();
219
+ const isManagedWorktree = isManagedWorktreePath(realPackageRoot, resolveKnownManagedWorktreeRoots(home));
220
+ if (sourceRoot !== null && !isManagedWorktree && realPackageRoot === realpathSync(sourceRoot)) {
162
221
  return true;
222
+ }
163
223
  return trustedHomeInstallPackageRoots().some((anchor) => existsSync(anchor) && isPathInsideOrEqual(anchor, realPackageRoot));
164
224
  }
165
225
  catch {
166
226
  return false;
167
227
  }
168
228
  }
169
- function trustedHomeInstallPackageRoots() {
170
- const home = process.env.HOME || homedir();
229
+ function trustedHomeInstallPackageRoots(home = process.env.HOME || homedir()) {
171
230
  return [
172
231
  join(home, ".claude", "plugins", "cache", "webpresso", "agent-kit"),
173
232
  join(home, ".bun", "install", "global", "node_modules", "@webpresso", "agent-kit"),
174
233
  ];
175
234
  }
235
+ /**
236
+ * Every root under which agent-kit itself installs (or checks out) a `bin/wp`.
237
+ * A `wp` launcher recorded inside one of these was written by agent-kit; a
238
+ * launcher anywhere else is an operator's deliberate choice.
239
+ */
240
+ export function agentKitOwnedCommandRoots(probe = {}) {
241
+ const home = probe.home ?? process.env.HOME ?? homedir();
242
+ const sourceRoot = (probe.sourceRoot ?? currentAgentKitPackageRoot)();
243
+ return [
244
+ ...trustedHomeInstallPackageRoots(home),
245
+ join(home, ".vite-plus"),
246
+ ...resolveKnownManagedWorktreeRoots(home),
247
+ ...(sourceRoot === null ? [] : [sourceRoot]),
248
+ ];
249
+ }
250
+ const WP_LAUNCHER_BASENAMES = new Set(["wp", "wp.exe"]);
251
+ /**
252
+ * Ownership — not shape — decides whether setup may retire a persisted MCP
253
+ * entry. An entry is agent-kit's own when it launches `wp mcp` from an
254
+ * ABSOLUTE path AND either that path is gone (one we would have deleted) or
255
+ * it lives inside one
256
+ * of {@link agentKitOwnedCommandRoots}. A *live* `wp mcp` launcher outside
257
+ * those roots is an operator pointing Codex at a custom build: preserve it.
258
+ */
259
+ export function isAgentKitOwnedWpCommand(command, args, probe = {}) {
260
+ if (!WP_LAUNCHER_BASENAMES.has(basename(command).toLowerCase()))
261
+ return false;
262
+ if (args.length !== WEBPRESSO_MCP_SPEC.args.length)
263
+ return false;
264
+ if (args.some((value, index) => value !== WEBPRESSO_MCP_SPEC.args[index]))
265
+ return false;
266
+ // Every agent-kit writer of this block persisted an ABSOLUTE launcher path
267
+ // (see resolveWebpressoMcpEntry). A bare PATH name — the spelling the Codex
268
+ // plugin manifest itself ships — or a cwd-relative path is therefore
269
+ // operator-authored, and its meaning depends on a PATH/cwd this predicate
270
+ // cannot reconstruct. Preserve it and let the doctor report it.
271
+ if (!isAbsolute(command))
272
+ return false;
273
+ const exists = probe.exists ?? existsSync;
274
+ // Only an absolute path can have been written by us and then rotted away
275
+ // (deleted managed worktree, replaced versioned install tree).
276
+ if (!exists(command))
277
+ return true;
278
+ return agentKitOwnedCommandRoots(probe).some((root) => isPathContained(root, command));
279
+ }
176
280
  function packageWpFromAdjacentNpmCmdShim(entryPath) {
177
281
  if (!entryPath.toLowerCase().endsWith(".cmd"))
178
282
  return null;
@@ -9,10 +9,21 @@
9
9
  import { existsSync, mkdirSync, readFileSync, writeFileSync, statSync, readdirSync } from "node:fs";
10
10
  import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
11
11
  import { convergeUnitFromTarget } from "./convergence.js";
12
- import { resolveUntrackedCollision } from "./untracked-collision.js";
13
- function guardUntrackedOverwrite(targetPath, incoming, opts) {
12
+ import { isGitIgnored, resolveUntrackedCollision } from "./untracked-collision.js";
13
+ /**
14
+ * Guard an overwrite against clobbering unknown untracked content. Returns a
15
+ * warning to surface to the caller when the write proceeded past divergent
16
+ * content it could safely recover from (git-ignored lane only); throws when
17
+ * the write must be blocked.
18
+ *
19
+ * `patchDerived` must be true only when `incoming` was computed FROM the
20
+ * target's existing content (e.g. `patchJsonFile`'s key-preserving patch) —
21
+ * that is what makes it safe to write through divergent unmarked content
22
+ * without a backup.
23
+ */
24
+ function guardUntrackedOverwrite(targetPath, incoming, opts, patchDerived) {
14
25
  if (!opts.repoRoot)
15
- return;
26
+ return undefined;
16
27
  const repoRoot = resolve(opts.repoRoot);
17
28
  const target = resolve(targetPath);
18
29
  const repoRelative = relative(repoRoot, target);
@@ -20,19 +31,25 @@ function guardUntrackedOverwrite(targetPath, incoming, opts) {
20
31
  repoRelative === ".." ||
21
32
  repoRelative.startsWith(`..${sep}`) ||
22
33
  isAbsolute(repoRelative)) {
23
- return;
34
+ return undefined;
24
35
  }
36
+ const gitPath = repoRelative.split(sep).join("/");
37
+ const scope = isGitIgnored(repoRoot, gitPath) ? "repo-ignored" : "repo-committed";
25
38
  const result = resolveUntrackedCollision({
26
39
  unit: convergeUnitFromTarget(repoRoot, target, {
27
40
  action: "modify",
28
- scope: "repo-committed",
41
+ scope,
29
42
  reason: "setup merge target",
30
43
  }),
31
44
  desiredContent: incoming,
32
45
  force: opts.overwrite,
46
+ patchDerived,
33
47
  });
34
48
  if (!result.ok)
35
49
  throw new Error(result.error);
50
+ if (result.warning)
51
+ console.error(`wp setup: ${result.warning}`);
52
+ return result.warning;
36
53
  }
37
54
  export function writeFileMerged(targetPath, incoming, opts = {}) {
38
55
  const exists = existsSync(targetPath);
@@ -51,9 +68,9 @@ export function writeFileMerged(targetPath, incoming, opts = {}) {
51
68
  if (shouldOverwrite) {
52
69
  if (opts.dryRun)
53
70
  return { targetPath, action: "skipped-dry" };
54
- guardUntrackedOverwrite(targetPath, incoming, opts);
71
+ const warning = guardUntrackedOverwrite(targetPath, incoming, opts, false);
55
72
  writeFileSync(targetPath, incoming);
56
- return { targetPath, action: "overwritten" };
73
+ return { targetPath, action: "overwritten", ...(warning ? { note: warning } : {}) };
57
74
  }
58
75
  if (opts.dryRun)
59
76
  return { targetPath, action: "skipped-dry" };
@@ -119,11 +136,14 @@ export function patchJsonFile(targetPath, patcher, opts = {}) {
119
136
  if (opts.dryRun) {
120
137
  return { targetPath, action: "skipped-dry" };
121
138
  }
122
- if (exists)
123
- guardUntrackedOverwrite(targetPath, incoming, opts);
139
+ const warning = exists ? guardUntrackedOverwrite(targetPath, incoming, opts, true) : undefined;
124
140
  mkdirSync(dirname(targetPath), { recursive: true });
125
141
  writeFileSync(targetPath, incoming);
126
- return { targetPath, action: exists ? "overwritten" : "created" };
142
+ return {
143
+ targetPath,
144
+ action: exists ? "overwritten" : "created",
145
+ ...(warning ? { note: warning } : {}),
146
+ };
127
147
  }
128
148
  export function summarizeResults(results) {
129
149
  const summary = {
@@ -30,13 +30,11 @@ const TECH_STACK_RULES = [
30
30
  { dep: /^typescript$/, label: "TypeScript" },
31
31
  ];
32
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.
33
+ // the full `native_tool_names` registry list (~15-17 bytes per tool). Soft
34
+ // target 8_180 leaves headroom under 8_192. The ui-compose batch
35
+ // (wp_ui_blocks_list, wp_ui_compose, wp_ui_preview) required trimming catalog
36
+ // AGENTS.md.tpl prose rather than raising the ceiling. Next breach of 8_192
37
+ // should restructure (or stop embedding the full tool list), not bump again.
40
38
  export const AGENTS_MD_MAX_BYTES = 8_180;
41
39
  export function renderRepositoryMap(consumer) {
42
40
  const packages = consumer.workspacePackages;
@@ -6,21 +6,18 @@
6
6
  * the patch tiny and deterministic: per-server upserts, no TOML parser
7
7
  * dependency, no edits to unrelated user config.
8
8
  *
9
- * Managed blocks come from two sources:
10
- * 1. `[mcp_servers.webpresso]`points at webpresso's own MCP server.
11
- * Path-stability requires discovery: webpresso lives in different
12
- * locations depending on how the user installed it (Claude plugin
13
- * install, bun global, pnpm/npm global). Discovery happens at scaffold
14
- * time; the resolved absolute path is written into the codex config.
15
- * When the unified-cli sibling cutover lands (`webpresso mcp serve`
16
- * from a path-stable bin), this block collapses to a fixed `command`.
17
- * 2. Consumer-owned `.mcp.json` package declarations — compiled to setup-
18
- * provisioned, exact-version binaries with environment-variable names
19
- * forwarded through Codex's stdio `env_vars` allow-list.
9
+ * Webpresso's own MCP server is NOT one of them. Codex receives `webpresso`
10
+ * over exactly one delivery channel the bundled Codex plugin's `.mcp.json`.
11
+ * Setup therefore *retires* the `[mcp_servers.webpresso]` block earlier
12
+ * versions wrote here (see {@link pruneLegacyCodexWebpressoMcp}) instead of
13
+ * refreshing it. What remains managed here is consumer-owned `.mcp.json`
14
+ * package declarations compiled to setup-provisioned, exact-version binaries
15
+ * with environment-variable names forwarded through Codex's stdio `env_vars`
16
+ * allow-list.
20
17
  */
21
18
  import { spawnSync } from "node:child_process";
22
- import { findWebpressoMcpEntry, WEBPRESSO_MCP_SERVER_NAME, type WebpressoInstallProbe } from "#cli/commands/init/mcp-spec.js";
23
- export { findWebpressoMcpEntry, WEBPRESSO_MCP_SERVER_NAME };
19
+ import { WEBPRESSO_MCP_SERVER_NAME, type AgentKitOwnedCommandProbe, type WebpressoInstallProbe } from "#cli/commands/init/mcp-spec.js";
20
+ export { WEBPRESSO_MCP_SERVER_NAME };
24
21
  export type { WebpressoInstallProbe };
25
22
  import type { MergeOptions } from "#cli/commands/init/merge";
26
23
  import { type PinnedCodexMcpDefinition } from "#hooks/host-config-contract.js";
@@ -143,35 +140,63 @@ export type EnsureClaudePlaywrightMcpResult = {
143
140
  path: string;
144
141
  };
145
142
  export declare function ensureClaudePlaywrightMcp(input: EnsureClaudePlaywrightMcpInput): EnsureClaudePlaywrightMcpResult;
146
- export declare function agentKitMcpBlock(entryPath: string): string;
147
- export declare function upsertWebpressoMcpServer(raw: string, entryPath: string): string;
148
- export interface EnsureCodexWebpressoMcpInput {
143
+ export type PruneOwnedMcpServerOutcome = {
144
+ readonly kind: "removed";
145
+ readonly raw: string;
146
+ readonly command: string;
147
+ } | {
148
+ readonly kind: "absent";
149
+ readonly raw: string;
150
+ } | {
151
+ readonly kind: "preserved";
152
+ readonly raw: string;
153
+ readonly command: string | null;
154
+ } | {
155
+ readonly kind: "ambiguous";
156
+ readonly raw: string;
157
+ };
158
+ /**
159
+ * Remove `[mcp_servers.<serverId>]` (plus its contiguous sub-tables) when
160
+ * `isOwned` claims the block's launch tuple. Fails closed — leaving `raw`
161
+ * byte-identical — for every TOML spelling this line scanner cannot rewrite
162
+ * safely (dotted keys, inline parent table, array tables, non-contiguous
163
+ * sub-tables).
164
+ */
165
+ export declare function pruneOwnedMcpServerBlock(raw: string, serverId: string, isOwned: (command: string, args: readonly string[]) => boolean): PruneOwnedMcpServerOutcome;
166
+ export interface PruneLegacyCodexWebpressoMcpInput {
149
167
  options: MergeOptions;
150
- /** Test seam — override the resolved MCP entry path. */
151
- entryPath?: string;
152
168
  /** Test seam — override `$CODEX_HOME/config.toml`. */
153
169
  configPath?: string;
154
- /** Test seam — override the install-discovery probe. */
155
- probe?: WebpressoInstallProbe;
170
+ /** Test seam — override agent-kit install-root ownership resolution. */
171
+ ownership?: AgentKitOwnedCommandProbe;
156
172
  }
157
- export type EnsureCodexWebpressoMcpResult = {
158
- kind: "codex-webpresso-mcp-written";
173
+ export type PruneLegacyCodexWebpressoMcpResult = {
174
+ kind: "codex-legacy-webpresso-mcp-removed";
159
175
  path: string;
160
- entryPath: string;
176
+ command: string;
161
177
  } | {
162
- kind: "codex-webpresso-mcp-unchanged";
178
+ kind: "codex-legacy-webpresso-mcp-absent";
163
179
  path: string;
164
- entryPath: string;
165
180
  } | {
166
- kind: "codex-webpresso-mcp-ambiguous";
181
+ kind: "codex-legacy-webpresso-mcp-preserved";
167
182
  path: string;
168
- entryPath: string;
183
+ command: string | null;
169
184
  } | {
170
- kind: "codex-webpresso-mcp-skipped-dry-run";
185
+ kind: "codex-legacy-webpresso-mcp-ambiguous";
171
186
  path: string;
172
187
  } | {
173
- kind: "codex-webpresso-mcp-not-installed";
188
+ kind: "codex-legacy-webpresso-mcp-skipped-dry-run";
174
189
  path: string;
175
- checked: readonly string[];
176
190
  };
177
- export declare function ensureCodexWebpressoMcp(input: EnsureCodexWebpressoMcpInput): EnsureCodexWebpressoMcpResult;
191
+ /**
192
+ * Retire the legacy user-global `[mcp_servers.webpresso]` block. Runs from
193
+ * setup AND from the `wp codex` launch preflight — the launch path is the only
194
+ * one that reaches machines that never re-run setup.
195
+ */
196
+ export declare function pruneLegacyCodexWebpressoMcp(input: PruneLegacyCodexWebpressoMcpInput): PruneLegacyCodexWebpressoMcpResult;
197
+ /**
198
+ * Installed Codex plugin MCP manifests — the surviving delivery channel.
199
+ * Cheap directory probe only: no `codex plugin` subprocess belongs on the
200
+ * launch hot path.
201
+ */
202
+ export declare function findInstalledCodexPluginMcpManifests(homeDir?: string): readonly string[];