@webpresso/agent-kit 3.1.30 → 3.2.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.
Potentially problematic release.
This version of @webpresso/agent-kit might be problematic. Click here for more details.
- package/README.md +53 -15
- package/bin/_run.js +179 -19
- 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/ci-guardrails-wiring.d.ts +22 -0
- package/dist/esm/audit/ci-guardrails-wiring.js +90 -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 +8 -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 +43 -1
- 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 +65 -10
- 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.js +36 -11
- 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/trust/command-runner.d.ts +26 -0
- package/dist/esm/blueprint/trust/command-runner.js +148 -0
- 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 +62 -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 +10 -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 +34 -0
- package/dist/esm/cli/commands/agent-launch.d.ts +6 -1
- package/dist/esm/cli/commands/agent-launch.js +82 -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 +17 -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 +6 -3
- 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 +24 -0
- package/dist/esm/cli/commands/init/convergence-apply.js +221 -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 +1 -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/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 +11 -0
- package/dist/esm/cli/commands/init/scaffold-base-kit.js +56 -1
- 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 +51 -23
- 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 +7 -0
- package/dist/esm/cli/commands/init/scaffolders/file-transaction.js +58 -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-rotate.d.ts +49 -0
- package/dist/esm/cli/commands/opencode-rotate.js +135 -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/review.d.ts +35 -3
- package/dist/esm/cli/commands/review.js +729 -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/setup-help.js +1 -1
- package/dist/esm/cli/commands/setup-repair-help.js +1 -1
- package/dist/esm/cli/commands/worktree/router-dispatch.js +13 -8
- 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 +3 -3
- package/dist/esm/hooks/doctor.js +30 -15
- 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/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 +23 -8
- package/dist/esm/hooks/stop/qa-changed-files.d.ts +11 -0
- package/dist/esm/hooks/stop/qa-changed-files.js +74 -0
- package/dist/esm/mcp/blueprint/_shared/lifecycle.js +14 -1
- package/dist/esm/mcp/blueprint/handlers/document-mutations.js +78 -4
- 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 +4 -1
- package/dist/esm/mcp/tools/audit.js +30 -0
- package/dist/esm/mcp/tools/audits.d.ts +3 -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-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/review/authority.js +13 -5
- package/dist/esm/review/availability.d.ts +22 -0
- package/dist/esm/review/availability.js +107 -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.js +26 -9
- 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 +87 -6
- package/dist/esm/review/execution/supervisor.js +5 -0
- package/dist/esm/review/execution/types.d.ts +8 -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 +12 -0
- package/dist/esm/review/subject.js +74 -22
- package/dist/esm/runtime/opencode-account-materializer.d.ts +34 -0
- package/dist/esm/runtime/opencode-account-materializer.js +33 -0
- package/dist/esm/status/snapshot.js +30 -19
- 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 -1
- package/dist/esm/test-helpers/global-setup.js +68 -24
- 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/registry.d.ts +8 -0
- package/dist/esm/worktrees/registry.js +18 -3
- package/package.json +16 -13
|
@@ -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", "test-scan-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", "ci-guardrails-wiring", "test-scan-perf", "worktree-main-ownership", "sast", "harness-surfaces", "weakness-mining", "harness-overlay-evidence", "host-substitution-risk", "legacy-workflow-identity", "rules", "skills", "typescript-version"];
|
|
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" | "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"];
|
|
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-guardrails-wiring" | "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" | "host-substitution-risk" | "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" | "typescript-version" | "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", "host-substitution-risk", "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", "typescript-version", "ci-guardrails-wiring", "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", "ci-guardrails-wiring"];
|
|
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];
|
|
@@ -17,6 +17,7 @@ const affectedSafeKinds = [
|
|
|
17
17
|
"no-first-party-mjs",
|
|
18
18
|
"no-math-random",
|
|
19
19
|
"test-scan-perf",
|
|
20
|
+
"ci-guardrails-wiring",
|
|
20
21
|
];
|
|
21
22
|
const mcpKindIds = [
|
|
22
23
|
"test-smells",
|
|
@@ -44,6 +45,7 @@ const mcpKindIds = [
|
|
|
44
45
|
"harness-surfaces",
|
|
45
46
|
"weakness-mining",
|
|
46
47
|
"harness-overlay-evidence",
|
|
48
|
+
"host-substitution-risk",
|
|
47
49
|
"ai-contracts",
|
|
48
50
|
"atomic-state-writes",
|
|
49
51
|
"no-relative-package-scripts",
|
|
@@ -60,6 +62,8 @@ const mcpKindIds = [
|
|
|
60
62
|
"test-scan-perf",
|
|
61
63
|
"session-memory-hardcut",
|
|
62
64
|
"worktree-main-ownership",
|
|
65
|
+
"typescript-version",
|
|
66
|
+
"ci-guardrails-wiring",
|
|
63
67
|
"sast",
|
|
64
68
|
];
|
|
65
69
|
const affectedSafe = new Set(affectedSafeKinds);
|
|
@@ -132,15 +136,18 @@ const repoKinds = [
|
|
|
132
136
|
"secrets-config",
|
|
133
137
|
"consumer-agent-kit-dependency",
|
|
134
138
|
"ci-test-perf",
|
|
139
|
+
"ci-guardrails-wiring",
|
|
135
140
|
"test-scan-perf",
|
|
136
141
|
"worktree-main-ownership",
|
|
137
142
|
"sast",
|
|
138
143
|
"harness-surfaces",
|
|
139
144
|
"weakness-mining",
|
|
140
145
|
"harness-overlay-evidence",
|
|
146
|
+
"host-substitution-risk",
|
|
141
147
|
"legacy-workflow-identity",
|
|
142
148
|
"rules",
|
|
143
149
|
"skills",
|
|
150
|
+
"typescript-version",
|
|
144
151
|
];
|
|
145
152
|
function descriptor(id, kindClass) {
|
|
146
153
|
return {
|
|
@@ -151,7 +158,7 @@ function descriptor(id, kindClass) {
|
|
|
151
158
|
gateClass: signalKinds.has(id) ? "signal" : "blocking",
|
|
152
159
|
scopeSafety: affectedSafe.has(id) ? "affected-safe" : "full-scan-only",
|
|
153
160
|
summaryLabel: id === "sast" ? "SAST" : id,
|
|
154
|
-
...(
|
|
161
|
+
...(["sast", "host-substitution-risk"].includes(id) ? { includedInGuardrails: false } : {}),
|
|
155
162
|
...(["blueprint-readme-drift", "docs-frontmatter"].includes(id) ? { fixable: true } : {}),
|
|
156
163
|
};
|
|
157
164
|
}
|
|
@@ -133,10 +133,9 @@ function shouldSkipDirectory(name) {
|
|
|
133
133
|
".stryker-tmp",
|
|
134
134
|
".agent",
|
|
135
135
|
".agents",
|
|
136
|
-
".gemini",
|
|
137
|
-
".windsurf",
|
|
138
136
|
".runtime",
|
|
139
137
|
".codex",
|
|
138
|
+
".grok",
|
|
140
139
|
"_worktrees",
|
|
141
140
|
// Gitignored Claude Code agent surface — agent worktree scratch under
|
|
142
141
|
// .claude/worktrees/* carries vendored package manifests that are not the
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { parse as parseYaml } from "yaml";
|
|
4
|
+
/**
|
|
5
|
+
* `wp audit typescript-version` — enforce the native TypeScript compiler major
|
|
6
|
+
* at consumers.
|
|
7
|
+
*
|
|
8
|
+
* agent-kit migrated itself to TypeScript 7 (the native Go compiler) and pins
|
|
9
|
+
* `typescript: ^7.0.2` in every shipped template, but nothing checked that a
|
|
10
|
+
* downstream consumer stayed on 7 — so a consumer could silently regress to a
|
|
11
|
+
* legacy TypeScript major (observed: edge-matte pinned `typescript: ^6.0.3`).
|
|
12
|
+
* This audit ships through `wp audit guardrails`, so consumers inherit it
|
|
13
|
+
* automatically on their next `wp` bump with no CI edits.
|
|
14
|
+
*
|
|
15
|
+
* It gates only the `typescript` compiler dependency. `@typescript/typescript6`
|
|
16
|
+
* — the Microsoft-published TS6 compiler-API compatibility package that
|
|
17
|
+
* agent-kit itself depends on until TS 7.1 stabilizes the programmatic API — is
|
|
18
|
+
* a distinct dependency and is never flagged.
|
|
19
|
+
*/
|
|
20
|
+
const REQUIRED_MAJOR = 7;
|
|
21
|
+
const COMPILER_DEP = "typescript";
|
|
22
|
+
// Inline escape hatch for a documented, temporary exception (e.g. a consumer
|
|
23
|
+
// mid-migration). Placed as a comment in pnpm-workspace.yaml or in the manifest.
|
|
24
|
+
const OPT_OUT_MARKER = "typescript-version-allow";
|
|
25
|
+
/** Lowest major a version range can resolve to (the enforceable floor). */
|
|
26
|
+
function majorFloor(range) {
|
|
27
|
+
const match = /(\d+)/u.exec(range);
|
|
28
|
+
return match ? Number(match[1]) : undefined;
|
|
29
|
+
}
|
|
30
|
+
/** `catalog:` / `workspace:` pins resolve elsewhere; `*`/`latest` carry no floor. */
|
|
31
|
+
function isDelegatedPin(value) {
|
|
32
|
+
return (value.startsWith("catalog:") ||
|
|
33
|
+
value.startsWith("workspace:") ||
|
|
34
|
+
value === "*" ||
|
|
35
|
+
value === "latest");
|
|
36
|
+
}
|
|
37
|
+
function checkTypescriptPin(source, value, violations) {
|
|
38
|
+
if (isDelegatedPin(value))
|
|
39
|
+
return;
|
|
40
|
+
const major = majorFloor(value);
|
|
41
|
+
if (major === undefined || major >= REQUIRED_MAJOR)
|
|
42
|
+
return;
|
|
43
|
+
violations.push({
|
|
44
|
+
file: source,
|
|
45
|
+
severity: "error",
|
|
46
|
+
ruleId: "typescript-version",
|
|
47
|
+
message: `typescript is pinned to "${value}" (major ${major}); the native compiler ` +
|
|
48
|
+
`requires TypeScript ${REQUIRED_MAJOR}. Bump to ^${REQUIRED_MAJOR} (or a catalog: ref). ` +
|
|
49
|
+
`The @typescript/typescript6 compiler-API bridge is separate and allowed.`,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
function collectManifestPaths(rootDirectory) {
|
|
53
|
+
const paths = [];
|
|
54
|
+
if (existsSync(join(rootDirectory, "package.json")))
|
|
55
|
+
paths.push("package.json");
|
|
56
|
+
for (const group of ["packages", "apps"]) {
|
|
57
|
+
const dir = join(rootDirectory, group);
|
|
58
|
+
if (!existsSync(dir))
|
|
59
|
+
continue;
|
|
60
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
61
|
+
if (!entry.isDirectory())
|
|
62
|
+
continue;
|
|
63
|
+
const manifest = join(group, entry.name, "package.json");
|
|
64
|
+
if (existsSync(join(rootDirectory, manifest)))
|
|
65
|
+
paths.push(manifest);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return paths;
|
|
69
|
+
}
|
|
70
|
+
export function auditTypescriptVersion(rootDirectory) {
|
|
71
|
+
const violations = [];
|
|
72
|
+
const sources = [];
|
|
73
|
+
let checked = 0;
|
|
74
|
+
// 1. Workspace catalog pin — the primary contract; catches catalog-level drift.
|
|
75
|
+
const workspacePath = join(rootDirectory, "pnpm-workspace.yaml");
|
|
76
|
+
if (existsSync(workspacePath)) {
|
|
77
|
+
const raw = readFileSync(workspacePath, "utf8");
|
|
78
|
+
sources.push({ path: "pnpm-workspace.yaml", kind: "pnpm-workspace" });
|
|
79
|
+
if (!raw.includes(OPT_OUT_MARKER)) {
|
|
80
|
+
const workspace = (parseYaml(raw) ?? {});
|
|
81
|
+
const catalogPin = workspace.catalog?.[COMPILER_DEP];
|
|
82
|
+
if (typeof catalogPin === "string") {
|
|
83
|
+
checked += 1;
|
|
84
|
+
checkTypescriptPin("pnpm-workspace.yaml (catalog.typescript)", catalogPin, violations);
|
|
85
|
+
}
|
|
86
|
+
// pnpm also supports NAMED catalogs (`catalogs.<name>.typescript`), a
|
|
87
|
+
// separate map from the default `catalog` above. A pin placed there
|
|
88
|
+
// resolves the same way for consumers using `catalog:<name>` refs, so it
|
|
89
|
+
// must be enforced identically or a consumer can dodge the check by
|
|
90
|
+
// moving the pin into a named catalog.
|
|
91
|
+
for (const [catalogName, entries] of Object.entries(workspace.catalogs ?? {})) {
|
|
92
|
+
const namedPin = entries[COMPILER_DEP];
|
|
93
|
+
if (typeof namedPin !== "string")
|
|
94
|
+
continue;
|
|
95
|
+
checked += 1;
|
|
96
|
+
checkTypescriptPin(`pnpm-workspace.yaml (catalogs.${catalogName}.typescript)`, namedPin, violations);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// 2. Direct typescript pins in package manifests (catalog:/workspace: skipped —
|
|
101
|
+
// they resolve through the catalog checked above).
|
|
102
|
+
for (const manifest of collectManifestPaths(rootDirectory)) {
|
|
103
|
+
const raw = readFileSync(join(rootDirectory, manifest), "utf8");
|
|
104
|
+
sources.push({ path: manifest, kind: "package-manifest" });
|
|
105
|
+
if (raw.includes(OPT_OUT_MARKER))
|
|
106
|
+
continue;
|
|
107
|
+
const parsed = JSON.parse(raw);
|
|
108
|
+
for (const field of ["dependencies", "devDependencies"]) {
|
|
109
|
+
const pin = parsed[field]?.[COMPILER_DEP];
|
|
110
|
+
if (typeof pin === "string") {
|
|
111
|
+
checked += 1;
|
|
112
|
+
checkTypescriptPin(`${manifest} (${field}.typescript)`, pin, violations);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return {
|
|
117
|
+
ok: violations.length === 0,
|
|
118
|
+
title: `TypeScript ${REQUIRED_MAJOR} consumer pin`,
|
|
119
|
+
checked,
|
|
120
|
+
sources,
|
|
121
|
+
violations,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
@@ -74,14 +74,56 @@ export function serializeBlueprint(blueprint) {
|
|
|
74
74
|
delete data.completed_at;
|
|
75
75
|
}
|
|
76
76
|
const { tasks: _tasks, task_statuses: _task_statuses, ...cleanedData } = data;
|
|
77
|
-
|
|
77
|
+
const serialized = quoteGeneratedProgressScalar(matter.stringify(content, cleanedData), blueprint.progress);
|
|
78
|
+
return preserveOriginalFrontmatterScalars(serialized, blueprint.raw);
|
|
78
79
|
}
|
|
80
|
+
// Frontmatter keys serializeBlueprint intentionally rewrites; every other
|
|
81
|
+
// scalar must survive a write byte-for-byte.
|
|
82
|
+
const SERIALIZE_OWNED_FRONTMATTER_KEYS = new Set([
|
|
83
|
+
"status",
|
|
84
|
+
"complexity",
|
|
85
|
+
"last_updated",
|
|
86
|
+
"progress",
|
|
87
|
+
"completed_at",
|
|
88
|
+
]);
|
|
89
|
+
const FRONTMATTER_SCALAR_LINE = /^([A-Za-z0-9_]+):[ \t]+\S.*$/;
|
|
79
90
|
function quoteGeneratedProgressScalar(serialized, progress) {
|
|
80
91
|
if (!progress)
|
|
81
92
|
return serialized;
|
|
82
93
|
const escaped = progress.replace(/'/g, "''");
|
|
83
94
|
return serialized.replace(/^progress: .+$/m, `progress: '${escaped}'`);
|
|
84
95
|
}
|
|
96
|
+
// `matter.stringify` re-dumps the whole frontmatter YAML, which canonicalizes
|
|
97
|
+
// scalar quoting (e.g. `title: "X"` -> `title: X`, `owner: "claude"` ->
|
|
98
|
+
// `owner: claude`). Those bytes feed the plan-subject digest, so a cosmetic
|
|
99
|
+
// re-quote on a write silently invalidates a recorded review approval. Restore
|
|
100
|
+
// the ORIGINAL single-line scalar verbatim for every field this serializer does
|
|
101
|
+
// not own, so a status/progress write never disturbs `title`/`owner`/`created`
|
|
102
|
+
// etc. Multi-line and array fields (no inline value) are left to js-yaml, which
|
|
103
|
+
// dumps them deterministically. Generalizes the earlier progress-only patch.
|
|
104
|
+
function preserveOriginalFrontmatterScalars(serialized, originalRaw) {
|
|
105
|
+
const originalFrontmatter = /^---\r?\n([\s\S]*?)\r?\n---/.exec(originalRaw)?.[1] ?? "";
|
|
106
|
+
const originalScalars = new Map();
|
|
107
|
+
for (const line of originalFrontmatter.split("\n")) {
|
|
108
|
+
const match = FRONTMATTER_SCALAR_LINE.exec(line);
|
|
109
|
+
if (match && !SERIALIZE_OWNED_FRONTMATTER_KEYS.has(match[1])) {
|
|
110
|
+
originalScalars.set(match[1], line);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (originalScalars.size === 0)
|
|
114
|
+
return serialized;
|
|
115
|
+
const block = /^(---\r?\n)([\s\S]*?)(\r?\n---(?:\r?\n|$))/.exec(serialized);
|
|
116
|
+
if (!block)
|
|
117
|
+
return serialized;
|
|
118
|
+
const rebuilt = block[2]
|
|
119
|
+
.split("\n")
|
|
120
|
+
.map((line) => {
|
|
121
|
+
const match = FRONTMATTER_SCALAR_LINE.exec(line);
|
|
122
|
+
return (match && originalScalars.get(match[1])) || line;
|
|
123
|
+
})
|
|
124
|
+
.join("\n");
|
|
125
|
+
return `${serialized.slice(0, block.index)}${block[1]}${rebuilt}${block[3]}${serialized.slice(block.index + block[0].length)}`;
|
|
126
|
+
}
|
|
85
127
|
function extractCheckboxStatus(section) {
|
|
86
128
|
const checkboxRegex = /^- \[([ x])\]/gm;
|
|
87
129
|
const matches = Array.from(section.matchAll(checkboxRegex));
|
|
@@ -3,3 +3,17 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import type { CriteriaResult } from "#core/types";
|
|
5
5
|
export declare function checkAcceptanceCriteria(markdown: string): CriteriaResult;
|
|
6
|
+
/**
|
|
7
|
+
* Count checked acceptance-criteria checkboxes that are NOT explained by an
|
|
8
|
+
* explicit `**Status:** done`/`dropped` line on their containing task.
|
|
9
|
+
*
|
|
10
|
+
* `completeTask()` (see `markdown/helpers.ts`) checks every checkbox inside
|
|
11
|
+
* a task's own section when that task is marked done — which is expected,
|
|
12
|
+
* honest progress tracking during `draft` (pretool-guard's `blueprint-first`
|
|
13
|
+
* validator explicitly allows implementing source while a blueprint is
|
|
14
|
+
* still draft). A checked box that is NOT inside a done/dropped task section
|
|
15
|
+
* — e.g. the top-level `## Approvals` checklist, or a task still `todo` with
|
|
16
|
+
* pre-checked boxes — still signals an unreviewed completion claim and must
|
|
17
|
+
* keep failing the draft anti-masquerade guard.
|
|
18
|
+
*/
|
|
19
|
+
export declare function countUnexplainedCheckedCriteria(markdown: string): number;
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Check acceptance criteria completion in plan markdown.
|
|
3
3
|
*/
|
|
4
|
+
const CHECKBOX_REGEX = /^- \[([ x])\]/gm;
|
|
5
|
+
const TASK_HEADER_REGEX = /^####\s+(?:\[[^\]]+\]\s+)?Task\s+\d+(?:\.\d+)+:.*$/gm;
|
|
6
|
+
const TASK_DONE_OR_DROPPED_REGEX = /^\*\*Status:\*\*\s*(done|dropped)\s*$/im;
|
|
7
|
+
const SECTION_BOUNDARY_REGEX = /\n##\s|\n---\n/;
|
|
4
8
|
export function checkAcceptanceCriteria(markdown) {
|
|
5
|
-
const
|
|
6
|
-
const matches = Array.from(markdown.matchAll(criteriaRegex));
|
|
9
|
+
const matches = Array.from(markdown.matchAll(CHECKBOX_REGEX));
|
|
7
10
|
const total = matches.length;
|
|
8
11
|
const checked = matches.filter((m) => m[1] === "x").length;
|
|
9
12
|
return {
|
|
@@ -12,3 +15,39 @@ export function checkAcceptanceCriteria(markdown) {
|
|
|
12
15
|
allChecked: total === 0 || checked === total,
|
|
13
16
|
};
|
|
14
17
|
}
|
|
18
|
+
function countChecked(text) {
|
|
19
|
+
return Array.from(text.matchAll(CHECKBOX_REGEX)).filter((m) => m[1] === "x").length;
|
|
20
|
+
}
|
|
21
|
+
function taskSectionEnd(markdown, start, nextTaskStart) {
|
|
22
|
+
const rest = markdown.slice(start);
|
|
23
|
+
const boundaryMatch = rest.match(SECTION_BOUNDARY_REGEX);
|
|
24
|
+
const boundaryIndex = boundaryMatch?.index === undefined ? markdown.length : start + boundaryMatch.index + 1;
|
|
25
|
+
return Math.min(nextTaskStart, boundaryIndex);
|
|
26
|
+
}
|
|
27
|
+
function sumCheckedInDoneOrDroppedTasks(markdown) {
|
|
28
|
+
const headers = Array.from(markdown.matchAll(TASK_HEADER_REGEX));
|
|
29
|
+
return headers.reduce((sum, header, index) => {
|
|
30
|
+
const start = header.index ?? 0;
|
|
31
|
+
const nextStart = headers[index + 1]?.index ?? markdown.length;
|
|
32
|
+
const section = markdown.slice(start, taskSectionEnd(markdown, start, nextStart));
|
|
33
|
+
return TASK_DONE_OR_DROPPED_REGEX.test(section) ? sum + countChecked(section) : sum;
|
|
34
|
+
}, 0);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Count checked acceptance-criteria checkboxes that are NOT explained by an
|
|
38
|
+
* explicit `**Status:** done`/`dropped` line on their containing task.
|
|
39
|
+
*
|
|
40
|
+
* `completeTask()` (see `markdown/helpers.ts`) checks every checkbox inside
|
|
41
|
+
* a task's own section when that task is marked done — which is expected,
|
|
42
|
+
* honest progress tracking during `draft` (pretool-guard's `blueprint-first`
|
|
43
|
+
* validator explicitly allows implementing source while a blueprint is
|
|
44
|
+
* still draft). A checked box that is NOT inside a done/dropped task section
|
|
45
|
+
* — e.g. the top-level `## Approvals` checklist, or a task still `todo` with
|
|
46
|
+
* pre-checked boxes — still signals an unreviewed completion claim and must
|
|
47
|
+
* keep failing the draft anti-masquerade guard.
|
|
48
|
+
*/
|
|
49
|
+
export function countUnexplainedCheckedCriteria(markdown) {
|
|
50
|
+
const { checked } = checkAcceptanceCriteria(markdown);
|
|
51
|
+
const explained = sumCheckedInDoneOrDroppedTasks(markdown);
|
|
52
|
+
return Math.max(0, checked - explained);
|
|
53
|
+
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Validate plan state consistency with folder structure and checkbox status.
|
|
3
3
|
*/
|
|
4
4
|
import { planStatusSchema } from "#core/schema";
|
|
5
|
-
import { checkAcceptanceCriteria } from "./criteria.js";
|
|
5
|
+
import { checkAcceptanceCriteria, countUnexplainedCheckedCriteria } from "./criteria.js";
|
|
6
6
|
/**
|
|
7
7
|
* Validate plan is in completed/ folder if status is completed.
|
|
8
8
|
*/
|
|
@@ -22,7 +22,9 @@ function validateCompletedFolder(status, isInCompleted, criteria) {
|
|
|
22
22
|
if (status === "completed" && !criteria.allChecked) {
|
|
23
23
|
return {
|
|
24
24
|
valid: false,
|
|
25
|
-
error: `Plan has status: completed but only ${criteria.checked}/${criteria.total} acceptance
|
|
25
|
+
error: `Plan has status: completed but only ${criteria.checked}/${criteria.total} acceptance ` +
|
|
26
|
+
`criteria are checked. Check the remaining boxes once that work is genuinely done, or ` +
|
|
27
|
+
`set status back to in-progress until it is.`,
|
|
26
28
|
};
|
|
27
29
|
}
|
|
28
30
|
return { valid: true };
|
|
@@ -30,7 +32,7 @@ function validateCompletedFolder(status, isInCompleted, criteria) {
|
|
|
30
32
|
/**
|
|
31
33
|
* Validate plan is in draft/ folder if status is draft.
|
|
32
34
|
*/
|
|
33
|
-
function validateDraftFolder(status, isInDraft,
|
|
35
|
+
function validateDraftFolder(status, isInDraft, markdown) {
|
|
34
36
|
if (status === "draft" && !isInDraft) {
|
|
35
37
|
return {
|
|
36
38
|
valid: false,
|
|
@@ -43,11 +45,20 @@ function validateDraftFolder(status, isInDraft, criteria) {
|
|
|
43
45
|
error: `Plan is in draft/ folder but has status: ${status} (expected status: draft)`,
|
|
44
46
|
};
|
|
45
47
|
}
|
|
46
|
-
if (status === "draft"
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
if (status === "draft") {
|
|
49
|
+
const unexplained = countUnexplainedCheckedCriteria(markdown);
|
|
50
|
+
if (unexplained > 0) {
|
|
51
|
+
return {
|
|
52
|
+
valid: false,
|
|
53
|
+
error: `Plan has status: draft but ${unexplained} acceptance criteria are checked outside ` +
|
|
54
|
+
`a task marked **Status:** done/dropped (expected 0). Marking a task's own status ` +
|
|
55
|
+
`done/dropped legitimately checks its boxes during draft implementation; a checked ` +
|
|
56
|
+
`box elsewhere (e.g. the Approvals checklist, or a still-todo task) claims unreviewed ` +
|
|
57
|
+
`completion. Fix by either setting the owning task's **Status:** to done/dropped if ` +
|
|
58
|
+
`the work is genuinely finished, unchecking the box if it isn't, or promoting the ` +
|
|
59
|
+
`blueprint with \`wp blueprint promote <slug>\` once it has been reviewed.`,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
51
62
|
}
|
|
52
63
|
return { valid: true };
|
|
53
64
|
}
|
|
@@ -72,11 +83,11 @@ function validateParkedFolder(status, isInParked) {
|
|
|
72
83
|
/**
|
|
73
84
|
* Validate state without path information.
|
|
74
85
|
*/
|
|
75
|
-
function validateStateOnly(status, criteria) {
|
|
86
|
+
function validateStateOnly(status, markdown, criteria) {
|
|
76
87
|
if (status === "completed" && !criteria.allChecked) {
|
|
77
88
|
return { valid: false, error: "Plan completed but criteria not met" };
|
|
78
89
|
}
|
|
79
|
-
if (status === "draft" &&
|
|
90
|
+
if (status === "draft" && countUnexplainedCheckedCriteria(markdown) > 0) {
|
|
80
91
|
return { valid: false, error: "Plan draft but criteria checked" };
|
|
81
92
|
}
|
|
82
93
|
return { valid: true };
|
|
@@ -84,14 +95,14 @@ function validateStateOnly(status, criteria) {
|
|
|
84
95
|
/**
|
|
85
96
|
* Validate state with path information.
|
|
86
97
|
*/
|
|
87
|
-
function validateStateWithPath(status, filePath, criteria) {
|
|
98
|
+
function validateStateWithPath(status, filePath, markdown, criteria) {
|
|
88
99
|
const isInCompleted = filePath.includes("/completed/") || filePath.includes("completed/");
|
|
89
100
|
const isInDraft = filePath.includes("/draft/") || filePath.includes("draft/");
|
|
90
101
|
const isInParked = filePath.includes("/parked/") || filePath.includes("parked/");
|
|
91
102
|
const completedResult = validateCompletedFolder(status, isInCompleted, criteria);
|
|
92
103
|
if (!completedResult.valid)
|
|
93
104
|
return completedResult;
|
|
94
|
-
const draftResult = validateDraftFolder(status, isInDraft,
|
|
105
|
+
const draftResult = validateDraftFolder(status, isInDraft, markdown);
|
|
95
106
|
if (!draftResult.valid)
|
|
96
107
|
return draftResult;
|
|
97
108
|
const parkedResult = validateParkedFolder(status, isInParked);
|
|
@@ -116,7 +127,7 @@ export function validatePlanState(markdown, filePath) {
|
|
|
116
127
|
}
|
|
117
128
|
const criteria = checkAcceptanceCriteria(markdown);
|
|
118
129
|
if (!filePath) {
|
|
119
|
-
return validateStateOnly(status, criteria);
|
|
130
|
+
return validateStateOnly(status, markdown, criteria);
|
|
120
131
|
}
|
|
121
|
-
return validateStateWithPath(status, filePath, criteria);
|
|
132
|
+
return validateStateWithPath(status, filePath, markdown, criteria);
|
|
122
133
|
}
|
|
@@ -4,8 +4,10 @@ import { findPackageAsset } from "#utils/package-assets.js";
|
|
|
4
4
|
const PACKAGED_MIGRATIONS_RELATIVE_DIR = "dist/esm/blueprint/db/migrations";
|
|
5
5
|
const SOURCE_MIGRATIONS_RELATIVE_DIR = "src/blueprint/db/migrations";
|
|
6
6
|
function resolveMigrationsDir() {
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
// Source checkouts may rebuild dist concurrently in another test project.
|
|
8
|
+
// Prefer immutable authoring SQL there; published packages fall back to dist.
|
|
9
|
+
const found = findPackageAsset(SOURCE_MIGRATIONS_RELATIVE_DIR) ??
|
|
10
|
+
findPackageAsset(PACKAGED_MIGRATIONS_RELATIVE_DIR);
|
|
9
11
|
if (found)
|
|
10
12
|
return found;
|
|
11
13
|
throw new Error(`Missing blueprint migrations at ${PACKAGED_MIGRATIONS_RELATIVE_DIR} or ${SOURCE_MIGRATIONS_RELATIVE_DIR}. ` +
|
|
@@ -43,6 +43,7 @@ export declare function countDistinctLogBackedApprovals(file: string, approvals:
|
|
|
43
43
|
export interface ApprovalGateEvaluation {
|
|
44
44
|
distinct: number;
|
|
45
45
|
openCodeGoModelReviewers: string[];
|
|
46
|
+
overrideReviewers: string[];
|
|
46
47
|
satisfied: boolean;
|
|
47
48
|
strongReviewers: string[];
|
|
48
49
|
}
|
|
@@ -59,5 +60,24 @@ export declare function validateApprovalGate(file: string, frontmatter: Lifecycl
|
|
|
59
60
|
completedApprovalGate?: boolean;
|
|
60
61
|
inProgressApprovalGate?: boolean;
|
|
61
62
|
}): BlueprintAuditIssue[];
|
|
63
|
+
/**
|
|
64
|
+
* Write-path mirror of {@link validateApprovalGate}'s `completed` branch.
|
|
65
|
+
*
|
|
66
|
+
* There is no single completion chokepoint: this helper is called from all
|
|
67
|
+
* three write-path owners that can set `status: completed` — the MCP transition
|
|
68
|
+
* funnel `applyLocalBlueprintTransition` (finalize/promote/transition), the CLI
|
|
69
|
+
* `promoteBlueprintLocked` (which does not traverse that funnel), and the
|
|
70
|
+
* execution engine's `finalize` intent via `applyBlueprintLifecycleToFile`
|
|
71
|
+
* (exec/resume/stop/move). Calling it before each write guarantees a blueprint
|
|
72
|
+
* can only reach `completed` when it carries the same plan-purpose approval CI's
|
|
73
|
+
* `wp audit blueprint-lifecycle` requires afterward. It delegates to
|
|
74
|
+
* {@link validateApprovalGate} verbatim — including the pre-rollout grandfather
|
|
75
|
+
* ({@link predatesCompletedApprovalGate}) and the v2 `review-events.jsonl`
|
|
76
|
+
* ledger — so the write-path and the audit-path can never diverge and a
|
|
77
|
+
* finalized blueprint can never fail its own lifecycle audit.
|
|
78
|
+
*
|
|
79
|
+
* @throws Error when no plan-purpose approval satisfies the promotion reviewer mix.
|
|
80
|
+
*/
|
|
81
|
+
export declare function assertCompletedApprovalGate(file: string, markdown: string): void;
|
|
62
82
|
export declare function runBlueprintAudit(options: RunBlueprintAuditOptions): Promise<BlueprintAuditResult>;
|
|
63
83
|
export {};
|
|
@@ -13,6 +13,7 @@ import { resolveBlueprintRoot } from "#utils/blueprint-root";
|
|
|
13
13
|
import { parseBlueprintDocumentRelativePath } from "#utils/document-paths.js";
|
|
14
14
|
import { relativeBlueprintSlug } from "#lifecycle/local.js";
|
|
15
15
|
import { collectV2ReviewProvenance, collectTrackedReviewProvenance, normalizeTrackedReviewApprovals, } from "#lifecycle/review-provenance.js";
|
|
16
|
+
import { MAINTAINER_OVERRIDE_REVIEWER } from "#review/events.js";
|
|
16
17
|
import { isPolicyApprovalVerdict } from "#review/verdict.js";
|
|
17
18
|
function isBlueprintOverview(file) {
|
|
18
19
|
const normalized = file.replace(/\\/g, "/");
|
|
@@ -94,11 +95,13 @@ function approvalReviewerKey(reviewer) {
|
|
|
94
95
|
return parts.at(-1) ?? normalized;
|
|
95
96
|
}
|
|
96
97
|
function provenanceBackedApprovalReviewers(file, approvals, options = {}) {
|
|
97
|
-
const
|
|
98
|
-
|
|
98
|
+
const purposes = options.purpose
|
|
99
|
+
? [options.purpose]
|
|
100
|
+
: ["plan", "delivery"];
|
|
101
|
+
const legacy = purposes.includes("plan")
|
|
99
102
|
? collectTrackedReviewProvenance(file, approvals, options).map((record) => record.reviewer)
|
|
100
103
|
: [];
|
|
101
|
-
const current = collectV2ReviewProvenance(file, purpose, "HEAD", options.issuesSink).map((record) => record.reviewer);
|
|
104
|
+
const current = purposes.flatMap((purpose) => collectV2ReviewProvenance(file, purpose, "HEAD", options.issuesSink).map((record) => record.reviewer));
|
|
102
105
|
return new Set([...legacy, ...current]);
|
|
103
106
|
}
|
|
104
107
|
export function countDistinctProvenanceBackedApprovals(file, approvals) {
|
|
@@ -116,15 +119,24 @@ export function evaluateApprovalReviewers(reviewerIds) {
|
|
|
116
119
|
const openCodeGoModelReviewers = reviewers
|
|
117
120
|
.map(approvalReviewerKey)
|
|
118
121
|
.filter((reviewer) => OPENCODE_GO_MODEL_APPROVAL_REVIEWERS.has(reviewer));
|
|
122
|
+
const overrideReviewers = reviewers
|
|
123
|
+
.map(approvalReviewerKey)
|
|
124
|
+
.filter((reviewer) => reviewer === MAINTAINER_OVERRIDE_REVIEWER);
|
|
119
125
|
return {
|
|
120
126
|
distinct: reviewers.length,
|
|
121
127
|
openCodeGoModelReviewers: [...new Set(openCodeGoModelReviewers)].sort(),
|
|
122
|
-
|
|
128
|
+
overrideReviewers: [...new Set(overrideReviewers)].sort(),
|
|
129
|
+
satisfied: strongReviewers.length >= 1 ||
|
|
130
|
+
new Set(openCodeGoModelReviewers).size >= 1 ||
|
|
131
|
+
new Set(overrideReviewers).size >= 1,
|
|
123
132
|
strongReviewers: [...new Set(strongReviewers)].sort(),
|
|
124
133
|
};
|
|
125
134
|
}
|
|
126
135
|
export function formatApprovalGateRequirement(evaluation) {
|
|
127
|
-
|
|
136
|
+
const override = evaluation.overrideReviewers.length >= 1
|
|
137
|
+
? ` + ${evaluation.overrideReviewers.length} maintainer-override(s)`
|
|
138
|
+
: "";
|
|
139
|
+
return `need 1 provenance-backed Claude/Codex/Grok or OpenCode-Go model approval (or a recorded maintainer-override); currently have ${evaluation.strongReviewers.length} Claude/Codex/Grok approval(s), ${evaluation.openCodeGoModelReviewers.length} OpenCode-Go model approval(s)${override}, ${evaluation.distinct} distinct provenance-backed approval(s) total`;
|
|
128
140
|
}
|
|
129
141
|
export function validateApprovalGate(file, frontmatter, options = {}) {
|
|
130
142
|
if (frontmatter.type !== "blueprint")
|
|
@@ -162,6 +174,31 @@ export function validateApprovalGate(file, frontmatter, options = {}) {
|
|
|
162
174
|
message: `Review evidence integrity: ${issue}`,
|
|
163
175
|
}));
|
|
164
176
|
}
|
|
177
|
+
/**
|
|
178
|
+
* Write-path mirror of {@link validateApprovalGate}'s `completed` branch.
|
|
179
|
+
*
|
|
180
|
+
* There is no single completion chokepoint: this helper is called from all
|
|
181
|
+
* three write-path owners that can set `status: completed` — the MCP transition
|
|
182
|
+
* funnel `applyLocalBlueprintTransition` (finalize/promote/transition), the CLI
|
|
183
|
+
* `promoteBlueprintLocked` (which does not traverse that funnel), and the
|
|
184
|
+
* execution engine's `finalize` intent via `applyBlueprintLifecycleToFile`
|
|
185
|
+
* (exec/resume/stop/move). Calling it before each write guarantees a blueprint
|
|
186
|
+
* can only reach `completed` when it carries the same plan-purpose approval CI's
|
|
187
|
+
* `wp audit blueprint-lifecycle` requires afterward. It delegates to
|
|
188
|
+
* {@link validateApprovalGate} verbatim — including the pre-rollout grandfather
|
|
189
|
+
* ({@link predatesCompletedApprovalGate}) and the v2 `review-events.jsonl`
|
|
190
|
+
* ledger — so the write-path and the audit-path can never diverge and a
|
|
191
|
+
* finalized blueprint can never fail its own lifecycle audit.
|
|
192
|
+
*
|
|
193
|
+
* @throws Error when no plan-purpose approval satisfies the promotion reviewer mix.
|
|
194
|
+
*/
|
|
195
|
+
export function assertCompletedApprovalGate(file, markdown) {
|
|
196
|
+
const frontmatter = readLifecycleAuditFrontmatter(markdown);
|
|
197
|
+
const errors = validateApprovalGate(file, { ...frontmatter, status: "completed" }, { completedApprovalGate: true }).filter((issue) => issue.level === "error");
|
|
198
|
+
if (errors.length > 0) {
|
|
199
|
+
throw new Error(errors.map((issue) => issue.message).join("; "));
|
|
200
|
+
}
|
|
201
|
+
}
|
|
165
202
|
function countTaskHeadings(raw) {
|
|
166
203
|
return raw.match(/^####\s+(?:\[[^\]]+\]\s+)?Task\s+/gm)?.length ?? 0;
|
|
167
204
|
}
|
|
@@ -226,7 +263,13 @@ function validateTaskState(blueprint) {
|
|
|
226
263
|
}
|
|
227
264
|
return issues;
|
|
228
265
|
}
|
|
229
|
-
function validateBlueprintSlugUniqueness(blueprints
|
|
266
|
+
function validateBlueprintSlugUniqueness(blueprints,
|
|
267
|
+
// When auditing a changed-only subset, `stagedSet` restricts reporting to
|
|
268
|
+
// duplicates a CHANGED blueprint participates in, and attributes the error to
|
|
269
|
+
// that changed file so a scope-filtering caller still surfaces it. A
|
|
270
|
+
// pre-existing duplicate among only-untouched blueprints is left for the
|
|
271
|
+
// full-corpus (CI) pass, not the changed-only hook.
|
|
272
|
+
scope) {
|
|
230
273
|
const grouped = new Map();
|
|
231
274
|
for (const blueprint of blueprints) {
|
|
232
275
|
const normalizedSlug = relativeBlueprintSlug(blueprint.slug);
|
|
@@ -239,12 +282,18 @@ function validateBlueprintSlugUniqueness(blueprints) {
|
|
|
239
282
|
}
|
|
240
283
|
}
|
|
241
284
|
const issues = [];
|
|
285
|
+
const stagedSet = scope?.stagedSet ?? null;
|
|
242
286
|
for (const [normalizedSlug, entries] of grouped) {
|
|
243
287
|
const uniqueLifecycleSlugs = new Set(entries.map((entry) => entry.slug));
|
|
244
288
|
if (uniqueLifecycleSlugs.size <= 1)
|
|
245
289
|
continue;
|
|
290
|
+
const changedEntry = stagedSet
|
|
291
|
+
? entries.find((entry) => stagedSet.has(normalizePath(path.relative(scope.projectRoot, entry.file))))
|
|
292
|
+
: undefined;
|
|
293
|
+
if (stagedSet && !changedEntry)
|
|
294
|
+
continue;
|
|
246
295
|
issues.push({
|
|
247
|
-
file: entries[0]?.file,
|
|
296
|
+
file: (changedEntry ?? entries[0])?.file,
|
|
248
297
|
level: "error",
|
|
249
298
|
message: `Blueprint slug "${normalizedSlug}" appears in multiple lifecycle locations: ${Array.from(uniqueLifecycleSlugs)
|
|
250
299
|
.toSorted()
|
|
@@ -620,10 +669,16 @@ export async function runBlueprintAudit(options) {
|
|
|
620
669
|
baseDir: resolveBlueprintRoot(options.projectRoot),
|
|
621
670
|
includeSpecialFolders: true,
|
|
622
671
|
});
|
|
623
|
-
const
|
|
672
|
+
const stagedSet = options.all || !options.stagedFiles ? null : new Set(options.stagedFiles.map(normalizePath));
|
|
673
|
+
const blueprintFiles = stagedSet === null
|
|
624
674
|
? scanned
|
|
625
|
-
: scanned.filter((entry) =>
|
|
626
|
-
|
|
675
|
+
: scanned.filter((entry) => stagedSet.has(normalizePath(path.relative(options.projectRoot, entry.path))));
|
|
676
|
+
// Slug uniqueness is a corpus-wide invariant, so validate it over ALL scanned
|
|
677
|
+
// blueprints even in changed-only mode — otherwise a changed blueprint that
|
|
678
|
+
// duplicates an *untouched* lifecycle slug would only be compared with itself
|
|
679
|
+
// and the error would vanish. Only the expensive per-file audit below is
|
|
680
|
+
// scoped to the changed subset.
|
|
681
|
+
issues.push(...validateBlueprintSlugUniqueness(scanned, { projectRoot: options.projectRoot, stagedSet }));
|
|
627
682
|
for (const entry of blueprintFiles) {
|
|
628
683
|
issues.push(...(await auditBlueprintFile(entry.path, entry.slug, options)));
|
|
629
684
|
}
|