@webpresso/agent-kit 3.1.30 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of @webpresso/agent-kit might be problematic. Click here for more details.
- package/README.md +53 -15
- package/bin/_run.js +179 -19
- package/catalog/AGENTS.md.tpl +3 -3
- package/catalog/agent/agents/implementer.md +45 -0
- package/catalog/agent/rules/parallel-orchestration-quota.md +100 -0
- package/catalog/agent/rules/pre-implementation.md +28 -1
- package/catalog/agent/rules/supported-agent-clis.md +9 -0
- package/catalog/agent/rules/typescript-version.md +50 -0
- package/catalog/agent/skills/codex/SKILL.md +25 -1
- package/catalog/agent/skills/verify/SKILL.md +4 -2
- package/catalog/docs/templates/blueprint.md +1 -1
- package/dist/esm/audit/agents.js +1 -0
- package/dist/esm/audit/blueprint-lifecycle-sql.d.ts +12 -0
- package/dist/esm/audit/blueprint-lifecycle-sql.js +23 -2
- package/dist/esm/audit/blueprint-pr-coverage.js +4 -0
- package/dist/esm/audit/ci-guardrails-wiring.d.ts +22 -0
- package/dist/esm/audit/ci-guardrails-wiring.js +90 -0
- package/dist/esm/audit/ci-test-perf.js +30 -0
- package/dist/esm/audit/command-surface.js +4 -2
- package/dist/esm/audit/host-substitution-risk.d.ts +2 -0
- package/dist/esm/audit/host-substitution-risk.js +256 -0
- package/dist/esm/audit/registry.d.ts +4 -4
- package/dist/esm/audit/registry.js +8 -1
- package/dist/esm/audit/toolchain-isolation.js +1 -2
- package/dist/esm/audit/typescript-version.d.ts +2 -0
- package/dist/esm/audit/typescript-version.js +123 -0
- package/dist/esm/blueprint/core/parser.js +43 -1
- package/dist/esm/blueprint/core/validation/criteria.d.ts +14 -0
- package/dist/esm/blueprint/core/validation/criteria.js +41 -2
- package/dist/esm/blueprint/core/validation/state.js +25 -14
- package/dist/esm/blueprint/db/migrations/run.js +4 -2
- package/dist/esm/blueprint/lifecycle/audit.d.ts +20 -0
- package/dist/esm/blueprint/lifecycle/audit.js +65 -10
- package/dist/esm/blueprint/lifecycle/canonicalize.d.ts +6 -0
- package/dist/esm/blueprint/lifecycle/canonicalize.js +135 -0
- package/dist/esm/blueprint/lifecycle/local.d.ts +2 -1
- package/dist/esm/blueprint/lifecycle/local.js +19 -3
- package/dist/esm/blueprint/lifecycle/review-provenance.js +36 -11
- package/dist/esm/blueprint/service/BlueprintCreationService.d.ts +1 -0
- package/dist/esm/blueprint/service/BlueprintCreationService.js +53 -4
- package/dist/esm/blueprint/service/blueprint-tech-debt-links.js +16 -2
- package/dist/esm/blueprint/trust/command-runner.d.ts +26 -0
- package/dist/esm/blueprint/trust/command-runner.js +148 -0
- package/dist/esm/blueprint/trust/gates.d.ts +7 -0
- package/dist/esm/blueprint/trust/gates.js +20 -0
- package/dist/esm/blueprint/trust/promotion.d.ts +8 -0
- package/dist/esm/blueprint/trust/promotion.js +62 -112
- package/dist/esm/blueprint/trust/scaffold.d.ts +19 -0
- package/dist/esm/blueprint/trust/scaffold.js +64 -0
- package/dist/esm/blueprint/trust/validator.d.ts +1 -0
- package/dist/esm/blueprint/trust/validator.js +7 -1
- package/dist/esm/blueprint/utils/archive.js +27 -5
- package/dist/esm/blueprint/utils/document-paths.d.ts +18 -0
- package/dist/esm/blueprint/utils/document-paths.js +45 -12
- package/dist/esm/build/cli-mcp-parity.js +10 -0
- package/dist/esm/cli/bundle/commands/blueprint.d.ts +1 -0
- package/dist/esm/cli/bundle/commands/blueprint.js +1 -0
- package/dist/esm/cli/cli.d.ts +1 -1
- package/dist/esm/cli/cli.js +34 -0
- package/dist/esm/cli/commands/agent-launch.d.ts +6 -1
- package/dist/esm/cli/commands/agent-launch.js +82 -4
- package/dist/esm/cli/commands/audit-core.d.ts +1 -1
- package/dist/esm/cli/commands/audit-core.js +8 -1
- package/dist/esm/cli/commands/audit.js +17 -5
- package/dist/esm/cli/commands/blueprint/execution.js +18 -6
- package/dist/esm/cli/commands/blueprint/mutations.js +59 -40
- package/dist/esm/cli/commands/blueprint/router-dispatch.d.ts +2 -1
- package/dist/esm/cli/commands/blueprint/router-dispatch.js +20 -1
- package/dist/esm/cli/commands/blueprint/router-output.d.ts +21 -0
- package/dist/esm/cli/commands/blueprint/router-output.js +48 -17
- package/dist/esm/cli/commands/blueprint/router.d.ts +7 -0
- package/dist/esm/cli/commands/blueprint/router.js +13 -1
- package/dist/esm/cli/commands/ci-preflight.d.ts +13 -2
- package/dist/esm/cli/commands/ci-preflight.js +6 -3
- package/dist/esm/cli/commands/compile.js +5 -31
- package/dist/esm/cli/commands/dash/delivery-status.d.ts +1 -0
- package/dist/esm/cli/commands/dash/delivery-status.js +11 -4
- package/dist/esm/cli/commands/doctor.js +5 -0
- package/dist/esm/cli/commands/hook.d.ts +5 -0
- package/dist/esm/cli/commands/hook.js +15 -9
- package/dist/esm/cli/commands/init/config.js +2 -2
- package/dist/esm/cli/commands/init/convergence-apply.d.ts +24 -0
- package/dist/esm/cli/commands/init/convergence-apply.js +221 -0
- package/dist/esm/cli/commands/init/convergence-state.d.ts +13 -0
- package/dist/esm/cli/commands/init/convergence-state.js +54 -0
- package/dist/esm/cli/commands/init/convergence.d.ts +23 -0
- package/dist/esm/cli/commands/init/convergence.js +57 -0
- package/dist/esm/cli/commands/init/git-convergence.d.ts +19 -0
- package/dist/esm/cli/commands/init/git-convergence.js +168 -0
- package/dist/esm/cli/commands/init/gitignore-patcher.js +2 -0
- package/dist/esm/cli/commands/init/grok-privacy-smoke.d.ts +52 -0
- package/dist/esm/cli/commands/init/grok-privacy-smoke.js +91 -0
- package/dist/esm/cli/commands/init/host-native-proof.d.ts +18 -0
- package/dist/esm/cli/commands/init/host-native-proof.js +88 -0
- package/dist/esm/cli/commands/init/host-visibility.d.ts +2 -2
- package/dist/esm/cli/commands/init/host-visibility.js +94 -5
- package/dist/esm/cli/commands/init/index.d.ts +1 -0
- package/dist/esm/cli/commands/init/index.js +326 -173
- package/dist/esm/cli/commands/init/mcp-spec.d.ts +57 -0
- package/dist/esm/cli/commands/init/mcp-spec.js +273 -0
- package/dist/esm/cli/commands/init/merge.d.ts +1 -0
- package/dist/esm/cli/commands/init/merge.js +30 -1
- package/dist/esm/cli/commands/init/plugin-cache-prune.d.ts +0 -3
- package/dist/esm/cli/commands/init/plugin-cache-prune.js +0 -1
- package/dist/esm/cli/commands/init/scaffold-agents-md.d.ts +1 -1
- package/dist/esm/cli/commands/init/scaffold-agents-md.js +9 -3
- package/dist/esm/cli/commands/init/scaffold-base-kit.d.ts +11 -0
- package/dist/esm/cli/commands/init/scaffold-base-kit.js +56 -1
- package/dist/esm/cli/commands/init/scaffolders/agent-hooks/emitters/grok.d.ts +14 -8
- package/dist/esm/cli/commands/init/scaffolders/agent-hooks/emitters/grok.js +41 -24
- package/dist/esm/cli/commands/init/scaffolders/agent-hooks/index.d.ts +2 -0
- package/dist/esm/cli/commands/init/scaffolders/agent-hooks/index.js +51 -23
- package/dist/esm/cli/commands/init/scaffolders/codex-mcp/index.d.ts +12 -31
- package/dist/esm/cli/commands/init/scaffolders/codex-mcp/index.js +288 -202
- package/dist/esm/cli/commands/init/scaffolders/file-transaction.d.ts +7 -0
- package/dist/esm/cli/commands/init/scaffolders/file-transaction.js +58 -0
- package/dist/esm/cli/commands/init/scaffolders/grok-mcp/index.d.ts +38 -0
- package/dist/esm/cli/commands/init/scaffolders/grok-mcp/index.js +73 -0
- package/dist/esm/cli/commands/init/scaffolders/opencode-plugin/index.d.ts +21 -0
- package/dist/esm/cli/commands/init/scaffolders/opencode-plugin/index.js +559 -63
- package/dist/esm/cli/commands/init/untracked-collision.d.ts +14 -0
- package/dist/esm/cli/commands/init/untracked-collision.js +42 -0
- package/dist/esm/cli/commands/opencode-rotate.d.ts +49 -0
- package/dist/esm/cli/commands/opencode-rotate.js +135 -0
- package/dist/esm/cli/commands/public-readiness.d.ts +11 -0
- package/dist/esm/cli/commands/public-readiness.js +42 -0
- package/dist/esm/cli/commands/review.d.ts +35 -3
- package/dist/esm/cli/commands/review.js +729 -209
- package/dist/esm/cli/commands/run-wait.d.ts +2 -0
- package/dist/esm/cli/commands/run-wait.js +122 -0
- package/dist/esm/cli/commands/setup-help.js +1 -1
- package/dist/esm/cli/commands/setup-repair-help.js +1 -1
- package/dist/esm/cli/commands/worktree/router-dispatch.js +13 -8
- package/dist/esm/cli/direct-provider-launch.js +9 -5
- package/dist/esm/cli/optional-tool-freshness.d.ts +27 -0
- package/dist/esm/cli/optional-tool-freshness.js +219 -9
- package/dist/esm/daemon/domains/git-actions.d.ts +29 -0
- package/dist/esm/daemon/domains/git-actions.js +136 -0
- package/dist/esm/daemon/domains/run-registry-interop.d.ts +16 -0
- package/dist/esm/daemon/domains/run-registry-interop.js +45 -0
- package/dist/esm/daemon/domains/sessions-search.d.ts +11 -0
- package/dist/esm/daemon/domains/sessions-search.js +29 -0
- package/dist/esm/daemon/handlers/blueprints-handlers.d.ts +5 -0
- package/dist/esm/daemon/handlers/blueprints-handlers.js +36 -0
- package/dist/esm/daemon/handlers/deploy-handlers.d.ts +6 -0
- package/dist/esm/daemon/handlers/deploy-handlers.js +5 -0
- package/dist/esm/daemon/handlers/fleet-handlers.d.ts +15 -0
- package/dist/esm/daemon/handlers/fleet-handlers.js +91 -0
- package/dist/esm/daemon/handlers/git-handlers.d.ts +6 -0
- package/dist/esm/daemon/handlers/git-handlers.js +27 -0
- package/dist/esm/daemon/handlers/product.d.ts +7 -0
- package/dist/esm/daemon/handlers/product.js +10 -119
- package/dist/esm/daemon/handlers/sessions-handlers.d.ts +12 -0
- package/dist/esm/daemon/handlers/sessions-handlers.js +31 -0
- package/dist/esm/daemon/handlers/shared.d.ts +5 -0
- package/dist/esm/daemon/handlers/shared.js +16 -0
- package/dist/esm/daemon/methods.d.ts +8 -1
- package/dist/esm/daemon/methods.js +7 -0
- package/dist/esm/daemon/protocol/contract.d.ts +43 -0
- package/dist/esm/daemon/protocol/contract.js +26 -0
- package/dist/esm/daemon/protocol/index.d.ts +1 -1
- package/dist/esm/daemon/protocol/index.js +1 -1
- package/dist/esm/hooks/__conformance__/matrix.d.ts +6 -0
- package/dist/esm/hooks/__conformance__/matrix.js +137 -0
- package/dist/esm/hooks/conformance/batch.js +5 -0
- package/dist/esm/hooks/doctor.d.ts +3 -3
- package/dist/esm/hooks/doctor.js +30 -15
- package/dist/esm/hooks/permission-request/index.js +9 -3
- package/dist/esm/hooks/permission-request/policy.d.ts +9 -0
- package/dist/esm/hooks/permission-request/policy.js +13 -0
- package/dist/esm/hooks/pretool-guard/dev-routing.d.ts +9 -0
- package/dist/esm/hooks/pretool-guard/dev-routing.js +41 -5
- package/dist/esm/hooks/pretool-guard/logger.d.ts +7 -0
- package/dist/esm/hooks/pretool-guard/logger.js +16 -0
- package/dist/esm/hooks/pretool-guard/runner.d.ts +6 -0
- package/dist/esm/hooks/pretool-guard/runner.js +68 -16
- package/dist/esm/hooks/pretool-guard/validators/blueprint.js +30 -10
- package/dist/esm/hooks/pretool-guard/validators/forbidden-commands.js +54 -0
- package/dist/esm/hooks/pretool-guard/validators/index.d.ts +7 -0
- package/dist/esm/hooks/pretool-guard/validators/index.js +21 -0
- package/dist/esm/hooks/pretool-guard/validators/path-contract.d.ts +19 -0
- package/dist/esm/hooks/pretool-guard/validators/path-contract.js +25 -8
- package/dist/esm/hooks/shared/guard-outcome.d.ts +149 -0
- package/dist/esm/hooks/shared/guard-outcome.js +215 -0
- package/dist/esm/hooks/shared/hook-bootstrap.d.ts +26 -0
- package/dist/esm/hooks/shared/hook-bootstrap.js +64 -1
- package/dist/esm/hooks/shared/types.d.ts +8 -5
- package/dist/esm/hooks/shared/types.js +23 -8
- package/dist/esm/hooks/stop/qa-changed-files.d.ts +11 -0
- package/dist/esm/hooks/stop/qa-changed-files.js +74 -0
- package/dist/esm/mcp/blueprint/_shared/lifecycle.js +14 -1
- package/dist/esm/mcp/blueprint/handlers/document-mutations.js +78 -4
- package/dist/esm/mcp/blueprint/handlers/review-log.js +8 -1
- package/dist/esm/mcp/blueprint/handlers/task-advance.js +113 -54
- package/dist/esm/mcp/blueprint/handlers/task-verify.js +39 -19
- package/dist/esm/mcp/blueprint/registration.js +3 -2
- package/dist/esm/mcp/server.d.ts +1 -0
- package/dist/esm/mcp/server.js +27 -0
- package/dist/esm/mcp/tools/_names.d.ts +1 -1
- package/dist/esm/mcp/tools/_names.js +5 -0
- package/dist/esm/mcp/tools/_registry.js +10 -0
- package/dist/esm/mcp/tools/_shared/audit-kinds.d.ts +1 -1
- package/dist/esm/mcp/tools/_shared/result.js +44 -1
- package/dist/esm/mcp/tools/audit.d.ts +4 -1
- package/dist/esm/mcp/tools/audit.js +30 -0
- package/dist/esm/mcp/tools/audits.d.ts +3 -0
- package/dist/esm/mcp/tools/ci-preflight.d.ts +50 -0
- package/dist/esm/mcp/tools/ci-preflight.js +183 -0
- package/dist/esm/mcp/tools/pr-upsert.js +39 -1
- package/dist/esm/mcp/tools/review-run.d.ts +28 -0
- package/dist/esm/mcp/tools/review-run.js +216 -0
- package/dist/esm/mcp/tools/run-wait.d.ts +86 -0
- package/dist/esm/mcp/tools/run-wait.js +593 -0
- package/dist/esm/mcp/tools/session-id.d.ts +3 -0
- package/dist/esm/mcp/tools/session-id.js +45 -0
- package/dist/esm/mcp/tools/session-info.d.ts +3 -0
- package/dist/esm/mcp/tools/session-info.js +49 -0
- package/dist/esm/output-transforms/audit.d.ts +23 -0
- package/dist/esm/output-transforms/audit.js +47 -0
- package/dist/esm/output-transforms/index.js +2 -0
- package/dist/esm/review/authority.js +13 -5
- package/dist/esm/review/availability.d.ts +22 -0
- package/dist/esm/review/availability.js +107 -20
- package/dist/esm/review/delivery-verifier.d.ts +27 -0
- package/dist/esm/review/delivery-verifier.js +115 -0
- package/dist/esm/review/events.d.ts +3 -2
- package/dist/esm/review/events.js +23 -3
- package/dist/esm/review/execution/adapters.js +26 -9
- package/dist/esm/review/execution/output-policy.d.ts +2 -0
- package/dist/esm/review/execution/output-policy.js +12 -0
- package/dist/esm/review/execution/review-checkout.js +87 -6
- package/dist/esm/review/execution/supervisor.js +5 -0
- package/dist/esm/review/execution/types.d.ts +8 -0
- package/dist/esm/review/opencode-account.d.ts +24 -0
- package/dist/esm/review/opencode-account.js +68 -0
- package/dist/esm/review/opencode-auth.d.ts +33 -0
- package/dist/esm/review/opencode-auth.js +79 -0
- package/dist/esm/review/subject.d.ts +12 -0
- package/dist/esm/review/subject.js +74 -22
- package/dist/esm/runtime/opencode-account-materializer.d.ts +34 -0
- package/dist/esm/runtime/opencode-account-materializer.js +33 -0
- package/dist/esm/status/snapshot.js +30 -19
- package/dist/esm/test/duration-sequencer.d.ts +20 -0
- package/dist/esm/test/duration-sequencer.js +54 -0
- package/dist/esm/test/shard-durations.json +922 -0
- package/dist/esm/test/worker-budget.d.ts +6 -0
- package/dist/esm/test/worker-budget.js +59 -1
- package/dist/esm/test-helpers/global-setup.d.ts +1 -1
- package/dist/esm/test-helpers/global-setup.js +68 -24
- package/dist/esm/ultragoal/service.d.ts +7 -0
- package/dist/esm/ultragoal/service.js +42 -2
- package/dist/esm/utils/write-json-file.js +3 -1
- package/dist/esm/worktrees/codex-project-trust.d.ts +15 -0
- package/dist/esm/worktrees/codex-project-trust.js +56 -0
- package/dist/esm/worktrees/registry.d.ts +8 -0
- package/dist/esm/worktrees/registry.js +18 -3
- package/package.json +16 -13
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { TransformContext, TransformResult } from "./index.js";
|
|
2
|
+
/**
|
|
3
|
+
* `wp audit <kind>` writes the full `formatRepoAuditReport` output (title +
|
|
4
|
+
* one bullet per violation) into its CLI log. Every audit kind normalizes to
|
|
5
|
+
* the shared `"audit"` tool name (see `normalizeToolName`), so without a
|
|
6
|
+
* dedicated transform that output fell through to `genericTransform`, whose
|
|
7
|
+
* error/fail keyword heuristic drops any violation bullet that doesn't
|
|
8
|
+
* literally contain "error"/"fail"/"✗"/"✘" — silently discarding most
|
|
9
|
+
* violation detail even when the full report is a handful of short lines.
|
|
10
|
+
*
|
|
11
|
+
* This transform recognizes the bullet-list shape directly so violation text
|
|
12
|
+
* survives regardless of wording, then defers to the same size-based
|
|
13
|
+
* clip/elision policy every other transform uses (`createTransformResult` /
|
|
14
|
+
* `clipRawOutput`'s existing byte threshold) to decide whether the (now
|
|
15
|
+
* correctly extracted) violation list is short enough to print inline or
|
|
16
|
+
* long enough to still defer to `wp logs audit`.
|
|
17
|
+
*
|
|
18
|
+
* Audit output that isn't shaped like a violation-bullet report (for example
|
|
19
|
+
* raw subprocess output from `mutation`/`bundle-budget`) has no matching
|
|
20
|
+
* bullet lines, so this falls back to the previous generic keyword-based
|
|
21
|
+
* behavior unchanged.
|
|
22
|
+
*/
|
|
23
|
+
export declare function auditTransform(rawOutput: string | undefined, context: TransformContext): TransformResult;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { createTransformResult } from "./metadata.js";
|
|
2
|
+
import { genericTransform } from "./generic.js";
|
|
3
|
+
// `formatRepoAuditReport` (src/audit/repo-guardrails.ts) always renders one
|
|
4
|
+
// violation per line as `- <location><metadata><message><fixability>`. That
|
|
5
|
+
// bullet marker is the one structural signal every repo-audit report shares,
|
|
6
|
+
// regardless of which audit kind produced it or whether the violation text
|
|
7
|
+
// happens to contain the words "error"/"fail" that the generic fallback
|
|
8
|
+
// keys off of.
|
|
9
|
+
const VIOLATION_LINE_RE = /^-\s+(.+)$/u;
|
|
10
|
+
/**
|
|
11
|
+
* `wp audit <kind>` writes the full `formatRepoAuditReport` output (title +
|
|
12
|
+
* one bullet per violation) into its CLI log. Every audit kind normalizes to
|
|
13
|
+
* the shared `"audit"` tool name (see `normalizeToolName`), so without a
|
|
14
|
+
* dedicated transform that output fell through to `genericTransform`, whose
|
|
15
|
+
* error/fail keyword heuristic drops any violation bullet that doesn't
|
|
16
|
+
* literally contain "error"/"fail"/"✗"/"✘" — silently discarding most
|
|
17
|
+
* violation detail even when the full report is a handful of short lines.
|
|
18
|
+
*
|
|
19
|
+
* This transform recognizes the bullet-list shape directly so violation text
|
|
20
|
+
* survives regardless of wording, then defers to the same size-based
|
|
21
|
+
* clip/elision policy every other transform uses (`createTransformResult` /
|
|
22
|
+
* `clipRawOutput`'s existing byte threshold) to decide whether the (now
|
|
23
|
+
* correctly extracted) violation list is short enough to print inline or
|
|
24
|
+
* long enough to still defer to `wp logs audit`.
|
|
25
|
+
*
|
|
26
|
+
* Audit output that isn't shaped like a violation-bullet report (for example
|
|
27
|
+
* raw subprocess output from `mutation`/`bundle-budget`) has no matching
|
|
28
|
+
* bullet lines, so this falls back to the previous generic keyword-based
|
|
29
|
+
* behavior unchanged.
|
|
30
|
+
*/
|
|
31
|
+
export function auditTransform(rawOutput, context) {
|
|
32
|
+
if (!rawOutput)
|
|
33
|
+
return {};
|
|
34
|
+
const failures = extractViolationBullets(rawOutput);
|
|
35
|
+
if (failures.length === 0)
|
|
36
|
+
return genericTransform(rawOutput, context);
|
|
37
|
+
return createTransformResult(rawOutput, failures.map((failure) => failure.message).join("\n"), context, { tier: 1, failures });
|
|
38
|
+
}
|
|
39
|
+
function extractViolationBullets(rawOutput) {
|
|
40
|
+
const failures = [];
|
|
41
|
+
for (const line of rawOutput.split(/\r?\n/u)) {
|
|
42
|
+
const message = VIOLATION_LINE_RE.exec(line)?.[1]?.trim();
|
|
43
|
+
if (message)
|
|
44
|
+
failures.push({ message });
|
|
45
|
+
}
|
|
46
|
+
return failures;
|
|
47
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { auditTransform } from "./audit.js";
|
|
1
2
|
import { genericTransform } from "./generic.js";
|
|
2
3
|
import { oxlintTransform } from "./oxlint.js";
|
|
3
4
|
import { tscTransform } from "./tsc.js";
|
|
@@ -8,6 +9,7 @@ const builtInTransforms = new Map([
|
|
|
8
9
|
["lint-oxlint", oxlintTransform],
|
|
9
10
|
["typecheck", tscTransform],
|
|
10
11
|
["test", vitestTransform],
|
|
12
|
+
["audit", auditTransform],
|
|
11
13
|
]);
|
|
12
14
|
const transforms = new Map(builtInTransforms);
|
|
13
15
|
export function registerTransform(toolName, transform) {
|
|
@@ -60,7 +60,7 @@ function assertStableSlug(slug) {
|
|
|
60
60
|
}
|
|
61
61
|
function locateBlueprint(entries, stableSlug) {
|
|
62
62
|
assertStableSlug(stableSlug);
|
|
63
|
-
const suffixPattern = new RegExp(`(?:^|/)${LIFECYCLE_SEGMENTS}/${stableSlug.replaceAll("/", "\\/")}
|
|
63
|
+
const suffixPattern = new RegExp(`(?:^|/)${LIFECYCLE_SEGMENTS}/${stableSlug.replaceAll("/", "\\/")}(?:/_overview\\.md|\\.md)$`, "u");
|
|
64
64
|
const matches = entries.filter((entry) => suffixPattern.test(entry.path) &&
|
|
65
65
|
REGULAR_MODE_PATTERN.test(entry.mode) &&
|
|
66
66
|
entry.type === "blob");
|
|
@@ -69,6 +69,9 @@ function locateBlueprint(entries, stableSlug) {
|
|
|
69
69
|
}
|
|
70
70
|
return matches[0];
|
|
71
71
|
}
|
|
72
|
+
function ledgerDirectoryForBlueprintPath(blueprintPath) {
|
|
73
|
+
return blueprintPath.endsWith("/_overview.md") ? path.posix.dirname(blueprintPath) : null;
|
|
74
|
+
}
|
|
72
75
|
function readBlob(cwd, entry) {
|
|
73
76
|
if (!REGULAR_MODE_PATTERN.test(entry.mode) || entry.type !== "blob") {
|
|
74
77
|
throw new Error(`Review authority path is not a regular blob: ${entry.path}`);
|
|
@@ -115,14 +118,16 @@ export function readReviewAuthorityAtRef(cwd, ref, stableSlug, purpose) {
|
|
|
115
118
|
const commit = resolveReviewCommit(cwd, ref);
|
|
116
119
|
const entries = parseRefTree(cwd, commit);
|
|
117
120
|
const overview = locateBlueprint(entries, stableSlug);
|
|
118
|
-
const blueprintDirectory =
|
|
119
|
-
const ledgerPath = path.posix.join(blueprintDirectory, "review-events.jsonl");
|
|
120
|
-
const ledger = entries.find((entry) => entry.path === ledgerPath);
|
|
121
|
+
const blueprintDirectory = ledgerDirectoryForBlueprintPath(overview.path);
|
|
122
|
+
const ledgerPath = blueprintDirectory === null ? null : path.posix.join(blueprintDirectory, "review-events.jsonl");
|
|
123
|
+
const ledger = ledgerPath === null ? undefined : entries.find((entry) => entry.path === ledgerPath);
|
|
121
124
|
const events = ledger ? parseReviewEventLog(readBlob(cwd, ledger).toString("utf8")) : [];
|
|
122
125
|
if (events.some((event) => event.blueprintSlug !== stableSlug)) {
|
|
123
126
|
throw new Error(`Review event ledger contains an event for a different blueprint: ${ledgerPath}`);
|
|
124
127
|
}
|
|
125
|
-
const { compromisedIds, issues } =
|
|
128
|
+
const { compromisedIds, issues } = blueprintDirectory === null
|
|
129
|
+
? { compromisedIds: new Set(), issues: [] }
|
|
130
|
+
: markCompromisedArtifactEvents(cwd, entries, blueprintDirectory, events);
|
|
126
131
|
const subject = purpose === "plan"
|
|
127
132
|
? createPlanSubjectAtRef(cwd, commit, stableSlug, overview.path)
|
|
128
133
|
: createDeliverySubjectAtRef(cwd, commit, stableSlug, overview.path);
|
|
@@ -160,6 +165,9 @@ export function readReviewAuthorityAtRef(cwd, ref, stableSlug, purpose) {
|
|
|
160
165
|
};
|
|
161
166
|
}
|
|
162
167
|
export function readWorkingReviewEvents(blueprintPath, stableSlug) {
|
|
168
|
+
if (path.basename(blueprintPath) !== "_overview.md") {
|
|
169
|
+
return { events: [], compromisedIds: new Set(), issues: [] };
|
|
170
|
+
}
|
|
163
171
|
const ledgerPath = path.join(path.dirname(blueprintPath), "review-events.jsonl");
|
|
164
172
|
if (!existsSync(ledgerPath))
|
|
165
173
|
return { events: [], compromisedIds: new Set(), issues: [] };
|
|
@@ -49,3 +49,25 @@ export declare function pruneExpiredAvailabilitySuppressions(state: ReviewAvaila
|
|
|
49
49
|
state: ReviewAvailabilityState;
|
|
50
50
|
pruned: string[];
|
|
51
51
|
};
|
|
52
|
+
export declare const OPENCODE_ACCOUNT_AVAILABILITY_SCHEMA_VERSION = 1;
|
|
53
|
+
export interface OpencodeAccountAvailabilityState {
|
|
54
|
+
readonly schemaVersion: 1;
|
|
55
|
+
readonly updatedAt: string;
|
|
56
|
+
readonly accounts: Record<string, ReviewAvailabilityRecord>;
|
|
57
|
+
}
|
|
58
|
+
export interface OpencodeAccountAvailabilityEvent {
|
|
59
|
+
readonly limitWindow?: ReviewLimitWindow;
|
|
60
|
+
readonly retryAfterMs?: number;
|
|
61
|
+
readonly failureCode?: string;
|
|
62
|
+
readonly terminalClassification?: string;
|
|
63
|
+
readonly at?: string;
|
|
64
|
+
}
|
|
65
|
+
export declare function opencodeAccountAvailabilityPath(): string;
|
|
66
|
+
export declare function loadOpencodeAccountAvailability(): OpencodeAccountAvailabilityState;
|
|
67
|
+
export declare function isOpencodeAccountSuppressed(state: OpencodeAccountAvailabilityState, canonicalId: string): boolean;
|
|
68
|
+
export declare function recordOpencodeAccountAvailability(canonicalId: string, event: OpencodeAccountAvailabilityEvent): OpencodeAccountAvailabilityState;
|
|
69
|
+
export declare function recordOpencodeAccountAvailabilityWithLock(canonicalId: string, event: OpencodeAccountAvailabilityEvent): Promise<OpencodeAccountAvailabilityState>;
|
|
70
|
+
export declare function pruneExpiredOpencodeAccountSuppressions(state: OpencodeAccountAvailabilityState, nowMs?: number): {
|
|
71
|
+
state: OpencodeAccountAvailabilityState;
|
|
72
|
+
pruned: string[];
|
|
73
|
+
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { NotInGitRepoError, withLock } from "#paths/state-root.js";
|
|
3
|
+
import { getStateRoot, NotInGitRepoError, withLock } from "#paths/state-root.js";
|
|
4
4
|
import { writeJsonFileAtomic } from "#shared-utils/write-json-file.js";
|
|
5
5
|
export const REVIEW_AVAILABILITY_SCHEMA_VERSION = 1;
|
|
6
6
|
const FAILURE_CLASSIFICATIONS = new Set(["provider-limit-reached", "provider-unavailable"]);
|
|
@@ -121,10 +121,7 @@ function isAvailabilitySuccess(event) {
|
|
|
121
121
|
return SUCCESS_CLASSIFICATIONS.has(event.terminalClassification);
|
|
122
122
|
}
|
|
123
123
|
function isProviderWideOpenCodeFailure(event) {
|
|
124
|
-
return (event.provider === "opencode" &&
|
|
125
|
-
(event.failureCode === "opencode-go-usage-limit" ||
|
|
126
|
-
event.reviewer === undefined ||
|
|
127
|
-
event.reviewer === "opencode"));
|
|
124
|
+
return (event.provider === "opencode" && (event.reviewer === undefined || event.reviewer === "opencode"));
|
|
128
125
|
}
|
|
129
126
|
function nextRecord(previous, event, input) {
|
|
130
127
|
const at = event.at ?? new Date().toISOString();
|
|
@@ -250,26 +247,35 @@ export function suppressedReviewers(state) {
|
|
|
250
247
|
.filter((record) => record.suppressed)
|
|
251
248
|
.map((record) => record.canonicalId));
|
|
252
249
|
}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
250
|
+
/**
|
|
251
|
+
* Shared expiry predicate for a suppressed availability record: applies to
|
|
252
|
+
* both the reviewer/provider store and the opencode-go account store below.
|
|
253
|
+
* A record clears when its terminal classification is no longer a failure
|
|
254
|
+
* classification, or when its retry window has elapsed.
|
|
255
|
+
*/
|
|
256
|
+
function suppressionExpired(record, nowMs) {
|
|
257
|
+
if (!record.suppressed)
|
|
258
|
+
return false;
|
|
259
|
+
if (!suppressedRecordStillApplies(record))
|
|
260
|
+
return true;
|
|
261
|
+
if (record.retryAfterMs === undefined || record.lastFailureAt === undefined)
|
|
262
|
+
return false;
|
|
263
|
+
const failureAt = Date.parse(record.lastFailureAt);
|
|
264
|
+
return Number.isFinite(failureAt) && failureAt + record.retryAfterMs <= nowMs;
|
|
265
|
+
}
|
|
266
|
+
function pruneRecordMap(records, nowMs, pruned) {
|
|
267
|
+
return Object.fromEntries(Object.entries(records).map(([key, record]) => {
|
|
268
|
+
if (suppressionExpired(record, nowMs)) {
|
|
257
269
|
pruned.push(key);
|
|
258
270
|
return [key, { ...record, suppressed: false }];
|
|
259
271
|
}
|
|
260
|
-
if (record.suppressed &&
|
|
261
|
-
record.retryAfterMs !== undefined &&
|
|
262
|
-
record.lastFailureAt !== undefined) {
|
|
263
|
-
const failureAt = Date.parse(record.lastFailureAt);
|
|
264
|
-
if (Number.isFinite(failureAt) && failureAt + record.retryAfterMs <= nowMs) {
|
|
265
|
-
pruned.push(key);
|
|
266
|
-
return [key, { ...record, suppressed: false }];
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
272
|
return [key, record];
|
|
270
273
|
}));
|
|
271
|
-
|
|
272
|
-
|
|
274
|
+
}
|
|
275
|
+
export function pruneExpiredAvailabilitySuppressions(state, nowMs = Date.now()) {
|
|
276
|
+
const pruned = [];
|
|
277
|
+
const providers = pruneRecordMap(state.providers, nowMs, pruned);
|
|
278
|
+
const reviewers = pruneRecordMap(state.reviewers, nowMs, pruned);
|
|
273
279
|
if (pruned.length === 0)
|
|
274
280
|
return { state, pruned };
|
|
275
281
|
return {
|
|
@@ -282,3 +288,84 @@ export function pruneExpiredAvailabilitySuppressions(state, nowMs = Date.now())
|
|
|
282
288
|
pruned,
|
|
283
289
|
};
|
|
284
290
|
}
|
|
291
|
+
// --- opencode-go multi-account cooldown store (user-scoped, cross-repo) ---
|
|
292
|
+
export const OPENCODE_ACCOUNT_AVAILABILITY_SCHEMA_VERSION = 1;
|
|
293
|
+
export function opencodeAccountAvailabilityPath() {
|
|
294
|
+
return path.join(getStateRoot(), "opencode-account-availability.json");
|
|
295
|
+
}
|
|
296
|
+
function emptyOpencodeAccountAvailability(now = new Date().toISOString()) {
|
|
297
|
+
return {
|
|
298
|
+
schemaVersion: OPENCODE_ACCOUNT_AVAILABILITY_SCHEMA_VERSION,
|
|
299
|
+
updatedAt: now,
|
|
300
|
+
accounts: {},
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
export function loadOpencodeAccountAvailability() {
|
|
304
|
+
const file = opencodeAccountAvailabilityPath();
|
|
305
|
+
if (!existsSync(file))
|
|
306
|
+
return emptyOpencodeAccountAvailability();
|
|
307
|
+
try {
|
|
308
|
+
const parsed = JSON.parse(readFileSync(file, "utf8"));
|
|
309
|
+
if (!isRecord(parsed) ||
|
|
310
|
+
parsed.schemaVersion !== OPENCODE_ACCOUNT_AVAILABILITY_SCHEMA_VERSION) {
|
|
311
|
+
return emptyOpencodeAccountAvailability();
|
|
312
|
+
}
|
|
313
|
+
return {
|
|
314
|
+
schemaVersion: OPENCODE_ACCOUNT_AVAILABILITY_SCHEMA_VERSION,
|
|
315
|
+
updatedAt: typeof parsed.updatedAt === "string" ? parsed.updatedAt : new Date().toISOString(),
|
|
316
|
+
accounts: normalizeRecordMap(parsed.accounts),
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
catch {
|
|
320
|
+
return emptyOpencodeAccountAvailability();
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
function saveOpencodeAccountAvailability(state) {
|
|
324
|
+
const file = opencodeAccountAvailabilityPath();
|
|
325
|
+
mkdirSync(path.dirname(file), { recursive: true });
|
|
326
|
+
writeJsonFileAtomic(file, state);
|
|
327
|
+
}
|
|
328
|
+
export function isOpencodeAccountSuppressed(state, canonicalId) {
|
|
329
|
+
return state.accounts[canonicalId]?.suppressed === true;
|
|
330
|
+
}
|
|
331
|
+
export function recordOpencodeAccountAvailability(canonicalId, event) {
|
|
332
|
+
const at = event.at ?? new Date().toISOString();
|
|
333
|
+
const current = loadOpencodeAccountAvailability();
|
|
334
|
+
const accounts = { ...current.accounts };
|
|
335
|
+
accounts[canonicalId] = {
|
|
336
|
+
canonicalId,
|
|
337
|
+
provider: "opencode",
|
|
338
|
+
terminalClassification: event.terminalClassification ?? "provider-limit-reached",
|
|
339
|
+
...(event.failureCode ? { failureCode: event.failureCode } : {}),
|
|
340
|
+
...(event.limitWindow ? { limitWindow: event.limitWindow } : {}),
|
|
341
|
+
...(event.retryAfterMs === undefined ? {} : { retryAfterMs: event.retryAfterMs }),
|
|
342
|
+
lastFailureAt: at,
|
|
343
|
+
suppressed: true,
|
|
344
|
+
suppressionScope: "provider",
|
|
345
|
+
suppressedAt: at,
|
|
346
|
+
};
|
|
347
|
+
const next = {
|
|
348
|
+
schemaVersion: OPENCODE_ACCOUNT_AVAILABILITY_SCHEMA_VERSION,
|
|
349
|
+
updatedAt: at,
|
|
350
|
+
accounts,
|
|
351
|
+
};
|
|
352
|
+
saveOpencodeAccountAvailability(next);
|
|
353
|
+
return next;
|
|
354
|
+
}
|
|
355
|
+
export function recordOpencodeAccountAvailabilityWithLock(canonicalId, event) {
|
|
356
|
+
return withLock("user", () => recordOpencodeAccountAvailability(canonicalId, event));
|
|
357
|
+
}
|
|
358
|
+
export function pruneExpiredOpencodeAccountSuppressions(state, nowMs = Date.now()) {
|
|
359
|
+
const pruned = [];
|
|
360
|
+
const accounts = pruneRecordMap(state.accounts, nowMs, pruned);
|
|
361
|
+
if (pruned.length === 0)
|
|
362
|
+
return { state, pruned };
|
|
363
|
+
return {
|
|
364
|
+
state: {
|
|
365
|
+
...state,
|
|
366
|
+
updatedAt: new Date(nowMs).toISOString(),
|
|
367
|
+
accounts,
|
|
368
|
+
},
|
|
369
|
+
pruned,
|
|
370
|
+
};
|
|
371
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { executeAllowedWpCommand } from "#trust/command-runner.js";
|
|
2
|
+
import type { ReviewTargetCheckout } from "#review/execution/review-checkout.js";
|
|
3
|
+
export interface ReviewGateVerificationCommand {
|
|
4
|
+
readonly command: string;
|
|
5
|
+
readonly outcome: "passed" | "failed" | "timed-out" | "deferred";
|
|
6
|
+
readonly durationMs: number;
|
|
7
|
+
readonly exitCode: number | null;
|
|
8
|
+
readonly signal?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface ReviewGateVerification {
|
|
11
|
+
readonly status: "passed" | "failed";
|
|
12
|
+
readonly commands: readonly ReviewGateVerificationCommand[];
|
|
13
|
+
readonly failureCode?: string;
|
|
14
|
+
readonly logPath?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface VerifyDeliveryGatesInput {
|
|
17
|
+
readonly purpose: "plan" | "delivery";
|
|
18
|
+
readonly blueprintContent: string;
|
|
19
|
+
readonly projectRoot: string;
|
|
20
|
+
readonly gateId: string;
|
|
21
|
+
readonly checkout: ReviewTargetCheckout;
|
|
22
|
+
readonly signal?: AbortSignal;
|
|
23
|
+
readonly executeCommand?: typeof executeAllowedWpCommand;
|
|
24
|
+
}
|
|
25
|
+
export declare function verifyDeliveryPromotionGates(input: VerifyDeliveryGatesInput): ReviewGateVerification | undefined;
|
|
26
|
+
export declare function verificationPromptSummary(verification: ReviewGateVerification | undefined): string | undefined;
|
|
27
|
+
export declare function verificationArtifactSection(verification: ReviewGateVerification | undefined): string;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { executeAllowedWpCommand, relativeCommandLogPath, } from "#trust/command-runner.js";
|
|
3
|
+
import { parseTrustDossier } from "#trust/dossier.js";
|
|
4
|
+
import { parseAllowedWpCommand } from "#trust/gates.js";
|
|
5
|
+
export function verifyDeliveryPromotionGates(input) {
|
|
6
|
+
if (input.purpose !== "delivery")
|
|
7
|
+
return undefined;
|
|
8
|
+
const parsed = parseTrustDossier(input.blueprintContent);
|
|
9
|
+
if (parsed.violations.length > 0) {
|
|
10
|
+
if (parsed.violations.length === 1 &&
|
|
11
|
+
parsed.violations[0]?.section === "Trust Dossier" &&
|
|
12
|
+
parsed.violations[0]?.message === "missing Trust Dossier section") {
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
return {
|
|
16
|
+
status: "failed",
|
|
17
|
+
commands: [],
|
|
18
|
+
failureCode: "malformed-trust-dossier",
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
const gates = parsed.dossier?.gates ?? [];
|
|
22
|
+
if (gates.length === 0)
|
|
23
|
+
return undefined;
|
|
24
|
+
const commands = [];
|
|
25
|
+
const executeCommand = input.executeCommand ?? executeAllowedWpCommand;
|
|
26
|
+
const logRoot = path.join(input.projectRoot, ".webpresso", "logs", "delivery-verification", input.gateId);
|
|
27
|
+
for (const gate of gates) {
|
|
28
|
+
if (input.signal?.aborted) {
|
|
29
|
+
return {
|
|
30
|
+
status: "failed",
|
|
31
|
+
commands,
|
|
32
|
+
failureCode: "verification-cancelled",
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
if (isApprovalDependentLifecycleGate(gate.command)) {
|
|
36
|
+
commands.push({
|
|
37
|
+
command: gate.command,
|
|
38
|
+
outcome: "deferred",
|
|
39
|
+
durationMs: 0,
|
|
40
|
+
exitCode: null,
|
|
41
|
+
});
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
const result = executeCommand({
|
|
45
|
+
command: gate.command,
|
|
46
|
+
executionRoot: input.checkout.cwd,
|
|
47
|
+
logRoot,
|
|
48
|
+
env: { CI: "1", NO_COLOR: "1", WP_FORCE_SOURCE: "1" },
|
|
49
|
+
});
|
|
50
|
+
commands.push(publicCommandResult(result));
|
|
51
|
+
if (result.outcome !== "passed") {
|
|
52
|
+
return {
|
|
53
|
+
status: "failed",
|
|
54
|
+
commands,
|
|
55
|
+
failureCode: result.failureCode ?? "verification-command-failed",
|
|
56
|
+
logPath: relativeCommandLogPath(input.projectRoot, result.logPath),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
const changes = input.checkout.changes();
|
|
60
|
+
if (changes.length > 0) {
|
|
61
|
+
return {
|
|
62
|
+
status: "failed",
|
|
63
|
+
commands,
|
|
64
|
+
failureCode: "verification-worktree-contaminated",
|
|
65
|
+
logPath: relativeCommandLogPath(input.projectRoot, result.logPath),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
status: "passed",
|
|
71
|
+
commands,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
export function verificationPromptSummary(verification) {
|
|
75
|
+
if (!verification)
|
|
76
|
+
return undefined;
|
|
77
|
+
if (verification.status !== "passed")
|
|
78
|
+
return undefined;
|
|
79
|
+
return [
|
|
80
|
+
"Host verification before outside review: passed.",
|
|
81
|
+
...verification.commands.map((command, index) => `${index + 1}. ${command.command}: ${command.outcome === "deferred" ? "deferred until review publication" : "passed"}`),
|
|
82
|
+
].join("\n");
|
|
83
|
+
}
|
|
84
|
+
function isApprovalDependentLifecycleGate(command) {
|
|
85
|
+
try {
|
|
86
|
+
const [, subcommand, kind] = parseAllowedWpCommand(command);
|
|
87
|
+
return subcommand === "audit" && kind === "blueprint-lifecycle";
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
export function verificationArtifactSection(verification) {
|
|
94
|
+
if (!verification)
|
|
95
|
+
return "";
|
|
96
|
+
const lines = [
|
|
97
|
+
"## Host Verification",
|
|
98
|
+
"",
|
|
99
|
+
`Status: ${verification.status}`,
|
|
100
|
+
...verification.commands.map((command, index) => `${index + 1}. ${command.command} -> ${command.outcome} (${command.durationMs}ms, exit ${command.exitCode ?? "null"})`),
|
|
101
|
+
...(verification.failureCode ? [`Failure code: ${verification.failureCode}`] : []),
|
|
102
|
+
...(verification.logPath ? [`Log: ${verification.logPath}`] : []),
|
|
103
|
+
"",
|
|
104
|
+
];
|
|
105
|
+
return `${lines.join("\n")}\n`;
|
|
106
|
+
}
|
|
107
|
+
function publicCommandResult(result) {
|
|
108
|
+
return {
|
|
109
|
+
command: result.command,
|
|
110
|
+
outcome: result.outcome,
|
|
111
|
+
durationMs: result.durationMs,
|
|
112
|
+
exitCode: result.exitCode,
|
|
113
|
+
...(result.signal ? { signal: result.signal } : {}),
|
|
114
|
+
};
|
|
115
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
export declare const MAINTAINER_OVERRIDE_REVIEWER = "maintainer-override";
|
|
2
3
|
export declare const reviewEventSchema: z.ZodObject<{
|
|
3
4
|
schemaVersion: z.ZodLiteral<2>;
|
|
4
5
|
id: z.ZodString;
|
|
@@ -16,12 +17,12 @@ export declare const reviewEventSchema: z.ZodObject<{
|
|
|
16
17
|
gateId: z.ZodString;
|
|
17
18
|
promptHash: z.ZodString;
|
|
18
19
|
reviewer: z.ZodString;
|
|
19
|
-
provider: z.ZodEnum<{
|
|
20
|
+
provider: z.ZodOptional<z.ZodEnum<{
|
|
20
21
|
claude: "claude";
|
|
21
22
|
codex: "codex";
|
|
22
23
|
grok: "grok";
|
|
23
24
|
opencode: "opencode";
|
|
24
|
-
}
|
|
25
|
+
}>>;
|
|
25
26
|
model: z.ZodOptional<z.ZodString>;
|
|
26
27
|
effort: z.ZodOptional<z.ZodEnum<{
|
|
27
28
|
high: "high";
|
|
@@ -9,6 +9,14 @@ const SHA256_PATTERN = /^[a-f0-9]{64}$/u;
|
|
|
9
9
|
const COMMIT_PATTERN = /^(?:[a-f0-9]{40}|[a-f0-9]{64})$/u;
|
|
10
10
|
const BLUEPRINT_SLUG_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*(?:\/[a-z0-9]+(?:-[a-z0-9]+)*)*$/u;
|
|
11
11
|
const ARTIFACT_PATH_PATTERN = /^review-artifacts\/[a-zA-Z0-9._-]+\.md$/u;
|
|
12
|
+
// The honest, auditable escape hatch reviewer id. A maintainer override is a
|
|
13
|
+
// HUMAN adjudication (not a machine backend), recorded only by `wp review
|
|
14
|
+
// override` after a real strong-reviewer rejection of the same subject. It
|
|
15
|
+
// satisfies the promotion gate DISTINCTLY (never folded into a strong or
|
|
16
|
+
// OpenCode-Go approval). Defined here — the lowest review-core module — so both
|
|
17
|
+
// the ledger read-time policy below and the lifecycle approval gate share one
|
|
18
|
+
// source of truth without an import cycle.
|
|
19
|
+
export const MAINTAINER_OVERRIDE_REVIEWER = "maintainer-override";
|
|
12
20
|
export const reviewEventSchema = z
|
|
13
21
|
.object({
|
|
14
22
|
schemaVersion: z.literal(2),
|
|
@@ -21,7 +29,10 @@ export const reviewEventSchema = z
|
|
|
21
29
|
gateId: z.string().min(1).max(120),
|
|
22
30
|
promptHash: z.string().regex(SHA256_PATTERN),
|
|
23
31
|
reviewer: z.string().min(1).max(120),
|
|
24
|
-
|
|
32
|
+
// Optional: present for machine-backend reviews (always set by `wp review
|
|
33
|
+
// gate`/`log`); absent for a human maintainer-override, which has no AI
|
|
34
|
+
// backend. Descriptive metadata only — no policy keys off `provider`.
|
|
35
|
+
provider: z.enum(["claude", "codex", "opencode", "grok"]).optional(),
|
|
25
36
|
model: z.string().min(1).max(240).optional(),
|
|
26
37
|
effort: z.enum(["medium", "high"]).optional(),
|
|
27
38
|
stage: z.string().min(1).max(120),
|
|
@@ -81,7 +92,7 @@ function orderedEvent(event) {
|
|
|
81
92
|
gateId: event.gateId,
|
|
82
93
|
promptHash: event.promptHash,
|
|
83
94
|
reviewer: event.reviewer,
|
|
84
|
-
provider: event.provider,
|
|
95
|
+
...(event.provider ? { provider: event.provider } : {}),
|
|
85
96
|
...(event.model ? { model: event.model } : {}),
|
|
86
97
|
...(event.effort ? { effort: event.effort } : {}),
|
|
87
98
|
stage: event.stage,
|
|
@@ -164,10 +175,19 @@ function isManualMarker(value) {
|
|
|
164
175
|
// dodge the policy while real gate ids (content hashes) can never collide.
|
|
165
176
|
return value.trim().toLowerCase() === "manual";
|
|
166
177
|
}
|
|
178
|
+
// Reviewers whose manual-stage approvals are legitimate governance decisions,
|
|
179
|
+
// not disguised machine approvals: a plain `human` review and a
|
|
180
|
+
// `maintainer-override` (a maintainer's hand-recorded adjudication). Both are
|
|
181
|
+
// human authority by construction, so a manual marker on their events is
|
|
182
|
+
// expected rather than a policy dodge.
|
|
183
|
+
const HUMAN_LIKE_REVIEWERS = new Set(["human", MAINTAINER_OVERRIDE_REVIEWER]);
|
|
184
|
+
function isHumanLikeReviewer(reviewer) {
|
|
185
|
+
return HUMAN_LIKE_REVIEWERS.has(canonicalReviewer(reviewer));
|
|
186
|
+
}
|
|
167
187
|
function isManualMachineApproval(event) {
|
|
168
188
|
return (isPolicyApprovalVerdict(event.verdict) &&
|
|
169
189
|
(isManualMarker(event.gateId) || isManualMarker(event.stage)) &&
|
|
170
|
-
|
|
190
|
+
!isHumanLikeReviewer(event.reviewer));
|
|
171
191
|
}
|
|
172
192
|
// `compromisedIds` marks events whose artifact evidence failed integrity
|
|
173
193
|
// verification. They still PARTICIPATE in reviewer supersession — a later
|
|
@@ -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;
|