@webpresso/agent-kit 3.1.28 → 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 +6 -6
- package/catalog/agent/agents/implementer.md +45 -0
- package/catalog/agent/rules/ci-cost-local-first.md +139 -0
- package/catalog/agent/rules/ci-test-perf.md +5 -1
- package/catalog/agent/rules/cmd-execution.md +9 -0
- package/catalog/agent/rules/parallel-orchestration-quota.md +100 -0
- package/catalog/agent/rules/pre-implementation.md +30 -2
- package/catalog/agent/rules/supported-agent-clis.md +9 -0
- package/catalog/agent/rules/test-scan-perf.md +80 -0
- package/catalog/agent/rules/typescript-version.md +50 -0
- package/catalog/agent/rules/workflow-skills-routing.md +4 -2
- package/catalog/agent/skills/codex/SKILL.md +25 -1
- package/catalog/agent/skills/fix/SKILL.md +3 -0
- package/catalog/agent/skills/verify/SKILL.md +22 -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 +72 -7
- 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 +11 -1
- package/dist/esm/audit/roadmap-links.js +6 -0
- package/dist/esm/audit/test-scan-perf.d.ts +18 -0
- package/dist/esm/audit/test-scan-perf.js +275 -0
- 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/engine.d.ts +9 -0
- package/dist/esm/blueprint/lifecycle/engine.js +53 -6
- 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 +118 -12
- package/dist/esm/blueprint/markdown/helpers.js +6 -2
- 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/ci/act-replay.js +110 -7
- package/dist/esm/ci/act-runner.d.ts +4 -0
- package/dist/esm/ci/act-runner.js +85 -26
- package/dist/esm/ci/act-worktree-git.d.ts +11 -0
- package/dist/esm/ci/act-worktree-git.js +219 -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 +20 -5
- package/dist/esm/cli/commands/blueprint/execution.js +18 -6
- package/dist/esm/cli/commands/blueprint/mutations.js +83 -47
- 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 +40 -3
- 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/ci.d.ts +8 -1
- package/dist/esm/cli/commands/ci.js +80 -21
- 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/dash/keymap.js +1 -0
- package/dist/esm/cli/commands/dash/launch-preferences.d.ts +15 -0
- package/dist/esm/cli/commands/dash/launch-preferences.js +46 -17
- package/dist/esm/cli/commands/dash/plan-once-label.d.ts +7 -0
- package/dist/esm/cli/commands/dash/plan-once-label.js +5 -0
- package/dist/esm/cli/commands/dash/plan-once-membership.d.ts +67 -0
- package/dist/esm/cli/commands/dash/plan-once-membership.js +151 -0
- package/dist/esm/cli/commands/dash/plan-once-merge.d.ts +143 -0
- package/dist/esm/cli/commands/dash/plan-once-merge.js +238 -0
- package/dist/esm/cli/commands/dash/plan-once.d.ts +22 -2
- package/dist/esm/cli/commands/dash/plan-once.js +41 -4
- package/dist/esm/cli/commands/dash/tui/attention-hud.d.ts +9 -0
- package/dist/esm/cli/commands/dash/tui/attention-hud.js +9 -0
- package/dist/esm/cli/commands/dash/tui/chat-bridge.d.ts +41 -0
- package/dist/esm/cli/commands/dash/tui/chat-bridge.js +73 -0
- package/dist/esm/cli/commands/dash/tui/chat-mode.d.ts +47 -0
- package/dist/esm/cli/commands/dash/tui/chat-mode.js +98 -0
- package/dist/esm/cli/commands/dash/tui/frame.d.ts +3 -0
- package/dist/esm/cli/commands/dash/tui/frame.js +3 -1
- package/dist/esm/cli/commands/dash/tui/palette.d.ts +1 -1
- package/dist/esm/cli/commands/dash/tui/palette.js +14 -0
- package/dist/esm/cli/commands/dash/tui/runtime.js +239 -4
- package/dist/esm/cli/commands/dash/tui/state.d.ts +41 -0
- package/dist/esm/cli/commands/dash/tui/state.js +257 -4
- package/dist/esm/cli/commands/doctor.js +5 -0
- package/dist/esm/cli/commands/format.js +0 -2
- 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.d.ts +0 -1
- package/dist/esm/cli/commands/init/config.js +3 -6
- 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/claude.d.ts +5 -6
- package/dist/esm/cli/commands/init/scaffolders/agent-hooks/emitters/claude.js +6 -13
- 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/agent-hooks/ir.d.ts +0 -2
- package/dist/esm/cli/commands/init/scaffolders/agent-hooks/ir.js +0 -2
- package/dist/esm/cli/commands/init/scaffolders/agent-hooks/merge.d.ts +6 -3
- package/dist/esm/cli/commands/init/scaffolders/agent-hooks/merge.js +39 -26
- 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 +297 -173
- 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/logs.d.ts +15 -0
- package/dist/esm/cli/commands/logs.js +192 -3
- 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/quality-log-store.d.ts +1 -0
- package/dist/esm/cli/commands/quality-log-store.js +187 -51
- package/dist/esm/cli/commands/review.d.ts +35 -3
- package/dist/esm/cli/commands/review.js +734 -217
- 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/secrets.d.ts +1 -0
- package/dist/esm/cli/commands/secrets.js +4 -1
- 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 +31 -3
- 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 +15 -0
- package/dist/esm/hooks/doctor.js +79 -8
- 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 +58 -4
- 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/mcp-redirect.d.ts +0 -1
- package/dist/esm/hooks/pretool-guard/validators/mcp-redirect.js +8 -5
- 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.d.ts +9 -7
- package/dist/esm/hooks/pretool-guard/validators/worktree-discipline.js +126 -14
- 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 +5 -1
- package/dist/esm/mcp/tools/audit.js +40 -0
- package/dist/esm/mcp/tools/audits.d.ts +4 -0
- package/dist/esm/mcp/tools/ci-act.js +9 -11
- 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/paths/state-root.js +3 -0
- package/dist/esm/review/authority.js +30 -7
- package/dist/esm/review/availability.d.ts +22 -0
- package/dist/esm/review/availability.js +123 -23
- 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 +27 -0
- package/dist/esm/review/subject.js +179 -32
- package/dist/esm/runtime/executor.d.ts +1 -0
- package/dist/esm/runtime/executor.js +2 -1
- package/dist/esm/runtime/opencode-account-materializer.d.ts +34 -0
- package/dist/esm/runtime/opencode-account-materializer.js +33 -0
- package/dist/esm/secret-gate/runner.d.ts +1 -0
- package/dist/esm/secret-gate/runner.js +4 -1
- 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/current.d.ts +1 -0
- package/dist/esm/worktrees/current.js +1 -0
- package/dist/esm/worktrees/project-agent-surfaces.d.ts +1 -0
- package/dist/esm/worktrees/project-agent-surfaces.js +2 -1
- package/dist/esm/worktrees/registry.d.ts +8 -0
- package/dist/esm/worktrees/registry.js +18 -3
- package/package.json +18 -15
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { TransformContext, TransformResult } from "./index.js";
|
|
2
|
+
/**
|
|
3
|
+
* `wp audit <kind>` writes the full `formatRepoAuditReport` output (title +
|
|
4
|
+
* one bullet per violation) into its CLI log. Every audit kind normalizes to
|
|
5
|
+
* the shared `"audit"` tool name (see `normalizeToolName`), so without a
|
|
6
|
+
* dedicated transform that output fell through to `genericTransform`, whose
|
|
7
|
+
* error/fail keyword heuristic drops any violation bullet that doesn't
|
|
8
|
+
* literally contain "error"/"fail"/"✗"/"✘" — silently discarding most
|
|
9
|
+
* violation detail even when the full report is a handful of short lines.
|
|
10
|
+
*
|
|
11
|
+
* This transform recognizes the bullet-list shape directly so violation text
|
|
12
|
+
* survives regardless of wording, then defers to the same size-based
|
|
13
|
+
* clip/elision policy every other transform uses (`createTransformResult` /
|
|
14
|
+
* `clipRawOutput`'s existing byte threshold) to decide whether the (now
|
|
15
|
+
* correctly extracted) violation list is short enough to print inline or
|
|
16
|
+
* long enough to still defer to `wp logs audit`.
|
|
17
|
+
*
|
|
18
|
+
* Audit output that isn't shaped like a violation-bullet report (for example
|
|
19
|
+
* raw subprocess output from `mutation`/`bundle-budget`) has no matching
|
|
20
|
+
* bullet lines, so this falls back to the previous generic keyword-based
|
|
21
|
+
* behavior unchanged.
|
|
22
|
+
*/
|
|
23
|
+
export declare function auditTransform(rawOutput: string | undefined, context: TransformContext): TransformResult;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { createTransformResult } from "./metadata.js";
|
|
2
|
+
import { genericTransform } from "./generic.js";
|
|
3
|
+
// `formatRepoAuditReport` (src/audit/repo-guardrails.ts) always renders one
|
|
4
|
+
// violation per line as `- <location><metadata><message><fixability>`. That
|
|
5
|
+
// bullet marker is the one structural signal every repo-audit report shares,
|
|
6
|
+
// regardless of which audit kind produced it or whether the violation text
|
|
7
|
+
// happens to contain the words "error"/"fail" that the generic fallback
|
|
8
|
+
// keys off of.
|
|
9
|
+
const VIOLATION_LINE_RE = /^-\s+(.+)$/u;
|
|
10
|
+
/**
|
|
11
|
+
* `wp audit <kind>` writes the full `formatRepoAuditReport` output (title +
|
|
12
|
+
* one bullet per violation) into its CLI log. Every audit kind normalizes to
|
|
13
|
+
* the shared `"audit"` tool name (see `normalizeToolName`), so without a
|
|
14
|
+
* dedicated transform that output fell through to `genericTransform`, whose
|
|
15
|
+
* error/fail keyword heuristic drops any violation bullet that doesn't
|
|
16
|
+
* literally contain "error"/"fail"/"✗"/"✘" — silently discarding most
|
|
17
|
+
* violation detail even when the full report is a handful of short lines.
|
|
18
|
+
*
|
|
19
|
+
* This transform recognizes the bullet-list shape directly so violation text
|
|
20
|
+
* survives regardless of wording, then defers to the same size-based
|
|
21
|
+
* clip/elision policy every other transform uses (`createTransformResult` /
|
|
22
|
+
* `clipRawOutput`'s existing byte threshold) to decide whether the (now
|
|
23
|
+
* correctly extracted) violation list is short enough to print inline or
|
|
24
|
+
* long enough to still defer to `wp logs audit`.
|
|
25
|
+
*
|
|
26
|
+
* Audit output that isn't shaped like a violation-bullet report (for example
|
|
27
|
+
* raw subprocess output from `mutation`/`bundle-budget`) has no matching
|
|
28
|
+
* bullet lines, so this falls back to the previous generic keyword-based
|
|
29
|
+
* behavior unchanged.
|
|
30
|
+
*/
|
|
31
|
+
export function auditTransform(rawOutput, context) {
|
|
32
|
+
if (!rawOutput)
|
|
33
|
+
return {};
|
|
34
|
+
const failures = extractViolationBullets(rawOutput);
|
|
35
|
+
if (failures.length === 0)
|
|
36
|
+
return genericTransform(rawOutput, context);
|
|
37
|
+
return createTransformResult(rawOutput, failures.map((failure) => failure.message).join("\n"), context, { tier: 1, failures });
|
|
38
|
+
}
|
|
39
|
+
function extractViolationBullets(rawOutput) {
|
|
40
|
+
const failures = [];
|
|
41
|
+
for (const line of rawOutput.split(/\r?\n/u)) {
|
|
42
|
+
const message = VIOLATION_LINE_RE.exec(line)?.[1]?.trim();
|
|
43
|
+
if (message)
|
|
44
|
+
failures.push({ message });
|
|
45
|
+
}
|
|
46
|
+
return failures;
|
|
47
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { auditTransform } from "./audit.js";
|
|
1
2
|
import { genericTransform } from "./generic.js";
|
|
2
3
|
import { oxlintTransform } from "./oxlint.js";
|
|
3
4
|
import { tscTransform } from "./tsc.js";
|
|
@@ -8,6 +9,7 @@ const builtInTransforms = new Map([
|
|
|
8
9
|
["lint-oxlint", oxlintTransform],
|
|
9
10
|
["typecheck", tscTransform],
|
|
10
11
|
["test", vitestTransform],
|
|
12
|
+
["audit", auditTransform],
|
|
11
13
|
]);
|
|
12
14
|
const transforms = new Map(builtInTransforms);
|
|
13
15
|
export function registerTransform(toolName, transform) {
|
|
@@ -247,6 +247,9 @@ export async function getSurfacePathAsync(name, scope, cwd = resolveCwd()) {
|
|
|
247
247
|
export async function withLock(scope, fn, cwd) {
|
|
248
248
|
const lockTarget = await getSurfacePathAsync(".lock", scope, cwd);
|
|
249
249
|
mkdirSync(dirname(lockTarget), { recursive: true });
|
|
250
|
+
// Keep retries short so lock-contention probes (review gate contender tests)
|
|
251
|
+
// still fail closed in a few seconds. Dual-writer create loops retry at the
|
|
252
|
+
// call site when they need longer budgets under CI load.
|
|
250
253
|
const release = await lockfile.lock(lockTarget, {
|
|
251
254
|
realpath: false,
|
|
252
255
|
retries: { retries: 5, minTimeout: 50, maxTimeout: 500, factor: 2 },
|
|
@@ -9,7 +9,7 @@ import { createHash } from "node:crypto";
|
|
|
9
9
|
import { existsSync, lstatSync, readFileSync } from "node:fs";
|
|
10
10
|
import path from "node:path";
|
|
11
11
|
import { evaluateReviewEvents, parseReviewEventLog } from "./events.js";
|
|
12
|
-
import { createDeliverySubjectAtRef, createPlanSubjectAtRef, resolveReviewCommit, } from "./subject.js";
|
|
12
|
+
import { createDeliverySubjectAtRef, createLegacyPlanSubjectAtRef, createPlanSubjectAtRef, resolveReviewCommit, } from "./subject.js";
|
|
13
13
|
const GIT_TIMEOUT_MS = 5_000;
|
|
14
14
|
const GIT_MAX_BUFFER_BYTES = 128 * 1024 * 1024;
|
|
15
15
|
const REGULAR_MODE_PATTERN = /^100(?:644|755)$/u;
|
|
@@ -60,7 +60,7 @@ function assertStableSlug(slug) {
|
|
|
60
60
|
}
|
|
61
61
|
function locateBlueprint(entries, stableSlug) {
|
|
62
62
|
assertStableSlug(stableSlug);
|
|
63
|
-
const suffixPattern = new RegExp(`(?:^|/)${LIFECYCLE_SEGMENTS}/${stableSlug.replaceAll("/", "\\/")}
|
|
63
|
+
const suffixPattern = new RegExp(`(?:^|/)${LIFECYCLE_SEGMENTS}/${stableSlug.replaceAll("/", "\\/")}(?:/_overview\\.md|\\.md)$`, "u");
|
|
64
64
|
const matches = entries.filter((entry) => suffixPattern.test(entry.path) &&
|
|
65
65
|
REGULAR_MODE_PATTERN.test(entry.mode) &&
|
|
66
66
|
entry.type === "blob");
|
|
@@ -69,6 +69,9 @@ function locateBlueprint(entries, stableSlug) {
|
|
|
69
69
|
}
|
|
70
70
|
return matches[0];
|
|
71
71
|
}
|
|
72
|
+
function ledgerDirectoryForBlueprintPath(blueprintPath) {
|
|
73
|
+
return blueprintPath.endsWith("/_overview.md") ? path.posix.dirname(blueprintPath) : null;
|
|
74
|
+
}
|
|
72
75
|
function readBlob(cwd, entry) {
|
|
73
76
|
if (!REGULAR_MODE_PATTERN.test(entry.mode) || entry.type !== "blob") {
|
|
74
77
|
throw new Error(`Review authority path is not a regular blob: ${entry.path}`);
|
|
@@ -115,23 +118,40 @@ export function readReviewAuthorityAtRef(cwd, ref, stableSlug, purpose) {
|
|
|
115
118
|
const commit = resolveReviewCommit(cwd, ref);
|
|
116
119
|
const entries = parseRefTree(cwd, commit);
|
|
117
120
|
const overview = locateBlueprint(entries, stableSlug);
|
|
118
|
-
const blueprintDirectory =
|
|
119
|
-
const ledgerPath = path.posix.join(blueprintDirectory, "review-events.jsonl");
|
|
120
|
-
const ledger = entries.find((entry) => entry.path === ledgerPath);
|
|
121
|
+
const blueprintDirectory = ledgerDirectoryForBlueprintPath(overview.path);
|
|
122
|
+
const ledgerPath = blueprintDirectory === null ? null : path.posix.join(blueprintDirectory, "review-events.jsonl");
|
|
123
|
+
const ledger = ledgerPath === null ? undefined : entries.find((entry) => entry.path === ledgerPath);
|
|
121
124
|
const events = ledger ? parseReviewEventLog(readBlob(cwd, ledger).toString("utf8")) : [];
|
|
122
125
|
if (events.some((event) => event.blueprintSlug !== stableSlug)) {
|
|
123
126
|
throw new Error(`Review event ledger contains an event for a different blueprint: ${ledgerPath}`);
|
|
124
127
|
}
|
|
125
|
-
const { compromisedIds, issues } =
|
|
128
|
+
const { compromisedIds, issues } = blueprintDirectory === null
|
|
129
|
+
? { compromisedIds: new Set(), issues: [] }
|
|
130
|
+
: markCompromisedArtifactEvents(cwd, entries, blueprintDirectory, events);
|
|
126
131
|
const subject = purpose === "plan"
|
|
127
132
|
? createPlanSubjectAtRef(cwd, commit, stableSlug, overview.path)
|
|
128
133
|
: createDeliverySubjectAtRef(cwd, commit, stableSlug, overview.path);
|
|
129
|
-
|
|
134
|
+
let evaluation = evaluateReviewEvents(events, {
|
|
130
135
|
purpose,
|
|
131
136
|
blueprintSlug: stableSlug,
|
|
132
137
|
subjectScheme: subject.scheme,
|
|
133
138
|
subjectDigest: subject.digest,
|
|
134
139
|
}, compromisedIds);
|
|
140
|
+
if (purpose === "plan" && evaluation.approvals.length === 0) {
|
|
141
|
+
// Digest-compatibility fallback: events recorded before the lifecycle
|
|
142
|
+
// stripped-key extension bound their subjectDigest to the legacy
|
|
143
|
+
// normalization. Accept those for the same tracked content; new events
|
|
144
|
+
// always record the current digest (see legacyNormalizePlanSubjectContent).
|
|
145
|
+
const legacySubject = createLegacyPlanSubjectAtRef(cwd, commit, stableSlug, overview.path);
|
|
146
|
+
if (legacySubject.digest !== subject.digest) {
|
|
147
|
+
evaluation = evaluateReviewEvents(events, {
|
|
148
|
+
purpose,
|
|
149
|
+
blueprintSlug: stableSlug,
|
|
150
|
+
subjectScheme: legacySubject.scheme,
|
|
151
|
+
subjectDigest: legacySubject.digest,
|
|
152
|
+
}, compromisedIds);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
135
155
|
return {
|
|
136
156
|
commit,
|
|
137
157
|
blueprintPath: overview.path,
|
|
@@ -145,6 +165,9 @@ export function readReviewAuthorityAtRef(cwd, ref, stableSlug, purpose) {
|
|
|
145
165
|
};
|
|
146
166
|
}
|
|
147
167
|
export function readWorkingReviewEvents(blueprintPath, stableSlug) {
|
|
168
|
+
if (path.basename(blueprintPath) !== "_overview.md") {
|
|
169
|
+
return { events: [], compromisedIds: new Set(), issues: [] };
|
|
170
|
+
}
|
|
148
171
|
const ledgerPath = path.join(path.dirname(blueprintPath), "review-events.jsonl");
|
|
149
172
|
if (!existsSync(ledgerPath))
|
|
150
173
|
return { events: [], compromisedIds: new Set(), issues: [] };
|
|
@@ -49,3 +49,25 @@ export declare function pruneExpiredAvailabilitySuppressions(state: ReviewAvaila
|
|
|
49
49
|
state: ReviewAvailabilityState;
|
|
50
50
|
pruned: string[];
|
|
51
51
|
};
|
|
52
|
+
export declare const OPENCODE_ACCOUNT_AVAILABILITY_SCHEMA_VERSION = 1;
|
|
53
|
+
export interface OpencodeAccountAvailabilityState {
|
|
54
|
+
readonly schemaVersion: 1;
|
|
55
|
+
readonly updatedAt: string;
|
|
56
|
+
readonly accounts: Record<string, ReviewAvailabilityRecord>;
|
|
57
|
+
}
|
|
58
|
+
export interface OpencodeAccountAvailabilityEvent {
|
|
59
|
+
readonly limitWindow?: ReviewLimitWindow;
|
|
60
|
+
readonly retryAfterMs?: number;
|
|
61
|
+
readonly failureCode?: string;
|
|
62
|
+
readonly terminalClassification?: string;
|
|
63
|
+
readonly at?: string;
|
|
64
|
+
}
|
|
65
|
+
export declare function opencodeAccountAvailabilityPath(): string;
|
|
66
|
+
export declare function loadOpencodeAccountAvailability(): OpencodeAccountAvailabilityState;
|
|
67
|
+
export declare function isOpencodeAccountSuppressed(state: OpencodeAccountAvailabilityState, canonicalId: string): boolean;
|
|
68
|
+
export declare function recordOpencodeAccountAvailability(canonicalId: string, event: OpencodeAccountAvailabilityEvent): OpencodeAccountAvailabilityState;
|
|
69
|
+
export declare function recordOpencodeAccountAvailabilityWithLock(canonicalId: string, event: OpencodeAccountAvailabilityEvent): Promise<OpencodeAccountAvailabilityState>;
|
|
70
|
+
export declare function pruneExpiredOpencodeAccountSuppressions(state: OpencodeAccountAvailabilityState, nowMs?: number): {
|
|
71
|
+
state: OpencodeAccountAvailabilityState;
|
|
72
|
+
pruned: string[];
|
|
73
|
+
};
|
|
@@ -1,13 +1,9 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { NotInGitRepoError, withLock } from "#paths/state-root.js";
|
|
3
|
+
import { getStateRoot, NotInGitRepoError, withLock } from "#paths/state-root.js";
|
|
4
4
|
import { writeJsonFileAtomic } from "#shared-utils/write-json-file.js";
|
|
5
5
|
export const REVIEW_AVAILABILITY_SCHEMA_VERSION = 1;
|
|
6
|
-
const FAILURE_CLASSIFICATIONS = new Set([
|
|
7
|
-
"provider-limit-reached",
|
|
8
|
-
"provider-unavailable",
|
|
9
|
-
"provider-failed",
|
|
10
|
-
]);
|
|
6
|
+
const FAILURE_CLASSIFICATIONS = new Set(["provider-limit-reached", "provider-unavailable"]);
|
|
11
7
|
const SUCCESS_CLASSIFICATIONS = new Set([
|
|
12
8
|
"complete",
|
|
13
9
|
"approved",
|
|
@@ -117,14 +113,15 @@ export function saveReviewAvailability(projectRoot, state) {
|
|
|
117
113
|
function shouldSuppress(event) {
|
|
118
114
|
return FAILURE_CLASSIFICATIONS.has(event.terminalClassification);
|
|
119
115
|
}
|
|
116
|
+
function suppressedRecordStillApplies(record) {
|
|
117
|
+
return (record.terminalClassification !== undefined &&
|
|
118
|
+
FAILURE_CLASSIFICATIONS.has(record.terminalClassification));
|
|
119
|
+
}
|
|
120
120
|
function isAvailabilitySuccess(event) {
|
|
121
121
|
return SUCCESS_CLASSIFICATIONS.has(event.terminalClassification);
|
|
122
122
|
}
|
|
123
123
|
function isProviderWideOpenCodeFailure(event) {
|
|
124
|
-
return (event.provider === "opencode" &&
|
|
125
|
-
(event.failureCode === "opencode-go-usage-limit" ||
|
|
126
|
-
event.reviewer === undefined ||
|
|
127
|
-
event.reviewer === "opencode"));
|
|
124
|
+
return (event.provider === "opencode" && (event.reviewer === undefined || event.reviewer === "opencode"));
|
|
128
125
|
}
|
|
129
126
|
function nextRecord(previous, event, input) {
|
|
130
127
|
const at = event.at ?? new Date().toISOString();
|
|
@@ -155,6 +152,15 @@ function nextRecord(previous, event, input) {
|
|
|
155
152
|
const success = isAvailabilitySuccess(event);
|
|
156
153
|
if (!input.suppress) {
|
|
157
154
|
const preserveSuppression = !success && previous?.suppressed === true;
|
|
155
|
+
if (preserveSuppression) {
|
|
156
|
+
return {
|
|
157
|
+
...previous,
|
|
158
|
+
...(event.model ? { lastModel: event.model } : {}),
|
|
159
|
+
...(event.effort ? { lastEffort: event.effort } : {}),
|
|
160
|
+
...(event.stage ? { lastStage: event.stage } : {}),
|
|
161
|
+
suppressed: true,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
158
164
|
return {
|
|
159
165
|
...common,
|
|
160
166
|
...(success
|
|
@@ -241,22 +247,35 @@ export function suppressedReviewers(state) {
|
|
|
241
247
|
.filter((record) => record.suppressed)
|
|
242
248
|
.map((record) => record.canonicalId));
|
|
243
249
|
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
250
|
+
/**
|
|
251
|
+
* Shared expiry predicate for a suppressed availability record: applies to
|
|
252
|
+
* both the reviewer/provider store and the opencode-go account store below.
|
|
253
|
+
* A record clears when its terminal classification is no longer a failure
|
|
254
|
+
* classification, or when its retry window has elapsed.
|
|
255
|
+
*/
|
|
256
|
+
function suppressionExpired(record, nowMs) {
|
|
257
|
+
if (!record.suppressed)
|
|
258
|
+
return false;
|
|
259
|
+
if (!suppressedRecordStillApplies(record))
|
|
260
|
+
return true;
|
|
261
|
+
if (record.retryAfterMs === undefined || record.lastFailureAt === undefined)
|
|
262
|
+
return false;
|
|
263
|
+
const failureAt = Date.parse(record.lastFailureAt);
|
|
264
|
+
return Number.isFinite(failureAt) && failureAt + record.retryAfterMs <= nowMs;
|
|
265
|
+
}
|
|
266
|
+
function pruneRecordMap(records, nowMs, pruned) {
|
|
267
|
+
return Object.fromEntries(Object.entries(records).map(([key, record]) => {
|
|
268
|
+
if (suppressionExpired(record, nowMs)) {
|
|
269
|
+
pruned.push(key);
|
|
270
|
+
return [key, { ...record, suppressed: false }];
|
|
255
271
|
}
|
|
256
272
|
return [key, record];
|
|
257
273
|
}));
|
|
258
|
-
|
|
259
|
-
|
|
274
|
+
}
|
|
275
|
+
export function pruneExpiredAvailabilitySuppressions(state, nowMs = Date.now()) {
|
|
276
|
+
const pruned = [];
|
|
277
|
+
const providers = pruneRecordMap(state.providers, nowMs, pruned);
|
|
278
|
+
const reviewers = pruneRecordMap(state.reviewers, nowMs, pruned);
|
|
260
279
|
if (pruned.length === 0)
|
|
261
280
|
return { state, pruned };
|
|
262
281
|
return {
|
|
@@ -269,3 +288,84 @@ export function pruneExpiredAvailabilitySuppressions(state, nowMs = Date.now())
|
|
|
269
288
|
pruned,
|
|
270
289
|
};
|
|
271
290
|
}
|
|
291
|
+
// --- opencode-go multi-account cooldown store (user-scoped, cross-repo) ---
|
|
292
|
+
export const OPENCODE_ACCOUNT_AVAILABILITY_SCHEMA_VERSION = 1;
|
|
293
|
+
export function opencodeAccountAvailabilityPath() {
|
|
294
|
+
return path.join(getStateRoot(), "opencode-account-availability.json");
|
|
295
|
+
}
|
|
296
|
+
function emptyOpencodeAccountAvailability(now = new Date().toISOString()) {
|
|
297
|
+
return {
|
|
298
|
+
schemaVersion: OPENCODE_ACCOUNT_AVAILABILITY_SCHEMA_VERSION,
|
|
299
|
+
updatedAt: now,
|
|
300
|
+
accounts: {},
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
export function loadOpencodeAccountAvailability() {
|
|
304
|
+
const file = opencodeAccountAvailabilityPath();
|
|
305
|
+
if (!existsSync(file))
|
|
306
|
+
return emptyOpencodeAccountAvailability();
|
|
307
|
+
try {
|
|
308
|
+
const parsed = JSON.parse(readFileSync(file, "utf8"));
|
|
309
|
+
if (!isRecord(parsed) ||
|
|
310
|
+
parsed.schemaVersion !== OPENCODE_ACCOUNT_AVAILABILITY_SCHEMA_VERSION) {
|
|
311
|
+
return emptyOpencodeAccountAvailability();
|
|
312
|
+
}
|
|
313
|
+
return {
|
|
314
|
+
schemaVersion: OPENCODE_ACCOUNT_AVAILABILITY_SCHEMA_VERSION,
|
|
315
|
+
updatedAt: typeof parsed.updatedAt === "string" ? parsed.updatedAt : new Date().toISOString(),
|
|
316
|
+
accounts: normalizeRecordMap(parsed.accounts),
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
catch {
|
|
320
|
+
return emptyOpencodeAccountAvailability();
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
function saveOpencodeAccountAvailability(state) {
|
|
324
|
+
const file = opencodeAccountAvailabilityPath();
|
|
325
|
+
mkdirSync(path.dirname(file), { recursive: true });
|
|
326
|
+
writeJsonFileAtomic(file, state);
|
|
327
|
+
}
|
|
328
|
+
export function isOpencodeAccountSuppressed(state, canonicalId) {
|
|
329
|
+
return state.accounts[canonicalId]?.suppressed === true;
|
|
330
|
+
}
|
|
331
|
+
export function recordOpencodeAccountAvailability(canonicalId, event) {
|
|
332
|
+
const at = event.at ?? new Date().toISOString();
|
|
333
|
+
const current = loadOpencodeAccountAvailability();
|
|
334
|
+
const accounts = { ...current.accounts };
|
|
335
|
+
accounts[canonicalId] = {
|
|
336
|
+
canonicalId,
|
|
337
|
+
provider: "opencode",
|
|
338
|
+
terminalClassification: event.terminalClassification ?? "provider-limit-reached",
|
|
339
|
+
...(event.failureCode ? { failureCode: event.failureCode } : {}),
|
|
340
|
+
...(event.limitWindow ? { limitWindow: event.limitWindow } : {}),
|
|
341
|
+
...(event.retryAfterMs === undefined ? {} : { retryAfterMs: event.retryAfterMs }),
|
|
342
|
+
lastFailureAt: at,
|
|
343
|
+
suppressed: true,
|
|
344
|
+
suppressionScope: "provider",
|
|
345
|
+
suppressedAt: at,
|
|
346
|
+
};
|
|
347
|
+
const next = {
|
|
348
|
+
schemaVersion: OPENCODE_ACCOUNT_AVAILABILITY_SCHEMA_VERSION,
|
|
349
|
+
updatedAt: at,
|
|
350
|
+
accounts,
|
|
351
|
+
};
|
|
352
|
+
saveOpencodeAccountAvailability(next);
|
|
353
|
+
return next;
|
|
354
|
+
}
|
|
355
|
+
export function recordOpencodeAccountAvailabilityWithLock(canonicalId, event) {
|
|
356
|
+
return withLock("user", () => recordOpencodeAccountAvailability(canonicalId, event));
|
|
357
|
+
}
|
|
358
|
+
export function pruneExpiredOpencodeAccountSuppressions(state, nowMs = Date.now()) {
|
|
359
|
+
const pruned = [];
|
|
360
|
+
const accounts = pruneRecordMap(state.accounts, nowMs, pruned);
|
|
361
|
+
if (pruned.length === 0)
|
|
362
|
+
return { state, pruned };
|
|
363
|
+
return {
|
|
364
|
+
state: {
|
|
365
|
+
...state,
|
|
366
|
+
updatedAt: new Date(nowMs).toISOString(),
|
|
367
|
+
accounts,
|
|
368
|
+
},
|
|
369
|
+
pruned,
|
|
370
|
+
};
|
|
371
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { executeAllowedWpCommand } from "#trust/command-runner.js";
|
|
2
|
+
import type { ReviewTargetCheckout } from "#review/execution/review-checkout.js";
|
|
3
|
+
export interface ReviewGateVerificationCommand {
|
|
4
|
+
readonly command: string;
|
|
5
|
+
readonly outcome: "passed" | "failed" | "timed-out" | "deferred";
|
|
6
|
+
readonly durationMs: number;
|
|
7
|
+
readonly exitCode: number | null;
|
|
8
|
+
readonly signal?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface ReviewGateVerification {
|
|
11
|
+
readonly status: "passed" | "failed";
|
|
12
|
+
readonly commands: readonly ReviewGateVerificationCommand[];
|
|
13
|
+
readonly failureCode?: string;
|
|
14
|
+
readonly logPath?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface VerifyDeliveryGatesInput {
|
|
17
|
+
readonly purpose: "plan" | "delivery";
|
|
18
|
+
readonly blueprintContent: string;
|
|
19
|
+
readonly projectRoot: string;
|
|
20
|
+
readonly gateId: string;
|
|
21
|
+
readonly checkout: ReviewTargetCheckout;
|
|
22
|
+
readonly signal?: AbortSignal;
|
|
23
|
+
readonly executeCommand?: typeof executeAllowedWpCommand;
|
|
24
|
+
}
|
|
25
|
+
export declare function verifyDeliveryPromotionGates(input: VerifyDeliveryGatesInput): ReviewGateVerification | undefined;
|
|
26
|
+
export declare function verificationPromptSummary(verification: ReviewGateVerification | undefined): string | undefined;
|
|
27
|
+
export declare function verificationArtifactSection(verification: ReviewGateVerification | undefined): string;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { executeAllowedWpCommand, relativeCommandLogPath, } from "#trust/command-runner.js";
|
|
3
|
+
import { parseTrustDossier } from "#trust/dossier.js";
|
|
4
|
+
import { parseAllowedWpCommand } from "#trust/gates.js";
|
|
5
|
+
export function verifyDeliveryPromotionGates(input) {
|
|
6
|
+
if (input.purpose !== "delivery")
|
|
7
|
+
return undefined;
|
|
8
|
+
const parsed = parseTrustDossier(input.blueprintContent);
|
|
9
|
+
if (parsed.violations.length > 0) {
|
|
10
|
+
if (parsed.violations.length === 1 &&
|
|
11
|
+
parsed.violations[0]?.section === "Trust Dossier" &&
|
|
12
|
+
parsed.violations[0]?.message === "missing Trust Dossier section") {
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
return {
|
|
16
|
+
status: "failed",
|
|
17
|
+
commands: [],
|
|
18
|
+
failureCode: "malformed-trust-dossier",
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
const gates = parsed.dossier?.gates ?? [];
|
|
22
|
+
if (gates.length === 0)
|
|
23
|
+
return undefined;
|
|
24
|
+
const commands = [];
|
|
25
|
+
const executeCommand = input.executeCommand ?? executeAllowedWpCommand;
|
|
26
|
+
const logRoot = path.join(input.projectRoot, ".webpresso", "logs", "delivery-verification", input.gateId);
|
|
27
|
+
for (const gate of gates) {
|
|
28
|
+
if (input.signal?.aborted) {
|
|
29
|
+
return {
|
|
30
|
+
status: "failed",
|
|
31
|
+
commands,
|
|
32
|
+
failureCode: "verification-cancelled",
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
if (isApprovalDependentLifecycleGate(gate.command)) {
|
|
36
|
+
commands.push({
|
|
37
|
+
command: gate.command,
|
|
38
|
+
outcome: "deferred",
|
|
39
|
+
durationMs: 0,
|
|
40
|
+
exitCode: null,
|
|
41
|
+
});
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
const result = executeCommand({
|
|
45
|
+
command: gate.command,
|
|
46
|
+
executionRoot: input.checkout.cwd,
|
|
47
|
+
logRoot,
|
|
48
|
+
env: { CI: "1", NO_COLOR: "1", WP_FORCE_SOURCE: "1" },
|
|
49
|
+
});
|
|
50
|
+
commands.push(publicCommandResult(result));
|
|
51
|
+
if (result.outcome !== "passed") {
|
|
52
|
+
return {
|
|
53
|
+
status: "failed",
|
|
54
|
+
commands,
|
|
55
|
+
failureCode: result.failureCode ?? "verification-command-failed",
|
|
56
|
+
logPath: relativeCommandLogPath(input.projectRoot, result.logPath),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
const changes = input.checkout.changes();
|
|
60
|
+
if (changes.length > 0) {
|
|
61
|
+
return {
|
|
62
|
+
status: "failed",
|
|
63
|
+
commands,
|
|
64
|
+
failureCode: "verification-worktree-contaminated",
|
|
65
|
+
logPath: relativeCommandLogPath(input.projectRoot, result.logPath),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
status: "passed",
|
|
71
|
+
commands,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
export function verificationPromptSummary(verification) {
|
|
75
|
+
if (!verification)
|
|
76
|
+
return undefined;
|
|
77
|
+
if (verification.status !== "passed")
|
|
78
|
+
return undefined;
|
|
79
|
+
return [
|
|
80
|
+
"Host verification before outside review: passed.",
|
|
81
|
+
...verification.commands.map((command, index) => `${index + 1}. ${command.command}: ${command.outcome === "deferred" ? "deferred until review publication" : "passed"}`),
|
|
82
|
+
].join("\n");
|
|
83
|
+
}
|
|
84
|
+
function isApprovalDependentLifecycleGate(command) {
|
|
85
|
+
try {
|
|
86
|
+
const [, subcommand, kind] = parseAllowedWpCommand(command);
|
|
87
|
+
return subcommand === "audit" && kind === "blueprint-lifecycle";
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
export function verificationArtifactSection(verification) {
|
|
94
|
+
if (!verification)
|
|
95
|
+
return "";
|
|
96
|
+
const lines = [
|
|
97
|
+
"## Host Verification",
|
|
98
|
+
"",
|
|
99
|
+
`Status: ${verification.status}`,
|
|
100
|
+
...verification.commands.map((command, index) => `${index + 1}. ${command.command} -> ${command.outcome} (${command.durationMs}ms, exit ${command.exitCode ?? "null"})`),
|
|
101
|
+
...(verification.failureCode ? [`Failure code: ${verification.failureCode}`] : []),
|
|
102
|
+
...(verification.logPath ? [`Log: ${verification.logPath}`] : []),
|
|
103
|
+
"",
|
|
104
|
+
];
|
|
105
|
+
return `${lines.join("\n")}\n`;
|
|
106
|
+
}
|
|
107
|
+
function publicCommandResult(result) {
|
|
108
|
+
return {
|
|
109
|
+
command: result.command,
|
|
110
|
+
outcome: result.outcome,
|
|
111
|
+
durationMs: result.durationMs,
|
|
112
|
+
exitCode: result.exitCode,
|
|
113
|
+
...(result.signal ? { signal: result.signal } : {}),
|
|
114
|
+
};
|
|
115
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
export declare const MAINTAINER_OVERRIDE_REVIEWER = "maintainer-override";
|
|
2
3
|
export declare const reviewEventSchema: z.ZodObject<{
|
|
3
4
|
schemaVersion: z.ZodLiteral<2>;
|
|
4
5
|
id: z.ZodString;
|
|
@@ -16,12 +17,12 @@ export declare const reviewEventSchema: z.ZodObject<{
|
|
|
16
17
|
gateId: z.ZodString;
|
|
17
18
|
promptHash: z.ZodString;
|
|
18
19
|
reviewer: z.ZodString;
|
|
19
|
-
provider: z.ZodEnum<{
|
|
20
|
+
provider: z.ZodOptional<z.ZodEnum<{
|
|
20
21
|
claude: "claude";
|
|
21
22
|
codex: "codex";
|
|
22
23
|
grok: "grok";
|
|
23
24
|
opencode: "opencode";
|
|
24
|
-
}
|
|
25
|
+
}>>;
|
|
25
26
|
model: z.ZodOptional<z.ZodString>;
|
|
26
27
|
effort: z.ZodOptional<z.ZodEnum<{
|
|
27
28
|
high: "high";
|
|
@@ -9,6 +9,14 @@ const SHA256_PATTERN = /^[a-f0-9]{64}$/u;
|
|
|
9
9
|
const COMMIT_PATTERN = /^(?:[a-f0-9]{40}|[a-f0-9]{64})$/u;
|
|
10
10
|
const BLUEPRINT_SLUG_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*(?:\/[a-z0-9]+(?:-[a-z0-9]+)*)*$/u;
|
|
11
11
|
const ARTIFACT_PATH_PATTERN = /^review-artifacts\/[a-zA-Z0-9._-]+\.md$/u;
|
|
12
|
+
// The honest, auditable escape hatch reviewer id. A maintainer override is a
|
|
13
|
+
// HUMAN adjudication (not a machine backend), recorded only by `wp review
|
|
14
|
+
// override` after a real strong-reviewer rejection of the same subject. It
|
|
15
|
+
// satisfies the promotion gate DISTINCTLY (never folded into a strong or
|
|
16
|
+
// OpenCode-Go approval). Defined here — the lowest review-core module — so both
|
|
17
|
+
// the ledger read-time policy below and the lifecycle approval gate share one
|
|
18
|
+
// source of truth without an import cycle.
|
|
19
|
+
export const MAINTAINER_OVERRIDE_REVIEWER = "maintainer-override";
|
|
12
20
|
export const reviewEventSchema = z
|
|
13
21
|
.object({
|
|
14
22
|
schemaVersion: z.literal(2),
|
|
@@ -21,7 +29,10 @@ export const reviewEventSchema = z
|
|
|
21
29
|
gateId: z.string().min(1).max(120),
|
|
22
30
|
promptHash: z.string().regex(SHA256_PATTERN),
|
|
23
31
|
reviewer: z.string().min(1).max(120),
|
|
24
|
-
|
|
32
|
+
// Optional: present for machine-backend reviews (always set by `wp review
|
|
33
|
+
// gate`/`log`); absent for a human maintainer-override, which has no AI
|
|
34
|
+
// backend. Descriptive metadata only — no policy keys off `provider`.
|
|
35
|
+
provider: z.enum(["claude", "codex", "opencode", "grok"]).optional(),
|
|
25
36
|
model: z.string().min(1).max(240).optional(),
|
|
26
37
|
effort: z.enum(["medium", "high"]).optional(),
|
|
27
38
|
stage: z.string().min(1).max(120),
|
|
@@ -81,7 +92,7 @@ function orderedEvent(event) {
|
|
|
81
92
|
gateId: event.gateId,
|
|
82
93
|
promptHash: event.promptHash,
|
|
83
94
|
reviewer: event.reviewer,
|
|
84
|
-
provider: event.provider,
|
|
95
|
+
...(event.provider ? { provider: event.provider } : {}),
|
|
85
96
|
...(event.model ? { model: event.model } : {}),
|
|
86
97
|
...(event.effort ? { effort: event.effort } : {}),
|
|
87
98
|
stage: event.stage,
|
|
@@ -164,10 +175,19 @@ function isManualMarker(value) {
|
|
|
164
175
|
// dodge the policy while real gate ids (content hashes) can never collide.
|
|
165
176
|
return value.trim().toLowerCase() === "manual";
|
|
166
177
|
}
|
|
178
|
+
// Reviewers whose manual-stage approvals are legitimate governance decisions,
|
|
179
|
+
// not disguised machine approvals: a plain `human` review and a
|
|
180
|
+
// `maintainer-override` (a maintainer's hand-recorded adjudication). Both are
|
|
181
|
+
// human authority by construction, so a manual marker on their events is
|
|
182
|
+
// expected rather than a policy dodge.
|
|
183
|
+
const HUMAN_LIKE_REVIEWERS = new Set(["human", MAINTAINER_OVERRIDE_REVIEWER]);
|
|
184
|
+
function isHumanLikeReviewer(reviewer) {
|
|
185
|
+
return HUMAN_LIKE_REVIEWERS.has(canonicalReviewer(reviewer));
|
|
186
|
+
}
|
|
167
187
|
function isManualMachineApproval(event) {
|
|
168
188
|
return (isPolicyApprovalVerdict(event.verdict) &&
|
|
169
189
|
(isManualMarker(event.gateId) || isManualMarker(event.stage)) &&
|
|
170
|
-
|
|
190
|
+
!isHumanLikeReviewer(event.reviewer));
|
|
171
191
|
}
|
|
172
192
|
// `compromisedIds` marks events whose artifact evidence failed integrity
|
|
173
193
|
// verification. They still PARTICIPATE in reviewer supersession — a later
|