@webpresso/agent-kit 3.3.3 → 3.3.5

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 (156) hide show
  1. package/THIRD-PARTY-NOTICES.md +12 -10
  2. package/catalog/agent/rules/changeset-release.md +15 -4
  3. package/catalog/agent/rules/managed-tool-binaries.md +67 -0
  4. package/catalog/agent/rules/parallel-orchestration-quota.md +99 -1
  5. package/catalog/agent/rules/pre-implementation.md +36 -20
  6. package/catalog/agent/rules/rtk-routing.md +22 -16
  7. package/catalog/agent/skills/autopilot/SKILL.md +4 -3
  8. package/catalog/agent/skills/ultragoal/SKILL.md +11 -9
  9. package/dist/esm/audit/agents.js +126 -26
  10. package/dist/esm/audit/hook-surface.js +5 -1
  11. package/dist/esm/audit/repo-guardrails.d.ts +8 -0
  12. package/dist/esm/audit/repo-guardrails.js +71 -1
  13. package/dist/esm/blueprint/core/parser.js +38 -12
  14. package/dist/esm/blueprint/execution/artifacts.js +8 -4
  15. package/dist/esm/blueprint/execution/frontmatter-keys.d.ts +17 -0
  16. package/dist/esm/blueprint/execution/frontmatter-keys.js +24 -0
  17. package/dist/esm/blueprint/execution/metadata.js +8 -4
  18. package/dist/esm/blueprint/lifecycle/audit.d.ts +21 -0
  19. package/dist/esm/blueprint/lifecycle/audit.js +89 -3
  20. package/dist/esm/blueprint/lifecycle/review-provenance.js +63 -10
  21. package/dist/esm/blueprint/lifecycle/rollout-anchor.d.ts +22 -0
  22. package/dist/esm/blueprint/lifecycle/rollout-anchor.js +37 -1
  23. package/dist/esm/blueprint/markdown/blocked-line.d.ts +49 -0
  24. package/dist/esm/blueprint/markdown/blocked-line.js +63 -0
  25. package/dist/esm/blueprint/markdown/helpers.js +10 -4
  26. package/dist/esm/blueprint/tracked-document/parser.js +3 -3
  27. package/dist/esm/blueprint/trust/dossier.js +41 -1
  28. package/dist/esm/blueprint/trust/gate-row.d.ts +55 -0
  29. package/dist/esm/blueprint/trust/gate-row.js +113 -0
  30. package/dist/esm/blueprint/trust/promotion.d.ts +17 -0
  31. package/dist/esm/blueprint/trust/promotion.js +19 -19
  32. package/dist/esm/build/atomic-file-copy.d.ts +32 -0
  33. package/dist/esm/build/atomic-file-copy.js +44 -0
  34. package/dist/esm/build/vendor-oxlint-plugins.d.ts +11 -0
  35. package/dist/esm/build/vendor-oxlint-plugins.js +48 -0
  36. package/dist/esm/cli/commands/blueprint/mutations.d.ts +15 -0
  37. package/dist/esm/cli/commands/blueprint/mutations.js +8 -2
  38. package/dist/esm/cli/commands/compile.d.ts +13 -0
  39. package/dist/esm/cli/commands/compile.js +182 -62
  40. package/dist/esm/cli/commands/format.js +17 -1
  41. package/dist/esm/cli/commands/gain/index.js +10 -3
  42. package/dist/esm/cli/commands/init/gitignore-patcher.js +6 -0
  43. package/dist/esm/cli/commands/init/index.js +32 -52
  44. package/dist/esm/cli/commands/init/mcp-spec.d.ts +48 -13
  45. package/dist/esm/cli/commands/init/mcp-spec.js +86 -30
  46. package/dist/esm/cli/commands/init/merge.js +30 -10
  47. package/dist/esm/cli/commands/init/scaffolders/codex-mcp/index.d.ts +56 -31
  48. package/dist/esm/cli/commands/init/scaffolders/codex-mcp/index.js +180 -43
  49. package/dist/esm/cli/commands/init/scaffolders/rtk/index.d.ts +17 -5
  50. package/dist/esm/cli/commands/init/scaffolders/rtk/index.js +56 -75
  51. package/dist/esm/cli/commands/init/scaffolders/runtime-check/index.js +7 -6
  52. package/dist/esm/cli/commands/init/untracked-collision.d.ts +12 -1
  53. package/dist/esm/cli/commands/init/untracked-collision.js +23 -1
  54. package/dist/esm/cli/commands/pr-help.js +1 -1
  55. package/dist/esm/cli/commands/pr.js +18 -4
  56. package/dist/esm/cli/commands/review.d.ts +1 -0
  57. package/dist/esm/cli/commands/review.js +137 -7
  58. package/dist/esm/cli/commands/sync.d.ts +8 -6
  59. package/dist/esm/cli/commands/sync.js +11 -9
  60. package/dist/esm/cli/commands/worktree/router-dispatch.d.ts +24 -2
  61. package/dist/esm/cli/commands/worktree/router-dispatch.js +80 -48
  62. package/dist/esm/cli/commands/worktree/router.js +5 -2
  63. package/dist/esm/cli/direct-provider-launch.js +12 -4
  64. package/dist/esm/compiler/flatten.d.ts +10 -1
  65. package/dist/esm/compiler/flatten.js +14 -4
  66. package/dist/esm/compose/admin-blocks-registry-integrity.d.ts +22 -0
  67. package/dist/esm/compose/admin-blocks-registry-integrity.js +90 -0
  68. package/dist/esm/compose/compose.d.ts +8 -0
  69. package/dist/esm/compose/compose.js +115 -8
  70. package/dist/esm/compose/ops-report-schema.js +17 -3
  71. package/dist/esm/compose/registry-resolve.d.ts +17 -0
  72. package/dist/esm/compose/registry-resolve.js +78 -11
  73. package/dist/esm/config/oxlint/graphql-conventions.js +2 -5
  74. package/dist/esm/config/oxlint/import-hygiene.js +21 -8
  75. package/dist/esm/config/oxlint/oxlint-config.fixture.d.ts +1 -1
  76. package/dist/esm/config/oxlint/oxlint-config.fixture.js +2 -3
  77. package/dist/esm/config/oxlint/oxlintrc.d.ts +5 -7
  78. package/dist/esm/config/oxlint/oxlintrc.js +19 -31
  79. package/dist/esm/config/oxlint/path-roles.js +53 -0
  80. package/dist/esm/config/oxlint/query-patterns.js +2 -5
  81. package/dist/esm/content/dispatch.js +4 -0
  82. package/dist/esm/content/loader.d.ts +1 -1
  83. package/dist/esm/content/loader.js +10 -6
  84. package/dist/esm/content/skill-policy.d.ts +11 -0
  85. package/dist/esm/content/skill-policy.js +33 -0
  86. package/dist/esm/daemon/domains/blueprint-exec-ledger.d.ts +86 -0
  87. package/dist/esm/daemon/domains/blueprint-exec-ledger.js +134 -0
  88. package/dist/esm/daemon/domains/blueprint-exec.d.ts +106 -0
  89. package/dist/esm/daemon/domains/blueprint-exec.js +161 -0
  90. package/dist/esm/daemon/domains/launch.d.ts +25 -0
  91. package/dist/esm/daemon/domains/launch.js +30 -1
  92. package/dist/esm/daemon/handlers/blueprints-handlers.d.ts +12 -1
  93. package/dist/esm/daemon/handlers/blueprints-handlers.js +148 -6
  94. package/dist/esm/daemon/handlers/product.d.ts +10 -0
  95. package/dist/esm/daemon/handlers/product.js +10 -1
  96. package/dist/esm/daemon/methods.d.ts +21 -1
  97. package/dist/esm/daemon/methods.js +20 -0
  98. package/dist/esm/daemon/protocol/contract.d.ts +58 -0
  99. package/dist/esm/daemon/protocol/contract.js +38 -0
  100. package/dist/esm/daemon/server.js +7 -0
  101. package/dist/esm/docs-linter/blueprint-plan.js +2 -2
  102. package/dist/esm/errors/wp-error.js +21 -2
  103. package/dist/esm/hooks/doctor.d.ts +11 -0
  104. package/dist/esm/hooks/doctor.js +114 -45
  105. package/dist/esm/hooks/pretool-guard/dev-routing.js +68 -5
  106. package/dist/esm/hooks/pretool-guard/rtk-rewrite.d.ts +24 -0
  107. package/dist/esm/hooks/pretool-guard/rtk-rewrite.js +50 -0
  108. package/dist/esm/hooks/pretool-guard/runner.js +6 -0
  109. package/dist/esm/hooks/pretool-guard/validators/worktree-discipline.js +62 -3
  110. package/dist/esm/hooks/status/index.js +1 -1
  111. package/dist/esm/mcp/blueprint/_shared/lifecycle.js +39 -11
  112. package/dist/esm/mcp/blueprint/handlers/review-log.d.ts +14 -7
  113. package/dist/esm/mcp/blueprint/handlers/review-log.js +46 -22
  114. package/dist/esm/mcp/blueprint/registration.js +1 -1
  115. package/dist/esm/mcp/tools/_shared/redact.d.ts +22 -0
  116. package/dist/esm/mcp/tools/_shared/redact.js +114 -8
  117. package/dist/esm/mcp/tools/gain.js +28 -2
  118. package/dist/esm/mcp/tools/worktree.js +65 -3
  119. package/dist/esm/mcp/tools/wp-ui-compose.js +38 -4
  120. package/dist/esm/mcp/tools/wp-ui-preview.js +14 -7
  121. package/dist/esm/review/authority.js +56 -1
  122. package/dist/esm/review/delivery-verifier.d.ts +4 -0
  123. package/dist/esm/review/delivery-verifier.js +9 -1
  124. package/dist/esm/review/lifecycle-writers.d.ts +76 -0
  125. package/dist/esm/review/lifecycle-writers.js +330 -0
  126. package/dist/esm/review/subject.js +66 -32
  127. package/dist/esm/status/snapshot.d.ts +5 -0
  128. package/dist/esm/status/snapshot.js +27 -2
  129. package/dist/esm/symlinker/consumers.d.ts +3 -1
  130. package/dist/esm/symlinker/consumers.js +7 -1
  131. package/dist/esm/symlinker/unified-sync.d.ts +3 -3
  132. package/dist/esm/symlinker/unified-sync.js +88 -11
  133. package/dist/esm/test/shard-durations.json +0 -2
  134. package/dist/esm/test-helpers/global-setup.js +38 -3
  135. package/dist/esm/tool-runtime/managed-rtk.d.ts +44 -0
  136. package/dist/esm/tool-runtime/managed-rtk.js +275 -0
  137. package/dist/esm/tool-runtime/resolve-runner.js +18 -194
  138. package/dist/esm/utils/package-root.d.ts +32 -0
  139. package/dist/esm/utils/package-root.js +82 -0
  140. package/dist/esm/worktrees/git-metadata.d.ts +41 -0
  141. package/dist/esm/worktrees/git-metadata.js +48 -0
  142. package/package.json +16 -21
  143. package/dist/esm/cli/commands/init/scaffolders/subagents/index.d.ts +0 -6
  144. package/dist/esm/cli/commands/init/scaffolders/subagents/index.js +0 -77
  145. package/dist/esm/config/oxlint/code-safety.d.ts +0 -18
  146. package/dist/esm/config/oxlint/foundation-purity.d.ts +0 -21
  147. package/dist/esm/config/oxlint/graphql-conventions.d.ts +0 -23
  148. package/dist/esm/config/oxlint/import-hygiene.d.ts +0 -34
  149. package/dist/esm/config/oxlint/index.d.ts +0 -22
  150. package/dist/esm/config/oxlint/index.js +0 -28
  151. package/dist/esm/config/oxlint/monorepo-paths.d.ts +0 -22
  152. package/dist/esm/config/oxlint/query-patterns.d.ts +0 -24
  153. package/dist/esm/config/oxlint/testing-quality.d.ts +0 -35
  154. package/dist/esm/config/oxlint/tier-boundaries.d.ts +0 -35
  155. package/dist/esm/output-transforms/rulesync.d.ts +0 -2
  156. package/dist/esm/output-transforms/rulesync.js +0 -79
@@ -1,17 +1,24 @@
1
1
  import { spawnSync } from "node:child_process";
2
2
  import { SessionMemoryStore } from "#session-memory/store.js";
3
3
  import { defaultIndexDbPath } from "#mcp/tools/session-restore.js";
4
+ import { resolveManagedRtkExecutable } from "#tool-runtime/managed-rtk.js";
4
5
  export function runGain(options = {}) {
5
6
  const dbPath = options.indexDbPath ?? defaultIndexDbPath(options.cwd);
6
7
  const webpressoStats = readWebpressoGainStats(dbPath);
7
8
  printWebpressoGain(webpressoStats, dbPath, { sessions: options.sessions === true });
8
9
  console.log("\n── RTK Gain Totals ──────────────────────────────────────────");
9
- const rtk = spawnSync("rtk", ["gain", "--format", "json"], { encoding: "utf8" });
10
+ const rtkBin = resolveManagedRtkExecutable();
11
+ if (!rtkBin) {
12
+ console.log(" Managed RTK not available.");
13
+ console.log(" Enable: wp setup --with rtk (or set WP_RTK_BIN to a capable rtk binary)");
14
+ return 0;
15
+ }
16
+ const rtk = spawnSync(rtkBin, ["gain", "--format", "json"], { encoding: "utf8" });
10
17
  if (rtk.error) {
11
18
  const err = rtk.error;
12
19
  if (err instanceof Error && "code" in err && err.code === "ENOENT") {
13
- console.log(" RTK not installed.");
14
- console.log(" Enable: wp setup --with rtk | Manual: brew install rtk");
20
+ console.log(" Managed RTK binary missing at resolved path.");
21
+ console.log(" Enable: wp setup --with rtk (or set WP_RTK_BIN)");
15
22
  return 0;
16
23
  }
17
24
  throw err;
@@ -76,6 +76,12 @@ export const GENERATED_PATHS_BLOCK = {
76
76
  ".agent/.blueprints.lock",
77
77
  ".agent/.blueprints.snapshot.sql",
78
78
  ".agent/.tail-hint-history.jsonl",
79
+ "# `wp compile` derives .rulesync/ from the canonical agent sources and replaces",
80
+ "# it wholesale on every run, so it is a build artifact rather than the",
81
+ "# hand-authored source tree rulesync's own docs describe. The .tmp- dirs are",
82
+ "# its staging area, normally renamed away but left behind by an interrupt.",
83
+ ".rulesync/",
84
+ ".rulesync.tmp-*",
79
85
  "# Stryker writes these mutation-test artifacts beside consumer source trees.",
80
86
  ".stryker-tmp/",
81
87
  "reports/mutation/",
@@ -54,14 +54,13 @@ import { ensureClaudeCodeUserPlugin } from "./scaffolders/claude-plugin/index.js
54
54
  import { ensureCodexUserPlugin } from "./scaffolders/codex-plugin/index.js";
55
55
  import { scaffoldClaudeRules } from "./scaffolders/claude-rules/index.js";
56
56
  import { ensureCodexCli } from "./scaffolders/codex-cli/index.js";
57
- import { ensureClaudePlaywrightMcp, ensureCodexProjectMcpServers, ensureCodexWebpressoMcp, } from "./scaffolders/codex-mcp/index.js";
57
+ import { ensureClaudePlaywrightMcp, ensureCodexProjectMcpServers, pruneLegacyCodexWebpressoMcp, } from "./scaffolders/codex-mcp/index.js";
58
58
  import { scaffoldExampleSkill } from "./scaffolders/example-skill/index.js";
59
59
  import { scaffoldLoreCommits } from "./scaffolders/lore-commits/index.js";
60
60
  import { ensureOpenCodeWebpressoPlugin } from "./scaffolders/opencode-plugin/index.js";
61
61
  import { ensureGrokWebpressoMcp } from "./scaffolders/grok-mcp/index.js";
62
62
  import { ensureRtk } from "./scaffolders/rtk/index.js";
63
63
  import { checkRuntimes } from "./scaffolders/runtime-check/index.js";
64
- import { scaffoldSubagents } from "./scaffolders/subagents/index.js";
65
64
  import { maybeRunVisionInterview } from "./scaffolders/vision/interview.js";
66
65
  import { scaffoldVision } from "./scaffolders/vision/index.js";
67
66
  import { scaffoldWorkspaceConfig } from "./scaffolders/workspace-config/index.js";
@@ -381,24 +380,7 @@ async function runUserOnlySetup(input) {
381
380
  console.log(" playwright mcp: - skipped (the preset requires a project-owned .mcp.json; run setup inside the project repo)");
382
381
  }
383
382
  if (selectedHosts.includes("codex")) {
384
- const webpressoMcpResult = ensureCodexWebpressoMcp({ options: userScopeOptions });
385
- switch (webpressoMcpResult.kind) {
386
- case "codex-webpresso-mcp-written":
387
- console.log(` codex webpresso mcp: ✓ ${webpressoMcpResult.path} → ${webpressoMcpResult.entryPath}`);
388
- break;
389
- case "codex-webpresso-mcp-unchanged":
390
- console.log(` codex webpresso mcp: already configured at ${webpressoMcpResult.path}`);
391
- break;
392
- case "codex-webpresso-mcp-ambiguous":
393
- console.warn(` codex webpresso mcp: ⚠ ambiguous MCP config at ${webpressoMcpResult.path}; left unchanged`);
394
- break;
395
- case "codex-webpresso-mcp-skipped-dry-run":
396
- console.log(" codex webpresso mcp: skipped (--dry-run)");
397
- break;
398
- case "codex-webpresso-mcp-not-installed":
399
- console.log(` codex webpresso mcp: ⚠ no install root found (checked ${webpressoMcpResult.checked.length} paths). Install @webpresso/agent-kit globally (\`bun add -g @webpresso/agent-kit\`) or via the Claude plugin to wire up codex MCP.`);
400
- break;
401
- }
383
+ reportLegacyCodexWebpressoMcpPrune(pruneLegacyCodexWebpressoMcp({ options: userScopeOptions }));
402
384
  console.log(" codex project mcp: - skipped (no project .mcp.json outside a repo)");
403
385
  }
404
386
  if (isCiEnvironment) {
@@ -696,6 +678,25 @@ function printCodexPluginResult(result) {
696
678
  break;
697
679
  }
698
680
  }
681
+ function reportLegacyCodexWebpressoMcpPrune(result) {
682
+ switch (result.kind) {
683
+ case "codex-legacy-webpresso-mcp-removed":
684
+ console.log(` codex webpresso mcp: ✓ retired legacy [mcp_servers.webpresso] in ${result.path} (the bundled Codex plugin is the single delivery channel)`);
685
+ break;
686
+ case "codex-legacy-webpresso-mcp-absent":
687
+ console.log(" codex webpresso mcp: ✓ delivered by the bundled Codex plugin");
688
+ break;
689
+ case "codex-legacy-webpresso-mcp-preserved":
690
+ console.warn(` codex webpresso mcp: ⚠ ${result.path} declares a user-authored webpresso server (${result.command ?? "no command"}); left unchanged — remove it manually to use the bundled Codex plugin`);
691
+ break;
692
+ case "codex-legacy-webpresso-mcp-ambiguous":
693
+ console.warn(` codex webpresso mcp: ⚠ ambiguous MCP config at ${result.path}; left unchanged`);
694
+ break;
695
+ case "codex-legacy-webpresso-mcp-skipped-dry-run":
696
+ console.log(" codex webpresso mcp: skipped (--dry-run)");
697
+ break;
698
+ }
699
+ }
699
700
  function reconcileCodexProjectMcpServers(repoRoot, options) {
700
701
  const result = ensureCodexProjectMcpServers({ repoRoot, options });
701
702
  switch (result.kind) {
@@ -1331,7 +1332,7 @@ export async function runInit(flags, deps = {}) {
1331
1332
  runUnifiedSync({
1332
1333
  catalogDir: join(catalogDir, "agent"),
1333
1334
  consumerRoot: consumer.repoRoot,
1334
- kinds: ["rule", "skill"],
1335
+ kinds: ["rule", "skill", "agent"],
1335
1336
  check: false,
1336
1337
  allowedSkillSlugs,
1337
1338
  hosts: selectedHosts,
@@ -1482,7 +1483,8 @@ export async function runInit(flags, deps = {}) {
1482
1483
  }
1483
1484
  throw error;
1484
1485
  }
1485
- const subagentResults = scaffoldSubagents({ repoRoot: consumer.repoRoot, options });
1486
+ // Canonical subagents (.claude/agents/) are projected by the unified sync
1487
+ // call above (kinds includes "agent") — no standalone scaffolder here.
1486
1488
  // Generated host instructions (AGENTS.md) are a tier-1 (guarded/full)
1487
1489
  // surface; the tier-0 quality profile omits them.
1488
1490
  // Base-kit may have created or updated package metadata earlier in this
@@ -1625,29 +1627,14 @@ export async function runInit(flags, deps = {}) {
1625
1627
  ? { kind: "apply" }
1626
1628
  : { kind: "skip", reason: skipGlobal ? "--skip-global" : "user-scope writes declined" });
1627
1629
  }
1628
- // Always upsert webpresso's MCP entry into the user's codex config when
1629
- // an install root is discoverable. Codex's config.toml is user-global, so
1630
- // we resolve to whatever absolute install path exists today (Claude
1631
- // plugin / bun global / pnpm global / npm global) and write that.
1630
+ // Codex receives webpresso over ONE channel: the bundled Codex plugin's
1631
+ // `.mcp.json`. Setup retires the user-global `[mcp_servers.webpresso]`
1632
+ // block earlier versions wrote here rather than refreshing it.
1632
1633
  if (selectedHosts.includes("codex")) {
1633
- const webpressoMcpResult = ensureCodexWebpressoMcp({ options: userScopeOptions });
1634
- switch (webpressoMcpResult.kind) {
1635
- case "codex-webpresso-mcp-written":
1636
- console.log(` codex webpresso mcp: ✓ ${webpressoMcpResult.path} → ${webpressoMcpResult.entryPath}`);
1637
- recordUserScope("codex-config");
1638
- break;
1639
- case "codex-webpresso-mcp-unchanged":
1640
- console.log(` codex webpresso mcp: already configured at ${webpressoMcpResult.path}`);
1641
- break;
1642
- case "codex-webpresso-mcp-ambiguous":
1643
- console.warn(` codex webpresso mcp: ⚠ ambiguous MCP config at ${webpressoMcpResult.path}; left unchanged`);
1644
- break;
1645
- case "codex-webpresso-mcp-skipped-dry-run":
1646
- console.log(" codex webpresso mcp: skipped (--dry-run)");
1647
- break;
1648
- case "codex-webpresso-mcp-not-installed":
1649
- console.log(` codex webpresso mcp: ⚠ no install root found (checked ${webpressoMcpResult.checked.length} paths). Install @webpresso/agent-kit globally (\`bun add -g @webpresso/agent-kit\`) or via the Claude plugin to wire up codex MCP.`);
1650
- break;
1634
+ const pruneResult = pruneLegacyCodexWebpressoMcp({ options: userScopeOptions });
1635
+ reportLegacyCodexWebpressoMcpPrune(pruneResult);
1636
+ if (pruneResult.kind === "codex-legacy-webpresso-mcp-removed") {
1637
+ recordUserScope("codex-config");
1651
1638
  }
1652
1639
  reconcileCodexProjectMcpServers(consumer.repoRoot, userScopeOptions);
1653
1640
  }
@@ -1829,7 +1816,7 @@ export async function runInit(flags, deps = {}) {
1829
1816
  const rtkResult = ensureRtk({ repoRoot: consumer.repoRoot, options: userScopeOptions });
1830
1817
  switch (rtkResult.kind) {
1831
1818
  case "rtk-ok":
1832
- console.log(rtkResult.installed ? " rtk: ✓ installed + configured" : " rtk: ✓");
1819
+ console.log(rtkResult.installed ? " rtk: ✓ managed binary installed" : " rtk: ✓");
1833
1820
  recordUserScope("rtk-install");
1834
1821
  break;
1835
1822
  case "rtk-skipped-dry-run":
@@ -1839,10 +1826,6 @@ export async function runInit(flags, deps = {}) {
1839
1826
  console.error(` rtk: ✗ ${rtkResult.hint}`);
1840
1827
  rtkFailure = "not-found";
1841
1828
  break;
1842
- case "rtk-init-failed":
1843
- console.error(` rtk: ✗ rtk init exited with ${rtkResult.exitCode}`);
1844
- rtkFailure = "init-failed";
1845
- break;
1846
1829
  }
1847
1830
  }
1848
1831
  const all = [
@@ -1870,7 +1853,6 @@ export async function runInit(flags, deps = {}) {
1870
1853
  ]
1871
1854
  : []),
1872
1855
  ...claudeRulesResults,
1873
- ...subagentResults,
1874
1856
  ...presetResults,
1875
1857
  ];
1876
1858
  const summary = summarizeResults(all);
@@ -2039,8 +2021,6 @@ export async function runInit(flags, deps = {}) {
2039
2021
  console.log("\nwp init: setup phases finished.");
2040
2022
  if (rtkFailure === "not-found")
2041
2023
  return EXIT_SETUP_FAIL;
2042
- if (rtkFailure === "init-failed")
2043
- return EXIT_WRITE_FAIL;
2044
2024
  if (isTelemetryEnabled(process.env)) {
2045
2025
  const payload = {
2046
2026
  event: "setup-complete",
@@ -30,7 +30,24 @@ export interface WebpressoInstallProbe {
30
30
  stableEntry?: () => string | null;
31
31
  /** Test seam — verify that a PATH entry is the Webpresso command. */
32
32
  stableEntryIsWebpresso?: (entryPath: string) => boolean;
33
+ /**
34
+ * Test seam — resolve the currently executing agent-kit package root.
35
+ *
36
+ * Without this seam the self-trust branch below is only reachable when the
37
+ * test file itself happens to live in a managed worktree, so the regression
38
+ * guard would silently skip in CI — where the checkout is a plain clone.
39
+ */
40
+ sourceRoot?: () => string | null;
33
41
  }
42
+ /**
43
+ * True when `entryPath` is the stable vite-plus multi-package `wp` shim that
44
+ * dispatches to `@webpresso/agent-kit` via `~/.vite-plus/bins/wp.json`.
45
+ *
46
+ * Unlike {@link isWebpressoWpEntry}, this does **not** require realpath to land
47
+ * inside a package `bin/wp` tree — vite-plus's `wp` is a symlink to the `vp`
48
+ * binary, so package-root ownership is proven via the bins metadata instead.
49
+ */
50
+ export declare function isVitePlusManagedWebpressoWp(entryPath: string): boolean;
34
51
  /**
35
52
  * Resolve the absolute path to webpresso's global `bin/wp` launcher on this
36
53
  * machine. Probes the locations consumers use to install webpresso, in order
@@ -43,25 +60,43 @@ export interface WebpressoInstallProbe {
43
60
  * 3. Claude plugin install — `~/.claude/plugins/cache/.../agent-kit/`
44
61
  * 4. bun global — `~/.bun/install/global/node_modules/@webpresso/agent-kit/`
45
62
  *
46
- * Host MCP configs (Grok/Codex/etc.) must never be pinned to a versioned
63
+ * Host MCP configs (Grok/OpenCode) must never be pinned to a versioned
47
64
  * installId tree: the next `wp update` / `vp update -g` deletes that path and
48
- * leaves every host with a dead launcher. Returns `null` when none of the
49
- * candidates yield a durable `wp`.
65
+ * leaves every host with a dead launcher. `entryPath` is `null` when none of
66
+ * the candidates yield a durable `wp`.
50
67
  */
51
- export declare function findWebpressoMcpEntry(probe?: WebpressoInstallProbe): string | null;
52
- /**
53
- * True when `entryPath` is the stable vite-plus multi-package `wp` shim that
54
- * dispatches to `@webpresso/agent-kit` via `~/.vite-plus/bins/wp.json`.
55
- *
56
- * Unlike {@link isWebpressoWpEntry}, this does **not** require realpath to land
57
- * inside a package `bin/wp` tree — vite-plus's `wp` is a symlink to the `vp`
58
- * binary, so package-root ownership is proven via the bins metadata instead.
59
- */
60
- export declare function isVitePlusManagedWebpressoWp(entryPath: string): boolean;
61
68
  export declare function resolveWebpressoMcpEntry(probe?: WebpressoInstallProbe): {
62
69
  entryPath: string | null;
63
70
  checked: readonly string[];
64
71
  };
72
+ /**
73
+ * Seams for classifying a persisted host-config `command` path as agent-kit's
74
+ * own. Kept separate from {@link WebpressoInstallProbe}: that probe answers
75
+ * "where should we WRITE a launcher", this one answers "did WE write this".
76
+ */
77
+ export interface AgentKitOwnedCommandProbe {
78
+ /** Test seam — override `$HOME`. */
79
+ readonly home?: string;
80
+ /** Test seam — resolve the currently executing agent-kit package root. */
81
+ readonly sourceRoot?: () => string | null;
82
+ /** Test seam — filesystem existence check for the persisted command path. */
83
+ readonly exists?: (path: string) => boolean;
84
+ }
85
+ /**
86
+ * Every root under which agent-kit itself installs (or checks out) a `bin/wp`.
87
+ * A `wp` launcher recorded inside one of these was written by agent-kit; a
88
+ * launcher anywhere else is an operator's deliberate choice.
89
+ */
90
+ export declare function agentKitOwnedCommandRoots(probe?: AgentKitOwnedCommandProbe): readonly string[];
91
+ /**
92
+ * Ownership — not shape — decides whether setup may retire a persisted MCP
93
+ * entry. An entry is agent-kit's own when it launches `wp mcp` from an
94
+ * ABSOLUTE path AND either that path is gone (one we would have deleted) or
95
+ * it lives inside one
96
+ * of {@link agentKitOwnedCommandRoots}. A *live* `wp mcp` launcher outside
97
+ * those roots is an operator pointing Codex at a custom build: preserve it.
98
+ */
99
+ export declare function isAgentKitOwnedWpCommand(command: string, args: readonly string[], probe?: AgentKitOwnedCommandProbe): boolean;
65
100
  export declare function isWebpressoWpEntry(entryPath: string): boolean;
66
101
  /** Run a discovery probe with bounded output and SIGKILL escalation. */
67
102
  export declare function runQuiet(cmd: string, args: readonly string[], timeoutMs?: number): string | null;
@@ -4,10 +4,15 @@
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";
@@ -17,6 +22,8 @@ import { execFileSync } from "node:child_process";
17
22
  import { fileURLToPath } from "node:url";
18
23
  import { isVersionedAgentKitPayloadPath } from "#cli/auto-update/self-invocation.js";
19
24
  import { pathCandidates } from "#runtime/command-exists.js";
25
+ import { isManagedWorktreePath, isPathContained } from "#worktrees/identity.js";
26
+ import { resolveKnownManagedWorktreeRoots } from "#worktrees/location.js";
20
27
  export const WEBPRESSO_MCP_SERVER_NAME = "webpresso";
21
28
  /**
22
29
  * The single canonical launch spec for the webpresso MCP server. `name` is the
@@ -63,26 +70,6 @@ const WP_LAUNCHER_REQUIRED_RELATIVES = [
63
70
  join("bin", "runtime-lanes.js"),
64
71
  join("bin", "runtime-manifest.json"),
65
72
  ];
66
- /**
67
- * Resolve the absolute path to webpresso's global `bin/wp` launcher on this
68
- * machine. Probes the locations consumers use to install webpresso, in order
69
- * of stability:
70
- *
71
- * 1. the stable global `wp` shim available on PATH (including the
72
- * vite-plus multi-package shim at `~/.vite-plus/bin/wp`)
73
- * 2. the currently executing `@webpresso/agent-kit` package **only when it
74
- * is not a vite-plus `agent-kit#<installId>` payload path**
75
- * 3. Claude plugin install — `~/.claude/plugins/cache/.../agent-kit/`
76
- * 4. bun global — `~/.bun/install/global/node_modules/@webpresso/agent-kit/`
77
- *
78
- * Host MCP configs (Grok/Codex/etc.) must never be pinned to a versioned
79
- * installId tree: the next `wp update` / `vp update -g` deletes that path and
80
- * leaves every host with a dead launcher. Returns `null` when none of the
81
- * candidates yield a durable `wp`.
82
- */
83
- export function findWebpressoMcpEntry(probe = {}) {
84
- return resolveWebpressoMcpEntry(probe).entryPath;
85
- }
86
73
  function isUsableWebpressoMcpRoot(root) {
87
74
  return WP_LAUNCHER_REQUIRED_RELATIVES.every((relativePath) => existsSync(join(root, relativePath)));
88
75
  }
@@ -117,6 +104,23 @@ export function isVitePlusManagedWebpressoWp(entryPath) {
117
104
  function isDurableMcpEntryPath(entryPath) {
118
105
  return !isVersionedAgentKitPayloadPath(entryPath);
119
106
  }
107
+ /**
108
+ * Resolve the absolute path to webpresso's global `bin/wp` launcher on this
109
+ * machine. Probes the locations consumers use to install webpresso, in order
110
+ * of stability:
111
+ *
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**
116
+ * 3. Claude plugin install — `~/.claude/plugins/cache/.../agent-kit/`
117
+ * 4. bun global — `~/.bun/install/global/node_modules/@webpresso/agent-kit/`
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`.
123
+ */
120
124
  export function resolveWebpressoMcpEntry(probe = {}) {
121
125
  const checked = [];
122
126
  if (probe.candidates === undefined) {
@@ -151,8 +155,9 @@ export function resolveWebpressoMcpEntry(probe = {}) {
151
155
  // Never materialize host MCP configs against a vite-plus installId tree.
152
156
  if (!isDurableMcpEntryPath(wpBin))
153
157
  continue;
154
- if (isTrustedWebpressoPackageRoot(root))
158
+ if (isTrustedWebpressoPackageRoot(root, probe.sourceRoot)) {
155
159
  return { entryPath: wpBin, checked };
160
+ }
156
161
  }
157
162
  }
158
163
  return { entryPath: null, checked };
@@ -200,27 +205,78 @@ function isPathInsideOrEqual(parent, candidate) {
200
205
  const rel = relative(resolve(parentRoot), resolve(candidateRoot));
201
206
  return rel === "" || (!rel.startsWith("..") && !isAbsolute(rel));
202
207
  }
203
- function isTrustedWebpressoPackageRoot(packageRoot) {
208
+ function isTrustedWebpressoPackageRoot(packageRoot, resolveSourceRoot = currentAgentKitPackageRoot) {
204
209
  try {
205
210
  if (!isUsableWebpressoPackageRoot(packageRoot))
206
211
  return false;
207
212
  const realPackageRoot = realpathSync(packageRoot);
208
- const sourceRoot = currentAgentKitPackageRoot();
209
- 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)) {
210
221
  return true;
222
+ }
211
223
  return trustedHomeInstallPackageRoots().some((anchor) => existsSync(anchor) && isPathInsideOrEqual(anchor, realPackageRoot));
212
224
  }
213
225
  catch {
214
226
  return false;
215
227
  }
216
228
  }
217
- function trustedHomeInstallPackageRoots() {
218
- const home = process.env.HOME || homedir();
229
+ function trustedHomeInstallPackageRoots(home = process.env.HOME || homedir()) {
219
230
  return [
220
231
  join(home, ".claude", "plugins", "cache", "webpresso", "agent-kit"),
221
232
  join(home, ".bun", "install", "global", "node_modules", "@webpresso", "agent-kit"),
222
233
  ];
223
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
+ }
224
280
  function packageWpFromAdjacentNpmCmdShim(entryPath) {
225
281
  if (!entryPath.toLowerCase().endsWith(".cmd"))
226
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 = {