@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.
- package/THIRD-PARTY-NOTICES.md +12 -10
- package/catalog/AGENTS.md.tpl +3 -5
- package/catalog/agent/rules/changeset-release.md +15 -4
- package/catalog/agent/rules/managed-tool-binaries.md +67 -0
- package/catalog/agent/rules/parallel-orchestration-quota.md +99 -1
- package/catalog/agent/rules/pre-implementation.md +36 -20
- package/catalog/agent/rules/rtk-routing.md +22 -16
- package/catalog/agent/skills/autopilot/SKILL.md +4 -3
- package/catalog/agent/skills/ultragoal/SKILL.md +11 -9
- package/catalog/agent/skills/verify/SKILL.md +12 -12
- package/catalog/compose/admin-blocks-registry.json +659 -0
- package/dist/esm/audit/hook-surface.js +5 -1
- package/dist/esm/audit/repo-guardrails.d.ts +8 -0
- package/dist/esm/audit/repo-guardrails.js +71 -1
- package/dist/esm/blueprint/core/parser.js +40 -16
- package/dist/esm/blueprint/core/validation/criteria.js +2 -1
- package/dist/esm/blueprint/execution/artifacts.js +8 -4
- package/dist/esm/blueprint/execution/frontmatter-keys.d.ts +17 -0
- package/dist/esm/blueprint/execution/frontmatter-keys.js +24 -0
- package/dist/esm/blueprint/execution/metadata.js +8 -4
- package/dist/esm/blueprint/lifecycle/audit.d.ts +21 -0
- package/dist/esm/blueprint/lifecycle/audit.js +89 -3
- package/dist/esm/blueprint/lifecycle/review-provenance.js +148 -40
- package/dist/esm/blueprint/lifecycle/rollout-anchor.d.ts +22 -0
- package/dist/esm/blueprint/lifecycle/rollout-anchor.js +37 -1
- package/dist/esm/blueprint/markdown/blocked-line.d.ts +49 -0
- package/dist/esm/blueprint/markdown/blocked-line.js +63 -0
- package/dist/esm/blueprint/markdown/helpers.js +10 -4
- package/dist/esm/blueprint/tracked-document/parser.js +3 -3
- package/dist/esm/blueprint/trust/gate-row.d.ts +55 -0
- package/dist/esm/blueprint/trust/gate-row.js +113 -0
- package/dist/esm/blueprint/trust/promotion.d.ts +17 -0
- package/dist/esm/blueprint/trust/promotion.js +19 -19
- package/dist/esm/build/atomic-file-copy.d.ts +32 -0
- package/dist/esm/build/atomic-file-copy.js +44 -0
- package/dist/esm/build/cli-mcp-parity.js +3 -2
- package/dist/esm/build/package-manifest.js +7 -2
- package/dist/esm/build/vendor-oxlint-plugins.d.ts +8 -0
- package/dist/esm/build/vendor-oxlint-plugins.js +53 -0
- package/dist/esm/cli/commands/audit-core.js +9 -1
- package/dist/esm/cli/commands/audit.js +16 -5
- package/dist/esm/cli/commands/blueprint/mutations.d.ts +15 -0
- package/dist/esm/cli/commands/blueprint/mutations.js +8 -2
- package/dist/esm/cli/commands/compile.d.ts +13 -0
- package/dist/esm/cli/commands/compile.js +182 -62
- package/dist/esm/cli/commands/format.js +17 -1
- package/dist/esm/cli/commands/gain/index.js +10 -3
- package/dist/esm/cli/commands/init/gitignore-patcher.js +6 -0
- package/dist/esm/cli/commands/init/index.js +29 -48
- package/dist/esm/cli/commands/init/mcp-spec.d.ts +54 -4
- package/dist/esm/cli/commands/init/mcp-spec.js +126 -22
- package/dist/esm/cli/commands/init/merge.js +30 -10
- package/dist/esm/cli/commands/init/scaffold-agents-md.js +5 -7
- package/dist/esm/cli/commands/init/scaffolders/codex-mcp/index.d.ts +56 -31
- package/dist/esm/cli/commands/init/scaffolders/codex-mcp/index.js +180 -43
- package/dist/esm/cli/commands/init/scaffolders/rtk/index.d.ts +17 -5
- package/dist/esm/cli/commands/init/scaffolders/rtk/index.js +56 -75
- package/dist/esm/cli/commands/init/scaffolders/runtime-check/index.js +7 -6
- package/dist/esm/cli/commands/init/untracked-collision.d.ts +12 -1
- package/dist/esm/cli/commands/init/untracked-collision.js +23 -1
- package/dist/esm/cli/commands/package-manager.js +8 -2
- package/dist/esm/cli/commands/pr-help.js +1 -1
- package/dist/esm/cli/commands/pr.js +18 -4
- package/dist/esm/cli/commands/review.js +60 -6
- package/dist/esm/cli/commands/worktree/router-dispatch.d.ts +24 -2
- package/dist/esm/cli/commands/worktree/router-dispatch.js +80 -48
- package/dist/esm/cli/commands/worktree/router.js +5 -2
- package/dist/esm/cli/direct-provider-launch.js +12 -4
- package/dist/esm/compiler/flatten.d.ts +10 -1
- package/dist/esm/compiler/flatten.js +14 -4
- package/dist/esm/compose/admin-blocks-registry-integrity.d.ts +22 -0
- package/dist/esm/compose/admin-blocks-registry-integrity.js +90 -0
- package/dist/esm/compose/compose.d.ts +46 -0
- package/dist/esm/compose/compose.js +250 -0
- package/dist/esm/compose/escape-html.d.ts +5 -0
- package/dist/esm/compose/escape-html.js +12 -0
- package/dist/esm/compose/ops-report-render.d.ts +5 -0
- package/dist/esm/compose/ops-report-render.js +71 -0
- package/dist/esm/compose/ops-report-schema.d.ts +42 -0
- package/dist/esm/compose/ops-report-schema.js +60 -0
- package/dist/esm/compose/preview.d.ts +14 -0
- package/dist/esm/compose/preview.js +32 -0
- package/dist/esm/compose/registry-resolve.d.ts +64 -0
- package/dist/esm/compose/registry-resolve.js +200 -0
- package/dist/esm/config/oxlint/graphql-conventions.js +2 -5
- package/dist/esm/config/oxlint/import-hygiene.js +21 -8
- package/dist/esm/config/oxlint/oxlint-config.fixture.d.ts +1 -1
- package/dist/esm/config/oxlint/oxlint-config.fixture.js +2 -3
- package/dist/esm/config/oxlint/oxlintrc.d.ts +5 -7
- package/dist/esm/config/oxlint/oxlintrc.js +18 -21
- package/dist/esm/config/oxlint/path-roles.js +53 -0
- package/dist/esm/config/oxlint/query-patterns.js +2 -5
- package/dist/esm/daemon/domains/blueprint-exec-ledger.d.ts +86 -0
- package/dist/esm/daemon/domains/blueprint-exec-ledger.js +134 -0
- package/dist/esm/daemon/domains/blueprint-exec.d.ts +106 -0
- package/dist/esm/daemon/domains/blueprint-exec.js +161 -0
- package/dist/esm/daemon/domains/launch.d.ts +25 -0
- package/dist/esm/daemon/domains/launch.js +30 -1
- package/dist/esm/daemon/handlers/blueprints-handlers.d.ts +12 -1
- package/dist/esm/daemon/handlers/blueprints-handlers.js +148 -6
- package/dist/esm/daemon/handlers/product.d.ts +10 -0
- package/dist/esm/daemon/handlers/product.js +10 -1
- package/dist/esm/daemon/methods.d.ts +21 -1
- package/dist/esm/daemon/methods.js +20 -0
- package/dist/esm/daemon/protocol/contract.d.ts +58 -0
- package/dist/esm/daemon/protocol/contract.js +38 -0
- package/dist/esm/daemon/server.js +7 -0
- package/dist/esm/docs-linter/blueprint-plan.js +2 -2
- package/dist/esm/errors/wp-error.js +21 -2
- package/dist/esm/git/changed-files.d.ts +20 -0
- package/dist/esm/git/changed-files.js +63 -0
- package/dist/esm/hooks/doctor.d.ts +11 -0
- package/dist/esm/hooks/doctor.js +114 -45
- package/dist/esm/hooks/pretool-guard/dev-routing.js +1 -1
- package/dist/esm/hooks/pretool-guard/rtk-rewrite.d.ts +24 -0
- package/dist/esm/hooks/pretool-guard/rtk-rewrite.js +50 -0
- package/dist/esm/hooks/pretool-guard/runner.js +6 -0
- package/dist/esm/hooks/status/index.js +1 -1
- package/dist/esm/hooks/stop/qa-changed-files.js +6 -1
- package/dist/esm/mcp/blueprint/_shared/lifecycle.js +39 -11
- package/dist/esm/mcp/blueprint/handlers/review-log.d.ts +14 -7
- package/dist/esm/mcp/blueprint/handlers/review-log.js +46 -22
- package/dist/esm/mcp/blueprint/handlers/validate.js +6 -0
- package/dist/esm/mcp/blueprint/registration.js +1 -1
- package/dist/esm/mcp/tools/_names.d.ts +1 -1
- package/dist/esm/mcp/tools/_names.js +4 -0
- package/dist/esm/mcp/tools/_registry.js +8 -0
- package/dist/esm/mcp/tools/_shared/redact.d.ts +22 -0
- package/dist/esm/mcp/tools/_shared/redact.js +114 -8
- package/dist/esm/mcp/tools/gain.js +28 -2
- package/dist/esm/mcp/tools/release-progress.d.ts +9 -0
- package/dist/esm/mcp/tools/release-progress.js +110 -0
- package/dist/esm/mcp/tools/worktree.js +65 -3
- package/dist/esm/mcp/tools/wp-ui-blocks-list.d.ts +3 -0
- package/dist/esm/mcp/tools/wp-ui-blocks-list.js +43 -0
- package/dist/esm/mcp/tools/wp-ui-compose.d.ts +3 -0
- package/dist/esm/mcp/tools/wp-ui-compose.js +96 -0
- package/dist/esm/mcp/tools/wp-ui-preview.d.ts +3 -0
- package/dist/esm/mcp/tools/wp-ui-preview.js +59 -0
- package/dist/esm/package.json +2 -0
- package/dist/esm/review/lifecycle-writers.d.ts +76 -0
- package/dist/esm/review/lifecycle-writers.js +330 -0
- package/dist/esm/review/subject.js +66 -32
- package/dist/esm/status/snapshot.d.ts +5 -0
- package/dist/esm/status/snapshot.js +27 -2
- package/dist/esm/test/shard-durations.json +0 -1
- package/dist/esm/test-helpers/global-setup.js +38 -3
- package/dist/esm/tool-runtime/managed-rtk.d.ts +44 -0
- package/dist/esm/tool-runtime/managed-rtk.js +275 -0
- package/dist/esm/tool-runtime/resolve-runner.js +18 -194
- package/dist/esm/utils/package-root.d.ts +32 -0
- package/dist/esm/utils/package-root.js +82 -0
- package/dist/esm/worktrees/git-metadata.d.ts +41 -0
- package/dist/esm/worktrees/git-metadata.js +48 -0
- package/package.json +27 -21
- package/dist/esm/config/oxlint/code-safety.d.ts +0 -18
- package/dist/esm/config/oxlint/foundation-purity.d.ts +0 -21
- package/dist/esm/config/oxlint/graphql-conventions.d.ts +0 -23
- package/dist/esm/config/oxlint/import-hygiene.d.ts +0 -34
- package/dist/esm/config/oxlint/index.d.ts +0 -22
- package/dist/esm/config/oxlint/index.js +0 -28
- package/dist/esm/config/oxlint/monorepo-paths.d.ts +0 -22
- package/dist/esm/config/oxlint/query-patterns.d.ts +0 -24
- package/dist/esm/config/oxlint/testing-quality.d.ts +0 -35
- package/dist/esm/config/oxlint/tier-boundaries.d.ts +0 -35
- package/dist/esm/output-transforms/rulesync.d.ts +0 -2
- package/dist/esm/output-transforms/rulesync.js +0 -79
|
@@ -1 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Redaction for the shared read-only MCP path (`wp_pr_status`, `wp_pr_wait`,
|
|
3
|
+
* `wp_run_wait` error tails, `_readonly-ops`, worker tails, hook logs — 11
|
|
4
|
+
* consumers). Output reaches agent context, session memory, and committed
|
|
5
|
+
* review artifacts in a public repository, so a credential surviving here is
|
|
6
|
+
* irreversible and externally exploitable.
|
|
7
|
+
*
|
|
8
|
+
* Three properties are load-bearing; changing any of them breaks a consumer:
|
|
9
|
+
*
|
|
10
|
+
* 1. {@link mask} is **partial disclosure by design** — first and last two
|
|
11
|
+
* characters survive above six, and the mask is deterministic.
|
|
12
|
+
* `run-wait.ts` redacts both sides of its SHA comparison and relies on
|
|
13
|
+
* identical input producing identical output.
|
|
14
|
+
* 2. The generic 40+-character rule masks full commit SHAs as a side effect,
|
|
15
|
+
* compensated downstream by that same mask-aware equality. Do not "fix" it
|
|
16
|
+
* here without updating those consumers.
|
|
17
|
+
* 3. **Every quantifier is bounded.** Callers pass unbounded log text —
|
|
18
|
+
* `run-wait.ts` redacts the whole tail *before* clipping it to 2000 bytes —
|
|
19
|
+
* so an unbounded `[A-Za-z0-9_]*` around a keyword alternation backtracks
|
|
20
|
+
* quadratically and a hostile CI log can stall the MCP path for tens of
|
|
21
|
+
* seconds. Keep the `{0,64}` bounds.
|
|
22
|
+
*/
|
|
1
23
|
export declare function redactText(value: string | undefined): string | undefined;
|
|
@@ -1,22 +1,128 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Redaction for the shared read-only MCP path (`wp_pr_status`, `wp_pr_wait`,
|
|
3
|
+
* `wp_run_wait` error tails, `_readonly-ops`, worker tails, hook logs — 11
|
|
4
|
+
* consumers). Output reaches agent context, session memory, and committed
|
|
5
|
+
* review artifacts in a public repository, so a credential surviving here is
|
|
6
|
+
* irreversible and externally exploitable.
|
|
7
|
+
*
|
|
8
|
+
* Three properties are load-bearing; changing any of them breaks a consumer:
|
|
9
|
+
*
|
|
10
|
+
* 1. {@link mask} is **partial disclosure by design** — first and last two
|
|
11
|
+
* characters survive above six, and the mask is deterministic.
|
|
12
|
+
* `run-wait.ts` redacts both sides of its SHA comparison and relies on
|
|
13
|
+
* identical input producing identical output.
|
|
14
|
+
* 2. The generic 40+-character rule masks full commit SHAs as a side effect,
|
|
15
|
+
* compensated downstream by that same mask-aware equality. Do not "fix" it
|
|
16
|
+
* here without updating those consumers.
|
|
17
|
+
* 3. **Every quantifier is bounded.** Callers pass unbounded log text —
|
|
18
|
+
* `run-wait.ts` redacts the whole tail *before* clipping it to 2000 bytes —
|
|
19
|
+
* so an unbounded `[A-Za-z0-9_]*` around a keyword alternation backtracks
|
|
20
|
+
* quadratically and a hostile CI log can stall the MCP path for tens of
|
|
21
|
+
* seconds. Keep the `{0,64}` bounds.
|
|
22
|
+
*/
|
|
23
|
+
/** Key-name fragments that advertise a secret. */
|
|
24
|
+
const SECRET_WORD = "PASSWORD|PASSWD|SECRET|TOKEN|API[_-]?KEY|ACCESS[_-]?KEY|PRIVATE[_-]?KEY|CREDENTIALS?";
|
|
25
|
+
/**
|
|
26
|
+
* Optional opening quote OUTSIDE the capture, then the value up to the first
|
|
27
|
+
* closing delimiter.
|
|
28
|
+
*
|
|
29
|
+
* Both halves matter. Excluding quotes from the class stops a greedy capture
|
|
30
|
+
* pulling the closing quote into the mask (`sk***6"` instead of `sk***56`).
|
|
31
|
+
* Consuming an optional *opening* quote is what keeps `KEY="secret"` matching
|
|
32
|
+
* at all — omitting it makes the `+` match zero characters, the pattern fail,
|
|
33
|
+
* and the most common real-world shape (quoted shell/JSON/YAML values) sail
|
|
34
|
+
* through completely unredacted.
|
|
35
|
+
*/
|
|
36
|
+
// `\x60` is the backtick: a literal one cannot appear inside a String.raw
|
|
37
|
+
// template, and `\`` is an invalid escape under the `u` flag.
|
|
38
|
+
const QUOTED_VALUE = String.raw `["'\x60]?([^\s"'\x60,;)\]}\[]+)`;
|
|
39
|
+
/** As above, with a length floor — see the `:` rule below. */
|
|
40
|
+
const QUOTED_VALUE_LONG = String.raw `["'\x60]?([^\s"'\x60,;)\]}\[]{12,})`;
|
|
41
|
+
/**
|
|
42
|
+
* Optional closing quote on the KEY, before the separator. JSON and quoted-key
|
|
43
|
+
* YAML put it there — `{"password":"…"}` — and without this the separator match
|
|
44
|
+
* fails on the key's own closing quote and the whole line sails through.
|
|
45
|
+
*/
|
|
46
|
+
const KEY_CLOSE = String.raw `["']?[ \t]*`;
|
|
47
|
+
/**
|
|
48
|
+
* Credential value for the keyword rules (`Bearer`, `Basic`, `Cookie`), which
|
|
49
|
+
* carry no assignment signal at all — and whose keywords are ordinary English
|
|
50
|
+
* words. Without a floor, `Basic checks passed` becomes
|
|
51
|
+
* `Basic [REDACTED] passed`, and that reaches users: `pr-upsert.ts` pushes PR
|
|
52
|
+
* **titles** through this function, so "Add Bearer token support" would render
|
|
53
|
+
* corrupted.
|
|
54
|
+
*
|
|
55
|
+
* So the value must be ≥12 characters AND contain at least one uppercase
|
|
56
|
+
* letter, digit or base64 punctuation — enough to separate `dXNlcjpwYXNzd29yZA==`
|
|
57
|
+
* from `authentication`.
|
|
58
|
+
*
|
|
59
|
+
* The keyword rules therefore drop the `i` flag and spell each casing out.
|
|
60
|
+
* Under `i`, `[A-Z]` also matches lowercase, so this lookahead would be
|
|
61
|
+
* silently satisfied by any long lowercase word and the check would do nothing.
|
|
62
|
+
*/
|
|
63
|
+
const KEYWORD_CRED = String.raw `["'\x60]?((?:(?=[^\s"'\x60,;)\]}\[]*[A-Z0-9+/=_-])[^\s"'\x60,;)\]}\[]{12,}|[^\s"'\x60,;)\]}\[]{24,}))`;
|
|
64
|
+
/**
|
|
65
|
+
* Patterns whose **first capture group** is the secret. Only that group is
|
|
66
|
+
* masked, so the header name, config key or URL host stays readable — a
|
|
67
|
+
* redactor that destroys the line it protects gets routed around.
|
|
68
|
+
*/
|
|
69
|
+
const CAPTURING_PATTERNS = [
|
|
70
|
+
// Originally-named credential env assignments.
|
|
71
|
+
new RegExp(String.raw `\b(?:GH_PACKAGES_TOKEN|GITHUB_TOKEN|GITHUB_PAT|NEON_API_KEY(?:_PLATFORM)?|CHEF_CI_TOKEN)\b` +
|
|
72
|
+
KEY_CLOSE +
|
|
73
|
+
String.raw `=[ \t]*` +
|
|
74
|
+
QUOTED_VALUE, "giu"),
|
|
75
|
+
// Generic secret-named key with `=`. Assignment is an unambiguous signal, so
|
|
76
|
+
// no length floor: `PASSWORD=x` should mask even when short.
|
|
77
|
+
new RegExp(String.raw `\b[A-Za-z0-9_]{0,64}(?:${SECRET_WORD})[A-Za-z0-9_]{0,64}` +
|
|
78
|
+
KEY_CLOSE +
|
|
79
|
+
String.raw `=[ \t]*` +
|
|
80
|
+
QUOTED_VALUE, "giu"),
|
|
81
|
+
// Generic secret-named key with `:`. Deliberately stricter than `=`, because
|
|
82
|
+
// `:` is overloaded across YAML, tables, markdown and plain prose — without a
|
|
83
|
+
// floor this masks `tokens: 512`, `secret: true`, `credentials: verified for
|
|
84
|
+
// 3 users` and `**Token:** an opaque handle`, corrupting ordinary CI output.
|
|
85
|
+
// A 12-character floor admits real secrets while excluding those.
|
|
86
|
+
new RegExp(String.raw `\b[A-Za-z0-9_]{0,64}(?:${SECRET_WORD})[A-Za-z0-9_]{0,64}` +
|
|
87
|
+
KEY_CLOSE +
|
|
88
|
+
String.raw `:[ \t]*` +
|
|
89
|
+
QUOTED_VALUE_LONG, "giu"),
|
|
90
|
+
// `Authorization: Bearer <token>` / bare `Bearer <token>`. Casings spelled
|
|
91
|
+
// out because `KEYWORD_CRED`'s lookahead is case-sensitive by necessity.
|
|
92
|
+
new RegExp(String.raw `\b(?:Bearer|bearer|BEARER)\s+` + KEYWORD_CRED, "gu"),
|
|
93
|
+
// `Authorization: Basic <base64>` — same header family, and the base64 is
|
|
94
|
+
// usually under the generic 40-character floor.
|
|
95
|
+
new RegExp(String.raw `\b(?:Basic|basic|BASIC)\s+` + KEYWORD_CRED, "gu"),
|
|
96
|
+
// Session cookies: the cookie NAME (`session`, `sid`) never matches a
|
|
97
|
+
// secret-word rule, so the value needs its own case or session tokens ride
|
|
98
|
+
// through CI and proxy logs intact. Anchoring on `name=` both avoids matching
|
|
99
|
+
// prose after a bare `Cookie:` and keeps the cookie name readable in output.
|
|
100
|
+
new RegExp(String.raw `\b(?:Set-|set-|SET-)?(?:Cookie|cookie|COOKIE)\s*:\s*[^\s=;]{1,64}=` + KEYWORD_CRED, "gu"),
|
|
101
|
+
// Credential-bearing URL userinfo: `https://user:pass@host/…`. Masking the
|
|
102
|
+
// whole userinfo keeps the host, which is what makes the log useful.
|
|
103
|
+
/:\/\/([^\s/@]+)@/gu,
|
|
3
104
|
];
|
|
4
|
-
|
|
105
|
+
/** Patterns where the entire match is the secret. */
|
|
106
|
+
const WHOLE_MATCH_PATTERNS = [
|
|
5
107
|
/\bghp_[A-Za-z0-9]{20,}\b/gu,
|
|
6
108
|
/\b(?:cf|sk|tok)_[A-Za-z0-9_-]{16,}\b/gu,
|
|
109
|
+
// JWTs, whose segments routinely fall under the generic 40-character floor.
|
|
110
|
+
/\beyJ[A-Za-z0-9_-]{4,}\.[A-Za-z0-9_-]{4,}\.[A-Za-z0-9_-]{4,}\b/gu,
|
|
7
111
|
/\b[A-Za-z0-9+/_-]{40,}\b/gu,
|
|
8
112
|
];
|
|
9
113
|
export function redactText(value) {
|
|
10
114
|
if (!value)
|
|
11
115
|
return value;
|
|
12
116
|
let output = value;
|
|
13
|
-
for (const pattern of
|
|
14
|
-
output = output.replace(pattern, (whole,
|
|
15
|
-
const trimmed = String(
|
|
16
|
-
|
|
117
|
+
for (const pattern of CAPTURING_PATTERNS) {
|
|
118
|
+
output = output.replace(pattern, (whole, secret) => {
|
|
119
|
+
const trimmed = String(secret ?? "").trim();
|
|
120
|
+
if (trimmed.length === 0)
|
|
121
|
+
return whole;
|
|
122
|
+
return whole.replace(trimmed, mask(trimmed));
|
|
17
123
|
});
|
|
18
124
|
}
|
|
19
|
-
for (const pattern of
|
|
125
|
+
for (const pattern of WHOLE_MATCH_PATTERNS) {
|
|
20
126
|
output = output.replace(pattern, (token) => mask(token));
|
|
21
127
|
}
|
|
22
128
|
return output;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { resolveManagedRtkExecutable } from "#tool-runtime/managed-rtk.js";
|
|
2
3
|
import { createSummaryOutputSchema, createSummaryResult } from "./_shared/result.js";
|
|
3
4
|
import { readonlyOpsBaseSchema, resolveReadonlyCwd, runReadonlyCommand } from "./_readonly-ops.js";
|
|
4
5
|
const inputSchema = readonlyOpsBaseSchema
|
|
@@ -19,8 +20,12 @@ const outputSchema = createSummaryOutputSchema({
|
|
|
19
20
|
}),
|
|
20
21
|
});
|
|
21
22
|
function commandFor(input) {
|
|
22
|
-
if (input.source === "rtk")
|
|
23
|
-
|
|
23
|
+
if (input.source === "rtk") {
|
|
24
|
+
const rtkBin = resolveManagedRtkExecutable();
|
|
25
|
+
if (!rtkBin)
|
|
26
|
+
return null;
|
|
27
|
+
return { command: rtkBin, args: ["gain", "--format", "json"] };
|
|
28
|
+
}
|
|
24
29
|
return { command: "./bin/wp", args: input.sessions ? ["gain", "--sessions"] : ["gain"] };
|
|
25
30
|
}
|
|
26
31
|
const tool = {
|
|
@@ -39,6 +44,27 @@ const tool = {
|
|
|
39
44
|
const input = inputSchema.parse(raw ?? {});
|
|
40
45
|
const cwd = resolveReadonlyCwd(input);
|
|
41
46
|
const command = commandFor(input);
|
|
47
|
+
if (command === null) {
|
|
48
|
+
return createSummaryResult({
|
|
49
|
+
passed: false,
|
|
50
|
+
summary: "managed RTK not available (wp setup --with rtk or WP_RTK_BIN)",
|
|
51
|
+
exitCode: 1,
|
|
52
|
+
counts: { commandCount: 0, passedCount: 0, failedCount: 1 },
|
|
53
|
+
details: {
|
|
54
|
+
cwd,
|
|
55
|
+
source: input.source,
|
|
56
|
+
format: input.format,
|
|
57
|
+
sessions: input.sessions,
|
|
58
|
+
command: {},
|
|
59
|
+
result: { error: "managed_rtk_missing" },
|
|
60
|
+
},
|
|
61
|
+
rawOutput: "",
|
|
62
|
+
truncated: false,
|
|
63
|
+
timedOut: false,
|
|
64
|
+
aborted: false,
|
|
65
|
+
warnings: ["managed RTK binary missing"],
|
|
66
|
+
});
|
|
67
|
+
}
|
|
42
68
|
const result = await runReadonlyCommand("gain", command.command, command.args, {
|
|
43
69
|
cwd,
|
|
44
70
|
timeoutMs: input.timeoutMs,
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ToolDescriptor } from "#mcp/auto-discover";
|
|
2
|
+
import { type RunReleaseProgressOptions } from "#cli/commands/release-progress.js";
|
|
3
|
+
export type ReleaseProgressToolDeps = Pick<RunReleaseProgressOptions, "fetchJobs" | "resolveLatestRunId">;
|
|
4
|
+
/**
|
|
5
|
+
* Build the tool descriptor. Tests inject gh seams; production uses defaults.
|
|
6
|
+
*/
|
|
7
|
+
export declare function createReleaseProgressTool(deps?: ReleaseProgressToolDeps): ToolDescriptor;
|
|
8
|
+
declare const tool: ToolDescriptor;
|
|
9
|
+
export default tool;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `wp_release_progress` — read-only Release workflow phase reporter for agents.
|
|
3
|
+
*
|
|
4
|
+
* Shares {@link runReleaseProgress} with the CLI so MCP and `wp release-progress`
|
|
5
|
+
* never diverge. Does not mutate GitHub; only reads via `gh`.
|
|
6
|
+
*/
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
import { runReleaseProgress, } from "#cli/commands/release-progress.js";
|
|
9
|
+
import { createSummaryOutputSchema, createSummaryResult } from "./_shared/result.js";
|
|
10
|
+
import { readonlyOpsBaseSchema } from "./_readonly-ops.js";
|
|
11
|
+
const inputSchema = readonlyOpsBaseSchema
|
|
12
|
+
.extend({
|
|
13
|
+
runId: z.union([z.string().min(1), z.number().int().positive()]).optional(),
|
|
14
|
+
repo: z.string().min(1).optional(),
|
|
15
|
+
})
|
|
16
|
+
.strict();
|
|
17
|
+
const releaseProgressDetailsSchema = z
|
|
18
|
+
.object({
|
|
19
|
+
runId: z.string(),
|
|
20
|
+
repo: z.string(),
|
|
21
|
+
phase: z.enum(["detect", "native-matrix", "publish", "done", "failed"]),
|
|
22
|
+
detail: z.string(),
|
|
23
|
+
native: z.object({
|
|
24
|
+
done: z.number().int().nonnegative(),
|
|
25
|
+
total: z.number().int().nonnegative(),
|
|
26
|
+
failed: z.number().int().nonnegative(),
|
|
27
|
+
inProgress: z.number().int().nonnegative(),
|
|
28
|
+
}),
|
|
29
|
+
publishRunning: z.boolean(),
|
|
30
|
+
publishDone: z.boolean(),
|
|
31
|
+
publishFailed: z.boolean(),
|
|
32
|
+
line: z.string(),
|
|
33
|
+
})
|
|
34
|
+
.strict();
|
|
35
|
+
const outputSchema = createSummaryOutputSchema({
|
|
36
|
+
details: releaseProgressDetailsSchema,
|
|
37
|
+
});
|
|
38
|
+
/**
|
|
39
|
+
* Build the tool descriptor. Tests inject gh seams; production uses defaults.
|
|
40
|
+
*/
|
|
41
|
+
export function createReleaseProgressTool(deps = {}) {
|
|
42
|
+
return {
|
|
43
|
+
name: "wp_release_progress",
|
|
44
|
+
description: "Read-only GitHub Actions Release workflow phase reporter (detect | native-matrix | publish | done | failed) with session-memory native matrix counts. Use instead of silent npm view polls after Version Packages merge. Does not mutate GitHub. Prefer this over shelling `wp release-progress` when available.",
|
|
45
|
+
inputSchema,
|
|
46
|
+
outputSchema,
|
|
47
|
+
annotations: {
|
|
48
|
+
title: "Release progress",
|
|
49
|
+
readOnlyHint: true,
|
|
50
|
+
destructiveHint: false,
|
|
51
|
+
idempotentHint: true,
|
|
52
|
+
openWorldHint: true,
|
|
53
|
+
},
|
|
54
|
+
handler: async (raw) => {
|
|
55
|
+
const input = inputSchema.parse(raw ?? {});
|
|
56
|
+
const repo = input.repo ?? "webpresso/agent-kit";
|
|
57
|
+
try {
|
|
58
|
+
const result = runReleaseProgress({
|
|
59
|
+
runId: input.runId,
|
|
60
|
+
repo,
|
|
61
|
+
json: false,
|
|
62
|
+
fetchJobs: deps.fetchJobs,
|
|
63
|
+
resolveLatestRunId: deps.resolveLatestRunId,
|
|
64
|
+
// Suppress CLI text I/O; structuredContent is the MCP contract.
|
|
65
|
+
stdout: { write: () => undefined },
|
|
66
|
+
stderr: { write: () => undefined },
|
|
67
|
+
});
|
|
68
|
+
const { progress, runId, line } = result;
|
|
69
|
+
const failed = progress.phase === "failed";
|
|
70
|
+
return createSummaryResult({
|
|
71
|
+
passed: !failed,
|
|
72
|
+
summary: line,
|
|
73
|
+
details: {
|
|
74
|
+
runId,
|
|
75
|
+
repo,
|
|
76
|
+
phase: progress.phase,
|
|
77
|
+
detail: progress.detail,
|
|
78
|
+
native: progress.native,
|
|
79
|
+
publishRunning: progress.publishRunning,
|
|
80
|
+
publishDone: progress.publishDone,
|
|
81
|
+
publishFailed: progress.publishFailed,
|
|
82
|
+
line,
|
|
83
|
+
},
|
|
84
|
+
warnings: failed ? [progress.detail] : [],
|
|
85
|
+
}, { isError: failed });
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
89
|
+
return createSummaryResult({
|
|
90
|
+
passed: false,
|
|
91
|
+
summary: `release-progress failed: ${message}`,
|
|
92
|
+
details: {
|
|
93
|
+
runId: input.runId === undefined ? "" : String(input.runId),
|
|
94
|
+
repo,
|
|
95
|
+
phase: "failed",
|
|
96
|
+
detail: message,
|
|
97
|
+
native: { done: 0, total: 0, failed: 0, inProgress: 0 },
|
|
98
|
+
publishRunning: false,
|
|
99
|
+
publishDone: false,
|
|
100
|
+
publishFailed: false,
|
|
101
|
+
line: `release-progress phase=failed native=0/0 ${message}`,
|
|
102
|
+
},
|
|
103
|
+
warnings: [message],
|
|
104
|
+
}, { isError: true });
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
const tool = createReleaseProgressTool();
|
|
110
|
+
export default tool;
|
|
@@ -15,6 +15,7 @@ import { gitBranchExists, listEntries, resolveNewWorktreeTarget, resolveWorktree
|
|
|
15
15
|
import { deriveRepoNamespace, resolveKnownManagedWorktreeRoots, resolveWorktreeRoot, } from "#worktrees/location.js";
|
|
16
16
|
import { readRepoOriginUrl, repoManagedRoot } from "#worktrees/manager.js";
|
|
17
17
|
import { buildWorktreeInventory, canonicalizeWorktreePath, isManagedWorktreePath, resolveRepoIdentity, } from "#worktrees/identity.js";
|
|
18
|
+
import { isStaleWorktreeRegistration, pruneStaleGitWorktreeMetadata, } from "#worktrees/git-metadata.js";
|
|
18
19
|
import { findRegistryCandidatesByPath, pruneStaleWorktreeRegistryEntries, removeWorktreeRegistryEntries, repoScopedPathPredicate, upsertWorktreeRegistryEntry, } from "#worktrees/registry.js";
|
|
19
20
|
const ACTIONS = ["list", "root", "new", "remove", "prune", "refresh"];
|
|
20
21
|
const MUTATING_ACTIONS = new Set(["new", "remove", "prune", "refresh"]);
|
|
@@ -105,6 +106,53 @@ function isRegisteredManagedWorktree(resolvedPath, _context) {
|
|
|
105
106
|
// a legacy-root worktree registered by an earlier binary authorizes removal.
|
|
106
107
|
return findRegistryCandidatesByPath(canonicalizeWorktreePath(resolvedPath)).length > 0;
|
|
107
108
|
}
|
|
109
|
+
/**
|
|
110
|
+
* A stale registration may already have lost its registry entry to an earlier
|
|
111
|
+
* prune, so registration alone cannot gate reconciling it. Fall back to the
|
|
112
|
+
* managed-root boundary, which still keeps unmanaged worktrees protected.
|
|
113
|
+
*/
|
|
114
|
+
function isRemovableManagedTarget(resolved, context, stale) {
|
|
115
|
+
if (isRegisteredManagedWorktree(resolved, context))
|
|
116
|
+
return true;
|
|
117
|
+
if (!stale)
|
|
118
|
+
return false;
|
|
119
|
+
return isManagedWorktreePath(canonicalizeWorktreePath(resolved), resolveKnownManagedWorktreeRoots());
|
|
120
|
+
}
|
|
121
|
+
/** Reconcile git + registry for a worktree whose directory is already gone. */
|
|
122
|
+
function clearStaleRegistration(input, repoRoot, resolved, entries, entry) {
|
|
123
|
+
const gitPruned = pruneStaleGitWorktreeMetadata(repoRoot, entries);
|
|
124
|
+
if (gitPruned.error) {
|
|
125
|
+
return {
|
|
126
|
+
passed: false,
|
|
127
|
+
summary: `wp_worktree remove failed: git worktree prune failed: ${gitPruned.error}`,
|
|
128
|
+
action: input.action,
|
|
129
|
+
executed: false,
|
|
130
|
+
warnings: ["git_worktree_prune_failed"],
|
|
131
|
+
nextAction: "Inspect the repository's git worktree admin records and retry.",
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
const warnings = [];
|
|
135
|
+
try {
|
|
136
|
+
removeWorktreeRegistryEntries(repoScopedPathPredicate(resolved, { authoritativeRepoKey: resolveRepoIdentity(repoRoot) }));
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
warnings.push("registry_cleanup_failed");
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
passed: true,
|
|
143
|
+
summary: `Cleared stale worktree registration ${resolved} (directory already gone)`,
|
|
144
|
+
action: input.action,
|
|
145
|
+
executed: true,
|
|
146
|
+
removed: {
|
|
147
|
+
path: resolved,
|
|
148
|
+
...(entry?.branch ? { branch: entry.branch.replace("refs/heads/", "") } : {}),
|
|
149
|
+
},
|
|
150
|
+
warnings,
|
|
151
|
+
...(warnings.includes("registry_cleanup_failed")
|
|
152
|
+
? { nextAction: "Run wp_worktree prune with execute:true to repair stale registry metadata." }
|
|
153
|
+
: {}),
|
|
154
|
+
};
|
|
155
|
+
}
|
|
108
156
|
function refreshManagedEntries(repoRoot) {
|
|
109
157
|
const { originUrl, repoNamespace } = managedContext(repoRoot);
|
|
110
158
|
const knownRoots = resolveKnownManagedWorktreeRoots();
|
|
@@ -234,6 +282,7 @@ function handleRemove(input, repoRoot) {
|
|
|
234
282
|
}
|
|
235
283
|
const entry = entries.find((candidate) => candidate.path === resolved);
|
|
236
284
|
const context = managedContext(repoRoot);
|
|
285
|
+
const stale = isStaleWorktreeRegistration(entry, resolved, existsSync);
|
|
237
286
|
if (resolved === repoRoot) {
|
|
238
287
|
return {
|
|
239
288
|
passed: false,
|
|
@@ -243,7 +292,7 @@ function handleRemove(input, repoRoot) {
|
|
|
243
292
|
warnings: ["main_checkout_protected"],
|
|
244
293
|
};
|
|
245
294
|
}
|
|
246
|
-
if (!
|
|
295
|
+
if (!isRemovableManagedTarget(resolved, context, stale)) {
|
|
247
296
|
return {
|
|
248
297
|
passed: false,
|
|
249
298
|
summary: `wp_worktree remove refused: ${resolved} is not a registered managed worktree for this repository`,
|
|
@@ -263,6 +312,10 @@ function handleRemove(input, repoRoot) {
|
|
|
263
312
|
nextAction: "Unlock the worktree explicitly with git after confirming it is safe, then retry.",
|
|
264
313
|
};
|
|
265
314
|
}
|
|
315
|
+
// Checked before the dirty gate: `git status` fails in a missing directory,
|
|
316
|
+
// which would otherwise be reported as "uncommitted changes".
|
|
317
|
+
if (stale)
|
|
318
|
+
return clearStaleRegistration(input, repoRoot, resolved, entries, entry);
|
|
266
319
|
if (isDirty(resolved)) {
|
|
267
320
|
return {
|
|
268
321
|
passed: false,
|
|
@@ -368,6 +421,10 @@ const tool = {
|
|
|
368
421
|
warnings: [],
|
|
369
422
|
}, false);
|
|
370
423
|
}
|
|
424
|
+
// Reconcile git's own admin records FIRST: a hand-deleted worktree
|
|
425
|
+
// directory leaves a record that still claims its branch, and it would
|
|
426
|
+
// otherwise read as live below and pin its registry entry in place too.
|
|
427
|
+
const gitPruned = pruneStaleGitWorktreeMetadata(repoRoot, listEntries(repoRoot));
|
|
371
428
|
// Repository-scoped, three-state prune across every known root: only THIS
|
|
372
429
|
// repo's entries proven stale against a COMPLETE git-live inventory drop;
|
|
373
430
|
// unknown, other-repo, and live-legacy entries are retained. `existsSync`
|
|
@@ -377,12 +434,17 @@ const tool = {
|
|
|
377
434
|
inventory,
|
|
378
435
|
scope: { authoritativeRepoKey: resolveRepoIdentity(repoRoot) },
|
|
379
436
|
});
|
|
437
|
+
const gitSummary = gitPruned.error
|
|
438
|
+
? ""
|
|
439
|
+
: ` and ${gitPruned.prunedPaths.length} stale git worktree registration${gitPruned.prunedPaths.length === 1 ? "" : "s"}`;
|
|
380
440
|
return result({
|
|
381
441
|
passed: true,
|
|
382
|
-
summary: `Pruned ${pruned.removed.length} stale managed registry entr${pruned.removed.length === 1 ? "y" : "ies"}`,
|
|
442
|
+
summary: `Pruned ${pruned.removed.length} stale managed registry entr${pruned.removed.length === 1 ? "y" : "ies"}${gitSummary}`,
|
|
383
443
|
action: input.action,
|
|
384
444
|
executed: true,
|
|
385
|
-
warnings:
|
|
445
|
+
warnings: gitPruned.error
|
|
446
|
+
? [...pruned.warnings, `git_worktree_prune_failed:${gitPruned.error}`]
|
|
447
|
+
: pruned.warnings,
|
|
386
448
|
}, false);
|
|
387
449
|
}
|
|
388
450
|
catch (error) {
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { resolveBlockRegistry } from "#compose/registry-resolve.js";
|
|
3
|
+
const inputSchema = z
|
|
4
|
+
.object({
|
|
5
|
+
cwd: z.string().optional(),
|
|
6
|
+
profile: z.enum(["admin-crud", "ops-report"]).optional(),
|
|
7
|
+
})
|
|
8
|
+
.strict();
|
|
9
|
+
const tool = {
|
|
10
|
+
name: "wp_ui_blocks_list",
|
|
11
|
+
description: "List the agent UI block catalog for composition. Resolves project `.webpresso/generated/frontend/blocks/registry.json` when present, otherwise the offline catalog snapshot. Always returns source (project|snapshot), registryVersion, and warnings (e.g. snapshot used inside a webpresso tree without a generated registry).",
|
|
12
|
+
inputSchema,
|
|
13
|
+
annotations: {
|
|
14
|
+
title: "UI blocks list",
|
|
15
|
+
readOnlyHint: true,
|
|
16
|
+
destructiveHint: false,
|
|
17
|
+
idempotentHint: true,
|
|
18
|
+
openWorldHint: false,
|
|
19
|
+
},
|
|
20
|
+
handler: async (raw) => {
|
|
21
|
+
const input = inputSchema.parse(raw ?? {});
|
|
22
|
+
const resolved = resolveBlockRegistry({ cwd: input.cwd });
|
|
23
|
+
// admin-crud: full registry (list/detail/form). ops-report: no admin blocks.
|
|
24
|
+
const blocks = input.profile === "ops-report" ? [] : resolved.registry.blocks;
|
|
25
|
+
const summary = `UI blocks: ${blocks.length} (source=${resolved.source}, version=${resolved.registryVersion})`;
|
|
26
|
+
const payload = {
|
|
27
|
+
passed: true,
|
|
28
|
+
summary,
|
|
29
|
+
source: resolved.source,
|
|
30
|
+
registryVersion: resolved.registryVersion,
|
|
31
|
+
path: resolved.path,
|
|
32
|
+
warnings: [...resolved.warnings],
|
|
33
|
+
profiles: ["admin-crud", "ops-report"],
|
|
34
|
+
blocks,
|
|
35
|
+
webpressoPresentWithoutRegistry: resolved.webpressoPresentWithoutRegistry,
|
|
36
|
+
};
|
|
37
|
+
return {
|
|
38
|
+
content: [{ type: "text", text: summary }],
|
|
39
|
+
structuredContent: payload,
|
|
40
|
+
};
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
export default tool;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { composeValidate } from "#compose/compose.js";
|
|
3
|
+
const inputSchema = z
|
|
4
|
+
.object({
|
|
5
|
+
cwd: z.string().optional(),
|
|
6
|
+
profile: z.enum(["admin-crud", "ops-report"]),
|
|
7
|
+
kind: z.enum(["list", "detail", "form"]).optional(),
|
|
8
|
+
spec: z.unknown(),
|
|
9
|
+
})
|
|
10
|
+
.strict()
|
|
11
|
+
.superRefine((value, ctx) => {
|
|
12
|
+
if (value.profile === "admin-crud" && value.kind === undefined) {
|
|
13
|
+
ctx.addIssue({
|
|
14
|
+
code: "custom",
|
|
15
|
+
message: "kind is required for admin-crud (list|detail|form)",
|
|
16
|
+
path: ["kind"],
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
const tool = {
|
|
21
|
+
name: "wp_ui_compose",
|
|
22
|
+
description: "Validate a profile-discriminated UI compose input. admin-crud: validate list/detail/form specs against the block registry (returns input spec unchanged when legal). ops-report: validate flat OpsReportSpec. Does not write files or open a browser.",
|
|
23
|
+
inputSchema,
|
|
24
|
+
annotations: {
|
|
25
|
+
title: "UI compose validate",
|
|
26
|
+
readOnlyHint: true,
|
|
27
|
+
destructiveHint: false,
|
|
28
|
+
idempotentHint: true,
|
|
29
|
+
openWorldHint: false,
|
|
30
|
+
},
|
|
31
|
+
handler: async (raw) => {
|
|
32
|
+
const parsed = inputSchema.safeParse(raw ?? {});
|
|
33
|
+
if (!parsed.success) {
|
|
34
|
+
const summary = `Invalid wp_ui_compose input: ${parsed.error.issues.map((i) => i.message).join("; ")}`;
|
|
35
|
+
return {
|
|
36
|
+
content: [{ type: "text", text: summary }],
|
|
37
|
+
structuredContent: {
|
|
38
|
+
passed: false,
|
|
39
|
+
summary,
|
|
40
|
+
ok: false,
|
|
41
|
+
diagnostics: parsed.error.issues.map((i) => ({
|
|
42
|
+
path: i.path.length > 0 ? i.path.join(".") : "(root)",
|
|
43
|
+
message: i.message,
|
|
44
|
+
})),
|
|
45
|
+
},
|
|
46
|
+
isError: true,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
const input = parsed.data;
|
|
50
|
+
if (input.profile === "admin-crud") {
|
|
51
|
+
if (input.spec === null || typeof input.spec !== "object" || Array.isArray(input.spec)) {
|
|
52
|
+
const summary = "Invalid wp_ui_compose admin-crud spec: expected a non-null object (not string/array/empty coercion)";
|
|
53
|
+
return {
|
|
54
|
+
content: [{ type: "text", text: summary }],
|
|
55
|
+
structuredContent: {
|
|
56
|
+
passed: false,
|
|
57
|
+
summary,
|
|
58
|
+
ok: false,
|
|
59
|
+
diagnostics: [{ path: "spec", message: summary }],
|
|
60
|
+
},
|
|
61
|
+
isError: true,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const composeInput = input.profile === "admin-crud"
|
|
66
|
+
? {
|
|
67
|
+
profile: "admin-crud",
|
|
68
|
+
kind: input.kind,
|
|
69
|
+
spec: input.spec,
|
|
70
|
+
}
|
|
71
|
+
: { profile: "ops-report", spec: input.spec };
|
|
72
|
+
const result = composeValidate(composeInput, { cwd: input.cwd });
|
|
73
|
+
const summary = result.ok
|
|
74
|
+
? `compose ok (${result.profile})`
|
|
75
|
+
: `compose failed (${result.profile}): ${result.diagnostics.map((d) => d.message).join("; ")}`;
|
|
76
|
+
// MCP tool execution errors (incl. domain validation) use isError:true so
|
|
77
|
+
// models self-correct; diagnostics stay in content text + structuredContent.
|
|
78
|
+
return {
|
|
79
|
+
content: [{ type: "text", text: summary }],
|
|
80
|
+
structuredContent: {
|
|
81
|
+
passed: result.ok,
|
|
82
|
+
summary,
|
|
83
|
+
profile: result.profile,
|
|
84
|
+
ok: result.ok,
|
|
85
|
+
spec: result.spec,
|
|
86
|
+
diagnostics: result.diagnostics,
|
|
87
|
+
registrySource: result.registrySource,
|
|
88
|
+
registryVersion: result.registryVersion,
|
|
89
|
+
warnings: result.warnings,
|
|
90
|
+
blockTypesValidated: result.blockTypesValidated,
|
|
91
|
+
},
|
|
92
|
+
...(result.ok ? {} : { isError: true }),
|
|
93
|
+
};
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
export default tool;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { previewOpsReport } from "#compose/preview.js";
|
|
3
|
+
const inputSchema = z
|
|
4
|
+
.object({
|
|
5
|
+
cwd: z.string().optional(),
|
|
6
|
+
profile: z.literal("ops-report"),
|
|
7
|
+
spec: z.unknown(),
|
|
8
|
+
})
|
|
9
|
+
.strict();
|
|
10
|
+
const tool = {
|
|
11
|
+
name: "wp_ui_preview",
|
|
12
|
+
description: "Validate an ops-report compose spec, write self-contained HTML under the worktree state surface (or .webpresso/compose fallback), and return { path } only. Does not open a browser. admin-crud is not supported for preview.",
|
|
13
|
+
inputSchema,
|
|
14
|
+
annotations: {
|
|
15
|
+
title: "UI ops-report preview",
|
|
16
|
+
readOnlyHint: false,
|
|
17
|
+
destructiveHint: false,
|
|
18
|
+
idempotentHint: true,
|
|
19
|
+
openWorldHint: false,
|
|
20
|
+
},
|
|
21
|
+
handler: async (raw) => {
|
|
22
|
+
const parsed = inputSchema.safeParse(raw ?? {});
|
|
23
|
+
if (!parsed.success) {
|
|
24
|
+
const message = `Invalid wp_ui_preview input: ${parsed.error.issues.map((i) => i.message).join("; ")} (ops-report profile only; admin-crud is not supported)`;
|
|
25
|
+
return {
|
|
26
|
+
content: [{ type: "text", text: message }],
|
|
27
|
+
structuredContent: {
|
|
28
|
+
passed: false,
|
|
29
|
+
summary: message,
|
|
30
|
+
},
|
|
31
|
+
isError: true,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
try {
|
|
35
|
+
const { path } = previewOpsReport({ cwd: parsed.data.cwd, spec: parsed.data.spec });
|
|
36
|
+
const summary = `ops-report preview written: ${path}`;
|
|
37
|
+
return {
|
|
38
|
+
content: [{ type: "text", text: summary }],
|
|
39
|
+
structuredContent: {
|
|
40
|
+
passed: true,
|
|
41
|
+
summary,
|
|
42
|
+
path,
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
48
|
+
return {
|
|
49
|
+
content: [{ type: "text", text: message }],
|
|
50
|
+
structuredContent: {
|
|
51
|
+
passed: false,
|
|
52
|
+
summary: message,
|
|
53
|
+
},
|
|
54
|
+
isError: true,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
export default tool;
|