@webpresso/agent-kit 3.1.30 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +53 -15
- package/bin/_run.js +183 -20
- package/catalog/AGENTS.md.tpl +3 -3
- package/catalog/agent/agents/implementer.md +45 -0
- package/catalog/agent/rules/parallel-orchestration-quota.md +100 -0
- package/catalog/agent/rules/pre-implementation.md +28 -1
- package/catalog/agent/rules/supported-agent-clis.md +9 -0
- package/catalog/agent/rules/typescript-version.md +50 -0
- package/catalog/agent/skills/codex/SKILL.md +25 -1
- package/catalog/agent/skills/verify/SKILL.md +4 -2
- package/catalog/docs/templates/blueprint.md +1 -1
- package/dist/esm/audit/agents.js +1 -0
- package/dist/esm/audit/blueprint-lifecycle-sql.d.ts +12 -0
- package/dist/esm/audit/blueprint-lifecycle-sql.js +23 -2
- package/dist/esm/audit/blueprint-pr-coverage.js +4 -0
- package/dist/esm/audit/blueprint-trust.js +14 -10
- package/dist/esm/audit/changeset-evidence.d.ts +23 -0
- package/dist/esm/audit/changeset-evidence.js +30 -0
- package/dist/esm/audit/changeset-required.d.ts +10 -0
- package/dist/esm/audit/changeset-required.js +73 -0
- package/dist/esm/audit/ci-guardrails-detection.d.ts +27 -0
- package/dist/esm/audit/ci-guardrails-detection.js +68 -0
- package/dist/esm/audit/ci-guardrails-wiring.d.ts +22 -0
- package/dist/esm/audit/ci-guardrails-wiring.js +80 -0
- package/dist/esm/audit/ci-test-perf.js +30 -0
- package/dist/esm/audit/command-surface.js +4 -2
- package/dist/esm/audit/host-substitution-risk.d.ts +2 -0
- package/dist/esm/audit/host-substitution-risk.js +256 -0
- package/dist/esm/audit/registry.d.ts +4 -4
- package/dist/esm/audit/registry.js +13 -1
- package/dist/esm/audit/toolchain-isolation.js +1 -2
- package/dist/esm/audit/typescript-version.d.ts +2 -0
- package/dist/esm/audit/typescript-version.js +123 -0
- package/dist/esm/blueprint/core/parser.js +101 -2
- package/dist/esm/blueprint/core/schema.d.ts +1 -0
- package/dist/esm/blueprint/core/schema.js +26 -0
- package/dist/esm/blueprint/core/validation/criteria.d.ts +14 -0
- package/dist/esm/blueprint/core/validation/criteria.js +41 -2
- package/dist/esm/blueprint/core/validation/state.js +25 -14
- package/dist/esm/blueprint/db/migrations/run.js +4 -2
- package/dist/esm/blueprint/lifecycle/audit.d.ts +20 -0
- package/dist/esm/blueprint/lifecycle/audit.js +87 -20
- package/dist/esm/blueprint/lifecycle/canonicalize.d.ts +6 -0
- package/dist/esm/blueprint/lifecycle/canonicalize.js +135 -0
- package/dist/esm/blueprint/lifecycle/local.d.ts +2 -1
- package/dist/esm/blueprint/lifecycle/local.js +19 -3
- package/dist/esm/blueprint/lifecycle/review-provenance.d.ts +6 -0
- package/dist/esm/blueprint/lifecycle/review-provenance.js +68 -12
- package/dist/esm/blueprint/lifecycle/rollout-anchor.d.ts +97 -0
- package/dist/esm/blueprint/lifecycle/rollout-anchor.js +161 -0
- package/dist/esm/blueprint/service/BlueprintCreationService.d.ts +1 -0
- package/dist/esm/blueprint/service/BlueprintCreationService.js +53 -4
- package/dist/esm/blueprint/service/blueprint-tech-debt-links.js +16 -2
- package/dist/esm/blueprint/sync/client.d.ts +2 -1
- package/dist/esm/blueprint/sync/client.js +1 -1
- package/dist/esm/blueprint/trust/command-runner.d.ts +26 -0
- package/dist/esm/blueprint/trust/command-runner.js +148 -0
- package/dist/esm/blueprint/trust/dossier.d.ts +1 -0
- package/dist/esm/blueprint/trust/dossier.js +40 -6
- package/dist/esm/blueprint/trust/gates.d.ts +7 -0
- package/dist/esm/blueprint/trust/gates.js +20 -0
- package/dist/esm/blueprint/trust/promotion.d.ts +8 -0
- package/dist/esm/blueprint/trust/promotion.js +74 -112
- package/dist/esm/blueprint/trust/scaffold.d.ts +19 -0
- package/dist/esm/blueprint/trust/scaffold.js +64 -0
- package/dist/esm/blueprint/trust/validator.d.ts +1 -0
- package/dist/esm/blueprint/trust/validator.js +7 -1
- package/dist/esm/blueprint/utils/archive.js +27 -5
- package/dist/esm/blueprint/utils/document-paths.d.ts +18 -0
- package/dist/esm/blueprint/utils/document-paths.js +45 -12
- package/dist/esm/build/cli-mcp-parity.js +15 -0
- package/dist/esm/cli/bundle/commands/blueprint.d.ts +1 -0
- package/dist/esm/cli/bundle/commands/blueprint.js +1 -0
- package/dist/esm/cli/cli.d.ts +1 -1
- package/dist/esm/cli/cli.js +43 -0
- package/dist/esm/cli/commands/agent-launch.d.ts +8 -1
- package/dist/esm/cli/commands/agent-launch.js +93 -4
- package/dist/esm/cli/commands/audit-core.d.ts +1 -1
- package/dist/esm/cli/commands/audit-core.js +8 -1
- package/dist/esm/cli/commands/audit.js +21 -5
- package/dist/esm/cli/commands/blueprint/execution.js +18 -6
- package/dist/esm/cli/commands/blueprint/mutations.js +59 -40
- package/dist/esm/cli/commands/blueprint/router-dispatch.d.ts +2 -1
- package/dist/esm/cli/commands/blueprint/router-dispatch.js +20 -1
- package/dist/esm/cli/commands/blueprint/router-output.d.ts +21 -0
- package/dist/esm/cli/commands/blueprint/router-output.js +48 -17
- package/dist/esm/cli/commands/blueprint/router.d.ts +7 -0
- package/dist/esm/cli/commands/blueprint/router.js +13 -1
- package/dist/esm/cli/commands/ci-preflight.d.ts +13 -2
- package/dist/esm/cli/commands/ci-preflight.js +30 -6
- package/dist/esm/cli/commands/compile.js +5 -31
- package/dist/esm/cli/commands/dash/delivery-status.d.ts +1 -0
- package/dist/esm/cli/commands/dash/delivery-status.js +11 -4
- package/dist/esm/cli/commands/doctor.js +5 -0
- package/dist/esm/cli/commands/hook.d.ts +5 -0
- package/dist/esm/cli/commands/hook.js +15 -9
- package/dist/esm/cli/commands/init/config.js +2 -2
- package/dist/esm/cli/commands/init/convergence-apply.d.ts +88 -0
- package/dist/esm/cli/commands/init/convergence-apply.js +325 -0
- package/dist/esm/cli/commands/init/convergence-state.d.ts +13 -0
- package/dist/esm/cli/commands/init/convergence-state.js +54 -0
- package/dist/esm/cli/commands/init/convergence.d.ts +23 -0
- package/dist/esm/cli/commands/init/convergence.js +57 -0
- package/dist/esm/cli/commands/init/git-convergence.d.ts +19 -0
- package/dist/esm/cli/commands/init/git-convergence.js +168 -0
- package/dist/esm/cli/commands/init/gitignore-patcher.js +2 -0
- package/dist/esm/cli/commands/init/grok-privacy-smoke.d.ts +52 -0
- package/dist/esm/cli/commands/init/grok-privacy-smoke.js +91 -0
- package/dist/esm/cli/commands/init/host-native-proof.d.ts +18 -0
- package/dist/esm/cli/commands/init/host-native-proof.js +88 -0
- package/dist/esm/cli/commands/init/host-visibility.d.ts +2 -2
- package/dist/esm/cli/commands/init/host-visibility.js +94 -5
- package/dist/esm/cli/commands/init/index.d.ts +3 -0
- package/dist/esm/cli/commands/init/index.js +326 -173
- package/dist/esm/cli/commands/init/mcp-spec.d.ts +57 -0
- package/dist/esm/cli/commands/init/mcp-spec.js +273 -0
- package/dist/esm/cli/commands/init/merge.d.ts +1 -0
- package/dist/esm/cli/commands/init/merge.js +30 -1
- package/dist/esm/cli/commands/init/package-root.d.ts +7 -0
- package/dist/esm/cli/commands/init/package-root.js +10 -2
- package/dist/esm/cli/commands/init/plugin-cache-prune.d.ts +0 -3
- package/dist/esm/cli/commands/init/plugin-cache-prune.js +0 -1
- package/dist/esm/cli/commands/init/scaffold-agents-md.d.ts +1 -1
- package/dist/esm/cli/commands/init/scaffold-agents-md.js +9 -3
- package/dist/esm/cli/commands/init/scaffold-base-kit.d.ts +20 -0
- package/dist/esm/cli/commands/init/scaffold-base-kit.js +61 -5
- package/dist/esm/cli/commands/init/scaffolders/agent-hooks/emitters/grok.d.ts +14 -8
- package/dist/esm/cli/commands/init/scaffolders/agent-hooks/emitters/grok.js +41 -24
- package/dist/esm/cli/commands/init/scaffolders/agent-hooks/index.d.ts +2 -0
- package/dist/esm/cli/commands/init/scaffolders/agent-hooks/index.js +54 -24
- package/dist/esm/cli/commands/init/scaffolders/codex-mcp/index.d.ts +12 -31
- package/dist/esm/cli/commands/init/scaffolders/codex-mcp/index.js +288 -202
- package/dist/esm/cli/commands/init/scaffolders/file-transaction.d.ts +22 -0
- package/dist/esm/cli/commands/init/scaffolders/file-transaction.js +230 -0
- package/dist/esm/cli/commands/init/scaffolders/grok-mcp/index.d.ts +38 -0
- package/dist/esm/cli/commands/init/scaffolders/grok-mcp/index.js +73 -0
- package/dist/esm/cli/commands/init/scaffolders/opencode-plugin/index.d.ts +21 -0
- package/dist/esm/cli/commands/init/scaffolders/opencode-plugin/index.js +559 -63
- package/dist/esm/cli/commands/init/untracked-collision.d.ts +14 -0
- package/dist/esm/cli/commands/init/untracked-collision.js +42 -0
- package/dist/esm/cli/commands/opencode-probe.d.ts +107 -0
- package/dist/esm/cli/commands/opencode-probe.js +323 -0
- package/dist/esm/cli/commands/opencode-rotate.d.ts +72 -0
- package/dist/esm/cli/commands/opencode-rotate.js +147 -0
- package/dist/esm/cli/commands/public-readiness.d.ts +11 -0
- package/dist/esm/cli/commands/public-readiness.js +42 -0
- package/dist/esm/cli/commands/quality-runner.js +3 -3
- package/dist/esm/cli/commands/review.d.ts +35 -3
- package/dist/esm/cli/commands/review.js +751 -209
- package/dist/esm/cli/commands/run-wait.d.ts +2 -0
- package/dist/esm/cli/commands/run-wait.js +122 -0
- package/dist/esm/cli/commands/rust-check.d.ts +24 -0
- package/dist/esm/cli/commands/rust-check.js +110 -0
- package/dist/esm/cli/commands/setup-help.js +1 -1
- package/dist/esm/cli/commands/setup-repair-help.js +1 -1
- package/dist/esm/cli/commands/typecheck.d.ts +1 -0
- package/dist/esm/cli/commands/typecheck.js +30 -1
- package/dist/esm/cli/commands/worktree/router-dispatch.d.ts +6 -0
- package/dist/esm/cli/commands/worktree/router-dispatch.js +126 -9
- package/dist/esm/cli/commands/worktree/router.js +10 -0
- package/dist/esm/cli/direct-provider-launch.js +9 -5
- package/dist/esm/cli/optional-tool-freshness.d.ts +27 -0
- package/dist/esm/cli/optional-tool-freshness.js +219 -9
- package/dist/esm/daemon/domains/git-actions.d.ts +29 -0
- package/dist/esm/daemon/domains/git-actions.js +136 -0
- package/dist/esm/daemon/domains/run-registry-interop.d.ts +16 -0
- package/dist/esm/daemon/domains/run-registry-interop.js +45 -0
- package/dist/esm/daemon/domains/sessions-search.d.ts +11 -0
- package/dist/esm/daemon/domains/sessions-search.js +29 -0
- package/dist/esm/daemon/handlers/blueprints-handlers.d.ts +5 -0
- package/dist/esm/daemon/handlers/blueprints-handlers.js +36 -0
- package/dist/esm/daemon/handlers/deploy-handlers.d.ts +6 -0
- package/dist/esm/daemon/handlers/deploy-handlers.js +5 -0
- package/dist/esm/daemon/handlers/fleet-handlers.d.ts +15 -0
- package/dist/esm/daemon/handlers/fleet-handlers.js +91 -0
- package/dist/esm/daemon/handlers/git-handlers.d.ts +6 -0
- package/dist/esm/daemon/handlers/git-handlers.js +27 -0
- package/dist/esm/daemon/handlers/product.d.ts +7 -0
- package/dist/esm/daemon/handlers/product.js +10 -119
- package/dist/esm/daemon/handlers/sessions-handlers.d.ts +12 -0
- package/dist/esm/daemon/handlers/sessions-handlers.js +31 -0
- package/dist/esm/daemon/handlers/shared.d.ts +5 -0
- package/dist/esm/daemon/handlers/shared.js +16 -0
- package/dist/esm/daemon/methods.d.ts +8 -1
- package/dist/esm/daemon/methods.js +7 -0
- package/dist/esm/daemon/protocol/contract.d.ts +43 -0
- package/dist/esm/daemon/protocol/contract.js +26 -0
- package/dist/esm/daemon/protocol/index.d.ts +1 -1
- package/dist/esm/daemon/protocol/index.js +1 -1
- package/dist/esm/hooks/__conformance__/matrix.d.ts +6 -0
- package/dist/esm/hooks/__conformance__/matrix.js +137 -0
- package/dist/esm/hooks/conformance/batch.js +5 -0
- package/dist/esm/hooks/doctor.d.ts +5 -3
- package/dist/esm/hooks/doctor.js +67 -16
- package/dist/esm/hooks/permission-request/index.js +9 -3
- package/dist/esm/hooks/permission-request/policy.d.ts +9 -0
- package/dist/esm/hooks/permission-request/policy.js +13 -0
- package/dist/esm/hooks/pretool-guard/dev-routing.d.ts +9 -0
- package/dist/esm/hooks/pretool-guard/dev-routing.js +41 -5
- package/dist/esm/hooks/pretool-guard/logger.d.ts +7 -0
- package/dist/esm/hooks/pretool-guard/logger.js +16 -0
- package/dist/esm/hooks/pretool-guard/runner.d.ts +6 -0
- package/dist/esm/hooks/pretool-guard/runner.js +68 -16
- package/dist/esm/hooks/pretool-guard/validators/blueprint.js +30 -10
- package/dist/esm/hooks/pretool-guard/validators/forbidden-commands.js +54 -0
- package/dist/esm/hooks/pretool-guard/validators/index.d.ts +7 -0
- package/dist/esm/hooks/pretool-guard/validators/index.js +21 -0
- package/dist/esm/hooks/pretool-guard/validators/path-contract.d.ts +19 -0
- package/dist/esm/hooks/pretool-guard/validators/path-contract.js +25 -8
- package/dist/esm/hooks/pretool-guard/validators/worktree-discipline.js +6 -1
- package/dist/esm/hooks/shared/guard-outcome.d.ts +149 -0
- package/dist/esm/hooks/shared/guard-outcome.js +215 -0
- package/dist/esm/hooks/shared/hook-bootstrap.d.ts +26 -0
- package/dist/esm/hooks/shared/hook-bootstrap.js +64 -1
- package/dist/esm/hooks/shared/types.d.ts +8 -5
- package/dist/esm/hooks/shared/types.js +50 -9
- package/dist/esm/hooks/stop/qa-changed-files.d.ts +11 -0
- package/dist/esm/hooks/stop/qa-changed-files.js +92 -0
- package/dist/esm/mcp/blueprint/_shared/lifecycle.js +14 -1
- package/dist/esm/mcp/blueprint/handlers/document-mutations.d.ts +5 -0
- package/dist/esm/mcp/blueprint/handlers/document-mutations.js +109 -11
- package/dist/esm/mcp/blueprint/handlers/review-log.js +8 -1
- package/dist/esm/mcp/blueprint/handlers/task-advance.js +113 -54
- package/dist/esm/mcp/blueprint/handlers/task-verify.js +39 -19
- package/dist/esm/mcp/blueprint/registration.js +3 -2
- package/dist/esm/mcp/server.d.ts +1 -0
- package/dist/esm/mcp/server.js +27 -0
- package/dist/esm/mcp/tools/_names.d.ts +1 -1
- package/dist/esm/mcp/tools/_names.js +5 -0
- package/dist/esm/mcp/tools/_registry.js +10 -0
- package/dist/esm/mcp/tools/_shared/audit-kinds.d.ts +1 -1
- package/dist/esm/mcp/tools/_shared/result.js +44 -1
- package/dist/esm/mcp/tools/audit.d.ts +5 -1
- package/dist/esm/mcp/tools/audit.js +42 -0
- package/dist/esm/mcp/tools/audits.d.ts +4 -0
- package/dist/esm/mcp/tools/ci-preflight.d.ts +50 -0
- package/dist/esm/mcp/tools/ci-preflight.js +183 -0
- package/dist/esm/mcp/tools/pr-upsert.js +39 -1
- package/dist/esm/mcp/tools/review-run.d.ts +28 -0
- package/dist/esm/mcp/tools/review-run.js +216 -0
- package/dist/esm/mcp/tools/run-wait.d.ts +86 -0
- package/dist/esm/mcp/tools/run-wait.js +593 -0
- package/dist/esm/mcp/tools/session-fetch-and-index.d.ts +1 -1
- package/dist/esm/mcp/tools/session-id.d.ts +3 -0
- package/dist/esm/mcp/tools/session-id.js +45 -0
- package/dist/esm/mcp/tools/session-info.d.ts +3 -0
- package/dist/esm/mcp/tools/session-info.js +49 -0
- package/dist/esm/output-transforms/audit.d.ts +23 -0
- package/dist/esm/output-transforms/audit.js +47 -0
- package/dist/esm/output-transforms/index.js +2 -0
- package/dist/esm/package.json +2 -0
- package/dist/esm/platform/client.d.ts +7 -1
- package/dist/esm/platform/github-contract-source.d.ts +2 -1
- package/dist/esm/pr-description/contract.js +24 -4
- package/dist/esm/review/authority.js +27 -14
- package/dist/esm/review/availability.d.ts +33 -0
- package/dist/esm/review/availability.js +122 -20
- package/dist/esm/review/delivery-verifier.d.ts +27 -0
- package/dist/esm/review/delivery-verifier.js +115 -0
- package/dist/esm/review/events.d.ts +3 -2
- package/dist/esm/review/events.js +23 -3
- package/dist/esm/review/execution/adapters.d.ts +11 -1
- package/dist/esm/review/execution/adapters.js +36 -10
- package/dist/esm/review/execution/artifacts.d.ts +1 -1
- package/dist/esm/review/execution/output-policy.d.ts +2 -0
- package/dist/esm/review/execution/output-policy.js +12 -0
- package/dist/esm/review/execution/review-checkout.js +121 -21
- package/dist/esm/review/execution/supervisor.js +36 -4
- package/dist/esm/review/execution/types.d.ts +9 -0
- package/dist/esm/review/opencode-account.d.ts +24 -0
- package/dist/esm/review/opencode-account.js +68 -0
- package/dist/esm/review/opencode-auth.d.ts +33 -0
- package/dist/esm/review/opencode-auth.js +79 -0
- package/dist/esm/review/subject.d.ts +25 -0
- package/dist/esm/review/subject.js +116 -26
- package/dist/esm/runtime/opencode-account-materializer.d.ts +34 -0
- package/dist/esm/runtime/opencode-account-materializer.js +33 -0
- package/dist/esm/rust/affected-manifests.d.ts +38 -0
- package/dist/esm/rust/affected-manifests.js +69 -0
- package/dist/esm/session-memory/fetch-index.d.ts +1 -1
- package/dist/esm/status/snapshot.js +32 -25
- package/dist/esm/test/duration-sequencer.d.ts +20 -0
- package/dist/esm/test/duration-sequencer.js +54 -0
- package/dist/esm/test/shard-durations.json +922 -0
- package/dist/esm/test/worker-budget.d.ts +6 -0
- package/dist/esm/test/worker-budget.js +59 -1
- package/dist/esm/test-helpers/global-setup.d.ts +1 -2
- package/dist/esm/test-helpers/global-setup.js +79 -32
- package/dist/esm/typecheck/planner.d.ts +9 -1
- package/dist/esm/typecheck/planner.js +31 -0
- package/dist/esm/ultragoal/service.d.ts +7 -0
- package/dist/esm/ultragoal/service.js +42 -2
- package/dist/esm/utils/write-json-file.js +3 -1
- package/dist/esm/worktrees/codex-project-trust.d.ts +15 -0
- package/dist/esm/worktrees/codex-project-trust.js +56 -0
- package/dist/esm/worktrees/orphan-scan.d.ts +50 -0
- package/dist/esm/worktrees/orphan-scan.js +211 -0
- package/dist/esm/worktrees/registry.d.ts +8 -0
- package/dist/esm/worktrees/registry.js +18 -3
- package/package.json +18 -13
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
export declare const MAINTAINER_OVERRIDE_REVIEWER = "maintainer-override";
|
|
2
3
|
export declare const reviewEventSchema: z.ZodObject<{
|
|
3
4
|
schemaVersion: z.ZodLiteral<2>;
|
|
4
5
|
id: z.ZodString;
|
|
@@ -16,12 +17,12 @@ export declare const reviewEventSchema: z.ZodObject<{
|
|
|
16
17
|
gateId: z.ZodString;
|
|
17
18
|
promptHash: z.ZodString;
|
|
18
19
|
reviewer: z.ZodString;
|
|
19
|
-
provider: z.ZodEnum<{
|
|
20
|
+
provider: z.ZodOptional<z.ZodEnum<{
|
|
20
21
|
claude: "claude";
|
|
21
22
|
codex: "codex";
|
|
22
23
|
grok: "grok";
|
|
23
24
|
opencode: "opencode";
|
|
24
|
-
}
|
|
25
|
+
}>>;
|
|
25
26
|
model: z.ZodOptional<z.ZodString>;
|
|
26
27
|
effort: z.ZodOptional<z.ZodEnum<{
|
|
27
28
|
high: "high";
|
|
@@ -9,6 +9,14 @@ const SHA256_PATTERN = /^[a-f0-9]{64}$/u;
|
|
|
9
9
|
const COMMIT_PATTERN = /^(?:[a-f0-9]{40}|[a-f0-9]{64})$/u;
|
|
10
10
|
const BLUEPRINT_SLUG_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*(?:\/[a-z0-9]+(?:-[a-z0-9]+)*)*$/u;
|
|
11
11
|
const ARTIFACT_PATH_PATTERN = /^review-artifacts\/[a-zA-Z0-9._-]+\.md$/u;
|
|
12
|
+
// The honest, auditable escape hatch reviewer id. A maintainer override is a
|
|
13
|
+
// HUMAN adjudication (not a machine backend), recorded only by `wp review
|
|
14
|
+
// override` after a real strong-reviewer rejection of the same subject. It
|
|
15
|
+
// satisfies the promotion gate DISTINCTLY (never folded into a strong or
|
|
16
|
+
// OpenCode-Go approval). Defined here — the lowest review-core module — so both
|
|
17
|
+
// the ledger read-time policy below and the lifecycle approval gate share one
|
|
18
|
+
// source of truth without an import cycle.
|
|
19
|
+
export const MAINTAINER_OVERRIDE_REVIEWER = "maintainer-override";
|
|
12
20
|
export const reviewEventSchema = z
|
|
13
21
|
.object({
|
|
14
22
|
schemaVersion: z.literal(2),
|
|
@@ -21,7 +29,10 @@ export const reviewEventSchema = z
|
|
|
21
29
|
gateId: z.string().min(1).max(120),
|
|
22
30
|
promptHash: z.string().regex(SHA256_PATTERN),
|
|
23
31
|
reviewer: z.string().min(1).max(120),
|
|
24
|
-
|
|
32
|
+
// Optional: present for machine-backend reviews (always set by `wp review
|
|
33
|
+
// gate`/`log`); absent for a human maintainer-override, which has no AI
|
|
34
|
+
// backend. Descriptive metadata only — no policy keys off `provider`.
|
|
35
|
+
provider: z.enum(["claude", "codex", "opencode", "grok"]).optional(),
|
|
25
36
|
model: z.string().min(1).max(240).optional(),
|
|
26
37
|
effort: z.enum(["medium", "high"]).optional(),
|
|
27
38
|
stage: z.string().min(1).max(120),
|
|
@@ -81,7 +92,7 @@ function orderedEvent(event) {
|
|
|
81
92
|
gateId: event.gateId,
|
|
82
93
|
promptHash: event.promptHash,
|
|
83
94
|
reviewer: event.reviewer,
|
|
84
|
-
provider: event.provider,
|
|
95
|
+
...(event.provider ? { provider: event.provider } : {}),
|
|
85
96
|
...(event.model ? { model: event.model } : {}),
|
|
86
97
|
...(event.effort ? { effort: event.effort } : {}),
|
|
87
98
|
stage: event.stage,
|
|
@@ -164,10 +175,19 @@ function isManualMarker(value) {
|
|
|
164
175
|
// dodge the policy while real gate ids (content hashes) can never collide.
|
|
165
176
|
return value.trim().toLowerCase() === "manual";
|
|
166
177
|
}
|
|
178
|
+
// Reviewers whose manual-stage approvals are legitimate governance decisions,
|
|
179
|
+
// not disguised machine approvals: a plain `human` review and a
|
|
180
|
+
// `maintainer-override` (a maintainer's hand-recorded adjudication). Both are
|
|
181
|
+
// human authority by construction, so a manual marker on their events is
|
|
182
|
+
// expected rather than a policy dodge.
|
|
183
|
+
const HUMAN_LIKE_REVIEWERS = new Set(["human", MAINTAINER_OVERRIDE_REVIEWER]);
|
|
184
|
+
function isHumanLikeReviewer(reviewer) {
|
|
185
|
+
return HUMAN_LIKE_REVIEWERS.has(canonicalReviewer(reviewer));
|
|
186
|
+
}
|
|
167
187
|
function isManualMachineApproval(event) {
|
|
168
188
|
return (isPolicyApprovalVerdict(event.verdict) &&
|
|
169
189
|
(isManualMarker(event.gateId) || isManualMarker(event.stage)) &&
|
|
170
|
-
|
|
190
|
+
!isHumanLikeReviewer(event.reviewer));
|
|
171
191
|
}
|
|
172
192
|
// `compromisedIds` marks events whose artifact evidence failed integrity
|
|
173
193
|
// verification. They still PARTICIPATE in reviewer supersession — a later
|
|
@@ -1,4 +1,14 @@
|
|
|
1
|
-
import type { ReviewRunInput, ReviewStreamAdapter } from "./types.js";
|
|
1
|
+
import type { ReviewAdapterEvent, ReviewRunInput, ReviewStreamAdapter } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Classify an OpenCode Go usage-limit message and derive its real reset window.
|
|
4
|
+
*
|
|
5
|
+
* Exported because the `wp opencode` launch-time health probe
|
|
6
|
+
* (`#cli/commands/opencode-probe.js`) runs the same provider text through the
|
|
7
|
+
* same classifier. A second copy of this wording/duration math would drift the
|
|
8
|
+
* moment the provider changes a phrase, and the two consumers would then
|
|
9
|
+
* disagree about whether an account is capped.
|
|
10
|
+
*/
|
|
11
|
+
export declare function opencodeUsageLimit(message: string): ReviewAdapterEvent | null;
|
|
2
12
|
export declare const claudeReviewAdapter: ReviewStreamAdapter;
|
|
3
13
|
export declare const codexReviewAdapter: ReviewStreamAdapter;
|
|
4
14
|
export declare const opencodeReviewAdapter: ReviewStreamAdapter;
|
|
@@ -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)
|
|
@@ -80,7 +92,16 @@ function parseResetDurationMs(message) {
|
|
|
80
92
|
}
|
|
81
93
|
return duration > 0 ? duration : undefined;
|
|
82
94
|
}
|
|
83
|
-
|
|
95
|
+
/**
|
|
96
|
+
* Classify an OpenCode Go usage-limit message and derive its real reset window.
|
|
97
|
+
*
|
|
98
|
+
* Exported because the `wp opencode` launch-time health probe
|
|
99
|
+
* (`#cli/commands/opencode-probe.js`) runs the same provider text through the
|
|
100
|
+
* same classifier. A second copy of this wording/duration math would drift the
|
|
101
|
+
* moment the provider changes a phrase, and the two consumers would then
|
|
102
|
+
* disagree about whether an account is capped.
|
|
103
|
+
*/
|
|
104
|
+
export function opencodeUsageLimit(message) {
|
|
84
105
|
if (!/\busage limit reached\b/iu.test(message))
|
|
85
106
|
return null;
|
|
86
107
|
const limitWindow = /\bweekly usage limit reached\b/iu.test(message)
|
|
@@ -94,6 +115,7 @@ function opencodeUsageLimit(message) {
|
|
|
94
115
|
return {
|
|
95
116
|
kind: "failure",
|
|
96
117
|
code: "opencode-go-usage-limit",
|
|
118
|
+
message: sanitizeProviderErrorDetail(message),
|
|
97
119
|
limitWindow,
|
|
98
120
|
...(retryAfterMs === undefined ? {} : { retryAfterMs }),
|
|
99
121
|
};
|
|
@@ -104,7 +126,7 @@ function claudeEvent(value) {
|
|
|
104
126
|
return { kind: "unsupported" };
|
|
105
127
|
if (record.type === "result") {
|
|
106
128
|
if (record.is_error === true || record.subtype === "error") {
|
|
107
|
-
return
|
|
129
|
+
return providerFailureEvent(record, failureCode(record));
|
|
108
130
|
}
|
|
109
131
|
return {
|
|
110
132
|
kind: "terminal",
|
|
@@ -112,7 +134,7 @@ function claudeEvent(value) {
|
|
|
112
134
|
};
|
|
113
135
|
}
|
|
114
136
|
if (record.type === "error")
|
|
115
|
-
return
|
|
137
|
+
return providerFailureEvent(record, failureCode(record));
|
|
116
138
|
if (record.type === "rate_limit_event") {
|
|
117
139
|
const rateLimit = objectValue(record.rate_limit_info);
|
|
118
140
|
return rateLimit?.status === "rejected"
|
|
@@ -127,7 +149,7 @@ function claudeEvent(value) {
|
|
|
127
149
|
}
|
|
128
150
|
if (record.type === "assistant") {
|
|
129
151
|
return typeof record.error === "string"
|
|
130
|
-
?
|
|
152
|
+
? providerFailureEvent(record, failureCode(record), record.error)
|
|
131
153
|
: { kind: "progress", frontier: claudeEnvelopeFrontier(record) };
|
|
132
154
|
}
|
|
133
155
|
if (record.type !== "stream_event")
|
|
@@ -151,7 +173,7 @@ function claudeEvent(value) {
|
|
|
151
173
|
return { kind: "progress", frontier: stableFrontier(event) };
|
|
152
174
|
}
|
|
153
175
|
if (event.type === "error")
|
|
154
|
-
return
|
|
176
|
+
return providerFailureEvent(event, failureCode(event));
|
|
155
177
|
return { kind: "unsupported" };
|
|
156
178
|
}
|
|
157
179
|
function opencodeText(record) {
|
|
@@ -216,7 +238,7 @@ function opencodeEvent(value) {
|
|
|
216
238
|
if (record.type === "error") {
|
|
217
239
|
const message = providerErrorMessage(record);
|
|
218
240
|
const usageLimit = message === null ? null : opencodeUsageLimit(message);
|
|
219
|
-
return usageLimit ??
|
|
241
|
+
return usageLimit ?? providerFailureEvent(record, failureCode(record), message);
|
|
220
242
|
}
|
|
221
243
|
// OpenCode's non-interactive JSON protocol closes a completed run with
|
|
222
244
|
// session.idle or step_finish(reason: "stop"); recorded Kimi and DeepSeek
|
|
@@ -293,10 +315,10 @@ function codexEvent(value) {
|
|
|
293
315
|
: message && /rate.?limit|quota/iu.test(message)
|
|
294
316
|
? "rate-limit-rejected"
|
|
295
317
|
: "provider-error");
|
|
296
|
-
return
|
|
318
|
+
return providerFailureEvent(record, code, message);
|
|
297
319
|
}
|
|
298
320
|
if (type.includes("rate_limit") && /reject|limit|quota/iu.test(message ?? "")) {
|
|
299
|
-
return
|
|
321
|
+
return providerFailureEvent(record, "rate-limit-rejected", message);
|
|
300
322
|
}
|
|
301
323
|
if (type === "result" ||
|
|
302
324
|
type === "final" ||
|
|
@@ -399,6 +421,10 @@ export const opencodeReviewAdapter = {
|
|
|
399
421
|
OPENCODE_CONFIG_CONTENT: OPENCODE_REVIEW_CONFIG,
|
|
400
422
|
OPENCODE_DISABLE_AUTOUPDATE: "true",
|
|
401
423
|
OPENCODE_DISABLE_PRUNE: "true",
|
|
424
|
+
// Isolate the credential store per account: the spawn reads
|
|
425
|
+
// `<authDataDir>/opencode/auth.json` instead of the user's global file,
|
|
426
|
+
// so mid-flight account failover never races a live interactive TUI.
|
|
427
|
+
...(input.authDataDir ? { XDG_DATA_HOME: input.authDataDir } : {}),
|
|
402
428
|
},
|
|
403
429
|
cleanup: attachment.cleanup,
|
|
404
430
|
};
|
|
@@ -506,7 +532,7 @@ export const grokReviewAdapter = {
|
|
|
506
532
|
return {
|
|
507
533
|
kind: "failure",
|
|
508
534
|
code: "provider-failed",
|
|
509
|
-
message,
|
|
535
|
+
message: sanitizeProviderErrorDetail(message),
|
|
510
536
|
};
|
|
511
537
|
}
|
|
512
538
|
return { kind: "progress", frontier: type || "event" };
|
|
@@ -14,4 +14,4 @@ export interface ReviewArtifactWriter {
|
|
|
14
14
|
export declare function reviewRunStatePath(root: string, runId: string): string;
|
|
15
15
|
export type ReviewRunStateCleanupResult = "missing" | "not-running" | "removed" | "refused";
|
|
16
16
|
export declare function removeOwnedRunningReviewState(root: string, runId: string, reservationToken: string): ReviewRunStateCleanupResult;
|
|
17
|
-
export declare function createReviewArtifactWriter(root: string, runId: string, startedAt: string, reservationToken?:
|
|
17
|
+
export declare function createReviewArtifactWriter(root: string, runId: string, startedAt: string, reservationToken?: string): ReviewArtifactWriter;
|
|
@@ -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,
|
|
@@ -23,24 +23,34 @@ function frame(hash, label, value) {
|
|
|
23
23
|
hash.update(`${label}\0${bytes.byteLength}\0`, "utf8");
|
|
24
24
|
hash.update(bytes);
|
|
25
25
|
}
|
|
26
|
-
|
|
26
|
+
// Fingerprint over the filtered line set `status()` produces below, minus
|
|
27
|
+
// pure gitignored (`!!`) entries, so the two contamination checks
|
|
28
|
+
// (`changes()`'s status() diff and the caller worktree fingerprint) cannot
|
|
29
|
+
// drift apart on what counts as "source" content while still tolerating
|
|
30
|
+
// expected runtime bookkeeping.
|
|
31
|
+
//
|
|
32
|
+
// Before the status()-alignment fix, the fingerprint hashed the raw
|
|
33
|
+
// unfiltered `git status` output (without `--ignored`), so anything status()
|
|
34
|
+
// already tolerates (the self-hosting bootstrap's idempotent `package.json`
|
|
35
|
+
// trailing-whitespace normalization, and non-source ignored paths under
|
|
36
|
+
// dist/.tshy/node_modules) could still flip the fingerprint and false-flag a
|
|
37
|
+
// clean caller worktree as contaminated.
|
|
38
|
+
//
|
|
39
|
+
// Reusing status() alone re-introduces a different false positive: the
|
|
40
|
+
// supervisor writes process records under gitignored `.webpresso/` (and similar
|
|
41
|
+
// runtime roots) during the run. Those appear as `!!` lines only when
|
|
42
|
+
// `--ignored` is on. Dropping `!!` here restores the prior fingerprint
|
|
43
|
+
// contract for ignored paths while keeping the package.json / non-`!!` filters
|
|
44
|
+
// shared with status(). Checkout-side status() still reports non-derived
|
|
45
|
+
// gitignored writes (e.g. a stray `.env`) on the disposable review tree.
|
|
46
|
+
function worktreeFingerprint(cwd, ignoredPaths = new Set()) {
|
|
27
47
|
const hash = createHash("sha256");
|
|
28
|
-
const
|
|
29
|
-
frame(hash, "
|
|
48
|
+
const lines = status(cwd, ignoredPaths).filter((line) => !line.startsWith("!!"));
|
|
49
|
+
frame(hash, "status-lines", lines.join("\n"));
|
|
30
50
|
frame(hash, "staged", gitBytes(cwd, ["diff", "--cached", "--binary", "--no-ext-diff", "HEAD", "--"]));
|
|
31
|
-
const records = porcelain.split("\0").filter(Boolean);
|
|
32
51
|
const dirtyPaths = new Set();
|
|
33
|
-
for (
|
|
34
|
-
|
|
35
|
-
dirtyPaths.add(record.slice(3));
|
|
36
|
-
if (record.slice(0, 2).match(/[RC]/u)) {
|
|
37
|
-
const pairedPath = records[index + 1];
|
|
38
|
-
if (pairedPath) {
|
|
39
|
-
dirtyPaths.add(pairedPath);
|
|
40
|
-
index += 1;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
}
|
|
52
|
+
for (const line of lines)
|
|
53
|
+
dirtyPaths.add(statusEntryPath(line));
|
|
44
54
|
for (const relativePath of [...dirtyPaths].filter(Boolean).toSorted()) {
|
|
45
55
|
const absolutePath = join(cwd, relativePath);
|
|
46
56
|
frame(hash, "dirty-path", relativePath);
|
|
@@ -60,12 +70,78 @@ function worktreeFingerprint(cwd) {
|
|
|
60
70
|
}
|
|
61
71
|
return hash.digest("hex");
|
|
62
72
|
}
|
|
63
|
-
|
|
73
|
+
// Path segments that mark gitignored content as NON-source: derived build
|
|
74
|
+
// outputs a Promotion Gate command (`./bin/wp typecheck`/`test`/etc.)
|
|
75
|
+
// regenerates while self-hosting this repo (`dist/`, `.tshy/`) and the
|
|
76
|
+
// reconstructed dependency tree (`node_modules`, already separately allowlisted
|
|
77
|
+
// via `ignoredPaths` when symlinked — kept here too for depth-safety, e.g. a
|
|
78
|
+
// nested per-workspace-package `node_modules`). A gitignored (`!!`) path whose
|
|
79
|
+
// ANY segment is one of these — at any workspace depth (e.g. `packages/x/dist`,
|
|
80
|
+
// `.tshy/build.log`) — is exempt from the contamination check. Confirmed
|
|
81
|
+
// empirically that the gate commands emit gitignored output exclusively under
|
|
82
|
+
// these roots.
|
|
83
|
+
//
|
|
84
|
+
// SCOPE (deliberate, documented): this verifier enforces the review TARGET's
|
|
85
|
+
// *source* integrity, not its build artifacts. It does not flag writes under a
|
|
86
|
+
// derived/dependency root, because such output can reach neither the caller
|
|
87
|
+
// nor the reviewed source (the checkout is a disposable tmpdir). Everything
|
|
88
|
+
// else stays strict: a tracked mutation (` M `/` D `), a new untracked source
|
|
89
|
+
// file (`?? `), and any NON-derived gitignored write (e.g. a stray `.env`) all
|
|
90
|
+
// still surface.
|
|
91
|
+
const NON_SOURCE_IGNORED_SEGMENTS = new Set(["dist", ".tshy", "node_modules"]);
|
|
92
|
+
function isNonSourceIgnoredPath(relativePath) {
|
|
93
|
+
return relativePath.split("/").some((segment) => NON_SOURCE_IGNORED_SEGMENTS.has(segment));
|
|
94
|
+
}
|
|
95
|
+
// This repo self-hosts: a Promotion Gate command's first `./bin/wp` invocation
|
|
96
|
+
// triggers wp's own bootstrap build, which runs tshy and then
|
|
97
|
+
// `./bin/wp format --file package.json` — an IDEMPOTENT normalization (e.g.
|
|
98
|
+
// adding a missing trailing newline). Re-running it a second time produces
|
|
99
|
+
// byte-identical output modulo trailing whitespace, so it is not a source
|
|
100
|
+
// mutation. Exempt ONLY that exact shape: a plain, unstaged content
|
|
101
|
+
// modification (` M `) of a file literally named `package.json` whose
|
|
102
|
+
// tracked-vs-working-tree bytes differ solely in trailing whitespace. Any
|
|
103
|
+
// other package.json change (dependency/script/field edits, staged changes,
|
|
104
|
+
// adds, deletes) still fails closed — this check never inspects JSON
|
|
105
|
+
// semantics, only trailing-whitespace-normalized byte equality.
|
|
106
|
+
function isNormalizedPackageJsonModification(cwd, line) {
|
|
107
|
+
if (!line.startsWith(" M "))
|
|
108
|
+
return false;
|
|
109
|
+
const relativePath = line.slice(3);
|
|
110
|
+
if (basename(relativePath) !== "package.json")
|
|
111
|
+
return false;
|
|
112
|
+
try {
|
|
113
|
+
const working = readFileSync(join(cwd, relativePath), "utf8");
|
|
114
|
+
const tracked = git(cwd, ["show", `HEAD:${relativePath}`]);
|
|
115
|
+
return working.replace(/\s+$/u, "") === tracked.replace(/\s+$/u, "");
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
// Single source of truth for turning a `git status --porcelain=v1` line into
|
|
122
|
+
// its relative path, shared by status()'s filter predicate and
|
|
123
|
+
// worktreeFingerprint()'s dirty-path hashing so both stay aligned by
|
|
124
|
+
// construction. Does not special-case the rename arrow (`old -> new`); a
|
|
125
|
+
// rename line's raw tail is used as-is, matching this function's prior
|
|
126
|
+
// (pre-refactor) inline behavior.
|
|
127
|
+
function statusEntryPath(line) {
|
|
128
|
+
return line.slice(3).replace(/\/$/u, "");
|
|
129
|
+
}
|
|
130
|
+
function status(cwd, ignoredPaths = new Set()) {
|
|
64
131
|
const output = git(cwd, ["status", "--porcelain=v1", "--untracked-files=all", "--ignored"]);
|
|
65
132
|
return output
|
|
66
133
|
.split(/\r?\n/u)
|
|
67
|
-
.
|
|
68
|
-
.filter((line) =>
|
|
134
|
+
.filter((line) => line.length > 0)
|
|
135
|
+
.filter((line) => {
|
|
136
|
+
const relativePath = statusEntryPath(line);
|
|
137
|
+
if (ignoredPaths.has(relativePath))
|
|
138
|
+
return false;
|
|
139
|
+
if (line.startsWith("!!") && isNonSourceIgnoredPath(relativePath))
|
|
140
|
+
return false;
|
|
141
|
+
if (isNormalizedPackageJsonModification(cwd, line))
|
|
142
|
+
return false;
|
|
143
|
+
return true;
|
|
144
|
+
});
|
|
69
145
|
}
|
|
70
146
|
function neutralizeSymlinks(directory) {
|
|
71
147
|
for (const entry of readdirSync(directory, { withFileTypes: true })) {
|
|
@@ -111,6 +187,7 @@ export function createReviewTargetCheckout(repositoryRoot, targetSha) {
|
|
|
111
187
|
git(cwd, ["update-ref", "HEAD", baselineHead]);
|
|
112
188
|
if (status(cwd).length > 0)
|
|
113
189
|
throw new Error("review target checkout was not clean at start");
|
|
190
|
+
const injectedPaths = projectCallerDependencies(repositoryRoot, cwd);
|
|
114
191
|
let cleaned = false;
|
|
115
192
|
return {
|
|
116
193
|
cwd,
|
|
@@ -119,7 +196,7 @@ export function createReviewTargetCheckout(repositoryRoot, targetSha) {
|
|
|
119
196
|
},
|
|
120
197
|
changes: () => {
|
|
121
198
|
try {
|
|
122
|
-
const changes = [...status(cwd)];
|
|
199
|
+
const changes = [...status(cwd, injectedPaths)];
|
|
123
200
|
if (git(cwd, ["rev-parse", "HEAD"]).trim() !== baselineHead) {
|
|
124
201
|
changes.push("review checkout HEAD changed");
|
|
125
202
|
}
|
|
@@ -145,3 +222,26 @@ export function createReviewTargetCheckout(repositoryRoot, targetSha) {
|
|
|
145
222
|
throw error;
|
|
146
223
|
}
|
|
147
224
|
}
|
|
225
|
+
function projectCallerDependencies(repositoryRoot, checkoutRoot) {
|
|
226
|
+
const projected = new Set();
|
|
227
|
+
const dependencyPath = "node_modules";
|
|
228
|
+
const callerPath = join(repositoryRoot, dependencyPath);
|
|
229
|
+
const targetPath = join(checkoutRoot, dependencyPath);
|
|
230
|
+
try {
|
|
231
|
+
lstatSync(callerPath);
|
|
232
|
+
}
|
|
233
|
+
catch {
|
|
234
|
+
return projected;
|
|
235
|
+
}
|
|
236
|
+
try {
|
|
237
|
+
lstatSync(targetPath);
|
|
238
|
+
return projected;
|
|
239
|
+
}
|
|
240
|
+
catch {
|
|
241
|
+
// Missing in the target archive means the caller dependency cache may be
|
|
242
|
+
// projected for local tooling without overwriting target-owned files.
|
|
243
|
+
}
|
|
244
|
+
symlinkSync(callerPath, targetPath, "dir");
|
|
245
|
+
projected.add(dependencyPath);
|
|
246
|
+
return projected;
|
|
247
|
+
}
|
|
@@ -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;
|
|
@@ -122,6 +123,10 @@ export async function superviseReviewExecution(input, adapter, deps = {}) {
|
|
|
122
123
|
let fallbackSelectionTimer;
|
|
123
124
|
let pendingFallback;
|
|
124
125
|
const seenFrontiers = new Set();
|
|
126
|
+
const droppedEventWarnings = [];
|
|
127
|
+
const recordDroppedProviderEvent = (reason) => {
|
|
128
|
+
droppedEventWarnings.push(reason);
|
|
129
|
+
};
|
|
125
130
|
const decoder = new StringDecoder("utf8");
|
|
126
131
|
const diagnosticDecoder = new StringDecoder("utf8");
|
|
127
132
|
let buffered = "";
|
|
@@ -191,6 +196,7 @@ export async function superviseReviewExecution(input, adapter, deps = {}) {
|
|
|
191
196
|
failureCode = code;
|
|
192
197
|
limitWindow = metadata.limitWindow;
|
|
193
198
|
retryAfterMs = metadata.retryAfterMs;
|
|
199
|
+
providerErrorDetail = metadata.providerErrorDetail;
|
|
194
200
|
requestTermination();
|
|
195
201
|
finalize();
|
|
196
202
|
};
|
|
@@ -228,19 +234,41 @@ export async function superviseReviewExecution(input, adapter, deps = {}) {
|
|
|
228
234
|
const processLine = (line) => {
|
|
229
235
|
if (line.trim().length === 0 || status !== null)
|
|
230
236
|
return;
|
|
231
|
-
|
|
232
|
-
fail("protocol-unsupported", "provider-event-too-large");
|
|
233
|
-
return;
|
|
234
|
-
}
|
|
237
|
+
const oversized = Buffer.byteLength(line, "utf8") > MAX_PROVIDER_EVENT_BYTES;
|
|
235
238
|
let value;
|
|
236
239
|
try {
|
|
237
240
|
value = JSON.parse(line);
|
|
238
241
|
}
|
|
239
242
|
catch {
|
|
243
|
+
// An oversized frame that also fails to parse carries no recoverable
|
|
244
|
+
// signal (a genuinely truncated/malformed line looks the same either
|
|
245
|
+
// way) -- drop it with a warning instead of failing a run that may
|
|
246
|
+
// still receive a complete, well-formed terminal verdict later. A
|
|
247
|
+
// normal-sized malformed line is still a protocol violation.
|
|
248
|
+
if (oversized) {
|
|
249
|
+
recordDroppedProviderEvent("provider-event-too-large-unparseable");
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
240
252
|
fail("protocol-unsupported", "invalid-json-event");
|
|
241
253
|
return;
|
|
242
254
|
}
|
|
243
255
|
const event = adapter.parseEvent(value);
|
|
256
|
+
if (oversized && (event.kind === "terminal" || event.kind === "failure")) {
|
|
257
|
+
// Terminal/verdict and failure frames are load-bearing: an oversized
|
|
258
|
+
// one is treated as a protocol violation, same as before this fix.
|
|
259
|
+
fail("protocol-unsupported", "provider-event-too-large");
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
if (oversized) {
|
|
263
|
+
// Non-terminal (progress/neutral/unsupported) oversized frames are
|
|
264
|
+
// dropped rather than failing the whole run -- a provider that echoes
|
|
265
|
+
// e.g. a large lockfile read in a progress frame must not discard an
|
|
266
|
+
// otherwise-complete review verdict. See catalog/agent/rules
|
|
267
|
+
// /no-timeout-as-fix.md: the byte bound stays in place, we just stop
|
|
268
|
+
// treating "one big frame" as fatal to the whole run.
|
|
269
|
+
recordDroppedProviderEvent(`provider-event-too-large-${event.kind}`);
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
244
272
|
if (event.kind === "unsupported") {
|
|
245
273
|
fail("protocol-unsupported", "unknown-provider-event");
|
|
246
274
|
return;
|
|
@@ -249,6 +277,7 @@ export async function superviseReviewExecution(input, adapter, deps = {}) {
|
|
|
249
277
|
fail("provider-failure", canonicalizeProviderFailureCode(event.code), {
|
|
250
278
|
...(event.limitWindow === undefined ? {} : { limitWindow: event.limitWindow }),
|
|
251
279
|
...(event.retryAfterMs === undefined ? {} : { retryAfterMs: event.retryAfterMs }),
|
|
280
|
+
...(event.message === undefined ? {} : { providerErrorDetail: event.message }),
|
|
252
281
|
});
|
|
253
282
|
return;
|
|
254
283
|
}
|
|
@@ -324,6 +353,7 @@ export async function superviseReviewExecution(input, adapter, deps = {}) {
|
|
|
324
353
|
fail("provider-failure", canonicalizeProviderFailureCode(event.code), {
|
|
325
354
|
...(event.limitWindow === undefined ? {} : { limitWindow: event.limitWindow }),
|
|
326
355
|
...(event.retryAfterMs === undefined ? {} : { retryAfterMs: event.retryAfterMs }),
|
|
356
|
+
...(event.message === undefined ? {} : { providerErrorDetail: event.message }),
|
|
327
357
|
});
|
|
328
358
|
}
|
|
329
359
|
};
|
|
@@ -425,9 +455,11 @@ export async function superviseReviewExecution(input, adapter, deps = {}) {
|
|
|
425
455
|
markerMatched,
|
|
426
456
|
...(result === undefined ? {} : { result }),
|
|
427
457
|
...(terminalFailureCode === undefined ? {} : { failureCode: terminalFailureCode }),
|
|
458
|
+
...(providerErrorDetail === undefined ? {} : { providerErrorDetail }),
|
|
428
459
|
...(limitWindow === undefined ? {} : { limitWindow }),
|
|
429
460
|
...(retryAfterMs === undefined ? {} : { retryAfterMs }),
|
|
430
461
|
summaryPath: artifacts.summaryPath,
|
|
462
|
+
...(droppedEventWarnings.length > 0 ? { warnings: [...droppedEventWarnings] } : {}),
|
|
431
463
|
};
|
|
432
464
|
}
|
|
433
465
|
function recordReviewProcessStart(reviewChild, processGroupId) {
|
|
@@ -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,7 +77,9 @@ 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;
|
|
84
|
+
readonly warnings?: readonly string[];
|
|
76
85
|
}
|
|
@@ -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;
|