@webpresso/agent-kit 3.3.1 → 3.3.3

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 (87) hide show
  1. package/catalog/AGENTS.md.tpl +3 -5
  2. package/catalog/agent/rules/pre-implementation.md +15 -8
  3. package/catalog/agent/skills/ultragoal/SKILL.md +5 -18
  4. package/catalog/agent/skills/verify/SKILL.md +12 -12
  5. package/catalog/compose/admin-blocks-registry.json +659 -0
  6. package/dist/esm/blueprint/core/parser.js +2 -4
  7. package/dist/esm/blueprint/core/validation/criteria.js +2 -1
  8. package/dist/esm/blueprint/core/validation/task-blocks.js +7 -1
  9. package/dist/esm/blueprint/lifecycle/review-provenance.js +85 -30
  10. package/dist/esm/blueprint/markdown/task-heading.d.ts +21 -0
  11. package/dist/esm/blueprint/markdown/task-heading.js +25 -0
  12. package/dist/esm/blueprint/trust/command-runner.d.ts +40 -0
  13. package/dist/esm/blueprint/trust/command-runner.js +265 -41
  14. package/dist/esm/build/cli-mcp-parity.js +6 -0
  15. package/dist/esm/build/package-manifest.js +7 -2
  16. package/dist/esm/ci/native-session-memory-cache.d.ts +2 -1
  17. package/dist/esm/ci/native-session-memory-cache.js +2 -1
  18. package/dist/esm/ci/release-progress.d.ts +37 -0
  19. package/dist/esm/ci/release-progress.js +139 -0
  20. package/dist/esm/cli/cli.d.ts +1 -1
  21. package/dist/esm/cli/cli.js +9 -0
  22. package/dist/esm/cli/commands/audit-core.js +9 -1
  23. package/dist/esm/cli/commands/audit.js +16 -5
  24. package/dist/esm/cli/commands/blueprint/mutations.js +2 -3
  25. package/dist/esm/cli/commands/init/mcp-spec.d.ts +18 -3
  26. package/dist/esm/cli/commands/init/mcp-spec.js +54 -6
  27. package/dist/esm/cli/commands/init/scaffold-agents-md.js +5 -7
  28. package/dist/esm/cli/commands/package-manager.js +8 -2
  29. package/dist/esm/cli/commands/release-progress.d.ts +53 -0
  30. package/dist/esm/cli/commands/release-progress.js +155 -0
  31. package/dist/esm/cli/commands/review.js +74 -11
  32. package/dist/esm/cli/utils.d.ts +8 -1
  33. package/dist/esm/cli/utils.js +12 -2
  34. package/dist/esm/compose/compose.d.ts +38 -0
  35. package/dist/esm/compose/compose.js +143 -0
  36. package/dist/esm/compose/escape-html.d.ts +5 -0
  37. package/dist/esm/compose/escape-html.js +12 -0
  38. package/dist/esm/compose/ops-report-render.d.ts +5 -0
  39. package/dist/esm/compose/ops-report-render.js +71 -0
  40. package/dist/esm/compose/ops-report-schema.d.ts +42 -0
  41. package/dist/esm/compose/ops-report-schema.js +46 -0
  42. package/dist/esm/compose/preview.d.ts +14 -0
  43. package/dist/esm/compose/preview.js +32 -0
  44. package/dist/esm/compose/registry-resolve.d.ts +47 -0
  45. package/dist/esm/compose/registry-resolve.js +133 -0
  46. package/dist/esm/git/changed-files.d.ts +20 -0
  47. package/dist/esm/git/changed-files.js +63 -0
  48. package/dist/esm/hooks/pretool-guard/validators/plan-frontmatter.js +3 -2
  49. package/dist/esm/hooks/stop/qa-changed-files.js +6 -1
  50. package/dist/esm/mcp/blueprint/handlers/document-mutations.js +2 -1
  51. package/dist/esm/mcp/blueprint/handlers/task-advance.js +2 -12
  52. package/dist/esm/mcp/blueprint/handlers/validate.js +6 -0
  53. package/dist/esm/mcp/tools/_names.d.ts +1 -1
  54. package/dist/esm/mcp/tools/_names.js +4 -0
  55. package/dist/esm/mcp/tools/_registry.js +8 -0
  56. package/dist/esm/mcp/tools/release-progress.d.ts +9 -0
  57. package/dist/esm/mcp/tools/release-progress.js +110 -0
  58. package/dist/esm/mcp/tools/wp-ui-blocks-list.d.ts +3 -0
  59. package/dist/esm/mcp/tools/wp-ui-blocks-list.js +43 -0
  60. package/dist/esm/mcp/tools/wp-ui-compose.d.ts +3 -0
  61. package/dist/esm/mcp/tools/wp-ui-compose.js +62 -0
  62. package/dist/esm/mcp/tools/wp-ui-preview.d.ts +3 -0
  63. package/dist/esm/mcp/tools/wp-ui-preview.js +52 -0
  64. package/dist/esm/package.json +2 -0
  65. package/dist/esm/review/delivery-verifier.d.ts +33 -2
  66. package/dist/esm/review/delivery-verifier.js +130 -24
  67. package/dist/esm/review/execution/review-checkout.d.ts +1 -0
  68. package/dist/esm/review/execution/review-checkout.js +386 -23
  69. package/dist/esm/review/execution/sandbox/adapter.d.ts +37 -0
  70. package/dist/esm/review/execution/sandbox/adapter.js +39 -0
  71. package/dist/esm/review/execution/sandbox/env.d.ts +12 -0
  72. package/dist/esm/review/execution/sandbox/env.js +37 -0
  73. package/dist/esm/review/execution/sandbox/index.d.ts +5 -0
  74. package/dist/esm/review/execution/sandbox/index.js +4 -0
  75. package/dist/esm/review/execution/sandbox/policy.d.ts +19 -0
  76. package/dist/esm/review/execution/sandbox/policy.js +95 -0
  77. package/dist/esm/review/execution/sandbox/probe.d.ts +20 -0
  78. package/dist/esm/review/execution/sandbox/probe.js +97 -0
  79. package/dist/esm/review/execution/sandbox/shell-quote.d.ts +14 -0
  80. package/dist/esm/review/execution/sandbox/shell-quote.js +18 -0
  81. package/dist/esm/review/execution/sandbox/tmpdir.d.ts +9 -0
  82. package/dist/esm/review/execution/sandbox/tmpdir.js +36 -0
  83. package/dist/esm/review/execution/sandbox/types.d.ts +31 -0
  84. package/dist/esm/review/execution/sandbox/types.js +6 -0
  85. package/dist/esm/review/execution/supervisor.js +44 -5
  86. package/dist/esm/review/execution/types.d.ts +2 -0
  87. package/package.json +24 -12
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Classify a GitHub Actions Release run into operator-facing progress phases.
3
+ *
4
+ * After Version Packages merge, agents must not sit on silent `npm view` polls:
5
+ * merge, native matrix, and npm publish are separate states. This pure helper
6
+ * maps job list snapshots (from `gh run view --json jobs`) into those states.
7
+ */
8
+ export type ReleaseProgressPhase = "detect" | "native-matrix" | "publish" | "done" | "failed";
9
+ export interface ReleaseJobSnapshot {
10
+ readonly name: string;
11
+ readonly status: string;
12
+ readonly conclusion: string | null | undefined;
13
+ }
14
+ export interface ReleaseProgress {
15
+ readonly phase: ReleaseProgressPhase;
16
+ readonly detail: string;
17
+ readonly native: {
18
+ readonly done: number;
19
+ readonly total: number;
20
+ readonly failed: number;
21
+ readonly inProgress: number;
22
+ };
23
+ readonly publishRunning: boolean;
24
+ readonly publishDone: boolean;
25
+ readonly publishFailed: boolean;
26
+ }
27
+ /**
28
+ * Classify release workflow jobs into a single progress phase for agent/ops reporting.
29
+ */
30
+ export declare function classifyReleaseRunProgress(jobs: readonly ReleaseJobSnapshot[]): ReleaseProgress;
31
+ /** Format one line for agent/ops logs (merge vs matrix vs publish). */
32
+ export declare function formatReleaseProgressLine(progress: ReleaseProgress): string;
33
+ /**
34
+ * Parse `gh run view <id> --json jobs` (or full run object with `jobs`) into snapshots.
35
+ * Production CLI and tests share this path so classification is never reimplemented.
36
+ */
37
+ export declare function jobsFromGhRunViewJson(payload: unknown): ReleaseJobSnapshot[];
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Classify a GitHub Actions Release run into operator-facing progress phases.
3
+ *
4
+ * After Version Packages merge, agents must not sit on silent `npm view` polls:
5
+ * merge, native matrix, and npm publish are separate states. This pure helper
6
+ * maps job list snapshots (from `gh run view --json jobs`) into those states.
7
+ */
8
+ const NATIVE_JOB_PREFIX = "Session memory native";
9
+ function isNativeJob(name) {
10
+ return name.startsWith(NATIVE_JOB_PREFIX);
11
+ }
12
+ function isFailure(conclusion) {
13
+ return conclusion === "failure" || conclusion === "cancelled" || conclusion === "timed_out";
14
+ }
15
+ function isSuccess(conclusion) {
16
+ return conclusion === "success" || conclusion === "skipped";
17
+ }
18
+ /**
19
+ * Classify release workflow jobs into a single progress phase for agent/ops reporting.
20
+ */
21
+ export function classifyReleaseRunProgress(jobs) {
22
+ const nativeJobs = jobs.filter((job) => isNativeJob(job.name));
23
+ const detect = jobs.find((job) => job.name === "Detect publishable versions");
24
+ const release = jobs.find((job) => job.name === "Release");
25
+ const nativeDone = nativeJobs.filter((job) => job.status === "completed" && isSuccess(job.conclusion)).length;
26
+ const nativeFailed = nativeJobs.filter((job) => job.status === "completed" && isFailure(job.conclusion)).length;
27
+ const nativeInProgress = nativeJobs.filter((job) => job.status === "in_progress").length;
28
+ const native = {
29
+ done: nativeDone,
30
+ total: nativeJobs.length,
31
+ failed: nativeFailed,
32
+ inProgress: nativeInProgress,
33
+ };
34
+ const publishRunning = release?.status === "in_progress";
35
+ const publishDone = release?.status === "completed" && isSuccess(release.conclusion);
36
+ const publishFailed = release?.status === "completed" && isFailure(release.conclusion);
37
+ if (nativeFailed > 0 || publishFailed) {
38
+ return {
39
+ phase: "failed",
40
+ detail: nativeFailed > 0
41
+ ? `Native matrix failed (${nativeFailed} cell(s))`
42
+ : `Release/publish job failed (${release?.conclusion ?? "unknown"})`,
43
+ native,
44
+ publishRunning,
45
+ publishDone,
46
+ publishFailed,
47
+ };
48
+ }
49
+ if (publishDone) {
50
+ return {
51
+ phase: "done",
52
+ detail: "Release job completed successfully (check npm for version)",
53
+ native,
54
+ publishRunning,
55
+ publishDone,
56
+ publishFailed,
57
+ };
58
+ }
59
+ if (publishRunning || (native.total > 0 && nativeDone === native.total && release)) {
60
+ return {
61
+ phase: "publish",
62
+ detail: publishRunning
63
+ ? "Native matrix complete; Release/publish job in progress"
64
+ : "Native matrix complete; waiting for Release job",
65
+ native,
66
+ publishRunning,
67
+ publishDone,
68
+ publishFailed,
69
+ };
70
+ }
71
+ if (native.total > 0 && (nativeInProgress > 0 || nativeDone < native.total)) {
72
+ return {
73
+ phase: "native-matrix",
74
+ detail: `Building session-memory natives ${nativeDone}/${native.total} complete` +
75
+ (nativeInProgress > 0 ? ` (${nativeInProgress} in progress)` : ""),
76
+ native,
77
+ publishRunning,
78
+ publishDone,
79
+ publishFailed,
80
+ };
81
+ }
82
+ if (detect && detect.status !== "completed") {
83
+ return {
84
+ phase: "detect",
85
+ detail: "Detecting whether this run will publish",
86
+ native,
87
+ publishRunning,
88
+ publishDone,
89
+ publishFailed,
90
+ };
91
+ }
92
+ return {
93
+ phase: "detect",
94
+ detail: "Release run starting",
95
+ native,
96
+ publishRunning,
97
+ publishDone,
98
+ publishFailed,
99
+ };
100
+ }
101
+ /** Format one line for agent/ops logs (merge vs matrix vs publish). */
102
+ export function formatReleaseProgressLine(progress) {
103
+ return `release-progress phase=${progress.phase} native=${progress.native.done}/${progress.native.total} ${progress.detail}`;
104
+ }
105
+ /**
106
+ * Parse `gh run view <id> --json jobs` (or full run object with `jobs`) into snapshots.
107
+ * Production CLI and tests share this path so classification is never reimplemented.
108
+ */
109
+ export function jobsFromGhRunViewJson(payload) {
110
+ if (payload == null || typeof payload !== "object") {
111
+ throw new Error("gh run view JSON must be an object with a jobs array");
112
+ }
113
+ const record = payload;
114
+ const jobsRaw = Array.isArray(payload)
115
+ ? payload
116
+ : Array.isArray(record.jobs)
117
+ ? record.jobs
118
+ : null;
119
+ if (!jobsRaw) {
120
+ throw new Error("gh run view JSON must include a jobs array");
121
+ }
122
+ return jobsRaw.map((entry, index) => {
123
+ if (entry == null || typeof entry !== "object") {
124
+ throw new Error(`jobs[${index}] must be an object`);
125
+ }
126
+ const job = entry;
127
+ const name = typeof job.name === "string" ? job.name : "";
128
+ const status = typeof job.status === "string" ? job.status : "";
129
+ if (!name || !status) {
130
+ throw new Error(`jobs[${index}] requires string name and status`);
131
+ }
132
+ const conclusion = job.conclusion === null || job.conclusion === undefined
133
+ ? null
134
+ : typeof job.conclusion === "string"
135
+ ? job.conclusion
136
+ : null;
137
+ return { name, status, conclusion };
138
+ });
139
+ }
@@ -5,7 +5,7 @@
5
5
  * Lazy-loads subcommand modules based on the first argv to keep startup
6
6
  * cheap. Modeled on apps/cli-wp/src/cli.ts.
7
7
  */
8
- declare const SUPPORTED_COMMANDS: readonly ["blueprint", "browser", "dash", "dash-helper-start", "dash-helper-complete", "claude", "codex", "grok", "opencode", "yolo", "config", "secrets", "roadmap", "status", "review", "sync", "audit", "qa", "ultragoal", "compile", "rule", "skill", "skills", "docs", "setup", "init", "dev", "deploy", "preview", "cleanup", "migrate", "doctor", "err", "test", "e2e", "ci", "ci-preflight", "public:readiness", "typecheck", "lint", "format", "rust-check", "logs", "session-id", "session-info", "session", "tech-debt", "pr", "fleet", "worktree", "mcp", "hook", "hooks", "self-install-guard", "optional-tool-refresh", "gain", "bench", "install", "add", "remove", "update", "exec", "run"];
8
+ declare const SUPPORTED_COMMANDS: readonly ["blueprint", "browser", "dash", "dash-helper-start", "dash-helper-complete", "claude", "codex", "grok", "opencode", "yolo", "config", "secrets", "roadmap", "status", "review", "sync", "audit", "qa", "ultragoal", "compile", "rule", "skill", "skills", "docs", "setup", "init", "dev", "deploy", "preview", "cleanup", "migrate", "doctor", "err", "test", "e2e", "ci", "ci-preflight", "release-progress", "public:readiness", "typecheck", "lint", "format", "rust-check", "logs", "session-id", "session-info", "session", "tech-debt", "pr", "fleet", "worktree", "mcp", "hook", "hooks", "self-install-guard", "optional-tool-refresh", "gain", "bench", "install", "add", "remove", "update", "exec", "run"];
9
9
  export type SupportedCommand = (typeof SUPPORTED_COMMANDS)[number];
10
10
  type RootHelpEntry = {
11
11
  readonly command: string;
@@ -55,6 +55,7 @@ const SUPPORTED_COMMANDS = [
55
55
  "e2e",
56
56
  "ci",
57
57
  "ci-preflight",
58
+ "release-progress",
58
59
  "public:readiness",
59
60
  "typecheck",
60
61
  "lint",
@@ -118,6 +119,10 @@ const ROOT_HELP_CORE_ENTRIES = [
118
119
  command: "ci-preflight",
119
120
  description: "Run the locally-runnable required CI checks fail-fast (the pre-push gate)",
120
121
  },
122
+ {
123
+ command: "release-progress",
124
+ description: "Report Release workflow phase (detect / native-matrix / publish) instead of silent npm view waits",
125
+ },
121
126
  {
122
127
  command: "public:readiness",
123
128
  description: "Run the public package readiness gate through the portable wp surface",
@@ -375,6 +380,10 @@ const COMMAND_REGISTRARS = {
375
380
  const { registerCiPreflightCommand } = await import("./commands/ci-preflight.js");
376
381
  registerCiPreflightCommand(cli);
377
382
  },
383
+ "release-progress": async (cli) => {
384
+ const { registerReleaseProgressCommand } = await import("./commands/release-progress.js");
385
+ registerReleaseProgressCommand(cli);
386
+ },
378
387
  "public:readiness": async (cli) => {
379
388
  const { registerPublicReadinessCommand } = await import("./commands/public-readiness.js");
380
389
  registerPublicReadinessCommand(cli);
@@ -79,7 +79,15 @@ export async function runAuditDispatch(auditKind, targets, options, deps) {
79
79
  case "blueprint-pr-coverage": {
80
80
  const { auditBlueprintPrCoverage } = await import("#audit/blueprint-pr-coverage");
81
81
  const root = options.root ?? target ?? deps.root;
82
- const baseRef = options.baseRef ?? options.base;
82
+ const explicitBaseRef = options.baseRef ?? options.base;
83
+ // Same pinned-base hazard as the branch-scoped audits: CI's
84
+ // `github.event.pull_request.base.sha` stops being the fork point once the
85
+ // branch is rebased, and this audit diffs `<baseRef>...HEAD`. Narrow it to
86
+ // the real fork point so coverage is judged on this PR's own files.
87
+ const { resolveBranchDiffBase } = await import("#git/changed-files");
88
+ const baseRef = explicitBaseRef
89
+ ? resolveBranchDiffBase(root, explicitBaseRef)
90
+ : explicitBaseRef;
83
91
  const result = auditBlueprintPrCoverage(root, { baseRef, headRef: options.headRef });
84
92
  return { kind: "repo-result", name: "blueprint-pr-coverage", result };
85
93
  }
@@ -9,7 +9,7 @@ import { STRYKER_COMMAND } from "#audit/run-stryker.js";
9
9
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
10
10
  import path from "node:path";
11
11
  import { addAffectedOptions, resolveAffectedTargets } from "#git/affected";
12
- import { defaultBranchBaseRef, getBranchRemovedFiles, getStagedRemovedFiles, } from "#git/changed-files";
12
+ import { defaultBranchBaseRef, getBranchRemovedFiles, getStagedRemovedFiles, resolveBranchDiffBase, } from "#git/changed-files";
13
13
  import { runAuditDispatch } from "#cli/commands/audit-core.js";
14
14
  import { createCliLogSink } from "#cli/commands/quality-log-store.js";
15
15
  import { emitCliCommandOutput, runLoggedChildCommand } from "#cli/commands/quality-runner.js";
@@ -284,6 +284,17 @@ export function registerAuditCommand(cli) {
284
284
  let dispatchOptions = options;
285
285
  let auditScope = "full";
286
286
  let changeRemovesFile = false;
287
+ // A caller-supplied `--base` is a PINNED sha (CI passes
288
+ // `github.event.pull_request.base.sha`, which does not advance with the
289
+ // base branch). After a rebase it stops being the fork point, so scoping
290
+ // off it silently absorbs everything merged into the base branch since
291
+ // the PR opened. Narrow it to the real fork point once, and use that one
292
+ // value everywhere the branch scope is derived so the affected file set,
293
+ // the removal probe, and the audit's own base-vs-head range agree.
294
+ const explicitBaseRef = options.baseRef ?? options.base;
295
+ const branchBaseRef = branch && explicitBaseRef
296
+ ? resolveBranchDiffBase(auditRoot, explicitBaseRef)
297
+ : explicitBaseRef;
287
298
  if (affected || branch) {
288
299
  if (kind &&
289
300
  AUDIT_KINDS.includes(kind) &&
@@ -309,7 +320,7 @@ export function registerAuditCommand(cli) {
309
320
  cwd: auditRoot,
310
321
  affected,
311
322
  branch,
312
- baseRef: options.baseRef ?? options.base,
323
+ baseRef: branchBaseRef,
313
324
  explicitTargets: [],
314
325
  policy: "fail-closed",
315
326
  mapChangedFiles: (files) => files,
@@ -360,7 +371,7 @@ export function registerAuditCommand(cli) {
360
371
  changeRemovesFile = affected
361
372
  ? affectedChangeRemovesFile(auditRoot, {
362
373
  branch,
363
- baseRef: options.baseRef ?? options.base,
374
+ baseRef: branchBaseRef,
364
375
  })
365
376
  : false;
366
377
  if (resolution.kind === "empty" && !changeRemovesFile) {
@@ -392,7 +403,7 @@ export function registerAuditCommand(cli) {
392
403
  changedOnly: true,
393
404
  affectedFiles: [],
394
405
  root: options.root ?? resolution.cwd,
395
- baseRef: options.baseRef ?? options.base,
406
+ baseRef: branchBaseRef,
396
407
  };
397
408
  }
398
409
  if (resolution.kind === "scoped") {
@@ -404,7 +415,7 @@ export function registerAuditCommand(cli) {
404
415
  changedOnly: true,
405
416
  affectedFiles: [...resolution.targets],
406
417
  root: options.root ?? resolution.cwd,
407
- baseRef: options.baseRef ?? options.base,
418
+ baseRef: branchBaseRef,
408
419
  };
409
420
  }
410
421
  }
@@ -17,7 +17,7 @@ import { randomUUID } from "node:crypto";
17
17
  import { tmpdir } from "node:os";
18
18
  import path from "node:path";
19
19
  import { parseBlueprint } from "#core/parser";
20
- import { escapeRegex } from "#utils/string";
20
+ import { buildTaskHeaderRegexForId } from "#markdown/task-heading";
21
21
  import { openDb } from "#db/connection.js";
22
22
  import { resolveBlueprintProjectionDbPath, withMarkdownWriteLock } from "#db/paths.js";
23
23
  import { getBlueprintDocumentPaths } from "#utils/document-paths.js";
@@ -209,8 +209,7 @@ function upsertCompletedAt(content, isoDate) {
209
209
  * Returns { lineIndex, currentStatus } or null if not found.
210
210
  */
211
211
  function findTaskStatusLine(lines, taskId) {
212
- const escapedId = escapeRegex(taskId);
213
- const taskPattern = new RegExp(`^####\\s+(?:\\[[^\\]]+\\]\\s+)?Task\\s+${escapedId}[:\\s]`);
212
+ const taskPattern = buildTaskHeaderRegexForId(taskId);
214
213
  let inBlock = false;
215
214
  for (let i = 0; i < lines.length; i++) {
216
215
  const line = lines[i] ?? "";
@@ -36,13 +36,28 @@ export interface WebpressoInstallProbe {
36
36
  * machine. Probes the locations consumers use to install webpresso, in order
37
37
  * of stability:
38
38
  *
39
- * 1. the stable global `wp` shim available on PATH
40
- * 2. the currently executing `@webpresso/agent-kit` package
39
+ * 1. the stable global `wp` shim available on PATH (including the
40
+ * vite-plus multi-package shim at `~/.vite-plus/bin/wp`)
41
+ * 2. the currently executing `@webpresso/agent-kit` package **only when it
42
+ * is not a vite-plus `agent-kit#<installId>` payload path**
41
43
  * 3. Claude plugin install — `~/.claude/plugins/cache/.../agent-kit/`
42
44
  * 4. bun global — `~/.bun/install/global/node_modules/@webpresso/agent-kit/`
43
- * Returns `null` when none of the candidates contain a usable `bin/wp`.
45
+ *
46
+ * Host MCP configs (Grok/Codex/etc.) must never be pinned to a versioned
47
+ * 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`.
44
50
  */
45
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;
46
61
  export declare function resolveWebpressoMcpEntry(probe?: WebpressoInstallProbe): {
47
62
  entryPath: string | null;
48
63
  checked: readonly string[];
@@ -12,9 +12,10 @@
12
12
  */
13
13
  import { accessSync, constants, existsSync, readFileSync, realpathSync, statSync } from "node:fs";
14
14
  import { homedir } from "node:os";
15
- import { dirname, isAbsolute, join, relative, resolve } from "node:path";
15
+ import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
16
16
  import { execFileSync } from "node:child_process";
17
17
  import { fileURLToPath } from "node:url";
18
+ import { isVersionedAgentKitPayloadPath } from "#cli/auto-update/self-invocation.js";
18
19
  import { pathCandidates } from "#runtime/command-exists.js";
19
20
  export const WEBPRESSO_MCP_SERVER_NAME = "webpresso";
20
21
  /**
@@ -67,11 +68,17 @@ const WP_LAUNCHER_REQUIRED_RELATIVES = [
67
68
  * machine. Probes the locations consumers use to install webpresso, in order
68
69
  * of stability:
69
70
  *
70
- * 1. the stable global `wp` shim available on PATH
71
- * 2. the currently executing `@webpresso/agent-kit` package
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**
72
75
  * 3. Claude plugin install — `~/.claude/plugins/cache/.../agent-kit/`
73
76
  * 4. bun global — `~/.bun/install/global/node_modules/@webpresso/agent-kit/`
74
- * Returns `null` when none of the candidates contain a usable `bin/wp`.
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`.
75
82
  */
76
83
  export function findWebpressoMcpEntry(probe = {}) {
77
84
  return resolveWebpressoMcpEntry(probe).entryPath;
@@ -79,6 +86,37 @@ export function findWebpressoMcpEntry(probe = {}) {
79
86
  function isUsableWebpressoMcpRoot(root) {
80
87
  return WP_LAUNCHER_REQUIRED_RELATIVES.every((relativePath) => existsSync(join(root, relativePath)));
81
88
  }
89
+ /**
90
+ * True when `entryPath` is the stable vite-plus multi-package `wp` shim that
91
+ * dispatches to `@webpresso/agent-kit` via `~/.vite-plus/bins/wp.json`.
92
+ *
93
+ * Unlike {@link isWebpressoWpEntry}, this does **not** require realpath to land
94
+ * inside a package `bin/wp` tree — vite-plus's `wp` is a symlink to the `vp`
95
+ * binary, so package-root ownership is proven via the bins metadata instead.
96
+ */
97
+ export function isVitePlusManagedWebpressoWp(entryPath) {
98
+ try {
99
+ if (isVersionedAgentKitPayloadPath(entryPath))
100
+ return false;
101
+ const binDir = dirname(entryPath);
102
+ if (basename(binDir) !== "bin")
103
+ return false;
104
+ if (basename(dirname(binDir)) !== ".vite-plus")
105
+ return false;
106
+ const base = basename(entryPath).toLowerCase();
107
+ if (base !== "wp" && base !== "wp.exe")
108
+ return false;
109
+ const metaPath = join(dirname(binDir), "bins", "wp.json");
110
+ const meta = JSON.parse(readFileSync(metaPath, "utf8"));
111
+ return meta.package === "@webpresso/agent-kit";
112
+ }
113
+ catch {
114
+ return false;
115
+ }
116
+ }
117
+ function isDurableMcpEntryPath(entryPath) {
118
+ return !isVersionedAgentKitPayloadPath(entryPath);
119
+ }
82
120
  export function resolveWebpressoMcpEntry(probe = {}) {
83
121
  const checked = [];
84
122
  if (probe.candidates === undefined) {
@@ -88,10 +126,17 @@ export function resolveWebpressoMcpEntry(probe = {}) {
88
126
  if (stableEntry) {
89
127
  checked.push(stableEntry);
90
128
  const packageWp = packageWpFromAdjacentNpmCmdShim(stableEntry);
91
- if (packageWp !== null)
129
+ if (packageWp !== null && isDurableMcpEntryPath(packageWp)) {
92
130
  return { entryPath: packageWp, checked };
131
+ }
93
132
  const verifierAllows = probe.stableEntryIsWebpresso?.(stableEntry) ?? true;
94
- if (verifierAllows && isWebpressoWpEntry(stableEntry)) {
133
+ // Prefer the vite-plus multi-package shim before package-root ownership:
134
+ // it survives installId swaps and is what PATH `wp` resolves to in the
135
+ // default consumer install.
136
+ if (verifierAllows && isVitePlusManagedWebpressoWp(stableEntry)) {
137
+ return { entryPath: stableEntry, checked };
138
+ }
139
+ if (verifierAllows && isWebpressoWpEntry(stableEntry) && isDurableMcpEntryPath(stableEntry)) {
95
140
  return { entryPath: stableEntry, checked };
96
141
  }
97
142
  }
@@ -103,6 +148,9 @@ export function resolveWebpressoMcpEntry(probe = {}) {
103
148
  continue;
104
149
  checked.push(root);
105
150
  const wpBin = join(root, WP_BIN_RELATIVE);
151
+ // Never materialize host MCP configs against a vite-plus installId tree.
152
+ if (!isDurableMcpEntryPath(wpBin))
153
+ continue;
106
154
  if (isTrustedWebpressoPackageRoot(root))
107
155
  return { entryPath: wpBin, checked };
108
156
  }
@@ -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;
@@ -184,6 +184,9 @@ function runGlobalUpdateCommand(deps) {
184
184
  else {
185
185
  failed = true;
186
186
  console.error(message);
187
+ // Required step failed — stop so optional host refreshes cannot
188
+ // look like a successful update after a broken agent-kit install.
189
+ break;
187
190
  }
188
191
  }
189
192
  }
@@ -195,6 +198,7 @@ function runGlobalUpdateCommand(deps) {
195
198
  else {
196
199
  failed = true;
197
200
  console.error(message);
201
+ break;
198
202
  }
199
203
  }
200
204
  }
@@ -313,8 +317,10 @@ function runAgentKitSelfUpdateStep(deps) {
313
317
  return spawnLike(status, new Error("another agent-kit installer is active; re-run `wp update` once it finishes"));
314
318
  case GUARD_EXIT_OWNERSHIP_LOST:
315
319
  return spawnLike(status, new Error("install lease ownership was lost mid-update; the takeover installer verifies the store — re-run `wp update` to confirm"));
316
- default:
317
- return spawnLike(status === 0 ? 1 : status, new Error(`self-install-guard exited ${status}`));
320
+ default: {
321
+ const code = status === 0 ? 1 : status;
322
+ return spawnLike(code, new Error(`agent-kit self-install failed (self-install-guard exit ${code}); re-run \`wp update\` or \`vp install -g @webpresso/agent-kit\``));
323
+ }
318
324
  }
319
325
  }
320
326
  function refreshCodexPlugin(deps) {
@@ -0,0 +1,53 @@
1
+ /**
2
+ * `wp release-progress` — report Release workflow phase (detect / native-matrix / publish).
3
+ *
4
+ * Agents must not poll only `npm view` after Version Packages merge. This command
5
+ * loads the live GitHub Actions job list and classifies progress via
6
+ * {@link classifyReleaseRunProgress}.
7
+ */
8
+ import type { CAC } from "cac";
9
+ import { type ReleaseJobSnapshot, type ReleaseProgress } from "#ci/release-progress.js";
10
+ export declare const RELEASE_PROGRESS_COMMAND_HELP: string;
11
+ export interface GhRunJobsFetcher {
12
+ (options: {
13
+ readonly runId: string;
14
+ readonly repo: string;
15
+ }): ReleaseJobSnapshot[];
16
+ }
17
+ export interface LatestReleaseRunResolver {
18
+ (options: {
19
+ readonly repo: string;
20
+ }): string;
21
+ }
22
+ /** Production fetcher: `gh run view --json jobs`. */
23
+ export declare function fetchReleaseJobsViaGh(options: {
24
+ readonly runId: string;
25
+ readonly repo: string;
26
+ }): ReleaseJobSnapshot[];
27
+ /** Production resolver: latest Release workflow run for the repo. */
28
+ export declare function resolveLatestReleaseRunIdViaGh(options: {
29
+ readonly repo: string;
30
+ }): string;
31
+ export interface RunReleaseProgressOptions {
32
+ readonly runId?: string | number;
33
+ readonly repo?: string;
34
+ readonly json?: boolean;
35
+ readonly fetchJobs?: GhRunJobsFetcher;
36
+ readonly resolveLatestRunId?: LatestReleaseRunResolver;
37
+ readonly stdout?: {
38
+ write(chunk: string): void;
39
+ };
40
+ readonly stderr?: {
41
+ write(chunk: string): void;
42
+ };
43
+ }
44
+ /**
45
+ * Core command body — injectable for tests; production uses gh CLI defaults.
46
+ */
47
+ export declare function runReleaseProgress(options?: RunReleaseProgressOptions): {
48
+ readonly exitCode: number;
49
+ readonly progress: ReleaseProgress;
50
+ readonly runId: string;
51
+ readonly line: string;
52
+ };
53
+ export declare function registerReleaseProgressCommand(cli: CAC): void;