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
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
interface TransportEvidence {
|
|
2
|
+
transport?: "rest";
|
|
3
|
+
transportUnavailable?: Array<{
|
|
4
|
+
field: string;
|
|
5
|
+
reason: string;
|
|
6
|
+
}>;
|
|
7
|
+
}
|
|
8
|
+
/** A future GitHub state cannot prove readiness through a negative blocker list. */
|
|
9
|
+
export declare function isKnownMergeStateStatus(status: string): boolean;
|
|
10
|
+
/**
|
|
11
|
+
* Missing feedback cannot certify readiness, even when GitHub reports CLEAN. Missing branch policy
|
|
12
|
+
* blocks only a non-CLEAN state, whose unmet requirements Shepherd derives from that policy. The
|
|
13
|
+
* aggregate `reviewDecision` is never a readiness input on either transport (approvals come from
|
|
14
|
+
* branch policy plus latest reviews), so its absence alone is not a gap.
|
|
15
|
+
*/
|
|
16
|
+
export declare function transportReadinessGaps(value: TransportEvidence, mergeStateStatus: string): Array<{
|
|
17
|
+
field: string;
|
|
18
|
+
reason: string;
|
|
19
|
+
}>;
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** A future GitHub state cannot prove readiness through a negative blocker list. */
|
|
2
|
+
export function isKnownMergeStateStatus(status) {
|
|
3
|
+
return [
|
|
4
|
+
"BEHIND",
|
|
5
|
+
"BLOCKED",
|
|
6
|
+
"CLEAN",
|
|
7
|
+
"DIRTY",
|
|
8
|
+
"DRAFT",
|
|
9
|
+
"HAS_HOOKS",
|
|
10
|
+
"UNKNOWN",
|
|
11
|
+
"UNSTABLE",
|
|
12
|
+
].includes(status);
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Missing feedback cannot certify readiness, even when GitHub reports CLEAN. Missing branch policy
|
|
16
|
+
* blocks only a non-CLEAN state, whose unmet requirements Shepherd derives from that policy. The
|
|
17
|
+
* aggregate `reviewDecision` is never a readiness input on either transport (approvals come from
|
|
18
|
+
* branch policy plus latest reviews), so its absence alone is not a gap.
|
|
19
|
+
*/
|
|
20
|
+
export function transportReadinessGaps(value, mergeStateStatus) {
|
|
21
|
+
if (value.transport !== "rest")
|
|
22
|
+
return [];
|
|
23
|
+
return (value.transportUnavailable ?? []).filter(({ field }) => /^(reviewThreads|reviewTranscripts|changesRequestedReviews|reviewSummaries|checks|checkRuns|checkSuites|checkAnnotations|annotations|nativeStack)(?:\.|$)/.test(field) ||
|
|
24
|
+
(mergeStateStatus !== "CLEAN" &&
|
|
25
|
+
["branchProtection", "branchRules", "mergeRequirements"].some((policy) => field === policy || field.startsWith(`${policy}.`))));
|
|
26
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type GithubTransport } from "./transport-mode.mts";
|
|
2
|
+
export type { GithubTransport } from "./transport-mode.mts";
|
|
3
|
+
export declare function getGithubTransport(): "graphql" | "rest";
|
|
4
|
+
/** Cloud-session marker or the specific GraphQL refusal observed by this client. */
|
|
5
|
+
export declare function isCcrTransport(): boolean;
|
|
6
|
+
/** Each client retains its auto fallback without sharing state with other clients. */
|
|
7
|
+
export declare function createGithubTransportRunner(mode?: GithubTransport, options?: {
|
|
8
|
+
verbose?: boolean;
|
|
9
|
+
}): <T>(work: () => Promise<T>, override?: GithubTransport) => Promise<T>;
|
|
10
|
+
/** Wrap the complete command (including every poll tick) in one transport scope. */
|
|
11
|
+
export declare function runWithGithubTransport<T>(mode: GithubTransport | undefined, work: () => Promise<T>, options?: {
|
|
12
|
+
verbose?: boolean;
|
|
13
|
+
}): Promise<T>;
|
|
14
|
+
/** Allow best-effort GraphQL reads to propagate failures eligible for transport fallback. */
|
|
15
|
+
export declare function isGithubReadFallbackError(error: unknown): boolean;
|
|
16
|
+
/** Choose named implementations; HTTP documents are never interpreted as REST routes. */
|
|
17
|
+
export declare function githubOperation<T>(name: string, graphql: () => Promise<T>, rest: () => Promise<T>, options?: {
|
|
18
|
+
mutation?: boolean;
|
|
19
|
+
}): Promise<T>;
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
import { loadConfig } from "../config/load.mjs";
|
|
3
|
+
import { GitHubRequestError, isRetryableGraphQlInternal } from "./errors.mjs";
|
|
4
|
+
import { isTransportError } from "./http-utils.mjs";
|
|
5
|
+
import { rateLimitKind } from "./rate-limit-kind.mjs";
|
|
6
|
+
import { isCcrGraphqlRefusal } from "./graphql-session-refusal.mjs";
|
|
7
|
+
import { parseGithubTransport } from "./transport-mode.mjs";
|
|
8
|
+
import { appendEntry } from "../log/log-file.mjs";
|
|
9
|
+
import { hasMixedGraphQlQuotaErrors, isPrimaryGraphQlQuotaExhausted, needsGraphQlQuotaProbe, } from "./graphql-quota-probe.mjs";
|
|
10
|
+
const context = new AsyncLocalStorage();
|
|
11
|
+
function configuredMode() {
|
|
12
|
+
return loadConfig().github?.transport ?? "auto";
|
|
13
|
+
}
|
|
14
|
+
export function getGithubTransport() {
|
|
15
|
+
const current = context.getStore();
|
|
16
|
+
const mode = current?.mode ?? configuredMode();
|
|
17
|
+
if (mode !== "auto")
|
|
18
|
+
return mode;
|
|
19
|
+
return current?.state.fallback || process.env["CLAUDE_CODE_REMOTE"] === "true"
|
|
20
|
+
? "rest"
|
|
21
|
+
: "graphql";
|
|
22
|
+
}
|
|
23
|
+
/** Cloud-session marker or the specific GraphQL refusal observed by this client. */
|
|
24
|
+
export function isCcrTransport() {
|
|
25
|
+
return process.env["CLAUDE_CODE_REMOTE"] === "true" || context.getStore()?.state.ccr === true;
|
|
26
|
+
}
|
|
27
|
+
/** Each client retains its auto fallback without sharing state with other clients. */
|
|
28
|
+
export function createGithubTransportRunner(mode, options) {
|
|
29
|
+
if (mode !== undefined)
|
|
30
|
+
parseGithubTransport(mode);
|
|
31
|
+
const state = { fallback: false, ccr: false };
|
|
32
|
+
return function run(work, override) {
|
|
33
|
+
const selected = parseGithubTransport(override ?? mode ?? configuredMode());
|
|
34
|
+
return context.run({ mode: selected, state, verbose: options?.verbose }, work);
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/** Wrap the complete command (including every poll tick) in one transport scope. */
|
|
38
|
+
export function runWithGithubTransport(mode, work, options) {
|
|
39
|
+
const current = context.getStore();
|
|
40
|
+
if (current && (mode === undefined || mode === current.mode))
|
|
41
|
+
return work();
|
|
42
|
+
return createGithubTransportRunner(mode, options)(work);
|
|
43
|
+
}
|
|
44
|
+
function responseMessage(error) {
|
|
45
|
+
const raw = error.responseMessage ?? "";
|
|
46
|
+
try {
|
|
47
|
+
const parsed = JSON.parse(raw);
|
|
48
|
+
if (typeof parsed.message === "string")
|
|
49
|
+
return parsed.message;
|
|
50
|
+
}
|
|
51
|
+
catch { }
|
|
52
|
+
return raw.trim();
|
|
53
|
+
}
|
|
54
|
+
function fallbackReason(error) {
|
|
55
|
+
if (isTransportError(error))
|
|
56
|
+
return "ambiguous";
|
|
57
|
+
if (!(error instanceof GitHubRequestError))
|
|
58
|
+
return undefined;
|
|
59
|
+
if (/bad credentials|requires authentication|resource not accessible|insufficient permission|access denied/i.test(error.responseMessage ?? ""))
|
|
60
|
+
return undefined;
|
|
61
|
+
// Mixed GraphQL errors must retain credential, permission and validation failures.
|
|
62
|
+
if (error.status === 401 ||
|
|
63
|
+
error.graphqlErrors?.some((entry) => /resource not accessible|permission|forbidden|unauthorized|unknown (?:field|argument)|cannot query|variable .* (?:invalid|required)/i.test(entry.message) ||
|
|
64
|
+
["FORBIDDEN", "UNAUTHORIZED", "GRAPHQL_VALIDATION_FAILED", "NOT_FOUND"].includes(entry.type?.toUpperCase() ?? "")))
|
|
65
|
+
return undefined;
|
|
66
|
+
const throttle = rateLimitKind(error);
|
|
67
|
+
if (isRetryableGraphQlInternal(error.graphqlErrors) &&
|
|
68
|
+
error.graphqlErrors?.some((entry) => !isRetryableGraphQlInternal([entry])))
|
|
69
|
+
return undefined;
|
|
70
|
+
if (hasMixedGraphQlQuotaErrors(error.graphqlErrors))
|
|
71
|
+
return undefined;
|
|
72
|
+
if (needsGraphQlQuotaProbe(error))
|
|
73
|
+
return "probe";
|
|
74
|
+
if (throttle === "secondary")
|
|
75
|
+
return undefined;
|
|
76
|
+
if (isCcrGraphqlRefusal(error.status, responseMessage(error)))
|
|
77
|
+
return "ccr";
|
|
78
|
+
// Exhausting the quota does not prove an INTERNAL/5xx mutation was refused.
|
|
79
|
+
if (error.status >= 500 || isRetryableGraphQlInternal(error.graphqlErrors))
|
|
80
|
+
return "ambiguous";
|
|
81
|
+
if (throttle === "primary" &&
|
|
82
|
+
(error.status === 403 ||
|
|
83
|
+
error.status === 429 ||
|
|
84
|
+
error.graphqlErrors?.some((entry) => /rate limit/i.test(entry.message) || entry.type === "RATE_LIMITED")) &&
|
|
85
|
+
(!error.rateLimit?.resource || error.rateLimit.resource === "graphql"))
|
|
86
|
+
return "refused";
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
/** Allow best-effort GraphQL reads to propagate failures eligible for transport fallback. */
|
|
90
|
+
export function isGithubReadFallbackError(error) {
|
|
91
|
+
return fallbackReason(error) !== undefined;
|
|
92
|
+
}
|
|
93
|
+
/** Choose named implementations; HTTP documents are never interpreted as REST routes. */
|
|
94
|
+
export async function githubOperation(name, graphql, rest, options) {
|
|
95
|
+
const current = context.getStore();
|
|
96
|
+
if (!current)
|
|
97
|
+
return runWithGithubTransport(undefined, () => githubOperation(name, graphql, rest, options));
|
|
98
|
+
if (getGithubTransport() === "rest")
|
|
99
|
+
return rest();
|
|
100
|
+
try {
|
|
101
|
+
return await graphql();
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
let reason = fallbackReason(error);
|
|
105
|
+
if (current.mode !== "auto" || reason === undefined)
|
|
106
|
+
throw error;
|
|
107
|
+
if (reason === "probe") {
|
|
108
|
+
if (!(await isPrimaryGraphQlQuotaExhausted()))
|
|
109
|
+
throw error;
|
|
110
|
+
reason = "refused";
|
|
111
|
+
}
|
|
112
|
+
if (!current.state.fallback) {
|
|
113
|
+
const message = `pr-shepherd: GitHub transport switched to REST after ${name} (${reason}).\n`;
|
|
114
|
+
appendEntry(message);
|
|
115
|
+
if (current.verbose)
|
|
116
|
+
process.stderr.write(message);
|
|
117
|
+
}
|
|
118
|
+
current.state.fallback = true;
|
|
119
|
+
if (reason === "ccr")
|
|
120
|
+
current.state.ccr = true;
|
|
121
|
+
// An ambiguous mutation may already have succeeded; never replay it.
|
|
122
|
+
if (options?.mutation && reason === "ambiguous")
|
|
123
|
+
throw error;
|
|
124
|
+
return rest();
|
|
125
|
+
}
|
|
126
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { EXIT, ShepherdError } from "../exit-codes.mjs";
|
|
2
|
+
export class UnsupportedRestOperationError extends ShepherdError {
|
|
3
|
+
constructor(operation, reason = "no supported REST endpoint is available") {
|
|
4
|
+
super(`${operation} is unsupported in REST transport: ${reason}`, EXIT.UNAVAILABLE);
|
|
5
|
+
this.name = "UnsupportedRestOperationError";
|
|
6
|
+
}
|
|
7
|
+
}
|
package/bin/mcp/server.mjs
CHANGED
|
@@ -10,15 +10,19 @@ import { formatPollSummaryResult } from "../cli/poll-summary-formatter.mjs";
|
|
|
10
10
|
import { projectStackOverview } from "../cli/stack-overview.mjs";
|
|
11
11
|
import { formatCliError, serializeGitHubRequestErrorDetails } from "../cli/error-format.mjs";
|
|
12
12
|
import { errorToExitCode, EXIT } from "../exit-codes.mjs";
|
|
13
|
+
import { formatApplyMergeResult } from "../cli/apply-merge-handler.mjs";
|
|
13
14
|
import { extractShepherdJournal } from "../journal/index.mjs";
|
|
15
|
+
import { formatPlaybookResult, runPlaybook } from "../commands/playbook.mjs";
|
|
14
16
|
const QUALIFIED_PR_ERROR = "pr must be a GitHub pull-request URL or an owner/repo#number reference";
|
|
15
17
|
const pr = z
|
|
16
18
|
.string()
|
|
17
19
|
.refine(isRepositoryQualifiedPrReference, { message: QUALIFIED_PR_ERROR })
|
|
18
20
|
.describe("GitHub pull-request URL or owner/repo#number; the explicit repository may differ from the server working directory");
|
|
19
21
|
const ids = z.array(z.string().min(1)).optional();
|
|
22
|
+
const transport = z.enum(["auto", "graphql", "rest"]).optional();
|
|
20
23
|
const iterateInputSchema = z
|
|
21
24
|
.object({
|
|
25
|
+
transport,
|
|
22
26
|
pr: pr.optional(),
|
|
23
27
|
prs: z.array(pr).min(1).optional(),
|
|
24
28
|
stack: pr.optional(),
|
|
@@ -26,6 +30,14 @@ const iterateInputSchema = z
|
|
|
26
30
|
stallTimeoutSeconds: z.number().nonnegative().optional(),
|
|
27
31
|
noAutoMarkReady: z.boolean().optional(),
|
|
28
32
|
merge: z.boolean().optional(),
|
|
33
|
+
instructions: z
|
|
34
|
+
.enum(["playbook", "inline"])
|
|
35
|
+
.optional()
|
|
36
|
+
.describe("fix_code instruction style. inline (default) prints every step each tick; playbook folds commit, push and journal into the Fix-code loop playbook."),
|
|
37
|
+
pollMode: z
|
|
38
|
+
.enum(["auto", "poll", "event"])
|
|
39
|
+
.optional()
|
|
40
|
+
.describe("Overrides poll.mode. event returns one tick with pollMode and nextCheck so the caller owns the wake-up; auto (default) selects event when CLAUDE_CODE_REMOTE=true."),
|
|
29
41
|
noAutoCancelActionable: z
|
|
30
42
|
.boolean()
|
|
31
43
|
.optional()
|
|
@@ -66,10 +78,33 @@ const acknowledgeQueueRemovalOperationSchema = z.object({
|
|
|
66
78
|
queueCommitOid: z.string().regex(/^[0-9a-f]{40}$/),
|
|
67
79
|
removedAtUnix: z.number().int().positive().safe(),
|
|
68
80
|
});
|
|
81
|
+
const mergeOperationSchema = z.object({
|
|
82
|
+
type: z.literal("merge"),
|
|
83
|
+
requireSha: z.string().regex(/^[0-9a-f]{40}$/),
|
|
84
|
+
mergeAction: z.enum(["direct_merge", "merge_queue", "default"]),
|
|
85
|
+
mergeMethod: z.enum(["merge", "squash", "rebase"]).optional(),
|
|
86
|
+
expectedStack: z
|
|
87
|
+
.object({
|
|
88
|
+
number: z.number().int().positive().safe(),
|
|
89
|
+
baseRefName: z.string().min(1),
|
|
90
|
+
prefix: z
|
|
91
|
+
.array(z.object({
|
|
92
|
+
pr: z.number().int().positive().safe(),
|
|
93
|
+
headRefName: z.string().min(1),
|
|
94
|
+
headRefOid: z.string().regex(/^[0-9a-f]{40}$/),
|
|
95
|
+
baseRefName: z.string().min(1),
|
|
96
|
+
}))
|
|
97
|
+
.min(1)
|
|
98
|
+
.max(100_000),
|
|
99
|
+
})
|
|
100
|
+
.optional(),
|
|
101
|
+
});
|
|
69
102
|
const applyInputSchema = z.object({
|
|
103
|
+
transport,
|
|
70
104
|
pr,
|
|
71
105
|
operations: z
|
|
72
106
|
.array(z.discriminatedUnion("type", [
|
|
107
|
+
mergeOperationSchema,
|
|
73
108
|
reviewMutationsOperationSchema,
|
|
74
109
|
markFilesViewedOperationSchema,
|
|
75
110
|
appendJournalOperationSchema,
|
|
@@ -78,12 +113,14 @@ const applyInputSchema = z.object({
|
|
|
78
113
|
.min(1),
|
|
79
114
|
});
|
|
80
115
|
const suggestionPatchInputSchema = z.object({
|
|
116
|
+
transport,
|
|
81
117
|
pr,
|
|
82
118
|
threadId: z.string().min(1),
|
|
83
119
|
message: z.string().min(1),
|
|
84
120
|
description: z.string().optional(),
|
|
85
121
|
});
|
|
86
122
|
const suggestionPatchesInputSchema = z.object({
|
|
123
|
+
transport,
|
|
87
124
|
pr,
|
|
88
125
|
suggestions: z
|
|
89
126
|
.array(z.object({
|
|
@@ -96,7 +133,7 @@ const suggestionPatchesInputSchema = z.object({
|
|
|
96
133
|
/** Creates a local-only MCP server with Shepherd's public operations. */
|
|
97
134
|
export function createPrShepherdMcpServer(options = {}) {
|
|
98
135
|
let shepherd = options.shepherd;
|
|
99
|
-
const getShepherd = () => (shepherd ??= createPrShepherd({ cwd: options.cwd }));
|
|
136
|
+
const getShepherd = () => (shepherd ??= createPrShepherd({ cwd: options.cwd, transport: options.transport }));
|
|
100
137
|
const server = new McpServer({ name: "pr-shepherd", version: readPackageVersion() });
|
|
101
138
|
server.registerTool("iterate", {
|
|
102
139
|
description: "Inspect one pull request, an explicit same-repository set, or a native stack and return one Shepherd tick.",
|
|
@@ -146,8 +183,8 @@ export function createPrShepherdMcpServer(options = {}) {
|
|
|
146
183
|
return extractShepherdJournal(input.body);
|
|
147
184
|
}, JSON.stringify));
|
|
148
185
|
server.registerTool("get_journal", {
|
|
149
|
-
description: "Fetch one pull request body
|
|
150
|
-
inputSchema: z.object({ pr }),
|
|
186
|
+
description: "Fetch one pull request body through the selected transport and extract its Shepherd Journal.",
|
|
187
|
+
inputSchema: z.object({ pr, transport }),
|
|
151
188
|
annotations: {
|
|
152
189
|
readOnlyHint: true,
|
|
153
190
|
destructiveHint: false,
|
|
@@ -155,6 +192,16 @@ export function createPrShepherdMcpServer(options = {}) {
|
|
|
155
192
|
openWorldHint: true,
|
|
156
193
|
},
|
|
157
194
|
}, async (input) => runTool(() => getShepherd().getJournal(requireRepositoryQualifiedPr(input)), JSON.stringify));
|
|
195
|
+
server.registerTool("playbook", {
|
|
196
|
+
description: "Return a playbook named by an instruction-step playbook pointer, from the package. Omit name to list playbooks. Read-only; no GitHub I/O.",
|
|
197
|
+
inputSchema: z.object({ name: z.string().optional() }),
|
|
198
|
+
annotations: {
|
|
199
|
+
readOnlyHint: true,
|
|
200
|
+
destructiveHint: false,
|
|
201
|
+
idempotentHint: true,
|
|
202
|
+
openWorldHint: false,
|
|
203
|
+
},
|
|
204
|
+
}, async (input) => runTool(async () => runPlaybook(input.name), formatPlaybookResult));
|
|
158
205
|
server.registerTool("build_suggestion_patches", {
|
|
159
206
|
description: "Build, but never apply, an ordered list of eligible review suggestion patches.",
|
|
160
207
|
inputSchema: suggestionPatchesInputSchema,
|
|
@@ -235,6 +282,8 @@ function formatApplyResult(result) {
|
|
|
235
282
|
.map((operation, index) => {
|
|
236
283
|
const heading = `## Operation ${index + 1}: ${operation.type}`;
|
|
237
284
|
switch (operation.type) {
|
|
285
|
+
case "merge":
|
|
286
|
+
return `${heading}\n\n${formatApplyMergeResult(operation.result)}`;
|
|
238
287
|
case "review_mutations":
|
|
239
288
|
return `${heading}\n\n${formatMutateResult(operation.result)}`;
|
|
240
289
|
case "mark_files_viewed":
|
|
@@ -17,3 +17,5 @@
|
|
|
17
17
|
*/
|
|
18
18
|
import type { BatchPrData, MergeStatusResult } from "../types.mts";
|
|
19
19
|
export declare function deriveMergeStatus(pr: BatchPrData): MergeStatusResult;
|
|
20
|
+
/** Steps 5–9 of the ladder: the status of a non-draft PR without conflicts. */
|
|
21
|
+
export declare function nonDraftMergeStatus(mergeStateStatus: BatchPrData["mergeStateStatus"]): MergeStatusResult["status"];
|
|
@@ -33,20 +33,8 @@ export function deriveMergeStatus(pr) {
|
|
|
33
33
|
else if (pr.isDraft || pr.mergeStateStatus === "DRAFT") {
|
|
34
34
|
status = "DRAFT";
|
|
35
35
|
}
|
|
36
|
-
else if (pr.mergeStateStatus === "BEHIND") {
|
|
37
|
-
status = "BEHIND";
|
|
38
|
-
}
|
|
39
|
-
else if (pr.mergeStateStatus === "BLOCKED" || pr.mergeStateStatus === "HAS_HOOKS") {
|
|
40
|
-
status = "BLOCKED";
|
|
41
|
-
}
|
|
42
|
-
else if (pr.mergeStateStatus === "UNSTABLE") {
|
|
43
|
-
status = "UNSTABLE";
|
|
44
|
-
}
|
|
45
|
-
else if (pr.mergeStateStatus === "UNKNOWN") {
|
|
46
|
-
status = "UNKNOWN";
|
|
47
|
-
}
|
|
48
36
|
else {
|
|
49
|
-
status =
|
|
37
|
+
status = nonDraftMergeStatus(pr.mergeStateStatus);
|
|
50
38
|
}
|
|
51
39
|
return {
|
|
52
40
|
status,
|
|
@@ -59,6 +47,22 @@ export function deriveMergeStatus(pr) {
|
|
|
59
47
|
mergeRequirements: deriveMergeRequirements(pr),
|
|
60
48
|
};
|
|
61
49
|
}
|
|
50
|
+
/** Steps 5–9 of the ladder: the status of a non-draft PR without conflicts. */
|
|
51
|
+
export function nonDraftMergeStatus(mergeStateStatus) {
|
|
52
|
+
switch (mergeStateStatus) {
|
|
53
|
+
case "BEHIND":
|
|
54
|
+
return "BEHIND";
|
|
55
|
+
case "BLOCKED":
|
|
56
|
+
case "HAS_HOOKS":
|
|
57
|
+
return "BLOCKED";
|
|
58
|
+
case "UNSTABLE":
|
|
59
|
+
return "UNSTABLE";
|
|
60
|
+
case "CLEAN":
|
|
61
|
+
return "CLEAN";
|
|
62
|
+
default:
|
|
63
|
+
return "UNKNOWN";
|
|
64
|
+
}
|
|
65
|
+
}
|
|
62
66
|
// ---------------------------------------------------------------------------
|
|
63
67
|
// Blocking bot review detection
|
|
64
68
|
// ---------------------------------------------------------------------------
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { MergeStatusResult } from "../types.mts";
|
|
2
|
+
/**
|
|
3
|
+
* A native-stack layer merges under the stack trunk's rules, not its parent branch's, so
|
|
4
|
+
* replace the parent's required contexts with the trunk's. A trunk with none clears them.
|
|
5
|
+
* Any other PR keeps the contexts derived from its own base.
|
|
6
|
+
*/
|
|
7
|
+
export declare function withTrunkRequiredContexts(mergeStatus: MergeStatusResult, contexts: readonly string[]): MergeStatusResult;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A native-stack layer merges under the stack trunk's rules, not its parent branch's, so
|
|
3
|
+
* replace the parent's required contexts with the trunk's. A trunk with none clears them.
|
|
4
|
+
* Any other PR keeps the contexts derived from its own base.
|
|
5
|
+
*/
|
|
6
|
+
export function withTrunkRequiredContexts(mergeStatus, contexts) {
|
|
7
|
+
const requirements = mergeStatus.mergeRequirements;
|
|
8
|
+
if (!requirements?.stack)
|
|
9
|
+
return mergeStatus;
|
|
10
|
+
const { requiredStatusChecks: _parent, ...rest } = requirements;
|
|
11
|
+
return {
|
|
12
|
+
...mergeStatus,
|
|
13
|
+
mergeRequirements: contexts.length > 0 ? { ...rest, requiredStatusChecks: { contexts: [...contexts] } } : rest,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
@@ -1,3 +1,8 @@
|
|
|
1
1
|
import type { MergeRequirements } from "../types.mts";
|
|
2
|
+
/**
|
|
3
|
+
* Lean JSON projection of the merge requirements. Drops exactly the entries that
|
|
4
|
+
* `formatMergeRequirementLines` omits, so text and JSON stay equivalent.
|
|
5
|
+
*/
|
|
6
|
+
export declare function projectMergeRequirements(req: MergeRequirements): Record<string, unknown> | null;
|
|
2
7
|
export declare function formatMergeRequirementLines(req: MergeRequirements): string[];
|
|
3
8
|
export declare function blockedReasonFromRequirements(req: MergeRequirements | undefined): string | null;
|
|
@@ -1,10 +1,32 @@
|
|
|
1
1
|
const REQUIRED = "[Required]";
|
|
2
2
|
const NOT_REQUIRED = "[Not Required]";
|
|
3
|
+
/** No approvals and none required: the trivial default, omitted from lean output. */
|
|
4
|
+
function isTrivialApprovals(req) {
|
|
5
|
+
return req.approvals.current === 0 && req.approvals.requiredCount === 0;
|
|
6
|
+
}
|
|
7
|
+
/** Resolution is known not to be required: unresolved threads are listed as items anyway. */
|
|
8
|
+
function isTrivialConversations(req) {
|
|
9
|
+
return req.conversationsResolved.required === false;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Lean JSON projection of the merge requirements. Drops exactly the entries that
|
|
13
|
+
* `formatMergeRequirementLines` omits, so text and JSON stay equivalent.
|
|
14
|
+
*/
|
|
15
|
+
export function projectMergeRequirements(req) {
|
|
16
|
+
const { approvals, conversationsResolved, ...rest } = req;
|
|
17
|
+
const projected = {
|
|
18
|
+
...(!isTrivialApprovals(req) && { approvals }),
|
|
19
|
+
...(!isTrivialConversations(req) && { conversationsResolved }),
|
|
20
|
+
...rest,
|
|
21
|
+
};
|
|
22
|
+
return Object.keys(projected).length > 0 ? projected : null;
|
|
23
|
+
}
|
|
3
24
|
export function formatMergeRequirementLines(req) {
|
|
4
|
-
const lines = [
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
25
|
+
const lines = [];
|
|
26
|
+
if (!isTrivialApprovals(req))
|
|
27
|
+
lines.push(formatApprovals(req.approvals.current, req.approvals.requiredCount));
|
|
28
|
+
if (!isTrivialConversations(req))
|
|
29
|
+
lines.push(formatConversations(req.conversationsResolved));
|
|
8
30
|
if (req.codeOwnerReview)
|
|
9
31
|
lines.push(`Code owner review: ${REQUIRED}`);
|
|
10
32
|
if (req.lastPushApproval)
|
|
@@ -18,8 +40,8 @@ export function formatMergeRequirementLines(req) {
|
|
|
18
40
|
lines.push(`Branch up to date: ${value} ${REQUIRED}`);
|
|
19
41
|
}
|
|
20
42
|
if (req.requiredStatusChecks) {
|
|
21
|
-
const
|
|
22
|
-
lines.push(`Required status checks: ${
|
|
43
|
+
const names = req.requiredStatusChecks.contexts.join(", ");
|
|
44
|
+
lines.push(`Required status checks: ${names} ${REQUIRED}`);
|
|
23
45
|
}
|
|
24
46
|
if (req.requiredDeployments) {
|
|
25
47
|
const envs = req.requiredDeployments.environments.join(", ");
|
|
@@ -41,11 +63,13 @@ export function blockedReasonFromRequirements(req) {
|
|
|
41
63
|
if (!req)
|
|
42
64
|
return null;
|
|
43
65
|
const unmet = [];
|
|
44
|
-
if (req.approvals.requiredCount
|
|
66
|
+
if (req.approvals.requiredCount !== undefined &&
|
|
67
|
+
req.approvals.requiredCount > 0 &&
|
|
68
|
+
req.approvals.current < req.approvals.requiredCount) {
|
|
45
69
|
const need = req.approvals.requiredCount - req.approvals.current;
|
|
46
70
|
unmet.push(`awaiting ${need} approval${need === 1 ? "" : "s"}`);
|
|
47
71
|
}
|
|
48
|
-
if (req.conversationsResolved.required &&
|
|
72
|
+
if (req.conversationsResolved.required === true && req.conversationsResolved.resolved === false) {
|
|
49
73
|
unmet.push("unresolved conversations are required");
|
|
50
74
|
}
|
|
51
75
|
if (req.branchUpToDate && !req.branchUpToDate.current) {
|
|
@@ -61,9 +85,11 @@ export function blockedReasonFromRequirements(req) {
|
|
|
61
85
|
return unmet.length > 0 ? unmet.join("; ") : null;
|
|
62
86
|
}
|
|
63
87
|
function formatApprovals(current, requiredCount) {
|
|
88
|
+
if (requiredCount === undefined) {
|
|
89
|
+
const currentValue = current === 0 ? "None" : String(current);
|
|
90
|
+
return `Approvals: ${currentValue} [Requirement Unknown]`;
|
|
91
|
+
}
|
|
64
92
|
const tag = requiredCount > 0 ? REQUIRED : NOT_REQUIRED;
|
|
65
|
-
if (current === 0 && requiredCount === 0)
|
|
66
|
-
return `Approvals: None ${NOT_REQUIRED}`;
|
|
67
93
|
if (current === 0)
|
|
68
94
|
return `Approvals: None ${REQUIRED}`;
|
|
69
95
|
if (requiredCount > 0)
|
|
@@ -71,12 +97,15 @@ function formatApprovals(current, requiredCount) {
|
|
|
71
97
|
return `Approvals: ${current} ${NOT_REQUIRED}`;
|
|
72
98
|
}
|
|
73
99
|
function formatConversations(c) {
|
|
74
|
-
const value = c.resolved ? "Yes" : "No";
|
|
75
|
-
|
|
100
|
+
const value = c.resolved === undefined ? "Unknown" : c.resolved ? "Yes" : "No";
|
|
101
|
+
// `required: false` never reaches here (see isTrivialConversations).
|
|
102
|
+
const tag = c.required === undefined ? "[Requirement Unknown]" : REQUIRED;
|
|
76
103
|
return `Conversations Resolved: ${value} ${tag}`;
|
|
77
104
|
}
|
|
78
105
|
function formatMergeQueue(q) {
|
|
79
106
|
const tag = q.required ? REQUIRED : NOT_REQUIRED;
|
|
107
|
+
if (q.inQueue === undefined)
|
|
108
|
+
return `Merge queue: Unknown ${tag}`;
|
|
80
109
|
if (q.inQueue) {
|
|
81
110
|
const pos = q.position != null ? `position ${q.position}` : "Yes";
|
|
82
111
|
const state = q.state ? ` ${q.state}` : "";
|
|
@@ -2,16 +2,23 @@ import { EMPTY_BRANCH_RULES } from "../github/batch-parsers-rules.mjs";
|
|
|
2
2
|
export function deriveMergeRequirements(pr) {
|
|
3
3
|
const rules = pr.branchRules ?? EMPTY_BRANCH_RULES;
|
|
4
4
|
const currentApprovals = pr.latestReviews.filter((r) => r.state === "APPROVED").length;
|
|
5
|
-
const
|
|
5
|
+
const policyUnavailable = pr.transport === "rest" &&
|
|
6
|
+
(pr.transportUnavailable ?? []).some(({ field }) => /^(?:branchRules|branchProtection)(?:\.|$)/.test(field));
|
|
7
|
+
const threadStatusUnavailable = pr.reviewThreads.some((thread) => thread.isResolved === undefined);
|
|
8
|
+
const unresolvedCount = threadStatusUnavailable
|
|
9
|
+
? undefined
|
|
10
|
+
: pr.reviewThreads.filter((thread) => thread.isResolved === false).length;
|
|
6
11
|
const req = {
|
|
7
12
|
approvals: {
|
|
8
13
|
current: currentApprovals,
|
|
9
|
-
requiredCount: rules.requiredApprovingReviewCount,
|
|
14
|
+
...(!policyUnavailable && { requiredCount: rules.requiredApprovingReviewCount }),
|
|
10
15
|
},
|
|
11
16
|
conversationsResolved: {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
17
|
+
...(unresolvedCount !== undefined && {
|
|
18
|
+
resolved: unresolvedCount === 0,
|
|
19
|
+
unresolvedCount,
|
|
20
|
+
}),
|
|
21
|
+
...(!policyUnavailable && { required: rules.requiresConversationResolution }),
|
|
15
22
|
},
|
|
16
23
|
};
|
|
17
24
|
if (rules.requiresCodeOwnerReviews)
|
|
@@ -39,8 +46,12 @@ export function deriveMergeRequirements(pr) {
|
|
|
39
46
|
if (queueRequired || pr.isInMergeQueue) {
|
|
40
47
|
req.mergeQueue = {
|
|
41
48
|
required: queueRequired,
|
|
42
|
-
|
|
43
|
-
|
|
49
|
+
...(pr.transport !== "rest" || pr.isMergeQueueEnabled !== undefined
|
|
50
|
+
? { enabled: Boolean(pr.isMergeQueueEnabled) }
|
|
51
|
+
: {}),
|
|
52
|
+
...(pr.transport !== "rest" || pr.isInMergeQueue !== undefined
|
|
53
|
+
? { inQueue: Boolean(pr.isInMergeQueue) }
|
|
54
|
+
: {}),
|
|
44
55
|
...(pr.mergeQueueEntry?.position !== undefined && { position: pr.mergeQueueEntry.position }),
|
|
45
56
|
...(pr.mergeQueueEntry?.state !== undefined && { state: pr.mergeQueueEntry.state }),
|
|
46
57
|
};
|
package/bin/state/base.mjs
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { accessSync, constants, lstatSync, mkdirSync } from "node:fs";
|
|
2
3
|
import { isAbsolute, join } from "node:path";
|
|
3
4
|
import { tmpdir } from "node:os";
|
|
4
5
|
import { SAFE_PR_NUMBER, SAFE_SEGMENT } from "../util/path-segment.mjs";
|
|
6
|
+
import { resolveDurableStateBase } from "./durable-state.mjs";
|
|
5
7
|
/** Read once per process; null when the platform or `getconf` offers no per-user temp dir. */
|
|
6
8
|
let darwinUserTempDir;
|
|
7
9
|
/**
|
|
@@ -23,10 +25,49 @@ function readDarwinUserTempDir() {
|
|
|
23
25
|
return null;
|
|
24
26
|
}
|
|
25
27
|
}
|
|
28
|
+
/** Read once per process; null when the platform is not Linux or the shared base is unusable. */
|
|
29
|
+
let linuxUserStateBase;
|
|
30
|
+
/**
|
|
31
|
+
* Linux has no per-user temp dir independent of `TMPDIR`, and sandboxed agent shells point
|
|
32
|
+
* `TMPDIR` at their own directory (e.g. `/tmp/claude-<uid>`). Use `/tmp/pr-shepherd-state-<uid>`
|
|
33
|
+
* instead so sandboxed and unsandboxed runs share state. `/tmp` is shared by every user, so the
|
|
34
|
+
* directory must be a real directory (not a symlink) owned by this user and not writable by
|
|
35
|
+
* anyone else; it must also be readable, writable, and searchable from this process. Otherwise fall back to `TMPDIR`.
|
|
36
|
+
*/
|
|
37
|
+
function readLinuxUserStateBase() {
|
|
38
|
+
if (process.platform !== "linux" || typeof process.getuid !== "function")
|
|
39
|
+
return null;
|
|
40
|
+
const uid = process.getuid();
|
|
41
|
+
const dir = join("/tmp", `pr-shepherd-state-${uid}`);
|
|
42
|
+
try {
|
|
43
|
+
mkdirSync(dir, { mode: 0o700 });
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
if (err.code !== "EEXIST")
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
const stat = lstatSync(dir);
|
|
51
|
+
if (!stat.isDirectory() || stat.uid !== uid || (stat.mode & 0o022) !== 0)
|
|
52
|
+
return null;
|
|
53
|
+
accessSync(dir, constants.R_OK | constants.W_OK | constants.X_OK);
|
|
54
|
+
return dir;
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
26
60
|
export function resolveStateBase() {
|
|
27
61
|
const envDir = process.env["PR_SHEPHERD_STATE_DIR"];
|
|
28
62
|
if (envDir)
|
|
29
63
|
return envDir;
|
|
64
|
+
const durable = resolveDurableStateBase();
|
|
65
|
+
if (durable)
|
|
66
|
+
return durable;
|
|
67
|
+
if (linuxUserStateBase === undefined)
|
|
68
|
+
linuxUserStateBase = readLinuxUserStateBase();
|
|
69
|
+
if (linuxUserStateBase)
|
|
70
|
+
return linuxUserStateBase;
|
|
30
71
|
if (darwinUserTempDir === undefined)
|
|
31
72
|
darwinUserTempDir = readDarwinUserTempDir();
|
|
32
73
|
return join(darwinUserTempDir ?? tmpdir(), "pr-shepherd-state");
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Run work whose state (seen markers, stall timers, reply records) must outlive a temp dir. */
|
|
2
|
+
export declare function runWithDurableState<T>(work: () => T): T;
|
|
3
|
+
/** Cloud sessions are always durable; other callers opt in through `runWithDurableState`. */
|
|
4
|
+
export declare function durableStateRequested(): boolean;
|
|
5
|
+
/** `<git-common-dir>/pr-shepherd-state`, or null when not requested or outside a repository. */
|
|
6
|
+
export declare function resolveDurableStateBase(): string | null;
|