@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.
Files changed (132) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.md +87 -124
  4. package/bin/_run.js +143 -1
  5. package/bin/runtime-manifest.json +40 -0
  6. package/catalog/AGENTS.md.tpl +7 -6
  7. package/catalog/agent/commands/plan-refine.md +3 -3
  8. package/catalog/agent/commands/pll.md +2 -0
  9. package/catalog/agent/guides/parallel-execution.md +2 -0
  10. package/catalog/agent/rules/extraction-parity.md +27 -1
  11. package/catalog/agent/rules/public-package-safety.md +24 -1
  12. package/catalog/agent/skills/pll/SKILL.md +1 -0
  13. package/catalog/base-kit/.github/workflows/ci.webpresso.yml.tmpl +33 -0
  14. package/catalog/base-kit/stryker.config.ts.tmpl +2 -2
  15. package/catalog/docs/templates/blueprint.md +1 -0
  16. package/catalog/docs/templates/blueprint.yaml +10 -12
  17. package/commands/blueprint.md +8 -43
  18. package/dist/esm/audit/blueprint-db-consistency.d.ts +1 -1
  19. package/dist/esm/audit/blueprint-db-consistency.js +6 -8
  20. package/dist/esm/audit/blueprint-lifecycle-sql.js +10 -3
  21. package/dist/esm/audit/cloudflare-deploy-contract.d.ts +3 -0
  22. package/dist/esm/audit/cloudflare-deploy-contract.js +80 -0
  23. package/dist/esm/audit/no-legacy-cli-bin.d.ts +3 -0
  24. package/dist/esm/audit/no-legacy-cli-bin.js +100 -0
  25. package/dist/esm/audit/package-surface.js +14 -1
  26. package/dist/esm/audit/repo-guardrails.js +40 -13
  27. package/dist/esm/audit/roadmap-links.js +23 -10
  28. package/dist/esm/blueprint/core/schema.d.ts +8 -8
  29. package/dist/esm/blueprint/core/schema.js +2 -2
  30. package/dist/esm/blueprint/db/enums.d.ts +1 -1
  31. package/dist/esm/blueprint/db/ingester.js +18 -10
  32. package/dist/esm/blueprint/lifecycle/audit.js +9 -2
  33. package/dist/esm/blueprint/lifecycle/local.js +15 -4
  34. package/dist/esm/blueprint/service/BlueprintCreationService.js +11 -6
  35. package/dist/esm/blueprint/service/BlueprintService.js +37 -19
  36. package/dist/esm/blueprint/service/scanner.js +73 -9
  37. package/dist/esm/blueprint/tracked-document/schema.d.ts +2 -2
  38. package/dist/esm/blueprint/utils/document-paths.d.ts +23 -0
  39. package/dist/esm/blueprint/utils/document-paths.js +91 -0
  40. package/dist/esm/build/package-manifest.js +7 -0
  41. package/dist/esm/build/release-policy.d.ts +27 -0
  42. package/dist/esm/build/release-policy.js +29 -0
  43. package/dist/esm/build/runtime-targets.d.ts +13 -0
  44. package/dist/esm/build/runtime-targets.js +48 -0
  45. package/dist/esm/cli/auto-update/detect-pm.d.ts +15 -0
  46. package/dist/esm/cli/auto-update/detect-pm.js +24 -9
  47. package/dist/esm/cli/auto-update/skip.js +9 -1
  48. package/dist/esm/cli/bundle/agent-command-inventory.d.ts +120 -0
  49. package/dist/esm/cli/bundle/agent-command-inventory.js +100 -0
  50. package/dist/esm/cli/bundle/index.d.ts +17 -0
  51. package/dist/esm/cli/bundle/index.js +15 -0
  52. package/dist/esm/cli/cli.d.ts +1 -1
  53. package/dist/esm/cli/cli.js +49 -5
  54. package/dist/esm/cli/commands/audit-core.d.ts +1 -1
  55. package/dist/esm/cli/commands/audit.js +2 -0
  56. package/dist/esm/cli/commands/blueprint/router.js +11 -8
  57. package/dist/esm/cli/commands/hook.d.ts +8 -0
  58. package/dist/esm/cli/commands/hook.js +47 -0
  59. package/dist/esm/cli/commands/init/index.js +35 -1
  60. package/dist/esm/cli/commands/init/scaffold-base-kit.js +1 -1
  61. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/codex-ownership.js +9 -1
  62. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/index.js +130 -20
  63. package/dist/esm/cli/commands/init/scaffolders/agent-kit-global/index.d.ts +65 -0
  64. package/dist/esm/cli/commands/init/scaffolders/agent-kit-global/index.js +64 -0
  65. package/dist/esm/cli/commands/package-manager.d.ts +15 -0
  66. package/dist/esm/cli/commands/package-manager.js +42 -0
  67. package/dist/esm/cli/commands/test.d.ts +1 -0
  68. package/dist/esm/cli/commands/test.js +2 -1
  69. package/dist/esm/cli/commands/typecheck.js +5 -20
  70. package/dist/esm/cli/package-scripts.d.ts +12 -0
  71. package/dist/esm/cli/package-scripts.js +59 -0
  72. package/dist/esm/cli/utils.js +3 -22
  73. package/dist/esm/cli/wp-extensions.d.ts +14 -0
  74. package/dist/esm/cli/wp-extensions.js +34 -0
  75. package/dist/esm/config/docs-lint/schemas/common.d.ts +1 -1
  76. package/dist/esm/config/docs-lint/schemas/implementation-plan.d.ts +2 -2
  77. package/dist/esm/config/docs-lint/schemas/parent-roadmap.d.ts +1 -1
  78. package/dist/esm/config/stryker/index.d.ts +85 -0
  79. package/dist/esm/config/stryker/index.js +31 -0
  80. package/dist/esm/e2e/command-builder.js +11 -2
  81. package/dist/esm/e2e/config.d.ts +65 -0
  82. package/dist/esm/e2e/config.js +126 -0
  83. package/dist/esm/e2e/execution.js +4 -0
  84. package/dist/esm/e2e/load-host-adapter.d.ts +6 -1
  85. package/dist/esm/e2e/load-host-adapter.js +27 -9
  86. package/dist/esm/e2e/run-planner.js +1 -0
  87. package/dist/esm/e2e/types.d.ts +2 -0
  88. package/dist/esm/format/index.js +1 -3
  89. package/dist/esm/hooks/guard-switch/index.d.ts +1 -1
  90. package/dist/esm/hooks/guard-switch/index.js +22 -14
  91. package/dist/esm/hooks/post-tool/lint-after-edit.d.ts +1 -0
  92. package/dist/esm/hooks/post-tool/lint-after-edit.js +5 -2
  93. package/dist/esm/hooks/pretool-guard/validators/file-conventions.js +1 -1
  94. package/dist/esm/hooks/pretool-guard/validators/forbidden-commands.d.ts +6 -0
  95. package/dist/esm/hooks/pretool-guard/validators/forbidden-commands.js +27 -2
  96. package/dist/esm/hooks/pretool-guard/validators/path-contract.d.ts +2 -1
  97. package/dist/esm/hooks/pretool-guard/validators/path-contract.js +59 -34
  98. package/dist/esm/hooks/pretool-guard/validators/plan-frontmatter.js +3 -3
  99. package/dist/esm/hooks/shared/routing-block.js +18 -4
  100. package/dist/esm/hooks/shared/validators/blueprint.js +3 -0
  101. package/dist/esm/hooks/stop/qa-changed-files.d.ts +1 -0
  102. package/dist/esm/hooks/stop/qa-changed-files.js +5 -2
  103. package/dist/esm/lint/index.js +1 -1
  104. package/dist/esm/mcp/auto-discover.d.ts +2 -0
  105. package/dist/esm/mcp/auto-discover.js +14 -6
  106. package/dist/esm/mcp/blueprint-server.js +30 -26
  107. package/dist/esm/mcp/cli.js +21 -0
  108. package/dist/esm/mcp/runners/test.js +15 -0
  109. package/dist/esm/mcp/server.d.ts +7 -0
  110. package/dist/esm/mcp/server.js +16 -27
  111. package/dist/esm/mcp/tools/_registry.d.ts +3 -0
  112. package/dist/esm/mcp/tools/_registry.js +21 -0
  113. package/dist/esm/mcp/tools/audit.d.ts +1 -0
  114. package/dist/esm/mcp/tools/audit.js +11 -0
  115. package/dist/esm/mcp/tools/e2e.d.ts +1 -1
  116. package/dist/esm/mcp/tools/typecheck.js +4 -2
  117. package/dist/esm/mutation/affected.d.ts +9 -0
  118. package/dist/esm/mutation/affected.js +36 -0
  119. package/dist/esm/package.json +5 -0
  120. package/dist/esm/runtime/package-version.d.ts +2 -0
  121. package/dist/esm/runtime/package-version.js +43 -0
  122. package/dist/esm/test/command-builder.d.ts +3 -0
  123. package/dist/esm/test/command-builder.js +22 -3
  124. package/dist/esm/tool-runtime/index.d.ts +2 -2
  125. package/dist/esm/tool-runtime/index.js +2 -1
  126. package/dist/esm/tool-runtime/resolve-runner.d.ts +3 -0
  127. package/dist/esm/tool-runtime/resolve-runner.js +7 -5
  128. package/dist/esm/typecheck/index.js +4 -2
  129. package/dist/esm/wp-extension/index.d.ts +50 -0
  130. package/dist/esm/wp-extension/index.js +268 -0
  131. package/package.json +67 -31
  132. 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-26'
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
@@ -1,7 +1,7 @@
1
- import { baseConfig } from '@webpresso/agent-kit/stryker'
1
+ import { typescriptBaseConfig } from '@webpresso/agent-kit/stryker'
2
2
 
3
3
  export default {
4
- ...baseConfig,
4
+ ...typescriptBaseConfig,
5
5
  thresholds: {
6
6
  high: 0,
7
7
  low: 0,
@@ -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 folder placement)
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/*/_overview.md'
85
+ - 'blueprints/*/*.md'
85
86
  - 'blueprints/*/*/_overview.md'
86
- - 'blueprints/*/*/*/_overview.md'
87
- - 'blueprints/completed/*/_overview.md'
88
- - 'blueprints/planned/*/_overview.md'
89
- - 'blueprints/parked/*/_overview.md'
90
- - 'blueprints/in-progress/*/_overview.md'
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 _overview.md for high-level summary. Detailed phases go in phase-N-name.md files.'
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:
@@ -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
- Mutation guidance:
18
+ Guidance:
19
19
 
20
- - V1 structured authoring is limited to `wp_blueprint_put + wp_blueprint_transition`.
21
- - Use `request_id` on `wp_blueprint_create`, `wp_blueprint_put`,
22
- `wp_blueprint_task_advance`, and `wp_blueprint_task_verify` when the caller
23
- may retry the same request.
24
- - Prefer passing `project_id` from `wp_blueprint_projects` whenever the current
25
- working directory can see more than one blueprint-bearing repo.
26
- - Carry `head_at_ingest` from `wp_blueprint_list`, `wp_blueprint_get`, or
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 `_overview.md` on disk has a corresponding DB row.
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 `_overview.md` on disk has a corresponding DB row.
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 { glob } from 'glob';
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
- // Blueprints follow blueprints/<status>/<slug>/_overview.md convention
80
- const overviewFiles = await glob('blueprints/**/_overview.md', {
81
- cwd,
82
- absolute: false,
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,3 @@
1
+ import type { RepoAuditResult } from './repo-guardrails.js';
2
+ export declare function auditCloudflareDeployContract(root: string): Promise<RepoAuditResult>;
3
+ //# sourceMappingURL=cloudflare-deploy-contract.d.ts.map
@@ -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,3 @@
1
+ import type { RepoAuditResult } from './repo-guardrails.js';
2
+ export declare function auditNoLegacyCliBin(rootDirectory?: string): RepoAuditResult;
3
+ //# sourceMappingURL=no-legacy-cli-bin.d.ts.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 overviewPath = join(statusRoot, entry.name, '_overview.md');
215
- checked += 1;
216
- if (!existsSync(overviewPath)) {
217
- violations.push({
218
- file: relativePath(root, overviewPath),
219
- message: 'Missing _overview.md',
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(overviewPath, 'utf8');
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, overviewPath),
228
- message: 'Blueprint overview must use type: blueprint or parent-roadmap',
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, overviewPath),
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, overviewPath),
265
+ file: relativePath(root, canonicalPath),
239
266
  frontmatter,
240
267
  status,
241
268
  }));