@wardby/cli 0.2.1 → 0.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/.env.example +34 -4
- package/README.md +41 -4
- package/dist/claude-coding-worker/driver.d.ts +6 -1
- package/dist/claude-coding-worker/driver.js +27 -1
- package/dist/claude-coding-worker/main.js +2 -0
- package/dist/claude-coding-worker/tool-socket.d.ts +9 -0
- package/dist/claude-coding-worker/tool-socket.js +26 -0
- package/dist/cli-help.d.ts +1 -1
- package/dist/cli-help.js +13 -4
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +219 -29
- package/dist/coding/collect-exclude.d.ts +25 -0
- package/dist/coding/collect-exclude.js +76 -0
- package/dist/coding/profile.d.ts +67 -22
- package/dist/coding/profile.js +58 -25
- package/dist/coding/protected-path-wording.d.ts +24 -0
- package/dist/coding/protected-path-wording.js +55 -0
- package/dist/coding/protected-paths.d.ts +31 -0
- package/dist/coding/protected-paths.js +48 -0
- package/dist/coding/protocol.d.ts +75 -4
- package/dist/coding/protocol.js +107 -7
- package/dist/coding/registry/adapters.d.ts +2 -0
- package/dist/coding/registry/adapters.js +9 -0
- package/dist/coding/registry/allowlist.d.ts +9 -0
- package/dist/coding/registry/allowlist.js +28 -0
- package/dist/coding/registry/json-scan.d.ts +55 -0
- package/dist/coding/registry/json-scan.js +191 -0
- package/dist/coding/registry/lockfiles.d.ts +6 -0
- package/dist/coding/registry/lockfiles.js +89 -0
- package/dist/coding/registry/npm-lockfile.d.ts +9 -0
- package/dist/coding/registry/npm-lockfile.js +73 -0
- package/dist/coding/registry/npm-plan.d.ts +29 -0
- package/dist/coding/registry/npm-plan.js +289 -0
- package/dist/coding/registry/npm.d.ts +7 -0
- package/dist/coding/registry/npm.js +252 -0
- package/dist/coding/registry/pypi.d.ts +4 -0
- package/dist/coding/registry/pypi.js +210 -0
- package/dist/coding/registry/report.d.ts +37 -0
- package/dist/coding/registry/report.js +31 -0
- package/dist/coding/registry/token.d.ts +4 -0
- package/dist/coding/registry/token.js +7 -0
- package/dist/coding/registry/types.d.ts +286 -0
- package/dist/coding/registry/types.js +19 -0
- package/dist/coding/registry/worker-config.d.ts +14 -0
- package/dist/coding/registry/worker-config.js +31 -0
- package/dist/coding/services/builtins.d.ts +22 -0
- package/dist/coding/services/builtins.js +85 -0
- package/dist/coding/services/catalog.d.ts +443 -0
- package/dist/coding/services/catalog.js +159 -0
- package/dist/coding/services/declaration.d.ts +11 -0
- package/dist/coding/services/declaration.js +114 -0
- package/dist/coding/services/note.d.ts +8 -0
- package/dist/coding/services/note.js +15 -0
- package/dist/coding/services/resolve.d.ts +25 -0
- package/dist/coding/services/resolve.js +50 -0
- package/dist/coding/services/wording.d.ts +25 -0
- package/dist/coding/services/wording.js +70 -0
- package/dist/coding-proxy/main.js +24 -4
- package/dist/coding-worker/artifact.d.ts +6 -0
- package/dist/coding-worker/debug-trace.d.ts +37 -0
- package/dist/coding-worker/debug-trace.js +117 -0
- package/dist/coding-worker/driver.d.ts +13 -1
- package/dist/coding-worker/driver.js +102 -13
- package/dist/coding-worker/errors.js +8 -0
- package/dist/coding-worker/main.js +10 -2
- package/dist/coding-worker/sdk.d.ts +2 -2
- package/dist/coding-worker/sdk.js +7 -2
- package/dist/coding-worker/types.d.ts +3 -0
- package/dist/config/providers.d.ts +41 -0
- package/dist/config/providers.js +66 -0
- package/dist/core/budget-groups.d.ts +108 -5
- package/dist/core/budget-groups.js +125 -19
- package/dist/core/budget-wording.d.ts +22 -0
- package/dist/core/budget-wording.js +55 -0
- package/dist/core/datastores.js +18 -2
- package/dist/core/db.d.ts +5 -1
- package/dist/core/db.js +8 -2
- package/dist/core/dispatch.d.ts +33 -6
- package/dist/core/dispatch.js +260 -43
- package/dist/core/engine-native.js +27 -10
- package/dist/core/grants.d.ts +86 -0
- package/dist/core/grants.js +126 -0
- package/dist/core/host-events.d.ts +49 -0
- package/dist/core/host-events.js +257 -0
- package/dist/core/host-identity-links.d.ts +54 -0
- package/dist/core/host-identity-links.js +189 -0
- package/dist/core/host-status.d.ts +61 -0
- package/dist/core/host-status.js +211 -0
- package/dist/core/in-flight-runs.d.ts +8 -0
- package/dist/core/in-flight-runs.js +56 -0
- package/dist/core/provider-wording.d.ts +12 -0
- package/dist/core/provider-wording.js +44 -0
- package/dist/core/reconciler.d.ts +38 -2
- package/dist/core/reconciler.js +86 -2
- package/dist/core/repo-access.d.ts +99 -0
- package/dist/core/repo-access.js +136 -0
- package/dist/core/review-host-checks.d.ts +11 -0
- package/dist/core/review-host-checks.js +41 -0
- package/dist/core/review-host-tools.d.ts +47 -0
- package/dist/core/review-host-tools.js +347 -0
- package/dist/core/run-heartbeat.d.ts +27 -0
- package/dist/core/run-heartbeat.js +54 -0
- package/dist/core/runner.d.ts +30 -8
- package/dist/core/runner.js +237 -40
- package/dist/core/secrets.d.ts +11 -2
- package/dist/core/secrets.js +25 -6
- package/dist/core/subagent-memory-tools.d.ts +1 -1
- package/dist/core/subagent-memory-tools.js +16 -3
- package/dist/core/tool-admin.d.ts +81 -0
- package/dist/core/tool-admin.js +129 -0
- package/dist/core/tool-names.d.ts +42 -0
- package/dist/core/tool-names.js +64 -0
- package/dist/core/untrusted-content.d.ts +32 -0
- package/dist/core/untrusted-content.js +72 -0
- package/dist/core/webhooks.d.ts +8 -1
- package/dist/core/webhooks.js +23 -2
- package/dist/generated/prisma/browser.d.ts +100 -0
- package/dist/generated/prisma/client.d.ts +100 -0
- package/dist/generated/prisma/commonInputTypes.d.ts +30 -0
- package/dist/generated/prisma/enums.d.ts +6 -0
- package/dist/generated/prisma/enums.js +6 -1
- package/dist/generated/prisma/internal/class.d.ts +143 -0
- package/dist/generated/prisma/internal/class.js +4 -4
- package/dist/generated/prisma/internal/prismaNamespace.d.ts +1746 -577
- package/dist/generated/prisma/internal/prismaNamespace.js +179 -6
- package/dist/generated/prisma/internal/prismaNamespaceBrowser.d.ts +186 -0
- package/dist/generated/prisma/internal/prismaNamespaceBrowser.js +179 -6
- package/dist/generated/prisma/models/Agent.d.ts +294 -1
- package/dist/generated/prisma/models/AgentRepository.d.ts +1425 -0
- package/dist/generated/prisma/models/AgentRepository.js +1 -0
- package/dist/generated/prisma/models/AgentTool.d.ts +95 -1
- package/dist/generated/prisma/models/AuthUser.d.ts +56 -1
- package/dist/generated/prisma/models/CodingAgentProfile.d.ts +258 -7
- package/dist/generated/prisma/models/CodingProxySession.d.ts +123 -2
- package/dist/generated/prisma/models/CodingRun.d.ts +1276 -95
- package/dist/generated/prisma/models/CodingService.d.ts +1348 -0
- package/dist/generated/prisma/models/CodingService.js +1 -0
- package/dist/generated/prisma/models/HostEventDelivery.d.ts +946 -0
- package/dist/generated/prisma/models/HostEventDelivery.js +1 -0
- package/dist/generated/prisma/models/HostIdentity.d.ts +1232 -0
- package/dist/generated/prisma/models/HostIdentity.js +1 -0
- package/dist/generated/prisma/models/HostIdentityLinkRequest.d.ts +1473 -0
- package/dist/generated/prisma/models/HostIdentityLinkRequest.js +1 -0
- package/dist/generated/prisma/models/Principal.d.ts +455 -0
- package/dist/generated/prisma/models/RegistryAllowance.d.ts +1148 -0
- package/dist/generated/prisma/models/RegistryAllowance.js +1 -0
- package/dist/generated/prisma/models/RegistryApprovedVersion.d.ts +1219 -0
- package/dist/generated/prisma/models/RegistryApprovedVersion.js +1 -0
- package/dist/generated/prisma/models/RegistryFetch.d.ts +1428 -0
- package/dist/generated/prisma/models/RegistryFetch.js +1 -0
- package/dist/generated/prisma/models/RegistryPlanRefusal.d.ts +1294 -0
- package/dist/generated/prisma/models/RegistryPlanRefusal.js +1 -0
- package/dist/generated/prisma/models/RegistryVersionFact.d.ts +1085 -0
- package/dist/generated/prisma/models/RegistryVersionFact.js +1 -0
- package/dist/generated/prisma/models/ResourceGrant.d.ts +1437 -0
- package/dist/generated/prisma/models/ResourceGrant.js +1 -0
- package/dist/generated/prisma/models/Run.d.ts +389 -1
- package/dist/generated/prisma/models/RunHostCheck.d.ts +1239 -0
- package/dist/generated/prisma/models/RunHostCheck.js +1 -0
- package/dist/generated/prisma/models/RunHostStatus.d.ts +1315 -0
- package/dist/generated/prisma/models/RunHostStatus.js +1 -0
- package/dist/generated/prisma/models/Tool.d.ts +15 -3
- package/dist/generated/prisma/models.d.ts +13 -0
- package/dist/help/build.d.ts +1 -0
- package/dist/help/build.js +9 -0
- package/dist/help/catalog.d.ts +24 -0
- package/dist/help/catalog.js +160 -0
- package/dist/help/cli.d.ts +2 -0
- package/dist/help/cli.js +65 -0
- package/dist/help/runtime.d.ts +3 -0
- package/dist/help/runtime.js +44 -0
- package/dist/help/search.d.ts +9 -0
- package/dist/help/search.js +104 -0
- package/dist/help-index.json +660 -0
- package/dist/import/cli-args.js +3 -2
- package/dist/import/create.d.ts +5 -0
- package/dist/import/create.js +67 -13
- package/dist/import/index.js +19 -9
- package/dist/import/neutral-schema.d.ts +11 -11
- package/dist/mcp/auth/access.d.ts +70 -0
- package/dist/mcp/auth/access.js +58 -0
- package/dist/mcp/auth/grants-cli.d.ts +149 -0
- package/dist/mcp/auth/grants-cli.js +518 -0
- package/dist/mcp/auth/host-account-cli.d.ts +2 -0
- package/dist/mcp/auth/host-account-cli.js +47 -0
- package/dist/mcp/auth/ownership.d.ts +26 -52
- package/dist/mcp/auth/ownership.js +19 -14
- package/dist/mcp/auth/repo-authorization.d.ts +22 -0
- package/dist/mcp/auth/repo-authorization.js +51 -0
- package/dist/mcp/auth/resource-server.d.ts +31 -2
- package/dist/mcp/auth/resource-server.js +84 -3
- package/dist/mcp/auth/self-hosted/browser.js +2 -2
- package/dist/mcp/auth/self-hosted/cli.js +47 -13
- package/dist/mcp/auth/self-hosted/credentials.d.ts +20 -2
- package/dist/mcp/auth/self-hosted/credentials.js +62 -4
- package/dist/mcp/auth/self-hosted/session.d.ts +2 -1
- package/dist/mcp/context.d.ts +27 -1
- package/dist/mcp/errors.d.ts +25 -6
- package/dist/mcp/errors.js +98 -0
- package/dist/mcp/host-events/github-ingress.d.ts +36 -0
- package/dist/mcp/host-events/github-ingress.js +92 -0
- package/dist/mcp/host-events/github-user-callback.d.ts +18 -0
- package/dist/mcp/host-events/github-user-callback.js +50 -0
- package/dist/mcp/index.d.ts +8 -1
- package/dist/mcp/index.js +106 -16
- package/dist/mcp/server.js +27 -9
- package/dist/mcp/tools/agents.js +446 -48
- package/dist/mcp/tools/budget-groups.js +3 -3
- package/dist/mcp/tools/datastore.js +22 -15
- package/dist/mcp/tools/grants.d.ts +2 -0
- package/dist/mcp/tools/grants.js +239 -0
- package/dist/mcp/tools/help.d.ts +5 -0
- package/dist/mcp/tools/help.js +67 -0
- package/dist/mcp/tools/host-accounts.d.ts +2 -0
- package/dist/mcp/tools/host-accounts.js +114 -0
- package/dist/mcp/tools/memory.d.ts +7 -1
- package/dist/mcp/tools/memory.js +5 -5
- package/dist/mcp/tools/repositories.d.ts +2 -0
- package/dist/mcp/tools/repositories.js +181 -0
- package/dist/mcp/tools/runs.d.ts +6 -0
- package/dist/mcp/tools/runs.js +60 -6
- package/dist/mcp/tools/scheduling.js +3 -6
- package/dist/mcp/tools/secrets.js +18 -6
- package/dist/mcp/tools/services.d.ts +2 -0
- package/dist/mcp/tools/services.js +222 -0
- package/dist/mcp/tools/subagents.js +52 -16
- package/dist/mcp/tools/tools.d.ts +41 -0
- package/dist/mcp/tools/tools.js +201 -40
- package/dist/mcp/tools/trigger.js +36 -6
- package/dist/mcp/tools/webhooks.js +15 -4
- package/dist/mcp/transport/streamable-http.d.ts +10 -0
- package/dist/mcp/transport/streamable-http.js +32 -2
- package/dist/providers/auth/delegating.d.ts +19 -0
- package/dist/providers/auth/delegating.js +71 -0
- package/dist/providers/auth/index.d.ts +2 -0
- package/dist/providers/auth/index.js +11 -0
- package/dist/providers/auth/self-hosted.d.ts +2 -1
- package/dist/providers/auth/self-hosted.js +16 -4
- package/dist/providers/auth/types.d.ts +6 -0
- package/dist/providers/coding-proxy/memory-ledger.d.ts +3 -0
- package/dist/providers/coding-proxy/memory-ledger.js +21 -2
- package/dist/providers/coding-proxy/metering.d.ts +4 -0
- package/dist/providers/coding-proxy/metering.js +28 -2
- package/dist/providers/coding-proxy/prisma-ledger.d.ts +17 -0
- package/dist/providers/coding-proxy/prisma-ledger.js +50 -5
- package/dist/providers/coding-proxy/proxy.d.ts +13 -0
- package/dist/providers/coding-proxy/proxy.js +479 -18
- package/dist/providers/coding-proxy/registry/audit.d.ts +131 -0
- package/dist/providers/coding-proxy/registry/audit.js +380 -0
- package/dist/providers/coding-proxy/registry/bounded-fetch.d.ts +16 -0
- package/dist/providers/coding-proxy/registry/bounded-fetch.js +77 -0
- package/dist/providers/coding-proxy/registry/plan.d.ts +58 -0
- package/dist/providers/coding-proxy/registry/plan.js +304 -0
- package/dist/providers/coding-proxy/registry/prisma-store.d.ts +34 -0
- package/dist/providers/coding-proxy/registry/prisma-store.js +151 -0
- package/dist/providers/coding-proxy/registry/service.d.ts +213 -0
- package/dist/providers/coding-proxy/registry/service.js +1137 -0
- package/dist/providers/coding-proxy/registry/store.d.ts +127 -0
- package/dist/providers/coding-proxy/registry/store.js +70 -0
- package/dist/providers/coding-proxy/runtime.d.ts +17 -0
- package/dist/providers/coding-proxy/runtime.js +63 -0
- package/dist/providers/coding-proxy/secure-fetch.js +0 -1
- package/dist/providers/coding-proxy/server.d.ts +11 -0
- package/dist/providers/coding-proxy/server.js +163 -0
- package/dist/providers/coding-proxy/types.d.ts +15 -0
- package/dist/providers/engine/types.d.ts +10 -0
- package/dist/providers/executor/build.d.ts +2 -2
- package/dist/providers/executor/composition.d.ts +3 -0
- package/dist/providers/executor/composition.js +19 -0
- package/dist/providers/executor/container.d.ts +105 -3
- package/dist/providers/executor/container.js +296 -20
- package/dist/providers/executor/dbos.d.ts +2 -3
- package/dist/providers/executor/in-process.d.ts +2 -3
- package/dist/providers/executor/routing.d.ts +7 -0
- package/dist/providers/executor/routing.js +13 -0
- package/dist/providers/executor/types.d.ts +22 -0
- package/dist/providers/jobs/claude-tool-setup.d.ts +23 -0
- package/dist/providers/jobs/claude-tool-setup.js +50 -0
- package/dist/providers/jobs/collect-prune.d.ts +7 -0
- package/dist/providers/jobs/collect-prune.js +27 -0
- package/dist/providers/jobs/docker-isolation.d.ts +48 -3
- package/dist/providers/jobs/docker-isolation.js +213 -22
- package/dist/providers/jobs/docker-services.d.ts +35 -0
- package/dist/providers/jobs/docker-services.js +191 -0
- package/dist/providers/jobs/docker.d.ts +49 -2
- package/dist/providers/jobs/docker.js +279 -32
- package/dist/providers/jobs/fake-kubernetes-api.d.ts +1 -0
- package/dist/providers/jobs/fake-kubernetes-api.js +12 -3
- package/dist/providers/jobs/kubernetes-isolation.d.ts +17 -2
- package/dist/providers/jobs/kubernetes-isolation.js +238 -57
- package/dist/providers/jobs/kubernetes-platform.d.ts +10 -4
- package/dist/providers/jobs/kubernetes-platform.js +11 -5
- package/dist/providers/jobs/kubernetes-preflight.js +3 -0
- package/dist/providers/jobs/kubernetes.d.ts +21 -2
- package/dist/providers/jobs/kubernetes.js +112 -21
- package/dist/providers/jobs/types.d.ts +18 -0
- package/dist/providers/llm/anthropic.js +6 -2
- package/dist/providers/llm/bedrock.js +2 -1
- package/dist/providers/llm/claude-messages.d.ts +5 -1
- package/dist/providers/llm/claude-messages.js +1 -0
- package/dist/providers/llm/claude-provider.d.ts +3 -1
- package/dist/providers/llm/claude-provider.js +5 -1
- package/dist/providers/llm/index.d.ts +1 -1
- package/dist/providers/llm/index.js +1 -1
- package/dist/providers/llm/pricing-anthropic.d.ts +10 -0
- package/dist/providers/llm/pricing-anthropic.js +32 -14
- package/dist/providers/llm/pricing-bedrock-claude.d.ts +8 -0
- package/dist/providers/llm/pricing-bedrock-claude.js +9 -0
- package/dist/providers/llm/routing.d.ts +10 -1
- package/dist/providers/llm/routing.js +18 -0
- package/dist/providers/llm/types.d.ts +12 -0
- package/dist/providers/llm/types.js +8 -1
- package/dist/providers/review-host/diff-lines.d.ts +16 -0
- package/dist/providers/review-host/diff-lines.js +59 -0
- package/dist/providers/review-host/github-events.d.ts +6 -0
- package/dist/providers/review-host/github-events.js +180 -0
- package/dist/providers/review-host/github-user-auth.d.ts +38 -0
- package/dist/providers/review-host/github-user-auth.js +128 -0
- package/dist/providers/review-host/github.d.ts +57 -0
- package/dist/providers/review-host/github.js +567 -0
- package/dist/providers/review-host/index.d.ts +12 -0
- package/dist/providers/review-host/index.js +30 -0
- package/dist/providers/review-host/review-format.d.ts +19 -0
- package/dist/providers/review-host/review-format.js +59 -0
- package/dist/providers/review-host/types.d.ts +284 -0
- package/dist/providers/review-host/types.js +24 -0
- package/dist/providers/vcs/git.d.ts +16 -6
- package/dist/providers/vcs/git.js +77 -13
- package/dist/providers/vcs/github.d.ts +74 -3
- package/dist/providers/vcs/github.js +195 -15
- package/dist/providers/vcs/types.d.ts +42 -4
- package/dist/quickstart/index.js +13 -2
- package/dist/sandbox/fetch-policy.d.ts +20 -2
- package/dist/sandbox/fetch-policy.js +64 -3
- package/dist/sandbox/host-functions.d.ts +9 -1
- package/dist/sandbox/host-functions.js +12 -5
- package/dist/serve.js +1 -1
- package/dist/wardby-bin.js +6 -0
- package/docs/README.md +30 -0
- package/docs/architecture-runtime.md +90 -0
- package/docs/assets/brand/wardby-icon-512.png +0 -0
- package/docs/assets/brand/wardby-icon.svg +16 -0
- package/docs/assets/brand/wardby-mascot-profile-512.png +0 -0
- package/docs/assets/brand/wardby-mascot.png +0 -0
- package/docs/assets/brand/wardby-mascot.svg +5 -0
- package/docs/assets/wardby-workflow.svg +106 -0
- package/docs/code-review-agents.md +481 -0
- package/docs/coding-agent-setup.md +172 -0
- package/docs/coding-packages.md +455 -0
- package/docs/coding-services.md +300 -0
- package/docs/coding-worker-byo-images.md +98 -0
- package/docs/coding-worker-isolation.md +1061 -0
- package/docs/getting-started-gke.md +615 -0
- package/docs/getting-started-identity-provider.md +308 -0
- package/docs/getting-started.md +133 -0
- package/docs/observability.md +53 -0
- package/docs/release-verification.md +66 -0
- package/docs/security-deployment.md +657 -0
- package/help/code-review-agents.md +31 -0
- package/help/coding-packages.md +31 -0
- package/help/coding-services.md +71 -0
- package/help/creating-agents.md +68 -0
- package/help/deploy-gke.md +39 -0
- package/help/deployment-targets.md +39 -0
- package/help/errors/budget-group-exhausted.md +25 -0
- package/help/errors/docker-isolation-unsupported.md +26 -0
- package/help/errors/protected-path.md +45 -0
- package/help/errors/repo-access.md +27 -0
- package/help/errors/service-declaration-invalid.md +27 -0
- package/help/errors/service-declaration-unavailable.md +25 -0
- package/help/errors/service-launcher-unsupported.md +30 -0
- package/help/errors/service-not-allowed.md +27 -0
- package/help/errors/service-unknown.md +23 -0
- package/help/errors/service-unready.md +49 -0
- package/help/getting-started.md +31 -0
- package/help/github.md +30 -0
- package/help/identity-and-access.md +39 -0
- package/help/mcp.md +30 -0
- package/help/native-capabilities.md +30 -0
- package/help/observability.md +41 -0
- package/help/operating-agents.md +30 -0
- package/help/security.md +27 -0
- package/help/troubleshooting/budgets.md +26 -0
- package/help/troubleshooting/coding-workers.md +47 -0
- package/help/troubleshooting/repository-access.md +27 -0
- package/package.json +12 -3
- package/prisma/migrations/20260925010000_coding_collect_exclude/migration.sql +8 -0
- package/prisma/migrations/20260925015000_allowed_egress_default/migration.sql +8 -0
- package/prisma/migrations/20260925020000_coding_package_registry/migration.sql +55 -0
- package/prisma/migrations/20260925030000_tool_name_per_owner/migration.sql +11 -0
- package/prisma/migrations/20260926010000_run_trigger_host_event/migration.sql +7 -0
- package/prisma/migrations/20260926020000_code_review_hosts/migration.sql +53 -0
- package/prisma/migrations/20260926030000_auth_user_roles/migration.sql +9 -0
- package/prisma/migrations/20260926040000_agent_effort/migration.sql +6 -0
- package/prisma/migrations/20260926050000_registry_lockfile_plan/migration.sql +32 -0
- package/prisma/migrations/20260926050000_repo_access_authorization/migration.sql +82 -0
- package/prisma/migrations/20260926060000_registry_plan_refusal/migration.sql +19 -0
- package/prisma/migrations/20260926100000_registry_plan_refusal_published_at/migration.sql +5 -0
- package/prisma/migrations/20260926190000_run_host_status/migration.sql +18 -0
- package/prisma/migrations/20260926210000_run_host_status_at_dispatch/migration.sql +5 -0
- package/prisma/migrations/20260927010000_resource_grants/migration.sql +72 -0
- package/prisma/migrations/20260927020000_proxy_session_budget_exhausted/migration.sql +3 -0
- package/prisma/migrations/20260927030000_coding_debug_trace/migration.sql +4 -0
- package/prisma/migrations/20260927040000_proxy_session_upstream_failure/migration.sql +3 -0
- package/prisma/migrations/20260927050000_coding_run_services/migration.sql +74 -0
- package/prisma/migrations/20260928000000_coding_run_tool_image/migration.sql +5 -0
- package/prisma/schema.prisma +407 -21
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/** Agent ids are cuids; anything else could break out of the HTML comment. */
|
|
2
|
+
export const SAFE_AGENT_MARKER = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/;
|
|
3
|
+
const SAFE_SHA = /^[0-9a-f]{40}$/;
|
|
4
|
+
const MARKER_PREFIX = "<!-- wardby:review:";
|
|
5
|
+
function assertMarker(agentMarker) {
|
|
6
|
+
if (!SAFE_AGENT_MARKER.test(agentMarker))
|
|
7
|
+
throw new Error("review_marker_invalid");
|
|
8
|
+
}
|
|
9
|
+
export function reviewMarker(agentMarker, headSha) {
|
|
10
|
+
assertMarker(agentMarker);
|
|
11
|
+
if (!SAFE_SHA.test(headSha))
|
|
12
|
+
throw new Error("review_marker_invalid");
|
|
13
|
+
return `${MARKER_PREFIX}${agentMarker} sha=${headSha} -->`;
|
|
14
|
+
}
|
|
15
|
+
export function hasReviewMarker(body, agentMarker) {
|
|
16
|
+
assertMarker(agentMarker);
|
|
17
|
+
return body.includes(`${MARKER_PREFIX}${agentMarker} sha=`);
|
|
18
|
+
}
|
|
19
|
+
export function parseReviewMarker(body, agentMarker) {
|
|
20
|
+
assertMarker(agentMarker);
|
|
21
|
+
const match = new RegExp(`${MARKER_PREFIX}${agentMarker} sha=([0-9a-f]{40}) -->`).exec(body);
|
|
22
|
+
return match ? match[1] : null;
|
|
23
|
+
}
|
|
24
|
+
export function verdictConclusion(verdict) {
|
|
25
|
+
if (verdict === "APPROVE")
|
|
26
|
+
return "success";
|
|
27
|
+
if (verdict === "CHANGES_REQUESTED")
|
|
28
|
+
return "failure";
|
|
29
|
+
return "neutral";
|
|
30
|
+
}
|
|
31
|
+
const BADGES = {
|
|
32
|
+
APPROVE: "✅ **Approved**",
|
|
33
|
+
CHANGES_REQUESTED: "❌ **Changes requested**",
|
|
34
|
+
COMMENT: "💬 **Comments**",
|
|
35
|
+
};
|
|
36
|
+
const FINDING_PREFIX = "<!-- wardby:finding:";
|
|
37
|
+
/** Tags an inline comment with the agent that wrote it, so only that agent resolves its thread. */
|
|
38
|
+
export function findingMarker(agentMarker) {
|
|
39
|
+
assertMarker(agentMarker);
|
|
40
|
+
return `${FINDING_PREFIX}${agentMarker} -->`;
|
|
41
|
+
}
|
|
42
|
+
export function hasFindingMarker(body, agentMarker) {
|
|
43
|
+
return body.includes(findingMarker(agentMarker));
|
|
44
|
+
}
|
|
45
|
+
export function renderInlineComment(comment, agentMarker) {
|
|
46
|
+
return `**[${comment.severity}]** ${comment.body}\n\n${findingMarker(agentMarker)}`;
|
|
47
|
+
}
|
|
48
|
+
export function renderSummaryComment(input) {
|
|
49
|
+
const sections = [
|
|
50
|
+
reviewMarker(input.agentMarker, input.headSha),
|
|
51
|
+
`${BADGES[input.verdict]} — reviewed \`${input.headSha.slice(0, 7)}\``,
|
|
52
|
+
input.summary,
|
|
53
|
+
input.body,
|
|
54
|
+
];
|
|
55
|
+
if (input.outside.length > 0) {
|
|
56
|
+
sections.push(["## Outside the diff", ...input.outside.map((c) => `- **[${c.severity}] ${c.path}:${c.line}** ${c.body}`)].join("\n"));
|
|
57
|
+
}
|
|
58
|
+
return sections.join("\n\n");
|
|
59
|
+
}
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host-neutral code-review surface for native agents. GitHub is the first
|
|
3
|
+
* implementation (./github.ts); Bitbucket Cloud is anticipated. Every method
|
|
4
|
+
* takes the repository and mints its own least-permission credential — no
|
|
5
|
+
* method accepts a token or a permission. See
|
|
6
|
+
* docs/private/2026-09-25-code-review-host-design.md §5.
|
|
7
|
+
*/
|
|
8
|
+
export type ReviewHostProvider = "github";
|
|
9
|
+
export declare const REVIEW_HOST_PROVIDERS: readonly ReviewHostProvider[];
|
|
10
|
+
export type ReviewVerdict = "APPROVE" | "CHANGES_REQUESTED" | "COMMENT";
|
|
11
|
+
export type CheckConclusion = "success" | "failure" | "neutral";
|
|
12
|
+
export type CommentSide = "LEFT" | "RIGHT";
|
|
13
|
+
export interface PullRequestFileView {
|
|
14
|
+
filename: string;
|
|
15
|
+
status: string;
|
|
16
|
+
additions: number;
|
|
17
|
+
deletions: number;
|
|
18
|
+
previousFilename?: string;
|
|
19
|
+
patch: string;
|
|
20
|
+
patchTruncated: boolean;
|
|
21
|
+
}
|
|
22
|
+
/** One of this agent's own review threads that is still unresolved. */
|
|
23
|
+
export interface ReviewThreadView {
|
|
24
|
+
/** Opaque host id; pass it back in PublishReviewInput.resolveThreadIds. */
|
|
25
|
+
id: string;
|
|
26
|
+
path: string;
|
|
27
|
+
/** The line in the current head, or null when the thread is outdated (its line no longer exists). */
|
|
28
|
+
line: number | null;
|
|
29
|
+
outdated: boolean;
|
|
30
|
+
/** The finding as posted, without its marker, capped at 1,000 characters. */
|
|
31
|
+
body: string;
|
|
32
|
+
}
|
|
33
|
+
export interface PullRequestView {
|
|
34
|
+
number: number;
|
|
35
|
+
title: string;
|
|
36
|
+
body: string;
|
|
37
|
+
author: string | null;
|
|
38
|
+
state: string;
|
|
39
|
+
merged: boolean;
|
|
40
|
+
draft: boolean;
|
|
41
|
+
baseRef: string;
|
|
42
|
+
headRef: string;
|
|
43
|
+
headSha: string;
|
|
44
|
+
isFork: boolean;
|
|
45
|
+
htmlUrl: string;
|
|
46
|
+
/** Head sha recorded in this agent's summary comment, if it reviewed before. */
|
|
47
|
+
lastReviewedSha: string | null;
|
|
48
|
+
/** Set when `files` is the compare of `comparedFrom...headSha` rather than the whole PR. */
|
|
49
|
+
comparedFrom: string | null;
|
|
50
|
+
/**
|
|
51
|
+
* The base branch was merged into the PR since `comparedFrom`: `files` is
|
|
52
|
+
* then the PR's own diff (against its base), limited to files that changed
|
|
53
|
+
* since `comparedFrom`, so base-branch changes are left out.
|
|
54
|
+
*/
|
|
55
|
+
baseMergedSince: boolean;
|
|
56
|
+
files: PullRequestFileView[];
|
|
57
|
+
/** This agent's own unresolved review threads on the PR (the first 100 threads are searched). */
|
|
58
|
+
openThreads: ReviewThreadView[];
|
|
59
|
+
}
|
|
60
|
+
export interface PullRequestHead {
|
|
61
|
+
headSha: string;
|
|
62
|
+
isFork: boolean;
|
|
63
|
+
state: string;
|
|
64
|
+
}
|
|
65
|
+
export type FileReadResult = {
|
|
66
|
+
kind: "file";
|
|
67
|
+
path: string;
|
|
68
|
+
ref: string;
|
|
69
|
+
totalLines: number;
|
|
70
|
+
startLine: number;
|
|
71
|
+
endLine: number;
|
|
72
|
+
truncated: boolean;
|
|
73
|
+
content: string;
|
|
74
|
+
} | {
|
|
75
|
+
kind: "directory";
|
|
76
|
+
path: string;
|
|
77
|
+
entries: string[];
|
|
78
|
+
} | {
|
|
79
|
+
kind: "not_found";
|
|
80
|
+
path: string;
|
|
81
|
+
};
|
|
82
|
+
export interface FileListView {
|
|
83
|
+
ref: string;
|
|
84
|
+
count: number;
|
|
85
|
+
truncated: boolean;
|
|
86
|
+
files: string[];
|
|
87
|
+
}
|
|
88
|
+
export interface InlineComment {
|
|
89
|
+
path: string;
|
|
90
|
+
line: number;
|
|
91
|
+
side: CommentSide;
|
|
92
|
+
severity: string;
|
|
93
|
+
body: string;
|
|
94
|
+
}
|
|
95
|
+
export interface PublishReviewInput {
|
|
96
|
+
prNumber: number;
|
|
97
|
+
headSha: string;
|
|
98
|
+
verdict: ReviewVerdict;
|
|
99
|
+
summary: string;
|
|
100
|
+
body: string;
|
|
101
|
+
comments: InlineComment[];
|
|
102
|
+
/** Identifies this agent's summary comment; the agent id. */
|
|
103
|
+
agentMarker: string;
|
|
104
|
+
/**
|
|
105
|
+
* Names a completed check to create when no checkId is supplied (a run not
|
|
106
|
+
* started by a host event). With neither, no check is created.
|
|
107
|
+
*/
|
|
108
|
+
checkName?: string;
|
|
109
|
+
/** The run's own in-progress check, when the control plane started one. */
|
|
110
|
+
checkId?: string;
|
|
111
|
+
/**
|
|
112
|
+
* Threads to resolve because this head fixes them. Only this agent's own
|
|
113
|
+
* unresolved threads on this PR are resolved; any other id is skipped.
|
|
114
|
+
*/
|
|
115
|
+
resolveThreadIds?: string[];
|
|
116
|
+
}
|
|
117
|
+
export type PublishReviewResult = {
|
|
118
|
+
published: true;
|
|
119
|
+
reviewUrl: string | null;
|
|
120
|
+
summaryCommentUrl: string;
|
|
121
|
+
/** Null when no check was completed or created (neither checkId nor checkName). */
|
|
122
|
+
checkId: string | null;
|
|
123
|
+
/** The verdict's check mapping — informational when checkId is null. */
|
|
124
|
+
checkConclusion: CheckConclusion;
|
|
125
|
+
inlineCount: number;
|
|
126
|
+
outsideDiffCount: number;
|
|
127
|
+
resolvedThreadIds: string[];
|
|
128
|
+
/** Requested ids that were not resolved: not this agent's open thread on this PR, or the host refused. */
|
|
129
|
+
skippedThreadIds: string[];
|
|
130
|
+
} | {
|
|
131
|
+
published: false;
|
|
132
|
+
reason: "stale_head";
|
|
133
|
+
currentHeadSha: string;
|
|
134
|
+
};
|
|
135
|
+
export interface CommentInput {
|
|
136
|
+
number: number;
|
|
137
|
+
body: string;
|
|
138
|
+
replyToReviewCommentId?: string;
|
|
139
|
+
}
|
|
140
|
+
/** A comment the App wrote, to edit later: a conversation comment or a reply in a review thread. */
|
|
141
|
+
export interface EditCommentInput {
|
|
142
|
+
kind: "conversation" | "inline";
|
|
143
|
+
id: string;
|
|
144
|
+
body: string;
|
|
145
|
+
}
|
|
146
|
+
export interface CommentRef {
|
|
147
|
+
/**
|
|
148
|
+
* "conversation" = a top-level comment on an issue/PR conversation; "inline" = a comment on a diff line (review
|
|
149
|
+
* thread); "subject" = the issue or PR itself (its title/description), with `id` its number.
|
|
150
|
+
*/
|
|
151
|
+
kind: "conversation" | "inline" | "subject";
|
|
152
|
+
id: string;
|
|
153
|
+
}
|
|
154
|
+
export interface StartCheckInput {
|
|
155
|
+
headSha: string;
|
|
156
|
+
name: string;
|
|
157
|
+
}
|
|
158
|
+
export interface CompleteCheckInput {
|
|
159
|
+
checkId: string;
|
|
160
|
+
conclusion: CheckConclusion;
|
|
161
|
+
title: string;
|
|
162
|
+
summary: string;
|
|
163
|
+
text?: string;
|
|
164
|
+
detailsUrl?: string;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* A user's effective permission on one repository, host-neutral and ranked
|
|
168
|
+
* none < read < triage < write < maintain < admin. GitHub maps its
|
|
169
|
+
* collaborator permission onto these directly; GitLab would map reporter ->
|
|
170
|
+
* read, developer -> write, maintainer -> maintain, owner -> admin.
|
|
171
|
+
*/
|
|
172
|
+
export type HostPermission = "none" | "read" | "triage" | "write" | "maintain" | "admin";
|
|
173
|
+
export declare const HOST_PERMISSION_RANK: Readonly<Record<HostPermission, number>>;
|
|
174
|
+
/** A host user, by its immutable numeric id (as text) and its current login. */
|
|
175
|
+
export interface HostUser {
|
|
176
|
+
id: string;
|
|
177
|
+
login: string;
|
|
178
|
+
}
|
|
179
|
+
export interface CodeReviewHost {
|
|
180
|
+
readonly provider: ReviewHostProvider;
|
|
181
|
+
/**
|
|
182
|
+
* The user's effective permission on `repository`, asked with the App's own
|
|
183
|
+
* installation credential (never a user token). Answers only for the user
|
|
184
|
+
* with this id: a login that now names someone else is re-resolved by id,
|
|
185
|
+
* and `login` in the result is the user's current login.
|
|
186
|
+
*/
|
|
187
|
+
repositoryPermission(repository: string, user: HostUser): Promise<{
|
|
188
|
+
level: HostPermission;
|
|
189
|
+
login: string;
|
|
190
|
+
}>;
|
|
191
|
+
readPullRequest(repository: string, prNumber: number, opts: {
|
|
192
|
+
sinceSha?: string;
|
|
193
|
+
maxPatchChars: number;
|
|
194
|
+
agentMarker: string;
|
|
195
|
+
}): Promise<PullRequestView>;
|
|
196
|
+
pullRequestHead(repository: string, prNumber: number): Promise<PullRequestHead>;
|
|
197
|
+
readFile(repository: string, path: string, ref: string | undefined, window: {
|
|
198
|
+
startLine: number;
|
|
199
|
+
maxLines: number;
|
|
200
|
+
}): Promise<FileReadResult>;
|
|
201
|
+
listFiles(repository: string, ref: string | undefined, pathPrefix?: string): Promise<FileListView>;
|
|
202
|
+
publishReview(repository: string, input: PublishReviewInput): Promise<PublishReviewResult>;
|
|
203
|
+
/** `id` identifies the new comment for editComment; a reply is an "inline" comment, anything else "conversation". */
|
|
204
|
+
comment(repository: string, input: CommentInput): Promise<{
|
|
205
|
+
url: string;
|
|
206
|
+
id: string;
|
|
207
|
+
}>;
|
|
208
|
+
/** Replaces the body of a comment the App wrote. */
|
|
209
|
+
editComment(repository: string, input: EditCommentInput): Promise<void>;
|
|
210
|
+
/** Marks a comment as picked up (GitHub: a 👀 reaction); a host without reactions may do nothing. */
|
|
211
|
+
acknowledge(repository: string, target: CommentRef): Promise<void>;
|
|
212
|
+
startCheck(repository: string, input: StartCheckInput): Promise<{
|
|
213
|
+
checkId: string;
|
|
214
|
+
}>;
|
|
215
|
+
completeCheck(repository: string, input: CompleteCheckInput): Promise<void>;
|
|
216
|
+
}
|
|
217
|
+
export type ReviewHostErrorCode = "host_not_installed" | "host_permission_missing" | "host_api_error" | "host_invalid_response";
|
|
218
|
+
export declare class ReviewHostError extends Error {
|
|
219
|
+
readonly code: ReviewHostErrorCode;
|
|
220
|
+
constructor(code: ReviewHostErrorCode, message?: string);
|
|
221
|
+
}
|
|
222
|
+
export type ReviewHostRegistry = Partial<Record<ReviewHostProvider, CodeReviewHost>>;
|
|
223
|
+
/**
|
|
224
|
+
* A host's user-authorization (OAuth web) flow, used only to learn who a
|
|
225
|
+
* wardby principal is on the host. Implementations never store or return the
|
|
226
|
+
* user token: `complete` reads the user and revokes the token at once.
|
|
227
|
+
*/
|
|
228
|
+
export interface HostUserAuthorizer {
|
|
229
|
+
readonly provider: ReviewHostProvider;
|
|
230
|
+
authorizeUrl(input: {
|
|
231
|
+
state: string;
|
|
232
|
+
codeChallenge: string;
|
|
233
|
+
redirectUri: string;
|
|
234
|
+
}): string;
|
|
235
|
+
complete(input: {
|
|
236
|
+
code: string;
|
|
237
|
+
codeVerifier: string;
|
|
238
|
+
redirectUri: string;
|
|
239
|
+
}): Promise<{
|
|
240
|
+
hostUserId: string;
|
|
241
|
+
login: string;
|
|
242
|
+
}>;
|
|
243
|
+
}
|
|
244
|
+
export type HostUserAuthorizerRegistry = Partial<Record<ReviewHostProvider, HostUserAuthorizer>>;
|
|
245
|
+
/**
|
|
246
|
+
* Host-neutral shape a webhook adapter (github-events.ts) normalises its
|
|
247
|
+
* provider payload into, for src/core/host-events.ts to route.
|
|
248
|
+
*/
|
|
249
|
+
export type HostEvent = {
|
|
250
|
+
kind: "pr_updated";
|
|
251
|
+
provider: ReviewHostProvider;
|
|
252
|
+
repository: string;
|
|
253
|
+
prNumber: number;
|
|
254
|
+
headSha: string;
|
|
255
|
+
isFork: boolean;
|
|
256
|
+
} | {
|
|
257
|
+
kind: "check_rerun";
|
|
258
|
+
provider: ReviewHostProvider;
|
|
259
|
+
repository: string;
|
|
260
|
+
prNumber: number;
|
|
261
|
+
headSha: string;
|
|
262
|
+
checkName: string;
|
|
263
|
+
} | {
|
|
264
|
+
kind: "mention";
|
|
265
|
+
provider: ReviewHostProvider;
|
|
266
|
+
repository: string;
|
|
267
|
+
number: number;
|
|
268
|
+
isPullRequest: boolean;
|
|
269
|
+
comment: CommentRef;
|
|
270
|
+
/** Set when the mention is inside an inline review thread. */
|
|
271
|
+
replyToReviewCommentId?: string;
|
|
272
|
+
/** The comment text; for a "subject" mention, the issue/PR description. */
|
|
273
|
+
body: string;
|
|
274
|
+
author: string;
|
|
275
|
+
/** The author's immutable numeric host user id, as text; their permission is checked by it. */
|
|
276
|
+
authorId: string;
|
|
277
|
+
/** The issue or PR the mention is on, when the payload carries it. */
|
|
278
|
+
subject?: {
|
|
279
|
+
title: string;
|
|
280
|
+
body: string;
|
|
281
|
+
};
|
|
282
|
+
/** The run that opened this PR, when its description carries a valid run marker. */
|
|
283
|
+
priorRunId?: string;
|
|
284
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host-neutral code-review surface for native agents. GitHub is the first
|
|
3
|
+
* implementation (./github.ts); Bitbucket Cloud is anticipated. Every method
|
|
4
|
+
* takes the repository and mints its own least-permission credential — no
|
|
5
|
+
* method accepts a token or a permission. See
|
|
6
|
+
* docs/private/2026-09-25-code-review-host-design.md §5.
|
|
7
|
+
*/
|
|
8
|
+
export const REVIEW_HOST_PROVIDERS = ["github"];
|
|
9
|
+
export const HOST_PERMISSION_RANK = {
|
|
10
|
+
none: 0,
|
|
11
|
+
read: 1,
|
|
12
|
+
triage: 2,
|
|
13
|
+
write: 3,
|
|
14
|
+
maintain: 4,
|
|
15
|
+
admin: 5,
|
|
16
|
+
};
|
|
17
|
+
export class ReviewHostError extends Error {
|
|
18
|
+
code;
|
|
19
|
+
constructor(code, message = code) {
|
|
20
|
+
super(message);
|
|
21
|
+
this.code = code;
|
|
22
|
+
this.name = "ReviewHostError";
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { type GitHubRepositoryAccess } from "./github.js";
|
|
2
|
-
import type { FinalizeChangesDetails, FinalizeChangesResult, PreparedWorkspace, VcsPrepareInput, VcsProvider } from "./types.js";
|
|
1
|
+
import { type GitHubRepositoryAccess, type RepositoryFileInput } from "./github.js";
|
|
2
|
+
import type { ContinuationFinishedDetails, ContinuationOutcome, FinalizeChangesDetails, FinalizeChangesResult, PreparedWorkspace, VcsPrepareInput, VcsProvider } from "./types.js";
|
|
3
3
|
export declare const DEFAULT_MAX_CHANGED_FILES = 1000;
|
|
4
4
|
export declare const DEFAULT_MAX_DIFF_BYTES: number;
|
|
5
5
|
export interface GitCommandOptions {
|
|
@@ -43,6 +43,18 @@ export interface GitVcsProviderOptions {
|
|
|
43
43
|
/** Test-only transport override; production composition always uses github.com. */
|
|
44
44
|
cloneUrlForRepository?: (repository: string) => string;
|
|
45
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* Whether a changed path is protected, in this order: the WARDBY_PROTECTED_PATHS
|
|
48
|
+
* baseline's exception (.wardby/services.yaml, which a builder may propose; it
|
|
49
|
+
* takes effect only after merge, docs/coding-services.md) is never protected;
|
|
50
|
+
* anything else under the baseline (.wardby/**) always is, whatever the agent's
|
|
51
|
+
* exceptions say; any other path is protected when it matches one of the
|
|
52
|
+
* agent's `patterns` and is not one of its exceptions (entries with a leading
|
|
53
|
+
* "!", each a literal path, so order does not matter). The baseline is applied
|
|
54
|
+
* here, at enforcement, rather than stored, so every existing agent and
|
|
55
|
+
* in-flight run gets it.
|
|
56
|
+
*/
|
|
57
|
+
export declare function protectedPathMatcher(patterns: readonly string[]): (path: string) => boolean;
|
|
46
58
|
export declare class GitVcsProvider implements VcsProvider {
|
|
47
59
|
private readonly options;
|
|
48
60
|
private readonly rootDir;
|
|
@@ -74,10 +86,8 @@ export declare class GitVcsProvider implements VcsProvider {
|
|
|
74
86
|
* github.ts). Safe to call more than once for the same run. Same
|
|
75
87
|
* never-throw contract as notifyContinuationStarted.
|
|
76
88
|
*/
|
|
77
|
-
notifyContinuationFinished(workspace: PreparedWorkspace, outcome:
|
|
78
|
-
|
|
79
|
-
agentName?: string;
|
|
80
|
-
}): Promise<void>;
|
|
89
|
+
notifyContinuationFinished(workspace: PreparedWorkspace, outcome: ContinuationOutcome, details?: ContinuationFinishedDetails): Promise<void>;
|
|
90
|
+
readRepositoryFile(input: RepositoryFileInput): Promise<string | null>;
|
|
81
91
|
private validateInput;
|
|
82
92
|
private expectedPaths;
|
|
83
93
|
private validatePrepared;
|
|
@@ -2,6 +2,8 @@ import { spawn } from "node:child_process";
|
|
|
2
2
|
import { lstat, mkdir, opendir, readFile, readlink, realpath, rm } from "node:fs/promises";
|
|
3
3
|
import { isAbsolute, resolve, sep } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { gitExcludePathspecs, normalizeCollectExclusions } from "../../coding/collect-exclude.js";
|
|
6
|
+
import { WARDBY_PROTECTED_PATHS, isProtectedPathException, isWellFormedProtectedPath, protectedPathBody, protectsSomePath, } from "../../coding/protected-paths.js";
|
|
5
7
|
import { ensurePrivateDirectory } from "../../core/private-directory.js";
|
|
6
8
|
import { MAX_REDACTED_SPAN, redactTokenShapedValues, normalizeGitHubRepository, normalizeGitRef, } from "../../coding/protocol.js";
|
|
7
9
|
import { isSafeGitHubInstallationToken } from "./github.js";
|
|
@@ -139,14 +141,10 @@ export class NodeGitCommandRunner {
|
|
|
139
141
|
});
|
|
140
142
|
}
|
|
141
143
|
}
|
|
144
|
+
/** Validates one protectedPaths entry; a leading "!" makes it an exception (see protectedPathMatcher). */
|
|
142
145
|
function validateProtectedPath(value) {
|
|
143
146
|
const path = value.trim();
|
|
144
|
-
if (!path
|
|
145
|
-
Buffer.byteLength(path, "utf8") > 512 ||
|
|
146
|
-
path.startsWith("/") ||
|
|
147
|
-
path.startsWith("./") ||
|
|
148
|
-
path.includes("\\") ||
|
|
149
|
-
path.split("/").some((part) => !part || part === "." || part === "..")) {
|
|
147
|
+
if (Buffer.byteLength(path, "utf8") > 512 || !isWellFormedProtectedPath(path)) {
|
|
150
148
|
throw new Error("vcs_protected_path_invalid");
|
|
151
149
|
}
|
|
152
150
|
return path;
|
|
@@ -173,6 +171,33 @@ function globRegex(pattern) {
|
|
|
173
171
|
}
|
|
174
172
|
return new RegExp(`${source}$`);
|
|
175
173
|
}
|
|
174
|
+
/** The fixed baseline, split once: its literal exceptions and its protected globs. */
|
|
175
|
+
const WARDBY_UNPROTECTED = new Set(WARDBY_PROTECTED_PATHS.filter(isProtectedPathException).map(protectedPathBody));
|
|
176
|
+
const WARDBY_PROTECTED = WARDBY_PROTECTED_PATHS.filter((pattern) => !isProtectedPathException(pattern)).map((pattern) => globRegex(pattern));
|
|
177
|
+
/**
|
|
178
|
+
* Whether a changed path is protected, in this order: the WARDBY_PROTECTED_PATHS
|
|
179
|
+
* baseline's exception (.wardby/services.yaml, which a builder may propose; it
|
|
180
|
+
* takes effect only after merge, docs/coding-services.md) is never protected;
|
|
181
|
+
* anything else under the baseline (.wardby/**) always is, whatever the agent's
|
|
182
|
+
* exceptions say; any other path is protected when it matches one of the
|
|
183
|
+
* agent's `patterns` and is not one of its exceptions (entries with a leading
|
|
184
|
+
* "!", each a literal path, so order does not matter). The baseline is applied
|
|
185
|
+
* here, at enforcement, rather than stored, so every existing agent and
|
|
186
|
+
* in-flight run gets it.
|
|
187
|
+
*/
|
|
188
|
+
export function protectedPathMatcher(patterns) {
|
|
189
|
+
const exceptions = new Set(patterns.filter(isProtectedPathException).map(protectedPathBody));
|
|
190
|
+
const protectedPatterns = patterns
|
|
191
|
+
.filter((pattern) => !isProtectedPathException(pattern))
|
|
192
|
+
.map((pattern) => globRegex(pattern));
|
|
193
|
+
return (path) => {
|
|
194
|
+
if (WARDBY_UNPROTECTED.has(path))
|
|
195
|
+
return false;
|
|
196
|
+
if (WARDBY_PROTECTED.some((matcher) => matcher.test(path)))
|
|
197
|
+
return true;
|
|
198
|
+
return protectedPatterns.some((matcher) => matcher.test(path)) && !exceptions.has(path);
|
|
199
|
+
};
|
|
200
|
+
}
|
|
176
201
|
function validateChangedPath(path) {
|
|
177
202
|
if (!path ||
|
|
178
203
|
isAbsolute(path) ||
|
|
@@ -352,7 +377,15 @@ export class GitVcsProvider {
|
|
|
352
377
|
await this.inspectWorkspace(prepared.workspacePath);
|
|
353
378
|
await this.assertRemote(prepared);
|
|
354
379
|
await this.assertSafeLocalConfig(prepared.gitMetadataPath);
|
|
355
|
-
|
|
380
|
+
// Excluded paths (src/coding/collect-exclude.ts) are never collected from the worker, so
|
|
381
|
+
// they are also neither added nor deleted here: tracked files under them stay untouched.
|
|
382
|
+
await this.gitFor(prepared, [
|
|
383
|
+
"add",
|
|
384
|
+
"--all",
|
|
385
|
+
"--",
|
|
386
|
+
":/",
|
|
387
|
+
...gitExcludePathspecs(normalizeCollectExclusions(prepared.collectExclude)),
|
|
388
|
+
]);
|
|
356
389
|
const changed = nullSeparated((await this.gitFor(prepared, [
|
|
357
390
|
"diff",
|
|
358
391
|
"--cached",
|
|
@@ -364,8 +397,7 @@ export class GitVcsProvider {
|
|
|
364
397
|
])).stdout);
|
|
365
398
|
if (changed.length > this.maxChangedFiles)
|
|
366
399
|
throw new Error("vcs_changed_file_limit");
|
|
367
|
-
const
|
|
368
|
-
const protectedChange = changed.find((path) => protectedMatchers.some(({ matcher }) => matcher.test(path)));
|
|
400
|
+
const protectedChange = changed.find(protectedPathMatcher(prepared.protectedPaths));
|
|
369
401
|
if (protectedChange)
|
|
370
402
|
throw new Error(`vcs_protected_path:${protectedChange}`);
|
|
371
403
|
let diff;
|
|
@@ -412,15 +444,20 @@ export class GitVcsProvider {
|
|
|
412
444
|
// run's id), not this run's own -- this finds the existing open PR and
|
|
413
445
|
// returns it rather than creating a new one, since headRef/baseRef
|
|
414
446
|
// already match it exactly. No other change needed here: pushing a new
|
|
415
|
-
// commit onto that branch already updates the PR natively
|
|
447
|
+
// commit onto that branch already updates the PR natively -- which is
|
|
448
|
+
// also why a PR a person has since marked ready for review is accepted
|
|
449
|
+
// (acceptReadyForReview) rather than failing an already-pushed run.
|
|
416
450
|
const pullRequest = await this.options.github.createOrFindDraftPullRequest({
|
|
417
451
|
runId: prepared.continuation?.rootRunId ?? prepared.runId,
|
|
452
|
+
...(prepared.continuation ? { acceptReadyForReview: true } : {}),
|
|
418
453
|
repository: prepared.repository,
|
|
419
454
|
baseRef: prepared.baseRef,
|
|
420
455
|
headRef: prepared.headRef,
|
|
421
456
|
summary: details?.summary,
|
|
422
457
|
tests: details?.tests,
|
|
423
458
|
tag: details?.tag,
|
|
459
|
+
packages: details?.packages,
|
|
460
|
+
packageRefusals: details?.packageRefusals,
|
|
424
461
|
});
|
|
425
462
|
return {
|
|
426
463
|
outcome: prepared.continuation ? "pull_request_updated" : "pull_request_opened",
|
|
@@ -498,14 +535,25 @@ export class GitVcsProvider {
|
|
|
498
535
|
};
|
|
499
536
|
const label = runLabel(details?.agentName, workspace.runId);
|
|
500
537
|
const summarySuffix = details?.summary ? `\n\n${details.summary}` : "";
|
|
501
|
-
const
|
|
538
|
+
const budgetSuffix = details?.budgetSentence ? ` ${details.budgetSentence}` : "";
|
|
539
|
+
const body = outcome === "succeeded"
|
|
540
|
+
? `✅ ${label} finished.${summarySuffix}`
|
|
541
|
+
: outcome === "budget_exhausted"
|
|
542
|
+
? `❌ ${label} ran out of budget.${budgetSuffix}${summarySuffix}`
|
|
543
|
+
: details?.serviceSentence
|
|
544
|
+
? `❌ ${label} could not start: ${details.serviceSentence}${summarySuffix}`
|
|
545
|
+
: details?.providerSentence
|
|
546
|
+
? `❌ ${label} could not run: ${details.providerSentence}${summarySuffix}`
|
|
547
|
+
: details?.protectedPathSentence
|
|
548
|
+
? `❌ ${label} could not open its changes: ${details.protectedPathSentence}${summarySuffix}`
|
|
549
|
+
: `❌ ${label} failed.${summarySuffix}`;
|
|
502
550
|
await Promise.allSettled([
|
|
503
551
|
this.options.github.updateContinuationStatusComment({ ...identity, body }),
|
|
504
552
|
this.options.github.completeContinuationCheckRun({
|
|
505
553
|
repository: workspace.repository,
|
|
506
554
|
headSha: workspace.baseCommit,
|
|
507
555
|
runId: workspace.runId,
|
|
508
|
-
outcome,
|
|
556
|
+
outcome: outcome === "succeeded" ? "succeeded" : "failed",
|
|
509
557
|
}),
|
|
510
558
|
]);
|
|
511
559
|
}
|
|
@@ -513,6 +561,17 @@ export class GitVcsProvider {
|
|
|
513
561
|
// Best-effort observability only -- must never affect the real run.
|
|
514
562
|
}
|
|
515
563
|
}
|
|
564
|
+
async readRepositoryFile(input) {
|
|
565
|
+
const github = this.options.github;
|
|
566
|
+
if (!github.readFileAtRef)
|
|
567
|
+
throw new Error("vcs_read_file_unsupported");
|
|
568
|
+
return github.readFileAtRef({
|
|
569
|
+
repository: normalizeGitHubRepository(input.repository),
|
|
570
|
+
ref: normalizeGitRef(input.ref),
|
|
571
|
+
path: validateChangedPath(input.path),
|
|
572
|
+
maxBytes: input.maxBytes,
|
|
573
|
+
});
|
|
574
|
+
}
|
|
516
575
|
validateInput(input) {
|
|
517
576
|
if (!SAFE_RUN_ID.test(input.runId))
|
|
518
577
|
throw new Error("vcs_run_id_invalid");
|
|
@@ -540,7 +599,10 @@ export class GitVcsProvider {
|
|
|
540
599
|
const protectedPaths = [...new Set(input.protectedPaths.map(validateProtectedPath))];
|
|
541
600
|
if (protectedPaths.length === 0 || protectedPaths.length > 128)
|
|
542
601
|
throw new Error("vcs_protected_paths_invalid");
|
|
543
|
-
|
|
602
|
+
if (!protectsSomePath(protectedPaths))
|
|
603
|
+
throw new Error("vcs_protected_paths_invalid");
|
|
604
|
+
const collectExclude = [...normalizeCollectExclusions(input.collectExclude ?? []).paths];
|
|
605
|
+
return { runId: input.runId, repository, baseRef, headRef, protectedPaths, collectExclude, continuation };
|
|
544
606
|
}
|
|
545
607
|
expectedPaths(runId) {
|
|
546
608
|
if (!SAFE_RUN_ID.test(runId))
|
|
@@ -566,6 +628,8 @@ export class GitVcsProvider {
|
|
|
566
628
|
workspace.gitMetadataPath !== expected.gitMetadataPath ||
|
|
567
629
|
workspace.protectedPaths.length !== normalized.protectedPaths.length ||
|
|
568
630
|
workspace.protectedPaths.some((path, index) => path !== normalized.protectedPaths[index]) ||
|
|
631
|
+
workspace.collectExclude.length !== normalized.collectExclude.length ||
|
|
632
|
+
workspace.collectExclude.some((path, index) => path !== normalized.collectExclude[index]) ||
|
|
569
633
|
workspace.continuation?.rootRunId !== normalized.continuation?.rootRunId) {
|
|
570
634
|
throw new Error("vcs_workspace_handle_invalid");
|
|
571
635
|
}
|