@webpresso/agent-kit 3.1.30 → 3.3.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/README.md +53 -15
- package/bin/_run.js +183 -20
- package/catalog/AGENTS.md.tpl +3 -3
- package/catalog/agent/agents/implementer.md +45 -0
- package/catalog/agent/rules/parallel-orchestration-quota.md +100 -0
- package/catalog/agent/rules/pre-implementation.md +28 -1
- package/catalog/agent/rules/supported-agent-clis.md +9 -0
- package/catalog/agent/rules/typescript-version.md +50 -0
- package/catalog/agent/skills/codex/SKILL.md +25 -1
- package/catalog/agent/skills/verify/SKILL.md +4 -2
- package/catalog/docs/templates/blueprint.md +1 -1
- package/dist/esm/audit/agents.js +1 -0
- package/dist/esm/audit/blueprint-lifecycle-sql.d.ts +12 -0
- package/dist/esm/audit/blueprint-lifecycle-sql.js +23 -2
- package/dist/esm/audit/blueprint-pr-coverage.js +4 -0
- package/dist/esm/audit/blueprint-trust.js +14 -10
- package/dist/esm/audit/changeset-evidence.d.ts +23 -0
- package/dist/esm/audit/changeset-evidence.js +30 -0
- package/dist/esm/audit/changeset-required.d.ts +10 -0
- package/dist/esm/audit/changeset-required.js +73 -0
- package/dist/esm/audit/ci-guardrails-detection.d.ts +27 -0
- package/dist/esm/audit/ci-guardrails-detection.js +68 -0
- package/dist/esm/audit/ci-guardrails-wiring.d.ts +22 -0
- package/dist/esm/audit/ci-guardrails-wiring.js +80 -0
- package/dist/esm/audit/ci-test-perf.js +30 -0
- package/dist/esm/audit/command-surface.js +4 -2
- package/dist/esm/audit/host-substitution-risk.d.ts +2 -0
- package/dist/esm/audit/host-substitution-risk.js +256 -0
- package/dist/esm/audit/registry.d.ts +4 -4
- package/dist/esm/audit/registry.js +13 -1
- package/dist/esm/audit/toolchain-isolation.js +1 -2
- package/dist/esm/audit/typescript-version.d.ts +2 -0
- package/dist/esm/audit/typescript-version.js +123 -0
- package/dist/esm/blueprint/core/parser.js +101 -2
- package/dist/esm/blueprint/core/schema.d.ts +1 -0
- package/dist/esm/blueprint/core/schema.js +26 -0
- package/dist/esm/blueprint/core/validation/criteria.d.ts +14 -0
- package/dist/esm/blueprint/core/validation/criteria.js +41 -2
- package/dist/esm/blueprint/core/validation/state.js +25 -14
- package/dist/esm/blueprint/db/migrations/run.js +4 -2
- package/dist/esm/blueprint/lifecycle/audit.d.ts +20 -0
- package/dist/esm/blueprint/lifecycle/audit.js +87 -20
- package/dist/esm/blueprint/lifecycle/canonicalize.d.ts +6 -0
- package/dist/esm/blueprint/lifecycle/canonicalize.js +135 -0
- package/dist/esm/blueprint/lifecycle/local.d.ts +2 -1
- package/dist/esm/blueprint/lifecycle/local.js +19 -3
- package/dist/esm/blueprint/lifecycle/review-provenance.d.ts +6 -0
- package/dist/esm/blueprint/lifecycle/review-provenance.js +68 -12
- package/dist/esm/blueprint/lifecycle/rollout-anchor.d.ts +97 -0
- package/dist/esm/blueprint/lifecycle/rollout-anchor.js +161 -0
- package/dist/esm/blueprint/service/BlueprintCreationService.d.ts +1 -0
- package/dist/esm/blueprint/service/BlueprintCreationService.js +53 -4
- package/dist/esm/blueprint/service/blueprint-tech-debt-links.js +16 -2
- package/dist/esm/blueprint/sync/client.d.ts +2 -1
- package/dist/esm/blueprint/sync/client.js +1 -1
- package/dist/esm/blueprint/trust/command-runner.d.ts +26 -0
- package/dist/esm/blueprint/trust/command-runner.js +148 -0
- package/dist/esm/blueprint/trust/dossier.d.ts +1 -0
- package/dist/esm/blueprint/trust/dossier.js +40 -6
- package/dist/esm/blueprint/trust/gates.d.ts +7 -0
- package/dist/esm/blueprint/trust/gates.js +20 -0
- package/dist/esm/blueprint/trust/promotion.d.ts +8 -0
- package/dist/esm/blueprint/trust/promotion.js +74 -112
- package/dist/esm/blueprint/trust/scaffold.d.ts +19 -0
- package/dist/esm/blueprint/trust/scaffold.js +64 -0
- package/dist/esm/blueprint/trust/validator.d.ts +1 -0
- package/dist/esm/blueprint/trust/validator.js +7 -1
- package/dist/esm/blueprint/utils/archive.js +27 -5
- package/dist/esm/blueprint/utils/document-paths.d.ts +18 -0
- package/dist/esm/blueprint/utils/document-paths.js +45 -12
- package/dist/esm/build/cli-mcp-parity.js +15 -0
- package/dist/esm/cli/bundle/commands/blueprint.d.ts +1 -0
- package/dist/esm/cli/bundle/commands/blueprint.js +1 -0
- package/dist/esm/cli/cli.d.ts +1 -1
- package/dist/esm/cli/cli.js +43 -0
- package/dist/esm/cli/commands/agent-launch.d.ts +8 -1
- package/dist/esm/cli/commands/agent-launch.js +93 -4
- package/dist/esm/cli/commands/audit-core.d.ts +1 -1
- package/dist/esm/cli/commands/audit-core.js +8 -1
- package/dist/esm/cli/commands/audit.js +21 -5
- package/dist/esm/cli/commands/blueprint/execution.js +18 -6
- package/dist/esm/cli/commands/blueprint/mutations.js +59 -40
- package/dist/esm/cli/commands/blueprint/router-dispatch.d.ts +2 -1
- package/dist/esm/cli/commands/blueprint/router-dispatch.js +20 -1
- package/dist/esm/cli/commands/blueprint/router-output.d.ts +21 -0
- package/dist/esm/cli/commands/blueprint/router-output.js +48 -17
- package/dist/esm/cli/commands/blueprint/router.d.ts +7 -0
- package/dist/esm/cli/commands/blueprint/router.js +13 -1
- package/dist/esm/cli/commands/ci-preflight.d.ts +13 -2
- package/dist/esm/cli/commands/ci-preflight.js +30 -6
- package/dist/esm/cli/commands/compile.js +5 -31
- package/dist/esm/cli/commands/dash/delivery-status.d.ts +1 -0
- package/dist/esm/cli/commands/dash/delivery-status.js +11 -4
- package/dist/esm/cli/commands/doctor.js +5 -0
- package/dist/esm/cli/commands/hook.d.ts +5 -0
- package/dist/esm/cli/commands/hook.js +15 -9
- package/dist/esm/cli/commands/init/config.js +2 -2
- package/dist/esm/cli/commands/init/convergence-apply.d.ts +88 -0
- package/dist/esm/cli/commands/init/convergence-apply.js +325 -0
- package/dist/esm/cli/commands/init/convergence-state.d.ts +13 -0
- package/dist/esm/cli/commands/init/convergence-state.js +54 -0
- package/dist/esm/cli/commands/init/convergence.d.ts +23 -0
- package/dist/esm/cli/commands/init/convergence.js +57 -0
- package/dist/esm/cli/commands/init/git-convergence.d.ts +19 -0
- package/dist/esm/cli/commands/init/git-convergence.js +168 -0
- package/dist/esm/cli/commands/init/gitignore-patcher.js +2 -0
- package/dist/esm/cli/commands/init/grok-privacy-smoke.d.ts +52 -0
- package/dist/esm/cli/commands/init/grok-privacy-smoke.js +91 -0
- package/dist/esm/cli/commands/init/host-native-proof.d.ts +18 -0
- package/dist/esm/cli/commands/init/host-native-proof.js +88 -0
- package/dist/esm/cli/commands/init/host-visibility.d.ts +2 -2
- package/dist/esm/cli/commands/init/host-visibility.js +94 -5
- package/dist/esm/cli/commands/init/index.d.ts +3 -0
- package/dist/esm/cli/commands/init/index.js +326 -173
- package/dist/esm/cli/commands/init/mcp-spec.d.ts +57 -0
- package/dist/esm/cli/commands/init/mcp-spec.js +273 -0
- package/dist/esm/cli/commands/init/merge.d.ts +1 -0
- package/dist/esm/cli/commands/init/merge.js +30 -1
- package/dist/esm/cli/commands/init/package-root.d.ts +7 -0
- package/dist/esm/cli/commands/init/package-root.js +10 -2
- package/dist/esm/cli/commands/init/plugin-cache-prune.d.ts +0 -3
- package/dist/esm/cli/commands/init/plugin-cache-prune.js +0 -1
- package/dist/esm/cli/commands/init/scaffold-agents-md.d.ts +1 -1
- package/dist/esm/cli/commands/init/scaffold-agents-md.js +9 -3
- package/dist/esm/cli/commands/init/scaffold-base-kit.d.ts +20 -0
- package/dist/esm/cli/commands/init/scaffold-base-kit.js +61 -5
- package/dist/esm/cli/commands/init/scaffolders/agent-hooks/emitters/grok.d.ts +14 -8
- package/dist/esm/cli/commands/init/scaffolders/agent-hooks/emitters/grok.js +41 -24
- package/dist/esm/cli/commands/init/scaffolders/agent-hooks/index.d.ts +2 -0
- package/dist/esm/cli/commands/init/scaffolders/agent-hooks/index.js +54 -24
- package/dist/esm/cli/commands/init/scaffolders/codex-mcp/index.d.ts +12 -31
- package/dist/esm/cli/commands/init/scaffolders/codex-mcp/index.js +288 -202
- package/dist/esm/cli/commands/init/scaffolders/file-transaction.d.ts +22 -0
- package/dist/esm/cli/commands/init/scaffolders/file-transaction.js +230 -0
- package/dist/esm/cli/commands/init/scaffolders/grok-mcp/index.d.ts +38 -0
- package/dist/esm/cli/commands/init/scaffolders/grok-mcp/index.js +73 -0
- package/dist/esm/cli/commands/init/scaffolders/opencode-plugin/index.d.ts +21 -0
- package/dist/esm/cli/commands/init/scaffolders/opencode-plugin/index.js +559 -63
- package/dist/esm/cli/commands/init/untracked-collision.d.ts +14 -0
- package/dist/esm/cli/commands/init/untracked-collision.js +42 -0
- package/dist/esm/cli/commands/opencode-probe.d.ts +107 -0
- package/dist/esm/cli/commands/opencode-probe.js +323 -0
- package/dist/esm/cli/commands/opencode-rotate.d.ts +72 -0
- package/dist/esm/cli/commands/opencode-rotate.js +147 -0
- package/dist/esm/cli/commands/public-readiness.d.ts +11 -0
- package/dist/esm/cli/commands/public-readiness.js +42 -0
- package/dist/esm/cli/commands/quality-runner.js +3 -3
- package/dist/esm/cli/commands/review.d.ts +35 -3
- package/dist/esm/cli/commands/review.js +751 -209
- package/dist/esm/cli/commands/run-wait.d.ts +2 -0
- package/dist/esm/cli/commands/run-wait.js +122 -0
- package/dist/esm/cli/commands/rust-check.d.ts +24 -0
- package/dist/esm/cli/commands/rust-check.js +110 -0
- package/dist/esm/cli/commands/setup-help.js +1 -1
- package/dist/esm/cli/commands/setup-repair-help.js +1 -1
- package/dist/esm/cli/commands/typecheck.d.ts +1 -0
- package/dist/esm/cli/commands/typecheck.js +30 -1
- package/dist/esm/cli/commands/worktree/router-dispatch.d.ts +6 -0
- package/dist/esm/cli/commands/worktree/router-dispatch.js +126 -9
- package/dist/esm/cli/commands/worktree/router.js +10 -0
- package/dist/esm/cli/direct-provider-launch.js +9 -5
- package/dist/esm/cli/optional-tool-freshness.d.ts +27 -0
- package/dist/esm/cli/optional-tool-freshness.js +219 -9
- package/dist/esm/daemon/domains/git-actions.d.ts +29 -0
- package/dist/esm/daemon/domains/git-actions.js +136 -0
- package/dist/esm/daemon/domains/run-registry-interop.d.ts +16 -0
- package/dist/esm/daemon/domains/run-registry-interop.js +45 -0
- package/dist/esm/daemon/domains/sessions-search.d.ts +11 -0
- package/dist/esm/daemon/domains/sessions-search.js +29 -0
- package/dist/esm/daemon/handlers/blueprints-handlers.d.ts +5 -0
- package/dist/esm/daemon/handlers/blueprints-handlers.js +36 -0
- package/dist/esm/daemon/handlers/deploy-handlers.d.ts +6 -0
- package/dist/esm/daemon/handlers/deploy-handlers.js +5 -0
- package/dist/esm/daemon/handlers/fleet-handlers.d.ts +15 -0
- package/dist/esm/daemon/handlers/fleet-handlers.js +91 -0
- package/dist/esm/daemon/handlers/git-handlers.d.ts +6 -0
- package/dist/esm/daemon/handlers/git-handlers.js +27 -0
- package/dist/esm/daemon/handlers/product.d.ts +7 -0
- package/dist/esm/daemon/handlers/product.js +10 -119
- package/dist/esm/daemon/handlers/sessions-handlers.d.ts +12 -0
- package/dist/esm/daemon/handlers/sessions-handlers.js +31 -0
- package/dist/esm/daemon/handlers/shared.d.ts +5 -0
- package/dist/esm/daemon/handlers/shared.js +16 -0
- package/dist/esm/daemon/methods.d.ts +8 -1
- package/dist/esm/daemon/methods.js +7 -0
- package/dist/esm/daemon/protocol/contract.d.ts +43 -0
- package/dist/esm/daemon/protocol/contract.js +26 -0
- package/dist/esm/daemon/protocol/index.d.ts +1 -1
- package/dist/esm/daemon/protocol/index.js +1 -1
- package/dist/esm/hooks/__conformance__/matrix.d.ts +6 -0
- package/dist/esm/hooks/__conformance__/matrix.js +137 -0
- package/dist/esm/hooks/conformance/batch.js +5 -0
- package/dist/esm/hooks/doctor.d.ts +5 -3
- package/dist/esm/hooks/doctor.js +67 -16
- package/dist/esm/hooks/permission-request/index.js +9 -3
- package/dist/esm/hooks/permission-request/policy.d.ts +9 -0
- package/dist/esm/hooks/permission-request/policy.js +13 -0
- package/dist/esm/hooks/pretool-guard/dev-routing.d.ts +9 -0
- package/dist/esm/hooks/pretool-guard/dev-routing.js +41 -5
- package/dist/esm/hooks/pretool-guard/logger.d.ts +7 -0
- package/dist/esm/hooks/pretool-guard/logger.js +16 -0
- package/dist/esm/hooks/pretool-guard/runner.d.ts +6 -0
- package/dist/esm/hooks/pretool-guard/runner.js +68 -16
- package/dist/esm/hooks/pretool-guard/validators/blueprint.js +30 -10
- package/dist/esm/hooks/pretool-guard/validators/forbidden-commands.js +54 -0
- package/dist/esm/hooks/pretool-guard/validators/index.d.ts +7 -0
- package/dist/esm/hooks/pretool-guard/validators/index.js +21 -0
- package/dist/esm/hooks/pretool-guard/validators/path-contract.d.ts +19 -0
- package/dist/esm/hooks/pretool-guard/validators/path-contract.js +25 -8
- package/dist/esm/hooks/pretool-guard/validators/worktree-discipline.js +6 -1
- package/dist/esm/hooks/shared/guard-outcome.d.ts +149 -0
- package/dist/esm/hooks/shared/guard-outcome.js +215 -0
- package/dist/esm/hooks/shared/hook-bootstrap.d.ts +26 -0
- package/dist/esm/hooks/shared/hook-bootstrap.js +64 -1
- package/dist/esm/hooks/shared/types.d.ts +8 -5
- package/dist/esm/hooks/shared/types.js +50 -9
- package/dist/esm/hooks/stop/qa-changed-files.d.ts +11 -0
- package/dist/esm/hooks/stop/qa-changed-files.js +92 -0
- package/dist/esm/mcp/blueprint/_shared/lifecycle.js +14 -1
- package/dist/esm/mcp/blueprint/handlers/document-mutations.d.ts +5 -0
- package/dist/esm/mcp/blueprint/handlers/document-mutations.js +109 -11
- package/dist/esm/mcp/blueprint/handlers/review-log.js +8 -1
- package/dist/esm/mcp/blueprint/handlers/task-advance.js +113 -54
- package/dist/esm/mcp/blueprint/handlers/task-verify.js +39 -19
- package/dist/esm/mcp/blueprint/registration.js +3 -2
- package/dist/esm/mcp/server.d.ts +1 -0
- package/dist/esm/mcp/server.js +27 -0
- package/dist/esm/mcp/tools/_names.d.ts +1 -1
- package/dist/esm/mcp/tools/_names.js +5 -0
- package/dist/esm/mcp/tools/_registry.js +10 -0
- package/dist/esm/mcp/tools/_shared/audit-kinds.d.ts +1 -1
- package/dist/esm/mcp/tools/_shared/result.js +44 -1
- package/dist/esm/mcp/tools/audit.d.ts +5 -1
- package/dist/esm/mcp/tools/audit.js +42 -0
- package/dist/esm/mcp/tools/audits.d.ts +4 -0
- package/dist/esm/mcp/tools/ci-preflight.d.ts +50 -0
- package/dist/esm/mcp/tools/ci-preflight.js +183 -0
- package/dist/esm/mcp/tools/pr-upsert.js +39 -1
- package/dist/esm/mcp/tools/review-run.d.ts +28 -0
- package/dist/esm/mcp/tools/review-run.js +216 -0
- package/dist/esm/mcp/tools/run-wait.d.ts +86 -0
- package/dist/esm/mcp/tools/run-wait.js +593 -0
- package/dist/esm/mcp/tools/session-fetch-and-index.d.ts +1 -1
- package/dist/esm/mcp/tools/session-id.d.ts +3 -0
- package/dist/esm/mcp/tools/session-id.js +45 -0
- package/dist/esm/mcp/tools/session-info.d.ts +3 -0
- package/dist/esm/mcp/tools/session-info.js +49 -0
- package/dist/esm/output-transforms/audit.d.ts +23 -0
- package/dist/esm/output-transforms/audit.js +47 -0
- package/dist/esm/output-transforms/index.js +2 -0
- package/dist/esm/package.json +2 -0
- package/dist/esm/platform/client.d.ts +7 -1
- package/dist/esm/platform/github-contract-source.d.ts +2 -1
- package/dist/esm/pr-description/contract.js +24 -4
- package/dist/esm/review/authority.js +27 -14
- package/dist/esm/review/availability.d.ts +33 -0
- package/dist/esm/review/availability.js +122 -20
- package/dist/esm/review/delivery-verifier.d.ts +27 -0
- package/dist/esm/review/delivery-verifier.js +115 -0
- package/dist/esm/review/events.d.ts +3 -2
- package/dist/esm/review/events.js +23 -3
- package/dist/esm/review/execution/adapters.d.ts +11 -1
- package/dist/esm/review/execution/adapters.js +36 -10
- package/dist/esm/review/execution/artifacts.d.ts +1 -1
- package/dist/esm/review/execution/output-policy.d.ts +2 -0
- package/dist/esm/review/execution/output-policy.js +12 -0
- package/dist/esm/review/execution/review-checkout.js +121 -21
- package/dist/esm/review/execution/supervisor.js +36 -4
- package/dist/esm/review/execution/types.d.ts +9 -0
- package/dist/esm/review/opencode-account.d.ts +24 -0
- package/dist/esm/review/opencode-account.js +68 -0
- package/dist/esm/review/opencode-auth.d.ts +33 -0
- package/dist/esm/review/opencode-auth.js +79 -0
- package/dist/esm/review/subject.d.ts +25 -0
- package/dist/esm/review/subject.js +116 -26
- package/dist/esm/runtime/opencode-account-materializer.d.ts +34 -0
- package/dist/esm/runtime/opencode-account-materializer.js +33 -0
- package/dist/esm/rust/affected-manifests.d.ts +38 -0
- package/dist/esm/rust/affected-manifests.js +69 -0
- package/dist/esm/session-memory/fetch-index.d.ts +1 -1
- package/dist/esm/status/snapshot.js +32 -25
- package/dist/esm/test/duration-sequencer.d.ts +20 -0
- package/dist/esm/test/duration-sequencer.js +54 -0
- package/dist/esm/test/shard-durations.json +922 -0
- package/dist/esm/test/worker-budget.d.ts +6 -0
- package/dist/esm/test/worker-budget.js +59 -1
- package/dist/esm/test-helpers/global-setup.d.ts +1 -2
- package/dist/esm/test-helpers/global-setup.js +79 -32
- package/dist/esm/typecheck/planner.d.ts +9 -1
- package/dist/esm/typecheck/planner.js +31 -0
- package/dist/esm/ultragoal/service.d.ts +7 -0
- package/dist/esm/ultragoal/service.js +42 -2
- package/dist/esm/utils/write-json-file.js +3 -1
- package/dist/esm/worktrees/codex-project-trust.d.ts +15 -0
- package/dist/esm/worktrees/codex-project-trust.js +56 -0
- package/dist/esm/worktrees/orphan-scan.d.ts +50 -0
- package/dist/esm/worktrees/orphan-scan.js +211 -0
- package/dist/esm/worktrees/registry.d.ts +8 -0
- package/dist/esm/worktrees/registry.js +18 -3
- package/package.json +18 -13
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: rule
|
|
3
|
+
slug: typescript-version
|
|
4
|
+
title: TypeScript 7 required at consumers
|
|
5
|
+
status: active
|
|
6
|
+
scope: repo
|
|
7
|
+
applies_to: [agents]
|
|
8
|
+
related:
|
|
9
|
+
- package-conventions
|
|
10
|
+
- ci-test-perf
|
|
11
|
+
created: "2026-07-24"
|
|
12
|
+
last_reviewed: "2026-07-24"
|
|
13
|
+
paths:
|
|
14
|
+
- "pnpm-workspace.yaml"
|
|
15
|
+
- "**/package.json"
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
# TypeScript 7 required at consumers
|
|
19
|
+
|
|
20
|
+
The Webpresso toolchain runs on **TypeScript 7** — the native (Go) compiler that
|
|
21
|
+
`tsc` resolves to since the 7.0 GA. Consumer repos must stay on the 7 major so
|
|
22
|
+
their typecheck, build, and shipped `.d.ts` match the compiler agent-kit ships
|
|
23
|
+
and tests against.
|
|
24
|
+
|
|
25
|
+
## What is enforced
|
|
26
|
+
|
|
27
|
+
`wp audit typescript-version` (bundled in `wp audit guardrails`, so it runs in
|
|
28
|
+
every consumer CI that calls guardrails, and updates automatically on the next
|
|
29
|
+
`wp` / agent-kit bump — no CI edits) fails when the `typescript` compiler
|
|
30
|
+
dependency is pinned below major 7, in either:
|
|
31
|
+
|
|
32
|
+
- the `catalog.typescript` pin in `pnpm-workspace.yaml`, or
|
|
33
|
+
- a direct `typescript` dependency/devDependency in any `package.json`
|
|
34
|
+
(`catalog:` / `workspace:` references are skipped — they resolve through the
|
|
35
|
+
catalog, which is checked directly).
|
|
36
|
+
|
|
37
|
+
## `@typescript/typescript6` is allowed — it is not legacy
|
|
38
|
+
|
|
39
|
+
TypeScript 7's **programmatic** compiler API is not stable until 7.1, so tooling
|
|
40
|
+
that calls the compiler API (e.g. `wp typecheck`) bridges through
|
|
41
|
+
`@typescript/typescript6`, the Microsoft-published TS6 compiler-API
|
|
42
|
+
compatibility package. That is a distinct dependency from the `typescript`
|
|
43
|
+
compiler and is **never** flagged by this audit. Keep `typescript` on `^7` and
|
|
44
|
+
`@typescript/typescript6` on `^6` — do not downgrade the `typescript` compiler.
|
|
45
|
+
|
|
46
|
+
## Temporary exception
|
|
47
|
+
|
|
48
|
+
A consumer mid-migration may add a `typescript-version-allow` comment in
|
|
49
|
+
`pnpm-workspace.yaml` (or the manifest) to suppress the check. Treat it as
|
|
50
|
+
temporary and remove it once the repo is on TypeScript 7.
|
|
@@ -8,13 +8,37 @@ license: MIT
|
|
|
8
8
|
|
|
9
9
|
Use this skill from Claude or another non-Codex host when the user wants Codex to independently review a diff, challenge a plan, or answer a repo question. Keep Codex read-only by default and treat its answer as external advice until independently verified.
|
|
10
10
|
|
|
11
|
+
## Primary path: the `wp_review_run` MCP tool
|
|
12
|
+
|
|
13
|
+
When the webpresso MCP server is available, call `wp_review_run` directly instead of any bash block below — it is the same `wp review run` typed runtime, called in-process, with two ergonomic wins: it takes a `prompt` string (no `--prompt-file` bookkeeping) and it runs the probe stage and the review stage in **one call** by default (the review stage is skipped automatically if the probe fails, so you never waste a full review invocation on a cold/broken provider).
|
|
14
|
+
|
|
15
|
+
```jsonc
|
|
16
|
+
// wp_review_run MCP tool call
|
|
17
|
+
{
|
|
18
|
+
"prompt": "<diff summary + what to look for>",
|
|
19
|
+
"provider": "codex",
|
|
20
|
+
// model, effort, artifactRoot, idleSeconds are all optional and default
|
|
21
|
+
// the same way the `wp review run` CLI does. Omit `stage` for automatic
|
|
22
|
+
// probe-then-review; pass stage: "probe" or stage: "review" only for
|
|
23
|
+
// manual single-stage control.
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
**Provider fallback lives in this one call.** If your primary provider (e.g. Codex) is unavailable — out of credits, auth expired — switch the `provider` parameter to another supported value (`claude`, `opencode`, `grok`) in the SAME `wp_review_run` call. Never shell into a different provider's CLI directly; that bypasses artifact capture under `.webpresso/reviews`, availability tracking, and the probe-then-review handshake this tool exists to provide. The pretool guard hook actively blocks shelling straight into a provider CLI's review-launching subcommand and redirects here.
|
|
28
|
+
|
|
29
|
+
The bash blocks in this skill (below) are the **MCP-unavailable fallback only** — use them when the webpresso MCP server itself is not reachable in the current host, not as a provider-fallback mechanism.
|
|
30
|
+
|
|
11
31
|
## Single-shot budget (anti-stampede)
|
|
12
32
|
|
|
13
33
|
- Default: **one** review invocation per request.
|
|
14
34
|
- Do **not** fan out parallel multi-host review matrices unless the user set `review_budget`/`N` > 1.
|
|
15
|
-
- Prefer `wp review run` / `wp review gate` over spawn/wait agent loops for review.
|
|
35
|
+
- Prefer `wp_review_run` (MCP) or `wp review run` / `wp review gate` (CLI fallback) over spawn/wait agent loops for review.
|
|
16
36
|
- Keep prompts bounded; no whole-repo paste.
|
|
17
37
|
|
|
38
|
+
## MCP-unavailable fallback (raw CLI)
|
|
39
|
+
|
|
40
|
+
Everything from here down is the manual `wp review run` CLI path documented for hosts or sessions where the webpresso MCP server is not reachable. Prefer `wp_review_run` above whenever MCP is available.
|
|
41
|
+
|
|
18
42
|
## Auth check
|
|
19
43
|
|
|
20
44
|
```bash
|
|
@@ -235,8 +235,10 @@ Reviewer preference (pick **one** path):
|
|
|
235
235
|
|
|
236
236
|
Approval evidence requirements:
|
|
237
237
|
|
|
238
|
-
- Blueprint lifecycle: `wp review gate <blueprint-slug> --target HEAD --json`
|
|
239
|
-
|
|
238
|
+
- Blueprint lifecycle: `wp review gate <blueprint-slug> --purpose delivery --target HEAD --json`
|
|
239
|
+
first. Delivery gates automatically run declared Promotion Gates from the
|
|
240
|
+
Trust Dossier before outside-review provider probes. Use `wp review log` /
|
|
241
|
+
`wp review read` for inspection.
|
|
240
242
|
- Each reviewer must return clear `APPROVED`/`BLOCKED` with enough evidence.
|
|
241
243
|
- Record as PR comments when a PR exists (model/tool, verdict, reviewed commit).
|
|
242
244
|
- Unavailable reviewer → try next preferred; if still short, report gap and
|
|
@@ -22,7 +22,7 @@ approvals: [] # Legacy approval mirror; authority may also come from v2 review-e
|
|
|
22
22
|
- Draft slug: `{{slug}}`
|
|
23
23
|
- Output path: `{{output_path}}`
|
|
24
24
|
- Generated command: `wp blueprint new "{{description}}" --complexity {{complexity}}`
|
|
25
|
-
- Default shape: folder (`blueprints/<status>/<slug>/_overview.md`, with sibling `reviews.md` for approvals); flat `blueprints/<status>/<slug>.md`
|
|
25
|
+
- Default shape: folder (`blueprints/<status>/<slug>/_overview.md`, with sibling `reviews.md` for approvals); flat `blueprints/<status>/<slug>.md` stays readable but cannot hold review evidence, ledgers, or approvals — run `wp blueprint canonicalize <slug>` first
|
|
26
26
|
- Validation scope: parser compliance before write
|
|
27
27
|
|
|
28
28
|
## Architecture Overview
|
package/dist/esm/audit/agents.js
CHANGED
|
@@ -35,4 +35,16 @@ export interface BlueprintLifecycleAuditOptions {
|
|
|
35
35
|
*/
|
|
36
36
|
readonly affectedFiles?: readonly string[];
|
|
37
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* Decide the file scope for the per-file `runBlueprintAudit` pass. In
|
|
40
|
+
* changed-only mode with a resolved changed scope, audit only the changed
|
|
41
|
+
* blueprint files (bounded, pre-commit-fast); otherwise audit the whole corpus
|
|
42
|
+
* (`all: true`, the CI-grade pass). Extracted + exported so the scoping contract
|
|
43
|
+
* is unit-testable without standing up the full corpus.
|
|
44
|
+
*/
|
|
45
|
+
export declare function resolveRunBlueprintAuditScope(changedOnly: boolean, changedFiles: ReadonlySet<string> | null): {
|
|
46
|
+
readonly stagedFiles: string[];
|
|
47
|
+
} | {
|
|
48
|
+
readonly all: true;
|
|
49
|
+
};
|
|
38
50
|
export declare function auditBlueprintLifecycleSql(cwd?: string, options?: BlueprintLifecycleAuditOptions): Promise<RepoAuditResult>;
|
|
@@ -357,17 +357,20 @@ function listTrackedBlueprintPaths(cwd) {
|
|
|
357
357
|
}
|
|
358
358
|
function changedBlueprintScope(candidates) {
|
|
359
359
|
const slugs = new Set();
|
|
360
|
+
const files = new Set();
|
|
360
361
|
for (const [currentPath, candidate] of candidates) {
|
|
362
|
+
files.add(currentPath);
|
|
361
363
|
const currentSlug = blueprintSlugFromGitPath(currentPath);
|
|
362
364
|
if (currentSlug)
|
|
363
365
|
slugs.add(currentSlug);
|
|
364
366
|
for (const previousPath of candidate.previousPaths) {
|
|
367
|
+
files.add(previousPath);
|
|
365
368
|
const previousSlug = blueprintSlugFromGitPath(previousPath);
|
|
366
369
|
if (previousSlug)
|
|
367
370
|
slugs.add(previousSlug);
|
|
368
371
|
}
|
|
369
372
|
}
|
|
370
|
-
return { slugs };
|
|
373
|
+
return { slugs, files };
|
|
371
374
|
}
|
|
372
375
|
/**
|
|
373
376
|
* When `--affected` supplies an explicit file set (pre-commit staged files or
|
|
@@ -435,6 +438,19 @@ function readPreviousLifecycleStatusFromCandidate(cwd, row, candidate, trackedPa
|
|
|
435
438
|
}
|
|
436
439
|
return null;
|
|
437
440
|
}
|
|
441
|
+
/**
|
|
442
|
+
* Decide the file scope for the per-file `runBlueprintAudit` pass. In
|
|
443
|
+
* changed-only mode with a resolved changed scope, audit only the changed
|
|
444
|
+
* blueprint files (bounded, pre-commit-fast); otherwise audit the whole corpus
|
|
445
|
+
* (`all: true`, the CI-grade pass). Extracted + exported so the scoping contract
|
|
446
|
+
* is unit-testable without standing up the full corpus.
|
|
447
|
+
*/
|
|
448
|
+
export function resolveRunBlueprintAuditScope(changedOnly, changedFiles) {
|
|
449
|
+
if (changedOnly && changedFiles !== null) {
|
|
450
|
+
return { stagedFiles: [...changedFiles] };
|
|
451
|
+
}
|
|
452
|
+
return { all: true };
|
|
453
|
+
}
|
|
438
454
|
export async function auditBlueprintLifecycleSql(cwd = process.cwd(), options = {}) {
|
|
439
455
|
const budgets = loadBudgets(cwd);
|
|
440
456
|
const wipInProgressMax = budgets["blueprint-wip-in-progress-max"].max ?? 3;
|
|
@@ -522,10 +538,15 @@ export async function auditBlueprintLifecycleSql(cwd = process.cwd(), options =
|
|
|
522
538
|
if (options.changedOnly && changedScope) {
|
|
523
539
|
titleNotices.push(`changed-only: ${changedScope.slugs.size} blueprint(s)`);
|
|
524
540
|
}
|
|
541
|
+
// In changed-only mode, scope the per-file blueprint audit to the changed
|
|
542
|
+
// blueprints. Auditing all blueprints (`all: true`) parses and validates the
|
|
543
|
+
// entire corpus (~1300 files, ~70s) even for a one-blueprint change — far too
|
|
544
|
+
// slow for the pre-commit hot path. The whole-corpus pass is CI's job; the
|
|
545
|
+
// structural + SQL projection checks below still run over every blueprint.
|
|
525
546
|
const { runBlueprintAudit } = await import("../blueprint/lifecycle/audit.js");
|
|
526
547
|
const lifecycle = await runBlueprintAudit({
|
|
527
548
|
projectRoot: cwd,
|
|
528
|
-
|
|
549
|
+
...resolveRunBlueprintAuditScope(options.changedOnly === true, changedScope?.files ?? null),
|
|
529
550
|
completedApprovalGate: options.changedOnly === true,
|
|
530
551
|
inProgressApprovalGate: options.changedOnly === true,
|
|
531
552
|
strict: true,
|
|
@@ -262,6 +262,10 @@ function isDependabotDependencyFile(filePath) {
|
|
|
262
262
|
normalized === "bun.lockb" ||
|
|
263
263
|
normalized === "deno.lock" ||
|
|
264
264
|
normalized === "pnpm-workspace.yaml" ||
|
|
265
|
+
normalized === "Cargo.toml" ||
|
|
266
|
+
normalized.endsWith("/Cargo.toml") ||
|
|
267
|
+
normalized === "Cargo.lock" ||
|
|
268
|
+
normalized.endsWith("/Cargo.lock") ||
|
|
265
269
|
(normalized.startsWith(".github/workflows/") && /\.ya?ml$/u.test(basename)));
|
|
266
270
|
}
|
|
267
271
|
function normalizeHeadRef(value) {
|
|
@@ -4,15 +4,16 @@ import matter from "gray-matter";
|
|
|
4
4
|
import { parseTrustDossier } from "#trust/dossier.js";
|
|
5
5
|
import { validateBlueprintTrust } from "#trust/validator.js";
|
|
6
6
|
import { parseBlueprintDocumentRelativePath } from "#utils/document-paths.js";
|
|
7
|
+
import { stableSlugForBlueprintFile } from "#lifecycle/review-provenance.js";
|
|
8
|
+
import { isBlueprintExemptFromRollout, ROLLOUT_DATE } from "#lifecycle/rollout-anchor.js";
|
|
7
9
|
const EXECUTABLE_DIRS = ["planned", "in-progress", "completed"];
|
|
8
|
-
const TRUST_DOSSIER_ROLLOUT_DATE = "2026-07-12";
|
|
9
10
|
export function auditBlueprintTrust(rootDirectory = process.cwd()) {
|
|
10
11
|
const violations = [];
|
|
11
12
|
let checked = 0;
|
|
12
13
|
for (const file of findExecutableBlueprints(rootDirectory)) {
|
|
13
14
|
checked += 1;
|
|
14
15
|
const markdown = readFileSync(path.join(rootDirectory, file), "utf8");
|
|
15
|
-
const grandfatherMissingDossier = isPreContractBlueprintMissingDossier(markdown);
|
|
16
|
+
const grandfatherMissingDossier = isPreContractBlueprintMissingDossier(markdown, rootDirectory, file);
|
|
16
17
|
const status = readStatus(markdown);
|
|
17
18
|
const normalizedStatus = isBlueprintTrustStatus(status) ? status : "planned";
|
|
18
19
|
if (!isBlueprintTrustStatus(status)) {
|
|
@@ -40,20 +41,23 @@ export function auditBlueprintTrust(rootDirectory = process.cwd()) {
|
|
|
40
41
|
}
|
|
41
42
|
return { ok: violations.length === 0, title: "Blueprint trust", checked, violations };
|
|
42
43
|
}
|
|
43
|
-
function isPreContractBlueprintMissingDossier(markdown) {
|
|
44
|
+
function isPreContractBlueprintMissingDossier(markdown, repoRoot, file) {
|
|
44
45
|
const parsedDossier = parseTrustDossier(markdown);
|
|
45
46
|
if (parsedDossier.violations.length !== 1 ||
|
|
46
47
|
parsedDossier.violations[0]?.section !== "Trust Dossier" ||
|
|
47
48
|
parsedDossier.violations[0]?.message !== "missing Trust Dossier section") {
|
|
48
49
|
return false;
|
|
49
50
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
51
|
+
// Was a compare against frontmatter `created`, which the blueprint own
|
|
52
|
+
// author controls -- writing an old date suppressed the missing-dossier
|
|
53
|
+
// violation outright. Presence at the rollout anchor commit is the
|
|
54
|
+
// unforgeable equivalent; see `#lifecycle/rollout-anchor.js`. Fails closed.
|
|
55
|
+
return isBlueprintExemptFromRollout({
|
|
56
|
+
repoRoot,
|
|
57
|
+
slug: stableSlugForBlueprintFile(file),
|
|
58
|
+
created: matter(markdown).data["created"],
|
|
59
|
+
rolloutDate: ROLLOUT_DATE,
|
|
60
|
+
});
|
|
57
61
|
}
|
|
58
62
|
export function findExecutableBlueprints(rootDirectory) {
|
|
59
63
|
const root = path.join(rootDirectory, "blueprints");
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared "does this change carry release evidence" predicate.
|
|
3
|
+
*
|
|
4
|
+
* Single source of truth for the `changeset-missing` rule described in
|
|
5
|
+
* `catalog/agent/rules/changeset-release.md`: release-visible changes need a
|
|
6
|
+
* `.changeset/*.md` entry (or an all-docs change, or a `Changeset-exempt:`
|
|
7
|
+
* trailer). Consumed by both the live-PR readiness evaluator
|
|
8
|
+
* (`#status/snapshot.js`) and the local branch-scoped audit
|
|
9
|
+
* (`#audit/changeset-required.js`) so the two verdicts can never diverge.
|
|
10
|
+
*/
|
|
11
|
+
export declare const CHANGESET_EXEMPT_PATTERN: RegExp;
|
|
12
|
+
/**
|
|
13
|
+
* True when `files` carries release evidence for its own change: every
|
|
14
|
+
* changed file is documentation, one of the changed files is itself a
|
|
15
|
+
* `.changeset/*.md` entry, or one of `exemptionTexts` (PR body, commit
|
|
16
|
+
* messages, ...) carries a `Changeset-exempt: <reason>` trailer.
|
|
17
|
+
*
|
|
18
|
+
* Mirrors the original `wp_pr_status` behavior exactly: an empty `files` list
|
|
19
|
+
* is NOT treated as evidence (fails closed) — callers that consider "no
|
|
20
|
+
* changed files" a legitimate, non-suspicious state (e.g. a freshly rebased
|
|
21
|
+
* local branch) must short-circuit before calling this function.
|
|
22
|
+
*/
|
|
23
|
+
export declare function hasChangesetEvidence(files: readonly string[], exemptionTexts: readonly string[]): boolean;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared "does this change carry release evidence" predicate.
|
|
3
|
+
*
|
|
4
|
+
* Single source of truth for the `changeset-missing` rule described in
|
|
5
|
+
* `catalog/agent/rules/changeset-release.md`: release-visible changes need a
|
|
6
|
+
* `.changeset/*.md` entry (or an all-docs change, or a `Changeset-exempt:`
|
|
7
|
+
* trailer). Consumed by both the live-PR readiness evaluator
|
|
8
|
+
* (`#status/snapshot.js`) and the local branch-scoped audit
|
|
9
|
+
* (`#audit/changeset-required.js`) so the two verdicts can never diverge.
|
|
10
|
+
*/
|
|
11
|
+
const CHANGESET_FILE_PATTERN = /^\.changeset\/[^/]+\.md$/u;
|
|
12
|
+
export const CHANGESET_EXEMPT_PATTERN = /Changeset-exempt:\s*\S/iu;
|
|
13
|
+
/**
|
|
14
|
+
* True when `files` carries release evidence for its own change: every
|
|
15
|
+
* changed file is documentation, one of the changed files is itself a
|
|
16
|
+
* `.changeset/*.md` entry, or one of `exemptionTexts` (PR body, commit
|
|
17
|
+
* messages, ...) carries a `Changeset-exempt: <reason>` trailer.
|
|
18
|
+
*
|
|
19
|
+
* Mirrors the original `wp_pr_status` behavior exactly: an empty `files` list
|
|
20
|
+
* is NOT treated as evidence (fails closed) — callers that consider "no
|
|
21
|
+
* changed files" a legitimate, non-suspicious state (e.g. a freshly rebased
|
|
22
|
+
* local branch) must short-circuit before calling this function.
|
|
23
|
+
*/
|
|
24
|
+
export function hasChangesetEvidence(files, exemptionTexts) {
|
|
25
|
+
if (files.length > 0 && files.every((file) => file.endsWith(".md")))
|
|
26
|
+
return true;
|
|
27
|
+
if (files.some((file) => CHANGESET_FILE_PATTERN.test(file)))
|
|
28
|
+
return true;
|
|
29
|
+
return exemptionTexts.some((text) => CHANGESET_EXEMPT_PATTERN.test(text));
|
|
30
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { RepoAuditResult } from "./repo-guardrails.js";
|
|
2
|
+
export interface ChangesetRequiredOptions {
|
|
3
|
+
/** Base ref for the branch diff. Defaults to `origin/${GITHUB_BASE_REF:-main}`. */
|
|
4
|
+
readonly baseRef?: string;
|
|
5
|
+
/** Test/adapter seam for an already-resolved changed-file list. */
|
|
6
|
+
readonly changedFiles?: readonly string[];
|
|
7
|
+
/** Test/adapter seam for already-resolved commit messages. */
|
|
8
|
+
readonly commitMessages?: readonly string[];
|
|
9
|
+
}
|
|
10
|
+
export declare function auditChangesetRequired(rootDirectory?: string, options?: ChangesetRequiredOptions): RepoAuditResult;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { defaultBranchBaseRef, getBranchChangedFiles } from "#git/changed-files";
|
|
3
|
+
import { hasChangesetEvidence } from "./changeset-evidence.js";
|
|
4
|
+
/**
|
|
5
|
+
* `wp audit changeset-required` — the local, branch-scoped mirror of the
|
|
6
|
+
* `changeset-missing` blocker `wp_pr_status` computes against a live PR (see
|
|
7
|
+
* `#status/snapshot.js`). Both call the same `hasChangesetEvidence` predicate
|
|
8
|
+
* so a locally-green `wp ci-preflight` can never diverge from the landing
|
|
9
|
+
* verdict (`catalog/agent/rules/ci-cost-local-first.md`).
|
|
10
|
+
*
|
|
11
|
+
* Unlike the PR-based check (which reads `pr.files`/`pr.body` from the GitHub
|
|
12
|
+
* API), this audit resolves its own branch diff and scans local commit
|
|
13
|
+
* messages for a `Changeset-exempt: <reason>` trailer, so it works fully
|
|
14
|
+
* offline pre-push.
|
|
15
|
+
*/
|
|
16
|
+
const TITLE = "Changeset required";
|
|
17
|
+
const GIT_LOG_PROBE_OPTIONS = { timeout: 1_500, maxBuffer: 1024 * 1024 };
|
|
18
|
+
export function auditChangesetRequired(rootDirectory = process.cwd(), options = {}) {
|
|
19
|
+
const baseRef = options.baseRef?.trim() || defaultBranchBaseRef();
|
|
20
|
+
let changedFiles;
|
|
21
|
+
if (options.changedFiles) {
|
|
22
|
+
changedFiles = options.changedFiles;
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
const resolved = resolveChangedFiles(rootDirectory, baseRef);
|
|
26
|
+
if (resolved.error)
|
|
27
|
+
return fail(resolved.error, 0);
|
|
28
|
+
changedFiles = resolved.files;
|
|
29
|
+
}
|
|
30
|
+
if (changedFiles.length === 0) {
|
|
31
|
+
return passWithWarning(`[warn] changeset-required: no changed files vs ${baseRef} — skipping`, 0);
|
|
32
|
+
}
|
|
33
|
+
const exemptionMessages = options.commitMessages ?? readCommitMessages(rootDirectory, baseRef);
|
|
34
|
+
if (hasChangesetEvidence(changedFiles, exemptionMessages)) {
|
|
35
|
+
return pass(changedFiles.length);
|
|
36
|
+
}
|
|
37
|
+
return fail("release-visible changes require a .changeset/*.md entry (`wp run changeset`) " +
|
|
38
|
+
"or a `Changeset-exempt: <reason>` commit trailer", changedFiles.length);
|
|
39
|
+
}
|
|
40
|
+
function pass(checked) {
|
|
41
|
+
return { ok: true, title: TITLE, checked, violations: [] };
|
|
42
|
+
}
|
|
43
|
+
function passWithWarning(message, checked) {
|
|
44
|
+
return { ok: true, title: TITLE, checked, violations: [{ message }] };
|
|
45
|
+
}
|
|
46
|
+
function fail(message, checked) {
|
|
47
|
+
return { ok: false, title: TITLE, checked, violations: [{ message }] };
|
|
48
|
+
}
|
|
49
|
+
function resolveChangedFiles(cwd, baseRef) {
|
|
50
|
+
const result = getBranchChangedFiles(cwd, baseRef);
|
|
51
|
+
if (result.degraded) {
|
|
52
|
+
return {
|
|
53
|
+
files: [],
|
|
54
|
+
error: `unable to resolve changed files vs ${baseRef} (${result.reason}); ` +
|
|
55
|
+
"refusing to skip changeset enforcement",
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
return { files: result.files };
|
|
59
|
+
}
|
|
60
|
+
function readCommitMessages(cwd, baseRef) {
|
|
61
|
+
const result = spawnSync("git", ["log", "--format=%B%x00", `${baseRef}...HEAD`], {
|
|
62
|
+
cwd,
|
|
63
|
+
encoding: "utf8",
|
|
64
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
65
|
+
...GIT_LOG_PROBE_OPTIONS,
|
|
66
|
+
});
|
|
67
|
+
if (result.status !== 0 || result.error)
|
|
68
|
+
return [];
|
|
69
|
+
return result.stdout
|
|
70
|
+
.split("\0")
|
|
71
|
+
.map((entry) => entry.trim())
|
|
72
|
+
.filter(Boolean);
|
|
73
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared content-absence detection for the `wp audit guardrails` CI wiring
|
|
3
|
+
* contract. Both the audit (`ci-guardrails-wiring.ts`) and the self-healing
|
|
4
|
+
* injector (`scaffold-base-kit.ts`'s `ensureGuardrailsCiStep`) must agree on
|
|
5
|
+
* what counts as a "live" test-run step to anchor guardrails after — keeping
|
|
6
|
+
* that detection in one place is what keeps the two from silently diverging.
|
|
7
|
+
*
|
|
8
|
+
* Prior to this module, the audit matched the test-run pattern against whole
|
|
9
|
+
* file content (including inside `#`-commented lines) while the injector's
|
|
10
|
+
* anchor scan already skipped comment lines. A workflow whose only test-run
|
|
11
|
+
* mention was commented out therefore tripped the audit red with no anchor
|
|
12
|
+
* for the injector to repair — an unrepairable-red state (tech-debt h-016).
|
|
13
|
+
*/
|
|
14
|
+
/** True when `content` invokes `wp audit guardrails` anywhere (including
|
|
15
|
+
* inside comments — see the pattern's doc comment above). */
|
|
16
|
+
export declare function hasGuardrailsInvocation(content: string): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Scans `content` line by line, skipping commented-out lines, tracking the
|
|
19
|
+
* most recent step-start line (`- ...`), and returning the index of the step
|
|
20
|
+
* that owns the first non-commented line matching the live test-run pattern.
|
|
21
|
+
* Returns -1 when no such line exists.
|
|
22
|
+
*
|
|
23
|
+
* This is the single source of truth for "is there a live test-run step to
|
|
24
|
+
* anchor a guardrails step before" — used by the audit (as a boolean) and by
|
|
25
|
+
* the injector (as an insertion index).
|
|
26
|
+
*/
|
|
27
|
+
export declare function findLiveTestRunAnchor(content: string): number;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared content-absence detection for the `wp audit guardrails` CI wiring
|
|
3
|
+
* contract. Both the audit (`ci-guardrails-wiring.ts`) and the self-healing
|
|
4
|
+
* injector (`scaffold-base-kit.ts`'s `ensureGuardrailsCiStep`) must agree on
|
|
5
|
+
* what counts as a "live" test-run step to anchor guardrails after — keeping
|
|
6
|
+
* that detection in one place is what keeps the two from silently diverging.
|
|
7
|
+
*
|
|
8
|
+
* Prior to this module, the audit matched the test-run pattern against whole
|
|
9
|
+
* file content (including inside `#`-commented lines) while the injector's
|
|
10
|
+
* anchor scan already skipped comment lines. A workflow whose only test-run
|
|
11
|
+
* mention was commented out therefore tripped the audit red with no anchor
|
|
12
|
+
* for the injector to repair — an unrepairable-red state (tech-debt h-016).
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* The guardrails invocation, keyed on the command itself (not the `wp`
|
|
16
|
+
* prefix nor a marker comment) so `wp audit guardrails`,
|
|
17
|
+
* `./bin/wp audit guardrails`, and the `--affected`/`--full` variants all
|
|
18
|
+
* count as wired.
|
|
19
|
+
*
|
|
20
|
+
* NOTE: this intentionally matches inside comments too — it is a
|
|
21
|
+
* whole-content test, not line-scanned. A commented-out `wp audit
|
|
22
|
+
* guardrails` line therefore still counts as "wired" by both the audit and
|
|
23
|
+
* the injector. This is an inherent limitation of content-absence
|
|
24
|
+
* pattern-matching (neither side parses YAML as executable steps) rather
|
|
25
|
+
* than a bug unique to either detector — see tech-debt h-016. Do not "fix"
|
|
26
|
+
* this by line-scanning here without first solving real YAML-step parsing;
|
|
27
|
+
* a half-parsed heuristic would be worse than the documented limitation.
|
|
28
|
+
*/
|
|
29
|
+
const GUARDRAILS_INVOCATION_PATTERN = /\baudit guardrails\b/u;
|
|
30
|
+
/**
|
|
31
|
+
* Lines that run the default test suite — the signal that a workflow is a
|
|
32
|
+
* quality-CI pipeline that owes a guardrails step. Comment lines are
|
|
33
|
+
* excluded: a commented-out test-run mention is not a live test run.
|
|
34
|
+
*/
|
|
35
|
+
const TEST_RUN_PATTERN = /(?:\bvp\s+run\s+test\b|\bpnpm\s+(?:run\s+)?test\b|\bnpm\s+(?:run\s+)?test\b|\bbun\s+(?:run\s+)?test\b|\bwp\s+test\b|\bvitest\s+run\b)/iu;
|
|
36
|
+
function isCommentLine(line) {
|
|
37
|
+
return line.trim().startsWith("#");
|
|
38
|
+
}
|
|
39
|
+
/** True when `content` invokes `wp audit guardrails` anywhere (including
|
|
40
|
+
* inside comments — see the pattern's doc comment above). */
|
|
41
|
+
export function hasGuardrailsInvocation(content) {
|
|
42
|
+
return GUARDRAILS_INVOCATION_PATTERN.test(content);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Scans `content` line by line, skipping commented-out lines, tracking the
|
|
46
|
+
* most recent step-start line (`- ...`), and returning the index of the step
|
|
47
|
+
* that owns the first non-commented line matching the live test-run pattern.
|
|
48
|
+
* Returns -1 when no such line exists.
|
|
49
|
+
*
|
|
50
|
+
* This is the single source of truth for "is there a live test-run step to
|
|
51
|
+
* anchor a guardrails step before" — used by the audit (as a boolean) and by
|
|
52
|
+
* the injector (as an insertion index).
|
|
53
|
+
*/
|
|
54
|
+
export function findLiveTestRunAnchor(content) {
|
|
55
|
+
const lines = content.split("\n");
|
|
56
|
+
let lastStepStart = -1;
|
|
57
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
58
|
+
// Safe: index is bounded by the loop condition against lines.length.
|
|
59
|
+
const line = lines[index];
|
|
60
|
+
if (isCommentLine(line))
|
|
61
|
+
continue;
|
|
62
|
+
if (/^\s*-\s/u.test(line))
|
|
63
|
+
lastStepStart = index;
|
|
64
|
+
if (TEST_RUN_PATTERN.test(line))
|
|
65
|
+
return lastStepStart;
|
|
66
|
+
}
|
|
67
|
+
return -1;
|
|
68
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Guardrail: keep `wp audit guardrails` wired into a repo's own CI.
|
|
3
|
+
*
|
|
4
|
+
* `wp audit guardrails` is the delivery vehicle for every guardrail that ships
|
|
5
|
+
* inside the suite (e.g. `typescript-version` from #810). A consumer inherits a
|
|
6
|
+
* new guardrail automatically on their next agent-kit bump ONLY IF their CI
|
|
7
|
+
* still invokes `wp audit guardrails`. If that step is deleted, the whole suite
|
|
8
|
+
* silently stops running in CI and new guardrails never reach the consumer.
|
|
9
|
+
*
|
|
10
|
+
* This audit is dogfood / defense-in-depth — NOT the delivery guarantee. It
|
|
11
|
+
* cannot run if the very invariant it checks is already broken (a CI with no
|
|
12
|
+
* guardrails step never executes this audit in CI). The actual self-healing
|
|
13
|
+
* guarantee is the managed inject-by-content-absence repair in
|
|
14
|
+
* `scaffold-base-kit.ts` (`ensureGuardrailsCiStep`), which re-inserts the step
|
|
15
|
+
* on every `wp setup`. This audit's value is LOCAL: it is registered as
|
|
16
|
+
* affected-safe + MCP-visible, so `wp audit guardrails --affected` (pre-commit /
|
|
17
|
+
* IDE) catches the drift on the developer's machine even when CI has drifted.
|
|
18
|
+
*
|
|
19
|
+
* Modeled on the self-CI checks in `ci-test-perf.ts`.
|
|
20
|
+
*/
|
|
21
|
+
import type { RepoAuditResult } from "./repo-guardrails.js";
|
|
22
|
+
export declare function auditCiGuardrailsWiring(rootDirectory?: string): RepoAuditResult;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Guardrail: keep `wp audit guardrails` wired into a repo's own CI.
|
|
3
|
+
*
|
|
4
|
+
* `wp audit guardrails` is the delivery vehicle for every guardrail that ships
|
|
5
|
+
* inside the suite (e.g. `typescript-version` from #810). A consumer inherits a
|
|
6
|
+
* new guardrail automatically on their next agent-kit bump ONLY IF their CI
|
|
7
|
+
* still invokes `wp audit guardrails`. If that step is deleted, the whole suite
|
|
8
|
+
* silently stops running in CI and new guardrails never reach the consumer.
|
|
9
|
+
*
|
|
10
|
+
* This audit is dogfood / defense-in-depth — NOT the delivery guarantee. It
|
|
11
|
+
* cannot run if the very invariant it checks is already broken (a CI with no
|
|
12
|
+
* guardrails step never executes this audit in CI). The actual self-healing
|
|
13
|
+
* guarantee is the managed inject-by-content-absence repair in
|
|
14
|
+
* `scaffold-base-kit.ts` (`ensureGuardrailsCiStep`), which re-inserts the step
|
|
15
|
+
* on every `wp setup`. This audit's value is LOCAL: it is registered as
|
|
16
|
+
* affected-safe + MCP-visible, so `wp audit guardrails --affected` (pre-commit /
|
|
17
|
+
* IDE) catches the drift on the developer's machine even when CI has drifted.
|
|
18
|
+
*
|
|
19
|
+
* Modeled on the self-CI checks in `ci-test-perf.ts`.
|
|
20
|
+
*/
|
|
21
|
+
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
22
|
+
import { join, relative } from "node:path";
|
|
23
|
+
import { findLiveTestRunAnchor, hasGuardrailsInvocation } from "./ci-guardrails-detection.js";
|
|
24
|
+
const WORKFLOW_DIR = ".github/workflows";
|
|
25
|
+
const WORKFLOW_FILE_PATTERN = /\.(ya?ml)$/iu;
|
|
26
|
+
function walkWorkflowFiles(dir) {
|
|
27
|
+
if (!existsSync(dir))
|
|
28
|
+
return [];
|
|
29
|
+
const files = [];
|
|
30
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
31
|
+
const fullPath = join(dir, entry.name);
|
|
32
|
+
if (entry.isDirectory()) {
|
|
33
|
+
files.push(...walkWorkflowFiles(fullPath));
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
if (entry.isFile() && WORKFLOW_FILE_PATTERN.test(entry.name)) {
|
|
37
|
+
files.push(fullPath);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return files;
|
|
41
|
+
}
|
|
42
|
+
export function auditCiGuardrailsWiring(rootDirectory = process.cwd()) {
|
|
43
|
+
const workflowsRoot = join(rootDirectory, WORKFLOW_DIR);
|
|
44
|
+
const files = walkWorkflowFiles(workflowsRoot);
|
|
45
|
+
const testWorkflows = [];
|
|
46
|
+
let anyWorkflowWiresGuardrails = false;
|
|
47
|
+
for (const file of files) {
|
|
48
|
+
const content = readFileSync(file, "utf8");
|
|
49
|
+
if (hasGuardrailsInvocation(content))
|
|
50
|
+
anyWorkflowWiresGuardrails = true;
|
|
51
|
+
if (findLiveTestRunAnchor(content) !== -1) {
|
|
52
|
+
testWorkflows.push(relative(rootDirectory, file).replace(/\\/gu, "/"));
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// No quality-CI pipeline (no workflow runs the default test suite) → nothing
|
|
56
|
+
// to enforce; a repo without agent-kit CI is not "drifted".
|
|
57
|
+
const violations = [];
|
|
58
|
+
if (testWorkflows.length > 0 && !anyWorkflowWiresGuardrails) {
|
|
59
|
+
for (const workflow of testWorkflows) {
|
|
60
|
+
violations.push({
|
|
61
|
+
file: workflow,
|
|
62
|
+
severity: "error",
|
|
63
|
+
ruleId: "ci-guardrails-wiring",
|
|
64
|
+
message: `${workflow} runs the test suite but no workflow invokes \`wp audit guardrails\`. ` +
|
|
65
|
+
`Guardrails (e.g. typescript-version) reach this repo only while CI runs ` +
|
|
66
|
+
`\`wp audit guardrails\`. Re-run \`wp setup\` to restore the managed CI step.`,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
ok: violations.length === 0,
|
|
72
|
+
title: "CI guardrails wiring",
|
|
73
|
+
checked: files.length,
|
|
74
|
+
sources: files.map((file) => ({
|
|
75
|
+
path: relative(rootDirectory, file).replace(/\\/gu, "/"),
|
|
76
|
+
kind: "workflow",
|
|
77
|
+
})),
|
|
78
|
+
violations,
|
|
79
|
+
};
|
|
80
|
+
}
|