@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
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { readCurrentSessionInfo } from "#session-memory/current-session.js";
|
|
3
|
+
import { createSummaryOutputSchema, createSummaryResult } from "./_shared/result.js";
|
|
4
|
+
const inputSchema = z
|
|
5
|
+
.object({
|
|
6
|
+
cwd: z.string().optional(),
|
|
7
|
+
})
|
|
8
|
+
.strict();
|
|
9
|
+
const outputSchema = createSummaryOutputSchema({
|
|
10
|
+
details: z.object({
|
|
11
|
+
sessionId: z.string(),
|
|
12
|
+
executionModels: z.array(z.string()),
|
|
13
|
+
}),
|
|
14
|
+
}).extend({
|
|
15
|
+
sessionId: z.string(),
|
|
16
|
+
executionModels: z.array(z.string()),
|
|
17
|
+
});
|
|
18
|
+
function effectiveCwd(cwd) {
|
|
19
|
+
return cwd ?? process.env.CLAUDE_PROJECT_DIR ?? process.cwd();
|
|
20
|
+
}
|
|
21
|
+
const tool = {
|
|
22
|
+
name: "wp_session_info",
|
|
23
|
+
description: "Report the current agent session id and every distinct observed execution model for the PR AI/model disclosure contract. Structured equivalent of `wp session-info` (prefers a live host session id, then falls back to stored session-memory capture for this worktree and its sibling worktrees). Feed `sessionId` straight into `wp_pr_upsert`'s `description.sessionId` — that tool also auto-resolves it via the same underlying lookup when omitted, so calling this tool first is optional. An empty `sessionId` means no live or captured session was found; use `Session-skip: SKIP <reason>` in the disclosure instead. Prefer `wp_session_id` when you only need the id, not the model history — it skips the cross-worktree scan this tool does.",
|
|
24
|
+
inputSchema,
|
|
25
|
+
outputSchema,
|
|
26
|
+
annotations: {
|
|
27
|
+
title: "Session info",
|
|
28
|
+
readOnlyHint: true,
|
|
29
|
+
destructiveHint: false,
|
|
30
|
+
idempotentHint: true,
|
|
31
|
+
openWorldHint: false,
|
|
32
|
+
},
|
|
33
|
+
handler: async (raw) => {
|
|
34
|
+
const input = inputSchema.parse(raw ?? {});
|
|
35
|
+
const info = readCurrentSessionInfo(effectiveCwd(input.cwd), process.env);
|
|
36
|
+
const executionModels = [...info.models];
|
|
37
|
+
const payload = {
|
|
38
|
+
passed: true,
|
|
39
|
+
summary: info.sessionId
|
|
40
|
+
? `session id ${info.sessionId} with ${executionModels.length} observed model${executionModels.length === 1 ? "" : "s"}`
|
|
41
|
+
: "no live or captured session id found; use Session-skip in the PR disclosure",
|
|
42
|
+
sessionId: info.sessionId,
|
|
43
|
+
executionModels,
|
|
44
|
+
details: { sessionId: info.sessionId, executionModels },
|
|
45
|
+
};
|
|
46
|
+
return createSummaryResult(payload);
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
export default tool;
|
|
@@ -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) {
|
package/dist/esm/package.json
CHANGED
|
@@ -89,6 +89,8 @@
|
|
|
89
89
|
"#compiler/*": "./compiler/*.js",
|
|
90
90
|
"#review/*.js": "./review/*.js",
|
|
91
91
|
"#review/*": "./review/*.js",
|
|
92
|
+
"#rust/*.js": "./rust/*.js",
|
|
93
|
+
"#rust/*": "./rust/*.js",
|
|
92
94
|
"#telemetry/*.js": "./telemetry/*.js",
|
|
93
95
|
"#telemetry/*": "./telemetry/*.js",
|
|
94
96
|
"#codex/app-server/client": "./codex/app-server/client.js",
|
|
@@ -44,8 +44,14 @@ export interface PlatformResponse<T = unknown> {
|
|
|
44
44
|
readonly status: number;
|
|
45
45
|
readonly data: T;
|
|
46
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* Injectable fetch call-signature used by platform transport and tests.
|
|
49
|
+
* Narrower than `typeof fetch` (which requires Bun/DOM statics like
|
|
50
|
+
* `preconnect`) so vitest mocks remain assignable without casts.
|
|
51
|
+
*/
|
|
52
|
+
export type FetchLike = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
|
|
47
53
|
export interface PlatformClientOptions {
|
|
48
|
-
readonly fetchFn?:
|
|
54
|
+
readonly fetchFn?: FetchLike;
|
|
49
55
|
readonly maxAttempts?: number;
|
|
50
56
|
readonly baseRetryDelayMs?: number;
|
|
51
57
|
readonly maxRetryDelayMs?: number;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { type AuthenticatedPlatformContractSnapshot, type PlatformContractAuthority, type PlatformContractAuthorityProbeResult, type PlatformContractSourceAdapter } from "./contract-provenance.js";
|
|
2
|
+
import { type FetchLike } from "./client.js";
|
|
2
3
|
export interface GitHubContractSourceAdapterOptions {
|
|
3
4
|
readonly token: string;
|
|
4
5
|
readonly authority?: string;
|
|
5
6
|
readonly apiUrl?: string;
|
|
6
|
-
readonly fetchFn?:
|
|
7
|
+
readonly fetchFn?: FetchLike;
|
|
7
8
|
readonly sleep?: (delayMs: number) => Promise<void>;
|
|
8
9
|
}
|
|
9
10
|
export declare class GitHubContractSourceAdapter implements PlatformContractSourceAdapter {
|
|
@@ -31,13 +31,33 @@ const SESSION_SKIP_PREFIX = /^Session-skip:/im;
|
|
|
31
31
|
function trim(value) {
|
|
32
32
|
return value?.trim() ?? "";
|
|
33
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* Finds the value of a "<label>: <value>" list item at the head of a line,
|
|
36
|
+
* tolerating a leading "- " bullet and markdown emphasis wrapping the label
|
|
37
|
+
* (`**bold**`, `__bold__`, `*italic*`, `_italic_`) on either side of the
|
|
38
|
+
* colon — e.g. both "- Execution model(s): x" and
|
|
39
|
+
* "- **Execution model(s):** x" match. Still anchored to the start of the
|
|
40
|
+
* line (after stripping the bullet/emphasis decoration): a mention of the
|
|
41
|
+
* label mid-sentence never matches. Mirrors the CI `require_field` awk
|
|
42
|
+
* matcher in `.github/workflows/ci.agent-kit.yml` — keep the two in lockstep.
|
|
43
|
+
*/
|
|
34
44
|
function fieldValue(body, label) {
|
|
35
45
|
const expected = label.toLowerCase();
|
|
36
46
|
for (const line of body.split(/\r?\n/u)) {
|
|
37
|
-
const
|
|
38
|
-
if (
|
|
39
|
-
|
|
40
|
-
|
|
47
|
+
const colonIndex = line.indexOf(":");
|
|
48
|
+
if (colonIndex === -1)
|
|
49
|
+
continue;
|
|
50
|
+
const before = line
|
|
51
|
+
.slice(0, colonIndex)
|
|
52
|
+
.replace(/^\s+/u, "")
|
|
53
|
+
.replace(/^-\s+/u, "")
|
|
54
|
+
.replace(/[*_]+/gu, "");
|
|
55
|
+
if (before.toLowerCase() !== expected)
|
|
56
|
+
continue;
|
|
57
|
+
return line
|
|
58
|
+
.slice(colonIndex + 1)
|
|
59
|
+
.replace(/^[*_]+/u, "")
|
|
60
|
+
.trim();
|
|
41
61
|
}
|
|
42
62
|
return undefined;
|
|
43
63
|
}
|
|
@@ -9,7 +9,7 @@ import { createHash } from "node:crypto";
|
|
|
9
9
|
import { existsSync, lstatSync, readFileSync } from "node:fs";
|
|
10
10
|
import path from "node:path";
|
|
11
11
|
import { evaluateReviewEvents, parseReviewEventLog } from "./events.js";
|
|
12
|
-
import { createDeliverySubjectAtRef, createLegacyPlanSubjectAtRef, createPlanSubjectAtRef, resolveReviewCommit, } from "./subject.js";
|
|
12
|
+
import { createDeliverySubjectAtRef, createLegacyPlanSubjectAtRef, createPlanSubjectAtRef, createPreTagsStripPlanSubjectAtRef, resolveReviewCommit, } from "./subject.js";
|
|
13
13
|
const GIT_TIMEOUT_MS = 5_000;
|
|
14
14
|
const GIT_MAX_BUFFER_BYTES = 128 * 1024 * 1024;
|
|
15
15
|
const REGULAR_MODE_PATTERN = /^100(?:644|755)$/u;
|
|
@@ -60,7 +60,7 @@ function assertStableSlug(slug) {
|
|
|
60
60
|
}
|
|
61
61
|
function locateBlueprint(entries, stableSlug) {
|
|
62
62
|
assertStableSlug(stableSlug);
|
|
63
|
-
const suffixPattern = new RegExp(`(?:^|/)${LIFECYCLE_SEGMENTS}/${stableSlug.replaceAll("/", "\\/")}
|
|
63
|
+
const suffixPattern = new RegExp(`(?:^|/)${LIFECYCLE_SEGMENTS}/${stableSlug.replaceAll("/", "\\/")}(?:/_overview\\.md|\\.md)$`, "u");
|
|
64
64
|
const matches = entries.filter((entry) => suffixPattern.test(entry.path) &&
|
|
65
65
|
REGULAR_MODE_PATTERN.test(entry.mode) &&
|
|
66
66
|
entry.type === "blob");
|
|
@@ -69,6 +69,9 @@ function locateBlueprint(entries, stableSlug) {
|
|
|
69
69
|
}
|
|
70
70
|
return matches[0];
|
|
71
71
|
}
|
|
72
|
+
function ledgerDirectoryForBlueprintPath(blueprintPath) {
|
|
73
|
+
return blueprintPath.endsWith("/_overview.md") ? path.posix.dirname(blueprintPath) : null;
|
|
74
|
+
}
|
|
72
75
|
function readBlob(cwd, entry) {
|
|
73
76
|
if (!REGULAR_MODE_PATTERN.test(entry.mode) || entry.type !== "blob") {
|
|
74
77
|
throw new Error(`Review authority path is not a regular blob: ${entry.path}`);
|
|
@@ -115,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);
|
|
@@ -133,18 +138,23 @@ export function readReviewAuthorityAtRef(cwd, ref, stableSlug, purpose) {
|
|
|
133
138
|
subjectDigest: subject.digest,
|
|
134
139
|
}, compromisedIds);
|
|
135
140
|
if (purpose === "plan" && evaluation.approvals.length === 0) {
|
|
136
|
-
// Digest-compatibility
|
|
137
|
-
//
|
|
138
|
-
//
|
|
139
|
-
//
|
|
140
|
-
|
|
141
|
-
|
|
141
|
+
// Digest-compatibility fallbacks (newest-first after current):
|
|
142
|
+
// 1) pre-tags-strip: lifecycle strip without advisory `tags` (approvals recorded
|
|
143
|
+
// after the stripped-key extension but before the tags strip)
|
|
144
|
+
// 2) legacy: status + completed_at only (pre-extension)
|
|
145
|
+
// New events always record the current digest (with tags strip).
|
|
146
|
+
for (const make of [createPreTagsStripPlanSubjectAtRef, createLegacyPlanSubjectAtRef]) {
|
|
147
|
+
const alt = make(cwd, commit, stableSlug, overview.path);
|
|
148
|
+
if (alt.digest === subject.digest)
|
|
149
|
+
continue;
|
|
142
150
|
evaluation = evaluateReviewEvents(events, {
|
|
143
151
|
purpose,
|
|
144
152
|
blueprintSlug: stableSlug,
|
|
145
|
-
subjectScheme:
|
|
146
|
-
subjectDigest:
|
|
153
|
+
subjectScheme: alt.scheme,
|
|
154
|
+
subjectDigest: alt.digest,
|
|
147
155
|
}, compromisedIds);
|
|
156
|
+
if (evaluation.approvals.length > 0)
|
|
157
|
+
break;
|
|
148
158
|
}
|
|
149
159
|
}
|
|
150
160
|
return {
|
|
@@ -160,6 +170,9 @@ export function readReviewAuthorityAtRef(cwd, ref, stableSlug, purpose) {
|
|
|
160
170
|
};
|
|
161
171
|
}
|
|
162
172
|
export function readWorkingReviewEvents(blueprintPath, stableSlug) {
|
|
173
|
+
if (path.basename(blueprintPath) !== "_overview.md") {
|
|
174
|
+
return { events: [], compromisedIds: new Set(), issues: [] };
|
|
175
|
+
}
|
|
163
176
|
const ledgerPath = path.join(path.dirname(blueprintPath), "review-events.jsonl");
|
|
164
177
|
if (!existsSync(ledgerPath))
|
|
165
178
|
return { events: [], compromisedIds: new Set(), issues: [] };
|
|
@@ -45,7 +45,40 @@ export declare function recordReviewAvailability(projectRoot: string, event: Rev
|
|
|
45
45
|
export declare function recordReviewAvailabilityWithLock(projectRoot: string, event: ReviewAvailabilityEvent): Promise<ReviewAvailabilityState>;
|
|
46
46
|
export declare function isProviderSuppressed(state: ReviewAvailabilityState, provider: ReviewProvider): boolean;
|
|
47
47
|
export declare function suppressedReviewers(state: ReviewAvailabilityState): Set<string>;
|
|
48
|
+
/**
|
|
49
|
+
* Backstop age for suppressions recorded without a parseable retry window.
|
|
50
|
+
*
|
|
51
|
+
* Without this, a provider/account failure that never set `retryAfterMs`
|
|
52
|
+
* (unparsable limit text, incomplete event payload) stays suppressed forever
|
|
53
|
+
* and permanently removes that surface from rotation. 24h is long enough that
|
|
54
|
+
* a real multi-hour/day window still wins when present, and short enough that
|
|
55
|
+
* a sticky unknown-limit entry self-heals on the next calendar day instead of
|
|
56
|
+
* requiring a manual availability reset.
|
|
57
|
+
*/
|
|
58
|
+
export declare const MAX_SUPPRESSION_AGE_MS: number;
|
|
48
59
|
export declare function pruneExpiredAvailabilitySuppressions(state: ReviewAvailabilityState, nowMs?: number): {
|
|
49
60
|
state: ReviewAvailabilityState;
|
|
50
61
|
pruned: string[];
|
|
51
62
|
};
|
|
63
|
+
export declare const OPENCODE_ACCOUNT_AVAILABILITY_SCHEMA_VERSION = 1;
|
|
64
|
+
export interface OpencodeAccountAvailabilityState {
|
|
65
|
+
readonly schemaVersion: 1;
|
|
66
|
+
readonly updatedAt: string;
|
|
67
|
+
readonly accounts: Record<string, ReviewAvailabilityRecord>;
|
|
68
|
+
}
|
|
69
|
+
export interface OpencodeAccountAvailabilityEvent {
|
|
70
|
+
readonly limitWindow?: ReviewLimitWindow;
|
|
71
|
+
readonly retryAfterMs?: number;
|
|
72
|
+
readonly failureCode?: string;
|
|
73
|
+
readonly terminalClassification?: string;
|
|
74
|
+
readonly at?: string;
|
|
75
|
+
}
|
|
76
|
+
export declare function opencodeAccountAvailabilityPath(): string;
|
|
77
|
+
export declare function loadOpencodeAccountAvailability(): OpencodeAccountAvailabilityState;
|
|
78
|
+
export declare function isOpencodeAccountSuppressed(state: OpencodeAccountAvailabilityState, canonicalId: string): boolean;
|
|
79
|
+
export declare function recordOpencodeAccountAvailability(canonicalId: string, event: OpencodeAccountAvailabilityEvent): OpencodeAccountAvailabilityState;
|
|
80
|
+
export declare function recordOpencodeAccountAvailabilityWithLock(canonicalId: string, event: OpencodeAccountAvailabilityEvent): Promise<OpencodeAccountAvailabilityState>;
|
|
81
|
+
export declare function pruneExpiredOpencodeAccountSuppressions(state: OpencodeAccountAvailabilityState, nowMs?: number): {
|
|
82
|
+
state: OpencodeAccountAvailabilityState;
|
|
83
|
+
pruned: string[];
|
|
84
|
+
};
|
|
@@ -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,50 @@ export function suppressedReviewers(state) {
|
|
|
250
247
|
.filter((record) => record.suppressed)
|
|
251
248
|
.map((record) => record.canonicalId));
|
|
252
249
|
}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
250
|
+
/**
|
|
251
|
+
* Backstop age for suppressions recorded without a parseable retry window.
|
|
252
|
+
*
|
|
253
|
+
* Without this, a provider/account failure that never set `retryAfterMs`
|
|
254
|
+
* (unparsable limit text, incomplete event payload) stays suppressed forever
|
|
255
|
+
* and permanently removes that surface from rotation. 24h is long enough that
|
|
256
|
+
* a real multi-hour/day window still wins when present, and short enough that
|
|
257
|
+
* a sticky unknown-limit entry self-heals on the next calendar day instead of
|
|
258
|
+
* requiring a manual availability reset.
|
|
259
|
+
*/
|
|
260
|
+
export const MAX_SUPPRESSION_AGE_MS = 24 * 60 * 60 * 1000;
|
|
261
|
+
/**
|
|
262
|
+
* Shared expiry predicate for a suppressed availability record: applies to
|
|
263
|
+
* both the reviewer/provider store and the opencode-go account store below.
|
|
264
|
+
* A record clears when its terminal classification is no longer a failure
|
|
265
|
+
* classification, or when its retry window (or the max-age backstop) has
|
|
266
|
+
* elapsed.
|
|
267
|
+
*/
|
|
268
|
+
function suppressionExpired(record, nowMs) {
|
|
269
|
+
if (!record.suppressed)
|
|
270
|
+
return false;
|
|
271
|
+
if (!suppressedRecordStillApplies(record))
|
|
272
|
+
return true;
|
|
273
|
+
if (record.lastFailureAt === undefined)
|
|
274
|
+
return false;
|
|
275
|
+
const failureAt = Date.parse(record.lastFailureAt);
|
|
276
|
+
if (!Number.isFinite(failureAt))
|
|
277
|
+
return false;
|
|
278
|
+
const windowMs = record.retryAfterMs === undefined ? MAX_SUPPRESSION_AGE_MS : record.retryAfterMs;
|
|
279
|
+
return failureAt + windowMs <= nowMs;
|
|
280
|
+
}
|
|
281
|
+
function pruneRecordMap(records, nowMs, pruned) {
|
|
282
|
+
return Object.fromEntries(Object.entries(records).map(([key, record]) => {
|
|
283
|
+
if (suppressionExpired(record, nowMs)) {
|
|
257
284
|
pruned.push(key);
|
|
258
285
|
return [key, { ...record, suppressed: false }];
|
|
259
286
|
}
|
|
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
287
|
return [key, record];
|
|
270
288
|
}));
|
|
271
|
-
|
|
272
|
-
|
|
289
|
+
}
|
|
290
|
+
export function pruneExpiredAvailabilitySuppressions(state, nowMs = Date.now()) {
|
|
291
|
+
const pruned = [];
|
|
292
|
+
const providers = pruneRecordMap(state.providers, nowMs, pruned);
|
|
293
|
+
const reviewers = pruneRecordMap(state.reviewers, nowMs, pruned);
|
|
273
294
|
if (pruned.length === 0)
|
|
274
295
|
return { state, pruned };
|
|
275
296
|
return {
|
|
@@ -282,3 +303,84 @@ export function pruneExpiredAvailabilitySuppressions(state, nowMs = Date.now())
|
|
|
282
303
|
pruned,
|
|
283
304
|
};
|
|
284
305
|
}
|
|
306
|
+
// --- opencode-go multi-account cooldown store (user-scoped, cross-repo) ---
|
|
307
|
+
export const OPENCODE_ACCOUNT_AVAILABILITY_SCHEMA_VERSION = 1;
|
|
308
|
+
export function opencodeAccountAvailabilityPath() {
|
|
309
|
+
return path.join(getStateRoot(), "opencode-account-availability.json");
|
|
310
|
+
}
|
|
311
|
+
function emptyOpencodeAccountAvailability(now = new Date().toISOString()) {
|
|
312
|
+
return {
|
|
313
|
+
schemaVersion: OPENCODE_ACCOUNT_AVAILABILITY_SCHEMA_VERSION,
|
|
314
|
+
updatedAt: now,
|
|
315
|
+
accounts: {},
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
export function loadOpencodeAccountAvailability() {
|
|
319
|
+
const file = opencodeAccountAvailabilityPath();
|
|
320
|
+
if (!existsSync(file))
|
|
321
|
+
return emptyOpencodeAccountAvailability();
|
|
322
|
+
try {
|
|
323
|
+
const parsed = JSON.parse(readFileSync(file, "utf8"));
|
|
324
|
+
if (!isRecord(parsed) ||
|
|
325
|
+
parsed.schemaVersion !== OPENCODE_ACCOUNT_AVAILABILITY_SCHEMA_VERSION) {
|
|
326
|
+
return emptyOpencodeAccountAvailability();
|
|
327
|
+
}
|
|
328
|
+
return {
|
|
329
|
+
schemaVersion: OPENCODE_ACCOUNT_AVAILABILITY_SCHEMA_VERSION,
|
|
330
|
+
updatedAt: typeof parsed.updatedAt === "string" ? parsed.updatedAt : new Date().toISOString(),
|
|
331
|
+
accounts: normalizeRecordMap(parsed.accounts),
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
catch {
|
|
335
|
+
return emptyOpencodeAccountAvailability();
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
function saveOpencodeAccountAvailability(state) {
|
|
339
|
+
const file = opencodeAccountAvailabilityPath();
|
|
340
|
+
mkdirSync(path.dirname(file), { recursive: true });
|
|
341
|
+
writeJsonFileAtomic(file, state);
|
|
342
|
+
}
|
|
343
|
+
export function isOpencodeAccountSuppressed(state, canonicalId) {
|
|
344
|
+
return state.accounts[canonicalId]?.suppressed === true;
|
|
345
|
+
}
|
|
346
|
+
export function recordOpencodeAccountAvailability(canonicalId, event) {
|
|
347
|
+
const at = event.at ?? new Date().toISOString();
|
|
348
|
+
const current = loadOpencodeAccountAvailability();
|
|
349
|
+
const accounts = { ...current.accounts };
|
|
350
|
+
accounts[canonicalId] = {
|
|
351
|
+
canonicalId,
|
|
352
|
+
provider: "opencode",
|
|
353
|
+
terminalClassification: event.terminalClassification ?? "provider-limit-reached",
|
|
354
|
+
...(event.failureCode ? { failureCode: event.failureCode } : {}),
|
|
355
|
+
...(event.limitWindow ? { limitWindow: event.limitWindow } : {}),
|
|
356
|
+
...(event.retryAfterMs === undefined ? {} : { retryAfterMs: event.retryAfterMs }),
|
|
357
|
+
lastFailureAt: at,
|
|
358
|
+
suppressed: true,
|
|
359
|
+
suppressionScope: "provider",
|
|
360
|
+
suppressedAt: at,
|
|
361
|
+
};
|
|
362
|
+
const next = {
|
|
363
|
+
schemaVersion: OPENCODE_ACCOUNT_AVAILABILITY_SCHEMA_VERSION,
|
|
364
|
+
updatedAt: at,
|
|
365
|
+
accounts,
|
|
366
|
+
};
|
|
367
|
+
saveOpencodeAccountAvailability(next);
|
|
368
|
+
return next;
|
|
369
|
+
}
|
|
370
|
+
export function recordOpencodeAccountAvailabilityWithLock(canonicalId, event) {
|
|
371
|
+
return withLock("user", () => recordOpencodeAccountAvailability(canonicalId, event));
|
|
372
|
+
}
|
|
373
|
+
export function pruneExpiredOpencodeAccountSuppressions(state, nowMs = Date.now()) {
|
|
374
|
+
const pruned = [];
|
|
375
|
+
const accounts = pruneRecordMap(state.accounts, nowMs, pruned);
|
|
376
|
+
if (pruned.length === 0)
|
|
377
|
+
return { state, pruned };
|
|
378
|
+
return {
|
|
379
|
+
state: {
|
|
380
|
+
...state,
|
|
381
|
+
updatedAt: new Date(nowMs).toISOString(),
|
|
382
|
+
accounts,
|
|
383
|
+
},
|
|
384
|
+
pruned,
|
|
385
|
+
};
|
|
386
|
+
}
|
|
@@ -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
|
+
}
|