@webpresso/agent-kit 3.1.28 → 3.1.30

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 (98) hide show
  1. package/catalog/AGENTS.md.tpl +4 -4
  2. package/catalog/agent/rules/ci-cost-local-first.md +139 -0
  3. package/catalog/agent/rules/ci-test-perf.md +5 -1
  4. package/catalog/agent/rules/cmd-execution.md +9 -0
  5. package/catalog/agent/rules/pre-implementation.md +2 -1
  6. package/catalog/agent/rules/test-scan-perf.md +80 -0
  7. package/catalog/agent/rules/workflow-skills-routing.md +4 -2
  8. package/catalog/agent/skills/fix/SKILL.md +3 -0
  9. package/catalog/agent/skills/verify/SKILL.md +18 -0
  10. package/dist/esm/audit/blueprint-pr-coverage.js +68 -7
  11. package/dist/esm/audit/registry.d.ts +4 -4
  12. package/dist/esm/audit/registry.js +3 -0
  13. package/dist/esm/audit/roadmap-links.js +6 -0
  14. package/dist/esm/audit/test-scan-perf.d.ts +18 -0
  15. package/dist/esm/audit/test-scan-perf.js +275 -0
  16. package/dist/esm/blueprint/lifecycle/engine.d.ts +9 -0
  17. package/dist/esm/blueprint/lifecycle/engine.js +53 -6
  18. package/dist/esm/blueprint/lifecycle/review-provenance.d.ts +6 -0
  19. package/dist/esm/blueprint/lifecycle/review-provenance.js +93 -12
  20. package/dist/esm/blueprint/markdown/helpers.js +6 -2
  21. package/dist/esm/ci/act-replay.js +110 -7
  22. package/dist/esm/ci/act-runner.d.ts +4 -0
  23. package/dist/esm/ci/act-runner.js +85 -26
  24. package/dist/esm/ci/act-worktree-git.d.ts +11 -0
  25. package/dist/esm/ci/act-worktree-git.js +219 -0
  26. package/dist/esm/cli/commands/audit.js +3 -0
  27. package/dist/esm/cli/commands/blueprint/mutations.js +35 -18
  28. package/dist/esm/cli/commands/blueprint/router.js +27 -2
  29. package/dist/esm/cli/commands/ci.d.ts +8 -1
  30. package/dist/esm/cli/commands/ci.js +80 -21
  31. package/dist/esm/cli/commands/dash/keymap.js +1 -0
  32. package/dist/esm/cli/commands/dash/launch-preferences.d.ts +15 -0
  33. package/dist/esm/cli/commands/dash/launch-preferences.js +46 -17
  34. package/dist/esm/cli/commands/dash/plan-once-label.d.ts +7 -0
  35. package/dist/esm/cli/commands/dash/plan-once-label.js +5 -0
  36. package/dist/esm/cli/commands/dash/plan-once-membership.d.ts +67 -0
  37. package/dist/esm/cli/commands/dash/plan-once-membership.js +151 -0
  38. package/dist/esm/cli/commands/dash/plan-once-merge.d.ts +143 -0
  39. package/dist/esm/cli/commands/dash/plan-once-merge.js +238 -0
  40. package/dist/esm/cli/commands/dash/plan-once.d.ts +22 -2
  41. package/dist/esm/cli/commands/dash/plan-once.js +41 -4
  42. package/dist/esm/cli/commands/dash/tui/attention-hud.d.ts +9 -0
  43. package/dist/esm/cli/commands/dash/tui/attention-hud.js +9 -0
  44. package/dist/esm/cli/commands/dash/tui/chat-bridge.d.ts +41 -0
  45. package/dist/esm/cli/commands/dash/tui/chat-bridge.js +73 -0
  46. package/dist/esm/cli/commands/dash/tui/chat-mode.d.ts +47 -0
  47. package/dist/esm/cli/commands/dash/tui/chat-mode.js +98 -0
  48. package/dist/esm/cli/commands/dash/tui/frame.d.ts +3 -0
  49. package/dist/esm/cli/commands/dash/tui/frame.js +3 -1
  50. package/dist/esm/cli/commands/dash/tui/palette.d.ts +1 -1
  51. package/dist/esm/cli/commands/dash/tui/palette.js +14 -0
  52. package/dist/esm/cli/commands/dash/tui/runtime.js +239 -4
  53. package/dist/esm/cli/commands/dash/tui/state.d.ts +41 -0
  54. package/dist/esm/cli/commands/dash/tui/state.js +257 -4
  55. package/dist/esm/cli/commands/format.js +0 -2
  56. package/dist/esm/cli/commands/init/config.d.ts +0 -1
  57. package/dist/esm/cli/commands/init/config.js +1 -4
  58. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/emitters/claude.d.ts +5 -6
  59. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/emitters/claude.js +6 -13
  60. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/ir.d.ts +0 -2
  61. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/ir.js +0 -2
  62. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/merge.d.ts +6 -3
  63. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/merge.js +39 -26
  64. package/dist/esm/cli/commands/init/scaffolders/codex-mcp/index.js +61 -23
  65. package/dist/esm/cli/commands/logs.d.ts +15 -0
  66. package/dist/esm/cli/commands/logs.js +192 -3
  67. package/dist/esm/cli/commands/quality-log-store.d.ts +1 -0
  68. package/dist/esm/cli/commands/quality-log-store.js +187 -51
  69. package/dist/esm/cli/commands/review.js +5 -8
  70. package/dist/esm/cli/commands/secrets.d.ts +1 -0
  71. package/dist/esm/cli/commands/secrets.js +4 -1
  72. package/dist/esm/cli/direct-provider-launch.js +25 -1
  73. package/dist/esm/hooks/doctor.d.ts +15 -0
  74. package/dist/esm/hooks/doctor.js +56 -0
  75. package/dist/esm/hooks/pretool-guard/validators/forbidden-commands.js +4 -4
  76. package/dist/esm/hooks/pretool-guard/validators/mcp-redirect.d.ts +0 -1
  77. package/dist/esm/hooks/pretool-guard/validators/mcp-redirect.js +8 -5
  78. package/dist/esm/hooks/pretool-guard/validators/worktree-discipline.d.ts +9 -7
  79. package/dist/esm/hooks/pretool-guard/validators/worktree-discipline.js +126 -14
  80. package/dist/esm/mcp/tools/_shared/audit-kinds.d.ts +1 -1
  81. package/dist/esm/mcp/tools/audit.d.ts +2 -1
  82. package/dist/esm/mcp/tools/audit.js +10 -0
  83. package/dist/esm/mcp/tools/audits.d.ts +1 -0
  84. package/dist/esm/mcp/tools/ci-act.js +9 -11
  85. package/dist/esm/paths/state-root.js +3 -0
  86. package/dist/esm/review/authority.js +17 -2
  87. package/dist/esm/review/availability.js +18 -5
  88. package/dist/esm/review/subject.d.ts +15 -0
  89. package/dist/esm/review/subject.js +105 -10
  90. package/dist/esm/runtime/executor.d.ts +1 -0
  91. package/dist/esm/runtime/executor.js +2 -1
  92. package/dist/esm/secret-gate/runner.d.ts +1 -0
  93. package/dist/esm/secret-gate/runner.js +4 -1
  94. package/dist/esm/worktrees/current.d.ts +1 -0
  95. package/dist/esm/worktrees/current.js +1 -0
  96. package/dist/esm/worktrees/project-agent-surfaces.d.ts +1 -0
  97. package/dist/esm/worktrees/project-agent-surfaces.js +2 -1
  98. package/package.json +14 -14
@@ -63,10 +63,10 @@ asynchronously.
63
63
 
64
64
  ## Verify
65
65
 
66
- Before claiming completion, run the narrowest checks that prove the change:
67
- MCP/`wp` quality tools, typecheck, lint/format, affected tests, policy checks,
68
- docs/blueprint validation when those changed, and `wp sync --check` after
69
- template/catalog changes. On failure, fix root cause or record the blocker.
66
+ Before claiming completion, run the narrowest MCP/`wp` checks that prove the
67
+ change: typecheck, lint/format, affected tests, policy, docs/blueprint, and
68
+ `wp sync --check` after template/catalog changes. Fix root cause or record the
69
+ blocker. Before pushing, run `wp ci-preflight` (see `ci-cost-local-first`).
70
70
 
71
71
  ## Communicate
72
72
 
@@ -0,0 +1,139 @@
1
+ ---
2
+ type: rule
3
+ slug: ci-cost-local-first
4
+ title: CI cost and local-first gates
5
+ status: active
6
+ scope: repo
7
+ applies_to: [agents]
8
+ related:
9
+ - cmd-execution
10
+ - ci-test-perf
11
+ - pre-implementation
12
+ - hook-hot-path
13
+ - no-timeout-as-fix
14
+ created: "2026-07-21"
15
+ last_reviewed: "2026-07-21"
16
+ paths:
17
+ - ".github/workflows/**"
18
+ - "catalog/agent/**"
19
+ - "AGENTS.md"
20
+ - ".husky/**"
21
+ ---
22
+
23
+ # CI cost and local-first gates
24
+
25
+ Agents waste money when they treat remote CI as the primary debugger.
26
+ **Catch failures locally first.** Remote CI is confirmation, not exploration.
27
+
28
+ Monthly spend on private repos (Ubicloud + GitHub-hosted minutes + artifact
29
+ storage) is a product constraint. Prefer cheap local loops over burning full
30
+ PR matrices to discover format/lint/type/test/governance failures.
31
+
32
+ ## Iron law
33
+
34
+ 1. **Never open or update a PR you already know will fail** a locally-runnable
35
+ gate (format, guardrails, typecheck, lint, affected tests, PR body contract,
36
+ changeset/blueprint trailers when applicable).
37
+ 2. **Never use `git push --no-verify` / `HUSKY=0` / skip hooks** to “save
38
+ time.” That only moves the failure to paid CI and trains a red-loop habit.
39
+ 3. **Never re-run a failed CI job “to see if it flaked”** until you have a
40
+ local reproduction or a concrete flake hypothesis with evidence.
41
+ 4. **Never add GitHub-hosted runners** (`ubuntu-latest`, `macos-*`,
42
+ `windows-*`) on private-repo workflows except the **Release / npm OIDC
43
+ publish** path (and release-native targets that truly need that OS). Default
44
+ is **Ubicloud** (`ubicloud-standard-2` / `-4` only when justified).
45
+
46
+ ## Local gate stack (must pass before push)
47
+
48
+ | When | Command / check |
49
+ | -------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
50
+ | Every commit | Managed pre-commit (format + staged guardrails) |
51
+ | Every push | `wp ci-preflight` before pushing; in agent-kit itself this is also enforced by `.husky/pre-push` |
52
+ | Before PR open / body edit | PR template fields filled; trailers exact (`Blueprint-exempt`, `Changeset-exempt`, `Review-skip` as **start-of-line** when used) |
53
+ | Before claim merge-ready | `/verify <target>` then `/verify <target> --merge-ready` when landing |
54
+
55
+ `wp ci-preflight` is fail-fast and **branch-scoped**:
56
+
57
+ 1. `wp format --affected --branch --check`
58
+ 2. `wp audit guardrails --affected --branch`
59
+ 3. `wp typecheck --affected --branch`
60
+ 4. `wp lint --affected --branch`
61
+ 5. `wp test --affected --branch`
62
+
63
+ If preflight fails: **fix locally**, do not push. Reproduce with the printed
64
+ `wp …` line.
65
+
66
+ ### Bootstrap before preflight
67
+
68
+ Missing `node_modules` in a worktree → run `wp install` (or the repo’s setup),
69
+ then preflight. Use `vp install` only when `wp` has no equivalent. Do not push a
70
+ bare worktree “to let CI install.”
71
+
72
+ ## Cost model (private repos)
73
+
74
+ | Resource | Cost driver | Agent duty |
75
+ | ------------------------------ | ------------------------------------------------- | ---------------------------------------------- |
76
+ | Ubicloud minutes | Every PR job matrix (lint, tests, smokes, shards) | Local preflight; avoid drive-by full re-runs |
77
+ | GitHub-hosted `ubuntu-latest` | Billable Actions minutes | Only release/OIDC publish unless proven needed |
78
+ | GitHub `macos-*` / `windows-*` | ~10× Linux minutes | Release natives only; never default PR CI |
79
+ | Actions artifacts / caches | Storage quota; can block Release uploads | Prefer short retention; do not spam artifacts |
80
+ | Failed CI loops | Full matrix × attempts | Fix from logs locally before `gh run rerun` |
81
+
82
+ Related: `ci-test-perf.md` (keep coverage; speed via workers/cache/shards — not
83
+ by skipping tests). Prefer **fewer/cheaper runners + sharding** over raising
84
+ timeouts (`no-timeout-as-fix.md`).
85
+
86
+ ## Workflow authoring (when you touch CI)
87
+
88
+ - Private non-release jobs: `runs-on: ubicloud-standard-2` by default.
89
+ - Use `ubicloud-standard-4` only when wall-time evidence shows standard-2 is
90
+ insufficient **with** full coverage and workers (document why).
91
+ - Release publish job may use `ubuntu-latest` for npm trusted publishing /
92
+ provenance. Session-memory / platform native matrices that require Darwin or
93
+ Windows may use GH-hosted OS runners **only on the Release (publish=true)
94
+ path**, never on ordinary PR CI.
95
+ - Path filters, `concurrency` cancel-in-progress, and change-scope skips are
96
+ preferred over always-on heavy jobs.
97
+ - Do not add nightly/macOS “perf” jobs that run on every PR; keep them opt-in
98
+ (`workflow_dispatch`, label, or schedule) and prefer Ubicloud for any check
99
+ that is not Apple-signing/display-bound.
100
+
101
+ ## PR governance without burning CI
102
+
103
+ These fail **before** expensive tests if you get them wrong — fix **before**
104
+ push/open:
105
+
106
+ - PR description AI/model disclosure + `Session id` (or `Session-skip`)
107
+ - `Review-skip: SKIP <reason>` must match `^Review-skip:` at **line start**
108
+ (not nested under a bullet)
109
+ - `Blueprint-exempt:` / `Changeset-exempt:` in **commit message** and/or body
110
+ when required (blueprint-pr-coverage reads **git trailers**, not only the PR body)
111
+ - Version Packages / bot CI: do not invent admin-merge; use the runbook
112
+ (`docs/runbooks/version-packages-bot-ci.md` when present)
113
+
114
+ ## Forbidden agent habits
115
+
116
+ - Push → watch red CI → tweak → push again (the “CI as unit test” loop)
117
+ - Broaden CI timeouts / disable flakes instead of root-cause fix
118
+ - Schedule or matrix-expand macOS/Windows for convenience
119
+ - Upload large artifacts every PR “for debugging”
120
+ - Claim “CI is green” from a partial check while WP check / required jobs pending
121
+ - Force-push past hooks to land red work for “later fix”
122
+
123
+ ## Good loop
124
+
125
+ ```text
126
+ edit → scoped lint/type/test → commit (hooks) → wp ci-preflight → push
127
+ → open PR with complete body → watch CI once → fix root cause locally if red
128
+ ```
129
+
130
+ When CI fails: download/read the failed job log, reproduce with the matching
131
+ local `wp` command, fix, preflight, push **one** corrective commit.
132
+
133
+ ## Related
134
+
135
+ - `cmd-execution.md` — bookend QA; scoped commands in the middle
136
+ - `ci-test-perf.md` — parallelism and install cache
137
+ - `pre-implementation.md` — blueprint gates that only fail in CI if left open
138
+ - `hook-hot-path.md` — pre-push stays local and non-network
139
+ - CLI: `wp ci-preflight` (pre-push owner)
@@ -56,9 +56,13 @@ Mutation/Stryker jobs are auto-exempt when the step/job name or nearby lines men
56
56
  When a single job still exceeds budget **with full coverage**:
57
57
 
58
58
  1. Prefer **Vitest sharding** (`vitest run --shard=i/n` + blob reporter merge) over raising isolation-off globally.
59
- 2. Prefer a **larger runner** (`ubicloud-standard-4`, etc.) + more `VITEST_MAX_WORKERS` over dropping suites.
59
+ 2. Prefer a **larger runner** (`ubicloud-standard-4`, etc.) + more `VITEST_MAX_WORKERS` over dropping suites — only after `ubicloud-standard-2` is proven insufficient. Do not jump to GitHub-hosted macOS/Windows for ordinary test jobs.
60
60
  3. Cache **native/build artifacts** when warm steps rebuild every PR (producer-specific).
61
61
 
62
+ Private-repo default is **Ubicloud** for non-release jobs. GitHub-hosted
63
+ runners are reserved for Release/OIDC publish (and release-native OS builds).
64
+ See `ci-cost-local-first.md`.
65
+
62
66
  ## Related
63
67
 
64
68
  - Vitest performance: https://vitest.dev/guide/improving-performance
@@ -14,6 +14,15 @@ paths:
14
14
 
15
15
  # Command Execution Rules
16
16
 
17
+ ## Local-first before remote CI
18
+
19
+ Paid CI (Ubicloud + GitHub-hosted) is for confirmation. Use scoped commands
20
+ while iterating, then **`wp ci-preflight` before every push**. In agent-kit
21
+ itself, `.husky/pre-push` enforces that gate; consumer templates may leave
22
+ pre-push checks user-owned. Never `git push --no-verify` to skip a configured
23
+ local gate. See `ci-cost-local-first.md` for runner policy and anti-red-loop
24
+ habits.
25
+
17
26
  ## BOOKEND Rule: Full QA Runs Exactly Twice
18
27
 
19
28
  The full QA pipeline (e.g. `wp qa`, `bun run qa`) is a
@@ -264,7 +264,8 @@ Doing it post-merge is also impossible without a second PR: you cannot push the
264
264
  **Local pre-push check (catches the CI failure before it costs minutes):** the
265
265
  local lifecycle audit gives a false pass here because the owner worktree resolves
266
266
  on your machine. So do not rely on it alone — before opening or updating the PR,
267
- confirm no changed blueprint is still under `blueprints/in-progress/`:
267
+ run `wp ci-preflight` for ordinary quality gates and confirm no changed blueprint
268
+ is still under `blueprints/in-progress/`:
268
269
 
269
270
  ```bash
270
271
  git diff --name-only origin/main...HEAD | grep -q '^blueprints/in-progress/' \
@@ -0,0 +1,80 @@
1
+ ---
2
+ type: rule
3
+ slug: test-scan-perf
4
+ title: Test scan performance (no full-repo FS walks)
5
+ status: active
6
+ scope: repo
7
+ applies_to: [agents]
8
+ related:
9
+ - testing-philosophy
10
+ - ci-test-perf
11
+ created: "2026-07-20"
12
+ last_reviewed: "2026-07-20"
13
+ paths:
14
+ - "**/*.{test,spec}.{ts,tsx,js,jsx}"
15
+ - "src/audit/test-scan-perf.ts"
16
+ ---
17
+
18
+ # Test scan performance
19
+
20
+ Unit and contract tests must not walk the whole repository with
21
+ `readdirSync` / `statSync` / `readFileSync` and paper over the cost with a
22
+ 30s+ per-test timeout.
23
+
24
+ ## Forbidden pattern
25
+
26
+ ```ts
27
+ const repoRoot = findRepoRoot();
28
+ const walk = (dir: string) => {
29
+ for (const entry of readdirSync(dir)) {
30
+ const full = join(dir, entry);
31
+ if (statSync(full).isDirectory()) {
32
+ walk(full);
33
+ continue;
34
+ }
35
+ const text = readFileSync(full, "utf8");
36
+ // ...
37
+ }
38
+ };
39
+ walk(repoRoot);
40
+
41
+ it("scan", () => {
42
+ /* ... */
43
+ }, 30_000); // band-aid
44
+ ```
45
+
46
+ ## Preferred
47
+
48
+ ```ts
49
+ // Tracked files only — seconds, not tens of seconds.
50
+ const stdout = execFileSync(
51
+ "git",
52
+ ["-C", root, "grep", "-l", "-I", "-E", "RETIRED_SPECIFIER_A|RETIRED_SPECIFIER_B"],
53
+ { encoding: "utf8" },
54
+ );
55
+ ```
56
+
57
+ Or walk a **bounded** fixture / package tree (not `findRepoRoot()` / `process.cwd()`).
58
+
59
+ ## Guardrail
60
+
61
+ | Surface | Contract |
62
+ | ---------- | ---------------------------------- |
63
+ | Audit | `wp audit test-scan-perf` |
64
+ | Guardrails | included in `wp audit guardrails` |
65
+ | Scope | affected-safe (changed test files) |
66
+
67
+ ### Opt-out
68
+
69
+ ```ts
70
+ // test-scan-perf-allow — one-off inventory with justification
71
+ walk(repoRoot);
72
+
73
+ // or once per file:
74
+ // test-scan-perf-allow-file — full inventory intentionally walks the tree
75
+ ```
76
+
77
+ ## Related
78
+
79
+ - Implementation: `src/audit/test-scan-perf.ts`
80
+ - CI workers/cache: `ci-test-perf` (workflow-level, separate concern)
@@ -74,8 +74,10 @@ Map routing intent like this:
74
74
  - debugging and regressions → `investigate` then `fix`
75
75
  - bounded cleanup after behavior is green → `ai-deslop`
76
76
  - multi-task / parallel execute → `ultragoal` + `/pll` (ready-queue, worktrees)
77
- - local completeness → `/verify`; merge-ready outside voice `/verify --merge-ready`
78
- (exactly one sequential outside reviewer unless user sets N)
77
+ - local completeness → `/verify` then `wp ci-preflight` before push; merge-ready
78
+ outside voice → `/verify --merge-ready` (exactly one sequential outside
79
+ reviewer unless user sets N). Do not use remote CI as the first debugger
80
+ (`ci-cost-local-first`).
79
81
 
80
82
  Use the full `wp_*` MCP surface for tests, lint, typecheck, audits, session memory,
81
83
  ultragoal, worktrees, PRs, and quality gates — not shell reimplementations. Use
@@ -122,12 +122,15 @@ Run the narrowest checks that prove the repaired behavior on the real repo surfa
122
122
  - `wp_test` / targeted tests for the repaired path
123
123
  - `wp_lint` / `wp_typecheck` for changed surfaces
124
124
  - `wp_qa` only when the blast radius needs the bookend
125
+ - Before push: `wp ci-preflight` (local pre-push owner). Do not use remote CI
126
+ re-runs as the primary fix loop (`ci-cost-local-first`).
125
127
 
126
128
  Rules:
127
129
 
128
130
  - Prefer MCP `wp_*` over raw host Bash.
129
131
  - Reuse fresh logs / session-memory indexed output; do not re-run long commands just to re-read.
130
132
  - Read the exit code and summary before making a claim.
133
+ - Never `git push --no-verify` to land an unproven fix.
131
134
 
132
135
  Escalate to `/verify <target>` (local) or `/verify <target> --merge-ready` when any of these are true:
133
136
 
@@ -109,6 +109,9 @@ workflow:
109
109
  1. Identify target type (file / package / blueprint slug / all).
110
110
  2. Map each claim you plan to make to the exact command or log that proves it.
111
111
  3. If the target is a blueprint slug, run the repo's blueprint show/audit surface and record the acceptance boxes that still need proof.
112
+ 4. **CI cost / local-first:** treat remote CI as confirmation, not the first
113
+ debugger. Load `ci-cost-local-first` expectations: no hook skips, no
114
+ blind `gh run rerun`, Ubicloud for private non-release jobs.
112
115
 
113
116
  ## Phase 1 — Governance gates
114
117
 
@@ -130,12 +133,27 @@ Run the narrowest checks that prove the touched behavior:
130
133
  - `wp_test` / targeted tests
131
134
  - `wp_qa`, `wp_e2e`, build / package checks only when the change requires them
132
135
 
136
+ **Before push or PR open/update**, run the pre-push owner end-to-end:
137
+
138
+ ```bash
139
+ wp ci-preflight
140
+ ```
141
+
142
+ That is fail-fast branch-scoped format → guardrails → typecheck → lint →
143
+ test. Agent-kit itself enforces it from `.husky/pre-push`; consumer repos may
144
+ keep pre-push checks user-owned, so run the command explicitly before pushing.
145
+ Do **not** claim push-ready if preflight failed. Do **not** bypass hooks to ship
146
+ red work.
147
+
133
148
  Rules:
134
149
 
135
150
  - Prefer MCP `wp_*` over raw underlying tools.
136
151
  - Reuse fresh logs / session-memory indexed output if the runner auto-saves them.
137
152
  - Never claim broader correctness than the commands actually proved.
138
153
  - If the repo documents a "full QA" bookend, reserve `wp_qa` / `--full` for the final broad pass rather than every iteration.
154
+ - If CI is already red: read the failed job log, reproduce with the matching
155
+ local `wp` command, fix, re-run `wp ci-preflight`, then push **one**
156
+ corrective commit — do not burn another full matrix “to check.”
139
157
 
140
158
  ## Phase 3 — Cross-surface impact scan
141
159
 
@@ -1,5 +1,6 @@
1
1
  import { spawnSync } from "node:child_process";
2
- import { relative } from "node:path";
2
+ import { existsSync, readFileSync } from "node:fs";
3
+ import { join, relative } from "node:path";
3
4
  import { resolveBlueprintRoot } from "#utils/blueprint-root.js";
4
5
  const TITLE = "Blueprint PR coverage";
5
6
  const BLUEPRINT_EXEMPT_PATTERN = /^Blueprint-exempt:\s*(\S.*)$/im;
@@ -39,14 +40,33 @@ export function auditBlueprintPrCoverage(rootDirectory = process.cwd(), options
39
40
  const configuredRoot = normalizeRoot(normalizePath(relative(rootDirectory, resolveBlueprintRoot(rootDirectory))));
40
41
  const blueprintRoots = [...new Set(["blueprints", configuredRoot].filter(Boolean))];
41
42
  const finalEntries = entries.filter((entry) => entry.status !== "D");
42
- const activePaths = finalEntries.filter((entry) => {
43
+ const activeEntries = finalEntries.flatMap((entry) => {
43
44
  const location = parseBlueprintLocation(entry.path, blueprintRoots);
44
- return location !== null && ACTIVE_LIFECYCLE_DIRECTORIES.has(location.lifecycle);
45
+ if (!location || !ACTIVE_LIFECYCLE_DIRECTORIES.has(location.lifecycle))
46
+ return [];
47
+ return [{ entry, location }];
45
48
  });
46
- if (activePaths.length > 0) {
47
- return fail(`non-doc landing PR leaves changed active blueprint content at ${activePaths
48
- .map((entry) => entry.path)
49
- .join(", ")}; move the owner blueprint to completed before merge`, changedFiles.length);
49
+ const ownerActive = activeEntries.filter(({ location }) => location.slug === ownerSlug);
50
+ if (ownerActive.length > 0) {
51
+ return fail(`non-doc landing PR leaves owner blueprint "${ownerSlug}" in active lifecycle at ${ownerActive
52
+ .map(({ entry }) => entry.path)
53
+ .join(", ")}; move completed/${ownerSlug}/_overview.md (or .md) into the PR and drop active-lifecycle owner paths`, changedFiles.length);
54
+ }
55
+ // Parent-roadmap program trackers may co-edit on a child land PR while still
56
+ // active (draft/planned). Executable blueprints (type: blueprint) may not.
57
+ const nonOwnerActive = activeEntries.filter(({ location }) => location.slug !== ownerSlug);
58
+ const programTrackers = [];
59
+ const blockingNonOwner = [];
60
+ for (const { entry, location } of nonOwnerActive) {
61
+ if (isParentRoadmapDocument(rootDirectory, entry.path, location)) {
62
+ programTrackers.push(entry.path);
63
+ }
64
+ else {
65
+ blockingNonOwner.push(entry.path);
66
+ }
67
+ }
68
+ if (blockingNonOwner.length > 0) {
69
+ return fail(`non-doc landing PR changes active non-owner blueprint content at ${blockingNonOwner.join(", ")}; remove those paths from the PR or complete those blueprints first (owner ${ownerSlug} is separate)`, changedFiles.length);
50
70
  }
51
71
  const matchingCompletedOwner = finalEntries.some((entry) => {
52
72
  const location = parseBlueprintLocation(entry.path, blueprintRoots);
@@ -55,6 +75,9 @@ export function auditBlueprintPrCoverage(rootDirectory = process.cwd(), options
55
75
  if (!matchingCompletedOwner) {
56
76
  return fail(`non-doc PR on ${headRef} must change its completed owner blueprint at completed/${ownerSlug}/_overview.md (or completed/${ownerSlug}.md)`, changedFiles.length);
57
77
  }
78
+ if (programTrackers.length > 0) {
79
+ return passWithWarning(`[warn] co-edited parent-roadmap program tracker(s) on non-doc land: ${programTrackers.join(", ")}`, changedFiles.length);
80
+ }
58
81
  return pass(changedFiles.length);
59
82
  }
60
83
  function pass(checked) {
@@ -134,6 +157,44 @@ function parseBlueprintLocation(filePath, roots) {
134
157
  }
135
158
  return null;
136
159
  }
160
+ /**
161
+ * True when the changed path belongs to a `type: parent-roadmap` document.
162
+ * Nested non-overview files under a parent-roadmap folder inherit the overview
163
+ * type so review artifacts co-travel. Missing paths (unit-test fakes) return
164
+ * false → fail closed as executable blueprints.
165
+ */
166
+ function isParentRoadmapDocument(rootDirectory, filePath, location) {
167
+ const normalized = normalizePath(filePath);
168
+ const overviewCandidates = [
169
+ // Prefer the changed overview when it is the document itself.
170
+ ...(location.ownerDocument ? [normalized] : []),
171
+ // folder overview: …/<lifecycle>/<slug>/_overview.md
172
+ normalized.replace(new RegExp(`/(draft|planned|in-progress|parked|completed|archived)/${escapeRegex(location.slug)}(?:/.*)?$`, "u"), `/$1/${location.slug}/_overview.md`),
173
+ // flat overview: …/<lifecycle>/<slug>.md
174
+ normalized.replace(new RegExp(`/(draft|planned|in-progress|parked|completed|archived)/${escapeRegex(location.slug)}(?:/.*|\\.md)?$`, "u"), `/$1/${location.slug}.md`),
175
+ ];
176
+ const seen = new Set();
177
+ for (const candidate of overviewCandidates) {
178
+ if (seen.has(candidate))
179
+ continue;
180
+ seen.add(candidate);
181
+ const abs = join(rootDirectory, candidate);
182
+ if (!existsSync(abs))
183
+ continue;
184
+ try {
185
+ const head = readFileSync(abs, "utf8").slice(0, 4_096);
186
+ if (/^type:\s*['"]?parent-roadmap['"]?\s*$/mu.test(head))
187
+ return true;
188
+ }
189
+ catch {
190
+ // unreadable → fail closed
191
+ }
192
+ }
193
+ return false;
194
+ }
195
+ function escapeRegex(value) {
196
+ return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
197
+ }
137
198
  function isDependabotDependencyOnlyUpdate(cwd, options, changedFiles) {
138
199
  if (!changedFiles.every(isDependabotDependencyFile))
139
200
  return false;
@@ -14,11 +14,11 @@ export interface AuditKindDescriptor {
14
14
  }
15
15
  export declare const SCRIPT_AUDIT_KIND_IDS: readonly ["test-smells", "tph-e2e"];
16
16
  export declare const SPECIAL_AUDIT_KIND_IDS: readonly ["bundle-budget", "commit-message", "blueprint-pr-coverage", "mutation", "guardrails", "quality"];
17
- export declare const REPO_AUDIT_KIND_IDS: readonly ["catalog-drift", "package-surface", "command-surface", "reference-parity-matrix", "blueprint-readme-drift", "blueprint-lifecycle", "blueprint-trust", "roadmap-links", "docs-frontmatter", "agents", "vision", "tech-debt", "supported-agent-clis", "no-relative-parent-imports", "no-link-protocol", "no-relative-package-scripts", "test-isolation", "bucket-boundary", "skill-sizes", "broken-refs", "memory-rotation", "gitignore-agent-surfaces", "memory-unified", "compile-drift", "no-legacy-cli-bin", "architecture-drift", "cloudflare-deploy-contract", "toolchain-isolation", "absolute-path-policy", "no-first-party-mjs", "outside-review-runtime-ownership", "no-math-random", "agent-cost", "blueprint-db-consistency", "tech-debt-cadence", "cross-repo-correlation", "ai-contracts", "atomic-state-writes", "hook-surface", "hook-vendor-drift", "session-memory-hardcut", "open-source-licenses", "secrets-policy", "no-dev-vars", "secret-provider-quarantine", "github-actions-secrets", "security-quality-regressions", "secrets-config", "consumer-agent-kit-dependency", "ci-test-perf", "worktree-main-ownership", "sast", "harness-surfaces", "weakness-mining", "harness-overlay-evidence", "legacy-workflow-identity", "rules", "skills"];
17
+ export declare const REPO_AUDIT_KIND_IDS: readonly ["catalog-drift", "package-surface", "command-surface", "reference-parity-matrix", "blueprint-readme-drift", "blueprint-lifecycle", "blueprint-trust", "roadmap-links", "docs-frontmatter", "agents", "vision", "tech-debt", "supported-agent-clis", "no-relative-parent-imports", "no-link-protocol", "no-relative-package-scripts", "test-isolation", "bucket-boundary", "skill-sizes", "broken-refs", "memory-rotation", "gitignore-agent-surfaces", "memory-unified", "compile-drift", "no-legacy-cli-bin", "architecture-drift", "cloudflare-deploy-contract", "toolchain-isolation", "absolute-path-policy", "no-first-party-mjs", "outside-review-runtime-ownership", "no-math-random", "agent-cost", "blueprint-db-consistency", "tech-debt-cadence", "cross-repo-correlation", "ai-contracts", "atomic-state-writes", "hook-surface", "hook-vendor-drift", "session-memory-hardcut", "open-source-licenses", "secrets-policy", "no-dev-vars", "secret-provider-quarantine", "github-actions-secrets", "security-quality-regressions", "secrets-config", "consumer-agent-kit-dependency", "ci-test-perf", "test-scan-perf", "worktree-main-ownership", "sast", "harness-surfaces", "weakness-mining", "harness-overlay-evidence", "legacy-workflow-identity", "rules", "skills"];
18
18
  export declare const AUDIT_KIND_DESCRIPTORS: readonly AuditKindDescriptor[];
19
- export declare const AUDIT_KIND_IDS: readonly ["test-smells", "tph-e2e", ...("absolute-path-policy" | "agent-cost" | "agents" | "ai-contracts" | "architecture-drift" | "atomic-state-writes" | "blueprint-db-consistency" | "blueprint-lifecycle" | "blueprint-pr-coverage" | "blueprint-readme-drift" | "blueprint-trust" | "broken-refs" | "bucket-boundary" | "bundle-budget" | "catalog-drift" | "ci-test-perf" | "cloudflare-deploy-contract" | "command-surface" | "commit-message" | "compile-drift" | "consumer-agent-kit-dependency" | "cross-repo-correlation" | "docs-frontmatter" | "github-actions-secrets" | "gitignore-agent-surfaces" | "guardrails" | "harness-overlay-evidence" | "harness-surfaces" | "hook-surface" | "hook-vendor-drift" | "legacy-workflow-identity" | "memory-rotation" | "memory-unified" | "mutation" | "no-dev-vars" | "no-first-party-mjs" | "no-legacy-cli-bin" | "no-link-protocol" | "no-math-random" | "no-relative-package-scripts" | "no-relative-parent-imports" | "open-source-licenses" | "outside-review-runtime-ownership" | "package-surface" | "quality" | "reference-parity-matrix" | "roadmap-links" | "rules" | "sast" | "secret-provider-quarantine" | "secrets-config" | "secrets-policy" | "security-quality-regressions" | "session-memory-hardcut" | "skill-sizes" | "skills" | "supported-agent-clis" | "tech-debt" | "tech-debt-cadence" | "test-isolation" | "toolchain-isolation" | "vision" | "weakness-mining" | "worktree-main-ownership")[]];
20
- export declare const MCP_AUDIT_KIND_IDS: readonly ["test-smells", "tph-e2e", "agents", "catalog-drift", "package-surface", "reference-parity-matrix", "docs-frontmatter", "blueprint-readme-drift", "blueprint-pr-coverage", "blueprint-lifecycle", "blueprint-trust", "architecture-drift", "cloudflare-deploy-contract", "absolute-path-policy", "no-first-party-mjs", "no-math-random", "roadmap-links", "bundle-budget", "commit-message", "tech-debt", "supported-agent-clis", "hook-surface", "harness-surfaces", "weakness-mining", "harness-overlay-evidence", "ai-contracts", "atomic-state-writes", "no-relative-package-scripts", "toolchain-isolation", "open-source-licenses", "secrets-policy", "no-dev-vars", "github-actions-secrets", "secret-provider-quarantine", "security-quality-regressions", "secrets-config", "consumer-agent-kit-dependency", "ci-test-perf", "session-memory-hardcut", "worktree-main-ownership", "sast"];
21
- export declare const AFFECTED_SAFE_AUDIT_KIND_IDS: readonly ["blueprint-readme-drift", "blueprint-lifecycle", "bucket-boundary", "skill-sizes", "broken-refs", "no-dev-vars", "absolute-path-policy", "secret-provider-quarantine", "secrets-config", "docs-frontmatter", "no-relative-parent-imports", "no-link-protocol", "no-first-party-mjs", "no-math-random"];
19
+ export declare const AUDIT_KIND_IDS: readonly ["test-smells", "tph-e2e", ...("absolute-path-policy" | "agent-cost" | "agents" | "ai-contracts" | "architecture-drift" | "atomic-state-writes" | "blueprint-db-consistency" | "blueprint-lifecycle" | "blueprint-pr-coverage" | "blueprint-readme-drift" | "blueprint-trust" | "broken-refs" | "bucket-boundary" | "bundle-budget" | "catalog-drift" | "ci-test-perf" | "cloudflare-deploy-contract" | "command-surface" | "commit-message" | "compile-drift" | "consumer-agent-kit-dependency" | "cross-repo-correlation" | "docs-frontmatter" | "github-actions-secrets" | "gitignore-agent-surfaces" | "guardrails" | "harness-overlay-evidence" | "harness-surfaces" | "hook-surface" | "hook-vendor-drift" | "legacy-workflow-identity" | "memory-rotation" | "memory-unified" | "mutation" | "no-dev-vars" | "no-first-party-mjs" | "no-legacy-cli-bin" | "no-link-protocol" | "no-math-random" | "no-relative-package-scripts" | "no-relative-parent-imports" | "open-source-licenses" | "outside-review-runtime-ownership" | "package-surface" | "quality" | "reference-parity-matrix" | "roadmap-links" | "rules" | "sast" | "secret-provider-quarantine" | "secrets-config" | "secrets-policy" | "security-quality-regressions" | "session-memory-hardcut" | "skill-sizes" | "skills" | "supported-agent-clis" | "tech-debt" | "tech-debt-cadence" | "test-isolation" | "test-scan-perf" | "toolchain-isolation" | "vision" | "weakness-mining" | "worktree-main-ownership")[]];
20
+ export declare const MCP_AUDIT_KIND_IDS: readonly ["test-smells", "tph-e2e", "agents", "catalog-drift", "package-surface", "reference-parity-matrix", "docs-frontmatter", "blueprint-readme-drift", "blueprint-pr-coverage", "blueprint-lifecycle", "blueprint-trust", "architecture-drift", "cloudflare-deploy-contract", "absolute-path-policy", "no-first-party-mjs", "no-math-random", "roadmap-links", "bundle-budget", "commit-message", "tech-debt", "supported-agent-clis", "hook-surface", "harness-surfaces", "weakness-mining", "harness-overlay-evidence", "ai-contracts", "atomic-state-writes", "no-relative-package-scripts", "toolchain-isolation", "open-source-licenses", "secrets-policy", "no-dev-vars", "github-actions-secrets", "secret-provider-quarantine", "security-quality-regressions", "secrets-config", "consumer-agent-kit-dependency", "ci-test-perf", "test-scan-perf", "session-memory-hardcut", "worktree-main-ownership", "sast"];
21
+ export declare const AFFECTED_SAFE_AUDIT_KIND_IDS: readonly ["blueprint-readme-drift", "blueprint-lifecycle", "bucket-boundary", "skill-sizes", "broken-refs", "no-dev-vars", "absolute-path-policy", "secret-provider-quarantine", "secrets-config", "docs-frontmatter", "no-relative-parent-imports", "no-link-protocol", "no-first-party-mjs", "no-math-random", "test-scan-perf"];
22
22
  export type AuditKindId = (typeof AUDIT_KIND_IDS)[number];
23
23
  export type RepoAuditKindId = (typeof REPO_AUDIT_KIND_IDS)[number];
24
24
  export type MCPAuditKindId = (typeof MCP_AUDIT_KIND_IDS)[number];
@@ -16,6 +16,7 @@ const affectedSafeKinds = [
16
16
  "no-link-protocol",
17
17
  "no-first-party-mjs",
18
18
  "no-math-random",
19
+ "test-scan-perf",
19
20
  ];
20
21
  const mcpKindIds = [
21
22
  "test-smells",
@@ -56,6 +57,7 @@ const mcpKindIds = [
56
57
  "secrets-config",
57
58
  "consumer-agent-kit-dependency",
58
59
  "ci-test-perf",
60
+ "test-scan-perf",
59
61
  "session-memory-hardcut",
60
62
  "worktree-main-ownership",
61
63
  "sast",
@@ -130,6 +132,7 @@ const repoKinds = [
130
132
  "secrets-config",
131
133
  "consumer-agent-kit-dependency",
132
134
  "ci-test-perf",
135
+ "test-scan-perf",
133
136
  "worktree-main-ownership",
134
137
  "sast",
135
138
  "harness-surfaces",
@@ -58,7 +58,13 @@ export function auditRoadmapLinks(rootDirectory = process.cwd(), options = {}) {
58
58
  message: "Parent-roadmap execution-wave maps may list local child blueprints only; move cross-repo references to Cross-Plan References and use GitHub links there",
59
59
  });
60
60
  }
61
+ // Archived parent-roadmaps are non-executable program records. They may
62
+ // document completed children without a live execution-wave map or local
63
+ // children still declaring parent_roadmap. Do not fail CI on that residue.
61
64
  if (waveMapChildren.size === 0 && localChildren.length === 0) {
65
+ if (roadmap.status === "archived") {
66
+ continue;
67
+ }
62
68
  violations.push({
63
69
  file: roadmap.file,
64
70
  message: "Roadmap declares no children in its wave map",
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Guardrail: keep tests from silently growing multi-second / multi-ten-second
3
+ * full-repo filesystem scans, and from "fixing" that cost with elevated
4
+ * per-test timeouts.
5
+ *
6
+ * The private-UI cutover anti-regression suite originally walked the monorepo
7
+ * with readdirSync/statSync/readFileSync and needed a 30s timeout. Prefer
8
+ * `git grep` (tracked files) or a bounded fixture tree instead.
9
+ *
10
+ * Opt-out: `// test-scan-perf-allow` on the same line or previous line as the
11
+ * walk/timeout site, or `// test-scan-perf-allow-file` anywhere in the file.
12
+ */
13
+ import type { RepoAuditResult, RepoAuditViolation } from "./repo-guardrails.js";
14
+ export declare function isTestScanPerfTargetPath(relativePath: string): boolean;
15
+ export declare function findTestScanPerfViolationsInText(relativePath: string, text: string): RepoAuditViolation[];
16
+ export declare function auditTestScanPerf(rootDirectory?: string, options?: {
17
+ affectedFiles?: readonly string[];
18
+ }): RepoAuditResult;