@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
|
@@ -3,7 +3,7 @@ import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { CLAUDE_DEFAULT_REVIEW_MODEL, CODEX_DEFAULT_REVIEW_MODEL, GROK_DEFAULT_REVIEW_MODEL, } from "./types.js";
|
|
6
|
-
import { canonicalizeProviderFailureCode } from "./output-policy.js";
|
|
6
|
+
import { canonicalizeProviderFailureCode, sanitizeProviderErrorDetail } from "./output-policy.js";
|
|
7
7
|
function objectValue(value) {
|
|
8
8
|
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
9
9
|
? value
|
|
@@ -61,6 +61,18 @@ function providerErrorMessage(value) {
|
|
|
61
61
|
];
|
|
62
62
|
return candidates.find((candidate) => typeof candidate === "string") ?? null;
|
|
63
63
|
}
|
|
64
|
+
// Builds a "failure" event, attaching the provider's raw diagnostic text
|
|
65
|
+
// (sanitized and length-bounded) alongside the canonical short `code` when
|
|
66
|
+
// one is available. `code` stays the only field the retry/classification
|
|
67
|
+
// control flow reads; `message` exists purely to be surfaced to a caller who
|
|
68
|
+
// is stuck debugging a generic failure code.
|
|
69
|
+
function providerFailureEvent(value, code, message = providerErrorMessage(value)) {
|
|
70
|
+
return {
|
|
71
|
+
kind: "failure",
|
|
72
|
+
code,
|
|
73
|
+
...(message ? { message: sanitizeProviderErrorDetail(message) } : {}),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
64
76
|
function parseResetDurationMs(message) {
|
|
65
77
|
const reset = message.match(/\bresets?\s+in\s+([^.'"]+)/iu)?.[1];
|
|
66
78
|
if (!reset)
|
|
@@ -94,6 +106,7 @@ function opencodeUsageLimit(message) {
|
|
|
94
106
|
return {
|
|
95
107
|
kind: "failure",
|
|
96
108
|
code: "opencode-go-usage-limit",
|
|
109
|
+
message: sanitizeProviderErrorDetail(message),
|
|
97
110
|
limitWindow,
|
|
98
111
|
...(retryAfterMs === undefined ? {} : { retryAfterMs }),
|
|
99
112
|
};
|
|
@@ -104,7 +117,7 @@ function claudeEvent(value) {
|
|
|
104
117
|
return { kind: "unsupported" };
|
|
105
118
|
if (record.type === "result") {
|
|
106
119
|
if (record.is_error === true || record.subtype === "error") {
|
|
107
|
-
return
|
|
120
|
+
return providerFailureEvent(record, failureCode(record));
|
|
108
121
|
}
|
|
109
122
|
return {
|
|
110
123
|
kind: "terminal",
|
|
@@ -112,7 +125,7 @@ function claudeEvent(value) {
|
|
|
112
125
|
};
|
|
113
126
|
}
|
|
114
127
|
if (record.type === "error")
|
|
115
|
-
return
|
|
128
|
+
return providerFailureEvent(record, failureCode(record));
|
|
116
129
|
if (record.type === "rate_limit_event") {
|
|
117
130
|
const rateLimit = objectValue(record.rate_limit_info);
|
|
118
131
|
return rateLimit?.status === "rejected"
|
|
@@ -127,7 +140,7 @@ function claudeEvent(value) {
|
|
|
127
140
|
}
|
|
128
141
|
if (record.type === "assistant") {
|
|
129
142
|
return typeof record.error === "string"
|
|
130
|
-
?
|
|
143
|
+
? providerFailureEvent(record, failureCode(record), record.error)
|
|
131
144
|
: { kind: "progress", frontier: claudeEnvelopeFrontier(record) };
|
|
132
145
|
}
|
|
133
146
|
if (record.type !== "stream_event")
|
|
@@ -151,7 +164,7 @@ function claudeEvent(value) {
|
|
|
151
164
|
return { kind: "progress", frontier: stableFrontier(event) };
|
|
152
165
|
}
|
|
153
166
|
if (event.type === "error")
|
|
154
|
-
return
|
|
167
|
+
return providerFailureEvent(event, failureCode(event));
|
|
155
168
|
return { kind: "unsupported" };
|
|
156
169
|
}
|
|
157
170
|
function opencodeText(record) {
|
|
@@ -216,7 +229,7 @@ function opencodeEvent(value) {
|
|
|
216
229
|
if (record.type === "error") {
|
|
217
230
|
const message = providerErrorMessage(record);
|
|
218
231
|
const usageLimit = message === null ? null : opencodeUsageLimit(message);
|
|
219
|
-
return usageLimit ??
|
|
232
|
+
return usageLimit ?? providerFailureEvent(record, failureCode(record), message);
|
|
220
233
|
}
|
|
221
234
|
// OpenCode's non-interactive JSON protocol closes a completed run with
|
|
222
235
|
// session.idle or step_finish(reason: "stop"); recorded Kimi and DeepSeek
|
|
@@ -293,10 +306,10 @@ function codexEvent(value) {
|
|
|
293
306
|
: message && /rate.?limit|quota/iu.test(message)
|
|
294
307
|
? "rate-limit-rejected"
|
|
295
308
|
: "provider-error");
|
|
296
|
-
return
|
|
309
|
+
return providerFailureEvent(record, code, message);
|
|
297
310
|
}
|
|
298
311
|
if (type.includes("rate_limit") && /reject|limit|quota/iu.test(message ?? "")) {
|
|
299
|
-
return
|
|
312
|
+
return providerFailureEvent(record, "rate-limit-rejected", message);
|
|
300
313
|
}
|
|
301
314
|
if (type === "result" ||
|
|
302
315
|
type === "final" ||
|
|
@@ -399,6 +412,10 @@ export const opencodeReviewAdapter = {
|
|
|
399
412
|
OPENCODE_CONFIG_CONTENT: OPENCODE_REVIEW_CONFIG,
|
|
400
413
|
OPENCODE_DISABLE_AUTOUPDATE: "true",
|
|
401
414
|
OPENCODE_DISABLE_PRUNE: "true",
|
|
415
|
+
// Isolate the credential store per account: the spawn reads
|
|
416
|
+
// `<authDataDir>/opencode/auth.json` instead of the user's global file,
|
|
417
|
+
// so mid-flight account failover never races a live interactive TUI.
|
|
418
|
+
...(input.authDataDir ? { XDG_DATA_HOME: input.authDataDir } : {}),
|
|
402
419
|
},
|
|
403
420
|
cleanup: attachment.cleanup,
|
|
404
421
|
};
|
|
@@ -506,7 +523,7 @@ export const grokReviewAdapter = {
|
|
|
506
523
|
return {
|
|
507
524
|
kind: "failure",
|
|
508
525
|
code: "provider-failed",
|
|
509
|
-
message,
|
|
526
|
+
message: sanitizeProviderErrorDetail(message),
|
|
510
527
|
};
|
|
511
528
|
}
|
|
512
529
|
return { kind: "progress", frontier: type || "event" };
|
|
@@ -11,4 +11,6 @@ export type ReviewOutputPolicyResult = {
|
|
|
11
11
|
};
|
|
12
12
|
export declare function sanitizeReviewOutput(value: string): string;
|
|
13
13
|
export declare function applyReviewOutputPolicy(value: string, consumedBytes?: number): ReviewOutputPolicyResult;
|
|
14
|
+
export declare const REVIEW_ERROR_DETAIL_MAX_CHARS = 400;
|
|
15
|
+
export declare function sanitizeProviderErrorDetail(value: string): string;
|
|
14
16
|
export declare function canonicalizeProviderFailureCode(value: unknown): string;
|
|
@@ -23,6 +23,18 @@ export function applyReviewOutputPolicy(value, consumedBytes = 0) {
|
|
|
23
23
|
}
|
|
24
24
|
return { ok: true, value: sanitized, bytes, totalBytes: consumedBytes + bytes };
|
|
25
25
|
}
|
|
26
|
+
export const REVIEW_ERROR_DETAIL_MAX_CHARS = 400;
|
|
27
|
+
// Bounds and redacts a provider's raw diagnostic text (a JSON envelope's
|
|
28
|
+
// error.message field, or a raw stderr line) before it is retained anywhere.
|
|
29
|
+
// This feeds only the ephemeral gate state file and CLI stdout -- never the
|
|
30
|
+
// durable, git-committed review ledger -- but the same secret redaction that
|
|
31
|
+
// already applies to terminal/progress "result" text applies here too.
|
|
32
|
+
export function sanitizeProviderErrorDetail(value) {
|
|
33
|
+
const sanitized = sanitizeReviewOutput(value).replace(/\s+/gu, " ").trim();
|
|
34
|
+
return sanitized.length > REVIEW_ERROR_DETAIL_MAX_CHARS
|
|
35
|
+
? `${sanitized.slice(0, REVIEW_ERROR_DETAIL_MAX_CHARS)}…`
|
|
36
|
+
: sanitized;
|
|
37
|
+
}
|
|
26
38
|
export function canonicalizeProviderFailureCode(value) {
|
|
27
39
|
if (typeof value !== "string")
|
|
28
40
|
return "provider-error";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { execFileSync } from "node:child_process";
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
|
-
import { lstatSync, mkdtempSync, readFileSync, readdirSync, readlinkSync, rmSync, unlinkSync, writeFileSync, } from "node:fs";
|
|
3
|
+
import { lstatSync, mkdtempSync, readFileSync, readdirSync, readlinkSync, rmSync, symlinkSync, unlinkSync, writeFileSync, } from "node:fs";
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
|
-
import { join } from "node:path";
|
|
5
|
+
import { basename, join } from "node:path";
|
|
6
6
|
function git(cwd, args, input) {
|
|
7
7
|
return execFileSync("git", [...args], {
|
|
8
8
|
cwd,
|
|
@@ -60,12 +60,69 @@ function worktreeFingerprint(cwd) {
|
|
|
60
60
|
}
|
|
61
61
|
return hash.digest("hex");
|
|
62
62
|
}
|
|
63
|
-
|
|
63
|
+
// Path segments that mark gitignored content as NON-source: derived build
|
|
64
|
+
// outputs a Promotion Gate command (`./bin/wp typecheck`/`test`/etc.)
|
|
65
|
+
// regenerates while self-hosting this repo (`dist/`, `.tshy/`) and the
|
|
66
|
+
// reconstructed dependency tree (`node_modules`, already separately allowlisted
|
|
67
|
+
// via `ignoredPaths` when symlinked — kept here too for depth-safety, e.g. a
|
|
68
|
+
// nested per-workspace-package `node_modules`). A gitignored (`!!`) path whose
|
|
69
|
+
// ANY segment is one of these — at any workspace depth (e.g. `packages/x/dist`,
|
|
70
|
+
// `.tshy/build.log`) — is exempt from the contamination check. Confirmed
|
|
71
|
+
// empirically that the gate commands emit gitignored output exclusively under
|
|
72
|
+
// these roots.
|
|
73
|
+
//
|
|
74
|
+
// SCOPE (deliberate, documented): this verifier enforces the review TARGET's
|
|
75
|
+
// *source* integrity, not its build artifacts. It does not flag writes under a
|
|
76
|
+
// derived/dependency root, because such output can reach neither the caller
|
|
77
|
+
// nor the reviewed source (the checkout is a disposable tmpdir). Everything
|
|
78
|
+
// else stays strict: a tracked mutation (` M `/` D `), a new untracked source
|
|
79
|
+
// file (`?? `), and any NON-derived gitignored write (e.g. a stray `.env`) all
|
|
80
|
+
// still surface.
|
|
81
|
+
const NON_SOURCE_IGNORED_SEGMENTS = new Set(["dist", ".tshy", "node_modules"]);
|
|
82
|
+
function isNonSourceIgnoredPath(relativePath) {
|
|
83
|
+
return relativePath.split("/").some((segment) => NON_SOURCE_IGNORED_SEGMENTS.has(segment));
|
|
84
|
+
}
|
|
85
|
+
// This repo self-hosts: a Promotion Gate command's first `./bin/wp` invocation
|
|
86
|
+
// triggers wp's own bootstrap build, which runs tshy and then
|
|
87
|
+
// `./bin/wp format --file package.json` — an IDEMPOTENT normalization (e.g.
|
|
88
|
+
// adding a missing trailing newline). Re-running it a second time produces
|
|
89
|
+
// byte-identical output modulo trailing whitespace, so it is not a source
|
|
90
|
+
// mutation. Exempt ONLY that exact shape: a plain, unstaged content
|
|
91
|
+
// modification (` M `) of a file literally named `package.json` whose
|
|
92
|
+
// tracked-vs-working-tree bytes differ solely in trailing whitespace. Any
|
|
93
|
+
// other package.json change (dependency/script/field edits, staged changes,
|
|
94
|
+
// adds, deletes) still fails closed — this check never inspects JSON
|
|
95
|
+
// semantics, only trailing-whitespace-normalized byte equality.
|
|
96
|
+
function isNormalizedPackageJsonModification(cwd, line) {
|
|
97
|
+
if (!line.startsWith(" M "))
|
|
98
|
+
return false;
|
|
99
|
+
const relativePath = line.slice(3);
|
|
100
|
+
if (basename(relativePath) !== "package.json")
|
|
101
|
+
return false;
|
|
102
|
+
try {
|
|
103
|
+
const working = readFileSync(join(cwd, relativePath), "utf8");
|
|
104
|
+
const tracked = git(cwd, ["show", `HEAD:${relativePath}`]);
|
|
105
|
+
return working.replace(/\s+$/u, "") === tracked.replace(/\s+$/u, "");
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function status(cwd, ignoredPaths = new Set()) {
|
|
64
112
|
const output = git(cwd, ["status", "--porcelain=v1", "--untracked-files=all", "--ignored"]);
|
|
65
113
|
return output
|
|
66
114
|
.split(/\r?\n/u)
|
|
67
|
-
.
|
|
68
|
-
.filter((line) =>
|
|
115
|
+
.filter((line) => line.length > 0)
|
|
116
|
+
.filter((line) => {
|
|
117
|
+
const relativePath = line.slice(3).replace(/\/$/u, "");
|
|
118
|
+
if (ignoredPaths.has(relativePath))
|
|
119
|
+
return false;
|
|
120
|
+
if (line.startsWith("!!") && isNonSourceIgnoredPath(relativePath))
|
|
121
|
+
return false;
|
|
122
|
+
if (isNormalizedPackageJsonModification(cwd, line))
|
|
123
|
+
return false;
|
|
124
|
+
return true;
|
|
125
|
+
});
|
|
69
126
|
}
|
|
70
127
|
function neutralizeSymlinks(directory) {
|
|
71
128
|
for (const entry of readdirSync(directory, { withFileTypes: true })) {
|
|
@@ -111,6 +168,7 @@ export function createReviewTargetCheckout(repositoryRoot, targetSha) {
|
|
|
111
168
|
git(cwd, ["update-ref", "HEAD", baselineHead]);
|
|
112
169
|
if (status(cwd).length > 0)
|
|
113
170
|
throw new Error("review target checkout was not clean at start");
|
|
171
|
+
const injectedPaths = projectCallerDependencies(repositoryRoot, cwd);
|
|
114
172
|
let cleaned = false;
|
|
115
173
|
return {
|
|
116
174
|
cwd,
|
|
@@ -119,7 +177,7 @@ export function createReviewTargetCheckout(repositoryRoot, targetSha) {
|
|
|
119
177
|
},
|
|
120
178
|
changes: () => {
|
|
121
179
|
try {
|
|
122
|
-
const changes = [...status(cwd)];
|
|
180
|
+
const changes = [...status(cwd, injectedPaths)];
|
|
123
181
|
if (git(cwd, ["rev-parse", "HEAD"]).trim() !== baselineHead) {
|
|
124
182
|
changes.push("review checkout HEAD changed");
|
|
125
183
|
}
|
|
@@ -145,3 +203,26 @@ export function createReviewTargetCheckout(repositoryRoot, targetSha) {
|
|
|
145
203
|
throw error;
|
|
146
204
|
}
|
|
147
205
|
}
|
|
206
|
+
function projectCallerDependencies(repositoryRoot, checkoutRoot) {
|
|
207
|
+
const projected = new Set();
|
|
208
|
+
const dependencyPath = "node_modules";
|
|
209
|
+
const callerPath = join(repositoryRoot, dependencyPath);
|
|
210
|
+
const targetPath = join(checkoutRoot, dependencyPath);
|
|
211
|
+
try {
|
|
212
|
+
lstatSync(callerPath);
|
|
213
|
+
}
|
|
214
|
+
catch {
|
|
215
|
+
return projected;
|
|
216
|
+
}
|
|
217
|
+
try {
|
|
218
|
+
lstatSync(targetPath);
|
|
219
|
+
return projected;
|
|
220
|
+
}
|
|
221
|
+
catch {
|
|
222
|
+
// Missing in the target archive means the caller dependency cache may be
|
|
223
|
+
// projected for local tooling without overwriting target-owned files.
|
|
224
|
+
}
|
|
225
|
+
symlinkSync(callerPath, targetPath, "dir");
|
|
226
|
+
projected.add(dependencyPath);
|
|
227
|
+
return projected;
|
|
228
|
+
}
|
|
@@ -107,6 +107,7 @@ export async function superviseReviewExecution(input, adapter, deps = {}) {
|
|
|
107
107
|
});
|
|
108
108
|
let status = null;
|
|
109
109
|
let failureCode;
|
|
110
|
+
let providerErrorDetail;
|
|
110
111
|
let limitWindow;
|
|
111
112
|
let retryAfterMs;
|
|
112
113
|
let result;
|
|
@@ -191,6 +192,7 @@ export async function superviseReviewExecution(input, adapter, deps = {}) {
|
|
|
191
192
|
failureCode = code;
|
|
192
193
|
limitWindow = metadata.limitWindow;
|
|
193
194
|
retryAfterMs = metadata.retryAfterMs;
|
|
195
|
+
providerErrorDetail = metadata.providerErrorDetail;
|
|
194
196
|
requestTermination();
|
|
195
197
|
finalize();
|
|
196
198
|
};
|
|
@@ -249,6 +251,7 @@ export async function superviseReviewExecution(input, adapter, deps = {}) {
|
|
|
249
251
|
fail("provider-failure", canonicalizeProviderFailureCode(event.code), {
|
|
250
252
|
...(event.limitWindow === undefined ? {} : { limitWindow: event.limitWindow }),
|
|
251
253
|
...(event.retryAfterMs === undefined ? {} : { retryAfterMs: event.retryAfterMs }),
|
|
254
|
+
...(event.message === undefined ? {} : { providerErrorDetail: event.message }),
|
|
252
255
|
});
|
|
253
256
|
return;
|
|
254
257
|
}
|
|
@@ -324,6 +327,7 @@ export async function superviseReviewExecution(input, adapter, deps = {}) {
|
|
|
324
327
|
fail("provider-failure", canonicalizeProviderFailureCode(event.code), {
|
|
325
328
|
...(event.limitWindow === undefined ? {} : { limitWindow: event.limitWindow }),
|
|
326
329
|
...(event.retryAfterMs === undefined ? {} : { retryAfterMs: event.retryAfterMs }),
|
|
330
|
+
...(event.message === undefined ? {} : { providerErrorDetail: event.message }),
|
|
327
331
|
});
|
|
328
332
|
}
|
|
329
333
|
};
|
|
@@ -425,6 +429,7 @@ export async function superviseReviewExecution(input, adapter, deps = {}) {
|
|
|
425
429
|
markerMatched,
|
|
426
430
|
...(result === undefined ? {} : { result }),
|
|
427
431
|
...(terminalFailureCode === undefined ? {} : { failureCode: terminalFailureCode }),
|
|
432
|
+
...(providerErrorDetail === undefined ? {} : { providerErrorDetail }),
|
|
428
433
|
...(limitWindow === undefined ? {} : { limitWindow }),
|
|
429
434
|
...(retryAfterMs === undefined ? {} : { retryAfterMs }),
|
|
430
435
|
summaryPath: artifacts.summaryPath,
|
|
@@ -19,6 +19,12 @@ export interface ReviewRunInput {
|
|
|
19
19
|
readonly cwd?: string;
|
|
20
20
|
readonly registryRoot?: string;
|
|
21
21
|
readonly signal?: AbortSignal;
|
|
22
|
+
/**
|
|
23
|
+
* Isolated opencode auth store directory for this spawn. When set, the
|
|
24
|
+
* opencode adapter injects it as `XDG_DATA_HOME` so the child reads a
|
|
25
|
+
* per-account `auth.json` instead of the user's global credential store.
|
|
26
|
+
*/
|
|
27
|
+
readonly authDataDir?: string;
|
|
22
28
|
}
|
|
23
29
|
export interface ReviewInvocation {
|
|
24
30
|
readonly command: string;
|
|
@@ -41,6 +47,7 @@ export type ReviewAdapterEvent = {
|
|
|
41
47
|
} | {
|
|
42
48
|
readonly kind: "failure";
|
|
43
49
|
readonly code: string;
|
|
50
|
+
readonly message?: string;
|
|
44
51
|
readonly limitWindow?: ReviewLimitWindow;
|
|
45
52
|
readonly retryAfterMs?: number;
|
|
46
53
|
} | {
|
|
@@ -70,6 +77,7 @@ export interface ReviewRunSummary {
|
|
|
70
77
|
readonly markerMatched: boolean | null;
|
|
71
78
|
readonly result?: string;
|
|
72
79
|
readonly failureCode?: string;
|
|
80
|
+
readonly providerErrorDetail?: string;
|
|
73
81
|
readonly limitWindow?: ReviewLimitWindow;
|
|
74
82
|
readonly retryAfterMs?: number;
|
|
75
83
|
readonly summaryPath: string;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type OpencodeAccountAvailabilityState } from "./availability.js";
|
|
2
|
+
export interface OpencodeAccount {
|
|
3
|
+
readonly id: string;
|
|
4
|
+
readonly secretName: string;
|
|
5
|
+
readonly key: string;
|
|
6
|
+
}
|
|
7
|
+
export interface OpencodeAccountSelection {
|
|
8
|
+
readonly account: OpencodeAccount;
|
|
9
|
+
readonly cooled: boolean;
|
|
10
|
+
readonly waitMs?: number;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Discover opencode-go accounts from Doppler-sourced secrets shaped
|
|
14
|
+
* `OPENCODE_GO_KEY_<ID>`. Sorted by id ascending, de-duplicated by id.
|
|
15
|
+
*/
|
|
16
|
+
export declare function resolveOpencodeAccounts(secrets: Readonly<Record<string, string>>): readonly OpencodeAccount[];
|
|
17
|
+
export declare function opencodeAccountCanonicalId(id: string): string;
|
|
18
|
+
export declare function opencodeAccountDataDir(id: string, stateRoot?: string): string;
|
|
19
|
+
/**
|
|
20
|
+
* Select the account to use for the next opencode-go invocation. Callers must
|
|
21
|
+
* prune expired suppressions in `state` before calling this (via
|
|
22
|
+
* `pruneExpiredOpencodeAccountSuppressions`).
|
|
23
|
+
*/
|
|
24
|
+
export declare function selectActiveOpencodeAccount(accounts: readonly OpencodeAccount[], state: OpencodeAccountAvailabilityState, nowMs?: number): OpencodeAccountSelection | undefined;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { getStateRoot } from "#paths/state-root.js";
|
|
3
|
+
import { isOpencodeAccountSuppressed, } from "./availability.js";
|
|
4
|
+
const OPENCODE_GO_KEY_PATTERN = /^OPENCODE_GO_KEY_([A-Za-z0-9]+)$/;
|
|
5
|
+
/**
|
|
6
|
+
* Discover opencode-go accounts from Doppler-sourced secrets shaped
|
|
7
|
+
* `OPENCODE_GO_KEY_<ID>`. Sorted by id ascending, de-duplicated by id.
|
|
8
|
+
*/
|
|
9
|
+
export function resolveOpencodeAccounts(secrets) {
|
|
10
|
+
const byId = new Map();
|
|
11
|
+
for (const [secretName, key] of Object.entries(secrets)) {
|
|
12
|
+
if (!key || key.trim().length === 0)
|
|
13
|
+
continue;
|
|
14
|
+
const match = OPENCODE_GO_KEY_PATTERN.exec(secretName);
|
|
15
|
+
if (!match?.[1])
|
|
16
|
+
continue;
|
|
17
|
+
const id = match[1].toLowerCase();
|
|
18
|
+
if (!byId.has(id))
|
|
19
|
+
byId.set(id, { id, secretName, key });
|
|
20
|
+
}
|
|
21
|
+
return [...byId.values()].sort((a, b) => a.id.localeCompare(b.id));
|
|
22
|
+
}
|
|
23
|
+
export function opencodeAccountCanonicalId(id) {
|
|
24
|
+
return `opencode-go:${id}`;
|
|
25
|
+
}
|
|
26
|
+
export function opencodeAccountDataDir(id, stateRoot) {
|
|
27
|
+
return path.join(stateRoot ?? getStateRoot(), "opencode-accounts", id);
|
|
28
|
+
}
|
|
29
|
+
function suppressionExpiryMs(state, canonicalId) {
|
|
30
|
+
const record = state.accounts[canonicalId];
|
|
31
|
+
if (record === undefined || record.lastFailureAt === undefined)
|
|
32
|
+
return undefined;
|
|
33
|
+
const failureAt = Date.parse(record.lastFailureAt);
|
|
34
|
+
if (!Number.isFinite(failureAt))
|
|
35
|
+
return undefined;
|
|
36
|
+
return failureAt + (record.retryAfterMs ?? 0);
|
|
37
|
+
}
|
|
38
|
+
function pickSoonestExpiring(accounts, state, nowMs) {
|
|
39
|
+
const withExpiry = accounts.map((account) => ({
|
|
40
|
+
account,
|
|
41
|
+
expiresAt: suppressionExpiryMs(state, opencodeAccountCanonicalId(account.id)) ??
|
|
42
|
+
Number.POSITIVE_INFINITY,
|
|
43
|
+
}));
|
|
44
|
+
// `accounts` is non-empty whenever this is called, so a no-initial-value
|
|
45
|
+
// reduce is safe and keeps the accumulator type undefined-free.
|
|
46
|
+
const soonest = withExpiry.reduce((min, candidate) => candidate.expiresAt < min.expiresAt ? candidate : min);
|
|
47
|
+
const waitMs = Number.isFinite(soonest.expiresAt)
|
|
48
|
+
? Math.max(0, soonest.expiresAt - nowMs)
|
|
49
|
+
: undefined;
|
|
50
|
+
return {
|
|
51
|
+
account: soonest.account,
|
|
52
|
+
cooled: true,
|
|
53
|
+
...(waitMs === undefined ? {} : { waitMs }),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Select the account to use for the next opencode-go invocation. Callers must
|
|
58
|
+
* prune expired suppressions in `state` before calling this (via
|
|
59
|
+
* `pruneExpiredOpencodeAccountSuppressions`).
|
|
60
|
+
*/
|
|
61
|
+
export function selectActiveOpencodeAccount(accounts, state, nowMs = Date.now()) {
|
|
62
|
+
if (accounts.length === 0)
|
|
63
|
+
return undefined;
|
|
64
|
+
const available = accounts.find((account) => !isOpencodeAccountSuppressed(state, opencodeAccountCanonicalId(account.id)));
|
|
65
|
+
if (available)
|
|
66
|
+
return { account: available, cooled: false };
|
|
67
|
+
return pickSoonestExpiring(accounts, state, nowMs);
|
|
68
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve opencode's `auth.json` path from an XDG-style data-home env var.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors the resolver in `src/cli/commands/dash/opencode-db.ts`
|
|
5
|
+
* (`opencodeDatabasePath`), but targets `opencode/auth.json` instead of
|
|
6
|
+
* `opencode/opencode.db`.
|
|
7
|
+
*/
|
|
8
|
+
export declare function opencodeAuthPath(env?: Readonly<Record<string, string | undefined>>, home?: string): string;
|
|
9
|
+
export interface MaterializeOpencodeGoAuthOptions {
|
|
10
|
+
readonly dataDir?: string;
|
|
11
|
+
readonly env?: Readonly<Record<string, string | undefined>>;
|
|
12
|
+
readonly home?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface MaterializeOpencodeGoAuthResult {
|
|
15
|
+
readonly path: string;
|
|
16
|
+
readonly wrote: boolean;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Read the `opencode-go` API key currently on disk, or `undefined` when the
|
|
20
|
+
* store is missing, corrupt, or holds no `opencode-go` entry.
|
|
21
|
+
*
|
|
22
|
+
* Callers use this to identify which account is currently materialized by
|
|
23
|
+
* matching the value against their own resolved account list — that keeps the
|
|
24
|
+
* "which account is active" question answerable without persisting extra state.
|
|
25
|
+
* Never throws.
|
|
26
|
+
*/
|
|
27
|
+
export declare function readOpencodeGoKey(options?: MaterializeOpencodeGoAuthOptions): string | undefined;
|
|
28
|
+
/**
|
|
29
|
+
* Merge an `opencode-go` API-key entry into opencode's `auth.json`, preserving
|
|
30
|
+
* every other provider entry already on disk. Never throws on a missing,
|
|
31
|
+
* unreadable, or corrupt existing file — that state is treated as `{}`.
|
|
32
|
+
*/
|
|
33
|
+
export declare function materializeOpencodeGoAuth(key: string, options?: MaterializeOpencodeGoAuthOptions): MaterializeOpencodeGoAuthResult;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { mkdirSync, readFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { writeJsonFileAtomic } from "#shared-utils/write-json-file.js";
|
|
5
|
+
/**
|
|
6
|
+
* Resolve opencode's `auth.json` path from an XDG-style data-home env var.
|
|
7
|
+
*
|
|
8
|
+
* Mirrors the resolver in `src/cli/commands/dash/opencode-db.ts`
|
|
9
|
+
* (`opencodeDatabasePath`), but targets `opencode/auth.json` instead of
|
|
10
|
+
* `opencode/opencode.db`.
|
|
11
|
+
*/
|
|
12
|
+
export function opencodeAuthPath(env = process.env, home = homedir()) {
|
|
13
|
+
const dataHome = env["XDG_DATA_HOME"]?.trim() || join(home, ".local", "share");
|
|
14
|
+
return join(dataHome, "opencode", "auth.json");
|
|
15
|
+
}
|
|
16
|
+
function resolveAuthPath(options) {
|
|
17
|
+
if (options.dataDir !== undefined) {
|
|
18
|
+
return join(options.dataDir, "opencode", "auth.json");
|
|
19
|
+
}
|
|
20
|
+
return opencodeAuthPath(options.env, options.home);
|
|
21
|
+
}
|
|
22
|
+
function readExistingAuth(path) {
|
|
23
|
+
let raw;
|
|
24
|
+
try {
|
|
25
|
+
raw = readFileSync(path, "utf8");
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return {};
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
const parsed = JSON.parse(raw);
|
|
32
|
+
if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
33
|
+
return parsed;
|
|
34
|
+
}
|
|
35
|
+
return {};
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return {};
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function isSameOpencodeGoEntry(entry, key) {
|
|
42
|
+
if (entry === null || typeof entry !== "object")
|
|
43
|
+
return false;
|
|
44
|
+
const candidate = entry;
|
|
45
|
+
return candidate["type"] === "api" && candidate["key"] === key;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Read the `opencode-go` API key currently on disk, or `undefined` when the
|
|
49
|
+
* store is missing, corrupt, or holds no `opencode-go` entry.
|
|
50
|
+
*
|
|
51
|
+
* Callers use this to identify which account is currently materialized by
|
|
52
|
+
* matching the value against their own resolved account list — that keeps the
|
|
53
|
+
* "which account is active" question answerable without persisting extra state.
|
|
54
|
+
* Never throws.
|
|
55
|
+
*/
|
|
56
|
+
export function readOpencodeGoKey(options = {}) {
|
|
57
|
+
const entry = readExistingAuth(resolveAuthPath(options))["opencode-go"];
|
|
58
|
+
if (entry === null || typeof entry !== "object")
|
|
59
|
+
return undefined;
|
|
60
|
+
const candidate = entry;
|
|
61
|
+
return typeof candidate["key"] === "string" ? candidate["key"] : undefined;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Merge an `opencode-go` API-key entry into opencode's `auth.json`, preserving
|
|
65
|
+
* every other provider entry already on disk. Never throws on a missing,
|
|
66
|
+
* unreadable, or corrupt existing file — that state is treated as `{}`.
|
|
67
|
+
*/
|
|
68
|
+
export function materializeOpencodeGoAuth(key, options = {}) {
|
|
69
|
+
const path = resolveAuthPath(options);
|
|
70
|
+
const existing = readExistingAuth(path);
|
|
71
|
+
if (isSameOpencodeGoEntry(existing["opencode-go"], key)) {
|
|
72
|
+
return { path, wrote: false };
|
|
73
|
+
}
|
|
74
|
+
const entry = { type: "api", key };
|
|
75
|
+
const next = { ...existing, "opencode-go": entry };
|
|
76
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
77
|
+
writeJsonFileAtomic(path, next, { writeFileOptions: { mode: 0o600 } });
|
|
78
|
+
return { path, wrote: true };
|
|
79
|
+
}
|
|
@@ -12,13 +12,40 @@ interface TreeEntry {
|
|
|
12
12
|
readonly path: Buffer;
|
|
13
13
|
}
|
|
14
14
|
export declare function resolveReviewCommit(cwd: string, ref: string): string;
|
|
15
|
+
/**
|
|
16
|
+
* Repo-relative paths with uncommitted changes (staged, unstaged, or
|
|
17
|
+
* untracked) that are NOT review evidence (reviews.md / review-events.jsonl /
|
|
18
|
+
* review-artifacts/*.md — see isReviewEvidenceRelativePath). A `wp-git-tree-v1`
|
|
19
|
+
* subject digest is computed purely from the committed tree at a ref, so any
|
|
20
|
+
* non-evidence path here means the working tree diverges from what that
|
|
21
|
+
* digest — and any approval bound to it — actually covers. Review evidence is
|
|
22
|
+
* excluded because the gate's own artifact/ledger writes routinely leave the
|
|
23
|
+
* tree dirty between reruns without representing unreviewed content.
|
|
24
|
+
*/
|
|
25
|
+
export declare function findUnreviewedWorkingTreeChanges(cwd: string): readonly string[];
|
|
15
26
|
declare function isExcludedReviewEnvelope(entry: TreeEntry, blueprintRoot: Buffer): boolean;
|
|
27
|
+
export declare function isReviewEvidenceRelativePath(value: string): boolean;
|
|
16
28
|
declare function isReviewEvidencePath(entry: TreeEntry): boolean;
|
|
17
29
|
declare function updateField(hash: Hash, value: Buffer | string): void;
|
|
18
30
|
export declare function createDeliverySubjectAtRef(cwd: string, ref: string, blueprintSlug: string, blueprintPath?: string): ReviewSubject;
|
|
19
31
|
export declare function createSourceSubjectAtRef(cwd: string, ref: string): ReviewSubject;
|
|
20
32
|
export declare function normalizePlanSubjectContent(content: Buffer | string): Buffer;
|
|
33
|
+
/**
|
|
34
|
+
* Plan-subject digest for in-memory (working-tree) blueprint content. Used to
|
|
35
|
+
* accept working-ledger review events that digest-bind the exact bytes being
|
|
36
|
+
* committed (pre-commit runs before those events reach any git ref).
|
|
37
|
+
*/
|
|
38
|
+
export declare function createPlanSubjectDigestForContent(blueprintSlug: string, content: Buffer | string): string;
|
|
39
|
+
/**
|
|
40
|
+
* Digest-compatibility variant for review events recorded before the
|
|
41
|
+
* stripped-key extension of `neutralizeLifecycleFrontmatter`. Verification
|
|
42
|
+
* accepts a match against either normalization; new digests always use
|
|
43
|
+
* `normalizePlanSubjectContent`.
|
|
44
|
+
*/
|
|
45
|
+
export declare function legacyNormalizePlanSubjectContent(content: Buffer | string): Buffer;
|
|
21
46
|
export declare function createPlanSubjectAtRef(cwd: string, ref: string, blueprintSlug: string, blueprintPath?: string): ReviewSubject;
|
|
47
|
+
/** Plan subject digested with the pre-extension normalization (see above). */
|
|
48
|
+
export declare function createLegacyPlanSubjectAtRef(cwd: string, ref: string, blueprintSlug: string, blueprintPath?: string): ReviewSubject;
|
|
22
49
|
export declare const reviewSubjectInternals: {
|
|
23
50
|
isExcludedReviewEnvelope: typeof isExcludedReviewEnvelope;
|
|
24
51
|
isReviewEvidencePath: typeof isReviewEvidencePath;
|