pr-shepherd 0.56.6 → 0.57.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/.claude-plugin/plugin.json +1 -1
- package/README.md +44 -27
- package/bin/api.d.mts +25 -3
- package/bin/api.mjs +51 -13
- package/bin/checks/classify.d.mts +1 -0
- package/bin/checks/classify.mjs +1 -1
- package/bin/checks/conclusions.d.mts +5 -1
- package/bin/checks/conclusions.mjs +8 -2
- package/bin/checks/superseded.d.mts +8 -2
- package/bin/checks/superseded.mjs +24 -7
- package/bin/classify/loader.d.mts +2 -0
- package/bin/classify/loader.mjs +15 -0
- package/bin/classify/standard/author.d.mts +3 -0
- package/bin/classify/standard/author.mjs +10 -0
- package/bin/classify/standard/codecov-report.d.mts +4 -0
- package/bin/classify/standard/codecov-report.mjs +10 -0
- package/bin/classify/standard/coderabbit-notices.d.mts +8 -0
- package/bin/classify/standard/coderabbit-notices.mjs +38 -0
- package/bin/classify/standard/coderabbit-review-summary.d.mts +8 -0
- package/bin/classify/standard/coderabbit-review-summary.mjs +37 -0
- package/bin/classify/standard/codex-notices.d.mts +7 -0
- package/bin/classify/standard/codex-notices.mjs +36 -0
- package/bin/classify/standard/codex-review-summary.d.mts +7 -0
- package/bin/classify/standard/codex-review-summary.mjs +20 -0
- package/bin/classify/standard/dependabot-notices.d.mts +4 -0
- package/bin/classify/standard/dependabot-notices.mjs +15 -0
- package/bin/classify/standard/gemini-notices.d.mts +4 -0
- package/bin/classify/standard/gemini-notices.mjs +13 -0
- package/bin/classify/standard/index.d.mts +4 -0
- package/bin/classify/standard/index.mjs +22 -0
- package/bin/classify/standard/kilo-no-issues.d.mts +4 -0
- package/bin/classify/standard/kilo-no-issues.mjs +17 -0
- package/bin/classify/standard/sourcery-notices.d.mts +8 -0
- package/bin/classify/standard/sourcery-notices.mjs +20 -0
- package/bin/cli/api-usage-formatter.mjs +6 -1
- package/bin/cli/apply-merge-handler.d.mts +3 -0
- package/bin/cli/apply-merge-handler.mjs +73 -0
- package/bin/cli/arg-flags.d.mts +2 -0
- package/bin/cli/arg-flags.mjs +47 -0
- package/bin/cli/args.mjs +10 -41
- package/bin/cli/clean-handler.d.mts +1 -0
- package/bin/cli/clean-handler.mjs +71 -0
- package/bin/cli/default-poll.mjs +3 -0
- package/bin/cli/event-state-scope.d.mts +19 -0
- package/bin/cli/event-state-scope.mjs +60 -0
- package/bin/cli/fix-formatter.mjs +5 -11
- package/bin/cli/handlers.d.mts +0 -1
- package/bin/cli/handlers.mjs +8 -64
- package/bin/cli/help-command-pages.d.mts +62 -18
- package/bin/cli/help-command-pages.mjs +59 -15
- package/bin/cli/help-iterate-poll-pages.d.mts +2 -2
- package/bin/cli/help-iterate-poll-pages.mjs +6 -0
- package/bin/cli/help-log-file-page.d.mts +1 -1
- package/bin/cli/help-log-file-page.mjs +4 -1
- package/bin/cli/help-top-page.d.mts +1 -1
- package/bin/cli/help-top-page.mjs +5 -0
- package/bin/cli/help.d.mts +63 -19
- package/bin/cli/help.mjs +2 -0
- package/bin/cli/iterate-branch-segment.d.mts +2 -0
- package/bin/cli/iterate-branch-segment.mjs +12 -7
- package/bin/cli/iterate-flags.d.mts +7 -1
- package/bin/cli/iterate-flags.mjs +38 -0
- package/bin/cli/iterate-formatter.d.mts +6 -6
- package/bin/cli/iterate-formatter.mjs +56 -22
- package/bin/cli/iterate-instructions.mjs +45 -15
- package/bin/cli/iterate-lean.mjs +39 -14
- package/bin/cli/iterate-merge-formatter.d.mts +6 -0
- package/bin/cli/iterate-merge-formatter.mjs +24 -14
- package/bin/cli/iterate-verbose.mjs +3 -6
- package/bin/cli/list-formatters.mjs +3 -1
- package/bin/cli/mutate-formatter.mjs +10 -5
- package/bin/cli/next-check-format.d.mts +7 -0
- package/bin/cli/next-check-format.mjs +10 -0
- package/bin/cli/playbook-handler.d.mts +5 -0
- package/bin/cli/playbook-handler.mjs +40 -0
- package/bin/cli/poll-handler.mjs +7 -6
- package/bin/cli/poll-summary-formatter.mjs +11 -2
- package/bin/cli/poll-targets.mjs +2 -0
- package/bin/cli/resolve-validators.mjs +2 -2
- package/bin/cli/runner.mjs +6 -1
- package/bin/cli/stack-layer-view.d.mts +33 -0
- package/bin/cli/stack-layer-view.mjs +1 -0
- package/bin/cli/stack-overview.d.mts +10 -28
- package/bin/cli/stack-overview.mjs +39 -14
- package/bin/cli/transport-args.d.mts +5 -0
- package/bin/cli/transport-args.mjs +23 -0
- package/bin/cli/transport-formatter.d.mts +9 -0
- package/bin/cli/transport-formatter.mjs +13 -0
- package/bin/cli-parser.mjs +62 -7
- package/bin/commands/apply-merge-options.d.mts +4 -0
- package/bin/commands/apply-merge-options.mjs +34 -0
- package/bin/commands/apply-merge-output.d.mts +7 -0
- package/bin/commands/apply-merge-output.mjs +20 -0
- package/bin/commands/apply-merge-pull.d.mts +20 -0
- package/bin/commands/apply-merge-pull.mjs +15 -0
- package/bin/commands/apply-merge.d.mts +18 -0
- package/bin/commands/apply-merge.mjs +126 -0
- package/bin/commands/apply-queue-removal.mjs +7 -0
- package/bin/commands/check-annotations.mjs +28 -18
- package/bin/commands/check-execution-context.d.mts +5 -0
- package/bin/commands/check-execution-context.mjs +16 -1
- package/bin/commands/check-fingerprint.d.mts +26 -2
- package/bin/commands/check-fingerprint.mjs +64 -28
- package/bin/commands/check-terminal-report.mjs +11 -1
- package/bin/commands/check-unreported.d.mts +6 -2
- package/bin/commands/check-unreported.mjs +32 -12
- package/bin/commands/check-wait-detectors.d.mts +35 -0
- package/bin/commands/check-wait-detectors.mjs +107 -0
- package/bin/commands/check.d.mts +4 -2
- package/bin/commands/check.mjs +130 -40
- package/bin/commands/event-instructions.d.mts +11 -0
- package/bin/commands/event-instructions.mjs +27 -0
- package/bin/commands/event-mode.d.mts +14 -0
- package/bin/commands/event-mode.mjs +138 -0
- package/bin/commands/iterate/api-usage.mjs +4 -2
- package/bin/commands/iterate/base.mjs +4 -0
- package/bin/commands/iterate/check-blocker-gate.mjs +14 -2
- package/bin/commands/iterate/check-instructions.d.mts +9 -10
- package/bin/commands/iterate/check-instructions.mjs +23 -22
- package/bin/commands/iterate/classify.mjs +19 -5
- package/bin/commands/iterate/escalate.mjs +13 -3
- package/bin/commands/iterate/fix-code.mjs +26 -9
- package/bin/commands/iterate/fix-instruction-threads.d.mts +0 -8
- package/bin/commands/iterate/fix-instruction-threads.mjs +0 -16
- package/bin/commands/iterate/fix-loop-instruction.d.mts +9 -0
- package/bin/commands/iterate/fix-loop-instruction.mjs +35 -0
- package/bin/commands/iterate/helpers.mjs +15 -2
- package/bin/commands/iterate/index.mjs +26 -4
- package/bin/commands/iterate/mark-ready.mjs +50 -10
- package/bin/commands/iterate/merge-plan.d.mts +19 -0
- package/bin/commands/iterate/merge-plan.mjs +88 -0
- package/bin/commands/iterate/merge-state.mjs +3 -0
- package/bin/commands/iterate/merge.d.mts +4 -15
- package/bin/commands/iterate/merge.mjs +18 -51
- package/bin/commands/iterate/merged-base-pull-requests.d.mts +2 -6
- package/bin/commands/iterate/merged-base-pull-requests.mjs +15 -11
- package/bin/commands/iterate/native-stack-rebase.mjs +11 -0
- package/bin/commands/iterate/render.d.mts +8 -2
- package/bin/commands/iterate/render.mjs +28 -42
- package/bin/commands/iterate/stack-trunk-conflict.mjs +17 -1
- package/bin/commands/iterate/stale-ancestry.mjs +4 -1
- package/bin/commands/iterate/stall.d.mts +5 -1
- package/bin/commands/iterate/stall.mjs +19 -7
- package/bin/commands/iterate/thread-mutation-routing.d.mts +1 -0
- package/bin/commands/iterate/thread-mutation-routing.mjs +4 -2
- package/bin/commands/iterate/unreported-required.mjs +5 -2
- package/bin/commands/mark-files-as-viewed.mjs +7 -0
- package/bin/commands/next-check.d.mts +31 -0
- package/bin/commands/next-check.mjs +46 -0
- package/bin/commands/playbook.d.mts +13 -0
- package/bin/commands/playbook.mjs +48 -0
- package/bin/commands/poll-mode.d.mts +8 -0
- package/bin/commands/poll-mode.mjs +14 -0
- package/bin/commands/poll-progress.d.mts +1 -0
- package/bin/commands/poll-progress.mjs +6 -3
- package/bin/commands/poll-quota.d.mts +3 -2
- package/bin/commands/poll-quota.mjs +7 -3
- package/bin/commands/poll-summary-explicit-instructions.d.mts +2 -0
- package/bin/commands/poll-summary-explicit-instructions.mjs +10 -1
- package/bin/commands/poll-summary-instructions.mjs +16 -17
- package/bin/commands/poll-summary.mjs +42 -17
- package/bin/commands/poll.d.mts +0 -1
- package/bin/commands/poll.mjs +11 -19
- package/bin/commands/quota-selection.d.mts +2 -1
- package/bin/commands/quota-selection.mjs +12 -7
- package/bin/commands/resolve-mutate.mjs +33 -5
- package/bin/commands/rest-stack-merge-readiness.d.mts +4 -0
- package/bin/commands/rest-stack-merge-readiness.mjs +74 -0
- package/bin/commands/rule-auto-resolve.mjs +9 -3
- package/bin/commands/shepherd-journal.d.mts +5 -4
- package/bin/commands/shepherd-journal.mjs +9 -8
- package/bin/commands/stack-drain.d.mts +1 -9
- package/bin/commands/stack-drain.mjs +50 -37
- package/bin/commands/stack-handoffs.d.mts +10 -0
- package/bin/commands/stack-handoffs.mjs +25 -0
- package/bin/commands/stack-stall.d.mts +5 -0
- package/bin/commands/stack-stall.mjs +11 -0
- package/bin/commands/stack-work.d.mts +5 -0
- package/bin/commands/stack-work.mjs +32 -0
- package/bin/comments/existing-reply-scan.d.mts +17 -0
- package/bin/comments/existing-reply-scan.mjs +41 -0
- package/bin/comments/rate-limit.mjs +2 -2
- package/bin/comments/resolve.d.mts +7 -1
- package/bin/comments/resolve.mjs +108 -18
- package/bin/comments/rest-reply.d.mts +9 -0
- package/bin/comments/rest-reply.mjs +95 -0
- package/bin/comments/rest-review-mutations.d.mts +22 -0
- package/bin/comments/rest-review-mutations.mjs +125 -0
- package/bin/comments/review-visibility.d.mts +1 -1
- package/bin/comments/review-visibility.mjs +3 -2
- package/bin/comments/sha-poll.mjs +7 -3
- package/bin/comments/thread-visibility.mjs +10 -5
- package/bin/comments/uncertain-replies.d.mts +11 -0
- package/bin/comments/uncertain-replies.mjs +121 -0
- package/bin/comments/uncertain-reply-guards.d.mts +10 -0
- package/bin/comments/uncertain-reply-guards.mjs +94 -0
- package/bin/comments/visible-comments.d.mts +1 -1
- package/bin/comments/visible-comments.mjs +4 -2
- package/bin/config/load.d.mts +37 -1
- package/bin/config/load.mjs +73 -1
- package/bin/config.json +6 -2
- package/bin/exit-codes.d.mts +3 -0
- package/bin/exit-codes.mjs +8 -0
- package/bin/github/api-telemetry-aggregate.d.mts +15 -4
- package/bin/github/api-telemetry-aggregate.mjs +57 -18
- package/bin/github/api-telemetry.d.mts +4 -0
- package/bin/github/api-telemetry.mjs +22 -8
- package/bin/github/apply-review-preflight.d.mts +20 -0
- package/bin/github/apply-review-preflight.mjs +61 -0
- package/bin/github/base-behind-cache.d.mts +15 -0
- package/bin/github/base-behind-cache.mjs +18 -0
- package/bin/github/batch-parse-suites.d.mts +4 -2
- package/bin/github/batch-parse-suites.mjs +6 -1
- package/bin/github/batch-parser-helpers.mjs +40 -2
- package/bin/github/batch-parsers.mjs +1 -0
- package/bin/github/batch-raw-rules.d.mts +26 -0
- package/bin/github/batch-raw-types.d.mts +8 -6
- package/bin/github/batch-stack-evidence.d.mts +28 -0
- package/bin/github/batch-stack-evidence.mjs +62 -0
- package/bin/github/batch.d.mts +15 -2
- package/bin/github/batch.mjs +34 -17
- package/bin/github/check-annotations-batch.d.mts +0 -5
- package/bin/github/check-annotations-batch.mjs +25 -4
- package/bin/github/check-annotations.mjs +3 -1
- package/bin/github/client.d.mts +8 -19
- package/bin/github/client.mjs +45 -13
- package/bin/github/fingerprint-fields.d.mts +8 -0
- package/bin/github/fingerprint-fields.mjs +6 -1
- package/bin/github/fingerprint.d.mts +4 -2
- package/bin/github/fingerprint.mjs +6 -38
- package/bin/github/github-fetch.d.mts +2 -0
- package/bin/github/github-fetch.mjs +19 -0
- package/bin/github/gql/base-behind.gql +3 -0
- package/bin/github/gql/batch-pr-page.gql +16 -0
- package/bin/github/gql/batch-pr.gql +57 -0
- package/bin/github/gql/commit-check-contexts.gql +17 -0
- package/bin/github/gql/get-pr-body.gql +1 -0
- package/bin/github/gql/poll-stack-summary.gql +1 -0
- package/bin/github/gql/poll-summary-check-contexts.gql +5 -0
- package/bin/github/gql/poll-summary-fragment.gql +65 -15
- package/bin/github/gql/stack-handoff-check-runs.gql +42 -0
- package/bin/github/graphql-http.mjs +9 -4
- package/bin/github/graphql-internal-retry.d.mts +2 -0
- package/bin/github/graphql-internal-retry.mjs +1 -1
- package/bin/github/graphql-quota-probe.d.mts +6 -0
- package/bin/github/graphql-quota-probe.mjs +56 -0
- package/bin/github/graphql-session-refusal.d.mts +8 -0
- package/bin/github/graphql-session-refusal.mjs +14 -0
- package/bin/github/http-request.d.mts +1 -1
- package/bin/github/http-request.mjs +22 -6
- package/bin/github/http-utils.mjs +6 -0
- package/bin/github/merge-target-rules.d.mts +12 -3
- package/bin/github/merge-target-rules.mjs +71 -34
- package/bin/github/mutation-policy.d.mts +6 -0
- package/bin/github/mutation-policy.mjs +21 -0
- package/bin/github/poll-summary-annotation-probe.mjs +17 -10
- package/bin/github/poll-summary-check-blockers.d.mts +1 -1
- package/bin/github/poll-summary-check-blockers.mjs +2 -2
- package/bin/github/poll-summary-check-hydration.mjs +25 -10
- package/bin/github/poll-summary-checks.d.mts +2 -2
- package/bin/github/poll-summary-checks.mjs +9 -6
- package/bin/github/poll-summary-command.d.mts +2 -0
- package/bin/github/poll-summary-command.mjs +24 -0
- package/bin/github/poll-summary-fingerprint.d.mts +1 -1
- package/bin/github/poll-summary-fingerprint.mjs +4 -2
- package/bin/github/poll-summary-handoff-fields.d.mts +3 -0
- package/bin/github/poll-summary-handoff-fields.mjs +73 -0
- package/bin/github/poll-summary-projector.d.mts +1 -1
- package/bin/github/poll-summary-projector.mjs +17 -24
- package/bin/github/poll-summary-raw.d.mts +70 -10
- package/bin/github/poll-summary-readiness.mjs +4 -1
- package/bin/github/poll-summary-rest-queue.d.mts +3 -0
- package/bin/github/poll-summary-rest-queue.mjs +12 -0
- package/bin/github/poll-summary-review.d.mts +1 -1
- package/bin/github/poll-summary-review.mjs +23 -11
- package/bin/github/poll-summary-route.mjs +19 -3
- package/bin/github/poll-summary-stack-page.d.mts +11 -0
- package/bin/github/poll-summary-stack-page.mjs +27 -0
- package/bin/github/poll-summary-unreported.d.mts +3 -1
- package/bin/github/poll-summary-unreported.mjs +6 -3
- package/bin/github/poll-summary.d.mts +4 -1
- package/bin/github/poll-summary.mjs +47 -33
- package/bin/github/queries.d.mts +7 -3
- package/bin/github/queries.mjs +8 -4
- package/bin/github/rate-limit-sample.d.mts +7 -0
- package/bin/github/rate-limit-sample.mjs +12 -0
- package/bin/github/reply-recovery-read.d.mts +50 -0
- package/bin/github/reply-recovery-read.mjs +118 -0
- package/bin/github/reply-thread-transcripts.d.mts +0 -1
- package/bin/github/reply-thread-transcripts.mjs +24 -1
- package/bin/github/rest-annotation-read.d.mts +5 -0
- package/bin/github/rest-annotation-read.mjs +44 -0
- package/bin/github/rest-batch-read.d.mts +12 -0
- package/bin/github/rest-batch-read.mjs +57 -0
- package/bin/github/rest-batch-snapshot.d.mts +30 -0
- package/bin/github/rest-batch-snapshot.mjs +115 -0
- package/bin/github/rest-batch-unavailable.d.mts +5 -0
- package/bin/github/rest-batch-unavailable.mjs +16 -0
- package/bin/github/rest-check-read.d.mts +34 -0
- package/bin/github/rest-check-read.mjs +124 -0
- package/bin/github/rest-comment-authorship.d.mts +7 -0
- package/bin/github/rest-comment-authorship.mjs +11 -0
- package/bin/github/rest-conditional-scope.d.mts +20 -0
- package/bin/github/rest-conditional-scope.mjs +44 -0
- package/bin/github/rest-explicit-summary-read.d.mts +3 -0
- package/bin/github/rest-explicit-summary-read.mjs +8 -0
- package/bin/github/rest-feedback-read.d.mts +36 -0
- package/bin/github/rest-feedback-read.mjs +147 -0
- package/bin/github/rest-feedback-validation.d.mts +5 -0
- package/bin/github/rest-feedback-validation.mjs +28 -0
- package/bin/github/rest-http.d.mts +12 -0
- package/bin/github/rest-http.mjs +108 -23
- package/bin/github/rest-identities.d.mts +22 -0
- package/bin/github/rest-identities.mjs +134 -0
- package/bin/github/rest-merge-stack-guard.d.mts +14 -0
- package/bin/github/rest-merge-stack-guard.mjs +41 -0
- package/bin/github/rest-merge.d.mts +29 -0
- package/bin/github/rest-merge.mjs +56 -0
- package/bin/github/rest-merged-base-read.d.mts +9 -0
- package/bin/github/rest-merged-base-read.mjs +48 -0
- package/bin/github/rest-pr-core.d.mts +52 -0
- package/bin/github/rest-pr-core.mjs +60 -0
- package/bin/github/rest-reader-core.d.mts +16 -0
- package/bin/github/rest-reader-core.mjs +154 -0
- package/bin/github/rest-review-read.d.mts +11 -0
- package/bin/github/rest-review-read.mjs +47 -0
- package/bin/github/rest-rules-read.d.mts +9 -0
- package/bin/github/rest-rules-read.mjs +154 -0
- package/bin/github/rest-session-refusal.d.mts +5 -0
- package/bin/github/rest-session-refusal.mjs +21 -0
- package/bin/github/rest-snapshot-context.d.mts +10 -0
- package/bin/github/rest-snapshot-context.mjs +1 -0
- package/bin/github/rest-stack-read.d.mts +21 -0
- package/bin/github/rest-stack-read.mjs +47 -0
- package/bin/github/rest-stack-summary.d.mts +8 -0
- package/bin/github/rest-stack-summary.mjs +62 -0
- package/bin/github/rest-summary-read.d.mts +7 -0
- package/bin/github/rest-summary-read.mjs +112 -0
- package/bin/github/rest-terminal-batch.d.mts +14 -0
- package/bin/github/rest-terminal-batch.mjs +45 -0
- package/bin/github/rest-text.mjs +7 -3
- package/bin/github/rest-thread-read.d.mts +4 -0
- package/bin/github/rest-thread-read.mjs +35 -0
- package/bin/github/rest-viewer-read.d.mts +2 -0
- package/bin/github/rest-viewer-read.mjs +41 -0
- package/bin/github/stack-bottom.d.mts +3 -0
- package/bin/github/stack-bottom.mjs +14 -0
- package/bin/github/stack-handoff-context.d.mts +25 -0
- package/bin/github/stack-handoff-context.mjs +12 -0
- package/bin/github/stack-handoff-page.d.mts +17 -0
- package/bin/github/stack-handoff-page.mjs +306 -0
- package/bin/github/stack-handoff.d.mts +16 -0
- package/bin/github/stack-handoff.mjs +115 -0
- package/bin/github/stack-order.d.mts +6 -0
- package/bin/github/stack-order.mjs +13 -0
- package/bin/github/stack-read.d.mts +7 -5
- package/bin/github/stack-read.mjs +22 -24
- package/bin/github/stack-size-hint.d.mts +3 -0
- package/bin/github/stack-size-hint.mjs +14 -0
- package/bin/github/suggestion-thread.mjs +27 -1
- package/bin/github/thread-comments.mjs +9 -1
- package/bin/github/transport-evidence.d.mts +20 -0
- package/bin/github/transport-evidence.mjs +26 -0
- package/bin/github/transport-mode.d.mts +2 -0
- package/bin/github/transport-mode.mjs +5 -0
- package/bin/github/transport.d.mts +19 -0
- package/bin/github/transport.mjs +126 -0
- package/bin/github/unsupported-rest.d.mts +4 -0
- package/bin/github/unsupported-rest.mjs +7 -0
- package/bin/mcp/server.mjs +52 -3
- package/bin/merge-status/derive.d.mts +2 -0
- package/bin/merge-status/derive.mjs +17 -13
- package/bin/merge-status/required-check-requirements.d.mts +7 -0
- package/bin/merge-status/required-check-requirements.mjs +15 -0
- package/bin/merge-status/requirements-format.d.mts +5 -0
- package/bin/merge-status/requirements-format.mjs +41 -12
- package/bin/merge-status/requirements.mjs +18 -7
- package/bin/state/base.mjs +41 -0
- package/bin/state/durable-state.d.mts +6 -0
- package/bin/state/durable-state.mjs +41 -0
- package/bin/state/graphql-quota-policy.mjs +23 -16
- package/bin/state/merge-request.d.mts +25 -0
- package/bin/state/merge-request.mjs +145 -0
- package/bin/state/pr-fingerprint.d.mts +15 -0
- package/bin/state/pr-fingerprint.mjs +22 -7
- package/bin/state/rest-cache.d.mts +3 -0
- package/bin/state/rest-cache.mjs +1 -0
- package/bin/state/rest-snapshot-report.d.mts +21 -0
- package/bin/state/rest-snapshot-report.mjs +52 -0
- package/bin/state/seen-comments.d.mts +3 -0
- package/bin/state/seen-comments.mjs +18 -0
- package/bin/types/api-usage.d.mts +9 -1
- package/bin/types/escalate.d.mts +1 -1
- package/bin/types/github.d.mts +23 -9
- package/bin/types/iterate.d.mts +24 -2
- package/bin/types/merge-action.d.mts +1 -1
- package/bin/types/merge-requirements.d.mts +7 -7
- package/bin/types/next-check.d.mts +12 -0
- package/bin/types/next-check.mjs +1 -0
- package/bin/types/poll-summary.d.mts +21 -0
- package/bin/types/report.d.mts +13 -1
- package/bin/types/review-thread.d.mts +1 -1
- package/bin/types.d.mts +1 -0
- package/bin/types.mjs +1 -0
- package/package.json +3 -2
- package/plugins/pr-shepherd/.codex-plugin/plugin.json +1 -1
- package/plugins/pr-shepherd/.codex.mcp.json +1 -1
- package/plugins/pr-shepherd/.mcp.json +1 -1
- package/plugins/pr-shepherd/skills/mark-files-as-viewed/SKILL.md +1 -1
- package/plugins/pr-shepherd/skills/pr-shepherd/SKILL.md +14 -51
- package/plugins/pr-shepherd/skills/pr-shepherd/references/ci-failure-triage.md +12 -16
- package/plugins/pr-shepherd/skills/pr-shepherd/references/cloud-event-loop.md +14 -0
- package/plugins/pr-shepherd/skills/pr-shepherd/references/create-pr.md +10 -0
- package/plugins/pr-shepherd/skills/pr-shepherd/references/fix-code-loop.md +10 -0
- package/plugins/pr-shepherd/skills/pr-shepherd/references/mcp-fallback.md +9 -0
- package/plugins/pr-shepherd/skills/reduce-pr-noise/references/classifiers.md +2 -0
- package/bin/github/gql/pr-fingerprint.gql +0 -75
- package/plugins/pr-shepherd/skills/pr-shepherd/references/journal.md +0 -7
- package/plugins/pr-shepherd/skills/pr-shepherd/references/review-mutations.md +0 -9
- package/plugins/pr-shepherd/skills/pr-shepherd/references/stack-merge.md +0 -7
package/bin/comments/resolve.mjs
CHANGED
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
/* eslint-disable max-lines */
|
|
2
2
|
import { graphqlWithRateLimit } from "../github/client.mjs";
|
|
3
3
|
import { addPrShepherdMarker } from "./marker.mjs";
|
|
4
|
+
import { findExistingReplies } from "./existing-reply-scan.mjs";
|
|
4
5
|
import { isRateLimitMessage, rateLimitFromError, rateLimitFromGraphQlResult, } from "./rate-limit.mjs";
|
|
5
6
|
import { setPendingOps } from "./pending-ops.mjs";
|
|
6
7
|
import { waitForSha } from "./sha-poll.mjs";
|
|
8
|
+
import { githubOperation, getGithubTransport } from "../github/transport.mjs";
|
|
9
|
+
import { resolveGraphqlThreadId } from "../github/rest-identities.mjs";
|
|
10
|
+
import { applyRestReviewChunk, isAmbiguousMutationError } from "./rest-review-mutations.mjs";
|
|
11
|
+
import { assertReplyOutcomeKnown, rememberUncertainReplies, trackAdoptedReplyThreads, } from "./uncertain-replies.mjs";
|
|
12
|
+
import { isRestSessionRefusal } from "../github/rest-session-refusal.mjs";
|
|
13
|
+
import { EXIT, ShepherdError } from "../exit-codes.mjs";
|
|
14
|
+
import { readReplyRecoveryEvidence } from "../github/reply-recovery-read.mjs";
|
|
15
|
+
import { GitHubRequestError, isRetryableGraphQlInternal, } from "../github/errors.mjs";
|
|
7
16
|
const COMMENTED_DISMISS_ERROR_PATTERNS = [
|
|
8
17
|
/can\s*not\s+dismiss[\s\S]*?commented pull request review/i,
|
|
9
18
|
];
|
|
@@ -30,7 +39,15 @@ function mutationErrorMessage(errors, alias) {
|
|
|
30
39
|
.map((error) => error.message);
|
|
31
40
|
return messages.length > 0 ? messages.join("; ") : undefined;
|
|
32
41
|
}
|
|
33
|
-
|
|
42
|
+
function uncertainReplyIds(ids, confirmed, errors) {
|
|
43
|
+
return ids.filter((id, index) => {
|
|
44
|
+
if (confirmed.includes(id))
|
|
45
|
+
return false;
|
|
46
|
+
const applicable = errors.filter((error) => error.path === undefined || (Array.isArray(error.path) && error.path[0] === `p${index}`));
|
|
47
|
+
return applicable.length === 0 || isRetryableGraphQlInternal(applicable);
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
export async function applyResolveOptions(pr, repo, opts, preflight) {
|
|
34
51
|
const resolveThreadIds = dedupeIds(opts.resolveThreadIds ?? []);
|
|
35
52
|
const replyThreadIds = dedupeIds(opts.replyThreadIds ?? []);
|
|
36
53
|
const minimizeCommentIds = opts.minimizeCommentIds ?? [];
|
|
@@ -45,12 +62,30 @@ export async function applyResolveOptions(pr, repo, opts) {
|
|
|
45
62
|
if ((dismissReviewIds.length > 0 || replyThreadIds.length > 0) && !opts.dismissMessage) {
|
|
46
63
|
throw new Error("--message is required when replying to threads or dismissing reviews");
|
|
47
64
|
}
|
|
48
|
-
|
|
65
|
+
// A preflight that already saw the required head stands in for the first poll; its
|
|
66
|
+
// recovery evidence is used only when no poll wait separated it from the mutation.
|
|
67
|
+
const headConfirmed = !opts.requireSha || preflight?.headSha === opts.requireSha;
|
|
68
|
+
const preloadedEvidence = headConfirmed ? preflight?.evidence : undefined;
|
|
69
|
+
if (opts.requireSha && !headConfirmed) {
|
|
49
70
|
// Verify GitHub received the commit before resolving — prevents auto-merge
|
|
50
71
|
// before reviewers see the fix.
|
|
51
72
|
await waitForSha(pr, repo, opts.requireSha);
|
|
52
73
|
}
|
|
53
|
-
await
|
|
74
|
+
const known = await assertReplyOutcomeKnown({ repo, pr }, replyThreadIds, opts.dismissMessage ?? "");
|
|
75
|
+
const adopted = [
|
|
76
|
+
...known,
|
|
77
|
+
...(opts.adoptExistingReplies
|
|
78
|
+
? await findExistingReplies({ repo, pr }, replyThreadIds.filter((id) => !known.includes(id)), opts.dismissMessage ?? "", preloadedEvidence)
|
|
79
|
+
: []),
|
|
80
|
+
];
|
|
81
|
+
result.repliedThreads.push(...adopted);
|
|
82
|
+
trackAdoptedReplyThreads(result, adopted);
|
|
83
|
+
await bulkApply(replyThreadIds.filter((id) => !adopted.includes(id)), resolveThreadIds, minimizeCommentIds, dismissReviewIds, opts.dismissMessage ?? "", result, { repo, pr }, preloadedEvidence);
|
|
84
|
+
if (result.sessionRefusal)
|
|
85
|
+
result.instructions = [
|
|
86
|
+
"Restore GitHub access for this session using the proxy instructions above.",
|
|
87
|
+
"Retry only the pending IDs listed above.",
|
|
88
|
+
];
|
|
54
89
|
return result;
|
|
55
90
|
}
|
|
56
91
|
/** @deprecated Compatibility alias; use `autoResolveThreads`. */
|
|
@@ -68,6 +103,8 @@ export async function autoResolveThreads(threadIds) {
|
|
|
68
103
|
errors: [],
|
|
69
104
|
};
|
|
70
105
|
await bulkApply([], threadIds, [], [], "", result);
|
|
106
|
+
if (result.sessionRefusal)
|
|
107
|
+
throw new ShepherdError(result.sessionRefusal, EXIT.NOPERM);
|
|
71
108
|
return { resolved: result.resolvedThreads, errors: result.errors };
|
|
72
109
|
}
|
|
73
110
|
export async function autoMinimizeComments(minimizeIds) {
|
|
@@ -81,6 +118,8 @@ export async function autoMinimizeComments(minimizeIds) {
|
|
|
81
118
|
errors: [],
|
|
82
119
|
};
|
|
83
120
|
await bulkApply([], [], minimizeIds, [], "", result);
|
|
121
|
+
if (result.sessionRefusal)
|
|
122
|
+
throw new ShepherdError(result.sessionRefusal, EXIT.NOPERM);
|
|
84
123
|
return { minimized: result.minimizedComments, errors: result.errors };
|
|
85
124
|
}
|
|
86
125
|
// Keep mutation batches small so rate-limit stops leave a precise pending list.
|
|
@@ -102,7 +141,7 @@ function buildBulkMutation(replyIds, resolveIds, minimizeIds, dismissIds, dismis
|
|
|
102
141
|
}
|
|
103
142
|
return `mutation BulkApply {\n${ops.join("\n")}\n}`;
|
|
104
143
|
}
|
|
105
|
-
async function bulkApply(replyIds, resolveIds, minimizeIds, dismissIds, dismissMessage, result) {
|
|
144
|
+
async function bulkApply(replyIds, resolveIds, minimizeIds, dismissIds, dismissMessage, result, context, firstChunkEvidence) {
|
|
106
145
|
const allOps = [
|
|
107
146
|
...replyIds.map((id) => ({ kind: "p", id })),
|
|
108
147
|
...resolveIds.map((id) => ({ kind: "r", id })),
|
|
@@ -112,34 +151,76 @@ async function bulkApply(replyIds, resolveIds, minimizeIds, dismissIds, dismissM
|
|
|
112
151
|
for (let i = 0; i < allOps.length; i += BULK_CHUNK_SIZE) {
|
|
113
152
|
const chunk = allOps.slice(i, i + BULK_CHUNK_SIZE);
|
|
114
153
|
// eslint-disable-next-line no-await-in-loop
|
|
115
|
-
const stopped = await bulkApplyChunk(chunk.filter((o) => o.kind === "r").map((o) => o.id), chunk.filter((o) => o.kind === "p").map((o) => o.id), chunk.filter((o) => o.kind === "m").map((o) => o.id), chunk.filter((o) => o.kind === "d").map((o) => o.id), dismissMessage, result, i + BULK_CHUNK_SIZE < allOps.length);
|
|
154
|
+
const stopped = await bulkApplyChunk(chunk.filter((o) => o.kind === "r").map((o) => o.id), chunk.filter((o) => o.kind === "p").map((o) => o.id), chunk.filter((o) => o.kind === "m").map((o) => o.id), chunk.filter((o) => o.kind === "d").map((o) => o.id), dismissMessage, result, i + BULK_CHUNK_SIZE < allOps.length, context, i === 0 ? firstChunkEvidence : undefined);
|
|
116
155
|
if (stopped) {
|
|
117
156
|
setPendingOps(result, allOps.slice(i));
|
|
118
157
|
return;
|
|
119
158
|
}
|
|
120
159
|
}
|
|
121
160
|
}
|
|
122
|
-
async function bulkApplyChunk(resolveIds, replyIds, minimizeIds, dismissIds, dismissMessage, result, hasPendingAfter) {
|
|
123
|
-
const doc = buildBulkMutation(replyIds, resolveIds, minimizeIds, dismissIds, dismissMessage);
|
|
161
|
+
async function bulkApplyChunk(resolveIds, replyIds, minimizeIds, dismissIds, dismissMessage, result, hasPendingAfter, context, preloadedEvidence) {
|
|
124
162
|
let data = {};
|
|
125
163
|
let graphQlErrors = [];
|
|
126
164
|
let rateLimitStop;
|
|
127
165
|
let suppressCurrentChunkErrors = false;
|
|
166
|
+
let restStopped = false;
|
|
167
|
+
let graphqlMutationAttempted = false;
|
|
168
|
+
let replyEvidence = new Map();
|
|
169
|
+
if (context && replyIds.length && getGithubTransport() === "graphql") {
|
|
170
|
+
// Evidence is all-or-nothing per batch, as the standalone read is.
|
|
171
|
+
if (replyIds.every((id) => preloadedEvidence?.has(id)))
|
|
172
|
+
replyEvidence = new Map(replyIds.map((id) => [id, preloadedEvidence.get(id)]));
|
|
173
|
+
else
|
|
174
|
+
try {
|
|
175
|
+
replyEvidence = await readReplyRecoveryEvidence(context.repo, context.pr, replyIds);
|
|
176
|
+
}
|
|
177
|
+
catch {
|
|
178
|
+
/* Preserve explicit mutation eligibility when recovery evidence is unavailable. */
|
|
179
|
+
}
|
|
180
|
+
}
|
|
128
181
|
try {
|
|
129
|
-
const resp = await
|
|
130
|
-
|
|
131
|
-
|
|
182
|
+
const resp = await githubOperation("BulkApply", async () => {
|
|
183
|
+
const [graphqlReplies, graphqlResolves] = await Promise.all([
|
|
184
|
+
Promise.all(replyIds.map(resolveGraphqlThreadId)),
|
|
185
|
+
Promise.all(resolveIds.map(resolveGraphqlThreadId)),
|
|
186
|
+
]);
|
|
187
|
+
graphqlMutationAttempted = true;
|
|
188
|
+
return graphqlWithRateLimit(buildBulkMutation(graphqlReplies, graphqlResolves, minimizeIds, dismissIds, dismissMessage), {}, { allowPartialData: true });
|
|
189
|
+
}, () => applyRestReviewChunk({
|
|
190
|
+
...context,
|
|
191
|
+
replyIds,
|
|
192
|
+
resolveIds,
|
|
193
|
+
minimizeIds,
|
|
194
|
+
dismissIds,
|
|
195
|
+
message: dismissMessage,
|
|
196
|
+
}), { mutation: true });
|
|
197
|
+
if ("restStopped" in resp)
|
|
198
|
+
restStopped = resp.restStopped === true;
|
|
199
|
+
if ("sessionRefusal" in resp && typeof resp.sessionRefusal === "string")
|
|
200
|
+
result.sessionRefusal = resp.sessionRefusal;
|
|
132
201
|
data = resp.data;
|
|
133
202
|
graphQlErrors = (resp.errors ?? []);
|
|
134
203
|
const graphQlErrorMessages = graphQlErrors.map((e) => e.message);
|
|
135
|
-
suppressCurrentChunkErrors = graphQlErrorMessages.some(isRateLimitMessage);
|
|
204
|
+
suppressCurrentChunkErrors = restStopped || graphQlErrorMessages.some(isRateLimitMessage);
|
|
136
205
|
rateLimitStop = rateLimitFromGraphQlResult(graphQlErrorMessages, {
|
|
137
206
|
rateLimit: resp.rateLimit,
|
|
138
|
-
retryAfterSeconds: resp.retryAfterSeconds,
|
|
207
|
+
retryAfterSeconds: "retryAfterSeconds" in resp ? resp.retryAfterSeconds : undefined,
|
|
139
208
|
stopOnZeroRemaining: hasPendingAfter,
|
|
140
209
|
});
|
|
141
210
|
}
|
|
142
211
|
catch (err) {
|
|
212
|
+
if (isRestSessionRefusal(err))
|
|
213
|
+
throw err;
|
|
214
|
+
const uncertainIds = err instanceof GitHubRequestError && err.status === 200
|
|
215
|
+
? uncertainReplyIds(replyIds, [], err.graphqlErrors ?? [])
|
|
216
|
+
: replyIds;
|
|
217
|
+
const ambiguous = isAmbiguousMutationError(err) ||
|
|
218
|
+
(graphqlMutationAttempted &&
|
|
219
|
+
err instanceof GitHubRequestError &&
|
|
220
|
+
(isRetryableGraphQlInternal(err.graphqlErrors) ||
|
|
221
|
+
(err.status === 200 && (!err.graphqlErrors?.length || uncertainIds.length > 0))));
|
|
222
|
+
if (context && graphqlMutationAttempted && ambiguous)
|
|
223
|
+
await rememberUncertainReplies(context, uncertainIds, dismissMessage, replyEvidence);
|
|
143
224
|
const msg = err instanceof Error ? err.message : String(err);
|
|
144
225
|
const stop = rateLimitFromError(err, msg);
|
|
145
226
|
if (stop) {
|
|
@@ -155,7 +236,7 @@ async function bulkApplyChunk(resolveIds, replyIds, minimizeIds, dismissIds, dis
|
|
|
155
236
|
result.errors.push(`${id}: ${msg}`);
|
|
156
237
|
for (const id of dismissIds)
|
|
157
238
|
result.errors.push(`${id}: ${msg}`);
|
|
158
|
-
return
|
|
239
|
+
return ambiguous;
|
|
159
240
|
}
|
|
160
241
|
for (let i = 0; i < replyIds.length; i++) {
|
|
161
242
|
const id = replyIds[i];
|
|
@@ -164,21 +245,21 @@ async function bulkApplyChunk(resolveIds, replyIds, minimizeIds, dismissIds, dis
|
|
|
164
245
|
const p = data[`p${i}`];
|
|
165
246
|
if (p?.comment?.id)
|
|
166
247
|
result.repliedThreads.push(id);
|
|
167
|
-
else if (!suppressCurrentChunkErrors)
|
|
248
|
+
else if (!suppressCurrentChunkErrors || mutationErrorMessage(graphQlErrors, `p${i}`))
|
|
168
249
|
result.errors.push(`${id}: ${mutationErrorMessage(graphQlErrors, `p${i}`) ?? "reply returned null or comment not created"}`);
|
|
169
250
|
}
|
|
170
251
|
for (let i = 0; i < resolveIds.length; i++) {
|
|
171
252
|
const r = data[`r${i}`];
|
|
172
253
|
if (r?.thread?.isResolved === true)
|
|
173
254
|
result.resolvedThreads.push(resolveIds[i]);
|
|
174
|
-
else if (!suppressCurrentChunkErrors)
|
|
255
|
+
else if (!suppressCurrentChunkErrors || mutationErrorMessage(graphQlErrors, `r${i}`))
|
|
175
256
|
result.errors.push(`${resolveIds[i]}: ${mutationErrorMessage(graphQlErrors, `r${i}`) ?? "resolve returned null or thread not resolved"}`);
|
|
176
257
|
}
|
|
177
258
|
for (let i = 0; i < minimizeIds.length; i++) {
|
|
178
259
|
const m = data[`m${i}`];
|
|
179
260
|
if (m?.minimizedComment?.isMinimized === true)
|
|
180
261
|
result.minimizedComments.push(minimizeIds[i]);
|
|
181
|
-
else if (!suppressCurrentChunkErrors)
|
|
262
|
+
else if (!suppressCurrentChunkErrors || mutationErrorMessage(graphQlErrors, `m${i}`))
|
|
182
263
|
result.errors.push(`${minimizeIds[i]}: ${mutationErrorMessage(graphQlErrors, `m${i}`) ?? "minimize returned null or comment not minimized"}`);
|
|
183
264
|
}
|
|
184
265
|
const singleDismiss = dismissIds.length === 1;
|
|
@@ -198,17 +279,26 @@ async function bulkApplyChunk(resolveIds, replyIds, minimizeIds, dismissIds, dis
|
|
|
198
279
|
const d = data[`d${i}`];
|
|
199
280
|
if (d?.pullRequestReview != null)
|
|
200
281
|
result.dismissedReviews.push(dismissIds[i]);
|
|
201
|
-
else if (!suppressCurrentChunkErrors)
|
|
282
|
+
else if (!suppressCurrentChunkErrors || mutationErrorMessage(graphQlErrors, `d${i}`))
|
|
202
283
|
result.errors.push(commentedDismissErrorIndexes.has(i) || (singleDismiss && hasUnmappedCommentedDismissError)
|
|
203
284
|
? dismissReviewNonDismissibleMessage(dismissIds[i])
|
|
204
285
|
: `${dismissIds[i]}: ${mutationErrorMessage(graphQlErrors, `d${i}`) ?? "dismiss returned null"}`);
|
|
205
286
|
}
|
|
287
|
+
const internalFailure = graphqlMutationAttempted && isRetryableGraphQlInternal(graphQlErrors);
|
|
288
|
+
const uncertainIds = graphqlMutationAttempted
|
|
289
|
+
? uncertainReplyIds(replyIds, result.repliedThreads, graphQlErrors)
|
|
290
|
+
: [];
|
|
291
|
+
if (context && uncertainIds.length) {
|
|
292
|
+
await rememberUncertainReplies(context, uncertainIds, dismissMessage, replyEvidence);
|
|
293
|
+
}
|
|
206
294
|
if (rateLimitStop) {
|
|
207
295
|
result.errors.push(`rate limit: ${rateLimitStop.message}`);
|
|
208
296
|
result.rateLimit = rateLimitStop;
|
|
209
297
|
return true;
|
|
210
298
|
}
|
|
211
|
-
|
|
299
|
+
if (result.sessionRefusal)
|
|
300
|
+
return true;
|
|
301
|
+
return restStopped || internalFailure || uncertainIds.length > 0;
|
|
212
302
|
}
|
|
213
303
|
function dismissErrorAliasIndex(error) {
|
|
214
304
|
if (!Array.isArray(error.path))
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { RateLimitInfo } from "../github/http-utils.mts";
|
|
2
|
+
import type { RepoInfo } from "../github/client.mts";
|
|
3
|
+
interface ReplyResult {
|
|
4
|
+
id: string;
|
|
5
|
+
rateLimit?: RateLimitInfo;
|
|
6
|
+
}
|
|
7
|
+
export declare function isAmbiguousMutationError(error: unknown): boolean;
|
|
8
|
+
export declare function replyToThread(repo: RepoInfo, pr: number, root: string, body: string): Promise<ReplyResult>;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { readFile, writeFile, mkdir, unlink } from "node:fs/promises";
|
|
3
|
+
import { dirname } from "node:path";
|
|
4
|
+
import { resolvePrStatePath } from "../state/base.mjs";
|
|
5
|
+
import { rest, restWithRateLimit } from "../github/http.mjs";
|
|
6
|
+
import { GitHubRequestError } from "../github/errors.mjs";
|
|
7
|
+
import { isTransportError } from "../github/http-utils.mjs";
|
|
8
|
+
import { readRestPages, restRepoPath } from "../github/rest-reader-core.mjs";
|
|
9
|
+
import { isRestSessionRefusal } from "../github/rest-session-refusal.mjs";
|
|
10
|
+
export function isAmbiguousMutationError(error) {
|
|
11
|
+
return isTransportError(error) || (error instanceof GitHubRequestError && error.status >= 500);
|
|
12
|
+
}
|
|
13
|
+
function replyPath(repo, pr, root, body) {
|
|
14
|
+
const hash = createHash("sha256").update(`${root}\n${body}`).digest("hex");
|
|
15
|
+
return resolvePrStatePath({ owner: repo.owner, repo: repo.name, pr }, "pending-replies", `${hash}.json`);
|
|
16
|
+
}
|
|
17
|
+
async function readComments(repo, pr) {
|
|
18
|
+
return readRestPages(`${restRepoPath(repo)}/pulls/${pr}/comments`);
|
|
19
|
+
}
|
|
20
|
+
async function findPendingReply(repo, pr, pending) {
|
|
21
|
+
if (!pending.viewer)
|
|
22
|
+
return undefined;
|
|
23
|
+
const existing = new Set(pending.beforeIds);
|
|
24
|
+
const response = await readComments(repo, pr);
|
|
25
|
+
const matches = response.nodes.filter((comment) => String(comment.in_reply_to_id) === pending.root &&
|
|
26
|
+
comment.body === pending.body &&
|
|
27
|
+
comment.user?.login === pending.viewer &&
|
|
28
|
+
!existing.has(comment.id));
|
|
29
|
+
return matches.length === 1
|
|
30
|
+
? { id: String(matches[0].id), rateLimit: response.rateLimit }
|
|
31
|
+
: undefined;
|
|
32
|
+
}
|
|
33
|
+
export async function replyToThread(repo, pr, root, body) {
|
|
34
|
+
const path = replyPath(repo, pr, root, body);
|
|
35
|
+
let pending;
|
|
36
|
+
try {
|
|
37
|
+
pending = JSON.parse(await readFile(path, "utf8"));
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
if (error.code !== "ENOENT")
|
|
41
|
+
throw new Error("Cannot read pending REST reply; cannot safely retry", { cause: error });
|
|
42
|
+
}
|
|
43
|
+
if (pending) {
|
|
44
|
+
if (pending.root !== root || pending.body !== body || !Array.isArray(pending.beforeIds))
|
|
45
|
+
throw new Error("Invalid pending REST reply; cannot safely retry");
|
|
46
|
+
const id = await findPendingReply(repo, pr, pending);
|
|
47
|
+
if (!id)
|
|
48
|
+
throw new Error("Previous reply outcome is uncertain; refresh the thread before retrying, no duplicate reply was sent");
|
|
49
|
+
await unlink(path);
|
|
50
|
+
return id;
|
|
51
|
+
}
|
|
52
|
+
// These reads provide the exact author and pre-write IDs needed after a lost response.
|
|
53
|
+
let viewer;
|
|
54
|
+
try {
|
|
55
|
+
viewer = (await rest("GET", "/user")).login;
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
if (isRestSessionRefusal(error))
|
|
59
|
+
throw error;
|
|
60
|
+
if (!(error instanceof GitHubRequestError) || ![403, 404].includes(error.status))
|
|
61
|
+
throw error;
|
|
62
|
+
}
|
|
63
|
+
const comments = await readComments(repo, pr);
|
|
64
|
+
pending = {
|
|
65
|
+
root,
|
|
66
|
+
body,
|
|
67
|
+
...(viewer && { viewer }),
|
|
68
|
+
beforeIds: comments.nodes.map((comment) => comment.id),
|
|
69
|
+
};
|
|
70
|
+
await mkdir(dirname(path), { recursive: true });
|
|
71
|
+
await writeFile(path, JSON.stringify(pending), { flag: "wx" });
|
|
72
|
+
try {
|
|
73
|
+
const result = await restWithRateLimit("POST", `${restRepoPath(repo)}/pulls/${pr}/comments/${root}/replies`, { body });
|
|
74
|
+
if (!Number.isSafeInteger(result.data?.id) || result.data.id <= 0)
|
|
75
|
+
throw new Error("Reply returned no valid comment ID; outcome is uncertain");
|
|
76
|
+
await unlink(path);
|
|
77
|
+
return { id: String(result.data.id), rateLimit: result.rateLimit };
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
if (isAmbiguousMutationError(error)) {
|
|
81
|
+
try {
|
|
82
|
+
const id = await findPendingReply(repo, pr, pending);
|
|
83
|
+
if (id) {
|
|
84
|
+
await unlink(path);
|
|
85
|
+
return id;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
catch { }
|
|
89
|
+
}
|
|
90
|
+
else if (error instanceof GitHubRequestError && error.status >= 400 && error.status < 500) {
|
|
91
|
+
await unlink(path);
|
|
92
|
+
}
|
|
93
|
+
throw error;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { RepoInfo } from "../github/client.mts";
|
|
2
|
+
import type { RateLimitInfo } from "../github/http-utils.mts";
|
|
3
|
+
export declare function applyRestReviewChunk(input: {
|
|
4
|
+
repo?: RepoInfo;
|
|
5
|
+
pr?: number;
|
|
6
|
+
replyIds: string[];
|
|
7
|
+
resolveIds: string[];
|
|
8
|
+
minimizeIds: string[];
|
|
9
|
+
dismissIds: string[];
|
|
10
|
+
message: string;
|
|
11
|
+
}): Promise<{
|
|
12
|
+
data: Record<string, unknown>;
|
|
13
|
+
errors: Array<{
|
|
14
|
+
message: string;
|
|
15
|
+
path: string[];
|
|
16
|
+
}>;
|
|
17
|
+
rateLimit?: RateLimitInfo;
|
|
18
|
+
retryAfterSeconds?: number;
|
|
19
|
+
restStopped?: true;
|
|
20
|
+
sessionRefusal?: string;
|
|
21
|
+
}>;
|
|
22
|
+
export { isAmbiguousMutationError } from "./rest-reply.mts";
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { replyToThread, isAmbiguousMutationError } from "./rest-reply.mjs";
|
|
2
|
+
import { restWithRateLimit } from "../github/http.mjs";
|
|
3
|
+
import { GitHubRequestError } from "../github/errors.mjs";
|
|
4
|
+
import { isCcrTransport } from "../github/transport.mjs";
|
|
5
|
+
import { UnsupportedRestOperationError } from "../github/unsupported-rest.mjs";
|
|
6
|
+
import { resolveRestIdentity, resolveRestThreadRoot } from "../github/rest-identities.mjs";
|
|
7
|
+
import { readRestPages, restRepoPath } from "../github/rest-reader-core.mjs";
|
|
8
|
+
import { addPrShepherdMarker } from "./marker.mjs";
|
|
9
|
+
import { rateLimitFromError } from "./rate-limit.mjs";
|
|
10
|
+
import { markMutationDenied } from "../state/seen-comments.mjs";
|
|
11
|
+
import { readRestFeedback } from "../github/rest-feedback-read.mjs";
|
|
12
|
+
import { threadTranscriptBody } from "../threads/transcript.mjs";
|
|
13
|
+
import { isRestSessionRefusal } from "../github/rest-session-refusal.mjs";
|
|
14
|
+
async function reviewNumericId(repo, pr, id) {
|
|
15
|
+
if (/^[1-9][0-9]*$/.test(id))
|
|
16
|
+
return id;
|
|
17
|
+
try {
|
|
18
|
+
const entry = await resolveRestIdentity(id, "review");
|
|
19
|
+
if (entry.pr === pr &&
|
|
20
|
+
entry.repo.owner.toLowerCase() === repo.owner.toLowerCase() &&
|
|
21
|
+
entry.repo.name.toLowerCase() === repo.name.toLowerCase())
|
|
22
|
+
return entry.numericId;
|
|
23
|
+
}
|
|
24
|
+
catch { }
|
|
25
|
+
const reviews = (await readRestPages(`${restRepoPath(repo)}/pulls/${pr}/reviews`)).nodes;
|
|
26
|
+
const target = reviews.find((review) => review.node_id === id);
|
|
27
|
+
if (!target)
|
|
28
|
+
throw new Error(`Cannot address review ${id} through REST in ${repo.owner}/${repo.name}#${pr}`);
|
|
29
|
+
return String(target.id);
|
|
30
|
+
}
|
|
31
|
+
async function rememberDenied(input, id, kind) {
|
|
32
|
+
let key;
|
|
33
|
+
try {
|
|
34
|
+
const identity = input.repo && input.pr !== undefined ? undefined : await resolveRestIdentity(id);
|
|
35
|
+
const repo = input.repo ?? identity.repo;
|
|
36
|
+
const pr = input.pr ?? identity.pr;
|
|
37
|
+
key = { owner: repo.owner, repo: repo.name, pr };
|
|
38
|
+
const feedback = await readRestFeedback(pr, repo);
|
|
39
|
+
const root = kind === "dismiss" ? undefined : await resolveRestThreadRoot(repo, pr, id);
|
|
40
|
+
const body = kind === "dismiss"
|
|
41
|
+
? feedback.reviews.find((review) => review.node_id === id || String(review.id) === id)?.body
|
|
42
|
+
: feedback.threads.find((thread) => thread.id === id || thread.id === `rest-thread-${root}`);
|
|
43
|
+
if (body !== undefined && body !== null)
|
|
44
|
+
await markMutationDenied(key, id, typeof body === "string" ? body : threadTranscriptBody(body));
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
// Generated mutations already surfaced this revision; a failed refresh must not retry it.
|
|
48
|
+
if (key)
|
|
49
|
+
await markMutationDenied(key, id);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
export async function applyRestReviewChunk(input) {
|
|
53
|
+
const result = { data: {}, errors: [] };
|
|
54
|
+
const ops = [
|
|
55
|
+
...input.replyIds.map((id, index) => ({ id, alias: `p${index}`, kind: "reply" })),
|
|
56
|
+
...input.resolveIds.map((id, index) => ({ id, alias: `r${index}`, kind: "resolve" })),
|
|
57
|
+
...input.minimizeIds.map((id, index) => ({ id, alias: `m${index}`, kind: "minimize" })),
|
|
58
|
+
...input.dismissIds.map((id, index) => ({ id, alias: `d${index}`, kind: "dismiss" })),
|
|
59
|
+
];
|
|
60
|
+
for (const op of ops) {
|
|
61
|
+
try {
|
|
62
|
+
if (op.kind === "minimize")
|
|
63
|
+
throw new UnsupportedRestOperationError("minimizeComment");
|
|
64
|
+
let repo = input.repo;
|
|
65
|
+
let pr = input.pr;
|
|
66
|
+
if (!repo || pr === undefined) {
|
|
67
|
+
const identity = await resolveRestIdentity(op.id, op.kind === "dismiss" ? "review" : "thread");
|
|
68
|
+
repo = identity.repo;
|
|
69
|
+
pr = identity.pr;
|
|
70
|
+
}
|
|
71
|
+
const prefix = `${restRepoPath(repo)}/pulls/${pr}`;
|
|
72
|
+
if (op.kind === "reply") {
|
|
73
|
+
const root = await resolveRestThreadRoot(repo, pr, op.id);
|
|
74
|
+
const response = await replyToThread(repo, pr, root, addPrShepherdMarker(input.message));
|
|
75
|
+
result.rateLimit = response.rateLimit;
|
|
76
|
+
result.data[op.alias] = { comment: { id: response.id } };
|
|
77
|
+
}
|
|
78
|
+
else if (op.kind === "resolve") {
|
|
79
|
+
if (!isCcrTransport())
|
|
80
|
+
throw new UnsupportedRestOperationError("resolveReviewThread", "thread resolution requires the cloud CCR proxy");
|
|
81
|
+
const root = await resolveRestThreadRoot(repo, pr, op.id);
|
|
82
|
+
const response = await restWithRateLimit("POST", `${prefix}/ccr/comments/${root}/resolve`);
|
|
83
|
+
result.rateLimit = response.rateLimit;
|
|
84
|
+
if (response.data?.resolved !== true)
|
|
85
|
+
throw new Error("CCR resolve did not confirm the thread was resolved");
|
|
86
|
+
result.data[op.alias] = { thread: { isResolved: true } };
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
const review = await reviewNumericId(repo, pr, op.id);
|
|
90
|
+
const response = await restWithRateLimit("PUT", `${prefix}/reviews/${review}/dismissals`, { message: input.message });
|
|
91
|
+
result.rateLimit = response.rateLimit;
|
|
92
|
+
if (response.data?.state !== "DISMISSED")
|
|
93
|
+
throw new Error("Review dismissal did not confirm DISMISSED state");
|
|
94
|
+
result.data[op.alias] = { pullRequestReview: { state: "DISMISSED" } };
|
|
95
|
+
}
|
|
96
|
+
if (result.rateLimit?.remaining === 0) {
|
|
97
|
+
result.errors.push({ message: "GitHub REST core rate limit remaining is 0", path: [] });
|
|
98
|
+
result.restStopped = true;
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
if (isRestSessionRefusal(error)) {
|
|
104
|
+
result.sessionRefusal = error.message;
|
|
105
|
+
result.restStopped = true;
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
109
|
+
result.errors.push({ message, path: [op.alias] });
|
|
110
|
+
const stop = rateLimitFromError(error, message);
|
|
111
|
+
if (!stop && error instanceof GitHubRequestError && [403, 404].includes(error.status))
|
|
112
|
+
await rememberDenied(input, op.id, op.kind);
|
|
113
|
+
if (error instanceof GitHubRequestError) {
|
|
114
|
+
result.rateLimit = error.rateLimit;
|
|
115
|
+
result.retryAfterSeconds = error.retryAfterSeconds;
|
|
116
|
+
}
|
|
117
|
+
if (stop || isAmbiguousMutationError(error) || message.includes("outcome is uncertain")) {
|
|
118
|
+
result.restStopped = true;
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return result;
|
|
124
|
+
}
|
|
125
|
+
export { isAmbiguousMutationError } from "./rest-reply.mjs";
|
|
@@ -24,5 +24,5 @@ export declare function classifyReviewsForDisplay(reviews: Review[], seenMap: Ma
|
|
|
24
24
|
*
|
|
25
25
|
* Human-authored CR reviews continue to flow through the standard seen-gate.
|
|
26
26
|
*/
|
|
27
|
-
export declare function classifyChangesRequestedReviewsForDisplay(reviews: Review[], seenMap: Map<string, SeenMarker>, botUsernames: NormalizedBotUsernames, repeatBotReviews?: boolean): ReviewVisibility;
|
|
27
|
+
export declare function classifyChangesRequestedReviewsForDisplay(reviews: Review[], seenMap: Map<string, SeenMarker>, botUsernames: NormalizedBotUsernames, repeatBotReviews?: boolean, deniedDismissalIds?: ReadonlySet<string>): ReviewVisibility;
|
|
28
28
|
export {};
|
|
@@ -31,7 +31,7 @@ export function classifyReviewsForDisplay(reviews, seenMap) {
|
|
|
31
31
|
*
|
|
32
32
|
* Human-authored CR reviews continue to flow through the standard seen-gate.
|
|
33
33
|
*/
|
|
34
|
-
export function classifyChangesRequestedReviewsForDisplay(reviews, seenMap, botUsernames, repeatBotReviews = true) {
|
|
34
|
+
export function classifyChangesRequestedReviewsForDisplay(reviews, seenMap, botUsernames, repeatBotReviews = true, deniedDismissalIds = new Set()) {
|
|
35
35
|
const visible = [];
|
|
36
36
|
const toMarkSeen = [];
|
|
37
37
|
for (const review of reviews) {
|
|
@@ -39,8 +39,9 @@ export function classifyChangesRequestedReviewsForDisplay(reviews, seenMap, botU
|
|
|
39
39
|
const cls = classifyItem(review.id, review.body, seenMap);
|
|
40
40
|
if (isBot) {
|
|
41
41
|
if (cls === "unchanged") {
|
|
42
|
-
if (repeatBotReviews)
|
|
42
|
+
if (repeatBotReviews && !deniedDismissalIds.has(review.id)) {
|
|
43
43
|
visible.push({ ...review, staleBotCr: true });
|
|
44
|
+
}
|
|
44
45
|
}
|
|
45
46
|
else if (cls === "edited") {
|
|
46
47
|
visible.push({ ...review, edited: true });
|
|
@@ -3,11 +3,12 @@ import { loadConfig } from "../config/load.mjs";
|
|
|
3
3
|
import { sleep } from "../util/sleep.mjs";
|
|
4
4
|
export async function waitForSha(pr, repo, expectedSha) {
|
|
5
5
|
const { intervalMs: SHA_POLL_INTERVAL_MS, maxAttempts: SHA_POLL_MAX_ATTEMPTS } = loadConfig().resolve.shaPoll;
|
|
6
|
+
let lastSha;
|
|
6
7
|
for (let attempt = 0; attempt < SHA_POLL_MAX_ATTEMPTS; attempt++) {
|
|
7
8
|
try {
|
|
8
9
|
// eslint-disable-next-line no-await-in-loop
|
|
9
|
-
|
|
10
|
-
if (
|
|
10
|
+
lastSha = await getPrHeadSha(pr, repo.owner, repo.name);
|
|
11
|
+
if (lastSha === expectedSha)
|
|
11
12
|
return;
|
|
12
13
|
}
|
|
13
14
|
catch (err) {
|
|
@@ -19,5 +20,8 @@ export async function waitForSha(pr, repo, expectedSha) {
|
|
|
19
20
|
await sleep(SHA_POLL_INTERVAL_MS);
|
|
20
21
|
}
|
|
21
22
|
}
|
|
22
|
-
|
|
23
|
+
// Iterate prints a `--require-sha` that reads the current checkout's HEAD. Name that cause,
|
|
24
|
+
// so a run from another checkout can recover from this error alone.
|
|
25
|
+
const current = lastSha ? ` (currently ${lastSha})` : "";
|
|
26
|
+
throw new Error(`Timeout: GitHub PR #${pr} head SHA has not updated to ${expectedSha} after ${((SHA_POLL_MAX_ATTEMPTS - 1) * SHA_POLL_INTERVAL_MS) / 1000}s. Push may still be in transit — retry shortly. If this command ran outside the PR head checkout, rerun it with \`--require-sha\` set to the PR head SHA you pushed${current}.`);
|
|
23
27
|
}
|
|
@@ -23,9 +23,10 @@ function classifyFirstLookThread(thread, seenMap, firstLookStatus) {
|
|
|
23
23
|
export function classifyThreadVisibility(threads, seenMap, botUsernames = new Set(), repeatableThreadIds, resolveOtherHumanThreads = "none") {
|
|
24
24
|
const shouldRepeat = (thread) => repeatableThreadIds?.has(thread.id) ?? true;
|
|
25
25
|
const isOrdinaryHuman = (thread) => isHumanAuthor(thread) && !isConfiguredBotAuthor(thread, botUsernames);
|
|
26
|
-
const
|
|
26
|
+
const unknownStatusThreads = threads.filter((thread) => thread.isResolved === undefined || thread.isOutdated === undefined);
|
|
27
|
+
const unresolvedThreads = threads.filter((thread) => thread.isResolved === false && thread.isOutdated !== undefined);
|
|
27
28
|
const activeThreads = unresolvedThreads
|
|
28
|
-
.filter((t) =>
|
|
29
|
+
.filter((t) => t.isOutdated === false && t.isMinimized !== true)
|
|
29
30
|
.flatMap((t) => {
|
|
30
31
|
if (threadEndedByShepherd(t))
|
|
31
32
|
return [];
|
|
@@ -56,23 +57,27 @@ export function classifyThreadVisibility(threads, seenMap, botUsernames = new Se
|
|
|
56
57
|
});
|
|
57
58
|
const firstLookThreads = [
|
|
58
59
|
...threads.flatMap((t) => {
|
|
59
|
-
if (
|
|
60
|
+
if (t.isOutdated !== true || t.isResolved === undefined)
|
|
60
61
|
return [];
|
|
61
62
|
const visible = classifyFirstLookThread(t, seenMap, "outdated");
|
|
62
63
|
return visible ? [visible] : [];
|
|
63
64
|
}),
|
|
64
65
|
...threads.flatMap((t) => {
|
|
65
|
-
if (
|
|
66
|
+
if (t.isResolved !== true || t.isOutdated !== false)
|
|
66
67
|
return [];
|
|
67
68
|
const visible = classifyFirstLookThread(t, seenMap, "resolved");
|
|
68
69
|
return visible ? [visible] : [];
|
|
69
70
|
}),
|
|
70
71
|
...threads.flatMap((t) => {
|
|
71
|
-
if (!t.isMinimized || t.isResolved || t.isOutdated)
|
|
72
|
+
if (!t.isMinimized || t.isResolved !== false || t.isOutdated !== false)
|
|
72
73
|
return [];
|
|
73
74
|
const visible = classifyFirstLookThread(t, seenMap, "minimized");
|
|
74
75
|
return visible ? [visible] : [];
|
|
75
76
|
}),
|
|
77
|
+
...unknownStatusThreads.flatMap((thread) => {
|
|
78
|
+
const visible = classifyFirstLookThread(thread, seenMap, "unknown");
|
|
79
|
+
return visible ? [visible] : [];
|
|
80
|
+
}),
|
|
76
81
|
];
|
|
77
82
|
const seenMarkerIds = new Set();
|
|
78
83
|
const toMarkSeen = [...activeThreads, ...resolutionOnlyThreads, ...firstLookThreads].filter((thread) => {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { RepoInfo } from "../github/client.mts";
|
|
2
|
+
import { type ReplyEvidence } from "../github/reply-recovery-read.mts";
|
|
3
|
+
interface ReplyContext {
|
|
4
|
+
repo: RepoInfo;
|
|
5
|
+
pr: number;
|
|
6
|
+
}
|
|
7
|
+
export declare function trackAdoptedReplyThreads(result: object, ids: readonly string[]): void;
|
|
8
|
+
export declare function adoptedReplyThreads(result: object): readonly string[];
|
|
9
|
+
export declare function assertReplyOutcomeKnown(context: ReplyContext, ids: string[], message: string): Promise<string[]>;
|
|
10
|
+
export declare function rememberUncertainReplies(context: ReplyContext, ids: string[], message: string, evidence?: Map<string, ReplyEvidence>): Promise<void>;
|
|
11
|
+
export {};
|