@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
|
@@ -39,6 +39,43 @@ export function resolveReviewCommit(cwd, ref) {
|
|
|
39
39
|
}
|
|
40
40
|
return commit;
|
|
41
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* Repo-relative paths with uncommitted changes (staged, unstaged, or
|
|
44
|
+
* untracked) that are NOT review evidence (reviews.md / review-events.jsonl /
|
|
45
|
+
* review-artifacts/*.md — see isReviewEvidenceRelativePath). A `wp-git-tree-v1`
|
|
46
|
+
* subject digest is computed purely from the committed tree at a ref, so any
|
|
47
|
+
* non-evidence path here means the working tree diverges from what that
|
|
48
|
+
* digest — and any approval bound to it — actually covers. Review evidence is
|
|
49
|
+
* excluded because the gate's own artifact/ledger writes routinely leave the
|
|
50
|
+
* tree dirty between reruns without representing unreviewed content.
|
|
51
|
+
*/
|
|
52
|
+
export function findUnreviewedWorkingTreeChanges(cwd) {
|
|
53
|
+
const output = gitBuffer(cwd, [
|
|
54
|
+
"status",
|
|
55
|
+
"--porcelain=v1",
|
|
56
|
+
"--untracked-files=all",
|
|
57
|
+
"--ignored=no",
|
|
58
|
+
"-z",
|
|
59
|
+
]).toString("utf8");
|
|
60
|
+
const records = output.split("\0").filter((record) => record.length > 0);
|
|
61
|
+
const paths = new Set();
|
|
62
|
+
for (let index = 0; index < records.length; index += 1) {
|
|
63
|
+
const record = records[index];
|
|
64
|
+
const statusCode = record.slice(0, 2);
|
|
65
|
+
paths.add(record.slice(3));
|
|
66
|
+
if (/[RC]/u.test(statusCode)) {
|
|
67
|
+
// Rename/copy porcelain records are followed by the original path.
|
|
68
|
+
const originalPath = records[index + 1];
|
|
69
|
+
if (originalPath !== undefined) {
|
|
70
|
+
paths.add(originalPath);
|
|
71
|
+
index += 1;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return [...paths]
|
|
76
|
+
.filter((relativePath) => !isReviewEvidenceRelativePath(relativePath))
|
|
77
|
+
.toSorted();
|
|
78
|
+
}
|
|
42
79
|
function parseTree(cwd, commit) {
|
|
43
80
|
const output = gitBuffer(cwd, ["ls-tree", "-r", "-z", "--full-tree", commit]);
|
|
44
81
|
const entries = [];
|
|
@@ -74,31 +111,40 @@ function parseTree(cwd, commit) {
|
|
|
74
111
|
throw new Error("Truncated git ls-tree output.");
|
|
75
112
|
return entries.sort((left, right) => Buffer.compare(left.path, right.path));
|
|
76
113
|
}
|
|
77
|
-
function
|
|
114
|
+
function isRegularBlob(entry) {
|
|
115
|
+
return entry.type === "blob" && /^100(?:644|755)$/u.test(entry.mode.toString("ascii"));
|
|
116
|
+
}
|
|
117
|
+
function findBlueprintDocument(entries, blueprintSlug, blueprintPath) {
|
|
78
118
|
if (blueprintPath) {
|
|
79
119
|
const normalized = blueprintPath.replaceAll("\\", "/");
|
|
80
120
|
const target = Buffer.from(normalized, "utf8");
|
|
81
|
-
const matches = entries.filter((entry) => entry.path.equals(target));
|
|
82
|
-
if (matches.length === 1 && normalized.endsWith("
|
|
83
|
-
return
|
|
121
|
+
const matches = entries.filter((entry) => entry.path.equals(target) && isRegularBlob(entry));
|
|
122
|
+
if (matches.length === 1 && normalized.endsWith(".md")) {
|
|
123
|
+
return matches[0];
|
|
84
124
|
}
|
|
85
125
|
}
|
|
86
|
-
const
|
|
126
|
+
const folderSuffix = `/${blueprintSlug}/_overview.md`;
|
|
127
|
+
const flatSuffix = `/${blueprintSlug}.md`;
|
|
87
128
|
const matches = entries.filter((entry) => {
|
|
88
129
|
const normalized = entry.path.toString("utf8");
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
130
|
+
const suffix = normalized.endsWith(folderSuffix)
|
|
131
|
+
? folderSuffix
|
|
132
|
+
: normalized.endsWith(flatSuffix)
|
|
133
|
+
? flatSuffix
|
|
134
|
+
: undefined;
|
|
135
|
+
const lifecycle = suffix ? normalized.slice(0, -suffix.length).split("/").at(-1) : undefined;
|
|
136
|
+
return isRegularBlob(entry) && BLUEPRINT_STATUSES.has(lifecycle ?? "");
|
|
93
137
|
});
|
|
94
138
|
if (matches.length !== 1) {
|
|
95
139
|
throw new Error(`Expected one tracked blueprint ${blueprintSlug} at target commit, found ${matches.length}.`);
|
|
96
140
|
}
|
|
97
|
-
|
|
98
|
-
return overview.subarray(0, overview.length - Buffer.byteLength("_overview.md"));
|
|
141
|
+
return matches[0];
|
|
99
142
|
}
|
|
100
|
-
function
|
|
101
|
-
|
|
143
|
+
function blueprintEvidenceRoot(documentPath) {
|
|
144
|
+
const value = documentPath.toString("utf8");
|
|
145
|
+
if (!value.endsWith("/_overview.md"))
|
|
146
|
+
return null;
|
|
147
|
+
return documentPath.subarray(0, documentPath.length - Buffer.byteLength("_overview.md"));
|
|
102
148
|
}
|
|
103
149
|
function isExcludedReviewEnvelope(entry, blueprintRoot) {
|
|
104
150
|
if (!isRegularBlob(entry) ||
|
|
@@ -114,10 +160,10 @@ function isExcludedReviewEnvelope(entry, blueprintRoot) {
|
|
|
114
160
|
const artifactName = relative.slice(prefix.length);
|
|
115
161
|
return artifactName.length > 3 && !artifactName.includes("/") && artifactName.endsWith(".md");
|
|
116
162
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
163
|
+
// String-based counterpart of isReviewEvidencePath, shared with callers that
|
|
164
|
+
// only have a repo-relative path (no TreeEntry) — currently the working-tree
|
|
165
|
+
// divergence check in findUnreviewedWorkingTreeChanges below.
|
|
166
|
+
export function isReviewEvidenceRelativePath(value) {
|
|
121
167
|
if (!/^blueprints\/(?:[^/]+\/)*[^/]+\//u.test(value))
|
|
122
168
|
return false;
|
|
123
169
|
if (/(?:^|\/)reviews\.md$/u.test(value) || /(?:^|\/)review-events\.jsonl$/u.test(value)) {
|
|
@@ -125,6 +171,11 @@ function isReviewEvidencePath(entry) {
|
|
|
125
171
|
}
|
|
126
172
|
return /(?:^|\/)review-artifacts\/[^/]+\.md$/u.test(value);
|
|
127
173
|
}
|
|
174
|
+
function isReviewEvidencePath(entry) {
|
|
175
|
+
if (!isRegularBlob(entry))
|
|
176
|
+
return false;
|
|
177
|
+
return isReviewEvidenceRelativePath(entry.path.toString("utf8"));
|
|
178
|
+
}
|
|
128
179
|
function loadBlobContents(cwd, entries) {
|
|
129
180
|
const blobs = entries.filter((entry) => entry.type === "blob");
|
|
130
181
|
if (blobs.length === 0)
|
|
@@ -195,11 +246,13 @@ function readTreeAtRef(cwd, ref) {
|
|
|
195
246
|
}
|
|
196
247
|
export function createDeliverySubjectAtRef(cwd, ref, blueprintSlug, blueprintPath) {
|
|
197
248
|
const { commit, objectFormat, entries: allEntries } = readTreeAtRef(cwd, ref);
|
|
198
|
-
const
|
|
249
|
+
const document = findBlueprintDocument(allEntries, blueprintSlug, blueprintPath);
|
|
250
|
+
const blueprintRoot = blueprintEvidenceRoot(document.path);
|
|
199
251
|
// Exclude review evidence for the owning blueprint AND for every other blueprint
|
|
200
252
|
// (the same global exclusion source snapshots use): recording evidence under an
|
|
201
253
|
// unrelated blueprint must not invalidate an otherwise-identical delivery review.
|
|
202
|
-
const entries = allEntries.filter((entry) => !isExcludedReviewEnvelope(entry, blueprintRoot) &&
|
|
254
|
+
const entries = allEntries.filter((entry) => (blueprintRoot === null || !isExcludedReviewEnvelope(entry, blueprintRoot)) &&
|
|
255
|
+
!isReviewEvidencePath(entry));
|
|
203
256
|
return {
|
|
204
257
|
scheme: "wp-git-tree-v1",
|
|
205
258
|
digest: hashTreeEntries(cwd, entries, objectFormat, DELIVERY_DOMAIN),
|
|
@@ -255,12 +308,39 @@ function neutralizePromotionGateResults(text) {
|
|
|
255
308
|
}
|
|
256
309
|
return lines.join("\n");
|
|
257
310
|
}
|
|
258
|
-
//
|
|
259
|
-
//
|
|
311
|
+
// Frontmatter keys removed wholesale by the normalizer (value + folded
|
|
312
|
+
// continuation lines). Each mirrors exactly one lifecycle writer:
|
|
313
|
+
// `completed_at` (upsertCompletedAt) and the `wp blueprint start` /
|
|
314
|
+
// progress-stamp fields (setBlueprintFrontmatterFields lifecycle intents).
|
|
315
|
+
const PLAN_LIFECYCLE_STRIPPED_KEYS = [
|
|
316
|
+
"completed_at",
|
|
317
|
+
"last_updated",
|
|
318
|
+
"progress",
|
|
319
|
+
"worktree_owner_id",
|
|
320
|
+
"worktree_owner_branch",
|
|
321
|
+
];
|
|
322
|
+
// Neutralize the lifecycle-owned frontmatter fields (`status:` value plus the
|
|
323
|
+
// stripped keys above) INSIDE the leading frontmatter block only. A body line
|
|
260
324
|
// that merely looks like one of these (e.g. task text reading
|
|
261
325
|
// "completed_at: must remain unset until release") is plan substance and must
|
|
262
|
-
// keep affecting the digest.
|
|
326
|
+
// keep affecting the digest. Stripping removes the key line and any indented
|
|
327
|
+
// continuation lines so folded (`>-`) values normalize away too.
|
|
263
328
|
function neutralizeLifecycleFrontmatter(text) {
|
|
329
|
+
const match = /^---\r?\n[\s\S]*?\r?\n---(?:\r?\n|$)/u.exec(text);
|
|
330
|
+
if (!match)
|
|
331
|
+
return text;
|
|
332
|
+
let frontmatter = match[0];
|
|
333
|
+
frontmatter = frontmatter.replace(/^(status:\s*)(['"]?)[^'"\r\n]+?(['"]?)(\s*)$/m, `$1${PLAN_STATUS_PLACEHOLDER}$4`);
|
|
334
|
+
for (const key of PLAN_LIFECYCLE_STRIPPED_KEYS) {
|
|
335
|
+
frontmatter = frontmatter.replace(new RegExp(String.raw `^${key}:[^\n]*\n?(?:[ \t]+[^\n]*\n?)*`, "gmu"), "");
|
|
336
|
+
}
|
|
337
|
+
return `${frontmatter}${text.slice(match[0].length)}`;
|
|
338
|
+
}
|
|
339
|
+
// The pre-extension normalizer (status + completed_at only). Review events
|
|
340
|
+
// recorded before the stripped-key extension bound their subjectDigest to this
|
|
341
|
+
// normalization; verification accepts either digest so committed approvals are
|
|
342
|
+
// not stranded by the rollout. New events always record the current digest.
|
|
343
|
+
function legacyNeutralizeLifecycleFrontmatter(text) {
|
|
264
344
|
const match = /^---\r?\n[\s\S]*?\r?\n---(?:\r?\n|$)/u.exec(text);
|
|
265
345
|
if (!match)
|
|
266
346
|
return text;
|
|
@@ -305,26 +385,89 @@ function neutralizeReadinessStamps(text) {
|
|
|
305
385
|
// NO_CODE_CHANGE / GitLab patch-id semantics: approvals are re-derivable from
|
|
306
386
|
// substance-only digests, not carried). Each rule mirrors exactly one
|
|
307
387
|
// lifecycle writer, scoped to the location that writer owns:
|
|
308
|
-
// - frontmatter
|
|
309
|
-
// - frontmatter `completed_at:` line
|
|
388
|
+
// - frontmatter status/progress/owner fields (blueprint lifecycle mutations)
|
|
389
|
+
// - frontmatter `completed_at:` line (upsertCompletedAt)
|
|
310
390
|
// - Readiness Verdict `- verified-at:`/`- verified-head:` items (upsertReadinessValue)
|
|
311
391
|
// - Promotion Gates "Last result" cells (updateGateLastResult)
|
|
312
392
|
// Any other byte change — task text, acceptance criteria, scope, lookalike
|
|
313
393
|
// lines outside the owned locations — still changes the digest and dismisses
|
|
314
394
|
// prior plan approvals.
|
|
395
|
+
const PLAN_TASK_STATUS_PLACEHOLDER = "__wp-task-status__";
|
|
396
|
+
// Neutralize the task-execution state the lifecycle engine writes
|
|
397
|
+
// (updateTaskStatus, applyVerification): `**Status:** <status>` lines,
|
|
398
|
+
// acceptance checkbox marks, and `**Verification:**` +
|
|
399
|
+
// ```webpresso-evidence-v1 fenced evidence blocks. Task execution is lifecycle
|
|
400
|
+
// state, not plan substance — without this, the engine's own task_verify /
|
|
401
|
+
// finalize writes dismiss the plan approval and no completion commit can ever
|
|
402
|
+
// carry authority. Acceptance-criteria TEXT stays substance; only the check
|
|
403
|
+
// mark is neutralized.
|
|
404
|
+
function neutralizeTaskExecutionState(text) {
|
|
405
|
+
const lines = text.split("\n");
|
|
406
|
+
const out = [];
|
|
407
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
408
|
+
const line = lines[index];
|
|
409
|
+
if (/^\*\*Verification:\*\*\s*$/u.test(line)) {
|
|
410
|
+
// Skip the writer-owned block: "**Verification:**", blank(s), the
|
|
411
|
+
// evidence fence, and one trailing blank line.
|
|
412
|
+
let cursor = index + 1;
|
|
413
|
+
while (cursor < lines.length && lines[cursor].trim() === "")
|
|
414
|
+
cursor += 1;
|
|
415
|
+
if (/^```webpresso-evidence-v1\s*$/u.test(lines[cursor] ?? "")) {
|
|
416
|
+
cursor += 1;
|
|
417
|
+
while (cursor < lines.length && !/^```\s*$/u.test(lines[cursor]))
|
|
418
|
+
cursor += 1;
|
|
419
|
+
cursor += 1; // closing fence
|
|
420
|
+
if ((lines[cursor] ?? "").trim() === "")
|
|
421
|
+
cursor += 1;
|
|
422
|
+
index = cursor - 1;
|
|
423
|
+
continue;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
if (/^\*\*Status:\*\* (?:todo|in-progress|blocked|done|dropped)\s*$/u.test(line)) {
|
|
427
|
+
out.push(`**Status:** ${PLAN_TASK_STATUS_PLACEHOLDER}`);
|
|
428
|
+
continue;
|
|
429
|
+
}
|
|
430
|
+
out.push(line.replace(/^(\s*)- \[[xX ]\] /u, "$1- [_] "));
|
|
431
|
+
}
|
|
432
|
+
return out.join("\n");
|
|
433
|
+
}
|
|
315
434
|
export function normalizePlanSubjectContent(content) {
|
|
316
435
|
let text = typeof content === "string" ? content : content.toString("utf8");
|
|
317
436
|
text = neutralizeLifecycleFrontmatter(text);
|
|
318
437
|
text = neutralizeReadinessStamps(text);
|
|
319
438
|
text = neutralizePromotionGateResults(text);
|
|
439
|
+
text = neutralizeTaskExecutionState(text);
|
|
320
440
|
return Buffer.from(text, "utf8");
|
|
321
441
|
}
|
|
322
|
-
|
|
442
|
+
/**
|
|
443
|
+
* Plan-subject digest for in-memory (working-tree) blueprint content. Used to
|
|
444
|
+
* accept working-ledger review events that digest-bind the exact bytes being
|
|
445
|
+
* committed (pre-commit runs before those events reach any git ref).
|
|
446
|
+
*/
|
|
447
|
+
export function createPlanSubjectDigestForContent(blueprintSlug, content) {
|
|
448
|
+
const hash = createHash("sha256").update(PLAN_DOMAIN);
|
|
449
|
+
updateField(hash, blueprintSlug);
|
|
450
|
+
updateField(hash, normalizePlanSubjectContent(content));
|
|
451
|
+
return hash.digest("hex");
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* Digest-compatibility variant for review events recorded before the
|
|
455
|
+
* stripped-key extension of `neutralizeLifecycleFrontmatter`. Verification
|
|
456
|
+
* accepts a match against either normalization; new digests always use
|
|
457
|
+
* `normalizePlanSubjectContent`.
|
|
458
|
+
*/
|
|
459
|
+
export function legacyNormalizePlanSubjectContent(content) {
|
|
460
|
+
let text = typeof content === "string" ? content : content.toString("utf8");
|
|
461
|
+
text = legacyNeutralizeLifecycleFrontmatter(text);
|
|
462
|
+
text = neutralizeReadinessStamps(text);
|
|
463
|
+
text = neutralizePromotionGateResults(text);
|
|
464
|
+
return Buffer.from(text, "utf8");
|
|
465
|
+
}
|
|
466
|
+
function planSubjectAtRef(cwd, ref, blueprintSlug, normalize, blueprintPath) {
|
|
323
467
|
const commit = resolveReviewCommit(cwd, ref);
|
|
324
468
|
const entries = parseTree(cwd, commit);
|
|
325
|
-
const
|
|
326
|
-
const overviewPath =
|
|
327
|
-
const overview = entries.find((entry) => entry.path.equals(overviewPath));
|
|
469
|
+
const overview = findBlueprintDocument(entries, blueprintSlug, blueprintPath);
|
|
470
|
+
const overviewPath = overview.path;
|
|
328
471
|
if (!overview || overview.type !== "blob") {
|
|
329
472
|
throw new Error("Tracked blueprint overview is not a blob.");
|
|
330
473
|
}
|
|
@@ -333,10 +476,7 @@ export function createPlanSubjectAtRef(cwd, ref, blueprintSlug, blueprintPath) {
|
|
|
333
476
|
throw new Error("Tracked blueprint overview content is unavailable.");
|
|
334
477
|
const hash = createHash("sha256").update(PLAN_DOMAIN);
|
|
335
478
|
updateField(hash, blueprintSlug);
|
|
336
|
-
|
|
337
|
-
// safe pre-merge: no committed review-events.jsonl exists anywhere, so no
|
|
338
|
-
// recorded plan digest can break.
|
|
339
|
-
updateField(hash, normalizePlanSubjectContent(content));
|
|
479
|
+
updateField(hash, normalize(content));
|
|
340
480
|
return {
|
|
341
481
|
scheme: "wp-blueprint-v1",
|
|
342
482
|
digest: hash.digest("hex"),
|
|
@@ -344,6 +484,13 @@ export function createPlanSubjectAtRef(cwd, ref, blueprintSlug, blueprintPath) {
|
|
|
344
484
|
files: [overviewPath.toString("utf8")],
|
|
345
485
|
};
|
|
346
486
|
}
|
|
487
|
+
export function createPlanSubjectAtRef(cwd, ref, blueprintSlug, blueprintPath) {
|
|
488
|
+
return planSubjectAtRef(cwd, ref, blueprintSlug, normalizePlanSubjectContent, blueprintPath);
|
|
489
|
+
}
|
|
490
|
+
/** Plan subject digested with the pre-extension normalization (see above). */
|
|
491
|
+
export function createLegacyPlanSubjectAtRef(cwd, ref, blueprintSlug, blueprintPath) {
|
|
492
|
+
return planSubjectAtRef(cwd, ref, blueprintSlug, legacyNormalizePlanSubjectContent, blueprintPath);
|
|
493
|
+
}
|
|
347
494
|
export const reviewSubjectInternals = {
|
|
348
495
|
isExcludedReviewEnvelope,
|
|
349
496
|
isReviewEvidencePath,
|
|
@@ -14,6 +14,7 @@ export interface ResolveRuntimeEnvironmentOptions {
|
|
|
14
14
|
}
|
|
15
15
|
export interface RuntimeSpawnOptions {
|
|
16
16
|
readonly cwd?: string;
|
|
17
|
+
readonly envCwd?: string;
|
|
17
18
|
readonly profile?: RuntimeSelector;
|
|
18
19
|
readonly secretProfile?: string;
|
|
19
20
|
readonly environment?: string;
|
|
@@ -82,9 +82,10 @@ export function resolveRuntimeEnvironment(options = {}) {
|
|
|
82
82
|
}
|
|
83
83
|
export function buildRuntimeSpawnOptions(options = {}) {
|
|
84
84
|
const cwd = options.cwd ?? process.cwd();
|
|
85
|
+
const envCwd = options.envCwd ?? cwd;
|
|
85
86
|
const cache = options.cache ?? createRuntimeEnvCache();
|
|
86
87
|
const resolvedEnv = resolveRuntimeEnvironment({
|
|
87
|
-
cwd,
|
|
88
|
+
cwd: envCwd,
|
|
88
89
|
profile: options.profile,
|
|
89
90
|
secretProfile: options.secretProfile,
|
|
90
91
|
environment: options.environment,
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type OpencodeAccount } from "#review/opencode-account.js";
|
|
2
|
+
import { resolveRuntimeEnvironment } from "./executor.js";
|
|
3
|
+
export type OpencodeAccountsResolution = {
|
|
4
|
+
readonly ok: true;
|
|
5
|
+
readonly accounts: readonly OpencodeAccount[];
|
|
6
|
+
} | {
|
|
7
|
+
readonly ok: false;
|
|
8
|
+
readonly reason: string;
|
|
9
|
+
};
|
|
10
|
+
export interface ResolveOpencodeAccountsGracefullyInput {
|
|
11
|
+
readonly cwd: string;
|
|
12
|
+
/**
|
|
13
|
+
* Secret profile to resolve through — REQUIRED, never optional.
|
|
14
|
+
*
|
|
15
|
+
* Without it `resolveRuntimeEnvironment` passes no environment selector and
|
|
16
|
+
* the secret manager falls back to its own ambient, path-keyed config
|
|
17
|
+
* (Doppler's per-directory scopes, Infisical's discovered `.infisical.json`).
|
|
18
|
+
* That resolves only in a checkout that happens to sit under the scoped path,
|
|
19
|
+
* so discovery silently yields zero accounts in every managed worktree, CI
|
|
20
|
+
* clone, and fresh checkout — a failure mode that is invisible because callers
|
|
21
|
+
* degrade gracefully. Keeping this required makes that class of bug a compile
|
|
22
|
+
* error instead of a silent runtime degradation. Callers pass
|
|
23
|
+
* `resolveAgentSecretProfile(env)`.
|
|
24
|
+
*/
|
|
25
|
+
readonly secretProfile: string;
|
|
26
|
+
readonly resolveRuntimeEnvironment?: typeof resolveRuntimeEnvironment;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Resolve `OPENCODE_GO_KEY_*` secrets into accounts without ever throwing.
|
|
30
|
+
* On any resolution failure (missing profile, secret-manager error, empty
|
|
31
|
+
* result), returns a failure reason derived only from the error's own
|
|
32
|
+
* message/type — never from a secret value.
|
|
33
|
+
*/
|
|
34
|
+
export declare function resolveOpencodeAccountsGracefully(input: ResolveOpencodeAccountsGracefullyInput): OpencodeAccountsResolution;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { resolveOpencodeAccounts } from "#review/opencode-account.js";
|
|
2
|
+
import { resolveRuntimeEnvironment } from "./executor.js";
|
|
3
|
+
function describeError(error) {
|
|
4
|
+
if (error instanceof Error)
|
|
5
|
+
return `${error.name}: ${error.message}`;
|
|
6
|
+
return typeof error === "string" ? error : "unknown error";
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Resolve `OPENCODE_GO_KEY_*` secrets into accounts without ever throwing.
|
|
10
|
+
* On any resolution failure (missing profile, secret-manager error, empty
|
|
11
|
+
* result), returns a failure reason derived only from the error's own
|
|
12
|
+
* message/type — never from a secret value.
|
|
13
|
+
*/
|
|
14
|
+
export function resolveOpencodeAccountsGracefully(input) {
|
|
15
|
+
const resolveEnv = input.resolveRuntimeEnvironment ?? resolveRuntimeEnvironment;
|
|
16
|
+
let secrets;
|
|
17
|
+
try {
|
|
18
|
+
secrets = resolveEnv({
|
|
19
|
+
cwd: input.cwd,
|
|
20
|
+
profile: "secrets-only",
|
|
21
|
+
secretProfile: input.secretProfile,
|
|
22
|
+
environment: input.secretProfile,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
return { ok: false, reason: describeError(error) };
|
|
27
|
+
}
|
|
28
|
+
const accounts = resolveOpencodeAccounts(secrets);
|
|
29
|
+
if (accounts.length === 0) {
|
|
30
|
+
return { ok: false, reason: "no OPENCODE_GO_KEY_* secrets found" };
|
|
31
|
+
}
|
|
32
|
+
return { ok: true, accounts };
|
|
33
|
+
}
|
|
@@ -11,6 +11,7 @@ export interface SecretGateCommandOptions {
|
|
|
11
11
|
readonly command: string;
|
|
12
12
|
readonly args?: readonly string[];
|
|
13
13
|
readonly cwd?: string;
|
|
14
|
+
readonly commandCwd?: string;
|
|
14
15
|
readonly timeoutMs?: number;
|
|
15
16
|
readonly signal?: AbortSignal;
|
|
16
17
|
readonly forceSecretGate?: boolean;
|
|
@@ -35,6 +35,7 @@ export function buildSecretGateCommand(options) {
|
|
|
35
35
|
sink,
|
|
36
36
|
"--profile",
|
|
37
37
|
profile,
|
|
38
|
+
...(options.commandCwd ? ["--command-cwd", options.commandCwd] : []),
|
|
38
39
|
"--",
|
|
39
40
|
options.command,
|
|
40
41
|
...(options.args ?? []),
|
|
@@ -45,9 +46,11 @@ export function runSecretGateCommand(options) {
|
|
|
45
46
|
const timeoutMs = options.timeoutMs ?? 30_000;
|
|
46
47
|
const maxOutputBytes = options.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES;
|
|
47
48
|
const command = buildSecretGateCommand(options);
|
|
49
|
+
const runsInnerCommandDirectly = command.command === options.command;
|
|
50
|
+
const spawnCwd = runsInnerCommandDirectly && options.commandCwd ? options.commandCwd : options.cwd;
|
|
48
51
|
return new Promise((resolve) => {
|
|
49
52
|
const child = spawn(command.command, [...command.args], {
|
|
50
|
-
cwd:
|
|
53
|
+
cwd: spawnCwd,
|
|
51
54
|
env: buildRuntimeProcessEnv(options.cwd, process.env),
|
|
52
55
|
detached: process.platform !== "win32",
|
|
53
56
|
});
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { evaluateApprovalReviewers } from "#lifecycle/audit.js";
|
|
4
|
+
import { collectV2ReviewProvenance } from "#lifecycle/review-provenance.js";
|
|
4
5
|
import { evaluateRequiredChecks } from "#mcp/tools/_shared/required-checks.js";
|
|
5
6
|
import { redactText } from "#mcp/tools/_shared/redact.js";
|
|
6
7
|
import { runCommand } from "#mcp/tools/_shared/run-command.js";
|
|
7
8
|
import { readReviewAuthorityAtRef } from "#review/authority.js";
|
|
8
9
|
import { resolveBlueprintRoot } from "#utils/blueprint-root.js";
|
|
10
|
+
import { parseBlueprintDocumentRelativePath } from "#utils/document-paths.js";
|
|
9
11
|
export const statusLevelSchema = z.enum(["ready", "blocked", "partial", "unknown"]);
|
|
10
12
|
export const requiredCheckEvaluationSchema = z.object({
|
|
11
13
|
total: z.number().int().nonnegative(),
|
|
@@ -80,6 +82,11 @@ export const wpStatusSnapshotSchema = z.object({
|
|
|
80
82
|
});
|
|
81
83
|
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
82
84
|
const DEFAULT_MAX_OUTPUT_BYTES = 64_000;
|
|
85
|
+
// `gh pr view --json` includes the PR `body`, which alone can exceed the small
|
|
86
|
+
// caller diagnostic budget (GitHub bodies run to tens of KB). Parse that JSON
|
|
87
|
+
// against a generous semantic budget so mergeability/checks/blueprint-linkage
|
|
88
|
+
// fields are never lost to truncation; diagnostics keep the small caller budget.
|
|
89
|
+
const PR_VIEW_SEMANTIC_MAX_OUTPUT_BYTES = 1_048_576;
|
|
83
90
|
const DISCLOSURE_FIELDS = [
|
|
84
91
|
"Execution model(s)",
|
|
85
92
|
"Planning/refinement model(s)",
|
|
@@ -115,8 +122,8 @@ async function probe(cwd, command, args, options = {}) {
|
|
|
115
122
|
cwd,
|
|
116
123
|
timeoutMs: options.timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
117
124
|
});
|
|
125
|
+
const maxOutputBytes = options.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES;
|
|
118
126
|
if ("error" in outcome) {
|
|
119
|
-
const maxOutputBytes = options.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES;
|
|
120
127
|
return {
|
|
121
128
|
passed: false,
|
|
122
129
|
semanticStdout: "",
|
|
@@ -125,11 +132,11 @@ async function probe(cwd, command, args, options = {}) {
|
|
|
125
132
|
missingBinary: outcome.error.code === "ENOENT",
|
|
126
133
|
};
|
|
127
134
|
}
|
|
128
|
-
const
|
|
135
|
+
const semanticMaxOutputBytes = options.semanticMaxOutputBytes ?? maxOutputBytes;
|
|
129
136
|
return {
|
|
130
137
|
passed: outcome.exitCode === 0,
|
|
131
|
-
semanticStdout: clipBytes(outcome.stdout,
|
|
132
|
-
semanticTruncated: Buffer.byteLength(outcome.stdout, "utf8") >
|
|
138
|
+
semanticStdout: clipBytes(outcome.stdout, semanticMaxOutputBytes),
|
|
139
|
+
semanticTruncated: Buffer.byteLength(outcome.stdout, "utf8") > semanticMaxOutputBytes,
|
|
133
140
|
stdout: clipDiagnostic(outcome.stdout, maxOutputBytes),
|
|
134
141
|
stderr: clipDiagnostic(outcome.stderr, maxOutputBytes),
|
|
135
142
|
exitCode: outcome.exitCode,
|
|
@@ -307,33 +314,29 @@ function selectBlueprintFile(projectRoot, pr) {
|
|
|
307
314
|
const prefix = `${root}/`;
|
|
308
315
|
if (!file.path.startsWith(prefix))
|
|
309
316
|
continue;
|
|
310
|
-
const
|
|
311
|
-
if (
|
|
312
|
-
|
|
313
|
-
segments.some((segment) => segment.length === 0 || segment === "." || segment === "..")) {
|
|
314
|
-
continue;
|
|
315
|
-
}
|
|
316
|
-
const stableSlug = segments.slice(1, -1).join("/");
|
|
317
|
-
if (stableSlug)
|
|
318
|
-
return [{ ...file, stableSlug }];
|
|
317
|
+
const parsed = parseBlueprintDocumentRelativePath(file.path.slice(prefix.length));
|
|
318
|
+
if (parsed)
|
|
319
|
+
return [{ ...file, stableSlug: parsed.slug }];
|
|
319
320
|
}
|
|
320
321
|
return [];
|
|
321
322
|
});
|
|
322
323
|
const priority = (file) => file.changeType === "ADDED" ? 0 : file.changeType === "RENAMED" ? 1 : 2;
|
|
323
324
|
return candidates.toSorted((left, right) => priority(left) - priority(right))[0];
|
|
324
325
|
}
|
|
325
|
-
function resolveBlueprintEvidence(
|
|
326
|
+
function resolveBlueprintEvidence(projectRoot, pr, targetHash) {
|
|
326
327
|
const blueprintFile = selectBlueprintFile(projectRoot, pr);
|
|
327
328
|
if (!blueprintFile || !targetHash)
|
|
328
329
|
return undefined;
|
|
329
330
|
try {
|
|
330
|
-
const authority = readReviewAuthorityAtRef(
|
|
331
|
+
const authority = readReviewAuthorityAtRef(projectRoot, targetHash, blueprintFile.stableSlug, "delivery");
|
|
331
332
|
if (authority.blueprintPath !== blueprintFile.path)
|
|
332
333
|
return undefined;
|
|
334
|
+
const approvals = collectV2ReviewProvenance(path.resolve(projectRoot, blueprintFile.path), "delivery", targetHash);
|
|
333
335
|
return {
|
|
334
336
|
path: blueprintFile.path,
|
|
335
337
|
lifecycle: authority.lifecycle,
|
|
336
|
-
approvalsSatisfied: evaluateApprovalReviewers(
|
|
338
|
+
approvalsSatisfied: evaluateApprovalReviewers(approvals.map((approval) => approval.reviewer))
|
|
339
|
+
.satisfied,
|
|
337
340
|
};
|
|
338
341
|
}
|
|
339
342
|
catch {
|
|
@@ -582,7 +585,10 @@ export async function buildStatusSnapshot(options = {}) {
|
|
|
582
585
|
"files",
|
|
583
586
|
].join(",");
|
|
584
587
|
const [view, checksProbe] = await Promise.all([
|
|
585
|
-
probe(cwd, "gh", ["pr", "view", ...selector, "--json", fields],
|
|
588
|
+
probe(cwd, "gh", ["pr", "view", ...selector, "--json", fields], {
|
|
589
|
+
...probeOptions,
|
|
590
|
+
semanticMaxOutputBytes: PR_VIEW_SEMANTIC_MAX_OUTPUT_BYTES,
|
|
591
|
+
}),
|
|
586
592
|
probe(cwd, "gh", ["pr", "checks", ...selector, "--required", "--json", "name,state,bucket,link,description"], probeOptions),
|
|
587
593
|
]);
|
|
588
594
|
pr = view.passed ? parseJsonObject(view.semanticStdout) : undefined;
|
|
@@ -599,7 +605,7 @@ export async function buildStatusSnapshot(options = {}) {
|
|
|
599
605
|
readGreptileComments(cwd, pr, probeOptions),
|
|
600
606
|
])
|
|
601
607
|
: [{ complete: false, warning: "PR metadata unavailable." }, { comments: [] }];
|
|
602
|
-
const blueprint = resolveBlueprintEvidence(
|
|
608
|
+
const blueprint = resolveBlueprintEvidence(firstLine(topLevel.semanticStdout) ?? cwd, pr, localHead);
|
|
603
609
|
const readiness = evaluatePrReadiness({
|
|
604
610
|
localHead,
|
|
605
611
|
dirtyWorktree,
|
|
@@ -649,9 +655,14 @@ export async function buildStatusSnapshot(options = {}) {
|
|
|
649
655
|
threadsComplete: threads.complete,
|
|
650
656
|
greptile: classifyGreptile(checks ?? [], greptileComments.comments, stringField(pr?.headRefOid)),
|
|
651
657
|
};
|
|
658
|
+
// `body` is fully consumed above (release-evidence + disclosure via
|
|
659
|
+
// `evaluatePrReadiness`, blueprint linkage via `resolveBlueprintEvidence`),
|
|
660
|
+
// so drop it from the emitted metadata to keep the payload from re-ballooning
|
|
661
|
+
// to the size that truncated the parse in the first place.
|
|
662
|
+
const { body: _body, ...metadataWithoutBody } = pr ?? {};
|
|
652
663
|
context.pr = {
|
|
653
664
|
selector: selector[0],
|
|
654
|
-
metadata: pr,
|
|
665
|
+
metadata: pr ? metadataWithoutBody : undefined,
|
|
655
666
|
checks: readiness.checks
|
|
656
667
|
? { ...readiness.checks, checks: [...readiness.checks.checks] }
|
|
657
668
|
: undefined,
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { BaseSequencer, type TestSpecification } from "vitest/node";
|
|
2
|
+
export declare function weightFor(project: string, relativePath: string): number;
|
|
3
|
+
interface WeightedSpec {
|
|
4
|
+
readonly spec: TestSpecification;
|
|
5
|
+
readonly weight: number;
|
|
6
|
+
readonly key: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Deterministic greedy LPT partition of `specs` into `count` bins. Files are
|
|
10
|
+
* sorted heaviest-first (ties broken by path so the order is stable across
|
|
11
|
+
* every shard job), then each is placed into the currently-lightest bin (ties
|
|
12
|
+
* broken by lowest bin index). Returns all bins in index order.
|
|
13
|
+
*
|
|
14
|
+
* Exported for the contract test; the sequencer only needs one bin.
|
|
15
|
+
*/
|
|
16
|
+
export declare function partitionByDuration(specs: readonly WeightedSpec[], count: number): readonly (readonly WeightedSpec[])[];
|
|
17
|
+
export declare class DurationSequencer extends BaseSequencer {
|
|
18
|
+
shard(files: TestSpecification[]): Promise<TestSpecification[]>;
|
|
19
|
+
}
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { relative } from "node:path";
|
|
2
|
+
import { BaseSequencer } from "vitest/node";
|
|
3
|
+
import rawSnapshot from "./shard-durations.json" with { type: "json" };
|
|
4
|
+
const snapshot = rawSnapshot;
|
|
5
|
+
// A generated snapshot always carries a positive defaultWeight; clamp to 1 only
|
|
6
|
+
// to keep LPT well-defined if the JSON is ever hand-truncated to nothing.
|
|
7
|
+
const DEFAULT_WEIGHT = snapshot.defaultWeight > 0 ? snapshot.defaultWeight : 1;
|
|
8
|
+
export function weightFor(project, relativePath) {
|
|
9
|
+
return snapshot.durations[project]?.[relativePath] ?? DEFAULT_WEIGHT;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Deterministic greedy LPT partition of `specs` into `count` bins. Files are
|
|
13
|
+
* sorted heaviest-first (ties broken by path so the order is stable across
|
|
14
|
+
* every shard job), then each is placed into the currently-lightest bin (ties
|
|
15
|
+
* broken by lowest bin index). Returns all bins in index order.
|
|
16
|
+
*
|
|
17
|
+
* Exported for the contract test; the sequencer only needs one bin.
|
|
18
|
+
*/
|
|
19
|
+
export function partitionByDuration(specs, count) {
|
|
20
|
+
const bins = Array.from({ length: count }, () => []);
|
|
21
|
+
const loads = Array.from({ length: count }, () => 0);
|
|
22
|
+
const ordered = [...specs].sort((a, b) => b.weight !== a.weight ? b.weight - a.weight : a.key < b.key ? -1 : a.key > b.key ? 1 : 0);
|
|
23
|
+
for (const item of ordered) {
|
|
24
|
+
let target = 0;
|
|
25
|
+
let targetLoad = loads[0] ?? 0;
|
|
26
|
+
for (let i = 1; i < count; i += 1) {
|
|
27
|
+
const load = loads[i] ?? 0;
|
|
28
|
+
if (load < targetLoad) {
|
|
29
|
+
target = i;
|
|
30
|
+
targetLoad = load;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
// target is always in [0, count): bins/loads were sized to `count`.
|
|
34
|
+
(bins[target] ??= []).push(item);
|
|
35
|
+
loads[target] = targetLoad + item.weight;
|
|
36
|
+
}
|
|
37
|
+
return bins;
|
|
38
|
+
}
|
|
39
|
+
export class DurationSequencer extends BaseSequencer {
|
|
40
|
+
async shard(files) {
|
|
41
|
+
const { config } = this.ctx;
|
|
42
|
+
// No --shard (serial lane, local full runs): nothing to partition.
|
|
43
|
+
if (!config.shard)
|
|
44
|
+
return files;
|
|
45
|
+
const { index, count } = config.shard;
|
|
46
|
+
const weighted = files.map((spec) => {
|
|
47
|
+
const key = relative(config.root, spec.moduleId);
|
|
48
|
+
return { spec, key, weight: weightFor(spec.project.name, key) };
|
|
49
|
+
});
|
|
50
|
+
const bins = partitionByDuration(weighted, count);
|
|
51
|
+
// index is 1-based in [1, count]; the bin is always present.
|
|
52
|
+
return (bins[index - 1] ?? []).map((item) => item.spec);
|
|
53
|
+
}
|
|
54
|
+
}
|