@webpresso/agent-kit 0.21.5 → 0.24.0
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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +87 -124
- package/bin/_run.js +143 -1
- package/bin/runtime-manifest.json +40 -0
- package/catalog/AGENTS.md.tpl +7 -6
- package/catalog/agent/commands/plan-refine.md +3 -3
- package/catalog/agent/commands/pll.md +2 -0
- package/catalog/agent/guides/parallel-execution.md +2 -0
- package/catalog/agent/rules/extraction-parity.md +27 -1
- package/catalog/agent/rules/public-package-safety.md +24 -1
- package/catalog/agent/skills/pll/SKILL.md +1 -0
- package/catalog/base-kit/.github/workflows/ci.webpresso.yml.tmpl +33 -0
- package/catalog/base-kit/stryker.config.ts.tmpl +2 -2
- package/catalog/docs/templates/blueprint.md +1 -0
- package/catalog/docs/templates/blueprint.yaml +10 -12
- package/commands/blueprint.md +8 -43
- package/dist/esm/audit/blueprint-db-consistency.d.ts +1 -1
- package/dist/esm/audit/blueprint-db-consistency.js +6 -8
- package/dist/esm/audit/blueprint-lifecycle-sql.js +10 -3
- package/dist/esm/audit/cloudflare-deploy-contract.d.ts +3 -0
- package/dist/esm/audit/cloudflare-deploy-contract.js +80 -0
- package/dist/esm/audit/no-legacy-cli-bin.d.ts +3 -0
- package/dist/esm/audit/no-legacy-cli-bin.js +100 -0
- package/dist/esm/audit/package-surface.js +14 -1
- package/dist/esm/audit/repo-guardrails.js +40 -13
- package/dist/esm/audit/roadmap-links.js +23 -10
- package/dist/esm/blueprint/core/schema.d.ts +8 -8
- package/dist/esm/blueprint/core/schema.js +2 -2
- package/dist/esm/blueprint/db/enums.d.ts +1 -1
- package/dist/esm/blueprint/db/ingester.js +18 -10
- package/dist/esm/blueprint/lifecycle/audit.js +9 -2
- package/dist/esm/blueprint/lifecycle/local.js +15 -4
- package/dist/esm/blueprint/service/BlueprintCreationService.js +11 -6
- package/dist/esm/blueprint/service/BlueprintService.js +37 -19
- package/dist/esm/blueprint/service/scanner.js +73 -9
- package/dist/esm/blueprint/tracked-document/schema.d.ts +2 -2
- package/dist/esm/blueprint/utils/document-paths.d.ts +23 -0
- package/dist/esm/blueprint/utils/document-paths.js +91 -0
- package/dist/esm/build/package-manifest.js +7 -0
- package/dist/esm/build/release-policy.d.ts +27 -0
- package/dist/esm/build/release-policy.js +29 -0
- package/dist/esm/build/runtime-targets.d.ts +13 -0
- package/dist/esm/build/runtime-targets.js +48 -0
- package/dist/esm/cli/auto-update/detect-pm.d.ts +15 -0
- package/dist/esm/cli/auto-update/detect-pm.js +24 -9
- package/dist/esm/cli/auto-update/skip.js +9 -1
- package/dist/esm/cli/bundle/agent-command-inventory.d.ts +120 -0
- package/dist/esm/cli/bundle/agent-command-inventory.js +100 -0
- package/dist/esm/cli/bundle/index.d.ts +17 -0
- package/dist/esm/cli/bundle/index.js +15 -0
- package/dist/esm/cli/cli.d.ts +1 -1
- package/dist/esm/cli/cli.js +49 -5
- package/dist/esm/cli/commands/audit-core.d.ts +1 -1
- package/dist/esm/cli/commands/audit.js +2 -0
- package/dist/esm/cli/commands/blueprint/router.js +11 -8
- package/dist/esm/cli/commands/hook.d.ts +8 -0
- package/dist/esm/cli/commands/hook.js +47 -0
- package/dist/esm/cli/commands/init/index.js +35 -1
- package/dist/esm/cli/commands/init/scaffold-base-kit.js +1 -1
- package/dist/esm/cli/commands/init/scaffolders/agent-hooks/codex-ownership.js +9 -1
- package/dist/esm/cli/commands/init/scaffolders/agent-hooks/index.js +130 -20
- package/dist/esm/cli/commands/init/scaffolders/agent-kit-global/index.d.ts +65 -0
- package/dist/esm/cli/commands/init/scaffolders/agent-kit-global/index.js +64 -0
- package/dist/esm/cli/commands/package-manager.d.ts +15 -0
- package/dist/esm/cli/commands/package-manager.js +42 -0
- package/dist/esm/cli/commands/test.d.ts +1 -0
- package/dist/esm/cli/commands/test.js +2 -1
- package/dist/esm/cli/commands/typecheck.js +5 -20
- package/dist/esm/cli/package-scripts.d.ts +12 -0
- package/dist/esm/cli/package-scripts.js +59 -0
- package/dist/esm/cli/utils.js +3 -22
- package/dist/esm/cli/wp-extensions.d.ts +14 -0
- package/dist/esm/cli/wp-extensions.js +34 -0
- package/dist/esm/config/docs-lint/schemas/common.d.ts +1 -1
- package/dist/esm/config/docs-lint/schemas/implementation-plan.d.ts +2 -2
- package/dist/esm/config/docs-lint/schemas/parent-roadmap.d.ts +1 -1
- package/dist/esm/config/stryker/index.d.ts +85 -0
- package/dist/esm/config/stryker/index.js +31 -0
- package/dist/esm/e2e/command-builder.js +11 -2
- package/dist/esm/e2e/config.d.ts +65 -0
- package/dist/esm/e2e/config.js +126 -0
- package/dist/esm/e2e/execution.js +4 -0
- package/dist/esm/e2e/load-host-adapter.d.ts +6 -1
- package/dist/esm/e2e/load-host-adapter.js +27 -9
- package/dist/esm/e2e/run-planner.js +1 -0
- package/dist/esm/e2e/types.d.ts +2 -0
- package/dist/esm/format/index.js +1 -3
- package/dist/esm/hooks/guard-switch/index.d.ts +1 -1
- package/dist/esm/hooks/guard-switch/index.js +22 -14
- package/dist/esm/hooks/post-tool/lint-after-edit.d.ts +1 -0
- package/dist/esm/hooks/post-tool/lint-after-edit.js +5 -2
- package/dist/esm/hooks/pretool-guard/validators/file-conventions.js +1 -1
- package/dist/esm/hooks/pretool-guard/validators/forbidden-commands.d.ts +6 -0
- package/dist/esm/hooks/pretool-guard/validators/forbidden-commands.js +27 -2
- package/dist/esm/hooks/pretool-guard/validators/path-contract.d.ts +2 -1
- package/dist/esm/hooks/pretool-guard/validators/path-contract.js +59 -34
- package/dist/esm/hooks/pretool-guard/validators/plan-frontmatter.js +3 -3
- package/dist/esm/hooks/shared/routing-block.js +18 -4
- package/dist/esm/hooks/shared/validators/blueprint.js +3 -0
- package/dist/esm/hooks/stop/qa-changed-files.d.ts +1 -0
- package/dist/esm/hooks/stop/qa-changed-files.js +5 -2
- package/dist/esm/lint/index.js +1 -1
- package/dist/esm/mcp/auto-discover.d.ts +2 -0
- package/dist/esm/mcp/auto-discover.js +14 -6
- package/dist/esm/mcp/blueprint-server.js +30 -26
- package/dist/esm/mcp/cli.js +21 -0
- package/dist/esm/mcp/runners/test.js +15 -0
- package/dist/esm/mcp/server.d.ts +7 -0
- package/dist/esm/mcp/server.js +16 -27
- package/dist/esm/mcp/tools/_registry.d.ts +3 -0
- package/dist/esm/mcp/tools/_registry.js +21 -0
- package/dist/esm/mcp/tools/audit.d.ts +1 -0
- package/dist/esm/mcp/tools/audit.js +11 -0
- package/dist/esm/mcp/tools/e2e.d.ts +1 -1
- package/dist/esm/mcp/tools/typecheck.js +4 -2
- package/dist/esm/mutation/affected.d.ts +9 -0
- package/dist/esm/mutation/affected.js +36 -0
- package/dist/esm/package.json +5 -0
- package/dist/esm/runtime/package-version.d.ts +2 -0
- package/dist/esm/runtime/package-version.js +43 -0
- package/dist/esm/test/command-builder.d.ts +3 -0
- package/dist/esm/test/command-builder.js +22 -3
- package/dist/esm/tool-runtime/index.d.ts +2 -2
- package/dist/esm/tool-runtime/index.js +2 -1
- package/dist/esm/tool-runtime/resolve-runner.d.ts +3 -0
- package/dist/esm/tool-runtime/resolve-runner.js +7 -5
- package/dist/esm/typecheck/index.js +4 -2
- package/dist/esm/wp-extension/index.d.ts +50 -0
- package/dist/esm/wp-extension/index.js +268 -0
- package/package.json +67 -31
- package/skills/pll/SKILL.md +1 -0
|
@@ -9,7 +9,7 @@ related:
|
|
|
9
9
|
- package-conventions
|
|
10
10
|
- repo-restrictions
|
|
11
11
|
created: '2026-05-26'
|
|
12
|
-
last_reviewed: '2026-05-
|
|
12
|
+
last_reviewed: '2026-05-31'
|
|
13
13
|
paths:
|
|
14
14
|
- 'package.json'
|
|
15
15
|
- '.npmignore'
|
|
@@ -54,3 +54,26 @@ publish workflow, registry, or catalog assets:
|
|
|
54
54
|
|
|
55
55
|
If the tarball includes a denied class of content, the package is not ready to
|
|
56
56
|
publish. Fix the package surface instead of documenting the leak as acceptable.
|
|
57
|
+
|
|
58
|
+
## Shared deploy-contract surfaces stay provider-neutral
|
|
59
|
+
|
|
60
|
+
If a public package or template documents the approved Durable Object preview
|
|
61
|
+
lanes contract, keep the shared surface policy-only:
|
|
62
|
+
|
|
63
|
+
- treat `dev`, `preview_main`, `preview_pr_<n>`, and `prd` as internal lane
|
|
64
|
+
IDs, not as Cloudflare-facing environment names;
|
|
65
|
+
- derive provider env names separately and make them dash-safe;
|
|
66
|
+
- document that Durable Object-backed previews must not depend on Preview
|
|
67
|
+
URLs; the default is custom-domain environment previews, with
|
|
68
|
+
`workers_dev_env` reserved for explicit exception cases;
|
|
69
|
+
- keep the canonical production release metadata path at
|
|
70
|
+
`infra/release-metadata.production.json`;
|
|
71
|
+
- require the consuming repo's workflow/verifier to fail closed for
|
|
72
|
+
migration-bearing Durable Object releases;
|
|
73
|
+
- keep provider-specific plumbing, secrets, bindings, and release mechanics
|
|
74
|
+
out of shared `@webpresso/agent-kit` code, docs, and examples.
|
|
75
|
+
|
|
76
|
+
Shared packages may define the contract and the guardrail names
|
|
77
|
+
(`verify:deploy-contract`, metadata path, internal lane IDs), but the
|
|
78
|
+
provider-specific implementation belongs in the consuming repo that owns the
|
|
79
|
+
deployment target.
|
|
@@ -81,5 +81,6 @@ When no explicit task list is supplied, ground lane selection in `wp blueprint l
|
|
|
81
81
|
```bash
|
|
82
82
|
/pll "lint auth, lint utils, typecheck api [depends: lint auth], test api [depends: typecheck api]"
|
|
83
83
|
/pll tasks.md --max=6
|
|
84
|
+
/pll blueprints/in-progress/new-launch.md
|
|
84
85
|
/pll blueprints/in-progress/new-launch/_overview.md
|
|
85
86
|
```
|
|
@@ -64,3 +64,36 @@ jobs:
|
|
|
64
64
|
WP="$(pwd)/node_modules/.bin/wp"
|
|
65
65
|
[ -x "$WP" ] || WP=wp
|
|
66
66
|
"$WP" audit guardrails
|
|
67
|
+
|
|
68
|
+
deploy-contract:
|
|
69
|
+
runs-on: ubuntu-latest
|
|
70
|
+
steps:
|
|
71
|
+
- uses: actions/checkout@v5
|
|
72
|
+
- uses: pnpm/action-setup@v6
|
|
73
|
+
- uses: actions/setup-node@v5
|
|
74
|
+
with:
|
|
75
|
+
node-version: '24.16.0'
|
|
76
|
+
cache: pnpm
|
|
77
|
+
- uses: oven-sh/setup-bun@v2
|
|
78
|
+
with:
|
|
79
|
+
bun-version: latest
|
|
80
|
+
- run: pnpm install --frozen-lockfile
|
|
81
|
+
- name: Verify deploy-contract gate when production release metadata is present
|
|
82
|
+
run: |
|
|
83
|
+
set -euo pipefail
|
|
84
|
+
|
|
85
|
+
METADATA_PATH='infra/release-metadata.production.json'
|
|
86
|
+
|
|
87
|
+
if [ ! -f "$METADATA_PATH" ]; then
|
|
88
|
+
echo "No production release metadata present at $METADATA_PATH; skipping deploy-contract gate."
|
|
89
|
+
exit 0
|
|
90
|
+
fi
|
|
91
|
+
|
|
92
|
+
if ! node -e "const pkg=require('./package.json'); process.exit(pkg.scripts && pkg.scripts['verify:deploy-contract'] ? 0 : 1)"; then
|
|
93
|
+
echo "::error::Detected $METADATA_PATH but no verify:deploy-contract script."
|
|
94
|
+
echo "::error::Repos adopting the shared deploy contract must keep agent-kit provider-neutral and supply a repo-owned deploy verifier."
|
|
95
|
+
echo "::error::That verifier owns provider-specific env-name derivation, Preview-URL prohibitions for DO previews, and must fail closed for migration-bearing Durable Object releases."
|
|
96
|
+
exit 1
|
|
97
|
+
fi
|
|
98
|
+
|
|
99
|
+
pnpm run verify:deploy-contract
|
|
@@ -21,6 +21,7 @@ tags: []
|
|
|
21
21
|
- Draft slug: `{{slug}}`
|
|
22
22
|
- Output path: `{{output_path}}`
|
|
23
23
|
- Generated command: `wp blueprint new "{{description}}" --complexity {{complexity}}`
|
|
24
|
+
- Default shape: flat file (`blueprints/<status>/<slug>.md`)
|
|
24
25
|
- Validation scope: parser compliance before write
|
|
25
26
|
|
|
26
27
|
## Architecture Overview
|
|
@@ -4,6 +4,7 @@ description: Blueprint template for features/initiatives
|
|
|
4
4
|
notes:
|
|
5
5
|
- 'This file describes the current preferred blueprint structure, not an immutable final schema.'
|
|
6
6
|
- 'Repo-wide validity is determined by the live blueprint parser/audit rules, not by requiring every optional section below.'
|
|
7
|
+
- 'Default creation shape: blueprints/<status>/<slug>.md. Folder-based blueprints remain valid at blueprints/<status>/<slug>/_overview.md when sibling docs are needed.'
|
|
7
8
|
|
|
8
9
|
frontmatter:
|
|
9
10
|
required:
|
|
@@ -12,7 +13,7 @@ frontmatter:
|
|
|
12
13
|
description: Must be exactly "blueprint"
|
|
13
14
|
status:
|
|
14
15
|
enum: [draft, planned, parked, in-progress, completed, archived]
|
|
15
|
-
description: Current plan status (must match
|
|
16
|
+
description: Current plan status (must match lifecycle placement)
|
|
16
17
|
complexity:
|
|
17
18
|
enum: [XS, S, M, L, XL]
|
|
18
19
|
description: Estimated complexity
|
|
@@ -81,21 +82,18 @@ sections:
|
|
|
81
82
|
|
|
82
83
|
location:
|
|
83
84
|
patterns:
|
|
84
|
-
- 'blueprints
|
|
85
|
+
- 'blueprints/*/*.md'
|
|
85
86
|
- 'blueprints/*/*/_overview.md'
|
|
86
|
-
- 'blueprints
|
|
87
|
-
- 'blueprints/
|
|
88
|
-
- 'blueprints/
|
|
89
|
-
- 'blueprints/
|
|
90
|
-
- 'blueprints/
|
|
91
|
-
- 'blueprints/draft/*/_overview.md'
|
|
92
|
-
exclude:
|
|
93
|
-
- 'blueprints/draft/*.md' # Standalone draft trackers such as deferred backlog
|
|
87
|
+
- 'blueprints/completed/*.md'
|
|
88
|
+
- 'blueprints/planned/*.md'
|
|
89
|
+
- 'blueprints/parked/*.md'
|
|
90
|
+
- 'blueprints/in-progress/*.md'
|
|
91
|
+
- 'blueprints/draft/*.md'
|
|
94
92
|
|
|
95
93
|
naming:
|
|
96
|
-
pattern: '_overview.md'
|
|
94
|
+
pattern: '<slug>.md or _overview.md'
|
|
97
95
|
case: exact
|
|
98
|
-
notes: 'Use
|
|
96
|
+
notes: 'Use <slug>.md by default. Use folder + _overview.md when the blueprint needs sibling markdown docs.'
|
|
99
97
|
|
|
100
98
|
task_format:
|
|
101
99
|
heading:
|
package/commands/blueprint.md
CHANGED
|
@@ -15,47 +15,12 @@ Use the focused blueprint MCP tools.
|
|
|
15
15
|
- `wp_blueprint_task_verify` — mark a task `done` with evidence; accepts optional `request_id` and `head_at_ingest` for retry-safe verification
|
|
16
16
|
- `wp_blueprint_promote` / `wp_blueprint_finalize` — accept optional `project_id` for nested-workspace disambiguation
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
Guidance:
|
|
19
19
|
|
|
20
|
-
-
|
|
21
|
-
- Use `request_id`
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
-
|
|
25
|
-
|
|
26
|
-
-
|
|
27
|
-
`wp_blueprint_context` into `wp_blueprint_create`, `wp_blueprint_put`, and
|
|
28
|
-
other mutation calls when the caller needs stale-write protection across
|
|
29
|
-
retries or multi-agent handoff.
|
|
30
|
-
- Reusing the same `request_id` with the same payload is idempotent.
|
|
31
|
-
- Reusing the same `request_id` with a different payload is rejected.
|
|
32
|
-
- If `head_at_ingest` is stale, the mutation is rejected and points the caller
|
|
33
|
-
back to a canonical `wp_*` refresh path.
|
|
34
|
-
- `wp_blueprint_transition` uses `expected_version` (the current
|
|
35
|
-
`content_hash`) for blueprint-scoped optimistic concurrency.
|
|
36
|
-
|
|
37
|
-
Deferred patch boundary:
|
|
38
|
-
|
|
39
|
-
- `wp_blueprint_patch` is **not** part of the v1 canonical surface.
|
|
40
|
-
- If/when added, the patch model must be **semantic**, not raw markdown
|
|
41
|
-
mutation.
|
|
42
|
-
- Minimum deferred operations:
|
|
43
|
-
- `add_task`
|
|
44
|
-
- `update_task`
|
|
45
|
-
- `set_summary`
|
|
46
|
-
- `replace_decision`
|
|
47
|
-
- Until then, whole-document `wp_blueprint_put` is the canonical authoring path.
|
|
48
|
-
|
|
49
|
-
Deferred UI/editor boundary:
|
|
50
|
-
|
|
51
|
-
- A future MCP Apps blueprint editor is an **enhancement**, not part of the v1
|
|
52
|
-
correctness path.
|
|
53
|
-
- Any UI flow must layer on top of `wp_blueprint_put + wp_blueprint_transition`
|
|
54
|
-
instead of introducing a parallel write surface.
|
|
55
|
-
- Hosts without MCP Apps support must still complete the full authoring flow
|
|
56
|
-
through the structured tools alone.
|
|
57
|
-
- Minimum v2 editor contract:
|
|
58
|
-
- capability detection for MCP Apps support
|
|
59
|
-
- structured form/editor over the full blueprint document
|
|
60
|
-
- non-UI fallback guidance that routes back to `wp_blueprint_put` and
|
|
61
|
-
`wp_blueprint_transition`
|
|
20
|
+
- Prefer `project_id` from `wp_blueprint_projects` when multiple repos are visible.
|
|
21
|
+
- Use `request_id` for retry-safe mutations and reuse it only with the same payload.
|
|
22
|
+
- Carry `head_at_ingest` from read/context tools into stale-write-sensitive mutations.
|
|
23
|
+
- Author documents through `wp_blueprint_put`, then lifecycle with `wp_blueprint_transition`.
|
|
24
|
+
- Deferred `wp_blueprint_patch` semantic ops (`add_task`, `update_task`, `set_summary`, `replace_decision`) are future layers; patch is **not** part of the v1 canonical surface.
|
|
25
|
+
- MCP Apps editor support is a follow-on enhancement over `wp_blueprint_put` / `wp_blueprint_transition`.
|
|
26
|
+
- Hosts without MCP Apps support keep using the structured tools above.
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Checks (when enabled):
|
|
9
9
|
* 1. Every `blueprints` row's `file_path` actually exists on disk.
|
|
10
|
-
* 2. Every blueprint
|
|
10
|
+
* 2. Every canonical blueprint markdown file on disk has a corresponding DB row.
|
|
11
11
|
* 3. `content_hash` in DB matches the current SHA-256 of the file content.
|
|
12
12
|
*/
|
|
13
13
|
import type { RepoAuditResult } from './repo-guardrails.js';
|
|
@@ -7,13 +7,13 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Checks (when enabled):
|
|
9
9
|
* 1. Every `blueprints` row's `file_path` actually exists on disk.
|
|
10
|
-
* 2. Every blueprint
|
|
10
|
+
* 2. Every canonical blueprint markdown file on disk has a corresponding DB row.
|
|
11
11
|
* 3. `content_hash` in DB matches the current SHA-256 of the file content.
|
|
12
12
|
*/
|
|
13
13
|
import { createHash } from 'node:crypto';
|
|
14
14
|
import { existsSync, readFileSync } from 'node:fs';
|
|
15
15
|
import path from 'node:path';
|
|
16
|
-
import {
|
|
16
|
+
import { scanBlueprintDirectory } from '#service/scanner.js';
|
|
17
17
|
const DB_PATH = path.join('.agent', '.blueprints.db');
|
|
18
18
|
const _DISABLED_RESULT = {
|
|
19
19
|
ok: true,
|
|
@@ -76,12 +76,10 @@ export async function auditBlueprintDbConsistency(cwd) {
|
|
|
76
76
|
// 2: files on disk → verify each has a DB row
|
|
77
77
|
// -----------------------------------------------------------------------
|
|
78
78
|
const dbPaths = new Set(rows.map((r) => r.file_path));
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
ignore: ['node_modules/**'],
|
|
84
|
-
});
|
|
79
|
+
const overviewFiles = scanBlueprintDirectory({
|
|
80
|
+
baseDir: path.join(cwd, 'blueprints'),
|
|
81
|
+
includeSpecialFolders: true,
|
|
82
|
+
}).map((entry) => path.relative(cwd, entry.path).replace(/\\/g, '/'));
|
|
85
83
|
checked += overviewFiles.length;
|
|
86
84
|
for (const rel of overviewFiles) {
|
|
87
85
|
const normalised = rel.replace(/\\/g, '/');
|
|
@@ -45,6 +45,16 @@ export async function auditBlueprintLifecycleSql(cwd) {
|
|
|
45
45
|
const violations = [];
|
|
46
46
|
let checked = 0;
|
|
47
47
|
try {
|
|
48
|
+
const allBlueprints = db
|
|
49
|
+
.prepare('SELECT slug, status, file_path, progress_pct FROM blueprints')
|
|
50
|
+
.all();
|
|
51
|
+
if (allBlueprints.length === 0) {
|
|
52
|
+
const { auditBlueprintLifecycle } = await import('./repo-guardrails.js');
|
|
53
|
+
const markdownAudit = auditBlueprintLifecycle(cwd);
|
|
54
|
+
if (markdownAudit.checked > 0) {
|
|
55
|
+
return markdownAudit;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
48
58
|
// -----------------------------------------------------------------------
|
|
49
59
|
// 1. in-progress blueprints with 0 tasks
|
|
50
60
|
// -----------------------------------------------------------------------
|
|
@@ -68,9 +78,6 @@ export async function auditBlueprintLifecycleSql(cwd) {
|
|
|
68
78
|
// Derive the directory segment from the file_path and compare to status.
|
|
69
79
|
// Blueprint file_path convention: blueprints/<status>/<slug>/_overview.md
|
|
70
80
|
// -----------------------------------------------------------------------
|
|
71
|
-
const allBlueprints = db
|
|
72
|
-
.prepare('SELECT slug, status, file_path, progress_pct FROM blueprints')
|
|
73
|
-
.all();
|
|
74
81
|
checked += allBlueprints.length;
|
|
75
82
|
for (const row of allBlueprints) {
|
|
76
83
|
// Derive directory status from the path: second segment after 'blueprints/'
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { loadWebpressoConfigSafe } from '#e2e/load-host-adapter';
|
|
4
|
+
function violation(file, message) {
|
|
5
|
+
return { file, message };
|
|
6
|
+
}
|
|
7
|
+
function readProductionMetadata(metadataPath) {
|
|
8
|
+
return JSON.parse(readFileSync(metadataPath, 'utf8'));
|
|
9
|
+
}
|
|
10
|
+
export async function auditCloudflareDeployContract(root) {
|
|
11
|
+
let loaded;
|
|
12
|
+
try {
|
|
13
|
+
loaded = await loadWebpressoConfigSafe({ cwd: root });
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
17
|
+
return {
|
|
18
|
+
ok: false,
|
|
19
|
+
title: 'Cloudflare deploy contract',
|
|
20
|
+
checked: 1,
|
|
21
|
+
violations: [violation(path.join(root, 'webpresso.config.ts'), message)],
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
const configPath = loaded?.configPath ?? path.join(root, 'webpresso.config.ts');
|
|
25
|
+
const cloudflare = loaded?.config.deploy?.cloudflare;
|
|
26
|
+
if (!cloudflare) {
|
|
27
|
+
return {
|
|
28
|
+
ok: true,
|
|
29
|
+
title: 'Cloudflare deploy contract',
|
|
30
|
+
checked: 0,
|
|
31
|
+
violations: [],
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
const violations = [];
|
|
35
|
+
const metadataPath = path.join(root, cloudflare.production.metadataPath);
|
|
36
|
+
if (!existsSync(metadataPath)) {
|
|
37
|
+
violations.push(violation(configPath, `shared deploy contract requires ${cloudflare.production.metadataPath} to exist`));
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
try {
|
|
41
|
+
const metadata = readProductionMetadata(metadataPath);
|
|
42
|
+
if (metadata.durableObjectMigration === 'required' &&
|
|
43
|
+
metadata.rolloutMode !== 'direct') {
|
|
44
|
+
violations.push(violation(cloudflare.production.metadataPath, 'Durable Object migration releases must use rolloutMode "direct"'));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
49
|
+
violations.push(violation(cloudflare.production.metadataPath, `production release metadata must be valid JSON: ${message}`));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
for (const target of cloudflare.targets) {
|
|
53
|
+
const isDurableObjectTarget = (target.durableObjectBindings?.length ?? 0) > 0;
|
|
54
|
+
if (target.previewTransport === 'custom_domain_env' && !target.routeSpec) {
|
|
55
|
+
violations.push(violation(configPath, `target ${target.id} uses custom_domain_env but does not declare routeSpec`));
|
|
56
|
+
}
|
|
57
|
+
if (target.durableObjectBindings && target.durableObjectBindings.length === 0) {
|
|
58
|
+
violations.push(violation(configPath, `target ${target.id} declares durableObjectBindings but provides no env-specific bindings`));
|
|
59
|
+
}
|
|
60
|
+
if (isDurableObjectTarget && target.previewTransport !== 'custom_domain_env') {
|
|
61
|
+
violations.push(violation(configPath, `target ${target.id} is a Durable Object consumer and must use previewTransport "custom_domain_env"`));
|
|
62
|
+
}
|
|
63
|
+
if (isDurableObjectTarget && Object.keys(target.vars).length === 0) {
|
|
64
|
+
violations.push(violation(configPath, `target ${target.id} is a Durable Object consumer and must declare at least one env-specific var`));
|
|
65
|
+
}
|
|
66
|
+
if (isDurableObjectTarget && target.requiredSecrets.length === 0) {
|
|
67
|
+
violations.push(violation(configPath, `target ${target.id} is a Durable Object consumer and must declare at least one required secret name`));
|
|
68
|
+
}
|
|
69
|
+
if (target.storageMode === 'shared_via_script_name' && !target.blastRadiusDoc) {
|
|
70
|
+
violations.push(violation(configPath, `target ${target.id} uses shared_via_script_name without blastRadiusDoc`));
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
ok: violations.length === 0,
|
|
75
|
+
title: 'Cloudflare deploy contract',
|
|
76
|
+
checked: 1 + cloudflare.targets.length,
|
|
77
|
+
violations,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=cloudflare-deploy-contract.js.map
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { join, relative, resolve } from 'node:path';
|
|
3
|
+
// `wp` remains the canonical public CLI in AGENTS.md. This audit only guards
|
|
4
|
+
// retired compatibility aliases from leaking into active user-facing surfaces.
|
|
5
|
+
const LEGACY_COMMAND_PATTERN = /\b(?:ak|cli2|wk)\s+[a-z][\w:-]*(?:\s+[a-z][\w:-]*)*/giu;
|
|
6
|
+
const INTERNAL_HELPER_PATTERN = /\bwp-(?:pretool-guard|post-tool|stop-qa|guard-switch|sessionstart-routing|check-dev-link)\b/u;
|
|
7
|
+
const REPLACEMENT_PATTERN = /\bwebpresso agent [a-z][\w:-]*(?: [a-z][\w:-]*)*/iu;
|
|
8
|
+
const MIGRATION_MARKER_PATTERN = /\b(?:current-state|migration-only|replacement|future)\b/iu;
|
|
9
|
+
// Scan current user-facing/documentation inputs only. Source tests and completed/parked
|
|
10
|
+
// blueprints intentionally preserve historical command names as regression fixtures
|
|
11
|
+
// and audit evidence; blocking them would make this guardrail non-adoptable.
|
|
12
|
+
const SCAN_DIRS = [
|
|
13
|
+
'catalog',
|
|
14
|
+
'commands',
|
|
15
|
+
'scripts',
|
|
16
|
+
'test-fixtures',
|
|
17
|
+
'blueprints/planned',
|
|
18
|
+
'blueprints/in-progress',
|
|
19
|
+
];
|
|
20
|
+
const SKIP_DIRS = new Set(['node_modules', '.git', 'dist', 'build', '.agent', '.omx', '.codex']);
|
|
21
|
+
const TEXT_EXTENSIONS = new Set([
|
|
22
|
+
'.md',
|
|
23
|
+
'.mdx',
|
|
24
|
+
'.txt',
|
|
25
|
+
'.json',
|
|
26
|
+
'.yaml',
|
|
27
|
+
'.yml',
|
|
28
|
+
'.ts',
|
|
29
|
+
'.tsx',
|
|
30
|
+
'.js',
|
|
31
|
+
'.jsx',
|
|
32
|
+
'.sh',
|
|
33
|
+
]);
|
|
34
|
+
export function auditNoLegacyCliBin(rootDirectory = process.cwd()) {
|
|
35
|
+
const root = resolve(rootDirectory);
|
|
36
|
+
const violations = [];
|
|
37
|
+
let checked = 0;
|
|
38
|
+
for (const dir of SCAN_DIRS) {
|
|
39
|
+
const absoluteDir = join(root, dir);
|
|
40
|
+
if (!existsSync(absoluteDir))
|
|
41
|
+
continue;
|
|
42
|
+
for (const absolutePath of walkTextFiles(absoluteDir)) {
|
|
43
|
+
checked += 1;
|
|
44
|
+
const relativePath = relative(root, absolutePath);
|
|
45
|
+
const content = readFileSync(absolutePath, 'utf8');
|
|
46
|
+
const lines = content.split('\n');
|
|
47
|
+
for (const [index, line] of lines.entries()) {
|
|
48
|
+
const matches = line.matchAll(LEGACY_COMMAND_PATTERN);
|
|
49
|
+
for (const match of matches) {
|
|
50
|
+
const snippet = match[0]?.trim();
|
|
51
|
+
if (!snippet)
|
|
52
|
+
continue;
|
|
53
|
+
if (isAllowedLegacyLine(line))
|
|
54
|
+
continue;
|
|
55
|
+
violations.push({
|
|
56
|
+
file: relativePath,
|
|
57
|
+
message: `Line ${index + 1} exposes legacy command ${JSON.stringify(snippet)} without current-state/migration context and an exact \`webpresso agent ...\` replacement.`,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
ok: violations.length === 0,
|
|
65
|
+
title: 'No retired CLI aliases in active docs/scripts',
|
|
66
|
+
checked,
|
|
67
|
+
violations,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
function walkTextFiles(directory) {
|
|
71
|
+
const entries = readdirSync(directory, { withFileTypes: true });
|
|
72
|
+
const files = [];
|
|
73
|
+
for (const entry of entries) {
|
|
74
|
+
if (SKIP_DIRS.has(entry.name))
|
|
75
|
+
continue;
|
|
76
|
+
const absolutePath = join(directory, entry.name);
|
|
77
|
+
if (entry.isDirectory()) {
|
|
78
|
+
files.push(...walkTextFiles(absolutePath));
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (!entry.isFile())
|
|
82
|
+
continue;
|
|
83
|
+
const extension = entry.name.includes('.') ? `.${entry.name.split('.').pop()}` : '';
|
|
84
|
+
if (TEXT_EXTENSIONS.has(extension))
|
|
85
|
+
files.push(absolutePath);
|
|
86
|
+
}
|
|
87
|
+
return files;
|
|
88
|
+
}
|
|
89
|
+
function isAllowedLegacyLine(line) {
|
|
90
|
+
LEGACY_COMMAND_PATTERN.lastIndex = 0;
|
|
91
|
+
if (!LEGACY_COMMAND_PATTERN.test(line))
|
|
92
|
+
return true;
|
|
93
|
+
LEGACY_COMMAND_PATTERN.lastIndex = 0;
|
|
94
|
+
if (INTERNAL_HELPER_PATTERN.test(line))
|
|
95
|
+
return true;
|
|
96
|
+
if (!MIGRATION_MARKER_PATTERN.test(line))
|
|
97
|
+
return false;
|
|
98
|
+
return REPLACEMENT_PATTERN.test(line);
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=no-legacy-cli-bin.js.map
|
|
@@ -47,6 +47,7 @@ const SKIP_DIRECTORIES = new Set([
|
|
|
47
47
|
'.omx',
|
|
48
48
|
'.omc',
|
|
49
49
|
'.stryker-tmp',
|
|
50
|
+
'.webpresso-packed-surface',
|
|
50
51
|
'node_modules',
|
|
51
52
|
'dist',
|
|
52
53
|
'build',
|
|
@@ -393,6 +394,7 @@ function stagePackedFiles(root, destinationRoot, candidate, packedFiles) {
|
|
|
393
394
|
function runSecretlint(stageRoot, packageRoot) {
|
|
394
395
|
const rcPath = findSecretlintRc(packageRoot);
|
|
395
396
|
const outputFile = join(stageRoot, '.secretlint-output.json');
|
|
397
|
+
const toolRoot = findSecretlintToolRoot(packageRoot) ?? findSecretlintToolRoot(process.cwd());
|
|
396
398
|
const args = ['exec', 'secretlint', '--format', 'json', '--output', outputFile, '--no-gitignore'];
|
|
397
399
|
if (rcPath) {
|
|
398
400
|
args.push('--secretlintrc', rcPath);
|
|
@@ -403,7 +405,7 @@ function runSecretlint(stageRoot, packageRoot) {
|
|
|
403
405
|
args.push(join(stageRoot, '**/*'));
|
|
404
406
|
try {
|
|
405
407
|
execFileSync('pnpm', args, {
|
|
406
|
-
cwd: packageRoot,
|
|
408
|
+
cwd: toolRoot ?? packageRoot,
|
|
407
409
|
encoding: 'utf8',
|
|
408
410
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
409
411
|
maxBuffer: 20 * 1024 * 1024,
|
|
@@ -421,6 +423,17 @@ function runSecretlint(stageRoot, packageRoot) {
|
|
|
421
423
|
rmSync(outputFile, { force: true });
|
|
422
424
|
}
|
|
423
425
|
}
|
|
426
|
+
function findSecretlintToolRoot(start) {
|
|
427
|
+
let current = resolve(start);
|
|
428
|
+
while (true) {
|
|
429
|
+
if (existsSync(join(current, 'node_modules', '.bin', 'secretlint')))
|
|
430
|
+
return current;
|
|
431
|
+
const parent = dirname(current);
|
|
432
|
+
if (parent === current)
|
|
433
|
+
return undefined;
|
|
434
|
+
current = parent;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
424
437
|
function findSecretlintRc(root) {
|
|
425
438
|
const candidates = [
|
|
426
439
|
'.secretlintrc.json',
|
|
@@ -2,6 +2,7 @@ import { existsSync, readFileSync, readdirSync, writeFileSync } from 'node:fs';
|
|
|
2
2
|
import { join, relative, resolve, sep } from 'node:path';
|
|
3
3
|
import matter from 'gray-matter';
|
|
4
4
|
import { blueprintDerivedHandoffSchema } from '#execution/types';
|
|
5
|
+
import { BLUEPRINT_OVERVIEW_FILENAME, isBlueprintSupportingMarkdownRelativePath, parseBlueprintDocumentRelativePath, } from '#utils/document-paths.js';
|
|
5
6
|
import { validateLoreTrailers } from './commit-message-lore.js';
|
|
6
7
|
const DEFAULT_COMMIT_TYPES = [
|
|
7
8
|
'feat',
|
|
@@ -209,33 +210,59 @@ export function auditBlueprintLifecycle(rootDirectory = process.cwd(), options =
|
|
|
209
210
|
if (!existsSync(statusRoot))
|
|
210
211
|
continue;
|
|
211
212
|
for (const entry of readdirSync(statusRoot, { withFileTypes: true })) {
|
|
212
|
-
if (!entry.isDirectory())
|
|
213
|
+
if (!entry.isDirectory() && !entry.isFile())
|
|
213
214
|
continue;
|
|
214
|
-
const
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
215
|
+
const canonicalPath = entry.isDirectory()
|
|
216
|
+
? join(statusRoot, entry.name, BLUEPRINT_OVERVIEW_FILENAME)
|
|
217
|
+
: join(statusRoot, entry.name);
|
|
218
|
+
const relativeBlueprintPath = relative(blueprintsRoot, canonicalPath);
|
|
219
|
+
const parsedPath = parseBlueprintDocumentRelativePath(relativeBlueprintPath);
|
|
220
|
+
if (entry.isDirectory()) {
|
|
221
|
+
checked += 1;
|
|
222
|
+
if (!existsSync(canonicalPath)) {
|
|
223
|
+
const hasNestedMarkdown = readdirSync(join(statusRoot, entry.name), {
|
|
224
|
+
withFileTypes: true,
|
|
225
|
+
}).some((nested) => nested.isFile() && nested.name.endsWith('.md') && nested.name !== 'README.md');
|
|
226
|
+
if (hasNestedMarkdown) {
|
|
227
|
+
violations.push({
|
|
228
|
+
file: relativePath(root, canonicalPath),
|
|
229
|
+
message: 'Blueprint folders with supporting markdown must include _overview.md',
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
violations.push({
|
|
234
|
+
file: relativePath(root, canonicalPath),
|
|
235
|
+
message: 'Missing _overview.md',
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
if (!parsedPath || isBlueprintSupportingMarkdownRelativePath(relativeBlueprintPath)) {
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
checked += 1;
|
|
246
|
+
}
|
|
247
|
+
if (!parsedPath) {
|
|
221
248
|
continue;
|
|
222
249
|
}
|
|
223
|
-
const raw = readFileSync(
|
|
250
|
+
const raw = readFileSync(canonicalPath, 'utf8');
|
|
224
251
|
const frontmatter = matter(raw).data;
|
|
225
252
|
if (frontmatter.type !== 'blueprint' && frontmatter.type !== 'parent-roadmap') {
|
|
226
253
|
violations.push({
|
|
227
|
-
file: relativePath(root,
|
|
228
|
-
message: 'Blueprint
|
|
254
|
+
file: relativePath(root, canonicalPath),
|
|
255
|
+
message: 'Blueprint markdown must use type: blueprint or parent-roadmap',
|
|
229
256
|
});
|
|
230
257
|
}
|
|
231
258
|
if (frontmatter.status !== status) {
|
|
232
259
|
violations.push({
|
|
233
|
-
file: relativePath(root,
|
|
260
|
+
file: relativePath(root, canonicalPath),
|
|
234
261
|
message: `Blueprint status must match folder (${status})`,
|
|
235
262
|
});
|
|
236
263
|
}
|
|
237
264
|
violations.push(...validateBlueprintLinkingFrontmatter({
|
|
238
|
-
file: relativePath(root,
|
|
265
|
+
file: relativePath(root, canonicalPath),
|
|
239
266
|
frontmatter,
|
|
240
267
|
status,
|
|
241
268
|
}));
|