@wardby/cli 0.6.0 → 0.7.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/deploy/local/docker-compose.quickstart-coding.yml +13 -0
- package/dist/claude-coding-worker/driver.d.ts +21 -3
- package/dist/claude-coding-worker/driver.js +125 -17
- package/dist/claude-coding-worker/main.js +34 -6
- package/dist/claude-coding-worker/sdk.js +12 -1
- package/dist/claude-coding-worker/tool-socket.d.ts +27 -0
- package/dist/claude-coding-worker/tool-socket.js +111 -0
- package/dist/claude-coding-worker/transcript.d.ts +18 -0
- package/dist/claude-coding-worker/transcript.js +77 -0
- package/dist/cli-help.d.ts +1 -1
- package/dist/cli-help.js +1 -0
- package/dist/cli.js +24 -10
- package/dist/coding/continuation-wording.d.ts +35 -0
- package/dist/coding/continuation-wording.js +73 -0
- package/dist/coding/linked-issue-note.d.ts +23 -0
- package/dist/coding/linked-issue-note.js +47 -0
- package/dist/coding/merge-wording.d.ts +28 -0
- package/dist/coding/merge-wording.js +78 -0
- package/dist/coding/pr-description-note.d.ts +9 -0
- package/dist/coding/pr-description-note.js +32 -0
- package/dist/coding/profile.d.ts +46 -126
- package/dist/coding/profile.js +45 -4
- package/dist/coding/protected-path-wording.d.ts +8 -0
- package/dist/coding/protected-path-wording.js +1 -1
- package/dist/coding/protocol.d.ts +264 -346
- package/dist/coding/protocol.js +235 -12
- package/dist/coding/redact.d.ts +3 -0
- package/dist/coding/redact.js +12 -0
- package/dist/coding/registry/allowlist.d.ts +12 -0
- package/dist/coding/registry/allowlist.js +14 -1
- package/dist/coding/registry/audit-mode.d.ts +153 -0
- package/dist/coding/registry/audit-mode.js +304 -0
- package/dist/coding/registry/lockfile-shared.d.ts +36 -0
- package/dist/coding/registry/lockfile-shared.js +128 -0
- package/dist/coding/registry/lockfiles.js +1 -1
- package/dist/coding/registry/npm-lockfile-formats.d.ts +11 -0
- package/dist/coding/registry/npm-lockfile-formats.js +41 -0
- package/dist/coding/registry/npm-lockfile.d.ts +8 -4
- package/dist/coding/registry/npm-lockfile.js +4 -6
- package/dist/coding/registry/npm.js +78 -8
- package/dist/coding/registry/pnpm-lockfile.d.ts +17 -0
- package/dist/coding/registry/pnpm-lockfile.js +189 -0
- package/dist/coding/registry/pypi-plan.d.ts +50 -0
- package/dist/coding/registry/pypi-plan.js +406 -0
- package/dist/coding/registry/pypi-requirements.d.ts +58 -0
- package/dist/coding/registry/pypi-requirements.js +155 -0
- package/dist/coding/registry/pypi.d.ts +3 -27
- package/dist/coding/registry/pypi.js +40 -154
- package/dist/coding/registry/refusal-hints.d.ts +20 -0
- package/dist/coding/registry/refusal-hints.js +61 -0
- package/dist/coding/registry/refusal-wording.d.ts +11 -0
- package/dist/coding/registry/refusal-wording.js +56 -0
- package/dist/coding/registry/report.d.ts +70 -8
- package/dist/coding/registry/report.js +112 -5
- package/dist/coding/registry/types.d.ts +58 -8
- package/dist/coding/registry/yarn-lockfile.d.ts +30 -0
- package/dist/coding/registry/yarn-lockfile.js +367 -0
- package/dist/coding/services/catalog.d.ts +37 -351
- package/dist/coding/transcript/claude-code.d.ts +13 -0
- package/dist/coding/transcript/claude-code.js +273 -0
- package/dist/coding/transcript/download-protocol.d.ts +30 -0
- package/dist/coding/transcript/download-protocol.js +18 -0
- package/dist/coding/transcript/format.d.ts +20 -0
- package/dist/coding/transcript/format.js +32 -0
- package/dist/coding/transcript/recorder.d.ts +51 -0
- package/dist/coding/transcript/recorder.js +225 -0
- package/dist/coding/transcript/resume/checkout.d.ts +31 -0
- package/dist/coding/transcript/resume/checkout.js +203 -0
- package/dist/coding/transcript/resume/command.d.ts +17 -0
- package/dist/coding/transcript/resume/command.js +176 -0
- package/dist/coding/transcript/resume/download.d.ts +20 -0
- package/dist/coding/transcript/resume/download.js +153 -0
- package/dist/coding/transcript/resume/launch.d.ts +38 -0
- package/dist/coding/transcript/resume/launch.js +128 -0
- package/dist/coding/transcript/resume/session-file.d.ts +13 -0
- package/dist/coding/transcript/resume/session-file.js +79 -0
- package/dist/coding/transcript/resume/stand-in.d.ts +11 -0
- package/dist/coding/transcript/resume/stand-in.js +49 -0
- package/dist/coding-worker/artifact.d.ts +8 -3
- package/dist/coding-worker/artifact.js +3 -1
- package/dist/coding-worker/debug-trace.d.ts +6 -5
- package/dist/coding-worker/debug-trace.js +13 -18
- package/dist/coding-worker/dependency-cache.d.mts +229 -0
- package/dist/coding-worker/dependency-cache.mjs +2123 -0
- package/dist/coding-worker/driver.d.ts +4 -3
- package/dist/coding-worker/driver.js +116 -38
- package/dist/coding-worker/errors.d.ts +1 -0
- package/dist/coding-worker/errors.js +32 -0
- package/dist/coding-worker/task-note.d.ts +11 -0
- package/dist/coding-worker/task-note.js +13 -0
- package/dist/coding-worker/types.d.ts +11 -0
- package/dist/config/dependency-cache.d.ts +23 -0
- package/dist/config/dependency-cache.js +32 -0
- package/dist/config/providers.d.ts +12 -3
- package/dist/config/providers.js +55 -5
- package/dist/config/test-simulators.d.ts +82 -0
- package/dist/config/test-simulators.js +305 -0
- package/dist/core/async-context.d.ts +2 -0
- package/dist/core/async-context.js +12 -0
- package/dist/core/budget-events.d.ts +108 -0
- package/dist/core/budget-events.js +346 -0
- package/dist/core/budget-groups.d.ts +50 -1
- package/dist/core/budget-groups.js +236 -27
- package/dist/core/budget-stopped-head.d.ts +46 -0
- package/dist/core/budget-stopped-head.js +81 -0
- package/dist/core/budget-topup.d.ts +51 -0
- package/dist/core/budget-topup.js +156 -0
- package/dist/core/budget-wording.d.ts +16 -2
- package/dist/core/budget-wording.js +40 -2
- package/dist/core/coding-queue.d.ts +2 -1
- package/dist/core/coding-queue.js +7 -0
- package/dist/core/cron.d.ts +19 -1
- package/dist/core/cron.js +17 -0
- package/dist/core/datastores.js +1 -1
- package/dist/core/deferred-coding.d.ts +14 -0
- package/dist/core/deferred-coding.js +62 -0
- package/dist/core/delegation-child.d.ts +25 -0
- package/dist/core/delegation-child.js +37 -0
- package/dist/core/dispatch.d.ts +42 -0
- package/dist/core/dispatch.js +131 -56
- package/dist/core/engine-native.js +1 -1
- package/dist/core/error-code.d.ts +38 -0
- package/dist/core/error-code.js +168 -0
- package/dist/core/grants.d.ts +19 -6
- package/dist/core/grants.js +55 -6
- package/dist/core/host-events.d.ts +18 -2
- package/dist/core/host-events.js +284 -24
- package/dist/core/host-status.d.ts +85 -3
- package/dist/core/host-status.js +298 -20
- package/dist/core/issue-bridge.d.ts +50 -6
- package/dist/core/issue-bridge.js +124 -33
- package/dist/core/issue-events.d.ts +1 -1
- package/dist/core/issue-events.js +4 -10
- package/dist/core/issue-status.d.ts +1 -1
- package/dist/core/issue-status.js +54 -26
- package/dist/core/logger.d.ts +5 -0
- package/dist/core/logger.js +14 -1
- package/dist/core/lost-run-children.d.ts +6 -0
- package/dist/core/lost-run-children.js +32 -0
- package/dist/core/mark-ready.d.ts +34 -0
- package/dist/core/mark-ready.js +152 -0
- package/dist/core/markdown-code.d.ts +8 -0
- package/dist/core/markdown-code.js +107 -0
- package/dist/core/memory-tools.js +7 -0
- package/dist/core/merge-order-check.d.ts +2 -1
- package/dist/core/merge-order-check.js +8 -1
- package/dist/core/notification-templates.d.ts +1 -7
- package/dist/core/notification-templates.js +36 -2
- package/dist/core/pull-request-state-sync.js +2 -2
- package/dist/core/reconciler.d.ts +4 -3
- package/dist/core/reconciler.js +8 -1
- package/dist/core/related-pull-requests.d.ts +11 -3
- package/dist/core/related-pull-requests.js +27 -14
- package/dist/core/reply-parts.d.ts +21 -0
- package/dist/core/reply-parts.js +73 -0
- package/dist/core/review-fix.d.ts +1 -1
- package/dist/core/review-fix.js +2 -1
- package/dist/core/review-host-checks.d.ts +1 -1
- package/dist/core/review-host-tools.d.ts +3 -0
- package/dist/core/review-host-tools.js +99 -13
- package/dist/core/review-reads.d.ts +55 -0
- package/dist/core/review-reads.js +233 -0
- package/dist/core/run-status.d.ts +12 -0
- package/dist/core/run-status.js +12 -0
- package/dist/core/runner.d.ts +80 -34
- package/dist/core/runner.js +439 -144
- package/dist/core/schedule-start.d.ts +12 -0
- package/dist/core/schedule-start.js +14 -0
- package/dist/core/secret-broker-config.d.ts +10 -74
- package/dist/core/secret-guard.d.ts +46 -0
- package/dist/core/secret-guard.js +104 -0
- package/dist/core/secret-guarded-stores.d.ts +12 -0
- package/dist/core/secret-guarded-stores.js +41 -0
- package/dist/core/secret-spans.d.ts +50 -0
- package/dist/core/secret-spans.js +94 -0
- package/dist/core/stop-run.d.ts +46 -0
- package/dist/core/stop-run.js +61 -0
- package/dist/core/workflow-events.d.ts +14 -2
- package/dist/core/workflow-events.js +4 -0
- package/dist/core/workflow-run-events.d.ts +5 -3
- package/dist/core/workflow-run-events.js +43 -8
- package/dist/core/zod-json-schema.d.ts +4 -0
- package/dist/core/zod-json-schema.js +39 -0
- package/dist/core/zod3-window.d.ts +35 -0
- package/dist/core/zod3-window.js +124 -0
- package/dist/generated/prisma/browser.d.ts +34 -3
- package/dist/generated/prisma/client.d.ts +34 -3
- package/dist/generated/prisma/commonInputTypes.d.ts +220 -60
- package/dist/generated/prisma/enums.d.ts +10 -0
- package/dist/generated/prisma/enums.js +8 -0
- package/dist/generated/prisma/internal/class.d.ts +44 -0
- package/dist/generated/prisma/internal/class.js +4 -4
- package/dist/generated/prisma/internal/prismaNamespace.d.ts +428 -9
- package/dist/generated/prisma/internal/prismaNamespace.js +92 -1
- package/dist/generated/prisma/internal/prismaNamespaceBrowser.d.ts +95 -0
- package/dist/generated/prisma/internal/prismaNamespaceBrowser.js +92 -1
- package/dist/generated/prisma/models/Agent.d.ts +106 -8
- package/dist/generated/prisma/models/AgentRepository.d.ts +42 -1
- package/dist/generated/prisma/models/CodingAgentProfile.d.ts +229 -1
- package/dist/generated/prisma/models/CodingProxyRequest.d.ts +207 -3
- package/dist/generated/prisma/models/CodingProxySession.d.ts +114 -1
- package/dist/generated/prisma/models/CodingRun.d.ts +1637 -1
- package/dist/generated/prisma/models/DependencySnapshot.d.ts +1668 -0
- package/dist/generated/prisma/models/DependencySnapshot.js +1 -0
- package/dist/generated/prisma/models/IssuePullRequest.d.ts +6 -2
- package/dist/generated/prisma/models/LocalReview.d.ts +40 -1
- package/dist/generated/prisma/models/NativeGatewayCall.d.ts +39 -1
- package/dist/generated/prisma/models/NativeGatewaySession.d.ts +35 -1
- package/dist/generated/prisma/models/RegistryAllowance.d.ts +41 -1
- package/dist/generated/prisma/models/RegistryApprovedVersion.d.ts +75 -1
- package/dist/generated/prisma/models/RegistryAuditRecord.d.ts +1364 -0
- package/dist/generated/prisma/models/RegistryAuditRecord.js +1 -0
- package/dist/generated/prisma/models/Run.d.ts +738 -2
- package/dist/generated/prisma/models/RunBudgetEvent.d.ts +1211 -0
- package/dist/generated/prisma/models/RunBudgetEvent.js +1 -0
- package/dist/generated/prisma/models/RunTranscript.d.ts +1339 -0
- package/dist/generated/prisma/models/RunTranscript.js +1 -0
- package/dist/generated/prisma/models.d.ts +4 -0
- package/dist/help-index.json +1320 -69
- package/dist/import/create.js +32 -3
- package/dist/import/neutral-schema.d.ts +50 -281
- package/dist/import/neutral-schema.js +5 -4
- package/dist/knowledge/check.d.ts +1 -1
- package/dist/knowledge/cli.d.ts +11 -1
- package/dist/knowledge/cli.js +37 -4
- package/dist/knowledge/concept.d.ts +3 -19
- package/dist/knowledge/location.d.ts +31 -0
- package/dist/knowledge/location.js +78 -0
- package/dist/mcp/auth/access.d.ts +10 -2
- package/dist/mcp/auth/access.js +15 -1
- package/dist/mcp/auth/ownership.d.ts +7 -7
- package/dist/mcp/auth/self-hosted/credentials.d.ts +3 -3
- package/dist/mcp/auth/self-hosted/session.d.ts +2 -2
- package/dist/mcp/host-events/github-ingress.d.ts +2 -0
- package/dist/mcp/host-events/github-ingress.js +2 -1
- package/dist/mcp/index.d.ts +6 -0
- package/dist/mcp/index.js +58 -10
- package/dist/mcp/server.d.ts +13 -0
- package/dist/mcp/server.js +33 -14
- package/dist/mcp/tools/agents.d.ts +4 -0
- package/dist/mcp/tools/agents.js +198 -16
- package/dist/mcp/tools/datastore.js +17 -8
- package/dist/mcp/tools/grants.js +9 -3
- package/dist/mcp/tools/memory.js +11 -1
- package/dist/mcp/tools/notification-channels.js +2 -2
- package/dist/mcp/tools/packages.d.ts +2 -0
- package/dist/mcp/tools/packages.js +195 -0
- package/dist/mcp/tools/repositories.js +18 -5
- package/dist/mcp/tools/run-access.d.ts +16 -0
- package/dist/mcp/tools/run-access.js +25 -0
- package/dist/mcp/tools/run-transcript-download.d.ts +30 -0
- package/dist/mcp/tools/run-transcript-download.js +129 -0
- package/dist/mcp/tools/run-transcript.d.ts +19 -0
- package/dist/mcp/tools/run-transcript.js +70 -0
- package/dist/mcp/tools/runs.js +34 -12
- package/dist/mcp/tools/scheduling.js +3 -1
- package/dist/mcp/tools/secret-elicitation-form.js +5 -0
- package/dist/mcp/tools/secret-elicitation-server.d.ts +6 -0
- package/dist/mcp/tools/secret-elicitation-server.js +10 -61
- package/dist/mcp/tools/secrets.js +2 -2
- package/dist/mcp/tools/stop-run.d.ts +2 -0
- package/dist/mcp/tools/stop-run.js +82 -0
- package/dist/mcp/tools/subagents.js +2 -2
- package/dist/mcp/tools/tools.d.ts +1 -1
- package/dist/mcp/tools/tools.js +25 -9
- package/dist/mcp/transport/stdio-link-host.d.ts +20 -0
- package/dist/mcp/transport/stdio-link-host.js +67 -0
- package/dist/mcp/transport/streamable-http.d.ts +13 -0
- package/dist/mcp/transport/streamable-http.js +41 -1
- package/dist/native-worker/docker-launcher.d.ts +38 -2
- package/dist/native-worker/docker-launcher.js +65 -5
- package/dist/native-worker/gateway-server.d.ts +41 -3
- package/dist/native-worker/gateway-server.js +124 -27
- package/dist/native-worker/gateway.d.ts +27 -1
- package/dist/native-worker/gateway.js +70 -4
- package/dist/native-worker/http-server.d.ts +3 -1
- package/dist/native-worker/http-server.js +11 -7
- package/dist/native-worker/http-transport.js +1 -1
- package/dist/native-worker/kubernetes-launcher.d.ts +7 -3
- package/dist/native-worker/kubernetes-launcher.js +43 -3
- package/dist/native-worker/launch.d.ts +18 -1
- package/dist/native-worker/launch.js +6 -1
- package/dist/native-worker/ledger.d.ts +18 -2
- package/dist/native-worker/ledger.js +57 -4
- package/dist/native-worker/loopback.js +3 -8
- package/dist/native-worker/main.d.ts +2 -1
- package/dist/native-worker/main.js +14 -3
- package/dist/native-worker/protocol.d.ts +129 -577
- package/dist/native-worker/protocol.js +47 -1
- package/dist/native-worker/sandbox-executor.d.ts +17 -2
- package/dist/native-worker/sandbox-executor.js +78 -4
- package/dist/native-worker/stderr-redaction.d.ts +77 -0
- package/dist/native-worker/stderr-redaction.js +237 -0
- package/dist/native-worker/stdio.d.ts +1 -1
- package/dist/native-worker/stdio.js +6 -5
- package/dist/native-worker/warm-pool.d.ts +2 -2
- package/dist/native-worker/warm-pool.js +4 -4
- package/dist/native-worker/worker.js +28 -2
- package/dist/observability/metrics.js +6 -1
- package/dist/providers/auth/self-hosted.d.ts +1 -1
- package/dist/providers/coding-proxy/dependency-cache/eviction.d.ts +29 -0
- package/dist/providers/coding-proxy/dependency-cache/eviction.js +87 -0
- package/dist/providers/coding-proxy/dependency-cache/key.d.ts +33 -0
- package/dist/providers/coding-proxy/dependency-cache/key.js +86 -0
- package/dist/providers/coding-proxy/dependency-cache/model-request-seal.d.ts +18 -0
- package/dist/providers/coding-proxy/dependency-cache/model-request-seal.js +40 -0
- package/dist/providers/coding-proxy/dependency-cache/prisma-store.d.ts +25 -0
- package/dist/providers/coding-proxy/dependency-cache/prisma-store.js +131 -0
- package/dist/providers/coding-proxy/dependency-cache/service.d.ts +70 -0
- package/dist/providers/coding-proxy/dependency-cache/service.js +409 -0
- package/dist/providers/coding-proxy/dependency-cache/store.d.ts +85 -0
- package/dist/providers/coding-proxy/dependency-cache/store.js +95 -0
- package/dist/providers/coding-proxy/hold.d.ts +32 -0
- package/dist/providers/coding-proxy/hold.js +86 -0
- package/dist/providers/coding-proxy/interrupted.d.ts +70 -0
- package/dist/providers/coding-proxy/interrupted.js +104 -0
- package/dist/providers/coding-proxy/memory-ledger.d.ts +15 -2
- package/dist/providers/coding-proxy/memory-ledger.js +84 -8
- package/dist/providers/coding-proxy/metering.d.ts +16 -0
- package/dist/providers/coding-proxy/metering.js +27 -4
- package/dist/providers/coding-proxy/prisma-ledger.d.ts +15 -2
- package/dist/providers/coding-proxy/prisma-ledger.js +179 -67
- package/dist/providers/coding-proxy/proxy.d.ts +14 -1
- package/dist/providers/coding-proxy/proxy.js +166 -14
- package/dist/providers/coding-proxy/registry/audit.d.ts +53 -10
- package/dist/providers/coding-proxy/registry/audit.js +94 -17
- package/dist/providers/coding-proxy/registry/lockfile-parse-worker.d.ts +36 -0
- package/dist/providers/coding-proxy/registry/lockfile-parse-worker.js +67 -0
- package/dist/providers/coding-proxy/registry/lockfile-parse.d.ts +16 -0
- package/dist/providers/coding-proxy/registry/lockfile-parse.js +110 -0
- package/dist/providers/coding-proxy/registry/plan.d.ts +9 -0
- package/dist/providers/coding-proxy/registry/plan.js +201 -48
- package/dist/providers/coding-proxy/registry/prisma-store.d.ts +11 -5
- package/dist/providers/coding-proxy/registry/prisma-store.js +120 -17
- package/dist/providers/coding-proxy/registry/service.d.ts +97 -16
- package/dist/providers/coding-proxy/registry/service.js +579 -112
- package/dist/providers/coding-proxy/registry/store.d.ts +84 -9
- package/dist/providers/coding-proxy/registry/store.js +54 -1
- package/dist/providers/coding-proxy/runtime.d.ts +4 -0
- package/dist/providers/coding-proxy/runtime.js +78 -16
- package/dist/providers/coding-proxy/server.d.ts +6 -1
- package/dist/providers/coding-proxy/server.js +45 -1
- package/dist/providers/coding-proxy/types.d.ts +65 -1
- package/dist/providers/datastore/types.d.ts +5 -0
- package/dist/providers/engine/types.d.ts +17 -2
- package/dist/providers/engine/types.js +17 -0
- package/dist/providers/executor/composition.d.ts +10 -3
- package/dist/providers/executor/composition.js +47 -7
- package/dist/providers/executor/container.d.ts +361 -8
- package/dist/providers/executor/container.js +1136 -91
- package/dist/providers/executor/dbos-status.d.ts +31 -0
- package/dist/providers/executor/dbos-status.js +64 -0
- package/dist/providers/executor/dbos.d.ts +50 -1
- package/dist/providers/executor/dbos.js +207 -20
- package/dist/providers/executor/deferred.d.ts +12 -24
- package/dist/providers/executor/deferred.js +10 -29
- package/dist/providers/executor/in-process.d.ts +9 -1
- package/dist/providers/executor/in-process.js +14 -4
- package/dist/providers/executor/linked-issue-reader.d.ts +16 -0
- package/dist/providers/executor/linked-issue-reader.js +30 -0
- package/dist/providers/executor/routing.d.ts +2 -0
- package/dist/providers/executor/routing.js +4 -0
- package/dist/providers/executor/types.d.ts +13 -0
- package/dist/providers/index.d.ts +2 -1
- package/dist/providers/issue-tracker/types.d.ts +17 -0
- package/dist/providers/issue-tracker/types.js +39 -1
- package/dist/providers/jobs/claude-tool-setup.d.ts +4 -0
- package/dist/providers/jobs/claude-tool-setup.js +5 -2
- package/dist/providers/jobs/docker-isolation.d.ts +8 -0
- package/dist/providers/jobs/docker-isolation.js +17 -0
- package/dist/providers/jobs/docker.d.ts +1 -0
- package/dist/providers/jobs/docker.js +27 -2
- package/dist/providers/jobs/fake-kubernetes-api.d.ts +2 -0
- package/dist/providers/jobs/fake-kubernetes-api.js +3 -0
- package/dist/providers/jobs/kubernetes-api.d.ts +8 -0
- package/dist/providers/jobs/kubernetes-api.js +10 -0
- package/dist/providers/jobs/kubernetes-client.js +10 -1
- package/dist/providers/jobs/kubernetes-isolation.d.ts +2 -0
- package/dist/providers/jobs/kubernetes-isolation.js +17 -1
- package/dist/providers/jobs/kubernetes.d.ts +6 -1
- package/dist/providers/jobs/kubernetes.js +105 -16
- package/dist/providers/jobs/types.d.ts +33 -0
- package/dist/providers/jobs/types.js +13 -1
- package/dist/providers/llm/anthropic.js +5 -1
- package/dist/providers/llm/bedrock.d.ts +0 -12
- package/dist/providers/llm/bedrock.js +4 -0
- package/dist/providers/llm/claude-messages.d.ts +3 -1
- package/dist/providers/llm/claude-messages.js +4 -1
- package/dist/providers/llm/claude-provider.d.ts +2 -2
- package/dist/providers/llm/claude-provider.js +2 -2
- package/dist/providers/llm/openai.d.ts +2 -2
- package/dist/providers/llm/openai.js +6 -2
- package/dist/providers/llm/routing.d.ts +2 -2
- package/dist/providers/llm/routing.js +2 -2
- package/dist/providers/llm/types.d.ts +12 -2
- package/dist/providers/review-host/github-events.d.ts +9 -1
- package/dist/providers/review-host/github-events.js +34 -12
- package/dist/providers/review-host/github.d.ts +29 -2
- package/dist/providers/review-host/github.js +224 -29
- package/dist/providers/review-host/local.d.ts +8 -4
- package/dist/providers/review-host/local.js +49 -31
- package/dist/providers/review-host/patch-pages.d.ts +41 -0
- package/dist/providers/review-host/patch-pages.js +176 -0
- package/dist/providers/review-host/review-format.d.ts +19 -1
- package/dist/providers/review-host/review-format.js +38 -7
- package/dist/providers/review-host/types.d.ts +134 -2
- package/dist/providers/review-host/types.js +8 -0
- package/dist/providers/storage/gcs.d.ts +63 -0
- package/dist/providers/storage/gcs.js +69 -0
- package/dist/providers/storage/index.d.ts +15 -0
- package/dist/providers/storage/index.js +14 -0
- package/dist/providers/storage/local.d.ts +24 -0
- package/dist/providers/storage/local.js +92 -0
- package/dist/providers/storage/meter.d.ts +14 -0
- package/dist/providers/storage/meter.js +34 -0
- package/dist/providers/storage/s3.d.ts +41 -0
- package/dist/providers/storage/s3.js +107 -0
- package/dist/providers/storage/types.d.ts +37 -3
- package/dist/providers/storage/types.js +21 -4
- package/dist/providers/vcs/git.d.ts +33 -2
- package/dist/providers/vcs/git.js +324 -22
- package/dist/providers/vcs/github-remote.d.ts +26 -3
- package/dist/providers/vcs/github-remote.js +124 -14
- package/dist/providers/vcs/github.d.ts +214 -6
- package/dist/providers/vcs/github.js +447 -45
- package/dist/providers/vcs/index.js +8 -1
- package/dist/providers/vcs/pr-description.d.ts +127 -0
- package/dist/providers/vcs/pr-description.js +270 -0
- package/dist/providers/vcs/remote.d.ts +33 -1
- package/dist/providers/vcs/routing.d.ts +4 -2
- package/dist/providers/vcs/routing.js +6 -0
- package/dist/providers/vcs/types.d.ts +80 -3
- package/dist/quickstart/coding-seed.d.ts +14 -3
- package/dist/quickstart/coding-seed.js +213 -4
- package/dist/quickstart/reviewer-prompt.d.ts +1 -1
- package/dist/quickstart/reviewer-prompt.js +6 -6
- package/dist/quickstart-images.json +1 -1
- package/dist/sandbox/generated/zod.bundle.js +62 -1
- package/dist/sandbox/generated/zod3.bundle.js +5 -0
- package/dist/sandbox/host-functions.d.ts +1 -0
- package/dist/sandbox/host-functions.js +6 -11
- package/dist/sandbox/secret-broker.js +3 -4
- package/dist/sandbox/tool-capabilities.d.ts +10 -30
- package/dist/sandbox/zod-params.d.ts +37 -5
- package/dist/sandbox/zod-params.js +181 -26
- package/dist/serve.js +8 -1
- package/dist/viewer/api-schema.d.ts +349 -2627
- package/dist/viewer/api-schema.js +7 -0
- package/dist/viewer/build-schemas.js +6 -2
- package/dist/viewer/openapi.js +2 -4
- package/dist/wardby-bin.js +10 -0
- package/docs/agent-recipes.md +58 -20
- package/docs/code-review-agents.md +551 -41
- package/docs/coding-agent-setup.md +489 -7
- package/docs/coding-packages.md +1033 -90
- package/docs/coding-services.md +27 -12
- package/docs/coding-worker-byo-images.md +20 -0
- package/docs/coding-worker-isolation.md +105 -30
- package/docs/getting-started-gke.md +124 -137
- package/docs/getting-started-identity-provider.md +10 -1
- package/docs/getting-started.md +9 -0
- package/docs/jira-agents.md +17 -8
- package/docs/knowledge/blob-not-found-semantics.md +41 -0
- package/docs/knowledge/bounded-file-descriptor.md +52 -0
- package/docs/knowledge/delegation-admission-slots.md +52 -0
- package/docs/knowledge/http-dispatcher-bootstrap.md +61 -0
- package/docs/knowledge/import-rename-target.md +52 -0
- package/docs/knowledge/index.md +57 -0
- package/docs/knowledge/log.md +9 -0
- package/docs/knowledge/mcp-cancelled-task-precedence.md +41 -0
- package/docs/knowledge/mcp-http-prompt-discovery.md +41 -0
- package/docs/knowledge/mcp-schema-module-boundary.md +41 -0
- package/docs/knowledge/nullable-tool-uniqueness.md +52 -0
- package/docs/knowledge/overview-cli.md +74 -0
- package/docs/knowledge/overview-coding-proxy.md +72 -0
- package/docs/knowledge/overview-coding.md +72 -0
- package/docs/knowledge/overview-config.md +60 -0
- package/docs/knowledge/overview-core.md +72 -0
- package/docs/knowledge/overview-deploy.md +83 -0
- package/docs/knowledge/overview-mcp.md +61 -0
- package/docs/knowledge/overview-prisma.md +72 -0
- package/docs/knowledge/overview-providers.md +89 -0
- package/docs/knowledge/overview-sandbox.md +83 -0
- package/docs/knowledge/overview-surfaces.md +83 -0
- package/docs/knowledge/private-datastore-shared-boundary.md +54 -0
- package/docs/knowledge/proxy-settlement-projections.md +41 -0
- package/docs/knowledge/run-pricing-first-writer.md +43 -0
- package/docs/knowledge/schedule-claim-transaction.md +41 -0
- package/docs/knowledge/secret-entry-replica-contract.md +52 -0
- package/docs/knowledge/terminal-run-first-writer.md +41 -0
- package/docs/knowledge/transactional-agent-authorization.md +52 -0
- package/docs/knowledge/viewer-notify-write-isolation.md +30 -0
- package/docs/knowledge/warm-input-publication.md +30 -0
- package/docs/knowledge/workspace-staging-failure.md +54 -0
- package/docs/knowledge.md +54 -14
- package/docs/native-sandbox.md +119 -23
- package/docs/observability.md +4 -0
- package/docs/security-deployment.md +33 -2
- package/docs/slack-notifications.md +35 -19
- package/docs/tool-secrets.md +42 -10
- package/docs/viewer-api.openapi.yml +1 -1
- package/help/admin-viewer.md +24 -2
- package/help/architecture-agent.md +11 -6
- package/help/build-worker-image.md +3 -1
- package/help/builder-agent.md +19 -6
- package/help/code-review-agents.md +177 -6
- package/help/coding-packages.md +156 -6
- package/help/cost-attribution.md +9 -5
- package/help/creating-agents.md +14 -1
- package/help/dependency-cache.md +143 -0
- package/help/deploy-gke.md +15 -8
- package/help/errors/budget-exhausted.md +187 -0
- package/help/errors/budget-group-exhausted.md +5 -1
- package/help/errors/coding-stream-quota-exceeded.md +33 -0
- package/help/errors/coding-workspace-disk-exceeds-limit.md +39 -0
- package/help/errors/continuation-closed.md +9 -12
- package/help/errors/delegation-conflict.md +24 -0
- package/help/errors/file-list-truncated.md +45 -0
- package/help/errors/full-read-required.md +49 -0
- package/help/errors/github-account-not-allowed.md +4 -0
- package/help/errors/graph-incomplete.md +43 -0
- package/help/errors/graph-limit.md +43 -0
- package/help/errors/merge-conflict.md +49 -0
- package/help/errors/native-sandbox-gateway-error.md +20 -0
- package/help/errors/native-sandbox-launch-abandoned.md +21 -0
- package/help/errors/native-sandbox-worker-exited.md +3 -2
- package/help/errors/package-not-allowed-pending.md +52 -0
- package/help/errors/package-not-allowed.md +51 -0
- package/help/errors/package-refusals.md +43 -0
- package/help/errors/patch-cursor-malformed.md +31 -0
- package/help/errors/run-lost.md +39 -0
- package/help/errors/run-transcript-download.md +44 -0
- package/help/errors/secret-in-value.md +69 -0
- package/help/errors/target-merge.md +40 -0
- package/help/fix-merge-conflicts.md +73 -0
- package/help/github-app-setup.md +132 -0
- package/help/github.md +22 -0
- package/help/identity-and-access.md +2 -0
- package/help/jira.md +23 -6
- package/help/knowledge.md +16 -7
- package/help/local-repositories.md +1 -1
- package/help/low-power-mode.md +7 -1
- package/help/merge-order-check.md +5 -0
- package/help/native-capabilities.md +29 -1
- package/help/native-sandbox.md +15 -1
- package/help/operating-agents.md +5 -0
- package/help/package-audit-mode.md +91 -0
- package/help/related-pull-requests.md +10 -7
- package/help/resume-runs-locally.md +131 -0
- package/help/review-fix-rounds.md +33 -1
- package/help/sharing-agents.md +59 -0
- package/help/slack-notifications.md +19 -5
- package/help/status-comments.md +31 -0
- package/help/stop-run.md +58 -0
- package/help/troubleshooting/budgets.md +28 -0
- package/help/troubleshooting/coding-workers.md +78 -7
- package/package.json +17 -10
- package/prisma/migrations/20261016000000_registry_approved_accepted_advisories/migration.sql +3 -0
- package/prisma/migrations/20261016000001_mark_ready_when_passing/migration.sql +3 -0
- package/prisma/migrations/20261017000000_debug_trace_max_mb/migration.sql +4 -0
- package/prisma/migrations/20261018000000_dependency_cache/migration.sql +53 -0
- package/prisma/migrations/20261019000000_claude_max_output_tokens/migration.sql +5 -0
- package/prisma/migrations/20261021000000_pr_description_hash/migration.sql +4 -0
- package/prisma/migrations/20261022000000_dependency_snapshot_validation_metadata/migration.sql +4 -0
- package/prisma/migrations/20261023000000_package_audit_mode/migration.sql +27 -0
- package/prisma/migrations/20261024000000_budget_events/migration.sql +26 -0
- package/prisma/migrations/20261025000000_cache_aware_holds/migration.sql +9 -0
- package/prisma/migrations/20261026000000_budget_overrun/migration.sql +4 -0
- package/prisma/migrations/20261027000000_coding_run_request/migration.sql +1 -0
- package/prisma/migrations/20261028000000_native_gateway_review_reads/migration.sql +6 -0
- package/prisma/migrations/20261029000000_lead_survives_deploy/migration.sql +6 -0
- package/prisma/migrations/20261030000000_local_review_covered_sha/migration.sql +2 -0
- package/prisma/migrations/20261031000000_coding_run_budget_limit/migration.sql +9 -0
- package/prisma/migrations/20261101000000_run_transcripts/migration.sql +22 -0
- package/prisma/schema.prisma +311 -97
- package/dist/sandbox/generated/zod-to-json-schema.bundle.js +0 -5
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import { createPrivateKey } from "node:crypto";
|
|
2
2
|
import { importPKCS8, SignJWT } from "jose";
|
|
3
3
|
import { isGitHubLogin } from "../../config/github-allowed-accounts.js";
|
|
4
|
-
import {
|
|
4
|
+
import { testSimulatorBase } from "../../config/test-simulators.js";
|
|
5
|
+
import { ACCEPTED_ADVISORY_ID } from "../../coding/registry/allowlist.js";
|
|
6
|
+
import { packageAuditRows, packageAuditWording, summarizePackageAudit, } from "../../coding/registry/audit-mode.js";
|
|
7
|
+
import { refusalMeaning } from "../../coding/registry/refusal-hints.js";
|
|
8
|
+
import { DEPENDENCY_CACHE_PATH, normalizeGitHubRepository, normalizeGitRef, truncateText, } from "../../coding/protocol.js";
|
|
9
|
+
import { LATEST_RUN_MARKER_PREFIX, RELATED_SECTION_END, RELATED_SECTION_START, assessFinalMessage, descriptionAppliedComment, descriptionFallbackComment, descriptionSha256, extractRelatedBlock, findDescriptionComments, hasClosingKeyword, isUneditedDescription, supersededDescriptionComment, } from "./pr-description.js";
|
|
10
|
+
export { RELATED_SECTION_END, RELATED_SECTION_START };
|
|
5
11
|
const DEFAULT_API_BASE_URL = "https://api.github.com";
|
|
6
12
|
const DEFAULT_API_VERSION = "2026-03-10";
|
|
7
13
|
const RUN_MARKER_PREFIX = "<!-- wardby:";
|
|
@@ -40,6 +46,8 @@ export function isSafeGitHubInstallationToken(value) {
|
|
|
40
46
|
* executor validates with the shared one.
|
|
41
47
|
*/
|
|
42
48
|
export const PR_ISSUE_KEY = /^[A-Z][A-Z0-9_]{0,254}-[1-9]\d{0,9}$/;
|
|
49
|
+
/** Same shape as GITHUB_ISSUE_KEY (issue-tracker/types.ts), duplicated likewise. Only the Related section shows one. */
|
|
50
|
+
export const PR_GITHUB_ISSUE_KEY = /^[a-z0-9](?:[a-z0-9-]{0,37}[a-z0-9])?\/[a-z0-9._-]{1,100}#[1-9]\d{0,9}$/;
|
|
43
51
|
/** The issue key when it is well-formed; a malformed one is dropped everywhere it would be rendered. */
|
|
44
52
|
function validIssueKey(input) {
|
|
45
53
|
const key = input.issue?.key;
|
|
@@ -50,22 +58,29 @@ export function pullRequestTitle(input) {
|
|
|
50
58
|
const key = validIssueKey(input);
|
|
51
59
|
return key ? `[${key}] ${base}` : base;
|
|
52
60
|
}
|
|
53
|
-
/**
|
|
54
|
-
|
|
61
|
+
/**
|
|
62
|
+
* Validated issue parts; the tracker prefix is "" or "<Name> ". A GitHub issue
|
|
63
|
+
* key is accepted only with `orGitHub` (the Related section): the title and
|
|
64
|
+
* the body's issue line stay tracker-only.
|
|
65
|
+
*/
|
|
66
|
+
function issueParts(issue, orGitHub = false) {
|
|
55
67
|
const key = issue?.key;
|
|
56
|
-
if (typeof key !== "string" || !PR_ISSUE_KEY.test(key))
|
|
68
|
+
if (typeof key !== "string" || !(PR_ISSUE_KEY.test(key) || (orGitHub && PR_GITHUB_ISSUE_KEY.test(key)))) {
|
|
57
69
|
return undefined;
|
|
70
|
+
}
|
|
58
71
|
const url = issue?.url;
|
|
59
72
|
const safeUrl = typeof url === "string" && url.startsWith("https://") && !/[\s<>()]/.test(url) ? url : undefined;
|
|
60
73
|
const name = issue?.trackerName;
|
|
61
74
|
const tracker = typeof name === "string" && /^[A-Za-z][A-Za-z0-9 .-]{0,39}$/.test(name) ? `${name.trim()} ` : "";
|
|
62
75
|
return { key, ...(safeUrl ? { url: safeUrl } : {}), tracker };
|
|
63
76
|
}
|
|
77
|
+
/** The body's issue line: a tracker issue's link, else the same-repository issue the run came from. */
|
|
64
78
|
function issueLine(input) {
|
|
65
79
|
const parts = issueParts(input.issue);
|
|
66
|
-
if (
|
|
67
|
-
return
|
|
68
|
-
|
|
80
|
+
if (parts?.url)
|
|
81
|
+
return `Resolves ${parts.tracker}issue [${parts.key}](${parts.url})`;
|
|
82
|
+
const number = input.issueNumber;
|
|
83
|
+
return typeof number === "number" && Number.isSafeInteger(number) && number > 0 ? `From issue #${number}` : undefined;
|
|
69
84
|
}
|
|
70
85
|
/**
|
|
71
86
|
* Package names/versions/reasons come from registry metadata, not from
|
|
@@ -78,8 +93,17 @@ function issueLine(input) {
|
|
|
78
93
|
function isSafeMarkdownFragment(value) {
|
|
79
94
|
return !value.includes("`") && !value.includes("\n") && !value.includes("\r");
|
|
80
95
|
}
|
|
81
|
-
|
|
82
|
-
|
|
96
|
+
/** Whether a comment was written by this GitHub App (its bot user, through this App's id). */
|
|
97
|
+
function isThisAppsComment(comment, appId) {
|
|
98
|
+
const user = comment.user;
|
|
99
|
+
const app = comment.performed_via_github_app;
|
|
100
|
+
return (!!user &&
|
|
101
|
+
typeof user === "object" &&
|
|
102
|
+
user.type === "Bot" &&
|
|
103
|
+
!!app &&
|
|
104
|
+
typeof app === "object" &&
|
|
105
|
+
app.id === appId);
|
|
106
|
+
}
|
|
83
107
|
/** At most this many pull requests are listed; the rest are counted. */
|
|
84
108
|
export const MAX_RELATED_PULL_REQUESTS = 20;
|
|
85
109
|
/** GitHub rejects a pull request body longer than this. */
|
|
@@ -128,7 +152,7 @@ export function renderRelatedSection(input) {
|
|
|
128
152
|
const valid = input.entries.filter((entry) => relatedLine(entry, "-") !== undefined);
|
|
129
153
|
if (!valid.some((entry) => !entry.self))
|
|
130
154
|
return undefined;
|
|
131
|
-
const issue = issueParts(input.issue);
|
|
155
|
+
const issue = issueParts(input.issue, true);
|
|
132
156
|
const issueText = issue ? ` for ${issue.tracker}issue ${issue.url ? `[${issue.key}](${issue.url})` : issue.key}` : "";
|
|
133
157
|
const open = valid.filter((entry) => !isDone(entry)).slice(0, MAX_RELATED_PULL_REQUESTS);
|
|
134
158
|
const done = valid.filter(isDone).slice(0, MAX_RELATED_PULL_REQUESTS - open.length);
|
|
@@ -185,8 +209,8 @@ export function renderRelatedSection(input) {
|
|
|
185
209
|
RELATED_SECTION_END,
|
|
186
210
|
].join("\n");
|
|
187
211
|
}
|
|
188
|
-
/** The run marker, then optionally blank lines and the "Resolves …"
|
|
189
|
-
const BODY_HEAD = /^[^\S\n]*<!-- (?:wardby|reevo-run):\S+ -->[^\S\n]*(?:\n|$)(?:[^\S\n]*\n)*(?:Resolves [^\n]*(?:\n|$))?/;
|
|
212
|
+
/** The run marker, then optionally blank lines and the issue line ("Resolves … issue …" or "From issue #N"): the section goes right after. */
|
|
213
|
+
const BODY_HEAD = /^[^\S\n]*<!-- (?:wardby|reevo-run):\S+ -->[^\S\n]*(?:\n|$)(?:[^\S\n]*\n)*(?:(?:Resolves |From issue #\d)[^\n]*(?:\n|$))?/;
|
|
190
214
|
/**
|
|
191
215
|
* The body with `block` in place of the existing marked section, or inserted
|
|
192
216
|
* after the run marker (and the issue line) when there is none. Null — do not
|
|
@@ -223,13 +247,16 @@ export const MAX_PR_PACKAGE_LINES = 100;
|
|
|
223
247
|
/** A hard ceiling on each list's rendered size, whatever the entries
|
|
224
248
|
* contain, so the section can never push the body past GitHub's limit. */
|
|
225
249
|
const MAX_PR_PACKAGE_LIST_CHARS = 16 * 1024;
|
|
226
|
-
/**
|
|
227
|
-
*
|
|
228
|
-
|
|
250
|
+
/** The accepted-advisories list's ceiling: smaller, so adding it keeps the
|
|
251
|
+
* whole section's worst case well inside the body limit. */
|
|
252
|
+
const MAX_PR_WAIVER_LIST_CHARS = 8 * 1024;
|
|
253
|
+
/** Renders at most MAX_PR_PACKAGE_LINES lines (and at most `maxChars`
|
|
254
|
+
* characters) of `lines`, then a count of the rest. */
|
|
255
|
+
function cappedList(lines, maxChars = MAX_PR_PACKAGE_LIST_CHARS) {
|
|
229
256
|
const shown = [];
|
|
230
257
|
let chars = 0;
|
|
231
258
|
for (const line of lines.slice(0, MAX_PR_PACKAGE_LINES)) {
|
|
232
|
-
if (chars + line.length + 1 >
|
|
259
|
+
if (chars + line.length + 1 > maxChars)
|
|
233
260
|
break;
|
|
234
261
|
shown.push(line);
|
|
235
262
|
chars += line.length + 1;
|
|
@@ -237,6 +264,14 @@ function cappedList(lines) {
|
|
|
237
264
|
const more = lines.length - shown.length;
|
|
238
265
|
return more > 0 ? [...shown, `- …and ${more} more — see get_run for the full list`] : shown;
|
|
239
266
|
}
|
|
267
|
+
const MAX_DEPENDENCY_CACHE_FOLDERS = 4;
|
|
268
|
+
function dependencyCacheLine(folders) {
|
|
269
|
+
const shown = [...new Set(folders ?? [])]
|
|
270
|
+
.filter((folder) => DEPENDENCY_CACHE_PATH.test(folder))
|
|
271
|
+
.slice(0, MAX_DEPENDENCY_CACHE_FOLDERS)
|
|
272
|
+
.map((folder) => (folder === "." ? "the repo root" : `\`${folder}\``));
|
|
273
|
+
return shown.length > 0 ? `Dependency cache: restored \`node_modules\` for ${shown.join(", ")}.` : undefined;
|
|
274
|
+
}
|
|
240
275
|
function packagesSection(input) {
|
|
241
276
|
const packages = [
|
|
242
277
|
...new Map((input.packages ?? [])
|
|
@@ -252,11 +287,23 @@ function packagesSection(input) {
|
|
|
252
287
|
isSafeMarkdownFragment(refusal.reason))
|
|
253
288
|
.map((refusal) => [`${refusal.ecosystem}\0${refusal.name}\0${refusal.reason}`, refusal])).values(),
|
|
254
289
|
];
|
|
290
|
+
const restored = dependencyCacheLine(input.restoredDependencies);
|
|
255
291
|
if (packages.length === 0 && refusals.length === 0)
|
|
256
|
-
return
|
|
292
|
+
return restored;
|
|
293
|
+
// Only ids safe in a Markdown line; a package whose ids are all unsafe is
|
|
294
|
+
// not listed here (it is still listed as installed).
|
|
295
|
+
const waivers = packages.flatMap((pkg) => {
|
|
296
|
+
// Ids come from OSV (a waiver can match via alias), so keep only the
|
|
297
|
+
// policy id shape and render each in a code span.
|
|
298
|
+
const ids = (pkg.acceptedAdvisories ?? []).filter((id) => ACCEPTED_ADVISORY_ID.test(id));
|
|
299
|
+
return ids.length > 0 ? [`- \`${pkg.name}@${pkg.version}\`: ${ids.map((id) => `\`${id}\``).join(", ")}`] : [];
|
|
300
|
+
});
|
|
257
301
|
return [
|
|
302
|
+
...(restored ? [restored, ""] : []),
|
|
258
303
|
"<details>",
|
|
259
|
-
|
|
304
|
+
input.packagesScope === "pull_request"
|
|
305
|
+
? `<summary>Packages installed by wardby's runs on this pull request (${packages.length})</summary>`
|
|
306
|
+
: `<summary>Packages installed during this run (${packages.length})</summary>`,
|
|
260
307
|
"",
|
|
261
308
|
...cappedList(packages.map((pkg) => `- ${pkg.ecosystem} \`${pkg.name}@${pkg.version}\``)),
|
|
262
309
|
...(refusals.length > 0
|
|
@@ -266,6 +313,13 @@ function packagesSection(input) {
|
|
|
266
313
|
...cappedList(refusals.map((refusal) => `- ${refusal.ecosystem} \`${refusal.name}\`: ${refusal.reason}`)),
|
|
267
314
|
]
|
|
268
315
|
: []),
|
|
316
|
+
...(waivers.length > 0
|
|
317
|
+
? [
|
|
318
|
+
"",
|
|
319
|
+
"**Accepted advisories** (waived by this agent's package policy):",
|
|
320
|
+
...cappedList(waivers, MAX_PR_WAIVER_LIST_CHARS),
|
|
321
|
+
]
|
|
322
|
+
: []),
|
|
269
323
|
"</details>",
|
|
270
324
|
].join("\n");
|
|
271
325
|
}
|
|
@@ -282,15 +336,6 @@ const LOCKFILE_NAMES = new Set([
|
|
|
282
336
|
export function isLockfilePath(path) {
|
|
283
337
|
return LOCKFILE_NAMES.has(path.slice(path.lastIndexOf("/") + 1));
|
|
284
338
|
}
|
|
285
|
-
/** What each refusal code means to someone reading the PR, without the registry's internals. */
|
|
286
|
-
const REFUSAL_MEANING = {
|
|
287
|
-
wardby_package_not_allowed: "not on the allowlist, or only reachable through a refused package",
|
|
288
|
-
wardby_version_filtered: "withheld: newer than the release-age limit, or a high-severity advisory",
|
|
289
|
-
wardby_graph_incomplete: "its dependencies could not be confirmed (usually transient)",
|
|
290
|
-
wardby_lockfile_integrity_mismatch: "the lock file's integrity does not match the registry's",
|
|
291
|
-
wardby_upstream_error: "the upstream registry failed (transient)",
|
|
292
|
-
wardby_audit_unavailable: "the advisory check was unavailable (transient)",
|
|
293
|
-
};
|
|
294
339
|
/** At most this many refused packages are named in the warning; the full list stays in the details. */
|
|
295
340
|
const MAX_WARNING_NAMES = 8;
|
|
296
341
|
/**
|
|
@@ -320,7 +365,7 @@ function refusalWarning(input) {
|
|
|
320
365
|
const listed = [...names];
|
|
321
366
|
const shown = listed.slice(0, MAX_WARNING_NAMES).join(", ");
|
|
322
367
|
const more = listed.length > MAX_WARNING_NAMES ? ` and ${listed.length - MAX_WARNING_NAMES} more` : "";
|
|
323
|
-
const meaning =
|
|
368
|
+
const meaning = refusalMeaning(reason);
|
|
324
369
|
lines.push(`> - ${shown}${more}: \`${reason}\`${meaning ? ` (${meaning})` : ""}`);
|
|
325
370
|
}
|
|
326
371
|
const lockfiles = (input.changedLockfiles ?? []).filter(isSafeMarkdownFragment);
|
|
@@ -329,8 +374,51 @@ function refusalWarning(input) {
|
|
|
329
374
|
}
|
|
330
375
|
return lines.join("\n");
|
|
331
376
|
}
|
|
377
|
+
/** The banner when the audit report can't be rendered: fixed text, no record data. */
|
|
378
|
+
const PACKAGE_AUDIT_FALLBACK_BANNER = "> [!CAUTION]\n> **Package audit mode was on for this run.** Wardby's registry may have installed packages outside this agent's allowlist; see `get_run` (`packageAudit`) before merging.";
|
|
379
|
+
/**
|
|
380
|
+
* The package audit block: built only from registry records (names and
|
|
381
|
+
* versions through codeSpan, advisory ids by their policy shape), so neither
|
|
382
|
+
* the model nor a package's metadata can remove or soften it.
|
|
383
|
+
*/
|
|
384
|
+
function packageAuditSection(input) {
|
|
385
|
+
if (!input.packageAudit)
|
|
386
|
+
return undefined;
|
|
387
|
+
const summary = summarizePackageAudit(packageAuditRows(input.packageAudit.entries));
|
|
388
|
+
return packageAuditWording(input.packageAudit.mode, summary, { format: "pr" });
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* The pull request body, within GitHub's MAX_PULL_REQUEST_BODY_CHARS. When it
|
|
392
|
+
* would be longer, the informational Packages section goes first; if that is
|
|
393
|
+
* not enough, the agent's summary is truncated to fit (fitSummary keeps its
|
|
394
|
+
* closing-keyword line). The other sections are bounded and stay whole.
|
|
395
|
+
*/
|
|
396
|
+
export function pullRequestBody(input, options = {}) {
|
|
397
|
+
const body = buildPullRequestBody(input, options);
|
|
398
|
+
if (body.length <= MAX_PULL_REQUEST_BODY_CHARS)
|
|
399
|
+
return body;
|
|
400
|
+
const withoutPackages = options.omitPackages ? body : buildPullRequestBody(input, { omitPackages: true });
|
|
401
|
+
if (withoutPackages.length <= MAX_PULL_REQUEST_BODY_CHARS || !input.summary)
|
|
402
|
+
return withoutPackages;
|
|
403
|
+
const room = input.summary.length - (withoutPackages.length - MAX_PULL_REQUEST_BODY_CHARS);
|
|
404
|
+
return buildPullRequestBody({ ...input, summary: fitSummary(input.summary, room) }, { omitPackages: true });
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* `summary` cut to `max` characters. A last line with a closing keyword
|
|
408
|
+
* (`Resolves #12`) is kept after the cut, so merging still closes the issue.
|
|
409
|
+
*/
|
|
410
|
+
function fitSummary(summary, max) {
|
|
411
|
+
const measure = (value) => value.length;
|
|
412
|
+
const lines = summary.trimEnd().split("\n");
|
|
413
|
+
const last = lines[lines.length - 1];
|
|
414
|
+
if (lines.length > 1 && last.length <= 200 && hasClosingKeyword(last)) {
|
|
415
|
+
const kept = truncateText(lines.slice(0, -1).join("\n"), max - last.length - 2, measure);
|
|
416
|
+
return `${kept}\n\n${last}`;
|
|
417
|
+
}
|
|
418
|
+
return truncateText(summary, max, measure);
|
|
419
|
+
}
|
|
332
420
|
/** The hidden run marker stays first and unconditional: createOrFindDraftPullRequest's idempotent lookup depends on it. */
|
|
333
|
-
|
|
421
|
+
function buildPullRequestBody(input, options) {
|
|
334
422
|
const sections = [`${RUN_MARKER_PREFIX}${input.runId} -->`];
|
|
335
423
|
// Hidden marker stays first; the issue link is the first visible line.
|
|
336
424
|
const issue = issueLine(input);
|
|
@@ -352,6 +440,17 @@ export function pullRequestBody(input) {
|
|
|
352
440
|
catch {
|
|
353
441
|
// omitted, like the packages section
|
|
354
442
|
}
|
|
443
|
+
try {
|
|
444
|
+
const audit = packageAuditSection(input);
|
|
445
|
+
if (audit)
|
|
446
|
+
sections.push(audit);
|
|
447
|
+
}
|
|
448
|
+
catch {
|
|
449
|
+
// Unlike the packages section, the audit banner fails closed: a report
|
|
450
|
+
// that can't be rendered still says the mode was on.
|
|
451
|
+
if (input.packageAudit)
|
|
452
|
+
sections.push(PACKAGE_AUDIT_FALLBACK_BANNER);
|
|
453
|
+
}
|
|
355
454
|
if (input.summary)
|
|
356
455
|
sections.push(input.summary);
|
|
357
456
|
if (input.tests?.length) {
|
|
@@ -360,13 +459,16 @@ export function pullRequestBody(input) {
|
|
|
360
459
|
// The packages section is informational: a failure rendering it (e.g. a
|
|
361
460
|
// malformed report) omits the section and never fails finalization.
|
|
362
461
|
try {
|
|
363
|
-
const packages = packagesSection(input);
|
|
462
|
+
const packages = options.omitPackages ? undefined : packagesSection(input);
|
|
364
463
|
if (packages)
|
|
365
464
|
sections.push(packages);
|
|
366
465
|
}
|
|
367
466
|
catch {
|
|
368
467
|
// omitted
|
|
369
468
|
}
|
|
469
|
+
if (input.latestRunId && SAFE_RUN_ID.test(input.latestRunId)) {
|
|
470
|
+
sections.push(`${LATEST_RUN_MARKER_PREFIX}${input.latestRunId} -->`);
|
|
471
|
+
}
|
|
370
472
|
return sections.join("\n\n");
|
|
371
473
|
}
|
|
372
474
|
/** The hidden marker stays first and unconditional: findStatusComment's lookup depends on it. */
|
|
@@ -404,7 +506,9 @@ export class GitHubAppClient {
|
|
|
404
506
|
throw new Error("github_app_id_invalid");
|
|
405
507
|
if (!config.privateKey.trim())
|
|
406
508
|
throw new Error("github_app_private_key_invalid");
|
|
407
|
-
|
|
509
|
+
// WARDBY_TEST_SIMULATORS (test only, refused with production configuration): every call
|
|
510
|
+
// goes to the simulator instead, all or nothing.
|
|
511
|
+
this.apiBaseUrl = testSimulatorBase("github-api") ?? normalizeApiBaseUrl(config.apiBaseUrl ?? DEFAULT_API_BASE_URL);
|
|
408
512
|
this.apiVersion = config.apiVersion ?? DEFAULT_API_VERSION;
|
|
409
513
|
if (!/^\d{4}-\d{2}-\d{2}$/.test(this.apiVersion))
|
|
410
514
|
throw new Error("github_api_version_invalid");
|
|
@@ -611,11 +715,14 @@ export class GitHubAppClient {
|
|
|
611
715
|
}
|
|
612
716
|
async completeContinuationCheckRun(input) {
|
|
613
717
|
const repository = normalizeGitHubRepository(input.repository);
|
|
614
|
-
if (!SAFE_RUN_ID.test(input.runId) || !SAFE_COMMIT_SHA.test(input.headSha)) {
|
|
718
|
+
if (!SAFE_RUN_ID.test(input.runId) || (input.headSha !== undefined && !SAFE_COMMIT_SHA.test(input.headSha))) {
|
|
615
719
|
throw new Error("github_check_run_input_invalid");
|
|
616
720
|
}
|
|
721
|
+
const ref = input.headSha ?? (input.headRef === undefined ? undefined : normalizeGitRef(input.headRef));
|
|
722
|
+
if (!ref)
|
|
723
|
+
throw new Error("github_check_run_input_invalid");
|
|
617
724
|
await this.withChecksToken(repository, async (token) => {
|
|
618
|
-
const existing = await this.findCheckRun(token, repository,
|
|
725
|
+
const existing = await this.findCheckRun(token, repository, ref, input.runId);
|
|
619
726
|
if (!existing)
|
|
620
727
|
return;
|
|
621
728
|
const [owner, name] = repository.split("/");
|
|
@@ -653,8 +760,14 @@ export class GitHubAppClient {
|
|
|
653
760
|
draft: true,
|
|
654
761
|
}),
|
|
655
762
|
}, [201, 422]);
|
|
656
|
-
if (response.status === 201)
|
|
657
|
-
|
|
763
|
+
if (response.status === 201) {
|
|
764
|
+
const payload = record(await response.json());
|
|
765
|
+
const created = this.parsePullRequest(payload, repository);
|
|
766
|
+
// Hash what GitHub stored, not what was sent.
|
|
767
|
+
return typeof payload.body === "string"
|
|
768
|
+
? { ...created, descriptionSha256: descriptionSha256(payload.body) }
|
|
769
|
+
: created;
|
|
770
|
+
}
|
|
658
771
|
const raced = await this.findPullRequest(token, { ...input, repository, baseRef, headRef });
|
|
659
772
|
if (raced)
|
|
660
773
|
return raced;
|
|
@@ -675,6 +788,270 @@ export class GitHubAppClient {
|
|
|
675
788
|
throw new Error("github_pull_request_input_invalid");
|
|
676
789
|
return this.withRepositoryToken(repository, (token) => this.findPullRequest(token, { runId: input.runId, repository, baseRef, headRef, acceptReadyForReview: true }));
|
|
677
790
|
}
|
|
791
|
+
/** See GitHubRepositoryAccess.openPullRequestDraft: GitHub's own `draft` flag on the marked open PR. */
|
|
792
|
+
async openPullRequestDraft(input) {
|
|
793
|
+
const repository = normalizeGitHubRepository(input.repository);
|
|
794
|
+
const baseRef = normalizeGitRef(input.baseRef);
|
|
795
|
+
const headRef = normalizeGitRef(input.headRef);
|
|
796
|
+
if (!SAFE_RUN_ID.test(input.runId))
|
|
797
|
+
throw new Error("github_pull_request_input_invalid");
|
|
798
|
+
return this.withRepositoryToken(repository, async (token) => {
|
|
799
|
+
const found = await this.findMarkedPullRequest(token, {
|
|
800
|
+
runId: input.runId,
|
|
801
|
+
repository,
|
|
802
|
+
baseRef,
|
|
803
|
+
headRef,
|
|
804
|
+
acceptReadyForReview: true,
|
|
805
|
+
});
|
|
806
|
+
return found ? found.draft : null;
|
|
807
|
+
});
|
|
808
|
+
}
|
|
809
|
+
/** See GitHubRepositoryAccess.openPullRequestBase: the marked open PR's current base, same repository only. */
|
|
810
|
+
async openPullRequestBase(input) {
|
|
811
|
+
const repository = normalizeGitHubRepository(input.repository);
|
|
812
|
+
const baseRef = normalizeGitRef(input.baseRef);
|
|
813
|
+
const headRef = normalizeGitRef(input.headRef);
|
|
814
|
+
if (!SAFE_RUN_ID.test(input.runId))
|
|
815
|
+
throw new Error("github_pull_request_input_invalid");
|
|
816
|
+
return this.withRepositoryToken(repository, async (token) => {
|
|
817
|
+
const found = await this.findMarkedPullRequest(token, {
|
|
818
|
+
runId: input.runId,
|
|
819
|
+
repository,
|
|
820
|
+
baseRef,
|
|
821
|
+
headRef,
|
|
822
|
+
acceptReadyForReview: true,
|
|
823
|
+
});
|
|
824
|
+
if (!found)
|
|
825
|
+
return null;
|
|
826
|
+
const base = record(found.raw.base);
|
|
827
|
+
const head = record(found.raw.head);
|
|
828
|
+
const sameRepository = (side) => {
|
|
829
|
+
const name = side.repo && typeof side.repo === "object" ? side.repo.full_name : null;
|
|
830
|
+
return typeof name === "string" && name.toLowerCase() === repository.toLowerCase();
|
|
831
|
+
};
|
|
832
|
+
// A fork's branch is never fetched: wardby's own heads live in the PR's repository.
|
|
833
|
+
if (!sameRepository(base) || !sameRepository(head))
|
|
834
|
+
return null;
|
|
835
|
+
if (typeof base.ref !== "string")
|
|
836
|
+
throw new Error("github_api_invalid_response");
|
|
837
|
+
try {
|
|
838
|
+
return { number: found.pr.number, baseRef: normalizeGitRef(base.ref) };
|
|
839
|
+
}
|
|
840
|
+
catch (error) {
|
|
841
|
+
throw new Error("github_api_invalid_response", { cause: error });
|
|
842
|
+
}
|
|
843
|
+
});
|
|
844
|
+
}
|
|
845
|
+
/**
|
|
846
|
+
* A fix round's description update (see GitHubRepositoryAccess). One
|
|
847
|
+
* repository token; every failure becomes `skipped:error`.
|
|
848
|
+
*/
|
|
849
|
+
async updatePullRequestDescription(input) {
|
|
850
|
+
try {
|
|
851
|
+
const target = this.descriptionTarget(input);
|
|
852
|
+
return await this.withRepositoryToken(target.repository, async (token) => {
|
|
853
|
+
const state = await this.descriptionState(token, input, target);
|
|
854
|
+
if (!state)
|
|
855
|
+
return { kind: "skipped", reason: "not_open" };
|
|
856
|
+
const { pr, verdict, full, proposed } = state;
|
|
857
|
+
const { repository } = target;
|
|
858
|
+
if (!state.writable) {
|
|
859
|
+
// The comment truncates a body too long for it, so the full one goes in.
|
|
860
|
+
const commentPosted = await this.upsertDescriptionComment(token, repository, pr.number, input, full, state)
|
|
861
|
+
.then(() => true)
|
|
862
|
+
.catch(() => false);
|
|
863
|
+
return { kind: "edited_by_hand", commentPosted };
|
|
864
|
+
}
|
|
865
|
+
if (proposed === undefined)
|
|
866
|
+
return { kind: "skipped", reason: "too_long" };
|
|
867
|
+
const agentTextRunId = verdict.ok ? input.runId : input.baseline.agentTextRunId;
|
|
868
|
+
const outcome = verdict.ok
|
|
869
|
+
? { kind: "replaced" }
|
|
870
|
+
: { kind: "refreshed", reason: verdict.reason };
|
|
871
|
+
const proposedSha256 = descriptionSha256(proposed);
|
|
872
|
+
if (proposedSha256 === input.baseline.recordedSha256) {
|
|
873
|
+
return { kind: "unchanged", sha256: proposedSha256, agentTextRunId: input.baseline.agentTextRunId };
|
|
874
|
+
}
|
|
875
|
+
const [owner, name] = repository.split("/");
|
|
876
|
+
// Known, accepted race: a person editing between our read and this PATCH is overwritten (GitHub has no If-Match); the store's compare-and-set only covers wardby runs racing each other.
|
|
877
|
+
const response = await this.requestJson(`/repos/${encodeURIComponent(owner)}/${encodeURIComponent(name)}/pulls/${pr.number}`, token, { method: "PATCH", body: JSON.stringify({ body: proposed }) }, [200]);
|
|
878
|
+
const written = record(await response.json()).body;
|
|
879
|
+
if (typeof written !== "string")
|
|
880
|
+
throw new Error("github_api_invalid_response");
|
|
881
|
+
// A proposal left from an earlier round is stale now; a failure here never undoes the write.
|
|
882
|
+
await this.markDescriptionCommentApplied(token, repository, pr.number, input.runId, state).catch(() => undefined);
|
|
883
|
+
return { ...outcome, sha256: descriptionSha256(written), agentTextRunId };
|
|
884
|
+
});
|
|
885
|
+
}
|
|
886
|
+
catch {
|
|
887
|
+
return { kind: "skipped", reason: "error" };
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
/** See GitHubRepositoryAccess.proposeDescriptionComment. */
|
|
891
|
+
async proposeDescriptionComment(input) {
|
|
892
|
+
try {
|
|
893
|
+
if (!input.headSha || !/^[0-9a-f]{40}$/.test(input.headSha))
|
|
894
|
+
return false;
|
|
895
|
+
const target = this.descriptionTarget(input);
|
|
896
|
+
return await this.withRepositoryToken(target.repository, async (token) => {
|
|
897
|
+
const state = await this.descriptionState(token, input, target);
|
|
898
|
+
if (!state || state.writable)
|
|
899
|
+
return false;
|
|
900
|
+
await this.upsertDescriptionComment(token, target.repository, state.pr.number, input, state.full, state);
|
|
901
|
+
return true;
|
|
902
|
+
});
|
|
903
|
+
}
|
|
904
|
+
catch {
|
|
905
|
+
return false;
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
/** The validated repository and refs; throws unless this is wardby's own pull request branch. */
|
|
909
|
+
descriptionTarget(input) {
|
|
910
|
+
const repository = normalizeGitHubRepository(input.repository);
|
|
911
|
+
const baseRef = normalizeGitRef(input.baseRef);
|
|
912
|
+
const headRef = normalizeGitRef(input.headRef);
|
|
913
|
+
if (!SAFE_RUN_ID.test(input.runId) ||
|
|
914
|
+
!SAFE_RUN_ID.test(input.rootRunId) ||
|
|
915
|
+
!SAFE_RUN_ID.test(input.baseline.agentTextRunId) ||
|
|
916
|
+
(input.headSha !== undefined && !/^[0-9a-f]{40}$/.test(input.headSha)) ||
|
|
917
|
+
// Only wardby's own pull request: its branch is the root run's.
|
|
918
|
+
headRef !== `wardby/run-${input.rootRunId}`) {
|
|
919
|
+
throw new Error("github_pull_request_description_input_invalid");
|
|
920
|
+
}
|
|
921
|
+
return { repository, baseRef, headRef };
|
|
922
|
+
}
|
|
923
|
+
/**
|
|
924
|
+
* The open pull request, the description this run proposes, and whether
|
|
925
|
+
* wardby may write it directly: it is unedited (recorded or rebuilt hash),
|
|
926
|
+
* or a person copied wardby's latest proposal in (taken back). Null when
|
|
927
|
+
* the pull request isn't open. `comments` is set when it was read.
|
|
928
|
+
*/
|
|
929
|
+
async descriptionState(token, input, target) {
|
|
930
|
+
const { repository, baseRef, headRef } = target;
|
|
931
|
+
const found = await this.findMarkedPullRequest(token, {
|
|
932
|
+
runId: input.rootRunId,
|
|
933
|
+
repository,
|
|
934
|
+
baseRef,
|
|
935
|
+
headRef,
|
|
936
|
+
acceptReadyForReview: true,
|
|
937
|
+
});
|
|
938
|
+
if (!found)
|
|
939
|
+
return null;
|
|
940
|
+
const { pr, body: currentBody } = found;
|
|
941
|
+
const verdict = assessFinalMessage(input.summary, input.baseline.previousAgentText);
|
|
942
|
+
const summary = verdict.ok ? input.summary : input.baseline.previousAgentText;
|
|
943
|
+
const { full, proposed } = this.proposedDescription(input, repository, baseRef, headRef, summary, currentBody);
|
|
944
|
+
const unedited = isUneditedDescription({
|
|
945
|
+
currentBody,
|
|
946
|
+
rootRunId: input.rootRunId,
|
|
947
|
+
recordedSha256: input.baseline.recordedSha256,
|
|
948
|
+
...(input.baseline.legacySha256 ? { legacySha256: input.baseline.legacySha256 } : {}),
|
|
949
|
+
previousAgentText: input.baseline.previousAgentText,
|
|
950
|
+
});
|
|
951
|
+
if (unedited)
|
|
952
|
+
return { pr, currentBody, verdict, full, proposed, writable: true };
|
|
953
|
+
const comments = await this.findDescriptionComments(token, repository, pr.number);
|
|
954
|
+
const latest = comments.newest?.parsed.proposed;
|
|
955
|
+
// The author copied wardby's latest proposal in: the description is wardby's again.
|
|
956
|
+
const takenBack = latest !== undefined &&
|
|
957
|
+
isUneditedDescription({
|
|
958
|
+
currentBody,
|
|
959
|
+
rootRunId: input.rootRunId,
|
|
960
|
+
recordedSha256: descriptionSha256(latest),
|
|
961
|
+
});
|
|
962
|
+
return { pr, currentBody, verdict, full, proposed, writable: takenBack, comments };
|
|
963
|
+
}
|
|
964
|
+
/**
|
|
965
|
+
* The rebuilt description: the generated sections from this run with
|
|
966
|
+
* `summary` as the agent text, the latest-run marker last, and the current
|
|
967
|
+
* body's Related block re-inserted verbatim. `proposed` drops the Packages
|
|
968
|
+
* section when `full` exceeds GitHub's limit, and is undefined when it still
|
|
969
|
+
* would.
|
|
970
|
+
*/
|
|
971
|
+
proposedDescription(input, repository, baseRef, headRef, summary, currentBody) {
|
|
972
|
+
const related = extractRelatedBlock(currentBody.replace(/\r\n/g, "\n"));
|
|
973
|
+
const build = (omitPackages) => {
|
|
974
|
+
const body = pullRequestBody({
|
|
975
|
+
repository,
|
|
976
|
+
baseRef,
|
|
977
|
+
headRef,
|
|
978
|
+
runId: input.rootRunId,
|
|
979
|
+
latestRunId: input.runId,
|
|
980
|
+
summary,
|
|
981
|
+
tests: input.tests,
|
|
982
|
+
tag: input.tag,
|
|
983
|
+
packages: input.packages,
|
|
984
|
+
packagesScope: "pull_request",
|
|
985
|
+
packageRefusals: input.packageRefusals,
|
|
986
|
+
packageAudit: input.packageAudit,
|
|
987
|
+
restoredDependencies: input.restoredDependencies,
|
|
988
|
+
changedLockfiles: input.changedLockfiles,
|
|
989
|
+
issue: input.issue,
|
|
990
|
+
issueNumber: input.issueNumber,
|
|
991
|
+
}, { omitPackages });
|
|
992
|
+
return related ? (upsertRelatedSection(body, related) ?? body) : body;
|
|
993
|
+
};
|
|
994
|
+
const full = build(false);
|
|
995
|
+
if (full.length <= MAX_PULL_REQUEST_BODY_CHARS)
|
|
996
|
+
return { full, proposed: full };
|
|
997
|
+
const trimmed = build(true);
|
|
998
|
+
return { full, proposed: trimmed.length <= MAX_PULL_REQUEST_BODY_CHARS ? trimmed : undefined };
|
|
999
|
+
}
|
|
1000
|
+
/**
|
|
1001
|
+
* Creates the pull request's one description comment, or edits the newest
|
|
1002
|
+
* one this App wrote (unchanged text is not re-sent); older duplicates are
|
|
1003
|
+
* rewritten to point at it. A comment anyone else wrote is never edited.
|
|
1004
|
+
*/
|
|
1005
|
+
async upsertDescriptionComment(token, repository, pullRequestNumber, input, proposedBody, state) {
|
|
1006
|
+
const body = descriptionFallbackComment(input.runId, proposedBody, input.headSha);
|
|
1007
|
+
const { newest, older } = state.comments ?? (await this.findDescriptionComments(token, repository, pullRequestNumber));
|
|
1008
|
+
const [owner, name] = repository.split("/");
|
|
1009
|
+
const commentPath = (id) => `/repos/${encodeURIComponent(owner)}/${encodeURIComponent(name)}/issues/comments/${id}`;
|
|
1010
|
+
let url = newest?.url;
|
|
1011
|
+
if (newest) {
|
|
1012
|
+
if (newest.body.replace(/\r\n/g, "\n") !== body) {
|
|
1013
|
+
await this.requestJson(commentPath(newest.id), token, { method: "PATCH", body: JSON.stringify({ body }) }, [200]);
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
else {
|
|
1017
|
+
const response = await this.requestJson(`/repos/${encodeURIComponent(owner)}/${encodeURIComponent(name)}/issues/${pullRequestNumber}/comments`, token, { method: "POST", body: JSON.stringify({ body }) }, [201]);
|
|
1018
|
+
const created = record(await response.json()).html_url;
|
|
1019
|
+
url = typeof created === "string" ? created : undefined;
|
|
1020
|
+
}
|
|
1021
|
+
await this.supersedeDescriptionComments(token, commentPath, older, url);
|
|
1022
|
+
}
|
|
1023
|
+
/** Once a run applied its description, a pending proposal in the description comment is marked applied. */
|
|
1024
|
+
async markDescriptionCommentApplied(token, repository, pullRequestNumber, runId, state) {
|
|
1025
|
+
const { newest, older } = state.comments ?? (await this.findDescriptionComments(token, repository, pullRequestNumber));
|
|
1026
|
+
if (!newest)
|
|
1027
|
+
return;
|
|
1028
|
+
const [owner, name] = repository.split("/");
|
|
1029
|
+
const commentPath = (id) => `/repos/${encodeURIComponent(owner)}/${encodeURIComponent(name)}/issues/comments/${id}`;
|
|
1030
|
+
if (!newest.parsed.applied) {
|
|
1031
|
+
await this.requestJson(commentPath(newest.id), token, { method: "PATCH", body: JSON.stringify({ body: descriptionAppliedComment(runId) }) }, [200]);
|
|
1032
|
+
}
|
|
1033
|
+
await this.supersedeDescriptionComments(token, commentPath, older, newest.url);
|
|
1034
|
+
}
|
|
1035
|
+
/** Rewrites duplicate description comments to a pointer at the one in use. */
|
|
1036
|
+
async supersedeDescriptionComments(token, commentPath, older, url) {
|
|
1037
|
+
for (const duplicate of older) {
|
|
1038
|
+
await this.requestJson(commentPath(duplicate.id), token, { method: "PATCH", body: JSON.stringify({ body: supersededDescriptionComment(url) }) }, [200]);
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
/** The description comments this App wrote (see findDescriptionComments; the reviewer reads them the same way). */
|
|
1042
|
+
async findDescriptionComments(token, repository, pullRequestNumber) {
|
|
1043
|
+
const { id: appId } = await this.appIdentity();
|
|
1044
|
+
const [owner, name] = repository.split("/");
|
|
1045
|
+
return findDescriptionComments(async (page) => {
|
|
1046
|
+
const response = await this.requestJson(`/repos/${encodeURIComponent(owner)}/${encodeURIComponent(name)}/issues/${pullRequestNumber}/comments?per_page=100&page=${page}`, token);
|
|
1047
|
+
const payload = await response.json();
|
|
1048
|
+
if (!Array.isArray(payload))
|
|
1049
|
+
throw new Error("github_api_invalid_response");
|
|
1050
|
+
return payload;
|
|
1051
|
+
},
|
|
1052
|
+
// Only this App's own comments: never a person's, nor another App's or bot's.
|
|
1053
|
+
(comment) => isThisAppsComment(comment, appId));
|
|
1054
|
+
}
|
|
678
1055
|
async appJwt() {
|
|
679
1056
|
let keyObject;
|
|
680
1057
|
try {
|
|
@@ -740,11 +1117,19 @@ export class GitHubAppClient {
|
|
|
740
1117
|
await this.requestJson("/installation/token", token, { method: "DELETE" }, [204]);
|
|
741
1118
|
}
|
|
742
1119
|
async findPullRequest(token, input) {
|
|
1120
|
+
return (await this.findMarkedPullRequest(token, input))?.pr ?? null;
|
|
1121
|
+
}
|
|
1122
|
+
/**
|
|
1123
|
+
* findPullRequest, plus the matched pull request's raw body and payload. Not
|
|
1124
|
+
* filtered by base: a person may retarget the PR, and the head branch plus the
|
|
1125
|
+
* run marker already identify it. When several open PRs match, the one on
|
|
1126
|
+
* `baseRef` wins.
|
|
1127
|
+
*/
|
|
1128
|
+
async findMarkedPullRequest(token, input) {
|
|
743
1129
|
const [owner, name] = input.repository.split("/");
|
|
744
1130
|
const query = new URLSearchParams({
|
|
745
1131
|
state: "open",
|
|
746
1132
|
head: `${owner}:${input.headRef}`,
|
|
747
|
-
base: input.baseRef,
|
|
748
1133
|
per_page: "100",
|
|
749
1134
|
});
|
|
750
1135
|
const response = await this.requestJson(`/repos/${encodeURIComponent(owner)}/${encodeURIComponent(name)}/pulls?${query.toString()}`, token);
|
|
@@ -752,13 +1137,26 @@ export class GitHubAppClient {
|
|
|
752
1137
|
if (!Array.isArray(payload))
|
|
753
1138
|
throw new Error("github_api_invalid_response");
|
|
754
1139
|
const marker = `${RUN_MARKER_PREFIX}${input.runId} -->`;
|
|
1140
|
+
let first;
|
|
755
1141
|
for (const item of payload) {
|
|
756
1142
|
const candidate = record(item);
|
|
757
|
-
if (typeof candidate.body
|
|
758
|
-
|
|
1143
|
+
if (typeof candidate.body !== "string" || !candidate.body.includes(marker))
|
|
1144
|
+
continue;
|
|
1145
|
+
const base = candidate.base && typeof candidate.base === "object" ? candidate.base : {};
|
|
1146
|
+
if (base.ref === input.baseRef) {
|
|
1147
|
+
first = candidate;
|
|
1148
|
+
break;
|
|
759
1149
|
}
|
|
1150
|
+
first ??= candidate;
|
|
760
1151
|
}
|
|
761
|
-
|
|
1152
|
+
if (!first)
|
|
1153
|
+
return null;
|
|
1154
|
+
return {
|
|
1155
|
+
pr: this.parsePullRequest(first, input.repository, input.acceptReadyForReview === true),
|
|
1156
|
+
body: first.body,
|
|
1157
|
+
draft: first.draft === true,
|
|
1158
|
+
raw: first,
|
|
1159
|
+
};
|
|
762
1160
|
}
|
|
763
1161
|
parsePullRequest(value, repository, acceptReadyForReview = false) {
|
|
764
1162
|
const payload = record(value);
|
|
@@ -788,23 +1186,27 @@ export class GitHubAppClient {
|
|
|
788
1186
|
return { number, url: expectedUrl };
|
|
789
1187
|
}
|
|
790
1188
|
async findStatusComment(token, repository, pullRequestNumber, runId) {
|
|
1189
|
+
const marker = `${STATUS_COMMENT_MARKER_PREFIX}${runId} -->`;
|
|
1190
|
+
return this.findMarkedComment(token, repository, pullRequestNumber, (body) => body.includes(marker));
|
|
1191
|
+
}
|
|
1192
|
+
/** The first comment on the pull request (first page) whose body `matches`. */
|
|
1193
|
+
async findMarkedComment(token, repository, pullRequestNumber, matches) {
|
|
791
1194
|
const [owner, name] = repository.split("/");
|
|
792
1195
|
const response = await this.requestJson(`/repos/${encodeURIComponent(owner)}/${encodeURIComponent(name)}/issues/${pullRequestNumber}/comments?per_page=100`, token);
|
|
793
1196
|
const payload = await response.json();
|
|
794
1197
|
if (!Array.isArray(payload))
|
|
795
1198
|
throw new Error("github_api_invalid_response");
|
|
796
|
-
const marker = `${STATUS_COMMENT_MARKER_PREFIX}${runId} -->`;
|
|
797
1199
|
for (const item of payload) {
|
|
798
1200
|
const candidate = record(item);
|
|
799
|
-
if (typeof candidate.body
|
|
800
|
-
|
|
801
|
-
}
|
|
1201
|
+
if (typeof candidate.body !== "string" || !matches(candidate.body))
|
|
1202
|
+
continue;
|
|
1203
|
+
return { id: positiveInteger(candidate.id), body: candidate.body };
|
|
802
1204
|
}
|
|
803
1205
|
return null;
|
|
804
1206
|
}
|
|
805
|
-
async findCheckRun(token, repository,
|
|
1207
|
+
async findCheckRun(token, repository, ref, runId) {
|
|
806
1208
|
const [owner, name] = repository.split("/");
|
|
807
|
-
const response = await this.requestJson(`/repos/${encodeURIComponent(owner)}/${encodeURIComponent(name)}/commits/${
|
|
1209
|
+
const response = await this.requestJson(`/repos/${encodeURIComponent(owner)}/${encodeURIComponent(name)}/commits/${encodeURIComponent(ref)}/check-runs?per_page=100`, token);
|
|
808
1210
|
const payload = record(await response.json());
|
|
809
1211
|
const checkRuns = Array.isArray(payload.check_runs) ? payload.check_runs.map(record) : [];
|
|
810
1212
|
for (const candidate of checkRuns) {
|