@webpresso/agent-kit 3.3.3 → 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/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/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 +38 -12
- 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 +63 -10
- 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/vendor-oxlint-plugins.d.ts +8 -0
- package/dist/esm/build/vendor-oxlint-plugins.js +53 -0
- 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 +48 -13
- package/dist/esm/cli/commands/init/mcp-spec.js +86 -30
- package/dist/esm/cli/commands/init/merge.js +30 -10
- 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/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 +8 -0
- package/dist/esm/compose/compose.js +115 -8
- package/dist/esm/compose/ops-report-schema.js +17 -3
- package/dist/esm/compose/registry-resolve.d.ts +17 -0
- package/dist/esm/compose/registry-resolve.js +78 -11
- 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/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/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/registration.js +1 -1
- 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/worktree.js +65 -3
- package/dist/esm/mcp/tools/wp-ui-compose.js +38 -4
- package/dist/esm/mcp/tools/wp-ui-preview.js +14 -7
- 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 +16 -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
package/THIRD-PARTY-NOTICES.md
CHANGED
|
@@ -34,19 +34,21 @@ otherwise in their frontmatter.
|
|
|
34
34
|
These skills document how to invoke external CLIs or packages. Their upstream
|
|
35
35
|
binaries are **not** bundled in `@webpresso/agent-kit`.
|
|
36
36
|
|
|
37
|
-
## Runtime integrations
|
|
37
|
+
## Runtime integrations
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
39
|
+
Policy for which upstream CLIs are managed-download vs host-install lives in
|
|
40
|
+
`catalog/agent/rules/managed-tool-binaries.md`. Summary: **rtk** is managed
|
|
41
|
+
(pinned cache download on setup); **vp** / **bun** remain host package-manager
|
|
42
|
+
installs; **actionlint** is probe-only.
|
|
43
|
+
|
|
44
|
+
The table below records default preset vs opt-in. Consumers accept the upstream
|
|
43
45
|
license separately when an integration is installed.
|
|
44
46
|
|
|
45
|
-
| Integration | Default behavior
|
|
46
|
-
| --------------------------- |
|
|
47
|
-
| `playwright-mcp` | Opt-in.
|
|
48
|
-
| `workflow skill provenance` | Curated Markdown skill assets adapted from an MIT-licensed upstream workflow project; no upstream runtime checkout or binaries bundled.
|
|
49
|
-
| `rtk` |
|
|
47
|
+
| Integration | Default behavior | Upstream | License |
|
|
48
|
+
| --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- | ---------- |
|
|
49
|
+
| `playwright-mcp` | Opt-in. | [microsoft/playwright-mcp](https://github.com/microsoft/playwright-mcp) | Apache-2.0 |
|
|
50
|
+
| `workflow skill provenance` | Curated Markdown skill assets adapted from an MIT-licensed upstream workflow project; no upstream runtime checkout or binaries bundled. | [garrytan/gstack](https://github.com/garrytan/gstack) | MIT |
|
|
51
|
+
| `rtk` | Managed install (pinned release into Webpresso cache on setup); skipped when `WP_SKIP_RTK=1`. Binary not reimplemented; filters stay upstream. | [rtk-ai/rtk](https://github.com/rtk-ai/rtk) | Apache-2.0 |
|
|
50
52
|
|
|
51
53
|
## Runtime npm dependencies
|
|
52
54
|
|
|
@@ -91,9 +91,15 @@ When a feature branch with a `.changeset/<slug>.md` file merges to `main`,
|
|
|
91
91
|
2. The action runs `wp run version`, which preserves the canonical version
|
|
92
92
|
path: `changeset version && wp run sync-marketplace-version`.
|
|
93
93
|
3. When the Version PR is merged, CI runs `wp run release:publish`, which
|
|
94
|
-
calls `npm publish --
|
|
95
|
-
|
|
96
|
-
|
|
94
|
+
calls `npm publish --access public` inside GitHub Actions using npm trusted
|
|
95
|
+
publishing/OIDC. This workflow is the only production npm publish path for
|
|
96
|
+
Webpresso packages. **Provenance attestations are not currently produced.**
|
|
97
|
+
npm only issues provenance for a **public source repository**
|
|
98
|
+
(`https://docs.npmjs.com/generating-provenance-statements/`), and
|
|
99
|
+
`webpresso/agent-kit` is private, so `--provenance` was removed from every
|
|
100
|
+
publish call site. **Do not add `--provenance` back while this repo is
|
|
101
|
+
private — npm will reject the publish.** Provenance returns automatically
|
|
102
|
+
through this same OIDC flow if/when the repo becomes public.
|
|
97
103
|
4. After publish, CI verifies the `v<version>` tag on the mainline version-bump
|
|
98
104
|
commit and creates `release/v<version>` as the separate dist-carrying
|
|
99
105
|
compatibility branch for marketplace consumers.
|
|
@@ -203,7 +209,12 @@ dist-tag changes, or subsequent versions.
|
|
|
203
209
|
Use `.github/workflows/release.yml` — not `ci.agent-kit.yml` and not the
|
|
204
210
|
artifact-only matrix job names — because the `release` job in `release.yml` is
|
|
205
211
|
the job that runs `wp run release:publish`, has `id-token: write`, and invokes
|
|
206
|
-
`npm publish --
|
|
212
|
+
`npm publish --access public` via npm trusted publishing/OIDC. It does **not**
|
|
213
|
+
pass `--provenance`: npm only issues provenance for a public source
|
|
214
|
+
repository (`https://docs.npmjs.com/generating-provenance-statements/`), and
|
|
215
|
+
this repo is private. Do not add `--provenance` while the repo stays private —
|
|
216
|
+
npm will reject the publish. Provenance returns automatically through this
|
|
217
|
+
same OIDC flow once the repo becomes public.
|
|
207
218
|
|
|
208
219
|
## Required repo files
|
|
209
220
|
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: rule
|
|
3
|
+
slug: managed-tool-binaries
|
|
4
|
+
title: Managed third-party tool binaries
|
|
5
|
+
status: active
|
|
6
|
+
scope: repo
|
|
7
|
+
applies_to: [agents]
|
|
8
|
+
related: [rtk-routing]
|
|
9
|
+
created: "2026-07-25"
|
|
10
|
+
last_reviewed: "2026-07-25"
|
|
11
|
+
paths:
|
|
12
|
+
- "src/tool-runtime/**"
|
|
13
|
+
- "src/cli/commands/init/scaffolders/**"
|
|
14
|
+
- "compatible-versions.json"
|
|
15
|
+
- "THIRD-PARTY-NOTICES.md"
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
# Managed third-party tool binaries
|
|
19
|
+
|
|
20
|
+
Policy for when agent-kit **embeds** (managed download/cache) an upstream CLI
|
|
21
|
+
versus leaving install to the host package manager / user.
|
|
22
|
+
|
|
23
|
+
## Decision rule (YAGNI)
|
|
24
|
+
|
|
25
|
+
Manage a binary only when **all** of the following hold:
|
|
26
|
+
|
|
27
|
+
1. **Invisible filter or sidecar** — used on agent/hook hot paths without the
|
|
28
|
+
user intentionally invoking it (e.g. output filtering).
|
|
29
|
+
2. **Freestanding install creates dual ownership** — PATH/`init` hooks or
|
|
30
|
+
second install stories fight path-stable `wp` surfaces.
|
|
31
|
+
3. **Small, pinned, capability-probed** — single static binary (or tiny
|
|
32
|
+
archive), Apache/MIT-class license, bounded probe, fail-open degrade.
|
|
33
|
+
4. **No existing first-class package channel** — if the tool is already the
|
|
34
|
+
workspace package manager or a full runtime (npm/vp/bun), do not invent a
|
|
35
|
+
parallel download channel.
|
|
36
|
+
|
|
37
|
+
Otherwise: **probe + install hint**, or rely on existing global/package install.
|
|
38
|
+
|
|
39
|
+
## Inventory (current)
|
|
40
|
+
|
|
41
|
+
| Tool | Lane | Why |
|
|
42
|
+
| ---------------------------- | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
43
|
+
| **rtk** | **Managed** (`src/tool-runtime/managed-rtk.ts`) | Optional hot-path filter; freestanding PreToolUse hooks were dual/broken; pinned GitHub release; capability probe `gain --help` |
|
|
44
|
+
| **wp** | First-party | Own product binary / runtime staging |
|
|
45
|
+
| **vp** (vite-plus) | **Host / package-manager** | Workspace package manager + global `vp install`; CI uses `voidzero-dev/setup-vp`; `resolveGlobalCapableVpCommand` — not a cache-download sidecar |
|
|
46
|
+
| **bun** | **Host runtime** | JS runtime; install via bun.sh / brew; often co-installed with vite-plus; do not dual-manage |
|
|
47
|
+
| **actionlint** | **Probe-only** | Optional workflow lint; `runtime-check` prints hint only; not on agent PreToolUse hot path |
|
|
48
|
+
| **playwright** / MCP plugins | **Opt-in package** | Explicit setup/MCP install; not silent binary cache |
|
|
49
|
+
|
|
50
|
+
## Hard rules
|
|
51
|
+
|
|
52
|
+
- Do **not** add managed downloaders for `vp` or `bun` without a product
|
|
53
|
+
decision that retires the package-manager install path (would fork ownership).
|
|
54
|
+
- Do **not** reimplement upstream filter/CLI logic in TypeScript to avoid
|
|
55
|
+
shipping a binary.
|
|
56
|
+
- New managed tools must: pin version in `compatible-versions.json`, document
|
|
57
|
+
license in `THIRD-PARTY-NOTICES.md`, use absolute path resolution (never bare
|
|
58
|
+
PATH as the only source of truth), and degrade fail-open when skip/missing.
|
|
59
|
+
- Freestanding third-party PreToolUse hooks remain forbidden; setup strips
|
|
60
|
+
known RTK freestanding markers.
|
|
61
|
+
|
|
62
|
+
## Implementation pattern (when adding a managed tool)
|
|
63
|
+
|
|
64
|
+
1. Single module under `src/tool-runtime/managed-<tool>.ts` (resolve + ensure + probe).
|
|
65
|
+
2. Setup scaffolder calls `ensure*` only; no brew/cargo as the default lane.
|
|
66
|
+
3. All runtime call sites import the same resolve helper (DRY).
|
|
67
|
+
4. Tests: inject downloader; isolate cache via env root; no network in unit tests.
|
|
@@ -11,8 +11,9 @@ related:
|
|
|
11
11
|
- pre-implementation
|
|
12
12
|
- engineering-principles
|
|
13
13
|
- supported-agent-clis
|
|
14
|
+
- workflow-skills-routing
|
|
14
15
|
created: "2026-07-24"
|
|
15
|
-
last_reviewed: "2026-07-
|
|
16
|
+
last_reviewed: "2026-07-25"
|
|
16
17
|
paths:
|
|
17
18
|
- "blueprints/**"
|
|
18
19
|
- "catalog/agent/**"
|
|
@@ -29,6 +30,17 @@ policy, `cmd-execution.md` for the scoped-command surface,
|
|
|
29
30
|
`pre-implementation.md` for the blueprint gate, `engineering-principles.md` for
|
|
30
31
|
scope filters, `supported-agent-clis.md` for host tiers.
|
|
31
32
|
|
|
33
|
+
## Product mission anchor
|
|
34
|
+
|
|
35
|
+
Webpresso is a product for building websites and agents on webpresso framework
|
|
36
|
+
tooling, and every agent session here exists to move that product loop forward.
|
|
37
|
+
Token economy is a standing constraint on that loop, not an optimization applied
|
|
38
|
+
when convenient: prefer the harnesses and building blocks — `wp_*` tools,
|
|
39
|
+
skills, subagent dispatch — over long free-form context, and spend the fewest
|
|
40
|
+
tokens that still preserve quality. The orchestrator/executor split, the
|
|
41
|
+
one-deliverable brief, and the cheapest-capable-model rule below are all
|
|
42
|
+
instruments of that constraint, not independent preferences.
|
|
43
|
+
|
|
32
44
|
## Orchestrator / executor split
|
|
33
45
|
|
|
34
46
|
The session that owns the program is an **orchestrator**: it plans, sequences,
|
|
@@ -43,6 +55,56 @@ blueprint's owner worktree.
|
|
|
43
55
|
- The orchestrator runs from its own controller worktree cut from
|
|
44
56
|
`origin/main`, never from the primary checkout.
|
|
45
57
|
|
|
58
|
+
## One deliverable per brief
|
|
59
|
+
|
|
60
|
+
**Brief scope drives executor cost more than model tier does.** A brief listing
|
|
61
|
+
four numbered deliverables buys four investigations. Measured on one program:
|
|
62
|
+
multi-item briefs ran 374K–501K tokens each; the same class of defect briefed as
|
|
63
|
+
a single deliverable ran 117K and landed its PR. Trimming scope mid-flight does
|
|
64
|
+
**not** recover the spend — the exploration already happened.
|
|
65
|
+
|
|
66
|
+
- State **one** deliverable. Secondary polish is `only if trivial`, or omitted.
|
|
67
|
+
- Apply DRY/KISS/YAGNI to the **brief**, before dispatch — not to the diff after.
|
|
68
|
+
Ask "what is the single smallest change that fixes this?" and brief only that.
|
|
69
|
+
- **Prefer deletion-shaped fixes.** Removing a behavior usually beats adding a
|
|
70
|
+
mechanism to describe it. Deletion-shaped fixes are cheaper to brief,
|
|
71
|
+
implement, and review, and they close a defect class rather than narrowing it —
|
|
72
|
+
which also removes the hedged "narrowed, not closed" wording a partial fix
|
|
73
|
+
forces into its docs.
|
|
74
|
+
- Give the executor the diagnosis you already hold. Re-deriving a known root
|
|
75
|
+
cause is pure duplicated cost.
|
|
76
|
+
|
|
77
|
+
## Never await — poll
|
|
78
|
+
|
|
79
|
+
An executor that ends its turn to "wait for" a background job **deadlocks**: the
|
|
80
|
+
completion signal has usually already fired, so it idles indefinitely. Measured
|
|
81
|
+
on one program: four executors × 3–5 rounds ≈ 2M tokens for zero landed work.
|
|
82
|
+
Nudges did not break the loop; only orchestrator takeover did.
|
|
83
|
+
|
|
84
|
+
- Executors **poll in a loop**. Never end a turn waiting for a notification.
|
|
85
|
+
- Read durable state directly instead of awaiting: a review gate's outcome is in
|
|
86
|
+
`.webpresso/reviews/gates/<gate-id>.json` (`status`, `attempts[]`); CI is
|
|
87
|
+
readable via the PR status surface. Both are cheaper than waiting.
|
|
88
|
+
- If genuinely blocked, **report and stop** — an explicit blocker is recoverable,
|
|
89
|
+
an idle turn is not.
|
|
90
|
+
|
|
91
|
+
## Verify executor output, not executor reports
|
|
92
|
+
|
|
93
|
+
A report describes intent; the diff is what lands. Read the diff before trusting
|
|
94
|
+
a summary.
|
|
95
|
+
|
|
96
|
+
- **Measure diffs against the fork point** (`git merge-base origin/main HEAD`),
|
|
97
|
+
never `origin/main` directly — a stale branch renders main's newer content as
|
|
98
|
+
deletions, making a five-file change look like a thousand-file catastrophe.
|
|
99
|
+
- Watch for **whole-file rewrites from serializer round-trips**: editing YAML or
|
|
100
|
+
JSON by parsing and re-emitting silently renormalizes quoting and formatting
|
|
101
|
+
across the file, burying the real change and risking semantic drift. Config
|
|
102
|
+
edits are plain-text edits.
|
|
103
|
+
- Orchestrator claims about gate state must be **machine-checked before being
|
|
104
|
+
passed to an executor**. Asserting "gate X approved your work" without
|
|
105
|
+
confirming its `reviewedCommit`/target matches what is being shipped sends the
|
|
106
|
+
executor to land on a stale approval.
|
|
107
|
+
|
|
46
108
|
## Model tier by intrinsic difficulty
|
|
47
109
|
|
|
48
110
|
Pick the **cheapest model that can do the job**, judged by the hardest task in
|
|
@@ -66,6 +128,42 @@ work does not. A technically-correct but badly-judged UI passes every check and
|
|
|
66
128
|
still has to be redone, so the cheaper tier is not actually cheaper. Backend and
|
|
67
129
|
mechanical work keeps the cheapest-capable rule above.
|
|
68
130
|
|
|
131
|
+
## Operator model ladder (reviewed 2026-07-25)
|
|
132
|
+
|
|
133
|
+
The tier table above is host-agnostic. This section records how an operator maps
|
|
134
|
+
it onto the hosts actually installed today. It is a **dated operator default,
|
|
135
|
+
revisable per operator and per host** — agent-kit does not own model selection
|
|
136
|
+
or agent hosting, only the dispatch-cost discipline for the work agents hand
|
|
137
|
+
off.
|
|
138
|
+
|
|
139
|
+
- **Cheap and mid tiers are the default lanes, and they run wide.** Mechanical
|
|
140
|
+
S/XS work and ordinary M work fan out across several concurrent executors;
|
|
141
|
+
that parallelism is where the throughput comes from, so it is the normal case,
|
|
142
|
+
not an exceptional one.
|
|
143
|
+
- **The premium reasoning tier is narrow and deliberate** (e.g. the Claude Opus
|
|
144
|
+
class): L/XL blueprints, design/UI/frontend work, security- and
|
|
145
|
+
trust-boundary changes, and anything digest-, wire-format-, or
|
|
146
|
+
protocol-bearing. If one hard task is the only reason a blueprint wants this
|
|
147
|
+
tier, re-home that task — do not upgrade the whole lane.
|
|
148
|
+
- **The frontier gating tier is rare and human-invoked** (e.g. the Claude
|
|
149
|
+
Fable/Mythos class). Its job is milestone approval on one-way doors — a schema
|
|
150
|
+
or wire format that cannot be un-shipped, a security-posture change, an
|
|
151
|
+
irreversible migration — not execution. It never enters routine dispatch and is
|
|
152
|
+
never the default for a sized blueprint.
|
|
153
|
+
- **On a Codex host the operator's configured default model handles routine
|
|
154
|
+
checks**, with a premium family member reserved for the rare deep check. The
|
|
155
|
+
configuration owns that identity, not this document: read
|
|
156
|
+
`~/.codex/config.toml` (`model`), `CODEX_REVIEW_MODEL`, and
|
|
157
|
+
`CLAUDE_REVIEW_MODEL`. Never hardcode a model id or a preference ordering into
|
|
158
|
+
a plan, brief, or skill — the ids move faster than the prose does.
|
|
159
|
+
|
|
160
|
+
Two orderings deliberately live elsewhere and are **not** restated here: the
|
|
161
|
+
OpenCode reviewer purpose→family ordering, which is rendered into the
|
|
162
|
+
[`opencode-go` skill](../skills/opencode-go/SKILL.md) from the committed
|
|
163
|
+
reviewer policy and drift-gated by CI, and the agent-CLI tiers in
|
|
164
|
+
[`supported-agent-clis.md`](./supported-agent-clis.md). Read those; a second
|
|
165
|
+
copy is a second thing to update, and the stale copy is the one an agent reads.
|
|
166
|
+
|
|
69
167
|
## Quota awareness across hosts
|
|
70
168
|
|
|
71
169
|
Provider subscriptions are the budget. Treat weekly quota as a shared,
|
|
@@ -162,11 +162,17 @@ lifecycle-valid, but they have no sibling review authority; run
|
|
|
162
162
|
`wp blueprint canonicalize <slug>` before recording review ledgers, artifacts,
|
|
163
163
|
Ultragoal events, or approval frontmatter.
|
|
164
164
|
|
|
165
|
-
- **Gate input = committed in-folder review authority.** The
|
|
166
|
-
content-addressed v2 `review-events.jsonl` event produced by
|
|
167
|
-
The legacy path
|
|
168
|
-
|
|
169
|
-
|
|
165
|
+
- **Gate input = committed in-folder review authority.** The only path for new
|
|
166
|
+
blueprints is a content-addressed v2 `review-events.jsonl` event produced by
|
|
167
|
+
`wp review gate` (or `wp review override`). The legacy path — `_overview.md`
|
|
168
|
+
frontmatter `approvals:` backed by a matching committed `reviews.md` structured
|
|
169
|
+
entry and separate tracked artifact — **carries no content binding** (it never
|
|
170
|
+
reads or hashes the blueprint body), so it is accepted only for blueprints
|
|
171
|
+
grandfathered at `LEGACY_APPROVAL_LEG_ANCHOR_COMMIT`. On any blueprint created
|
|
172
|
+
after that anchor, a frontmatter `approvals:` entry does not satisfy the gate no
|
|
173
|
+
matter which reviewer it names; the audit says so explicitly rather than
|
|
174
|
+
reporting a bare "0 approvals". The markdown `## Approvals` checklist is only a
|
|
175
|
+
human-readable mirror.
|
|
170
176
|
- **Durable record = committed in-folder review history.** V2 events bind the
|
|
171
177
|
verdict to the reviewed subject digest and artifact hash. Legacy frontmatter
|
|
172
178
|
entries count only with matching `wp:review-entry` provenance and a tracked
|
|
@@ -182,8 +188,10 @@ Ultragoal events, or approval frontmatter.
|
|
|
182
188
|
Enforced by `wp audit blueprint-lifecycle`, the promotion command, and
|
|
183
189
|
changed-completed blueprint CI audits.
|
|
184
190
|
- **Recording a review entry is MCP-first**: use the `wp_blueprint_review_log`
|
|
185
|
-
MCP tool to write the artifact
|
|
186
|
-
|
|
191
|
+
MCP tool to write the artifact and the matching `reviews.md` structured entry
|
|
192
|
+
in one call (plus the `approvals:` frontmatter entry for `reject`/`no-verdict`
|
|
193
|
+
attribution — it never writes an `approve`/`approve-with-nits` entry there,
|
|
194
|
+
because that field is the unbound legacy leg). It is
|
|
187
195
|
**not** the same as this CLI's own `wp review log` subcommand — that writes
|
|
188
196
|
only the machine ledger (`review-events.jsonl`) and requires `--provider`
|
|
189
197
|
plus a `human` reviewer for any approval verdict; treat the two as distinct,
|
|
@@ -199,14 +207,14 @@ Ultragoal events, or approval frontmatter.
|
|
|
199
207
|
restriction exactly. It cannot mint a `claude`/`codex`/`grok`/OpenCode-Go
|
|
200
208
|
frontmatter approval: no caller-supplied pointer can prove a named non-human
|
|
201
209
|
reviewer actually produced an artifact, so the tool does not accept one.
|
|
202
|
-
Use it for `reject`/`no-verdict` entries from any reviewer, or a
|
|
203
|
-
human-adjudicated `approve`/`approve-with-nits`. Satisfying the
|
|
210
|
+
Use it for `reject`/`no-verdict` entries from any reviewer, or to record a
|
|
211
|
+
human-adjudicated `approve`/`approve-with-nits` in `reviews.md`. Satisfying the
|
|
204
212
|
strong-reviewer promotion gate for a `claude`/`codex`/`grok`/OpenCode-Go identity
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
213
|
+
requires a converged `wp review gate` run (ledger-backed, requires no
|
|
214
|
+
frontmatter at all). Hand-authoring `approvals:` frontmatter is no longer an
|
|
215
|
+
option on a post-anchor blueprint — the gate does not read that leg there — so
|
|
216
|
+
there is neither an MCP-reachable nor a hand-edited shortcut around the
|
|
217
|
+
content-bound ledger.
|
|
210
218
|
- **`wp review override <slug> --reason "…" --confirm` is the gated CLI escape
|
|
211
219
|
hatch for a genuinely un-convergeable gate** — a strong reviewer legitimately
|
|
212
220
|
rejected the subject but its sole remaining blocker is physically
|
|
@@ -342,16 +350,24 @@ scaffold a draft only to exempt it. For multi-file exempt follow-ups, use a
|
|
|
342
350
|
`wp worktree new` — never `bp/<slug>`, which pre-claims the owner branch and
|
|
343
351
|
collides with `wp blueprint start`.
|
|
344
352
|
|
|
345
|
-
|
|
346
|
-
|
|
353
|
+
`--switch-primary` (the current flag name; `--stash-primary` is accepted as a
|
|
354
|
+
deprecated alias and no longer stashes anything) is **not** a way to preserve
|
|
355
|
+
"user-owned edits" on the primary checkout — it never stashes primary state at
|
|
356
|
+
all. A **dirty** primary always refuses merge-cleanup, unconditionally, naming
|
|
357
|
+
the offending files, with or without this flag:
|
|
347
358
|
|
|
348
359
|
```bash
|
|
349
|
-
cd <primary-checkout> && wp worktree merge-cleanup <merged-worktree-or-branch> --base origin/main --
|
|
360
|
+
cd <primary-checkout> && wp worktree merge-cleanup <merged-worktree-or-branch> --base origin/main --switch-primary
|
|
350
361
|
```
|
|
351
362
|
|
|
352
|
-
|
|
353
|
-
switches the primary to the expected branch
|
|
354
|
-
remove + fast-forward cleanup
|
|
363
|
+
The flag bypasses only the OTHER refusal: a **clean** primary sitting on the
|
|
364
|
+
wrong branch. In that case it switches the primary to the expected branch and
|
|
365
|
+
then performs the normal remove + fast-forward cleanup — switching a clean
|
|
366
|
+
checkout is harmless, there is nothing uncommitted to lose. If the primary
|
|
367
|
+
checkout has uncommitted work — human-owned edits, a topic branch you're mid
|
|
368
|
+
way through, or leftover blueprint draft scaffold — commit it, `git stash` it
|
|
369
|
+
yourself, or run `wp blueprint abandon <slug> --confirm` first. Do not expect
|
|
370
|
+
`--switch-primary`/`--stash-primary` to preserve it for you.
|
|
355
371
|
|
|
356
372
|
## PR-level blueprint coverage gate
|
|
357
373
|
|
|
@@ -5,9 +5,9 @@ title: RTK Tool Routing
|
|
|
5
5
|
status: active
|
|
6
6
|
scope: repo
|
|
7
7
|
applies_to: [agents]
|
|
8
|
-
related: []
|
|
8
|
+
related: [managed-tool-binaries]
|
|
9
9
|
created: "2026-05-07"
|
|
10
|
-
last_reviewed: "2026-
|
|
10
|
+
last_reviewed: "2026-07-25"
|
|
11
11
|
paths:
|
|
12
12
|
- "**/*"
|
|
13
13
|
---
|
|
@@ -17,25 +17,26 @@ paths:
|
|
|
17
17
|
`rtk *` and the `wp_*` MCP tools are independent lanes. Webpresso-owned dev
|
|
18
18
|
workflows route to the matching `wp_*` MCP tool — each tool's `description`
|
|
19
19
|
states when to use it, and `pretool-guard` denies the common raw equivalents.
|
|
20
|
-
There is no injected routing block; the tool descriptions plus AGENTS.md are the
|
|
21
|
-
source of routing guidance.
|
|
22
|
-
|
|
23
|
-
Use `rtk *` for shell-tool output filtering on the long-tail command surface
|
|
24
|
-
that webpresso does not own.
|
|
25
20
|
|
|
26
21
|
## Ownership boundary
|
|
27
22
|
|
|
28
|
-
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
23
|
+
See also `managed-tool-binaries` for why only rtk is managed-download (not vp/bun).
|
|
24
|
+
|
|
25
|
+
- webpresso owns **managed packaging** of the upstream `rtk` binary (pinned
|
|
26
|
+
download into the Webpresso cache on setup), PreToolUse rewrite routing, and
|
|
27
|
+
wrapping of quality-engine runners when filtering is enabled
|
|
28
|
+
- rtk owns shell-tool **output filtering** (implementation stays upstream;
|
|
29
|
+
Apache-2.0; never reimplemented in TypeScript)
|
|
30
|
+
- freestanding consumer hooks (`rtk-rewrite.sh`, `rtk hook claude`) are
|
|
31
|
+
**retired** — never re-introduce them
|
|
32
|
+
- consumers **do not** brew/cargo install rtk for Webpresso workflows
|
|
33
33
|
|
|
34
34
|
## Hard rules
|
|
35
35
|
|
|
36
36
|
- Never reimplement upstream rtk filters in webpresso.
|
|
37
37
|
- Never wrap the `rtk` prefix behind `wp rtk`.
|
|
38
|
-
-
|
|
38
|
+
- Never install freestanding RTK PreToolUse groups in managed project settings.
|
|
39
|
+
- Prefer managed absolute rtk path from setup; do not require a global PATH install.
|
|
39
40
|
|
|
40
41
|
## Lane 4: Webpresso workflow/browser skills
|
|
41
42
|
|
|
@@ -45,6 +46,11 @@ Keep these as an independent lane from the `wp_*` MCP tools.
|
|
|
45
46
|
|
|
46
47
|
## Subprocess coverage note
|
|
47
48
|
|
|
48
|
-
wp
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
wp* tools shelling out via child_process.spawn own their own filtering via
|
|
50
|
+
managed rtk when available. pretool-guard may rewrite exact `git status` to the
|
|
51
|
+
managed rtk binary when present. When rtk is skipped (`WP_SKIP_RTK=1`) or
|
|
52
|
+
unavailable, commands passthrough unfiltered.
|
|
53
|
+
|
|
54
|
+
Long-tail RTK filtering beyond exact `git status` requires an explicit
|
|
55
|
+
`rtk …` invocation (or the managed absolute binary path); pretool-guard
|
|
56
|
+
does not auto-rewrite the rest of the rtk command surface.
|
|
@@ -20,7 +20,7 @@ using `wp_ultragoal_run` / `wp ultragoal run` and the current handoff.
|
|
|
20
20
|
|
|
21
21
|
## Outside-voice (required)
|
|
22
22
|
|
|
23
|
-
After each ultragoal **phase or plan-gate milestone**, run an **OpenCode Go** outside-voice review and pick the model by purpose
|
|
23
|
+
After each ultragoal **phase or plan-gate milestone**, run an **OpenCode Go** outside-voice review and pick the model by purpose. The purpose→family ordering lives in one place — the committed reviewer policy rendered into the `opencode-go` skill — so follow that skill and do not restate the ordering here. Resolve IDs from live `opencode models opencode-go`. Lifecycle authority only via converged `wp review gate` — never self-approve. Full protocol: the `ultragoal` skill. Respect `review_budget` (default owned by `workflow-skills-routing.md`): one sequential path, no multi-host stampede.
|
|
24
24
|
|
|
25
25
|
## Vague gate
|
|
26
26
|
|
|
@@ -34,8 +34,9 @@ A brief is anchored when it includes a file path, symbol, issue number, test nam
|
|
|
34
34
|
A leading `force:` is the only explicit bypass; strip it before creating the goal and
|
|
35
35
|
record the bypass in the blueprint.
|
|
36
36
|
|
|
37
|
-
Default budgets if the user omits them: `concurrency=4`, `fix_budget=1`,
|
|
38
|
-
`review_budget
|
|
37
|
+
Default budgets if the user omits them: `concurrency=4`, `fix_budget=1`, no
|
|
38
|
+
`/loop`; `review_budget` per `workflow-skills-routing.md`. See `ultragoal`
|
|
39
|
+
skill.
|
|
39
40
|
|
|
40
41
|
## Tooling map
|
|
41
42
|
|
|
@@ -21,12 +21,12 @@ multi-agent swarms.
|
|
|
21
21
|
|
|
22
22
|
## Default budgets (override only when the user sets them)
|
|
23
23
|
|
|
24
|
-
| Budget | Default
|
|
25
|
-
| --------------- |
|
|
26
|
-
| `concurrency` | 4 (max 8)
|
|
27
|
-
| `fix_budget` | 1
|
|
28
|
-
| `review_budget` |
|
|
29
|
-
| `loop` | off
|
|
24
|
+
| Budget | Default | Meaning |
|
|
25
|
+
| --------------- | -------------------------------- | -------------------------------------------------- |
|
|
26
|
+
| `concurrency` | 4 (max 8) | Parallel ready tasks / implementer lanes |
|
|
27
|
+
| `fix_budget` | 1 | Pre-existing issues fixable via `/fix` this phase |
|
|
28
|
+
| `review_budget` | see `workflow-skills-routing.md` | Outside-voice reviewer paths, local vs merge-ready |
|
|
29
|
+
| `loop` | off | Do not run `/loop` or unbounded stop-hooks |
|
|
30
30
|
|
|
31
31
|
Reject unbudgeted sleep-mode briefs (“complete all”, “full autopilot”, “go to sleep”)
|
|
32
32
|
unless they name a blueprint/slug, budgets, and a stop condition. Route vague briefs
|
|
@@ -66,7 +66,8 @@ Fallback to `wp …` CLI only when the matching MCP tool is unavailable. Prefer
|
|
|
66
66
|
6. **Opportunistic `/fix`:** at most `fix_budget` pre-existing issues per phase, each with
|
|
67
67
|
named invariant + failing proof. Excess → handoff backlog (do not expand epic).
|
|
68
68
|
7. **Validate / land:** local proof with `wp_qa` / targeted tools; outside review only when
|
|
69
|
-
merge-ready and within `review_budget`
|
|
69
|
+
merge-ready and within `review_budget` — **exactly one sequential** outside voice unless
|
|
70
|
+
the user sets N (the default value lives in `workflow-skills-routing.md`).
|
|
70
71
|
Prefer `wp review gate` for lifecycle authority. Use `wp_pr_*` for PR babysit — no
|
|
71
72
|
multi-sleep poll loops; no parallel multi-reviewer stampede.
|
|
72
73
|
8. Stop on `complete`, `cancelled`, blocked guard exhaustion, or `WP_ULTRAGOAL_DISABLE=1`.
|
|
@@ -87,7 +88,7 @@ primary checkout.
|
|
|
87
88
|
|
|
88
89
|
Autopilot and agent-driven ultragoal loops **must** request an **OpenCode Go** outside-voice review after each **goal/phase milestone**, and must pick the model by purpose. Direct `wp review run` output is advisory only. Lifecycle authority that can satisfy promotion/completion gates comes only from a converged **`wp review gate`** (content-addressed ledger + bound artifact).
|
|
89
90
|
|
|
90
|
-
Count each milestone review against `review_budget` for merge-ready claims: **exactly one sequential** reviewer path unless the user sets N. Prefer purpose-matched OpenCode Go first; do not fan out multi-host review swarms.
|
|
91
|
+
Count each milestone review against `review_budget` for merge-ready claims: **exactly one sequential** reviewer path unless the user sets N; the default value is owned by `workflow-skills-routing.md`. Prefer purpose-matched OpenCode Go first; do not fan out multi-host review swarms.
|
|
91
92
|
|
|
92
93
|
### Milestones that require a review
|
|
93
94
|
|
|
@@ -147,7 +148,8 @@ Use `--purpose plan` for plan-phase promotion and `--purpose delivery` for imple
|
|
|
147
148
|
- If no PR-bound green CI is available, fall back to local **`wp_qa`**.
|
|
148
149
|
- Open a PR early so CI can satisfy QA. Do **not** raise timeouts.
|
|
149
150
|
- **Land** still requires a **MERGED** PR with green required checks bound to the projected
|
|
150
|
-
source snapshot. Use `/verify --merge-ready` (review_budget
|
|
151
|
+
source snapshot. Use `/verify --merge-ready` (merge-ready `review_budget`, per
|
|
152
|
+
`workflow-skills-routing.md`), not default local verify.
|
|
151
153
|
|
|
152
154
|
## Snapshot / dirty worktree
|
|
153
155
|
|
|
@@ -33,7 +33,11 @@ export function detectDrift(entries) {
|
|
|
33
33
|
.map(([key, entries]) => ({ key, count: entries.length, entries }));
|
|
34
34
|
}
|
|
35
35
|
function isRewriter(command) {
|
|
36
|
-
|
|
36
|
+
// Managed pretool-guard emits updatedInput rewrites (exact git status → rtk).
|
|
37
|
+
// Classify it as a rewriter so freestanding RTK + pretool-guard collide in audit.
|
|
38
|
+
return (command.includes("wp-pretool-guard") ||
|
|
39
|
+
command.includes("wp hook pretool-guard") ||
|
|
40
|
+
command.includes("rtk hook claude") ||
|
|
37
41
|
command.endsWith("pretooluse.mjs") ||
|
|
38
42
|
command.includes("/pretooluse.mjs"));
|
|
39
43
|
}
|
|
@@ -49,6 +49,14 @@ export declare function auditCatalogDrift(rootDirectory?: string, options?: Cata
|
|
|
49
49
|
export declare function validateCommitMessage(message: string, options?: CommitMessageOptions): RepoAuditResult;
|
|
50
50
|
export declare function auditCommitMessageFile(messageFile: string, options?: CommitMessageOptions): RepoAuditResult;
|
|
51
51
|
export declare function auditDocsFrontmatter(rootDirectory?: string, options?: DocsFrontmatterOptions): RepoAuditResult;
|
|
52
|
+
/**
|
|
53
|
+
* Flags a blueprint whose raw frontmatter clearly lists one or more
|
|
54
|
+
* `approvals:` entries but whose YAML-parsed `approvals` field yields zero —
|
|
55
|
+
* the corruption signature `wp blueprint move --force-recovery` produces
|
|
56
|
+
* when it silently destroys approval authority without making the document
|
|
57
|
+
* fail to parse outright.
|
|
58
|
+
*/
|
|
59
|
+
export declare function detectSuspiciousZeroApprovals(raw: string, frontmatter: Record<string, unknown>): string | undefined;
|
|
52
60
|
export declare function auditBlueprintLifecycle(rootDirectory?: string, options?: BlueprintLifecycleOptions): RepoAuditResult;
|
|
53
61
|
export declare function formatRepoAuditReport(auditResult: RepoAuditResult): string;
|
|
54
62
|
export declare function parseFrontmatter(markdown: string): Record<string, string>;
|
|
@@ -210,6 +210,53 @@ export function auditDocsFrontmatter(rootDirectory = process.cwd(), options = {}
|
|
|
210
210
|
}
|
|
211
211
|
return result("Docs frontmatter", markdownFiles.length, violations);
|
|
212
212
|
}
|
|
213
|
+
const APPROVALS_KEY_LINE = /^approvals:\s*$/m;
|
|
214
|
+
/**
|
|
215
|
+
* Count `- ` list-item lines that follow a top-level `approvals:` key inside
|
|
216
|
+
* the RAW frontmatter text, stopping at the next top-level key. Returns -1
|
|
217
|
+
* when there is no `approvals:` key at all (nothing to check).
|
|
218
|
+
*
|
|
219
|
+
* This is deliberately independent of the YAML parser: it is the ground
|
|
220
|
+
* truth for "did the author intend approval entries here", used to catch a
|
|
221
|
+
* corrupted write where the YAML still happens to parse but the approvals
|
|
222
|
+
* mapping was silently truncated or displaced (e.g. a scalar-restore bug
|
|
223
|
+
* that inserts or drops lines near the approvals block).
|
|
224
|
+
*/
|
|
225
|
+
function countRawApprovalEntries(raw) {
|
|
226
|
+
const frontmatterBlock = /^---\r?\n([\s\S]*?)\r?\n---/.exec(raw)?.[1] ?? "";
|
|
227
|
+
const keyIndex = frontmatterBlock.search(APPROVALS_KEY_LINE);
|
|
228
|
+
if (keyIndex === -1)
|
|
229
|
+
return -1;
|
|
230
|
+
const afterKey = frontmatterBlock.slice(keyIndex).split("\n").slice(1);
|
|
231
|
+
let count = 0;
|
|
232
|
+
for (const line of afterKey) {
|
|
233
|
+
if (/^[A-Za-z0-9_]+:/.test(line))
|
|
234
|
+
break;
|
|
235
|
+
if (/^\s*-\s/.test(line))
|
|
236
|
+
count += 1;
|
|
237
|
+
}
|
|
238
|
+
return count;
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Flags a blueprint whose raw frontmatter clearly lists one or more
|
|
242
|
+
* `approvals:` entries but whose YAML-parsed `approvals` field yields zero —
|
|
243
|
+
* the corruption signature `wp blueprint move --force-recovery` produces
|
|
244
|
+
* when it silently destroys approval authority without making the document
|
|
245
|
+
* fail to parse outright.
|
|
246
|
+
*/
|
|
247
|
+
export function detectSuspiciousZeroApprovals(raw, frontmatter) {
|
|
248
|
+
const rawEntryCount = countRawApprovalEntries(raw);
|
|
249
|
+
if (rawEntryCount <= 0)
|
|
250
|
+
return undefined;
|
|
251
|
+
const parsedApprovals = frontmatter["approvals"];
|
|
252
|
+
const parsedCount = Array.isArray(parsedApprovals) ? parsedApprovals.length : 0;
|
|
253
|
+
if (parsedCount > 0)
|
|
254
|
+
return undefined;
|
|
255
|
+
return (`Frontmatter parses to 0 approvals but the raw YAML lists ${rawEntryCount} ` +
|
|
256
|
+
"approval entr" +
|
|
257
|
+
(rawEntryCount === 1 ? "y" : "ies") +
|
|
258
|
+
" — likely corrupted frontmatter near the approvals block.");
|
|
259
|
+
}
|
|
213
260
|
export function auditBlueprintLifecycle(rootDirectory = process.cwd(), options = {}) {
|
|
214
261
|
const root = resolve(rootDirectory);
|
|
215
262
|
const blueprintsRoot = resolve(root, options.blueprintsRoot ?? "blueprints");
|
|
@@ -259,7 +306,30 @@ export function auditBlueprintLifecycle(rootDirectory = process.cwd(), options =
|
|
|
259
306
|
continue;
|
|
260
307
|
}
|
|
261
308
|
const raw = readFileSync(canonicalPath, "utf8");
|
|
262
|
-
|
|
309
|
+
let frontmatter;
|
|
310
|
+
try {
|
|
311
|
+
frontmatter = matter(raw).data;
|
|
312
|
+
}
|
|
313
|
+
catch (error) {
|
|
314
|
+
// A blueprint write (e.g. `wp blueprint move --force-recovery`) can
|
|
315
|
+
// emit invalid YAML — most commonly a folded-scalar continuation
|
|
316
|
+
// line stranded after an already-quoted scalar. Every downstream
|
|
317
|
+
// reader (including the approval gate) then fails the same way this
|
|
318
|
+
// parse does, so surface it as a first-class violation instead of
|
|
319
|
+
// letting the whole audit crash on the first corrupted file.
|
|
320
|
+
violations.push({
|
|
321
|
+
file: relativePath(root, canonicalPath),
|
|
322
|
+
message: `Frontmatter failed to parse as YAML: ${error instanceof Error ? error.message : String(error)}`,
|
|
323
|
+
});
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
const suspiciousApprovals = detectSuspiciousZeroApprovals(raw, frontmatter);
|
|
327
|
+
if (suspiciousApprovals) {
|
|
328
|
+
violations.push({
|
|
329
|
+
file: relativePath(root, canonicalPath),
|
|
330
|
+
message: suspiciousApprovals,
|
|
331
|
+
});
|
|
332
|
+
}
|
|
263
333
|
if (frontmatter.type !== "blueprint" && frontmatter.type !== "parent-roadmap") {
|
|
264
334
|
violations.push({
|
|
265
335
|
file: relativePath(root, canonicalPath),
|