@wjarka/cezarion 0.16.0 → 0.17.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/LICENSE +1 -0
- package/README.md +594 -207
- package/dist/agent-config/account-identity.d.ts +3 -19
- package/dist/agent-config/account-identity.js +8 -5
- package/dist/agent-config/account-identity.js.map +1 -1
- package/dist/agent-config/catalog.d.ts +3 -14
- package/dist/agent-config/catalog.js.map +1 -1
- package/dist/agent-config/files.d.ts +2 -6
- package/dist/agent-config/files.js +2 -6
- package/dist/agent-config/files.js.map +1 -1
- package/dist/agent-config/model-settings/types.d.ts +1 -1
- package/dist/agent-config/seed.d.ts +11 -3
- package/dist/agent-config/seed.js +52 -48
- package/dist/agent-config/seed.js.map +1 -1
- package/dist/agent-config/service.js +3 -2
- package/dist/agent-config/service.js.map +1 -1
- package/dist/agent-config/validate.d.ts +1 -4
- package/dist/agent-config/validate.js.map +1 -1
- package/dist/application-update/helper.d.ts +2 -2
- package/dist/application-update/helper.js +7 -12
- package/dist/application-update/helper.js.map +1 -1
- package/dist/application-update/launcher.d.ts +4 -0
- package/dist/application-update/launcher.js +18 -12
- package/dist/application-update/launcher.js.map +1 -1
- package/dist/application-update/service.d.ts +3 -3
- package/dist/application-update/service.js +1 -1
- package/dist/application-update/service.js.map +1 -1
- package/dist/archive-on-merge-setting.d.ts +15 -0
- package/dist/archive-on-merge-setting.js +14 -0
- package/dist/archive-on-merge-setting.js.map +1 -0
- package/dist/artifacts/cli.d.ts +8 -0
- package/dist/artifacts/cli.js +21 -2
- package/dist/artifacts/cli.js.map +1 -1
- package/dist/artifacts/resolve.js +2 -1
- package/dist/artifacts/resolve.js.map +1 -1
- package/dist/artifacts/store.js.map +1 -1
- package/dist/automations/automation-cli.d.ts +59 -0
- package/dist/automations/automation-cli.js +509 -0
- package/dist/automations/automation-cli.js.map +1 -0
- package/dist/automations/builtin-skill.d.ts +7 -0
- package/dist/automations/builtin-skill.js +14 -0
- package/dist/automations/builtin-skill.js.map +1 -0
- package/dist/automations/github-baseline.d.ts +3 -0
- package/dist/automations/github-baseline.js +17 -0
- package/dist/automations/github-baseline.js.map +1 -0
- package/dist/automations/github-poller.d.ts +23 -4
- package/dist/automations/github-poller.js +157 -88
- package/dist/automations/github-poller.js.map +1 -1
- package/dist/automations/prompts.d.ts +12 -0
- package/dist/automations/prompts.js +160 -0
- package/dist/automations/prompts.js.map +1 -0
- package/dist/automations/schedule-runner.d.ts +43 -24
- package/dist/automations/schedule-runner.js +207 -20
- package/dist/automations/schedule-runner.js.map +1 -1
- package/dist/automations/scheduler.d.ts +10 -2
- package/dist/automations/scheduler.js +200 -54
- package/dist/automations/scheduler.js.map +1 -1
- package/dist/automations/service.d.ts +777 -0
- package/dist/automations/service.js +444 -0
- package/dist/automations/service.js.map +1 -0
- package/dist/automations/store.d.ts +17 -7
- package/dist/automations/store.js +88 -17
- package/dist/automations/store.js.map +1 -1
- package/dist/automations/task-template.d.ts +7 -2
- package/dist/automations/task-template.js +93 -44
- package/dist/automations/task-template.js.map +1 -1
- package/dist/automations/tool-adapter.d.ts +6 -0
- package/dist/automations/tool-adapter.js +108 -0
- package/dist/automations/tool-adapter.js.map +1 -0
- package/dist/automations/types.d.ts +1 -405
- package/dist/automations/types.js +1 -218
- package/dist/automations/types.js.map +1 -1
- package/dist/autosave-cleanup.d.ts +26 -0
- package/dist/autosave-cleanup.js +87 -0
- package/dist/autosave-cleanup.js.map +1 -0
- package/dist/autosave-git.d.ts +3 -0
- package/dist/autosave-git.js +67 -35
- package/dist/autosave-git.js.map +1 -1
- package/dist/autosave-group.d.ts +8 -0
- package/dist/autosave-group.js +61 -0
- package/dist/autosave-group.js.map +1 -0
- package/dist/autosave-holders.d.ts +31 -4
- package/dist/autosave-holders.js +92 -7
- package/dist/autosave-holders.js.map +1 -1
- package/dist/autosave-windows.d.ts +14 -0
- package/dist/autosave-windows.js +36 -2
- package/dist/autosave-windows.js.map +1 -1
- package/dist/ci-wait/client.d.ts +20 -3
- package/dist/ci-wait/client.js +40 -44
- package/dist/ci-wait/client.js.map +1 -1
- package/dist/ci-wait/controller.d.ts +622 -36
- package/dist/ci-wait/controller.js +152 -20
- package/dist/ci-wait/controller.js.map +1 -1
- package/dist/ci-wait/errors.d.ts +19 -0
- package/dist/ci-wait/errors.js +180 -2
- package/dist/ci-wait/errors.js.map +1 -1
- package/dist/ci-wait/github.d.ts +25 -1
- package/dist/ci-wait/github.js +76 -9
- package/dist/ci-wait/github.js.map +1 -1
- package/dist/ci-wait/mcp.d.ts +0 -1
- package/dist/ci-wait/mcp.js +0 -2
- package/dist/ci-wait/mcp.js.map +1 -1
- package/dist/ci-wait/supervisor.d.ts +7 -0
- package/dist/ci-wait/supervisor.js +113 -17
- package/dist/ci-wait/supervisor.js.map +1 -1
- package/dist/ci-wait/tools.d.ts +536 -16
- package/dist/ci-wait/tools.js +165 -20
- package/dist/ci-wait/tools.js.map +1 -1
- package/dist/config.d.ts +3 -17
- package/dist/config.js +4 -60
- package/dist/config.js.map +1 -1
- package/dist/contract/active-provider.d.ts +12 -0
- package/dist/contract/agent-profiles.d.ts +2 -10
- package/dist/contract/ask-schema.d.ts +22 -4
- package/dist/contract/ask.d.ts +6 -4
- package/dist/contract/attachments.d.ts +35 -0
- package/dist/contract/attention.d.ts +46 -37
- package/dist/contract/automation-requests.d.ts +308 -0
- package/dist/contract/automation-schedule.d.ts +40 -11
- package/dist/contract/automation-storage.d.ts +512 -0
- package/dist/contract/automations.d.ts +421 -40
- package/dist/contract/ci-wait.d.ts +41 -4
- package/dist/contract/config-requests.d.ts +36 -0
- package/dist/contract/conversations.d.ts +24 -10
- package/dist/contract/defined.d.ts +8 -0
- package/dist/contract/delegation.d.ts +312 -8
- package/dist/contract/events.d.ts +24 -3
- package/dist/contract/github-requests.d.ts +26 -0
- package/dist/contract/github.d.ts +60 -27
- package/dist/contract/handoff.d.ts +43 -0
- package/dist/contract/health.d.ts +2 -2
- package/dist/contract/image-path.d.ts +6 -0
- package/dist/contract/index.d.ts +31 -0
- package/dist/contract/index.js +3756 -1525
- package/dist/contract/live-coordination.d.ts +158 -0
- package/dist/contract/live-limits.d.ts +8 -0
- package/dist/contract/live.d.ts +123 -0
- package/dist/contract/pending-ask.d.ts +58 -0
- package/dist/contract/permissions.d.ts +54 -0
- package/dist/contract/preview.d.ts +33 -0
- package/dist/contract/project-limits.d.ts +33 -0
- package/dist/contract/projects.d.ts +486 -20
- package/dist/contract/provider-requests.d.ts +77 -0
- package/dist/contract/query-requests.d.ts +52 -0
- package/dist/contract/repo-requests.d.ts +12 -0
- package/dist/contract/run-labels.d.ts +8 -0
- package/dist/contract/run-limits.d.ts +8 -0
- package/dist/contract/run-predicates.d.ts +32 -0
- package/dist/contract/run-requests.d.ts +33 -0
- package/dist/contract/run-search.d.ts +193 -0
- package/dist/contract/runs.d.ts +2232 -1894
- package/dist/contract/scheduled-tasks.d.ts +187 -0
- package/dist/contract/skills.d.ts +22 -772
- package/dist/contract/team-skill-ref.d.ts +10 -0
- package/dist/{core → contract}/tool-display.d.ts +4 -10
- package/dist/contract/ui-state-requests.d.ts +60 -0
- package/dist/contract/version.d.ts +3 -0
- package/dist/contract/voice.d.ts +213 -0
- package/dist/contract/waits.d.ts +1204 -0
- package/dist/contract/workflow-file.d.ts +37 -0
- package/dist/contract/workflow-requests.d.ts +7 -0
- package/dist/contract/workflows.d.ts +1 -2
- package/dist/contract/workspace.d.ts +34 -82
- package/dist/contract/zoned-time.d.ts +2 -11
- package/dist/core/agent-env.d.ts +4 -16
- package/dist/core/agent-env.js +14 -16
- package/dist/core/agent-env.js.map +1 -1
- package/dist/core/agent-profiles.d.ts +28 -40
- package/dist/core/agent-profiles.js +56 -51
- package/dist/core/agent-profiles.js.map +1 -1
- package/dist/core/agent-runner.d.ts +88 -44
- package/dist/core/agent-runner.js +17 -18
- package/dist/core/agent-runner.js.map +1 -1
- package/dist/core/ask.d.ts +3 -0
- package/dist/core/ask.js +12 -0
- package/dist/core/ask.js.map +1 -1
- package/dist/core/backend-detect.js +16 -2
- package/dist/core/backend-detect.js.map +1 -1
- package/dist/core/claude-cli-runner.d.ts +23 -4
- package/dist/core/claude-cli-runner.js +280 -76
- package/dist/core/claude-cli-runner.js.map +1 -1
- package/dist/core/claude-model-catalog.d.ts +2 -10
- package/dist/core/claude-model-catalog.js +2 -10
- package/dist/core/claude-model-catalog.js.map +1 -1
- package/dist/core/claude-ui-mapper.d.ts +2 -2
- package/dist/core/claude-ui-mapper.js +94 -2
- package/dist/core/claude-ui-mapper.js.map +1 -1
- package/dist/core/codex-app-server-runner.d.ts +3 -4
- package/dist/core/codex-app-server-runner.js +146 -51
- package/dist/core/codex-app-server-runner.js.map +1 -1
- package/dist/core/codex-app-server-transport.d.ts +4 -3
- package/dist/core/codex-app-server-transport.js +11 -7
- package/dist/core/codex-app-server-transport.js.map +1 -1
- package/dist/core/codex-approvals.d.ts +12 -0
- package/dist/core/codex-approvals.js +161 -0
- package/dist/core/codex-approvals.js.map +1 -0
- package/dist/core/codex-permissions.d.ts +1 -1
- package/dist/core/codex-ui-mapper.js +45 -1
- package/dist/core/codex-ui-mapper.js.map +1 -1
- package/dist/core/cursor-acp-runner.d.ts +1 -1
- package/dist/core/cursor-acp-runner.js +39 -15
- package/dist/core/cursor-acp-runner.js.map +1 -1
- package/dist/core/cursor-cli-flags.d.ts +19 -0
- package/dist/core/cursor-cli-flags.js +47 -0
- package/dist/core/cursor-cli-flags.js.map +1 -0
- package/dist/core/cursor-model-catalog.d.ts +22 -0
- package/dist/core/cursor-model-catalog.js +53 -24
- package/dist/core/cursor-model-catalog.js.map +1 -1
- package/dist/core/cursor-print-capabilities.d.ts +16 -0
- package/dist/core/cursor-print-capabilities.js +141 -0
- package/dist/core/cursor-print-capabilities.js.map +1 -0
- package/dist/core/cursor-print-process.d.ts +31 -0
- package/dist/core/cursor-print-process.js +117 -0
- package/dist/core/cursor-print-process.js.map +1 -0
- package/dist/core/cursor-print-runner.d.ts +31 -0
- package/dist/core/cursor-print-runner.js +555 -0
- package/dist/core/cursor-print-runner.js.map +1 -0
- package/dist/core/cursor-print-tool-catalog.d.ts +10 -0
- package/dist/core/cursor-print-tool-catalog.js +91 -0
- package/dist/core/cursor-print-tool-catalog.js.map +1 -0
- package/dist/core/cursor-print-ui-mapper.d.ts +47 -0
- package/dist/core/cursor-print-ui-mapper.js +553 -0
- package/dist/core/cursor-print-ui-mapper.js.map +1 -0
- package/dist/core/cursor-runner.d.ts +24 -0
- package/dist/core/cursor-runner.js +149 -0
- package/dist/core/cursor-runner.js.map +1 -0
- package/dist/core/cursor-ui-mapper.js +5 -2
- package/dist/core/cursor-ui-mapper.js.map +1 -1
- package/dist/core/defined.d.ts +1 -0
- package/dist/core/defined.js +2 -0
- package/dist/core/defined.js.map +1 -0
- package/dist/core/hook-items.d.ts +14 -0
- package/dist/core/hook-items.js +13 -0
- package/dist/core/hook-items.js.map +1 -0
- package/dist/core/host-model-catalog.d.ts +2 -9
- package/dist/core/host-model-catalog.js +2 -9
- package/dist/core/host-model-catalog.js.map +1 -1
- package/dist/core/managed-session.js +15 -3
- package/dist/core/managed-session.js.map +1 -1
- package/dist/core/model-identity.d.ts +4 -21
- package/dist/core/model-identity.js.map +1 -1
- package/dist/core/model-presets.d.ts +2 -13
- package/dist/core/model-presets.js +2 -13
- package/dist/core/model-presets.js.map +1 -1
- package/dist/core/omp-model-catalog.js.map +1 -1
- package/dist/core/omp-runner.d.ts +6 -7
- package/dist/core/omp-runner.js +92 -33
- package/dist/core/omp-runner.js.map +1 -1
- package/dist/core/omp-ui-mapper.js +1 -1
- package/dist/core/omp-ui-mapper.js.map +1 -1
- package/dist/core/opencode-model-catalog.js.map +1 -1
- package/dist/core/opencode-permissions.d.ts +52 -0
- package/dist/core/opencode-permissions.js +101 -0
- package/dist/core/opencode-permissions.js.map +1 -0
- package/dist/core/opencode-server-runner.d.ts +10 -9
- package/dist/core/opencode-server-runner.js +286 -28
- package/dist/core/opencode-server-runner.js.map +1 -1
- package/dist/core/opencode-session-error.d.ts +15 -7
- package/dist/core/opencode-session-error.js +25 -9
- package/dist/core/opencode-session-error.js.map +1 -1
- package/dist/core/opencode-ui-mapper.js +2 -2
- package/dist/core/opencode-ui-mapper.js.map +1 -1
- package/dist/core/pi-model-catalog.js.map +1 -1
- package/dist/core/pi-runner.d.ts +12 -4
- package/dist/core/pi-runner.js +171 -63
- package/dist/core/pi-runner.js.map +1 -1
- package/dist/core/pi-ui-mapper.js +1 -1
- package/dist/core/pi-ui-mapper.js.map +1 -1
- package/dist/core/process-usage.d.ts +5 -0
- package/dist/core/process-usage.js +24 -4
- package/dist/core/process-usage.js.map +1 -1
- package/dist/core/provider-auth.d.ts +16 -107
- package/dist/core/provider-auth.js +16 -132
- package/dist/core/provider-auth.js.map +1 -1
- package/dist/core/runner-factory.d.ts +4 -1
- package/dist/core/runner-factory.js +3 -3
- package/dist/core/runner-factory.js.map +1 -1
- package/dist/core/runner-runtime.d.ts +22 -0
- package/dist/core/runner-runtime.js +66 -5
- package/dist/core/runner-runtime.js.map +1 -1
- package/dist/core/runner-tool-grants.d.ts +21 -0
- package/dist/core/runner-tool-grants.js +64 -0
- package/dist/core/runner-tool-grants.js.map +1 -0
- package/dist/core/secret-redaction.d.ts +3 -20
- package/dist/core/secret-redaction.js +3 -20
- package/dist/core/secret-redaction.js.map +1 -1
- package/dist/core/session-process.d.ts +22 -2
- package/dist/core/session-process.js +170 -12
- package/dist/core/session-process.js.map +1 -1
- package/dist/core/shell-env.d.ts +2 -15
- package/dist/core/shell-env.js +2 -15
- package/dist/core/shell-env.js.map +1 -1
- package/dist/core/ui-events.d.ts +60 -47
- package/dist/core/ui-events.js +0 -23
- package/dist/core/ui-events.js.map +1 -1
- package/dist/core/usage-limit.js.map +1 -1
- package/dist/data-gitignore.js +3 -1
- package/dist/data-gitignore.js.map +1 -1
- package/dist/delegation/capacity.d.ts +4 -8
- package/dist/delegation/capacity.js +4 -8
- package/dist/delegation/capacity.js.map +1 -1
- package/dist/delegation/cli.js +123 -21
- package/dist/delegation/cli.js.map +1 -1
- package/dist/delegation/context.d.ts +19 -3
- package/dist/delegation/context.js +217 -22
- package/dist/delegation/context.js.map +1 -1
- package/dist/delegation/conversations.d.ts +4 -3
- package/dist/delegation/conversations.js +7 -5
- package/dist/delegation/conversations.js.map +1 -1
- package/dist/delegation/destroy-observation.d.ts +1 -1
- package/dist/delegation/destroy-observation.js.map +1 -1
- package/dist/delegation/effort.js.map +1 -1
- package/dist/delegation/execution-identity.d.ts +1 -1
- package/dist/delegation/execution-identity.js +41 -7
- package/dist/delegation/execution-identity.js.map +1 -1
- package/dist/delegation/idle-test-app.d.ts +4 -0
- package/dist/delegation/idle-test-app.js +174 -0
- package/dist/delegation/idle-test-app.js.map +1 -0
- package/dist/delegation/input.js.map +1 -1
- package/dist/delegation/park-notice.d.ts +42 -0
- package/dist/delegation/park-notice.js +71 -0
- package/dist/delegation/park-notice.js.map +1 -0
- package/dist/delegation/policy.d.ts +2 -0
- package/dist/delegation/policy.js +9 -1
- package/dist/delegation/policy.js.map +1 -1
- package/dist/delegation/process-liveness.d.ts +10 -15
- package/dist/delegation/process-liveness.js +11 -35
- package/dist/delegation/process-liveness.js.map +1 -1
- package/dist/delegation/provision.js +21 -19
- package/dist/delegation/provision.js.map +1 -1
- package/dist/delegation/questions.d.ts +3 -1
- package/dist/delegation/questions.js +10 -5
- package/dist/delegation/questions.js.map +1 -1
- package/dist/delegation/readiness.d.ts +2 -2
- package/dist/delegation/results.d.ts +4 -1
- package/dist/delegation/results.js +49 -18
- package/dist/delegation/results.js.map +1 -1
- package/dist/delegation/retry-backoff.d.ts +8 -0
- package/dist/delegation/retry-backoff.js +8 -0
- package/dist/delegation/retry-backoff.js.map +1 -1
- package/dist/delegation/routes.d.ts +114 -30
- package/dist/delegation/routes.js +4 -1
- package/dist/delegation/routes.js.map +1 -1
- package/dist/delegation/scratch-cleanup.d.ts +4 -5
- package/dist/delegation/scratch-cleanup.js +10 -8
- package/dist/delegation/scratch-cleanup.js.map +1 -1
- package/dist/delegation/service.d.ts +12 -4
- package/dist/delegation/service.js +76 -28
- package/dist/delegation/service.js.map +1 -1
- package/dist/delegation/transport.d.ts +154 -70
- package/dist/delegation/wait.d.ts +11 -0
- package/dist/delegation/wait.js +24 -0
- package/dist/delegation/wait.js.map +1 -1
- package/dist/delegation/windows-test-app.d.ts +5 -0
- package/dist/delegation/windows-test-app.js +63 -0
- package/dist/delegation/windows-test-app.js.map +1 -0
- package/dist/delegation/windows-workspace-identity.d.ts +3 -0
- package/dist/delegation/windows-workspace-identity.js +22 -0
- package/dist/delegation/windows-workspace-identity.js.map +1 -0
- package/dist/delegation/workspace.d.ts +5 -10
- package/dist/delegation/workspace.js +136 -75
- package/dist/delegation/workspace.js.map +1 -1
- package/dist/detached-process.d.ts +103 -0
- package/dist/detached-process.js +301 -0
- package/dist/detached-process.js.map +1 -0
- package/dist/discovery/catalog.d.ts +2 -1
- package/dist/discovery/catalog.js.map +1 -1
- package/dist/discovery/cli.js +3 -1
- package/dist/discovery/cli.js.map +1 -1
- package/dist/git-diff-base.d.ts +6 -61
- package/dist/git-diff-base.js +6 -70
- package/dist/git-diff-base.js.map +1 -1
- package/dist/git-index-copy.d.ts +4 -0
- package/dist/git-index-copy.js +10 -0
- package/dist/git-index-copy.js.map +1 -0
- package/dist/git-worktree-lock.d.ts +2 -4
- package/dist/git-worktree-lock.js +2 -4
- package/dist/git-worktree-lock.js.map +1 -1
- package/dist/git-worktree-release.d.ts +2 -2
- package/dist/git-worktree-release.js.map +1 -1
- package/dist/git-worktree.d.ts +47 -76
- package/dist/git-worktree.js +240 -94
- package/dist/git-worktree.js.map +1 -1
- package/dist/handoff.d.ts +51 -41
- package/dist/handoff.js +130 -78
- package/dist/handoff.js.map +1 -1
- package/dist/index.js +60 -39
- package/dist/index.js.map +1 -1
- package/dist/merge-observations.d.ts +13 -0
- package/dist/merge-observations.js +55 -0
- package/dist/merge-observations.js.map +1 -0
- package/dist/open-url.js +4 -14
- package/dist/open-url.js.map +1 -1
- package/dist/paths.d.ts +10 -13
- package/dist/paths.js +36 -17
- package/dist/paths.js.map +1 -1
- package/dist/planner.d.ts +2 -1
- package/dist/planner.js +9 -6
- package/dist/planner.js.map +1 -1
- package/dist/preview/agent-address.d.ts +34 -0
- package/dist/preview/agent-address.js +51 -0
- package/dist/preview/agent-address.js.map +1 -0
- package/dist/preview/agent-marker.d.ts +17 -0
- package/dist/preview/agent-marker.js +71 -0
- package/dist/preview/agent-marker.js.map +1 -0
- package/dist/preview/cdp-proxy.d.ts +54 -0
- package/dist/preview/cdp-proxy.js +151 -0
- package/dist/preview/cdp-proxy.js.map +1 -0
- package/dist/preview/cdp.d.ts +39 -3
- package/dist/preview/cdp.js +155 -57
- package/dist/preview/cdp.js.map +1 -1
- package/dist/preview/chromium.d.ts +18 -6
- package/dist/preview/chromium.js +112 -25
- package/dist/preview/chromium.js.map +1 -1
- package/dist/preview/dev-server.d.ts +1 -1
- package/dist/preview/dev-server.js +20 -44
- package/dist/preview/dev-server.js.map +1 -1
- package/dist/preview/host.d.ts +70 -3
- package/dist/preview/host.js +314 -28
- package/dist/preview/host.js.map +1 -1
- package/dist/preview/registration.d.ts +10 -5
- package/dist/preview/registration.js +2 -0
- package/dist/preview/registration.js.map +1 -1
- package/dist/preview/session.d.ts +2 -0
- package/dist/preview/session.js +10 -0
- package/dist/preview/session.js.map +1 -1
- package/dist/process-wait/exit-file.d.ts +29 -0
- package/dist/process-wait/exit-file.js +67 -0
- package/dist/process-wait/exit-file.js.map +1 -0
- package/dist/process-wait/supervisor.d.ts +30 -0
- package/dist/process-wait/supervisor.js +71 -0
- package/dist/process-wait/supervisor.js.map +1 -0
- package/dist/release/snapshot.d.ts +6 -6
- package/dist/runs/agent-tmpdir.d.ts +51 -36
- package/dist/runs/agent-tmpdir.js +389 -132
- package/dist/runs/agent-tmpdir.js.map +1 -1
- package/dist/runs/auto-name.d.ts +6 -6
- package/dist/runs/auto-name.js +1 -1
- package/dist/runs/auto-name.js.map +1 -1
- package/dist/runs/client-request.d.ts +3 -3
- package/dist/runs/client-request.js +3 -3
- package/dist/runs/client-request.js.map +1 -1
- package/dist/runs/delegation-state.d.ts +11 -2
- package/dist/runs/delegation-state.js.map +1 -1
- package/dist/runs/event-history-agent-context.d.ts +14 -0
- package/dist/runs/event-history-agent-context.js +105 -0
- package/dist/runs/event-history-agent-context.js.map +1 -0
- package/dist/runs/event-history-context.d.ts +15 -0
- package/dist/runs/event-history-context.js +109 -0
- package/dist/runs/event-history-context.js.map +1 -0
- package/dist/runs/event-history.d.ts +4 -1
- package/dist/runs/event-history.js +29 -130
- package/dist/runs/event-history.js.map +1 -1
- package/dist/runs/history-stream.d.ts +2 -0
- package/dist/runs/history-stream.js +62 -0
- package/dist/runs/history-stream.js.map +1 -0
- package/dist/runs/legacy-index.d.ts +6 -19
- package/dist/runs/legacy-index.js +6 -19
- package/dist/runs/legacy-index.js.map +1 -1
- package/dist/runs/live-item-snapshots.d.ts +13 -0
- package/dist/runs/live-item-snapshots.js +79 -0
- package/dist/runs/live-item-snapshots.js.map +1 -0
- package/dist/runs/loaded-run-healing.d.ts +3 -0
- package/dist/runs/loaded-run-healing.js +17 -0
- package/dist/runs/loaded-run-healing.js.map +1 -0
- package/dist/runs/outcome-commit.d.ts +98 -0
- package/dist/runs/outcome-commit.js +132 -0
- package/dist/runs/outcome-commit.js.map +1 -0
- package/dist/runs/pull-requests.d.ts +14 -0
- package/dist/runs/pull-requests.js +96 -0
- package/dist/runs/pull-requests.js.map +1 -0
- package/dist/runs/raw-record.d.ts +3 -15
- package/dist/runs/raw-record.js +3 -15
- package/dist/runs/raw-record.js.map +1 -1
- package/dist/runs/retention.d.ts +2 -2
- package/dist/runs/retention.js +7 -0
- package/dist/runs/retention.js.map +1 -1
- package/dist/runs/review-gate.d.ts +1 -1
- package/dist/runs/review-gate.js.map +1 -1
- package/dist/runs/run-claims.d.ts +2 -9
- package/dist/runs/run-claims.js.map +1 -1
- package/dist/runs/run-database.d.ts +29 -34
- package/dist/runs/run-database.js +124 -46
- package/dist/runs/run-database.js.map +1 -1
- package/dist/runs/run-index.d.ts +6 -25
- package/dist/runs/run-index.js +25 -38
- package/dist/runs/run-index.js.map +1 -1
- package/dist/runs/run-row.d.ts +5 -34
- package/dist/runs/run-row.js +13 -38
- package/dist/runs/run-row.js.map +1 -1
- package/dist/runs/store.d.ts +1597 -744
- package/dist/runs/store.js +1231 -739
- package/dist/runs/store.js.map +1 -1
- package/dist/runs/task-markers.d.ts +1 -0
- package/dist/runs/task-markers.js +5 -1
- package/dist/runs/task-markers.js.map +1 -1
- package/dist/runs/task-refs.d.ts +3 -3
- package/dist/runs/transcript-facts-fold.d.ts +64 -0
- package/dist/runs/transcript-facts-fold.js +109 -0
- package/dist/runs/transcript-facts-fold.js.map +1 -0
- package/dist/runs/transcript-facts-load.d.ts +11 -0
- package/dist/runs/transcript-facts-load.js +66 -0
- package/dist/runs/transcript-facts-load.js.map +1 -0
- package/dist/runs/transcript-facts-queue.d.ts +30 -0
- package/dist/runs/transcript-facts-queue.js +227 -0
- package/dist/runs/transcript-facts-queue.js.map +1 -0
- package/dist/runs/transcript-facts-worker.d.ts +15 -0
- package/dist/runs/transcript-facts-worker.js +26 -0
- package/dist/runs/transcript-facts-worker.js.map +1 -0
- package/dist/runs/transcript-facts.d.ts +13 -51
- package/dist/runs/transcript-facts.js +86 -202
- package/dist/runs/transcript-facts.js.map +1 -1
- package/dist/runs/ui-event-sink.d.ts +5 -30
- package/dist/runs/ui-event-sink.js +8 -30
- package/dist/runs/ui-event-sink.js.map +1 -1
- package/dist/runs/webhook.d.ts +104 -38
- package/dist/runs/webhook.js +180 -77
- package/dist/runs/webhook.js.map +1 -1
- package/dist/server/app-type.d.ts +2 -17
- package/dist/server/archive-on-merge.d.ts +35 -0
- package/dist/server/archive-on-merge.js +202 -0
- package/dist/server/archive-on-merge.js.map +1 -0
- package/dist/server/capabilities.d.ts +3 -15
- package/dist/server/capabilities.js +6 -19
- package/dist/server/capabilities.js.map +1 -1
- package/dist/server/checkout-progress.d.ts +10 -0
- package/dist/server/checkout-progress.js +27 -0
- package/dist/server/checkout-progress.js.map +1 -0
- package/dist/server/checkout.d.ts +10 -19
- package/dist/server/checkout.js +15 -45
- package/dist/server/checkout.js.map +1 -1
- package/dist/server/cockpit-ownership.d.ts +2 -0
- package/dist/server/cockpit-ownership.js +9 -0
- package/dist/server/cockpit-ownership.js.map +1 -1
- package/dist/server/cold-repo-handles.d.ts +2 -7
- package/dist/server/cold-repo-handles.js +2 -7
- package/dist/server/cold-repo-handles.js.map +1 -1
- package/dist/server/completion-actions.d.ts +30 -0
- package/dist/server/completion-actions.js +204 -0
- package/dist/server/completion-actions.js.map +1 -0
- package/dist/server/create-project.d.ts +71 -0
- package/dist/server/create-project.js +448 -0
- package/dist/server/create-project.js.map +1 -0
- package/dist/server/forge/github-filters.d.ts +7 -1
- package/dist/server/forge/github-filters.js +66 -51
- package/dist/server/forge/github-filters.js.map +1 -1
- package/dist/server/forge/github-merge-requirements.d.ts +18 -0
- package/dist/server/forge/github-merge-requirements.js +137 -0
- package/dist/server/forge/github-merge-requirements.js.map +1 -0
- package/dist/server/forge/github.d.ts +98 -111
- package/dist/server/forge/github.js +625 -306
- package/dist/server/forge/github.js.map +1 -1
- package/dist/server/forge/types.d.ts +25 -22
- package/dist/server/fs-browse.d.ts +3 -28
- package/dist/server/fs-browse.js.map +1 -1
- package/dist/server/git-changes.d.ts +5 -15
- package/dist/server/git-changes.js +3 -50
- package/dist/server/git-changes.js.map +1 -1
- package/dist/server/git.d.ts +3 -1
- package/dist/server/git.js +12 -0
- package/dist/server/git.js.map +1 -1
- package/dist/server/github.d.ts +1 -1
- package/dist/server/github.js +1 -1
- package/dist/server/github.js.map +1 -1
- package/dist/server/launch-key.d.ts +2 -4
- package/dist/server/launch-key.js +2 -4
- package/dist/server/launch-key.js.map +1 -1
- package/dist/server/live-routes.d.ts +68 -0
- package/dist/server/live-routes.js +147 -0
- package/dist/server/live-routes.js.map +1 -0
- package/dist/server/open-in-app.d.ts +4 -13
- package/dist/server/open-in-app.js +5 -41
- package/dist/server/open-in-app.js.map +1 -1
- package/dist/server/open-in-targets.d.ts +42 -0
- package/dist/server/open-in-targets.js +110 -0
- package/dist/server/open-in-targets.js.map +1 -0
- package/dist/server/open-in-terminal.d.ts +3 -0
- package/dist/server/open-in-terminal.js +6 -26
- package/dist/server/open-in-terminal.js.map +1 -1
- package/dist/server/preview-cdp-socket.d.ts +21 -0
- package/dist/server/preview-cdp-socket.js +112 -0
- package/dist/server/preview-cdp-socket.js.map +1 -0
- package/dist/server/preview-socket.d.ts +11 -11
- package/dist/server/preview-socket.js +22 -22
- package/dist/server/preview-socket.js.map +1 -1
- package/dist/server/preview-sockets.d.ts +14 -0
- package/dist/server/preview-sockets.js +18 -0
- package/dist/server/preview-sockets.js.map +1 -0
- package/dist/server/project-context.d.ts +54 -18
- package/dist/server/project-context.js +178 -5
- package/dist/server/project-context.js.map +1 -1
- package/dist/server/provider-action-gate.d.ts +0 -2
- package/dist/server/provider-action-gate.js +0 -21
- package/dist/server/provider-action-gate.js.map +1 -1
- package/dist/server/provider-auth-runtime.d.ts +3 -18
- package/dist/server/provider-auth-runtime.js +3 -18
- package/dist/server/provider-auth-runtime.js.map +1 -1
- package/dist/server/repo-branches.d.ts +3 -9
- package/dist/server/repo-branches.js +6 -22
- package/dist/server/repo-branches.js.map +1 -1
- package/dist/server/run-event-feed.d.ts +20 -0
- package/dist/server/run-event-feed.js +190 -0
- package/dist/server/run-event-feed.js.map +1 -0
- package/dist/server/server-deps.d.ts +37 -0
- package/dist/server/server-deps.js +114 -0
- package/dist/server/server-deps.js.map +1 -0
- package/dist/server/server.d.ts +14082 -11998
- package/dist/server/server.js +785 -1647
- package/dist/server/server.js.map +1 -1
- package/dist/server/start-run-options.d.ts +82 -0
- package/dist/server/start-run-options.js +111 -0
- package/dist/server/start-run-options.js.map +1 -0
- package/dist/server/validators.d.ts +3 -29
- package/dist/server/validators.js +1 -2
- package/dist/server/validators.js.map +1 -1
- package/dist/server/ws.d.ts +3 -23
- package/dist/server/ws.js +3 -23
- package/dist/server/ws.js.map +1 -1
- package/dist/server-install/engine.d.ts +6 -9
- package/dist/server-install/engine.js +2 -9
- package/dist/server-install/engine.js.map +1 -1
- package/dist/server-install/platforms/ubuntu-vps.d.ts +1 -1
- package/dist/server-install/state.d.ts +2 -4
- package/dist/server-install/state.js +2 -4
- package/dist/server-install/state.js.map +1 -1
- package/dist/server-install/steps.d.ts +2 -5
- package/dist/server-install/steps.js +3 -6
- package/dist/server-install/steps.js.map +1 -1
- package/dist/server-install/types.d.ts +3 -6
- package/dist/server-install/types.js +2 -5
- package/dist/server-install/types.js.map +1 -1
- package/dist/server-install/ui.d.ts +2 -8
- package/dist/server-install/ui.js +3 -3
- package/dist/server-install/ui.js.map +1 -1
- package/dist/session-context.d.ts +64 -0
- package/dist/session-context.js +264 -0
- package/dist/session-context.js.map +1 -0
- package/dist/skills-provision.d.ts +62 -0
- package/dist/skills-provision.js +325 -0
- package/dist/skills-provision.js.map +1 -0
- package/dist/skills-remote.d.ts +29 -27
- package/dist/skills-remote.js +236 -100
- package/dist/skills-remote.js.map +1 -1
- package/dist/skills.d.ts +25 -36
- package/dist/skills.js +43 -54
- package/dist/skills.js.map +1 -1
- package/dist/subcommands.d.ts +11 -0
- package/dist/subcommands.js +24 -0
- package/dist/subcommands.js.map +1 -0
- package/dist/task-cli/cli.js +78 -25
- package/dist/task-cli/cli.js.map +1 -1
- package/dist/task-cli/discovery.d.ts +4 -2
- package/dist/task-cli/discovery.js +14 -13
- package/dist/task-cli/discovery.js.map +1 -1
- package/dist/task-cli/hint.d.ts +20 -0
- package/dist/task-cli/hint.js +14 -0
- package/dist/task-cli/hint.js.map +1 -0
- package/dist/task-cli/http.d.ts +1 -0
- package/dist/task-cli/http.js +3 -1
- package/dist/task-cli/http.js.map +1 -1
- package/dist/task-cli/projections.d.ts +14 -11
- package/dist/task-cli/projections.js +15 -11
- package/dist/task-cli/projections.js.map +1 -1
- package/dist/task-cli/watch.d.ts +18 -10
- package/dist/task-cli/watch.js +128 -20
- package/dist/task-cli/watch.js.map +1 -1
- package/dist/ui-state.d.ts +17 -3
- package/dist/ui-state.js +56 -9
- package/dist/ui-state.js.map +1 -1
- package/dist/update-check.d.ts +0 -10
- package/dist/update-check.js +1 -16
- package/dist/update-check.js.map +1 -1
- package/dist/voice/decide.d.ts +32 -0
- package/dist/voice/decide.js +297 -0
- package/dist/voice/decide.js.map +1 -0
- package/dist/voice/decision-log.d.ts +52 -0
- package/dist/voice/decision-log.js +107 -0
- package/dist/voice/decision-log.js.map +1 -0
- package/dist/voice/engine.d.ts +119 -0
- package/dist/voice/engine.js +60 -0
- package/dist/voice/engine.js.map +1 -0
- package/dist/voice/fake-engine.d.ts +10 -0
- package/dist/voice/fake-engine.js +192 -0
- package/dist/voice/fake-engine.js.map +1 -0
- package/dist/voice/jev-engine.d.ts +17 -0
- package/dist/voice/jev-engine.js +110 -0
- package/dist/voice/jev-engine.js.map +1 -0
- package/dist/voice/pending-ask.d.ts +17 -0
- package/dist/voice/pending-ask.js +50 -0
- package/dist/voice/pending-ask.js.map +1 -0
- package/dist/voice/routes.d.ts +115 -0
- package/dist/voice/routes.js +91 -0
- package/dist/voice/routes.js.map +1 -0
- package/dist/voice/sentence.d.ts +21 -0
- package/dist/voice/sentence.js +61 -0
- package/dist/voice/sentence.js.map +1 -0
- package/dist/voice/thresholds.d.ts +28 -0
- package/dist/voice/thresholds.js +29 -0
- package/dist/voice/thresholds.js.map +1 -0
- package/dist/webhooks/cli.d.ts +18 -0
- package/dist/webhooks/cli.js +145 -0
- package/dist/webhooks/cli.js.map +1 -0
- package/dist/webhooks/prompt.d.ts +5 -0
- package/dist/webhooks/prompt.js +26 -0
- package/dist/webhooks/prompt.js.map +1 -0
- package/dist/workflows/active-run.d.ts +167 -0
- package/dist/workflows/active-run.js +21 -0
- package/dist/workflows/active-run.js.map +1 -0
- package/dist/workflows/continuation-admission.d.ts +913 -0
- package/dist/workflows/continuation-admission.js +265 -0
- package/dist/workflows/continuation-admission.js.map +1 -0
- package/dist/workflows/execution-account.d.ts +13 -0
- package/dist/workflows/execution-account.js +18 -0
- package/dist/workflows/execution-account.js.map +1 -0
- package/dist/workflows/input-acknowledgement.d.ts +152 -0
- package/dist/workflows/input-acknowledgement.js +38 -0
- package/dist/workflows/input-acknowledgement.js.map +1 -0
- package/dist/workflows/line-order.d.ts +5 -0
- package/dist/workflows/line-order.js +25 -0
- package/dist/workflows/line-order.js.map +1 -0
- package/dist/workflows/outcome-batches.d.ts +20 -0
- package/dist/workflows/outcome-batches.js +25 -0
- package/dist/workflows/outcome-batches.js.map +1 -0
- package/dist/workflows/question-routing.d.ts +8 -0
- package/dist/workflows/question-routing.js +18 -0
- package/dist/workflows/question-routing.js.map +1 -0
- package/dist/workflows/run-scheduling.d.ts +35 -0
- package/dist/workflows/run-scheduling.js +61 -0
- package/dist/workflows/run-scheduling.js.map +1 -0
- package/dist/workflows/run-states.d.ts +190 -0
- package/dist/workflows/run-states.js +295 -0
- package/dist/workflows/run-states.js.map +1 -0
- package/dist/workflows/run.d.ts +1296 -278
- package/dist/workflows/run.js +5730 -3265
- package/dist/workflows/run.js.map +1 -1
- package/dist/workflows/turn-end.d.ts +116 -0
- package/dist/workflows/turn-end.js +68 -0
- package/dist/workflows/turn-end.js.map +1 -0
- package/dist/workflows/types.d.ts +35 -130
- package/dist/workflows/types.js +47 -100
- package/dist/workflows/types.js.map +1 -1
- package/dist/workflows/waits.d.ts +74 -0
- package/dist/workflows/waits.js +197 -0
- package/dist/workflows/waits.js.map +1 -0
- package/dist/workspace/agent-accounts.d.ts +5 -38
- package/dist/workspace/agent-accounts.js +14 -40
- package/dist/workspace/agent-accounts.js.map +1 -1
- package/dist/workspace/agent-profiles.d.ts +40 -16
- package/dist/workspace/agent-profiles.js +77 -17
- package/dist/workspace/agent-profiles.js.map +1 -1
- package/dist/workspace/config.d.ts +48 -49
- package/dist/workspace/config.js +68 -72
- package/dist/workspace/config.js.map +1 -1
- package/dist/workspace/migrations.d.ts +3 -13
- package/dist/workspace/migrations.js.map +1 -1
- package/dist/workspace/projects-cli.d.ts +1 -1
- package/dist/workspace/projects-cli.js.map +1 -1
- package/dist/workspace/projects.d.ts +15 -18
- package/dist/workspace/projects.js +20 -16
- package/dist/workspace/projects.js.map +1 -1
- package/dist/workspace/semaphore.d.ts +11 -45
- package/dist/workspace/semaphore.js +6 -18
- package/dist/workspace/semaphore.js.map +1 -1
- package/dist/workspace/ui-state.d.ts +6 -4
- package/dist/workspace/ui-state.js +6 -4
- package/dist/workspace/ui-state.js.map +1 -1
- package/dist/workspace/webhooks.d.ts +40 -0
- package/dist/workspace/webhooks.js +146 -0
- package/dist/workspace/webhooks.js.map +1 -0
- package/dist/worktree-manifest.d.ts +165 -0
- package/dist/worktree-manifest.js +530 -0
- package/dist/worktree-manifest.js.map +1 -0
- package/dist/worktree-setup.js.map +1 -1
- package/package.json +6 -5
- package/scripts/benchmark-run-store-fixture.ts +7 -3
- package/scripts/check-pack.mjs +3 -1
- package/scripts/inline-contract.mjs +80 -27
- package/scripts/install-as-command.mjs +3 -1
- package/scripts/measure-transcript-facts-health.mjs +87 -0
- package/scripts/measure-transcript-facts-lag.mjs +68 -0
- package/scripts/mock-ci-tool.mjs +49 -6
- package/scripts/mock-claude-model-refusal.mjs +42 -0
- package/scripts/mock-claude.mjs +833 -591
- package/scripts/mock-codex-app-server.mjs +785 -418
- package/scripts/mock-cursor-acp.mjs +296 -125
- package/scripts/mock-cursor-print.mjs +256 -0
- package/scripts/mock-deferred-turn-start.mjs +16 -0
- package/scripts/mock-delivery-control.d.mts +23 -0
- package/scripts/mock-delivery-control.mjs +80 -0
- package/scripts/mock-env-record.mjs +11 -0
- package/scripts/mock-failure-control.d.mts +13 -0
- package/scripts/mock-failure-control.mjs +68 -0
- package/scripts/mock-multi-pr.mjs +7 -0
- package/scripts/mock-nudge.mjs +45 -0
- package/scripts/mock-omp-rpc.mjs +429 -255
- package/scripts/mock-opencode-serve.mjs +972 -557
- package/scripts/mock-pi-rpc.mjs +558 -331
- package/scripts/mock-runner-crash.mjs +18 -2
- package/scripts/mock-session-context.d.mts +5 -0
- package/scripts/mock-session-context.mjs +56 -0
- package/scripts/mock-silent-tail.mjs +10 -3
- package/scripts/pi-default-tools.mjs +22 -0
- package/scripts/pi-project-skills.mjs +59 -0
- package/scripts/probe-cursor-print.mjs +627 -0
- package/scripts/transcript-facts-fixture.mjs +53 -0
- package/scripts/voice-replay.ts +50 -0
- package/scripts/windows-workspace-identity.cs +208 -0
- package/scripts/windows-workspace-identity.ps1 +10 -0
- package/web/dist/assets/alert-dialog-DfctKUMw.js +1 -0
- package/web/dist/assets/button-CeupvoKG.js +41 -0
- package/web/dist/assets/centered-state-hDsyXrnH.js +1 -0
- package/web/dist/assets/cez-idle-D3aj_9lp.js +1 -0
- package/web/dist/assets/diff-BnIXZK3i2.js +3 -0
- package/web/dist/assets/diff-controls-GewpW9aF.js +1 -0
- package/web/dist/assets/diff-view-Byo7gpvW.js +11 -0
- package/web/dist/assets/dropdown-menu-COj_J5wd.js +6 -0
- package/web/dist/assets/eye-B8okNWXU.js +1 -0
- package/web/dist/assets/git-toolbar-DY0z0n4S.js +1 -0
- package/web/dist/assets/github-DwU6ALRK.js +1 -0
- package/web/dist/assets/{image-preview-FOEfqpZP.js → image-preview-C_t9q45f.js} +1 -1
- package/web/dist/assets/index-BOfE8pyD.js +34 -0
- package/web/dist/assets/index-D9HpsmdW.css +1 -0
- package/web/dist/assets/live-protocol-Uce2NwEX.js +64 -0
- package/web/dist/assets/live-worker-BizfZSoh.ts +33 -0
- package/web/dist/assets/markdown-BBDDg-4t.js +1 -0
- package/web/dist/assets/nav-row-styles-AoIhhIwf.js +1 -0
- package/web/dist/assets/popover-D56fhiXj.js +1 -0
- package/web/dist/assets/preview-pane-connected-BmVqfSU5.js +3 -0
- package/web/dist/assets/reference-chip-gQNk-4jc.js +1 -0
- package/web/dist/assets/repo-git-B5ECDUuv.js +1 -0
- package/web/dist/assets/{repo-git-CoKp-g9K.css → repo-git-DVXNOJDT.css} +1 -1
- package/web/dist/assets/run-header-C0Y1NvEM.js +2 -0
- package/web/dist/assets/run-header-CC7VUTg9.css +1 -0
- package/web/dist/assets/skills-DUeTkZ9z.js +1 -0
- package/web/dist/assets/status-dot-1PR0i69P.js +1 -0
- package/web/dist/assets/tab-link-DmWk4l5O.js +1 -0
- package/web/dist/assets/task-changes-DKZXuW89.js +1 -0
- package/web/dist/assets/task-commits-BZ6KxWdF.js +1 -0
- package/web/dist/assets/task-files-DlF7Apcn.js +2 -0
- package/web/dist/assets/task-github-item-CVPo3geZ.js +1 -0
- package/web/dist/assets/task-thread-DtQ6uGfQ.css +1 -0
- package/web/dist/assets/task-thread-mm4-ECyo.js +9 -0
- package/web/dist/assets/thread-state-CIw2BlhT.js +4 -0
- package/web/dist/assets/use-hand-to-agent-state-BCDjzHnG.js +1 -0
- package/web/dist/assets/use-hand-to-agent-state-C7esPJ-N.css +1 -0
- package/web/dist/assets/workflows-D8vYD-ql.js +7 -0
- package/web/dist/assets/zoomable-image-CxQSsfcn.js +1 -0
- package/web/dist/index.html +13 -17
- package/web/dist/live-worker.js +4 -0
- package/dist/core/tool-display.js +0 -179
- package/dist/core/tool-display.js.map +0 -1
- package/dist/runs/human-ask-summary.d.ts +0 -9
- package/dist/runs/human-ask-summary.js +0 -39
- package/dist/runs/human-ask-summary.js.map +0 -1
- package/dist/runs/run-search.d.ts +0 -29
- package/dist/runs/run-search.js +0 -63
- package/dist/runs/run-search.js.map +0 -1
- package/dist/skills-banner.d.ts +0 -22
- package/dist/skills-banner.js +0 -41
- package/dist/skills-banner.js.map +0 -1
- package/dist/skills-update.d.ts +0 -88
- package/dist/skills-update.js +0 -392
- package/dist/skills-update.js.map +0 -1
- package/dist/todos.d.ts +0 -48
- package/dist/todos.js +0 -201
- package/dist/todos.js.map +0 -1
- package/web/dist/assets/agent-accounts-CFOBMVHc.js +0 -1
- package/web/dist/assets/alert-dialog-BrpNUwMt.js +0 -1
- package/web/dist/assets/button-C_rd7CPp.js +0 -43
- package/web/dist/assets/compare-variants-D6WXhvXA.js +0 -1
- package/web/dist/assets/diff-DrEARXTV.js +0 -3
- package/web/dist/assets/diff-controls-B1-j7nFg.js +0 -1
- package/web/dist/assets/diff-view-DvGLjdTk.js +0 -6
- package/web/dist/assets/dropdown-menu-BegOZFrj.js +0 -1
- package/web/dist/assets/format-DpNe3S0A.js +0 -1
- package/web/dist/assets/git-toolbar-Cw4ljmhB.js +0 -1
- package/web/dist/assets/github-CfV5sqL0.js +0 -1
- package/web/dist/assets/index-DgWWlOLy.js +0 -13
- package/web/dist/assets/index-fBFnuvxu.css +0 -1
- package/web/dist/assets/nav-row-styles-vp2JbhuJ.js +0 -1
- package/web/dist/assets/popover-BohP6WTx.js +0 -1
- package/web/dist/assets/preview-pane-connected-qTA5KNoJ.js +0 -3
- package/web/dist/assets/preview-state-vuMviq6q.js +0 -1
- package/web/dist/assets/prompt-templates-Cubdut_C.js +0 -15
- package/web/dist/assets/repo-git-Cf4t5PD0.js +0 -1
- package/web/dist/assets/run-diff-BpkUW37Z.js +0 -3
- package/web/dist/assets/run-header-68WHf9wV.js +0 -1
- package/web/dist/assets/run-header-8GObNx6F.css +0 -1
- package/web/dist/assets/search-x-I9Q8pgUa.js +0 -1
- package/web/dist/assets/skills-DFBiAuJG.js +0 -1
- package/web/dist/assets/skills-DiVIgKeU.js +0 -1
- package/web/dist/assets/square-terminal-B6RYYieR.js +0 -1
- package/web/dist/assets/status-dot-CqTJHSP9.js +0 -1
- package/web/dist/assets/tab-link-B07Hu4QX.js +0 -1
- package/web/dist/assets/task-changes--lQF6EYf.js +0 -1
- package/web/dist/assets/task-commits-Cx0qy4DN.js +0 -1
- package/web/dist/assets/task-files-D50s-gKW.js +0 -2
- package/web/dist/assets/task-github-item-DaqmSwYT.js +0 -1
- package/web/dist/assets/task-thread-BPQ64lui.js +0 -7
- package/web/dist/assets/task-thread-zNE2Q9E-.css +0 -1
- package/web/dist/assets/textarea-D20O4Pw0.js +0 -1
- package/web/dist/assets/thread-state-DWcFMrvb.js +0 -4
- package/web/dist/assets/use-hand-to-agent-state-BLIGmwbd.css +0 -1
- package/web/dist/assets/use-hand-to-agent-state-Cy9eyyfL.js +0 -1
- package/web/dist/assets/utils-JsL3ZsJf.js +0 -64
- package/web/dist/assets/workflows-jwG7uxqi.js +0 -11
- package/web/dist/assets/zoomable-image-BJJ64KGc.js +0 -1
|
@@ -1,9 +1,15 @@
|
|
|
1
|
+
import { defined } from '../../core/defined.js';
|
|
2
|
+
import { activeWorkflowRunsArgs, parseActiveWorkflowRuns } from '../../ci-wait/github.js';
|
|
3
|
+
import { exactLineIndex, nextLineIndex } from '../../contract/index.js';
|
|
4
|
+
import { pullRequestKey } from '../../runs/pull-requests.js';
|
|
5
|
+
import { matchesGithubPullRequest, mergedPullRequestObservations } from '../../merge-observations.js';
|
|
1
6
|
import { randomUUID } from 'node:crypto';
|
|
2
7
|
import { execFile } from 'node:child_process';
|
|
3
8
|
import { promisify } from 'node:util';
|
|
4
9
|
import { z } from 'zod';
|
|
5
|
-
import { GITHUB_SEARCH_MAX, REFERENCE_STATUS_MAX } from '../../contract/index.js';
|
|
6
|
-
import { fetchIssueProjects, fetchViewerLogin } from './github-filters.js';
|
|
10
|
+
import { GITHUB_COMMENT_COUNTS_MAX, GITHUB_SEARCH_MAX, REFERENCE_STATUS_MAX } from '../../contract/index.js';
|
|
11
|
+
import { fetchIssueProjects, fetchRepositoryProjects, fetchViewerLogin } from './github-filters.js';
|
|
12
|
+
import { fetchGithubMergeRequirements } from './github-merge-requirements.js';
|
|
7
13
|
import { autosaveCommit } from '../../git-worktree.js';
|
|
8
14
|
/**
|
|
9
15
|
* The GitHub forge driver — all `gh`-CLI logic in one place, moved here from
|
|
@@ -168,11 +174,14 @@ const ghPrViewSchema = z.object({
|
|
|
168
174
|
number: z.number(),
|
|
169
175
|
url: z.string(),
|
|
170
176
|
state: z.string().default('OPEN'),
|
|
177
|
+
mergedAt: z.unknown().optional(),
|
|
171
178
|
isDraft: z.boolean().default(false),
|
|
172
179
|
statusCheckRollup: ghStatusCheckRollup,
|
|
173
180
|
});
|
|
174
181
|
const mergeCheckSchema = z.object({
|
|
175
|
-
name: z.string().
|
|
182
|
+
name: z.string().optional(),
|
|
183
|
+
context: z.string().optional(),
|
|
184
|
+
targetUrl: z.string().nullish(),
|
|
176
185
|
state: z.string().nullish(),
|
|
177
186
|
status: z.string().nullish(),
|
|
178
187
|
conclusion: z.string().nullish(),
|
|
@@ -183,6 +192,7 @@ const mergePrSchema = z.object({
|
|
|
183
192
|
title: z.string(),
|
|
184
193
|
url: z.string(),
|
|
185
194
|
state: z.string(),
|
|
195
|
+
mergedAt: z.unknown().optional(),
|
|
186
196
|
isDraft: z.boolean().default(false),
|
|
187
197
|
headRefName: z.string(),
|
|
188
198
|
baseRefName: z.string(),
|
|
@@ -232,112 +242,183 @@ async function gh(repoRoot, args, timeout = 15_000, signal, context) {
|
|
|
232
242
|
});
|
|
233
243
|
return stdout;
|
|
234
244
|
}
|
|
235
|
-
/**
|
|
236
|
-
*
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
245
|
+
/** `gh api graphql` argv. `-f` sends every value as a String, and GitHub rejects a String where
|
|
246
|
+
* the query declares `Int!` ("Could not coerce value to Int", open-mercato/cezar#1366), so a
|
|
247
|
+
* number goes through `-F`. A string never does: `-F` would read `@file` and coerce
|
|
248
|
+
* `true`/numerals, which is not what an opaque cursor or a login means. */
|
|
249
|
+
export function ghGraphqlArgs(query, variables = {}) {
|
|
250
|
+
const args = ['api', 'graphql', '-f', `query=${query}`];
|
|
251
|
+
for (const [key, value] of Object.entries(variables)) {
|
|
252
|
+
args.push(typeof value === 'number' ? '-F' : '-f', `${key}=${value}`);
|
|
253
|
+
}
|
|
254
|
+
return args;
|
|
255
|
+
}
|
|
256
|
+
/** `owner/name` → `{ owner, name }`, or null when the handle isn't a clean two-part slug. */
|
|
257
|
+
export function parseOwnerName(nameWithOwner) {
|
|
258
|
+
const [owner, name, ...rest] = nameWithOwner.trim().split('/');
|
|
259
|
+
return owner && name && rest.length === 0 ? { owner, name } : null;
|
|
260
|
+
}
|
|
261
|
+
// Paged open lists defer comment counts; hydrate them through the counts route after paint.
|
|
262
|
+
/** GitHub GraphQL rejects `first` above 100 (EXCESSIVE_PAGINATION); larger limits walk pages. */
|
|
263
|
+
export const GH_GRAPHQL_PAGE_MAX = 100;
|
|
264
|
+
// Labels up to 100, which is what `gh issue list --json labels` read before the GraphQL page.
|
|
265
|
+
const LIST_NODE_FIELDS = 'number title author { login } createdAt url body labels(first: 100) { nodes { name color } }';
|
|
266
|
+
const listQuery = (side) => `
|
|
267
|
+
query ($owner: String!, $name: String!, $first: Int!, $after: String) {
|
|
240
268
|
repository(owner: $owner, name: $name) {
|
|
241
|
-
${
|
|
242
|
-
|
|
243
|
-
nodes { number comments { totalCount } }
|
|
269
|
+
${side}(first: $first, after: $after, states: OPEN, orderBy: {field: CREATED_AT, direction: DESC}) {
|
|
270
|
+
totalCount
|
|
244
271
|
pageInfo { hasNextPage endCursor }
|
|
272
|
+
nodes { ${LIST_NODE_FIELDS} ${side === 'issues' ? 'assignees(first: 20) { nodes { login } }' : 'isDraft additions deletions'} }
|
|
245
273
|
}
|
|
246
274
|
}
|
|
247
275
|
}`;
|
|
248
|
-
|
|
249
|
-
|
|
276
|
+
/** A GraphQL connection (`{ nodes }`) read as the flat array `gh … --json` used to return. */
|
|
277
|
+
const connectionOf = (item) => z.object({ nodes: z.array(item.nullable()).default([]) }).nullish()
|
|
278
|
+
.transform((connection) => (connection?.nodes ?? []).filter((node) => node != null));
|
|
279
|
+
const ghListNodeSchema = z.object({
|
|
280
|
+
number: z.number(),
|
|
281
|
+
title: z.string(),
|
|
282
|
+
author: ghAuthor,
|
|
283
|
+
createdAt: z.string(),
|
|
284
|
+
url: z.string(),
|
|
285
|
+
body: z.string().nullish(),
|
|
286
|
+
labels: connectionOf(ghLabel),
|
|
287
|
+
assignees: connectionOf(z.object({ login: z.string() })),
|
|
288
|
+
isDraft: z.boolean().nullish(),
|
|
289
|
+
additions: z.number().nullish(),
|
|
290
|
+
deletions: z.number().nullish(),
|
|
291
|
+
});
|
|
292
|
+
const ghListConnectionSchema = z.object({
|
|
293
|
+
totalCount: z.number().int().nonnegative(),
|
|
250
294
|
pageInfo: z.object({ hasNextPage: z.boolean(), endCursor: z.string().nullish() }),
|
|
295
|
+
nodes: z.array(ghListNodeSchema.nullable()),
|
|
251
296
|
});
|
|
252
|
-
|
|
253
|
-
* cursor. Exported for unit tests (the zod boundary + shape). Throws on a malformed envelope. */
|
|
254
|
-
export function parseCountsPage(out, root) {
|
|
297
|
+
function parseListConnection(out, side) {
|
|
255
298
|
const parsed = JSON.parse(out);
|
|
256
|
-
const
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
299
|
+
const message = parsed.errors?.map((e) => e.message).filter(Boolean).join(' ');
|
|
300
|
+
if (message)
|
|
301
|
+
throw new Error(message);
|
|
302
|
+
if (!parsed.data?.repository)
|
|
303
|
+
throw new Error('GitHub did not resolve this repository');
|
|
304
|
+
return ghListConnectionSchema.parse(parsed.data.repository[side]);
|
|
261
305
|
}
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
const
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
306
|
+
/**
|
|
307
|
+
* One side of the open list: up to `limit` rows from `cursor`, in requests of at most
|
|
308
|
+
* `GH_GRAPHQL_PAGE_MAX`. `nextCursor` is where the rows stop, or `null` once the side is
|
|
309
|
+
* exhausted. `done` asks for `first: 0`, which still answers `totalCount`, so an exhausted side
|
|
310
|
+
* keeps a fresh total without ever restarting from its first page (open-mercato/cezar#1366
|
|
311
|
+
* restarted it while the other side paged).
|
|
312
|
+
*/
|
|
313
|
+
export async function fetchListConnection(run, owner, name, side, limit, cursor, done = false) {
|
|
314
|
+
const query = listQuery(side);
|
|
315
|
+
if (done) {
|
|
316
|
+
const page = parseListConnection(await run(query, { owner, name, first: 0 }), side);
|
|
317
|
+
return { totalCount: page.totalCount, nodes: [], nextCursor: null };
|
|
318
|
+
}
|
|
319
|
+
const nodes = [];
|
|
320
|
+
let after = cursor;
|
|
321
|
+
for (;;) {
|
|
322
|
+
const first = Math.min(GH_GRAPHQL_PAGE_MAX, limit - nodes.length);
|
|
323
|
+
const page = parseListConnection(await run(query, { owner, name, first, ...(after ? { after } : {}) }), side);
|
|
324
|
+
const rows = page.nodes.filter((node) => node !== null);
|
|
325
|
+
nodes.push(...rows);
|
|
326
|
+
const next = page.pageInfo.hasNextPage ? page.pageInfo.endCursor ?? null : null;
|
|
327
|
+
// An empty page that claims more would loop forever; read it as the end of the side.
|
|
328
|
+
if (next === null || rows.length === 0)
|
|
329
|
+
return { totalCount: page.totalCount, nodes, nextCursor: null };
|
|
330
|
+
if (next === after)
|
|
331
|
+
throw new Error('GitHub list cursor did not advance');
|
|
332
|
+
// A short page with more behind it still ends this request: the cursor resumes it exactly.
|
|
333
|
+
if (nodes.length >= limit || rows.length < first)
|
|
334
|
+
return { totalCount: page.totalCount, nodes, nextCursor: next };
|
|
335
|
+
after = next;
|
|
290
336
|
}
|
|
291
337
|
}
|
|
292
|
-
/** `owner/name` → `{ owner, name }`, or null when the handle isn't a clean two-part slug. */
|
|
293
|
-
export function parseOwnerName(nameWithOwner) {
|
|
294
|
-
const [owner, name, ...rest] = nameWithOwner.trim().split('/');
|
|
295
|
-
return owner && name && rest.length === 0 ? { owner, name } : null;
|
|
296
|
-
}
|
|
297
338
|
/* Reads degrade to `available: false` with a hint — never an error (plan rule
|
|
298
339
|
7): no `gh`, no remote, offline all land on the same quiet path. A short
|
|
299
340
|
cache keeps tab switches from hammering the GitHub API; a cached fetch with
|
|
300
341
|
a bigger limit than asked serves fine (it's a superset). Keyed by repository/host/account context
|
|
301
|
-
(multi-project workspace, step 2.6): one project's — possibly private —
|
|
302
|
-
issues/PRs must never be served under another project's scope
|
|
303
|
-
`commentsCache` so an unbounded workspace can't grow it without limit. */
|
|
342
|
+
(multi-project workspace, step 2.6) plus the page asked for: one project's — possibly private —
|
|
343
|
+
issues/PRs must never be served under another project's scope, and page 2 is never page 1.
|
|
344
|
+
Bounded like `commentsCache` so an unbounded workspace can't grow it without limit. */
|
|
304
345
|
let listRequestOrder = 0;
|
|
346
|
+
/** Per context, the order of the newest page-1 refresh. A refresh drops the context's later
|
|
347
|
+
* pages (their cursors were cut from the old first page), and a later page that started before
|
|
348
|
+
* it may answer its caller but never re-enters the cache. Bounded with `listCache`. */
|
|
349
|
+
const listRefreshOrder = new Map();
|
|
305
350
|
const listCache = new Map();
|
|
306
351
|
const LIST_CACHE_MAX = 50;
|
|
307
352
|
const CACHE_MS = 60_000;
|
|
353
|
+
/** Which page key holds a projects generation, so `fetchGithubProjects` never scans the cache.
|
|
354
|
+
* Installed and evicted in step with `listCache`, so it shares its bound. */
|
|
355
|
+
const projectGenerations = new Map();
|
|
356
|
+
/** Board discovery (the `projectsV2` walk) per context: page 2 pays for membership only. A
|
|
357
|
+
* failed discovery is dropped, so the next page or refresh asks again. */
|
|
358
|
+
const repositoryProjectsCache = new Map();
|
|
359
|
+
function dropListEntry(key) {
|
|
360
|
+
const generation = listCache.get(key)?.data.projectsGeneration;
|
|
361
|
+
if (generation && projectGenerations.get(generation) === key)
|
|
362
|
+
projectGenerations.delete(generation);
|
|
363
|
+
listCache.delete(key);
|
|
364
|
+
}
|
|
365
|
+
function discoverRepositoryProjects(contextKey, refresh, start) {
|
|
366
|
+
const hit = repositoryProjectsCache.get(contextKey);
|
|
367
|
+
if (!refresh && hit && Date.now() - hit.at < CACHE_MS)
|
|
368
|
+
return hit.projects;
|
|
369
|
+
const projects = start();
|
|
370
|
+
const entry = { at: Date.now(), projects };
|
|
371
|
+
repositoryProjectsCache.delete(contextKey);
|
|
372
|
+
repositoryProjectsCache.set(contextKey, entry);
|
|
373
|
+
projects.catch(() => {
|
|
374
|
+
if (repositoryProjectsCache.get(contextKey) === entry)
|
|
375
|
+
repositoryProjectsCache.delete(contextKey);
|
|
376
|
+
});
|
|
377
|
+
while (repositoryProjectsCache.size > LIST_CACHE_MAX) {
|
|
378
|
+
const oldest = repositoryProjectsCache.keys().next().value;
|
|
379
|
+
if (oldest === undefined)
|
|
380
|
+
break;
|
|
381
|
+
repositoryProjectsCache.delete(oldest);
|
|
382
|
+
}
|
|
383
|
+
return projects;
|
|
384
|
+
}
|
|
308
385
|
export const GH_MAX_LIMIT = 1000;
|
|
309
|
-
|
|
386
|
+
function listPageKey(contextKey, page) {
|
|
387
|
+
return JSON.stringify([contextKey, page.issuesCursor ?? null, page.prsCursor ?? null, !!page.issuesDone, !!page.prsDone]);
|
|
388
|
+
}
|
|
389
|
+
export async function fetchGithub(repoRoot, refresh = false, limit = 30, page = {}) {
|
|
310
390
|
if (process.env.CEZ_DRY_RUN === '1')
|
|
311
|
-
return
|
|
391
|
+
return mockGithubPage(limit, page);
|
|
312
392
|
const context = githubContext(repoRoot);
|
|
313
393
|
const capped = Math.min(Math.max(limit, 1), GH_MAX_LIMIT);
|
|
314
|
-
const
|
|
394
|
+
const key = listPageKey(context.key, page);
|
|
395
|
+
const hit = listCache.get(key);
|
|
315
396
|
if (!refresh && hit && Date.now() - hit.at < CACHE_MS && hit.limit >= capped) {
|
|
316
397
|
return hit.data;
|
|
317
398
|
}
|
|
318
399
|
const order = ++listRequestOrder;
|
|
400
|
+
if (refresh && !page.issuesCursor && !page.prsCursor && !page.issuesDone && !page.prsDone) {
|
|
401
|
+
listRefreshOrder.delete(context.key);
|
|
402
|
+
listRefreshOrder.set(context.key, order);
|
|
403
|
+
while (listRefreshOrder.size > LIST_CACHE_MAX) {
|
|
404
|
+
const oldest = listRefreshOrder.keys().next().value;
|
|
405
|
+
if (oldest === undefined)
|
|
406
|
+
break;
|
|
407
|
+
listRefreshOrder.delete(oldest);
|
|
408
|
+
}
|
|
409
|
+
for (const [cached, entry] of [...listCache]) {
|
|
410
|
+
if (entry.contextKey === context.key && cached !== key)
|
|
411
|
+
dropListEntry(cached);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
319
414
|
try {
|
|
320
|
-
// No `
|
|
321
|
-
//
|
|
322
|
-
// dominant cost — it forced the 60 s budget below — and is now hydrated lazily per
|
|
323
|
-
// on-screen PR row via `fetchGithubChecks`. A big list still gets a little more wall
|
|
324
|
-
// clock than the default 30, but nothing like the old rollup walk.
|
|
415
|
+
// No `statusCheckRollup` (#664): it is hydrated lazily per on-screen PR row via
|
|
416
|
+
// `fetchGithubChecks`. A big aggregate list still gets a little more wall clock.
|
|
325
417
|
const timeout = capped > 100 ? 30_000 : 15_000;
|
|
326
|
-
const fields = 'number,title,author,createdAt,labels,body,url';
|
|
327
|
-
// The repo handle first (cheap) so the counts GraphQL query — which needs owner/name —
|
|
328
|
-
// can run parallel to the two expensive list calls below.
|
|
329
418
|
const ownerName = await resolveRepoHandleStrict(repoRoot, context);
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
args.push('-f', `${key}=${value}`);
|
|
334
|
-
return gh(repoRoot, args, timeout, undefined, context);
|
|
335
|
-
};
|
|
336
|
-
// Bound the counts pagination to the rows actually being fetched: a page is 100, so
|
|
337
|
-
// `ceil(capped / 100)` pages (still capped at GH_COUNTS_MAX_PAGES) cover exactly the visible
|
|
338
|
-
// window and no more — the default 30-item load pays ONE counts round-trip, not ten. Rows
|
|
339
|
-
// beyond the window keep `comments: 0`, which the UI reads as "no badge" (same as before).
|
|
340
|
-
const countsMaxPages = Math.min(GH_COUNTS_MAX_PAGES, Math.max(1, Math.ceil(capped / 100)));
|
|
419
|
+
if (!ownerName)
|
|
420
|
+
return { available: false, reason: 'Could not resolve this repository on GitHub.', issues: [], prs: [] };
|
|
421
|
+
const runGraphql = (query, variables) => gh(repoRoot, ghGraphqlArgs(query, variables), timeout, undefined, context);
|
|
341
422
|
// Optional project queries have one shared wall-clock budget. A missing scope, a cap,
|
|
342
423
|
// or malformed metadata must never turn an otherwise working list into unavailable.
|
|
343
424
|
const deadline = Date.now() + 15_000;
|
|
@@ -345,24 +426,18 @@ export async function fetchGithub(repoRoot, refresh = false, limit = 30) {
|
|
|
345
426
|
const remaining = deadline - Date.now();
|
|
346
427
|
if (remaining <= 0)
|
|
347
428
|
return Promise.reject(new Error('Project lookup timed out'));
|
|
348
|
-
|
|
349
|
-
for (const [key, value] of Object.entries(variables))
|
|
350
|
-
args.push('-f', `${key}=${value}`);
|
|
351
|
-
return gh(repoRoot, args, Math.min(remaining, 8_000), undefined, context);
|
|
429
|
+
return gh(repoRoot, ghGraphqlArgs(query, variables), Math.min(remaining, 8_000), undefined, context);
|
|
352
430
|
};
|
|
353
|
-
const
|
|
354
|
-
const
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
? fetchCommentCounts(runGraphql, ownerName.owner, ownerName.name, countsMaxPages)
|
|
364
|
-
: Promise.resolve({ issues: {}, prs: {} }),
|
|
365
|
-
fetchViewerLogin((query) => gh(repoRoot, ['api', 'graphql', '-f', `query=${query}`], 8_000, undefined, context)),
|
|
431
|
+
const firstPage = !page.issuesCursor && !page.prsCursor && !page.issuesDone && !page.prsDone;
|
|
432
|
+
const issueSide = fetchListConnection(runGraphql, ownerName.owner, ownerName.name, 'issues', capped, page.issuesCursor, page.issuesDone);
|
|
433
|
+
const projectLookup = fetchIssueProjects(projectGraphql, ownerName.owner, ownerName.name, async () => (await issueSide).nodes.map((node) => node.number), () => discoverRepositoryProjects(context.key, refresh, () => fetchRepositoryProjects(projectGraphql, ownerName.owner, ownerName.name)));
|
|
434
|
+
const [issuesPage, prsPage, viewerLogin] = await Promise.all([
|
|
435
|
+
issueSide,
|
|
436
|
+
fetchListConnection(runGraphql, ownerName.owner, ownerName.name, 'pullRequests', capped, page.prsCursor, page.prsDone),
|
|
437
|
+
// The viewer is the same on every page; only the first one asks.
|
|
438
|
+
firstPage
|
|
439
|
+
? fetchViewerLogin((query) => gh(repoRoot, ghGraphqlArgs(query), 8_000, undefined, context))
|
|
440
|
+
: Promise.resolve(undefined),
|
|
366
441
|
]);
|
|
367
442
|
// One repo-wide label→color map, filled as we flatten each item's labels.
|
|
368
443
|
const labelColors = {};
|
|
@@ -370,7 +445,7 @@ export async function fetchGithub(repoRoot, refresh = false, limit = 30) {
|
|
|
370
445
|
if (l.color && !labelColors[l.name])
|
|
371
446
|
labelColors[l.name] = l.color;
|
|
372
447
|
};
|
|
373
|
-
const issues =
|
|
448
|
+
const issues = issuesPage.nodes.map((i) => {
|
|
374
449
|
i.labels.forEach(recordColor);
|
|
375
450
|
return {
|
|
376
451
|
kind: 'issue',
|
|
@@ -381,32 +456,33 @@ export async function fetchGithub(repoRoot, refresh = false, limit = 30) {
|
|
|
381
456
|
labels: i.labels.map((l) => l.name),
|
|
382
457
|
body: (i.body ?? '').slice(0, 8_000),
|
|
383
458
|
url: i.url,
|
|
384
|
-
comments:
|
|
459
|
+
comments: 0,
|
|
385
460
|
assignees: i.assignees.map(a => a.login),
|
|
386
461
|
};
|
|
387
462
|
});
|
|
388
|
-
const prs =
|
|
463
|
+
const prs = prsPage.nodes.map((p) => {
|
|
389
464
|
p.labels.forEach(recordColor);
|
|
465
|
+
const isDraft = p.isDraft ?? false;
|
|
390
466
|
return {
|
|
391
467
|
kind: 'pr',
|
|
392
468
|
number: p.number,
|
|
393
469
|
title: p.title,
|
|
394
470
|
author: p.author?.login ?? '?',
|
|
395
471
|
createdAt: p.createdAt,
|
|
396
|
-
labels: [...p.labels.map((l) => l.name), ...(
|
|
472
|
+
labels: [...p.labels.map((l) => l.name), ...(isDraft ? ['draft'] : [])],
|
|
397
473
|
body: (p.body ?? '').slice(0, 8_000),
|
|
398
474
|
url: p.url,
|
|
399
|
-
comments:
|
|
400
|
-
isDraft
|
|
401
|
-
additions: p.additions,
|
|
402
|
-
deletions: p.deletions,
|
|
475
|
+
comments: 0,
|
|
476
|
+
isDraft,
|
|
477
|
+
additions: p.additions ?? 0,
|
|
478
|
+
deletions: p.deletions ?? 0,
|
|
403
479
|
// Hydrated lazily by `fetchGithubChecks` for on-screen rows (#664) — the list no
|
|
404
480
|
// longer pays for the CI rollup of every open PR.
|
|
405
481
|
checks: null,
|
|
406
482
|
};
|
|
407
483
|
});
|
|
408
484
|
// Only verified values from the same repository may survive a refresh.
|
|
409
|
-
const previous =
|
|
485
|
+
const previous = hit?.data.repo === `${ownerName.owner}/${ownerName.name}` ? hit.data : undefined;
|
|
410
486
|
for (const issue of issues) {
|
|
411
487
|
const known = previous?.issues.find(row => row.number === issue.number)?.projectIds;
|
|
412
488
|
if (known !== undefined)
|
|
@@ -415,7 +491,7 @@ export async function fetchGithub(repoRoot, refresh = false, limit = 30) {
|
|
|
415
491
|
const generation = randomUUID();
|
|
416
492
|
const data = {
|
|
417
493
|
available: true,
|
|
418
|
-
|
|
494
|
+
repo: `${ownerName.owner}/${ownerName.name}`,
|
|
419
495
|
syncedAt: new Date().toISOString(),
|
|
420
496
|
issues,
|
|
421
497
|
prs,
|
|
@@ -424,12 +500,17 @@ export async function fetchGithub(repoRoot, refresh = false, limit = 30) {
|
|
|
424
500
|
projectsState: 'refreshing',
|
|
425
501
|
projectsGeneration: generation,
|
|
426
502
|
...(previous?.projects ? { projects: previous.projects } : {}),
|
|
503
|
+
issuesTotal: issuesPage.totalCount,
|
|
504
|
+
prsTotal: prsPage.totalCount,
|
|
505
|
+
issuesNextCursor: issuesPage.nextCursor,
|
|
506
|
+
prsNextCursor: prsPage.nextCursor,
|
|
507
|
+
commentsDeferred: true,
|
|
427
508
|
};
|
|
428
509
|
const projects = projectLookup.then((result) => {
|
|
429
510
|
const metadata = 'membership' in result
|
|
430
511
|
? { generation, state: 'ready', projects: result.projects, membership: result.membership }
|
|
431
512
|
: { generation, state: 'unavailable', reason: result.projectsReason };
|
|
432
|
-
const current = listCache.get(
|
|
513
|
+
const current = listCache.get(key);
|
|
433
514
|
if (current?.data.projectsGeneration === generation) {
|
|
434
515
|
current.data = {
|
|
435
516
|
...current.data,
|
|
@@ -444,30 +525,50 @@ export async function fetchGithub(repoRoot, refresh = false, limit = 30) {
|
|
|
444
525
|
});
|
|
445
526
|
// Concurrent refreshes may finish in reverse order. An older list must not evict
|
|
446
527
|
// the newer generation (and invalidate the newer client's metadata follow-up).
|
|
447
|
-
if ((listCache.get(
|
|
448
|
-
|
|
449
|
-
listCache.set(context.key,
|
|
528
|
+
if ((listCache.get(key)?.order ?? -1) < order && order >= (listRefreshOrder.get(context.key) ?? 0)) {
|
|
529
|
+
dropListEntry(key); // re-insert so this key becomes the newest
|
|
530
|
+
listCache.set(key, { contextKey: context.key, order, at: Date.now(), limit: capped, data, projects });
|
|
531
|
+
projectGenerations.set(generation, key);
|
|
450
532
|
}
|
|
451
533
|
while (listCache.size > LIST_CACHE_MAX) {
|
|
452
534
|
const oldest = listCache.keys().next().value;
|
|
453
535
|
if (oldest === undefined)
|
|
454
536
|
break;
|
|
455
|
-
|
|
537
|
+
dropListEntry(oldest);
|
|
456
538
|
}
|
|
457
539
|
return data;
|
|
458
540
|
}
|
|
459
541
|
catch (err) {
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
542
|
+
return { available: false, reason: ghFailureReason(err), issues: [], prs: [] };
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
/** A `gh` failure as one readable line. GitHub's own words come on stderr; the Node message
|
|
546
|
+
* starts with the whole argv, and a GraphQL argv is a multi-line query, so its first line is
|
|
547
|
+
* a fragment of that query rather than the reason (open-mercato/cezar#1366). */
|
|
548
|
+
export function ghFailureReason(err) {
|
|
549
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
550
|
+
if (/ENOENT/.test(message))
|
|
551
|
+
return 'gh CLI not found — install it and run `gh auth login`';
|
|
552
|
+
const failure = (err ?? {});
|
|
553
|
+
const stderr = typeof failure.stderr === 'string' ? failure.stderr : '';
|
|
554
|
+
if (stderr.trim()) {
|
|
555
|
+
const lines = stderr.split('\n').map((line) => line.trim()).filter(Boolean);
|
|
556
|
+
return lines.find((line) => !/^Command failed:/i.test(line)) ?? lines[0];
|
|
557
|
+
}
|
|
558
|
+
if (failure.killed === true || typeof failure.signal === 'string')
|
|
559
|
+
return 'GitHub did not answer in time. Refresh to try again.';
|
|
560
|
+
// Without stderr, everything after "Command failed:" is the argv, which is query text.
|
|
561
|
+
if (/^Command failed:/i.test(message))
|
|
562
|
+
return 'GitHub request failed. Refresh to try again.';
|
|
563
|
+
return message.split('\n').map((line) => line.trim()).find(Boolean) ?? 'gh failed';
|
|
466
564
|
}
|
|
467
565
|
/** Bounded lookup already started by the list. Never launch work for an expired generation. */
|
|
468
566
|
export async function fetchGithubProjects(repoRoot, generation) {
|
|
469
|
-
const
|
|
470
|
-
|
|
567
|
+
const key = projectGenerations.get(generation);
|
|
568
|
+
const entry = key === undefined ? undefined : listCache.get(key);
|
|
569
|
+
// A generation from another repository, host or account never resolves here, even when
|
|
570
|
+
// its UUID is known: the context is part of what the generation was issued for.
|
|
571
|
+
if (!entry || entry.contextKey !== githubContext(repoRoot).key || entry.data.projectsGeneration !== generation) {
|
|
471
572
|
return { generation, state: 'unavailable', reason: 'Project metadata expired. Refresh to try again.' };
|
|
472
573
|
}
|
|
473
574
|
return entry.projects;
|
|
@@ -475,14 +576,7 @@ export async function fetchGithubProjects(repoRoot, generation) {
|
|
|
475
576
|
function firstLine(s) {
|
|
476
577
|
return s.split('\n').find((l) => l.trim().length > 0)?.trim() ?? 'gh failed';
|
|
477
578
|
}
|
|
478
|
-
//
|
|
479
|
-
// `fetchGithub` above lists the OPEN set only (`gh issue/pr list` defaults to `--state open`),
|
|
480
|
-
// and the tab's search is an in-memory filter over exactly that payload — so a closed or merged
|
|
481
|
-
// item is not "past the window", it was never fetched at all and no amount of scrolling reaches
|
|
482
|
-
// it. This tier asks GitHub instead of re-filtering what we already hold. It is deliberately a
|
|
483
|
-
// SECOND, on-demand path rather than widening the list to `--state all`: the list is the tab's
|
|
484
|
-
// hot path, and making every load pay for hundreds of closed rows to serve an occasional lookup
|
|
485
|
-
// would trade a search bug for a performance one.
|
|
579
|
+
// On-demand cross-state search stays separate from the hot open list.
|
|
486
580
|
/** Hits per search. `gh search`'s own default is 30; 50 matches the page size the incremental-
|
|
487
581
|
* loading spec settled on and keeps one search to a single `gh` round-trip. */
|
|
488
582
|
export const GH_SEARCH_MAX = GITHUB_SEARCH_MAX;
|
|
@@ -513,6 +607,8 @@ const ghSearchHitSchema = ghIssueSchema.extend({
|
|
|
513
607
|
/** `gh {issue,pr} view <n> --json …` — the exact-number path. Shares `ghIssueSchema`'s core;
|
|
514
608
|
* `isDraft`/`additions`/`deletions` are PR-only and default harmlessly for issues. */
|
|
515
609
|
const ghViewHitSchema = ghIssueSchema.extend({
|
|
610
|
+
state: z.string().optional(),
|
|
611
|
+
mergedAt: z.unknown().optional(),
|
|
516
612
|
assignees: z.array(z.object({ login: z.string() })).optional(),
|
|
517
613
|
isDraft: z.boolean().default(false),
|
|
518
614
|
additions: z.number().default(0),
|
|
@@ -565,9 +661,15 @@ async function viewGithubHit(repoRoot, kind, number) {
|
|
|
565
661
|
'view',
|
|
566
662
|
String(number),
|
|
567
663
|
'--json',
|
|
568
|
-
kind === 'pr' ? `${SEARCH_FIELDS},isDraft,additions,deletions` : `${SEARCH_FIELDS},assignees`,
|
|
664
|
+
kind === 'pr' ? `${SEARCH_FIELDS},isDraft,additions,deletions,state,mergedAt` : `${SEARCH_FIELDS},assignees`,
|
|
569
665
|
]);
|
|
570
|
-
|
|
666
|
+
const hit = ghViewHitSchema.parse(JSON.parse(out));
|
|
667
|
+
if (kind === 'pr' && hit.state === 'MERGED') {
|
|
668
|
+
const handle = await resolveRepoHandle(repoRoot);
|
|
669
|
+
if (handle)
|
|
670
|
+
mergedPullRequestObservations.observe({ owner: handle.owner, repo: handle.name, number }, hit);
|
|
671
|
+
}
|
|
672
|
+
return hit;
|
|
571
673
|
}
|
|
572
674
|
catch (err) {
|
|
573
675
|
if (MISSING_ITEM_RE.test(err instanceof Error ? err.message : String(err)))
|
|
@@ -591,22 +693,8 @@ export async function viewGithubItem(repoRoot, kind, number, labelColors = {}) {
|
|
|
591
693
|
return hit ? finishViewHit(repoRoot, kind, number, hit, labelColors) : null;
|
|
592
694
|
}
|
|
593
695
|
/**
|
|
594
|
-
*
|
|
595
|
-
*
|
|
596
|
-
* Two query shapes, because they want different GitHub calls:
|
|
597
|
-
* - **A bare number** (`4507`, `#4507`) is a lookup, not a search: `gh {pr,issue} view <n>` is
|
|
598
|
-
* one cheap, state-agnostic call that finds a PR merged two years ago as readily as one opened
|
|
599
|
-
* today. GitHub's search index cannot be relied on to surface an exact number at all.
|
|
600
|
-
* - **Anything else** is a text search: `gh search {prs,issues} <query> --repo owner/name`, with
|
|
601
|
-
* `--state` deliberately OMITTED — the flag only accepts `open|closed`, and leaving it off is
|
|
602
|
-
* what searches every state.
|
|
603
|
-
*
|
|
604
|
-
* A numeric query that resolves to nothing (wrong repo, deleted, or simply a number the user meant
|
|
605
|
-
* as text) falls through to the text path rather than reporting "not found" — `4507` should still
|
|
606
|
-
* find a PR whose title contains it.
|
|
607
|
-
*
|
|
608
|
-
* Never throws: every failure lands on `{available: false, reason}`, the same quiet degrade the
|
|
609
|
-
* rest of the tab uses. Exported for unit tests.
|
|
696
|
+
* Search any state: exact numbers try a direct lookup, then text fallback; text search omits
|
|
697
|
+
* state restriction. Return availability and a reason on failure instead of throwing.
|
|
610
698
|
*/
|
|
611
699
|
export async function searchGithubItems(repoRoot, kind, query, limit = GH_SEARCH_MAX) {
|
|
612
700
|
const trimmed = query.trim();
|
|
@@ -721,9 +809,7 @@ async function hydrateSearchProjects(repoRoot, kind, data) {
|
|
|
721
809
|
const remaining = deadline - Date.now();
|
|
722
810
|
if (remaining <= 0)
|
|
723
811
|
return Promise.reject(new Error('Project lookup timed out'));
|
|
724
|
-
const args =
|
|
725
|
-
for (const [key, value] of Object.entries(variables))
|
|
726
|
-
args.push('-f', `${key}=${value}`);
|
|
812
|
+
const args = ghGraphqlArgs(query, variables);
|
|
727
813
|
return gh(repoRoot, args, Math.min(remaining, 8_000));
|
|
728
814
|
};
|
|
729
815
|
const projects = await fetchIssueProjects(run, handle.owner, handle.name, data.items.map(item => item.number));
|
|
@@ -731,6 +817,32 @@ async function hydrateSearchProjects(repoRoot, kind, data) {
|
|
|
731
817
|
return { ...data, projectsReason: projects.projectsReason };
|
|
732
818
|
return { ...data, items: data.items.map(item => ({ ...item, projectIds: projects.membership[item.number] ?? [] })) };
|
|
733
819
|
}
|
|
820
|
+
/** The dry-run catalog, paged like the real list: numeric offset cursors per side, done flags
|
|
821
|
+
* honored, exact totals, and counts deferred to the counts route (hearsay-tools/cezarion#1183). */
|
|
822
|
+
function mockGithubPage(limit, page) {
|
|
823
|
+
const full = mockGithub();
|
|
824
|
+
const side = (rows, cursor, done) => {
|
|
825
|
+
const start = done ? rows.length : Number(cursor ?? 0);
|
|
826
|
+
const slice = rows.slice(start, start + Math.max(limit, 1));
|
|
827
|
+
const end = start + slice.length;
|
|
828
|
+
return { rows: slice.map((row) => ({ ...row, comments: 0 })), next: end < rows.length ? String(end) : null };
|
|
829
|
+
};
|
|
830
|
+
const issues = side(full.issues, page.issuesCursor, page.issuesDone);
|
|
831
|
+
const prs = side(full.prs, page.prsCursor, page.prsDone);
|
|
832
|
+
const firstPage = !page.issuesCursor && !page.prsCursor && !page.issuesDone && !page.prsDone;
|
|
833
|
+
const { viewerLogin, ...rest } = full;
|
|
834
|
+
return {
|
|
835
|
+
...rest,
|
|
836
|
+
...(firstPage && viewerLogin ? { viewerLogin } : {}),
|
|
837
|
+
issues: issues.rows,
|
|
838
|
+
prs: prs.rows,
|
|
839
|
+
issuesTotal: full.issues.length,
|
|
840
|
+
prsTotal: full.prs.length,
|
|
841
|
+
issuesNextCursor: issues.next,
|
|
842
|
+
prsNextCursor: prs.next,
|
|
843
|
+
commentsDeferred: true,
|
|
844
|
+
};
|
|
845
|
+
}
|
|
734
846
|
/** CEZ_DRY_RUN=1 — a small fixed catalog so the GitHub tab is demoable offline. */
|
|
735
847
|
function mockGithub() {
|
|
736
848
|
const mk = (over) => ({
|
|
@@ -879,7 +991,7 @@ export const THREAD_ENTRY_CAP = 200;
|
|
|
879
991
|
const COMMENT_BODY_CAP = 8_000;
|
|
880
992
|
/** `gh api …/issues/{n}/comments` JSON → `ForgeComment[]`. Exported for unit tests. */
|
|
881
993
|
export function normalizeComments(raw) {
|
|
882
|
-
return z.array(ghIssueCommentSchema).parse(raw).map((c) => ({
|
|
994
|
+
return z.array(ghIssueCommentSchema).parse(raw).map((c) => defined({
|
|
883
995
|
id: c.id,
|
|
884
996
|
author: c.user?.login ?? '?',
|
|
885
997
|
avatarUrl: c.user?.avatar_url ?? undefined,
|
|
@@ -901,7 +1013,7 @@ export function normalizeReviews(raw) {
|
|
|
901
1013
|
const emptyBody = (r.body ?? '').trim().length === 0;
|
|
902
1014
|
return !(emptyBody && (state === 'COMMENTED' || state === 'PENDING'));
|
|
903
1015
|
})
|
|
904
|
-
.map((r) => ({
|
|
1016
|
+
.map((r) => defined({
|
|
905
1017
|
id: r.id,
|
|
906
1018
|
author: r.user?.login ?? '?',
|
|
907
1019
|
avatarUrl: r.user?.avatar_url ?? undefined,
|
|
@@ -1367,6 +1479,101 @@ export async function fetchPrChecks(runGraphql, owner, name, numbers, chunkSize
|
|
|
1367
1479
|
}
|
|
1368
1480
|
return out;
|
|
1369
1481
|
}
|
|
1482
|
+
const itemDetailSchema = z.object({
|
|
1483
|
+
body: z.string().nullish(),
|
|
1484
|
+
additions: z.number().int().nonnegative().optional(),
|
|
1485
|
+
deletions: z.number().int().nonnegative().optional(),
|
|
1486
|
+
});
|
|
1487
|
+
/** One item's body (and a PR's diffstat), or `undefined` on any failure. */
|
|
1488
|
+
async function fetchItemDetail(repoRoot, kind, number) {
|
|
1489
|
+
try {
|
|
1490
|
+
const out = await gh(repoRoot, [kind, 'view', String(number), '--json', kind === 'pr' ? 'body,additions,deletions' : 'body']);
|
|
1491
|
+
const parsed = itemDetailSchema.parse(JSON.parse(out));
|
|
1492
|
+
return {
|
|
1493
|
+
body: (parsed.body ?? '').slice(0, COMMENT_BODY_CAP),
|
|
1494
|
+
...(kind === 'pr' && parsed.additions !== undefined ? { additions: parsed.additions } : {}),
|
|
1495
|
+
...(kind === 'pr' && parsed.deletions !== undefined ? { deletions: parsed.deletions } : {}),
|
|
1496
|
+
};
|
|
1497
|
+
}
|
|
1498
|
+
catch {
|
|
1499
|
+
return undefined;
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1502
|
+
// Per-item comment counts (hearsay-tools/cezarion#1183): the list defers them, and the cockpit
|
|
1503
|
+
// asks for its loaded rows after paint. Keyed `context␀kind␀number`, same TTL and bound as checks.
|
|
1504
|
+
const commentCountsCache = new Map();
|
|
1505
|
+
const commentCountsSchema = z.object({
|
|
1506
|
+
data: z.object({
|
|
1507
|
+
repository: z.record(z.string(), z.object({ comments: z.object({ totalCount: z.number().int().nonnegative() }) }).nullable()),
|
|
1508
|
+
}),
|
|
1509
|
+
});
|
|
1510
|
+
/**
|
|
1511
|
+
* Comment counts for the given issue or PR numbers, in one aliased GraphQL query. A number GitHub
|
|
1512
|
+
* does not know is absent (unknown), never 0. Degrades to `{ available: false, reason }` — never a
|
|
1513
|
+
* throw. Numbers are de-duplicated, validated and capped at `GITHUB_COMMENT_COUNTS_MAX`.
|
|
1514
|
+
*/
|
|
1515
|
+
export async function fetchGithubCommentCounts(repoRoot, kind, numbers, refresh = false) {
|
|
1516
|
+
if (process.env.CEZ_DRY_RUN === '1')
|
|
1517
|
+
return mockGithubCommentCounts(kind, numbers);
|
|
1518
|
+
const context = githubContext(repoRoot);
|
|
1519
|
+
const wanted = [...new Set(numbers)].filter((n) => Number.isSafeInteger(n) && n > 0).slice(0, GITHUB_COMMENT_COUNTS_MAX);
|
|
1520
|
+
const counts = {};
|
|
1521
|
+
const misses = [];
|
|
1522
|
+
const now = Date.now();
|
|
1523
|
+
const keyOf = (n) => `${context.key}\0${kind}\0${n}`;
|
|
1524
|
+
for (const n of wanted) {
|
|
1525
|
+
const hit = commentCountsCache.get(keyOf(n));
|
|
1526
|
+
if (!refresh && hit && now - hit.at < CACHE_MS)
|
|
1527
|
+
counts[n] = hit.count;
|
|
1528
|
+
else
|
|
1529
|
+
misses.push(n);
|
|
1530
|
+
}
|
|
1531
|
+
if (misses.length === 0)
|
|
1532
|
+
return { available: true, counts };
|
|
1533
|
+
try {
|
|
1534
|
+
const handle = await resolveRepoHandleStrict(repoRoot, context);
|
|
1535
|
+
if (!handle)
|
|
1536
|
+
return { available: false, reason: 'Could not resolve this repository on GitHub.' };
|
|
1537
|
+
// Integers only in the aliases; owner and name travel as variables.
|
|
1538
|
+
const fields = misses.map((n) => `n${n}: issueOrPullRequest(number: ${n}) {
|
|
1539
|
+
... on Issue { comments { totalCount } }
|
|
1540
|
+
... on PullRequest { comments { totalCount } }
|
|
1541
|
+
}`).join('\n');
|
|
1542
|
+
// A number GitHub does not know comes back NOT_FOUND and makes `gh` exit 1 with the rest of
|
|
1543
|
+
// the batch on stdout; `refStatusGraphql` keeps that partial answer.
|
|
1544
|
+
const raw = await refStatusGraphql(repoRoot, context)(`query($owner: String!, $name: String!) { repository(owner: $owner, name: $name) { ${fields} } }`, { owner: handle.owner, name: handle.name });
|
|
1545
|
+
const rows = commentCountsSchema.parse(JSON.parse(raw)).data.repository;
|
|
1546
|
+
for (const n of misses) {
|
|
1547
|
+
const count = rows[`n${n}`]?.comments.totalCount;
|
|
1548
|
+
if (count === undefined)
|
|
1549
|
+
continue; // unknown number: absent, and asked again next time
|
|
1550
|
+
counts[n] = count;
|
|
1551
|
+
commentCountsCache.delete(keyOf(n));
|
|
1552
|
+
commentCountsCache.set(keyOf(n), { at: now, count });
|
|
1553
|
+
}
|
|
1554
|
+
while (commentCountsCache.size > CHECKS_CACHE_MAX) {
|
|
1555
|
+
const oldest = commentCountsCache.keys().next().value;
|
|
1556
|
+
if (oldest === undefined)
|
|
1557
|
+
break;
|
|
1558
|
+
commentCountsCache.delete(oldest);
|
|
1559
|
+
}
|
|
1560
|
+
return { available: true, counts };
|
|
1561
|
+
}
|
|
1562
|
+
catch (err) {
|
|
1563
|
+
return { available: false, reason: ghFailureReason(err) };
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
/** CEZ_DRY_RUN=1 — the catalog's own counts, so the offline list shows its badges. */
|
|
1567
|
+
function mockGithubCommentCounts(kind, numbers) {
|
|
1568
|
+
const rows = kind === 'pr' ? mockGithub().prs : mockGithub().issues;
|
|
1569
|
+
const counts = {};
|
|
1570
|
+
for (const n of numbers) {
|
|
1571
|
+
const row = rows.find((item) => item.number === n);
|
|
1572
|
+
if (row)
|
|
1573
|
+
counts[n] = row.comments;
|
|
1574
|
+
}
|
|
1575
|
+
return { available: true, counts };
|
|
1576
|
+
}
|
|
1370
1577
|
// Per-PR checks cache: keyed `repoRoot␀number`, same 60 s TTL as the list cache but BOUNDED. Lets
|
|
1371
1578
|
// a repeated visible-window hydration within a minute serve cached glyphs instead of re-querying.
|
|
1372
1579
|
const checksCache = new Map();
|
|
@@ -1403,9 +1610,7 @@ export async function fetchGithubChecks(repoRoot, numbers) {
|
|
|
1403
1610
|
if (!ownerName)
|
|
1404
1611
|
return { available: false, reason: 'repository handle unavailable' };
|
|
1405
1612
|
const runGraphql = (query, variables) => {
|
|
1406
|
-
const args =
|
|
1407
|
-
for (const [key, value] of Object.entries(variables))
|
|
1408
|
-
args.push('-f', `${key}=${value}`);
|
|
1613
|
+
const args = ghGraphqlArgs(query, variables);
|
|
1409
1614
|
return gh(repoRoot, args);
|
|
1410
1615
|
};
|
|
1411
1616
|
const fetched = await fetchPrChecks(runGraphql, ownerName.owner, ownerName.name, misses);
|
|
@@ -1464,6 +1669,8 @@ function reviewerKey(ref) {
|
|
|
1464
1669
|
const ghRefStatusPrSchema = z
|
|
1465
1670
|
.object({
|
|
1466
1671
|
state: z.string(),
|
|
1672
|
+
url: z.unknown().optional(),
|
|
1673
|
+
mergedAt: z.unknown().optional(),
|
|
1467
1674
|
isDraft: z.boolean().nullish(),
|
|
1468
1675
|
reviewDecision: z.string().nullish(),
|
|
1469
1676
|
/** `MERGEABLE` | `CONFLICTING` | `UNKNOWN` — GitHub computes it in the background, so
|
|
@@ -1546,85 +1753,19 @@ const ghRefStatusIssueSchema = z.object({ state: z.string(), stateReason: z.stri
|
|
|
1546
1753
|
// `commits` silently removed.
|
|
1547
1754
|
const ghRefStatusSchema = z.record(z.string(), z.unknown());
|
|
1548
1755
|
/**
|
|
1549
|
-
*
|
|
1550
|
-
*
|
|
1551
|
-
* Issues and pull requests share one numbering space in a repository, so a number is exactly one
|
|
1552
|
-
* of the two and asking which is a question the forge can answer — where asking `issue(number:)`
|
|
1553
|
-
* about a pull request is a question with no answer, and GitHub says so with a NOT_FOUND *error*
|
|
1554
|
-
* rather than a null. That mattered far more than it looks: `gh api graphql` exits non-zero the
|
|
1555
|
-
* moment a response carries an `errors` array, so one mis-guessed kind used to fail the whole
|
|
1556
|
-
* batch and report every reference in it as "not found" (see `refStatusGraphql`).
|
|
1557
|
-
*
|
|
1558
|
-
* It also fixes the reference whose kind the cockpit guessed wrong — `taskReferences` infers it
|
|
1559
|
-
* from which field carried the number, and a bare `#774` can land in either — because the answer
|
|
1560
|
-
* carries `__typename` and is filed under what the number REALLY is.
|
|
1561
|
-
*
|
|
1562
|
-
* `committedDate`, the head commit's `parents.totalCount` (`first: 0` — the COUNT is the whole
|
|
1563
|
-
* question, so no parent is fetched) and the last CHANGES_REQUESTED review's `submittedAt` cost
|
|
1564
|
-
* nothing extra, riding the same node, and are what let the precedence tell a review the author has
|
|
1565
|
-
* already responded to from one still about the code on screen. The last `ReviewRequestedEvent` is
|
|
1566
|
-
* asked for the same reason: `reviewRequests` says only THAT someone is on the hook, never since
|
|
1567
|
-
* when, and the difference between a request made before the review and one made after it is the
|
|
1568
|
-
* difference between a reviewer who has not looked yet and an author who has answered.
|
|
1569
|
-
*
|
|
1570
|
-
* `mergeable` rides it too, and is the reason the batch can answer "this one conflicts" without
|
|
1571
|
-
* the per-PR probe `prMergeState` runs for the merge box. It is NOT folded into the status: see
|
|
1572
|
-
* `conflicts` in the contract for why the two stay separate axes.
|
|
1756
|
+
* Query issueOrPullRequest once per number and use __typename for the actual kind. Include
|
|
1757
|
+
* branch-work and review timestamps for freshness decisions.
|
|
1573
1758
|
*/
|
|
1574
1759
|
function refStatusQuery(numbers) {
|
|
1575
1760
|
const aliases = numbers
|
|
1576
|
-
.map((n, i) => ` r${i}: issueOrPullRequest(number: ${n}) { __typename ... on PullRequest { state isDraft reviewDecision mergeable commits(last: 1) { nodes { commit { committedDate parents(first: 1) { totalCount nodes { committedDate } } statusCheckRollup { state } } } } reviews(last: 1, states: CHANGES_REQUESTED) { nodes { submittedAt } } reviewRequests(first: 20) { totalCount nodes { requestedReviewer { __typename ... on User { login } ... on Bot { login } ... on Team { slug } } } } timelineItems(last: 20, itemTypes: [REVIEW_REQUESTED_EVENT]) { nodes { ... on ReviewRequestedEvent { createdAt requestedReviewer { __typename ... on User { login } ... on Bot { login } ... on Team { slug } } } } } } ... on Issue { state stateReason } }`)
|
|
1761
|
+
.map((n, i) => ` r${i}: issueOrPullRequest(number: ${n}) { __typename ... on PullRequest { url mergedAt state isDraft reviewDecision mergeable commits(last: 1) { nodes { commit { committedDate parents(first: 1) { totalCount nodes { committedDate } } statusCheckRollup { state } } } } reviews(last: 1, states: CHANGES_REQUESTED) { nodes { submittedAt } } reviewRequests(first: 20) { totalCount nodes { requestedReviewer { __typename ... on User { login } ... on Bot { login } ... on Team { slug } } } } timelineItems(last: 20, itemTypes: [REVIEW_REQUESTED_EVENT]) { nodes { ... on ReviewRequestedEvent { createdAt requestedReviewer { __typename ... on User { login } ... on Bot { login } ... on Team { slug } } } } } } ... on Issue { state stateReason } }`)
|
|
1577
1762
|
.join('\n');
|
|
1578
|
-
return `query ($owner: String!, $name: String!) {\n repository(owner: $owner, name: $name) {\n${aliases}\n }\n}`;
|
|
1763
|
+
return `query ($owner: String!, $name: String!) {\n repository(owner: $owner, name: $name) {\nnameWithOwner\n${aliases}\n }\n}`;
|
|
1579
1764
|
}
|
|
1580
1765
|
/**
|
|
1581
|
-
*
|
|
1582
|
-
*
|
|
1583
|
-
*
|
|
1584
|
-
* freshness of the signal, not by how heavy a blocker it is:
|
|
1585
|
-
*
|
|
1586
|
-
* Read the ranking as **whose move is it**, which is the question a table is scanned for:
|
|
1587
|
-
*
|
|
1588
|
-
* 1. `merged` / `closed` — nobody's. Terminal, whatever checks or reviews say.
|
|
1589
|
-
* 2. `draft` — the author's, and they have said so themselves.
|
|
1590
|
-
* 3. `checks-pending` — the machine's. CI running means a commit was JUST pushed, the newest
|
|
1591
|
-
* thing that has happened to this PR, so it outranks a requested change unconditionally.
|
|
1592
|
-
* 4. `changes-requested` — the AUTHOR's, and only while that is still true: the review must be
|
|
1593
|
-
* about the code that is there now, with no re-review already asked for.
|
|
1594
|
-
* 5. `checks-failing` — the author's again. A reviewer cannot approve a red PR anyway.
|
|
1595
|
-
* 6. `review-required` — the REVIEWER's: they have been asked, or the author has answered and
|
|
1596
|
-
* the merge is now blocked on someone coming back to look.
|
|
1597
|
-
* 7. `ready` — open, not a draft, nothing failing, nothing running, nobody waited on.
|
|
1598
|
-
*
|
|
1599
|
-
* The subtle one is (4) → (6), and it is not a preference but a data finding. `reviewDecision`
|
|
1600
|
-
* stays `CHANGES_REQUESTED` after the author has responded — GitHub does not clear it until a
|
|
1601
|
-
* reviewer submits again — so on its own it points at the author forever. Two signals say the ball
|
|
1602
|
-
* has moved back:
|
|
1603
|
-
*
|
|
1604
|
-
* - **a review request made AFTER the review**, which is the author clicking re-request.
|
|
1605
|
-
* Authoritative, and observed live alongside a stale `CHANGES_REQUESTED` and an EMPTY
|
|
1606
|
-
* `latestReviews` — the case that has no other tell.
|
|
1607
|
-
* - **a non-merge commit newer than the review**, the fallback for an author who pushed without
|
|
1608
|
-
* clicking anything. Merges are excluded because GitHub's "Update branch" button writes one,
|
|
1609
|
-
* dated now, that answers nothing — the reflexive click on a stale PR must not clear a
|
|
1610
|
-
* rejection.
|
|
1611
|
-
*
|
|
1612
|
-
* The "after" in the first one is load-bearing, and its absence was a reported bug. A request that
|
|
1613
|
-
* PREDATES the review is a reviewer who has not looked yet, and on a PR where several people were
|
|
1614
|
-
* asked and one of them rejected it the others stay listed forever — so a bare
|
|
1615
|
-
* `reviewRequests.totalCount > 0` reads a live "changes requested" as answered and hides it behind
|
|
1616
|
-
* "waiting for review" (observed live: three reviewers asked at 10:28, changes requested
|
|
1617
|
-
* the next day, one reviewer still pending).
|
|
1618
|
-
*
|
|
1619
|
-
* Either way the words change from "you owe edits" to "they owe a look", and so does the colour:
|
|
1620
|
-
* danger is the author's move, info is the reviewer's.
|
|
1621
|
-
*
|
|
1622
|
-
* Both timestamps are optional and their ABSENCE is conservative: with no dates to compare and no
|
|
1623
|
-
* re-request, a review counts as current, and the chip keeps pointing at the author.
|
|
1624
|
-
*
|
|
1625
|
-
* `reviewDecision` is null on a repo with no review policy, which is exactly why `ready` is not
|
|
1626
|
-
* spelled "approved": on such a repo a green PR IS ready, and no approval will ever arrive — but a
|
|
1627
|
-
* requested reviewer still moves it to `review-required`, because someone was explicitly asked.
|
|
1766
|
+
* Derive whose action is pending: terminal, draft, pending checks, current requested changes,
|
|
1767
|
+
* failing checks, review, then ready. Use dated review/work evidence; mergeability remains a
|
|
1768
|
+
* separate axis.
|
|
1628
1769
|
*/
|
|
1629
1770
|
export function derivePrReferenceStatus(pr) {
|
|
1630
1771
|
const state = pr.state.toUpperCase();
|
|
@@ -1734,6 +1875,10 @@ export async function fetchRefStatuses(runGraphql, owner, name, numbers, chunkSi
|
|
|
1734
1875
|
const pr = ghRefStatusPrSchema.parse(node);
|
|
1735
1876
|
if (!pr)
|
|
1736
1877
|
return;
|
|
1878
|
+
if ((!raw.errors || (Array.isArray(raw.errors) && raw.errors.length === 0)) &&
|
|
1879
|
+
typeof repository.nameWithOwner === 'string' && repository.nameWithOwner.toLowerCase() === `${owner}/${name}`.toLowerCase()) {
|
|
1880
|
+
mergedPullRequestObservations.observe({ owner, repo: name, number }, { ...pr, number });
|
|
1881
|
+
}
|
|
1737
1882
|
const head = pr.commits?.nodes[0]?.commit;
|
|
1738
1883
|
const rollup = head?.statusCheckRollup;
|
|
1739
1884
|
const mergeability = mergeabilityOf(pr.state, pr.mergeable);
|
|
@@ -1805,19 +1950,8 @@ export function __seedRefStatusCacheForTests(repoRoot, entries) {
|
|
|
1805
1950
|
}
|
|
1806
1951
|
}
|
|
1807
1952
|
/**
|
|
1808
|
-
*
|
|
1809
|
-
*
|
|
1810
|
-
* Called where cezar itself CHANGES a pull request — it merges one, it opens one — because those
|
|
1811
|
-
* are the only forge changes this process can know about without asking. Everything else has to
|
|
1812
|
-
* be polled (GitHub cannot push to a cockpit with no public endpoint), but waiting out a TTL to
|
|
1813
|
-
* notice our own merge is a self-inflicted staleness: for up to a minute every chip would keep
|
|
1814
|
-
* showing the pre-merge status of a pull request the user watched this server merge.
|
|
1815
|
-
*
|
|
1816
|
-
* Deleting rather than overwriting with a guessed `merged`: the forge is the authority on what a
|
|
1817
|
-
* reference is, and a mutation that reports success is still not the same as having read the
|
|
1818
|
-
* result. The next reader pays one query and gets the truth — after which the answer is `merged`,
|
|
1819
|
-
* `recheckAfterMs` goes null, and the cockpit stops polling that batch entirely. Invalidating here
|
|
1820
|
-
* therefore REDUCES long-run traffic rather than adding to it.
|
|
1953
|
+
* Invalidate one reference after a known mutation. Let the next forge read establish status
|
|
1954
|
+
* rather than caching a guessed result.
|
|
1821
1955
|
*/
|
|
1822
1956
|
export function forgetRefStatus(repoRoot, number) {
|
|
1823
1957
|
refStatusCache.delete(refStatusKey(repoRoot, number));
|
|
@@ -1848,7 +1982,7 @@ export function readCachedRefStatuses(repoRoot, numbers) {
|
|
|
1848
1982
|
return out;
|
|
1849
1983
|
}
|
|
1850
1984
|
// The `#N` in a forge URL. Defined beside the run search matcher, which reads it too (#864).
|
|
1851
|
-
export { refNumberFromUrl } from '../../
|
|
1985
|
+
export { refNumberFromUrl } from '../../contract/index.js';
|
|
1852
1986
|
/** A closed issue or an abandoned PR can be REOPENED, so this is long rather than forever — but
|
|
1853
1987
|
* it is the rare event, and re-asking a hundred settled references every minute to catch it is
|
|
1854
1988
|
* the wrong trade. */
|
|
@@ -1929,28 +2063,12 @@ const MERGEABILITY_UNKNOWN_WINDOW_MS = 60_000;
|
|
|
1929
2063
|
* thing. Five minutes is the same order as the cockpit's own reconnect cadence. */
|
|
1930
2064
|
const REF_STATUS_RETRY_MS = 5 * 60_000;
|
|
1931
2065
|
/**
|
|
1932
|
-
*
|
|
1933
|
-
*
|
|
1934
|
-
* `gh api graphql` exits non-zero whenever the reply carries an `errors` array — even when `data`
|
|
1935
|
-
* is fully populated for every alias that DID resolve. With one alias per reference that is not an
|
|
1936
|
-
* edge case, it is the normal case: a single number that no longer exists (or never did, having
|
|
1937
|
-
* been scraped from a transcript that named another repository) makes GitHub answer
|
|
1938
|
-
* `{data: {...everything else...}, errors: [NOT_FOUND]}` and `gh` exit 1.
|
|
1939
|
-
*
|
|
1940
|
-
* `execFile` rejects on a non-zero exit, so that used to throw away a whole batch's worth of
|
|
1941
|
-
* perfectly good statuses and report every reference in it as "not found" — including, in the bug
|
|
1942
|
-
* that produced this function, an open pull request in the project's own repository.
|
|
1943
|
-
*
|
|
1944
|
-
* So: a non-zero exit whose stdout still carries a usable `data.repository` is a partial success
|
|
1945
|
-
* and is used as-is. Anything else — no stdout, unparseable stdout, or a null `repository`, which
|
|
1946
|
-
* means the repo handle itself did not resolve — rethrows, so a real failure still degrades to
|
|
1947
|
-
* `{available: false}` instead of masquerading as "none of these exist".
|
|
2066
|
+
* Use failed gh GraphQL stdout only when it contains usable data.repository. Preserve successful
|
|
2067
|
+
* aliases in partial responses; rethrow malformed or unresolved-repository failures.
|
|
1948
2068
|
*/
|
|
1949
2069
|
function refStatusGraphql(repoRoot, context = githubContext(repoRoot)) {
|
|
1950
2070
|
return async (query, variables) => {
|
|
1951
|
-
const args =
|
|
1952
|
-
for (const [key, value] of Object.entries(variables))
|
|
1953
|
-
args.push('-f', `${key}=${value}`);
|
|
2071
|
+
const args = ghGraphqlArgs(query, variables);
|
|
1954
2072
|
try {
|
|
1955
2073
|
return await gh(repoRoot, args, 15_000, undefined, context);
|
|
1956
2074
|
}
|
|
@@ -2170,7 +2288,7 @@ function mockGithubRefStatus(prs, issues) {
|
|
|
2170
2288
|
*/
|
|
2171
2289
|
export async function fetchGithubComments(repoRoot, kind, number, refresh = false) {
|
|
2172
2290
|
if (process.env.CEZ_DRY_RUN === '1')
|
|
2173
|
-
return mockGithubComments(kind);
|
|
2291
|
+
return mockGithubComments(kind, number);
|
|
2174
2292
|
// NUL separator: cannot appear in a filesystem path, so roots can never alias.
|
|
2175
2293
|
const key = `${repoRoot}\0${kind}#${number}`;
|
|
2176
2294
|
const hit = commentsCache.get(key);
|
|
@@ -2178,6 +2296,10 @@ export async function fetchGithubComments(repoRoot, kind, number, refresh = fals
|
|
|
2178
2296
|
return hit.data;
|
|
2179
2297
|
// PR conversation comments live on the issues endpoint too — always use it for the body thread.
|
|
2180
2298
|
const legacyComments = () => gh(repoRoot, ['api', `repos/{owner}/{repo}/issues/${number}/comments`, '--paginate']);
|
|
2299
|
+
// The detail tier (hearsay-tools/cezarion#1183): the list no longer has to carry a body, so
|
|
2300
|
+
// the pane asks for it here, in parallel with the thread. It is attached only to a thread
|
|
2301
|
+
// that loaded, and a failed view never costs the thread.
|
|
2302
|
+
const detail = fetchItemDetail(repoRoot, kind, number);
|
|
2181
2303
|
try {
|
|
2182
2304
|
let commentRows = [];
|
|
2183
2305
|
let events;
|
|
@@ -2245,17 +2367,17 @@ export async function fetchGithubComments(repoRoot, kind, number, refresh = fals
|
|
|
2245
2367
|
const handle = await resolveRepoHandle(repoRoot);
|
|
2246
2368
|
if (handle) {
|
|
2247
2369
|
const runGraphql = (query, variables) => {
|
|
2248
|
-
const args =
|
|
2249
|
-
for (const [k, v] of Object.entries(variables))
|
|
2250
|
-
args.push('-f', `${k}=${v}`);
|
|
2370
|
+
const args = ghGraphqlArgs(query, variables);
|
|
2251
2371
|
return gh(repoRoot, args);
|
|
2252
2372
|
};
|
|
2253
2373
|
const checks = await fetchCommitChecks(runGraphql, handle.owner, handle.name, commitShas);
|
|
2254
2374
|
for (const event of events ?? []) {
|
|
2255
2375
|
if (event.kind !== 'committed' || !event.sha)
|
|
2256
2376
|
continue;
|
|
2257
|
-
if (event.sha in checks)
|
|
2258
|
-
event.checks
|
|
2377
|
+
if (event.sha in checks) {
|
|
2378
|
+
delete event.checks;
|
|
2379
|
+
Object.assign(event, defined({ checks: checks[event.sha] }));
|
|
2380
|
+
}
|
|
2259
2381
|
}
|
|
2260
2382
|
}
|
|
2261
2383
|
}
|
|
@@ -2265,15 +2387,18 @@ export async function fetchGithubComments(repoRoot, kind, number, refresh = fals
|
|
|
2265
2387
|
parts.push(normalizeReviews(JSON.parse(reviewsOut)));
|
|
2266
2388
|
}
|
|
2267
2389
|
const { comments, truncated } = mergeThread(parts);
|
|
2268
|
-
const data = {
|
|
2390
|
+
const data = defined({
|
|
2269
2391
|
available: true,
|
|
2270
2392
|
comments,
|
|
2271
2393
|
// OR-folded exactly as before, so the pre-existing >200-comments trigger is preserved
|
|
2272
2394
|
// rather than replaced.
|
|
2273
2395
|
truncated: truncated || eventsTruncated || stoppedShort || undefined,
|
|
2274
|
-
};
|
|
2396
|
+
});
|
|
2275
2397
|
if (events)
|
|
2276
2398
|
data.events = events;
|
|
2399
|
+
const itemDetail = await detail;
|
|
2400
|
+
if (itemDetail)
|
|
2401
|
+
data.detail = itemDetail;
|
|
2277
2402
|
cacheComments(key, data);
|
|
2278
2403
|
return data;
|
|
2279
2404
|
}
|
|
@@ -2289,7 +2414,7 @@ export async function fetchGithubComments(repoRoot, kind, number, refresh = fals
|
|
|
2289
2414
|
}
|
|
2290
2415
|
/** CEZ_DRY_RUN=1 — a small fixed thread (one image-bearing comment, plus a review for PRs) so
|
|
2291
2416
|
* the whole feature is demoable and e2e-testable offline. */
|
|
2292
|
-
function mockGithubComments(kind) {
|
|
2417
|
+
function mockGithubComments(kind, number) {
|
|
2293
2418
|
const base = Date.now() - 3_600_000;
|
|
2294
2419
|
const at = (offset) => new Date(base + offset).toISOString();
|
|
2295
2420
|
const comments = [
|
|
@@ -2396,7 +2521,14 @@ function mockGithubComments(kind) {
|
|
|
2396
2521
|
createdAt: at(1_800_000),
|
|
2397
2522
|
});
|
|
2398
2523
|
}
|
|
2399
|
-
|
|
2524
|
+
// The detail tier (hearsay-tools/cezarion#1183): the catalog row's own body and diffstat.
|
|
2525
|
+
const row = number === undefined ? undefined
|
|
2526
|
+
: (kind === 'pr' ? mockGithub().prs : mockGithub().issues).find((item) => item.number === number);
|
|
2527
|
+
const detail = row ? {
|
|
2528
|
+
body: row.body ?? '',
|
|
2529
|
+
...(kind === 'pr' ? { additions: row.additions ?? 0, deletions: row.deletions ?? 0 } : {}),
|
|
2530
|
+
} : undefined;
|
|
2531
|
+
return { available: true, comments, events, ...(detail ? { detail } : {}) };
|
|
2400
2532
|
}
|
|
2401
2533
|
// ---- draft-PR creation (review gate, spec 009) ------------------------------
|
|
2402
2534
|
// Final autosave-commit → `git push -u origin cez/<id8>` → `gh pr create
|
|
@@ -2470,23 +2602,20 @@ export async function createDraftPr(input) {
|
|
|
2470
2602
|
*/
|
|
2471
2603
|
export function buildPrBody(handoffText, task) {
|
|
2472
2604
|
const goal = section(handoffText, '## Goal') || task.trim();
|
|
2473
|
-
const progress = section(handoffText, '## Progress log')
|
|
2474
|
-
.split('\n')
|
|
2475
|
-
.filter((l) => l.trim())
|
|
2476
|
-
.slice(0, PROGRESS_LINES_MAX)
|
|
2477
|
-
.join('\n');
|
|
2605
|
+
const progress = section(handoffText, '## Progress log').split('\n').filter((l) => l.trim()).slice(0, PROGRESS_LINES_MAX).join('\n');
|
|
2478
2606
|
const parts = ['## Goal', '', goal];
|
|
2479
2607
|
if (progress)
|
|
2480
2608
|
parts.push('', '## Progress log', '', progress);
|
|
2481
2609
|
parts.push('', '---', '', '🤖 made with cezar');
|
|
2482
2610
|
return parts.join('\n');
|
|
2483
2611
|
}
|
|
2484
|
-
/** Text of one `## Header` section, up to the next `## ` header.
|
|
2612
|
+
/** Text of one `## Header` section, up to the next `## ` header, with LF line endings. The header
|
|
2613
|
+
* is a whole line (hearsay-tools/cezarion#1147), so a Goal that quotes one is never taken for it. */
|
|
2485
2614
|
function section(text, header) {
|
|
2486
|
-
const start = text
|
|
2615
|
+
const start = exactLineIndex(text, header);
|
|
2487
2616
|
if (start < 0)
|
|
2488
2617
|
return '';
|
|
2489
|
-
const rest = text.slice(start
|
|
2618
|
+
const rest = text.slice(nextLineIndex(text, start)).replaceAll('\r\n', '\n');
|
|
2490
2619
|
const next = rest.indexOf('\n## ');
|
|
2491
2620
|
return (next >= 0 ? rest.slice(0, next) : rest).trim();
|
|
2492
2621
|
}
|
|
@@ -2616,21 +2745,32 @@ export function normalizeMergeState(raw, policyRaw, requirements = {
|
|
|
2616
2745
|
? 'changes-requested'
|
|
2617
2746
|
: pr.reviewDecision?.toUpperCase() === 'REVIEW_REQUIRED'
|
|
2618
2747
|
? 'review-required'
|
|
2619
|
-
:
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2748
|
+
: !pr.reviewDecision?.trim() && requirements.readable && requirements.requiredApprovingReviews === 0
|
|
2749
|
+
? 'not-required'
|
|
2750
|
+
: 'unknown';
|
|
2751
|
+
const checks = (pr.statusCheckRollup ?? []).map((check) => {
|
|
2752
|
+
const name = check.name ?? check.context ?? 'Check';
|
|
2753
|
+
const url = check.detailsUrl ?? check.targetUrl;
|
|
2754
|
+
return {
|
|
2755
|
+
name,
|
|
2756
|
+
state: mergeCheckState(check),
|
|
2757
|
+
required: requirements.readable ? requirements.requiredChecks.includes(name) : null,
|
|
2758
|
+
...(url?.startsWith('https://') || url?.startsWith('http://') ? { url } : {}),
|
|
2759
|
+
};
|
|
2760
|
+
});
|
|
2761
|
+
if (requirements.readable) {
|
|
2762
|
+
for (const name of requirements.requiredChecks) {
|
|
2763
|
+
if (!checks.some((check) => check.name === name))
|
|
2764
|
+
checks.push({ name, state: 'pending', required: true });
|
|
2765
|
+
}
|
|
2766
|
+
}
|
|
2628
2767
|
const methods = [];
|
|
2629
|
-
|
|
2768
|
+
const methodAllowed = (method) => requirements.allowedMergeMethods?.includes(method) ?? true;
|
|
2769
|
+
if (policy.allow_squash_merge && methodAllowed('squash'))
|
|
2630
2770
|
methods.push('squash');
|
|
2631
|
-
if (policy.allow_merge_commit)
|
|
2771
|
+
if (policy.allow_merge_commit && methodAllowed('merge'))
|
|
2632
2772
|
methods.push('merge');
|
|
2633
|
-
if (policy.allow_rebase_merge)
|
|
2773
|
+
if (policy.allow_rebase_merge && methodAllowed('rebase'))
|
|
2634
2774
|
methods.push('rebase');
|
|
2635
2775
|
const defaultMethod = policy.squash_merge_commit_title && methods.includes('squash')
|
|
2636
2776
|
? 'squash'
|
|
@@ -2659,7 +2799,13 @@ export function normalizeMergeState(raw, policyRaw, requirements = {
|
|
|
2659
2799
|
eligibility = 'blocked';
|
|
2660
2800
|
blockers.push({ code: 'reviews', message: reviewDecision === 'changes-requested' ? 'Changes were requested.' : 'A required review is missing.' });
|
|
2661
2801
|
}
|
|
2662
|
-
else if (
|
|
2802
|
+
else if ((requirements.unresolvedThreads ?? 0) > 0) {
|
|
2803
|
+
eligibility = 'blocked';
|
|
2804
|
+
blockers.push({ code: 'review-threads', message: 'Required review threads must be resolved before merging.' });
|
|
2805
|
+
}
|
|
2806
|
+
else if (reviewDecision === 'unknown' || !requirements.readable ||
|
|
2807
|
+
(requirements.requiresThreadResolution && requirements.unresolvedThreads == null) ||
|
|
2808
|
+
checks.some((check) => check.required === true && check.state === 'unknown')) {
|
|
2663
2809
|
eligibility = 'unknown';
|
|
2664
2810
|
blockers.push({
|
|
2665
2811
|
code: 'rules-unknown',
|
|
@@ -2669,6 +2815,7 @@ export function normalizeMergeState(raw, policyRaw, requirements = {
|
|
|
2669
2815
|
else if (checks.some((check) => check.state === 'pending') || pr.mergeStateStatus?.toUpperCase() === 'UNSTABLE') {
|
|
2670
2816
|
eligibility = 'pending';
|
|
2671
2817
|
blockers.push({ code: 'pending', message: 'Checks or GitHub mergeability are still pending.' });
|
|
2818
|
+
// GitHub's aggregate still enforces other rules and required check provider identities.
|
|
2672
2819
|
}
|
|
2673
2820
|
else if (mergeable !== 'mergeable' ||
|
|
2674
2821
|
!['CLEAN', 'HAS_HOOKS'].includes(pr.mergeStateStatus?.toUpperCase() ?? '') ||
|
|
@@ -2731,23 +2878,19 @@ async function fetchPrMergeState(repoRoot, repoRef, number, refresh = false) {
|
|
|
2731
2878
|
try {
|
|
2732
2879
|
const prOut = await gh(repoRoot, [
|
|
2733
2880
|
'pr', 'view', String(number), '--json',
|
|
2734
|
-
'number,title,url,state,isDraft,headRefName,baseRefName,headRefOid,mergeable,mergeStateStatus,reviewDecision,statusCheckRollup',
|
|
2881
|
+
'number,title,url,state,isDraft,headRefName,baseRefName,headRefOid,mergeable,mergeStateStatus,reviewDecision,statusCheckRollup,mergedAt',
|
|
2735
2882
|
]);
|
|
2736
2883
|
const parsedPr = mergePrSchema.parse(JSON.parse(prOut));
|
|
2737
|
-
|
|
2884
|
+
if (!matchesGithubPullRequest({ ...repoRef, number }, parsedPr))
|
|
2885
|
+
return { available: false, reason: 'GitHub returned a different pull request.' };
|
|
2886
|
+
mergedPullRequestObservations.observe({ ...repoRef, number }, parsedPr);
|
|
2887
|
+
const [policyOut, requirements] = await Promise.all([
|
|
2738
2888
|
gh(repoRoot, ['api', `repos/${repoRef.owner}/${repoRef.repo}`]),
|
|
2739
|
-
gh(repoRoot,
|
|
2740
|
-
'api',
|
|
2741
|
-
`repos/${repoRef.owner}/${repoRef.repo}/branches/${encodeURIComponent(parsedPr.baseRefName)}/protection/required_status_checks`,
|
|
2742
|
-
'--jq',
|
|
2743
|
-
'[.contexts[]?, .checks[]?.context] | unique',
|
|
2744
|
-
])
|
|
2745
|
-
.then((output) => ({ readable: true, requiredChecks: z.array(z.string()).parse(JSON.parse(output)) }))
|
|
2746
|
-
.catch(() => ({ readable: false, requiredChecks: [] })),
|
|
2889
|
+
fetchGithubMergeRequirements((args) => gh(repoRoot, args), repoRef.owner, repoRef.repo, parsedPr.baseRefName, number),
|
|
2747
2890
|
]);
|
|
2748
2891
|
const value = {
|
|
2749
2892
|
available: true,
|
|
2750
|
-
mergeState: normalizeMergeState(parsedPr, JSON.parse(policyOut),
|
|
2893
|
+
mergeState: normalizeMergeState(parsedPr, JSON.parse(policyOut), requirements),
|
|
2751
2894
|
};
|
|
2752
2895
|
mergeStateCache.set(key, { at: Date.now(), value });
|
|
2753
2896
|
return value;
|
|
@@ -2757,7 +2900,16 @@ async function fetchPrMergeState(repoRoot, repoRef, number, refresh = false) {
|
|
|
2757
2900
|
}
|
|
2758
2901
|
}
|
|
2759
2902
|
export function evictGithubProjectCaches(repoRoot) {
|
|
2760
|
-
|
|
2903
|
+
const contextKey = githubContext(repoRoot).key;
|
|
2904
|
+
listCache.forEach((entry, key) => {
|
|
2905
|
+
if (entry.contextKey === contextKey)
|
|
2906
|
+
dropListEntry(key);
|
|
2907
|
+
});
|
|
2908
|
+
repositoryProjectsCache.delete(contextKey);
|
|
2909
|
+
commentCountsCache.forEach((_value, key) => {
|
|
2910
|
+
if (key.startsWith(`${contextKey}\0`))
|
|
2911
|
+
commentCountsCache.delete(key);
|
|
2912
|
+
});
|
|
2761
2913
|
mergeStateCache.forEach((_value, key) => {
|
|
2762
2914
|
if (key.startsWith(`${repoRoot}:`))
|
|
2763
2915
|
mergeStateCache.delete(key);
|
|
@@ -2777,29 +2929,53 @@ async function mergePullRequest(repoRoot, repoRef, number, input) {
|
|
|
2777
2929
|
return { merged: false, status: 409, error: 'A merge is already in progress.', code: 'concurrent' };
|
|
2778
2930
|
mergeInflight.add(key);
|
|
2779
2931
|
try {
|
|
2932
|
+
if (input.canProceed?.() === false)
|
|
2933
|
+
return { merged: false, status: 409, error: 'Automatic merging was cancelled.', code: 'cancelled' };
|
|
2780
2934
|
const fresh = await fetchPrMergeState(repoRoot, repoRef, number, true);
|
|
2935
|
+
if (input.canProceed?.() === false)
|
|
2936
|
+
return { merged: false, status: 409, error: 'Automatic merging was cancelled.', code: 'cancelled' };
|
|
2781
2937
|
if (!fresh.available)
|
|
2782
2938
|
return { merged: false, status: 502, error: fresh.reason };
|
|
2783
2939
|
const current = fresh.mergeState;
|
|
2784
2940
|
if (current.headSha !== input.expectedHeadSha) {
|
|
2785
2941
|
return { merged: false, status: 409, error: 'The pull request head changed. Review the new commits before merging.', code: 'stale-head', current };
|
|
2786
2942
|
}
|
|
2943
|
+
if (input.expectedHeadRef !== undefined && current.headRef !== input.expectedHeadRef) {
|
|
2944
|
+
return { merged: false, status: 409, error: 'The pull request branch changed.', code: 'stale-branch', current };
|
|
2945
|
+
}
|
|
2946
|
+
if (input.requirePassingChecks && (current.checks.length === 0 || current.checks.some(check => check.state !== 'passing'))) {
|
|
2947
|
+
return { merged: false, status: 409, error: 'Automatic merging requires at least one check and every current-head check passing.', code: 'checks-not-passing', current };
|
|
2948
|
+
}
|
|
2787
2949
|
if (!current.methods.includes(input.method)) {
|
|
2788
2950
|
return { merged: false, status: 409, error: 'That merge method is no longer enabled.', code: 'disabled-method', current };
|
|
2789
2951
|
}
|
|
2790
|
-
if (!mergePreflightAllowed(current, input.overrideRules)) {
|
|
2952
|
+
if (!mergePreflightAllowed(current, !input.requirePassingChecks && input.overrideRules)) {
|
|
2791
2953
|
return { merged: false, status: 409, error: current.blockers[0]?.message ?? 'The pull request is not eligible to merge.', code: current.eligibility, current };
|
|
2792
2954
|
}
|
|
2955
|
+
if (input.requirePassingChecks && process.env.CEZ_DRY_RUN !== '1') {
|
|
2956
|
+
if (!repoRef)
|
|
2957
|
+
return { merged: false, status: 404, error: 'GitHub repository not found.' };
|
|
2958
|
+
const active = parseActiveWorkflowRuns(await gh(repoRoot, activeWorkflowRunsArgs({
|
|
2959
|
+
repository: `${repoRef.owner}/${repoRef.repo}`, headSha: current.headSha,
|
|
2960
|
+
})));
|
|
2961
|
+
if (input.canProceed?.() === false)
|
|
2962
|
+
return { merged: false, status: 409, error: 'Automatic merging was cancelled.', code: 'cancelled' };
|
|
2963
|
+
if (active)
|
|
2964
|
+
return { merged: false, status: 409, error: 'Waiting for active CI workflows on the current PR head.', code: 'checks-active', current };
|
|
2965
|
+
}
|
|
2793
2966
|
if (process.env.CEZ_DRY_RUN === '1') {
|
|
2794
2967
|
evictGithubProjectCaches(repoRoot);
|
|
2795
2968
|
return { merged: true, number, url: current.url, method: input.method, mergeCommitSha: 'abcdef0123456789abcdef0123456789abcdef01' };
|
|
2796
2969
|
}
|
|
2797
2970
|
if (!repoRef)
|
|
2798
2971
|
return { merged: false, status: 404, error: 'GitHub repository not found.' };
|
|
2972
|
+
if (input.canProceed?.() === false)
|
|
2973
|
+
return { merged: false, status: 409, error: 'Automatic merging was cancelled.', code: 'cancelled' };
|
|
2799
2974
|
const out = await gh(repoRoot, [
|
|
2800
2975
|
'api', '--method', 'PUT', `repos/${repoRef.owner}/${repoRef.repo}/pulls/${number}/merge`,
|
|
2801
2976
|
'-f', `merge_method=${input.method}`, '-f', `sha=${input.expectedHeadSha}`,
|
|
2802
2977
|
]);
|
|
2978
|
+
// Once GitHub accepted the mutation, report its real outcome even if intent changed in flight.
|
|
2803
2979
|
const result = ghMergeResultSchema.parse(JSON.parse(out));
|
|
2804
2980
|
if (!result.merged)
|
|
2805
2981
|
return { merged: false, status: 409, error: result.message ?? 'GitHub refused the merge.', code: 'github-blocked', current };
|
|
@@ -2818,6 +2994,61 @@ async function mergePullRequest(repoRoot, repoRef, number, input) {
|
|
|
2818
2994
|
export function mergePreflightAllowed(current, overrideRules = false) {
|
|
2819
2995
|
return current.canMerge || (overrideRules && current.canOverride);
|
|
2820
2996
|
}
|
|
2997
|
+
/** gh has no compare-and-swap ready operation. Check ownership immediately before marking ready,
|
|
2998
|
+
* then refresh again; the later merge has its own head guard and GitHub's atomic SHA check. */
|
|
2999
|
+
async function readyPullRequest(repoRoot, repoRef, number, input) {
|
|
3000
|
+
if (!Number.isSafeInteger(number) || number < 1 || !/^[0-9a-f]{40}$/.test(input.expectedHeadSha) || !input.expectedHeadRef) {
|
|
3001
|
+
return { available: false, reason: 'A valid pull request, head SHA and task branch are required.' };
|
|
3002
|
+
}
|
|
3003
|
+
const key = `${repoRoot}:${number}`;
|
|
3004
|
+
if (mergeInflight.has(key))
|
|
3005
|
+
return { available: false, reason: 'A pull request mutation is already in progress.' };
|
|
3006
|
+
mergeInflight.add(key);
|
|
3007
|
+
try {
|
|
3008
|
+
if (input.canProceed?.() === false)
|
|
3009
|
+
return { available: false, reason: 'Marking the pull request ready was cancelled.' };
|
|
3010
|
+
const dryRun = process.env.CEZ_DRY_RUN === '1';
|
|
3011
|
+
if (!dryRun) {
|
|
3012
|
+
if (!repoRef)
|
|
3013
|
+
return { available: false, reason: 'GitHub remote could not be resolved' };
|
|
3014
|
+
const pr = mergePrSchema.pick({ number: true, url: true, state: true, isDraft: true, headRefName: true, headRefOid: true }).parse(JSON.parse(await gh(repoRoot, [
|
|
3015
|
+
'pr', 'view', String(number), '--json', 'number,url,state,isDraft,headRefName,headRefOid',
|
|
3016
|
+
])));
|
|
3017
|
+
if (input.canProceed?.() === false)
|
|
3018
|
+
return { available: false, reason: 'Marking the pull request ready was cancelled.' };
|
|
3019
|
+
if (!matchesGithubPullRequest({ ...repoRef, number }, pr))
|
|
3020
|
+
return { available: false, reason: 'GitHub returned a different pull request.' };
|
|
3021
|
+
if (pr.headRefOid !== input.expectedHeadSha)
|
|
3022
|
+
return { available: false, reason: 'The pull request head changed.' };
|
|
3023
|
+
if (pr.headRefName !== input.expectedHeadRef)
|
|
3024
|
+
return { available: false, reason: 'The pull request branch changed.' };
|
|
3025
|
+
if (pr.state !== 'OPEN')
|
|
3026
|
+
return { available: false, reason: 'The pull request is no longer open.' };
|
|
3027
|
+
if (pr.isDraft) {
|
|
3028
|
+
if (input.canProceed?.() === false)
|
|
3029
|
+
return { available: false, reason: 'Marking the pull request ready was cancelled.' };
|
|
3030
|
+
await gh(repoRoot, ['pr', 'ready', String(number), '--repo', `github.com/${repoRef.owner}/${repoRef.repo}`]);
|
|
3031
|
+
evictGithubProjectCaches(repoRoot);
|
|
3032
|
+
if (input.canProceed?.() === false)
|
|
3033
|
+
return { available: false, reason: 'Marking the pull request ready was cancelled.' };
|
|
3034
|
+
}
|
|
3035
|
+
}
|
|
3036
|
+
const fresh = await fetchPrMergeState(repoRoot, repoRef, number, true);
|
|
3037
|
+
if (input.canProceed?.() === false)
|
|
3038
|
+
return { available: false, reason: 'Marking the pull request ready was cancelled.' };
|
|
3039
|
+
if (fresh.available && fresh.mergeState.headSha !== input.expectedHeadSha)
|
|
3040
|
+
return { available: false, reason: 'The pull request head changed.' };
|
|
3041
|
+
if (fresh.available && fresh.mergeState.headRef !== input.expectedHeadRef)
|
|
3042
|
+
return { available: false, reason: 'The pull request branch changed.' };
|
|
3043
|
+
return fresh;
|
|
3044
|
+
}
|
|
3045
|
+
catch {
|
|
3046
|
+
return { available: false, reason: 'GitHub could not mark the pull request ready for review.' };
|
|
3047
|
+
}
|
|
3048
|
+
finally {
|
|
3049
|
+
mergeInflight.delete(key);
|
|
3050
|
+
}
|
|
3051
|
+
}
|
|
2821
3052
|
const GH_PR_STATES = {
|
|
2822
3053
|
MERGED: 'merged',
|
|
2823
3054
|
CLOSED: 'closed',
|
|
@@ -2827,8 +3058,8 @@ export function createGithubDriver(repoRoot, repoRef) {
|
|
|
2827
3058
|
kind: 'github',
|
|
2828
3059
|
detect: () => detectGithub(repoRoot),
|
|
2829
3060
|
detectCached: () => detectGithubCached(repoRoot),
|
|
2830
|
-
listIssues: async (opts) => (await fetchGithub(repoRoot, opts?.refresh, opts?.limit)).issues,
|
|
2831
|
-
listPRs: async (opts) => (await fetchGithub(repoRoot, opts?.refresh, opts?.limit)).prs,
|
|
3061
|
+
listIssues: async (opts) => (await fetchGithub(repoRoot, opts?.refresh, opts?.limit, opts)).issues,
|
|
3062
|
+
listPRs: async (opts) => (await fetchGithub(repoRoot, opts?.refresh, opts?.limit, opts)).prs,
|
|
2832
3063
|
// The open-only list tier's escape hatch (#730) — this is the only path that can reach a
|
|
2833
3064
|
// closed or merged item.
|
|
2834
3065
|
searchItems: (kind, query, opts) => searchGithubItems(repoRoot, kind, query, opts?.limit),
|
|
@@ -2841,8 +3072,10 @@ export function createGithubDriver(repoRoot, repoRef) {
|
|
|
2841
3072
|
if (process.env.CEZ_DRY_RUN === '1')
|
|
2842
3073
|
return null;
|
|
2843
3074
|
try {
|
|
2844
|
-
const out = await gh(repoRoot, ['pr', 'view', branch, '--json', 'number,url,state,isDraft,statusCheckRollup']);
|
|
3075
|
+
const out = await gh(repoRoot, ['pr', 'view', branch, '--json', 'number,url,state,isDraft,statusCheckRollup,mergedAt']);
|
|
2845
3076
|
const pr = ghPrViewSchema.parse(JSON.parse(out));
|
|
3077
|
+
if (repoRef)
|
|
3078
|
+
mergedPullRequestObservations.observe({ ...repoRef, number: pr.number }, pr);
|
|
2846
3079
|
return {
|
|
2847
3080
|
number: pr.number,
|
|
2848
3081
|
url: pr.url,
|
|
@@ -2856,6 +3089,7 @@ export function createGithubDriver(repoRoot, repoRef) {
|
|
|
2856
3089
|
}
|
|
2857
3090
|
},
|
|
2858
3091
|
prMergeState: (number, opts) => fetchPrMergeState(repoRoot, repoRef, number, opts?.refresh),
|
|
3092
|
+
readyPR: (number, input) => readyPullRequest(repoRoot, repoRef, number, input),
|
|
2859
3093
|
mergePR: (number, input) => mergePullRequest(repoRoot, repoRef, number, input),
|
|
2860
3094
|
viewUrl: (kind, ref) => {
|
|
2861
3095
|
if (!repoRef)
|
|
@@ -2878,4 +3112,89 @@ export function createGithubDriver(repoRoot, repoRef) {
|
|
|
2878
3112
|
},
|
|
2879
3113
|
};
|
|
2880
3114
|
}
|
|
3115
|
+
/** A missing alias must not poison unrelated families. Discard partial data and requery only
|
|
3116
|
+
* unaffected aliases once; archive authority still comes from an error-free response. */
|
|
3117
|
+
async function mergedPullRequestChunk(runGraphql, initial) {
|
|
3118
|
+
let chunk = initial;
|
|
3119
|
+
for (let attempt = 0; attempt < 2 && chunk.length; attempt++) {
|
|
3120
|
+
const query = `query($owner: String!, $name: String!) { repository(owner: $owner, name: $name) { nameWithOwner ${chunk.map(pr => `pr${pr.number}: pullRequest(number: ${pr.number}) { number url state mergedAt }`).join(' ')} } }`;
|
|
3121
|
+
let response;
|
|
3122
|
+
let failure;
|
|
3123
|
+
try {
|
|
3124
|
+
response = JSON.parse(await runGraphql(query, { owner: chunk[0].owner, name: chunk[0].repo }));
|
|
3125
|
+
}
|
|
3126
|
+
catch (error) {
|
|
3127
|
+
// gh exits 1 on GraphQL errors, with the structured envelope on stdout. Neither stderr
|
|
3128
|
+
// nor timeout/abort output is evidence that one particular alias was missing.
|
|
3129
|
+
const rejected = z.object({ code: z.literal(1), stdout: z.string() }).safeParse(error);
|
|
3130
|
+
if (!rejected.success)
|
|
3131
|
+
throw error;
|
|
3132
|
+
try {
|
|
3133
|
+
response = JSON.parse(rejected.data.stdout);
|
|
3134
|
+
}
|
|
3135
|
+
catch {
|
|
3136
|
+
throw error;
|
|
3137
|
+
}
|
|
3138
|
+
failure = error;
|
|
3139
|
+
}
|
|
3140
|
+
const partial = z.object({
|
|
3141
|
+
errors: z.array(z.object({ type: z.literal('NOT_FOUND'), path: z.tuple([z.literal('repository'), z.string()]) })).nonempty(),
|
|
3142
|
+
data: z.object({ repository: z.object({ nameWithOwner: z.string() }) }),
|
|
3143
|
+
}).safeParse(response);
|
|
3144
|
+
const aliases = new Set(chunk.map(pr => `pr${pr.number}`));
|
|
3145
|
+
if (attempt === 0 && partial.success &&
|
|
3146
|
+
partial.data.data.repository.nameWithOwner.toLowerCase() === `${chunk[0].owner}/${chunk[0].repo}`.toLowerCase() &&
|
|
3147
|
+
partial.data.errors.every(error => aliases.has(error.path[1]))) {
|
|
3148
|
+
const missing = new Set(partial.data.errors.map(error => error.path[1]));
|
|
3149
|
+
chunk = chunk.filter(pr => !missing.has(`pr${pr.number}`));
|
|
3150
|
+
continue;
|
|
3151
|
+
}
|
|
3152
|
+
if (failure)
|
|
3153
|
+
throw failure;
|
|
3154
|
+
return response;
|
|
3155
|
+
}
|
|
3156
|
+
return undefined;
|
|
3157
|
+
}
|
|
3158
|
+
/** Strict archive authority: no partial GraphQL success, redirects, guessed identity or date. */
|
|
3159
|
+
export async function fetchMergedPullRequests(runGraphql, identities) {
|
|
3160
|
+
const repositories = new Map();
|
|
3161
|
+
for (const identity of new Map(identities.map(pr => [pullRequestKey(pr), pr])).values()) {
|
|
3162
|
+
const key = `${identity.owner}/${identity.repo}`;
|
|
3163
|
+
repositories.set(key, [...(repositories.get(key) ?? []), identity]);
|
|
3164
|
+
}
|
|
3165
|
+
const merged = [];
|
|
3166
|
+
for (const [repository, prs] of repositories) {
|
|
3167
|
+
for (let offset = 0; offset < prs.length; offset += 25) {
|
|
3168
|
+
const chunk = prs.slice(offset, offset + 25);
|
|
3169
|
+
const response = await mergedPullRequestChunk(runGraphql, chunk);
|
|
3170
|
+
const parsed = z.object({ errors: z.array(z.unknown()).optional(), data: z.object({ repository: z.object({ nameWithOwner: z.string() }).passthrough().nullable() }) }).safeParse(response);
|
|
3171
|
+
if (!parsed.success || parsed.data.errors?.length || parsed.data.data.repository?.nameWithOwner.toLowerCase() !== repository.toLowerCase())
|
|
3172
|
+
continue;
|
|
3173
|
+
for (const pr of chunk) {
|
|
3174
|
+
const fact = mergedPullRequestObservations.observe(pr, parsed.data.data.repository[`pr${pr.number}`]);
|
|
3175
|
+
if (fact)
|
|
3176
|
+
merged.push(fact);
|
|
3177
|
+
}
|
|
3178
|
+
}
|
|
3179
|
+
}
|
|
3180
|
+
return merged;
|
|
3181
|
+
}
|
|
3182
|
+
export async function lookupMergedPullRequests(repoRoot, identities, signal) {
|
|
3183
|
+
signal.throwIfAborted();
|
|
3184
|
+
if (process.env.CEZ_DRY_RUN === '1')
|
|
3185
|
+
return [];
|
|
3186
|
+
const unique = [...new Map(identities.map(pr => [pullRequestKey(pr), pr])).values()];
|
|
3187
|
+
const known = unique.flatMap(pr => mergedPullRequestObservations.get(pr) ?? []);
|
|
3188
|
+
const missing = unique.filter(pr => !mergedPullRequestObservations.get(pr));
|
|
3189
|
+
if (missing.length === 0)
|
|
3190
|
+
return known;
|
|
3191
|
+
const context = githubContext(repoRoot);
|
|
3192
|
+
const fetched = await fetchMergedPullRequests(async (query, variables) => {
|
|
3193
|
+
const args = ['api', 'graphql', '--hostname', 'github.com', '-f', `query=${query}`];
|
|
3194
|
+
for (const [key, value] of Object.entries(variables))
|
|
3195
|
+
args.push('-f', `${key}=${value}`);
|
|
3196
|
+
return gh(repoRoot, args, 15_000, signal, context);
|
|
3197
|
+
}, missing);
|
|
3198
|
+
return [...known, ...fetched];
|
|
3199
|
+
}
|
|
2881
3200
|
//# sourceMappingURL=github.js.map
|