pr-shepherd 0.56.6 → 0.57.1
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 +150 -280
- 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/codex-notices.d.mts +8 -0
- package/bin/classify/standard/codex-notices.mjs +34 -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 +7 -0
- package/bin/classify/standard/index.mjs +21 -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 +57 -16
- 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/captured-proxy-agent.d.mts +3 -0
- package/bin/github/captured-proxy-agent.mjs +8 -0
- 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 +32 -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/legacy-dispatcher.d.mts +6 -0
- package/bin/github/legacy-dispatcher.mjs +44 -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.d.mts +6 -1
- package/bin/github/poll-summary-readiness.mjs +13 -4
- 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 +35 -20
- 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 +4 -3
- 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 +3 -1
- 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,41 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
3
|
+
import { join, resolve } from "node:path";
|
|
4
|
+
import { getEffectiveCwd } from "../execution-context.mjs";
|
|
5
|
+
/**
|
|
6
|
+
* A cloud session can be recycled between ticks, and its temp dir with it. The git common
|
|
7
|
+
* directory lives with the repository checkout, so state stored there survives whatever the
|
|
8
|
+
* session does with `TMPDIR`. Only a read-only `git rev-parse` is ever invoked.
|
|
9
|
+
*/
|
|
10
|
+
const durableScope = new AsyncLocalStorage();
|
|
11
|
+
const commonDirByCwd = new Map();
|
|
12
|
+
/** Run work whose state (seen markers, stall timers, reply records) must outlive a temp dir. */
|
|
13
|
+
export function runWithDurableState(work) {
|
|
14
|
+
return durableScope.run(true, work);
|
|
15
|
+
}
|
|
16
|
+
/** Cloud sessions are always durable; other callers opt in through `runWithDurableState`. */
|
|
17
|
+
export function durableStateRequested() {
|
|
18
|
+
return durableScope.getStore() === true || process.env["CLAUDE_CODE_REMOTE"] === "true";
|
|
19
|
+
}
|
|
20
|
+
/** `<git-common-dir>/pr-shepherd-state`, or null when not requested or outside a repository. */
|
|
21
|
+
export function resolveDurableStateBase() {
|
|
22
|
+
if (!durableStateRequested())
|
|
23
|
+
return null;
|
|
24
|
+
const cwd = getEffectiveCwd();
|
|
25
|
+
if (!commonDirByCwd.has(cwd))
|
|
26
|
+
commonDirByCwd.set(cwd, readCommonDir(cwd));
|
|
27
|
+
return commonDirByCwd.get(cwd) ?? null;
|
|
28
|
+
}
|
|
29
|
+
function readCommonDir(cwd) {
|
|
30
|
+
try {
|
|
31
|
+
const out = execFileSync("git", ["rev-parse", "--git-common-dir"], {
|
|
32
|
+
cwd,
|
|
33
|
+
encoding: "utf8",
|
|
34
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
35
|
+
}).trim();
|
|
36
|
+
return out ? join(resolve(cwd, out), "pr-shepherd-state") : null;
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -1,13 +1,25 @@
|
|
|
1
|
+
import { mostConservativeSample } from "../github/rate-limit-sample.mjs";
|
|
1
2
|
export function evaluateGraphqlQuotaWarning(bands, sample, previous, observedAt = Date.now() / 1000) {
|
|
2
3
|
const windowRolled = previous !== null && sample.resetAt > previous.resetAt && observedAt >= previous.resetAt;
|
|
4
|
+
// A sample whose own window already ended before the saved one is stale, whatever its
|
|
5
|
+
// credential, resource, or limit: keep the saved sample and do not re-arm from it.
|
|
6
|
+
const stale = previous !== null && sample.resetAt < previous.resetAt && observedAt >= sample.resetAt;
|
|
3
7
|
const rearm = previous === null ||
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
(!stale &&
|
|
9
|
+
(previous.resource !== sample.resource ||
|
|
10
|
+
previous.limit !== sample.limit ||
|
|
11
|
+
windowRolled ||
|
|
12
|
+
fingerprintChanged(previous, sample)));
|
|
13
|
+
// Same window: the lowest remaining and the highest used seen so far, so an older sample (or a
|
|
14
|
+
// proxy route whose X-RateLimit-Used disagrees) never lowers the warning level.
|
|
15
|
+
const effective = rearm || previous === null
|
|
16
|
+
? sample
|
|
17
|
+
: stale
|
|
18
|
+
? savedSample(previous)
|
|
19
|
+
: {
|
|
20
|
+
...mostConservativeSample(savedSample(previous), sample),
|
|
21
|
+
resetAt: Math.max(previous.resetAt, sample.resetAt),
|
|
22
|
+
};
|
|
11
23
|
const warned = new Set(rearm || previous === null ? [] : previous.warnedThresholds);
|
|
12
24
|
const rearmEpoch = rearm ? (previous?.rearmEpoch ?? 0) + 1 : (previous?.rearmEpoch ?? 0);
|
|
13
25
|
const crossed = bands.filter((band) => effective.remaining * 100 <= effective.limit * band.remainingPercent);
|
|
@@ -15,7 +27,9 @@ export function evaluateGraphqlQuotaWarning(bands, sample, previous, observedAt
|
|
|
15
27
|
for (const band of crossed)
|
|
16
28
|
warned.add(band.remainingPercent);
|
|
17
29
|
const active = newCrossed.at(-1);
|
|
18
|
-
const credentialFingerprint =
|
|
30
|
+
const credentialFingerprint = stale
|
|
31
|
+
? previous?.credentialFingerprint
|
|
32
|
+
: (sample.credentialFingerprint ?? previous?.credentialFingerprint);
|
|
19
33
|
const state = {
|
|
20
34
|
resource: effective.resource,
|
|
21
35
|
limit: effective.limit,
|
|
@@ -49,14 +63,7 @@ function fingerprintChanged(previous, sample) {
|
|
|
49
63
|
sample.credentialFingerprint !== undefined &&
|
|
50
64
|
previous.credentialFingerprint !== sample.credentialFingerprint);
|
|
51
65
|
}
|
|
52
|
-
|
|
53
|
-
function usageRegressed(previous, sample) {
|
|
54
|
-
if (sample.used !== undefined && previous.lastUsed !== undefined) {
|
|
55
|
-
return sample.used < previous.lastUsed;
|
|
56
|
-
}
|
|
57
|
-
return sample.remaining > previous.lastRemaining;
|
|
58
|
-
}
|
|
59
|
-
function newerSavedSample(previous) {
|
|
66
|
+
function savedSample(previous) {
|
|
60
67
|
return {
|
|
61
68
|
resource: previous.resource,
|
|
62
69
|
limit: previous.limit,
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { RestMergeOptions, RestMergeResponse } from "../github/rest-merge.mts";
|
|
2
|
+
export interface MergeRequestRecord {
|
|
3
|
+
version: 1;
|
|
4
|
+
options: RestMergeOptions;
|
|
5
|
+
startedAtUnix: number;
|
|
6
|
+
uuid?: string;
|
|
7
|
+
response?: RestMergeResponse;
|
|
8
|
+
uncertain?: true;
|
|
9
|
+
/** Identifies the one consumable source of a permanently recorded successor. */
|
|
10
|
+
replacementToken?: string;
|
|
11
|
+
}
|
|
12
|
+
type Key = {
|
|
13
|
+
owner: string;
|
|
14
|
+
repo: string;
|
|
15
|
+
pr: number;
|
|
16
|
+
};
|
|
17
|
+
export declare function readMergeRequest(key: Key): Promise<MergeRequestRecord | null>;
|
|
18
|
+
/** Persist intent exclusively before contacting GitHub; another process cannot submit it twice. */
|
|
19
|
+
export declare function claimMergeRequest(key: Key, record: MergeRequestRecord): Promise<boolean>;
|
|
20
|
+
/** Consume one durable successor of a failed request or an enqueue guarded by an older head. */
|
|
21
|
+
export declare function replaceFailedMergeRequest(key: Key, previous: MergeRequestRecord, next: MergeRequestRecord): Promise<boolean>;
|
|
22
|
+
export declare function writeMergeRequest(key: Key, record: MergeRequestRecord): Promise<void>;
|
|
23
|
+
/** A delayed status poll can update only its own intent, never a later main request. */
|
|
24
|
+
export declare function writeMergeRequestStatus(key: Key, record: MergeRequestRecord): Promise<void>;
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import { link, mkdir, readFile, rename, unlink, writeFile } from "node:fs/promises";
|
|
3
|
+
import { dirname } from "node:path";
|
|
4
|
+
import { resolvePrStatePath } from "./base.mjs";
|
|
5
|
+
import { restMergeStackGuardKey, validateRestMergeStackGuard, } from "../github/rest-merge-stack-guard.mjs";
|
|
6
|
+
const path = (key) => resolvePrStatePath(key, "merge-async.json");
|
|
7
|
+
const generation = (record) => createHash("sha256")
|
|
8
|
+
.update(JSON.stringify([
|
|
9
|
+
record.options.requireSha,
|
|
10
|
+
record.options.mergeAction,
|
|
11
|
+
record.options.mergeMethod,
|
|
12
|
+
record.startedAtUnix,
|
|
13
|
+
record.replacementToken,
|
|
14
|
+
...(record.options.expectedStack
|
|
15
|
+
? [restMergeStackGuardKey(record.options.expectedStack)]
|
|
16
|
+
: []),
|
|
17
|
+
]))
|
|
18
|
+
.digest("hex")
|
|
19
|
+
.slice(0, 24);
|
|
20
|
+
const statusPath = (key, record) => resolvePrStatePath(key, `merge-async-status-${generation(record)}.json`);
|
|
21
|
+
export async function readMergeRequest(key) {
|
|
22
|
+
let raw;
|
|
23
|
+
try {
|
|
24
|
+
raw = await readFile(path(key), "utf8");
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
if (error.code === "ENOENT")
|
|
28
|
+
return null;
|
|
29
|
+
throw error;
|
|
30
|
+
}
|
|
31
|
+
const record = parseRecord(raw);
|
|
32
|
+
try {
|
|
33
|
+
const status = parseRecord(await readFile(statusPath(key, record), "utf8"));
|
|
34
|
+
if (generation(status) !== generation(record))
|
|
35
|
+
throw new Error("Asynchronous merge status does not match its persisted intent");
|
|
36
|
+
return status;
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
if (error.code === "ENOENT")
|
|
40
|
+
return record;
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function parseRecord(raw) {
|
|
45
|
+
const record = JSON.parse(raw);
|
|
46
|
+
if (record?.version !== 1 ||
|
|
47
|
+
!/^[0-9a-f]{40}$/.test(record.options?.requireSha ?? "") ||
|
|
48
|
+
!["direct_merge", "merge_queue", "default"].includes(record.options.mergeAction) ||
|
|
49
|
+
!Number.isSafeInteger(record.startedAtUnix) ||
|
|
50
|
+
(record.uuid !== undefined && !/^[0-9a-f-]{36}$/i.test(record.uuid)) ||
|
|
51
|
+
(record.replacementToken !== undefined &&
|
|
52
|
+
!/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(record.replacementToken))) {
|
|
53
|
+
throw new Error("Invalid asynchronous merge state; outcome must be reconciled before another request");
|
|
54
|
+
}
|
|
55
|
+
if (record.options.expectedStack !== undefined) {
|
|
56
|
+
validateRestMergeStackGuard(record.options.expectedStack);
|
|
57
|
+
if (record.options.expectedStack.prefix.at(-1)?.headRefOid !== record.options.requireSha)
|
|
58
|
+
throw new Error("Invalid asynchronous merge state: expectedStack head differs from guarded SHA");
|
|
59
|
+
}
|
|
60
|
+
return record;
|
|
61
|
+
}
|
|
62
|
+
/** Persist intent exclusively before contacting GitHub; another process cannot submit it twice. */
|
|
63
|
+
export async function claimMergeRequest(key, record) {
|
|
64
|
+
await mkdir(dirname(path(key)), { recursive: true });
|
|
65
|
+
try {
|
|
66
|
+
await writeFile(path(key), `${JSON.stringify(record)}\n`, { flag: "wx" });
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
if (error.code === "EEXIST")
|
|
71
|
+
return false;
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/** Consume one durable successor of a failed request or an enqueue guarded by an older head. */
|
|
76
|
+
export async function replaceFailedMergeRequest(key, previous, next) {
|
|
77
|
+
if (previous.uncertain ||
|
|
78
|
+
(previous.response?.status !== "failed" &&
|
|
79
|
+
!(previous.response?.status === "enqueued" &&
|
|
80
|
+
previous.options.requireSha !== next.options.requireSha)))
|
|
81
|
+
return false;
|
|
82
|
+
const stem = resolvePrStatePath(key, `merge-async-successor-${generation(previous)}`);
|
|
83
|
+
const candidate = { ...next, replacementToken: randomUUID() };
|
|
84
|
+
const source = `${stem}-${candidate.replacementToken}.json`;
|
|
85
|
+
let created = false;
|
|
86
|
+
let published = false;
|
|
87
|
+
try {
|
|
88
|
+
await writeFile(source, `${JSON.stringify(candidate)}\n`, { flag: "wx" });
|
|
89
|
+
created = true;
|
|
90
|
+
if (JSON.stringify(await readMergeRequest(key)) !== JSON.stringify(previous))
|
|
91
|
+
return false;
|
|
92
|
+
let persisted = candidate;
|
|
93
|
+
try {
|
|
94
|
+
// Publish only complete bytes. This permanent marker must never be consumed or rewritten.
|
|
95
|
+
await link(source, `${stem}.json`);
|
|
96
|
+
published = true;
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
if (error.code !== "EEXIST")
|
|
100
|
+
throw error;
|
|
101
|
+
persisted = parseRecord(await readFile(`${stem}.json`, "utf8"));
|
|
102
|
+
if (!persisted.replacementToken)
|
|
103
|
+
throw new Error("Legacy merge successor has no recovery token; reconcile its outcome");
|
|
104
|
+
if (persisted.options.requireSha !== next.options.requireSha ||
|
|
105
|
+
persisted.options.mergeAction !== next.options.mergeAction ||
|
|
106
|
+
persisted.options.mergeMethod !== next.options.mergeMethod ||
|
|
107
|
+
restMergeStackGuardKey(persisted.options.expectedStack) !==
|
|
108
|
+
restMergeStackGuardKey(next.options.expectedStack))
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
if (JSON.stringify(await readMergeRequest(key)) !== JSON.stringify(previous))
|
|
112
|
+
return false;
|
|
113
|
+
try {
|
|
114
|
+
// Never recreate this source: its absence means another caller already consumed the intent.
|
|
115
|
+
await rename(`${stem}-${persisted.replacementToken}.json`, path(key));
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
if (error.code === "ENOENT")
|
|
119
|
+
return false;
|
|
120
|
+
throw error;
|
|
121
|
+
}
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
finally {
|
|
125
|
+
if (created && !published)
|
|
126
|
+
await unlink(source).catch(() => { });
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
export async function writeMergeRequest(key, record) {
|
|
130
|
+
await writeRecord(path(key), record);
|
|
131
|
+
}
|
|
132
|
+
/** A delayed status poll can update only its own intent, never a later main request. */
|
|
133
|
+
export async function writeMergeRequestStatus(key, record) {
|
|
134
|
+
await writeRecord(statusPath(key, record), record);
|
|
135
|
+
}
|
|
136
|
+
async function writeRecord(destination, record) {
|
|
137
|
+
const temporary = `${destination}.${randomUUID()}.tmp`;
|
|
138
|
+
try {
|
|
139
|
+
await writeFile(temporary, `${JSON.stringify(record)}\n`);
|
|
140
|
+
await rename(temporary, destination);
|
|
141
|
+
}
|
|
142
|
+
finally {
|
|
143
|
+
await unlink(temporary).catch(() => { });
|
|
144
|
+
}
|
|
145
|
+
}
|
|
@@ -6,6 +6,15 @@ export interface StoredPrFingerprint {
|
|
|
6
6
|
inputDigest: string;
|
|
7
7
|
fingerprint: PrFingerprint;
|
|
8
8
|
report: ShepherdReport;
|
|
9
|
+
/**
|
|
10
|
+
* Conditional REST change detectors read before the GraphQL snapshot that produced
|
|
11
|
+
* `report`, and when that snapshot ran (unix ms). Dropped whenever the report is rewritten.
|
|
12
|
+
*/
|
|
13
|
+
detectors?: StoredWaitDetectors;
|
|
14
|
+
}
|
|
15
|
+
export interface StoredWaitDetectors {
|
|
16
|
+
digest: string;
|
|
17
|
+
fullAt: number;
|
|
9
18
|
}
|
|
10
19
|
export declare function fingerprintInputDigest(config: PrShepherdConfig): string;
|
|
11
20
|
export declare function loadPrFingerprint(key: {
|
|
@@ -18,3 +27,9 @@ export declare function storePrFingerprint(key: {
|
|
|
18
27
|
repo: string;
|
|
19
28
|
pr: number;
|
|
20
29
|
}, fingerprint: PrFingerprint, report: ShepherdReport, config: PrShepherdConfig): Promise<void>;
|
|
30
|
+
/** Record the detector digest read before this tick's GraphQL snapshot beside its report. */
|
|
31
|
+
export declare function attachWaitDetectors(key: {
|
|
32
|
+
owner: string;
|
|
33
|
+
repo: string;
|
|
34
|
+
pr: number;
|
|
35
|
+
}, stored: StoredPrFingerprint, detectors: StoredWaitDetectors): Promise<void>;
|
|
@@ -5,7 +5,13 @@ import { dirname } from "node:path";
|
|
|
5
5
|
import { resolvePrStatePath } from "./base.mjs";
|
|
6
6
|
import { discoverRuleFiles } from "../classify/loader.mjs";
|
|
7
7
|
import { getEffectiveCwd } from "../execution-context.mjs";
|
|
8
|
-
const VERSION =
|
|
8
|
+
const VERSION = 4;
|
|
9
|
+
let packageVersion;
|
|
10
|
+
/** Bundled standard rules change with the package, so their digest input is its version. */
|
|
11
|
+
function readPackageVersion() {
|
|
12
|
+
packageVersion ??= JSON.parse(readFileSync(new URL("../../package.json", import.meta.url), "utf8")).version;
|
|
13
|
+
return packageVersion;
|
|
14
|
+
}
|
|
9
15
|
export function fingerprintInputDigest(config) {
|
|
10
16
|
const hash = createHash("sha256");
|
|
11
17
|
hash.update(JSON.stringify({
|
|
@@ -21,6 +27,9 @@ export function fingerprintInputDigest(config) {
|
|
|
21
27
|
neverCancelRuns: config.actions.neverCancelRuns,
|
|
22
28
|
workWhileQueued: config.actions.workWhileQueued,
|
|
23
29
|
},
|
|
30
|
+
standardRules: (config.standardRules?.length ?? 0) > 0
|
|
31
|
+
? { names: config.standardRules, version: readPackageVersion() }
|
|
32
|
+
: undefined,
|
|
24
33
|
}));
|
|
25
34
|
for (const file of discoverRuleFiles(getEffectiveCwd())) {
|
|
26
35
|
hash.update("\0");
|
|
@@ -48,17 +57,23 @@ export async function loadPrFingerprint(key) {
|
|
|
48
57
|
}
|
|
49
58
|
}
|
|
50
59
|
export async function storePrFingerprint(key, fingerprint, report, config) {
|
|
60
|
+
await writeStored(key, {
|
|
61
|
+
version: VERSION,
|
|
62
|
+
inputDigest: fingerprintInputDigest(config),
|
|
63
|
+
fingerprint,
|
|
64
|
+
report,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
/** Record the detector digest read before this tick's GraphQL snapshot beside its report. */
|
|
68
|
+
export async function attachWaitDetectors(key, stored, detectors) {
|
|
69
|
+
await writeStored(key, { ...stored, detectors });
|
|
70
|
+
}
|
|
71
|
+
async function writeStored(key, payload) {
|
|
51
72
|
const path = resolvePrStatePath(key, "fingerprint.json");
|
|
52
73
|
let tmp;
|
|
53
74
|
try {
|
|
54
75
|
await mkdir(dirname(path), { recursive: true });
|
|
55
76
|
tmp = `${path}.${randomUUID()}.tmp`;
|
|
56
|
-
const payload = {
|
|
57
|
-
version: VERSION,
|
|
58
|
-
inputDigest: fingerprintInputDigest(config),
|
|
59
|
-
fingerprint,
|
|
60
|
-
report,
|
|
61
|
-
};
|
|
62
77
|
await writeFile(tmp, `${JSON.stringify(payload)}\n`, "utf8");
|
|
63
78
|
await rename(tmp, path);
|
|
64
79
|
tmp = undefined;
|
|
@@ -30,6 +30,8 @@ export interface EtagCacheEntry {
|
|
|
30
30
|
body: unknown;
|
|
31
31
|
storedAt: number;
|
|
32
32
|
headSha?: string;
|
|
33
|
+
/** Pagination `Link` header; GitHub does not reliably repeat it on a 304. */
|
|
34
|
+
link?: string;
|
|
33
35
|
}
|
|
34
36
|
export interface DerivedCacheEntry<T = unknown> {
|
|
35
37
|
kind: "derived";
|
|
@@ -43,6 +45,7 @@ export declare function storeEtagEntry(key: StateKey, name: string, fields: {
|
|
|
43
45
|
etag: string;
|
|
44
46
|
body: unknown;
|
|
45
47
|
headSha?: string;
|
|
48
|
+
link?: string;
|
|
46
49
|
}): Promise<void>;
|
|
47
50
|
export declare function loadDerived<T = unknown>(key: StateKey, name: string): Promise<DerivedCacheEntry<T> | null>;
|
|
48
51
|
export declare function storeDerived<T = unknown>(key: StateKey, name: string, value: T, headSha?: string): Promise<void>;
|
package/bin/state/rest-cache.mjs
CHANGED
|
@@ -34,6 +34,7 @@ export async function storeEtagEntry(key, name, fields) {
|
|
|
34
34
|
body: fields.body,
|
|
35
35
|
storedAt: Date.now(),
|
|
36
36
|
...(fields.headSha !== undefined && { headSha: fields.headSha }),
|
|
37
|
+
...(fields.link !== undefined && { link: fields.link }),
|
|
37
38
|
});
|
|
38
39
|
}
|
|
39
40
|
export async function loadDerived(key, name) {
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { PrShepherdConfig } from "../config/load.mts";
|
|
2
|
+
import type { ShepherdReport } from "../types.mts";
|
|
3
|
+
/**
|
|
4
|
+
* The last REST-transport report plus the digest of the conditional-read snapshot it was built
|
|
5
|
+
* from. The REST counterpart of `fingerprint.json`: when the next tick's reads are all 304 and
|
|
6
|
+
* hash to the same digest, the stored report is still current.
|
|
7
|
+
*/
|
|
8
|
+
export interface StoredRestSnapshotReport {
|
|
9
|
+
version: number;
|
|
10
|
+
inputDigest: string;
|
|
11
|
+
snapshotDigest: string;
|
|
12
|
+
report: ShepherdReport;
|
|
13
|
+
}
|
|
14
|
+
type Key = {
|
|
15
|
+
owner: string;
|
|
16
|
+
repo: string;
|
|
17
|
+
pr: number;
|
|
18
|
+
};
|
|
19
|
+
export declare function loadRestSnapshotReport(key: Key): Promise<StoredRestSnapshotReport | null>;
|
|
20
|
+
export declare function storeRestSnapshotReport(key: Key, snapshotDigest: string, report: ShepherdReport, config: PrShepherdConfig): Promise<void>;
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { mkdir, readFile, rename, unlink, writeFile } from "node:fs/promises";
|
|
3
|
+
import { dirname } from "node:path";
|
|
4
|
+
import { resolvePrStatePath } from "./base.mjs";
|
|
5
|
+
import { fingerprintInputDigest } from "./pr-fingerprint.mjs";
|
|
6
|
+
const VERSION = 2;
|
|
7
|
+
export async function loadRestSnapshotReport(key) {
|
|
8
|
+
try {
|
|
9
|
+
const parsed = JSON.parse(await readFile(resolvePrStatePath(key, "rest-report.json"), "utf8"));
|
|
10
|
+
const record = parsed;
|
|
11
|
+
return record["version"] === VERSION &&
|
|
12
|
+
typeof record["inputDigest"] === "string" &&
|
|
13
|
+
typeof record["snapshotDigest"] === "string" &&
|
|
14
|
+
record["report"] !== null &&
|
|
15
|
+
typeof record["report"] === "object"
|
|
16
|
+
? parsed
|
|
17
|
+
: null;
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export async function storeRestSnapshotReport(key, snapshotDigest, report, config) {
|
|
24
|
+
const path = resolvePrStatePath(key, "rest-report.json");
|
|
25
|
+
let tmp;
|
|
26
|
+
try {
|
|
27
|
+
await mkdir(dirname(path), { recursive: true });
|
|
28
|
+
tmp = `${path}.${randomUUID()}.tmp`;
|
|
29
|
+
const payload = {
|
|
30
|
+
version: VERSION,
|
|
31
|
+
inputDigest: fingerprintInputDigest(config),
|
|
32
|
+
snapshotDigest,
|
|
33
|
+
report,
|
|
34
|
+
};
|
|
35
|
+
await writeFile(tmp, `${JSON.stringify(payload)}\n`, "utf8");
|
|
36
|
+
await rename(tmp, path);
|
|
37
|
+
tmp = undefined;
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
// An optimization only; a failed write just means the next tick rebuilds the report.
|
|
41
|
+
}
|
|
42
|
+
finally {
|
|
43
|
+
if (tmp !== undefined) {
|
|
44
|
+
try {
|
|
45
|
+
await unlink(tmp);
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
// best-effort cleanup
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -49,6 +49,9 @@ export declare function hasSeen(key: StateKey, id: string): Promise<boolean>;
|
|
|
49
49
|
* All errors are silently swallowed — the marker is best-effort.
|
|
50
50
|
*/
|
|
51
51
|
export declare function markSeen(key: StateKey, id: string, body: string): Promise<void>;
|
|
52
|
+
/** A definite permission denial suppresses generated retries until the feedback changes. */
|
|
53
|
+
export declare function markMutationDenied(key: StateKey, id: string, body?: string): Promise<void>;
|
|
54
|
+
export declare function mutationWasDenied(id: string, body: string, map: Map<string, SeenMarker>): boolean;
|
|
52
55
|
export declare function markReviewInlineThreads(key: StateKey, reviewId: string, inlineThreadIds: readonly string[]): Promise<void>;
|
|
53
56
|
/**
|
|
54
57
|
* Write a marker after Shepherd successfully replies to a review thread.
|
|
@@ -105,6 +105,24 @@ export async function hasSeen(key, id) {
|
|
|
105
105
|
export async function markSeen(key, id, body) {
|
|
106
106
|
await writeSeenMarker(key, id, { bodyHash: hashBody(body) });
|
|
107
107
|
}
|
|
108
|
+
/** A definite permission denial suppresses generated retries until the feedback changes. */
|
|
109
|
+
export async function markMutationDenied(key, id, body) {
|
|
110
|
+
let bodyHash = body === undefined ? undefined : hashBody(body);
|
|
111
|
+
if (bodyHash === undefined) {
|
|
112
|
+
try {
|
|
113
|
+
const marker = JSON.parse(await readFile(resolvePath(key, id), "utf8"));
|
|
114
|
+
bodyHash = marker.bodyHash;
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
if (typeof bodyHash === "string")
|
|
121
|
+
await writeSeenMarker(key, id, { deniedMutationBodyHash: bodyHash });
|
|
122
|
+
}
|
|
123
|
+
export function mutationWasDenied(id, body, map) {
|
|
124
|
+
return map.get(id)?.deniedMutationBodyHash === hashBody(body);
|
|
125
|
+
}
|
|
108
126
|
export async function markReviewInlineThreads(key, reviewId, inlineThreadIds) {
|
|
109
127
|
await writeSeenMarker(key, reviewId, {
|
|
110
128
|
inlineThreadIds: [...new Set(inlineThreadIds)].sort((a, b) => a.localeCompare(b)),
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
export interface ApiResourceUsage {
|
|
2
2
|
resource: string;
|
|
3
|
-
|
|
3
|
+
/** Quota-consuming requests; excludes `notModified` responses. Omitted when 0. */
|
|
4
|
+
requestCount?: number;
|
|
5
|
+
/** Conditional reads answered 304 Not Modified (no quota consumed). Omitted when 0. */
|
|
6
|
+
notModified?: number;
|
|
4
7
|
limit: number;
|
|
5
8
|
used?: number;
|
|
6
9
|
remaining: number;
|
|
@@ -29,6 +32,11 @@ export interface ApiUsage {
|
|
|
29
32
|
credentialSources: string[];
|
|
30
33
|
graphql?: GraphqlApiUsage;
|
|
31
34
|
rest?: ApiResourceUsage[];
|
|
35
|
+
/**
|
|
36
|
+
* REST responses that carried no rate-limit headers, so no pool reported a charge (the Claude
|
|
37
|
+
* Code proxy's `ccr` routes). Omitted when 0.
|
|
38
|
+
*/
|
|
39
|
+
unmeteredRestRequestCount?: number;
|
|
32
40
|
}
|
|
33
41
|
export interface GraphqlQuotaWarning {
|
|
34
42
|
resource: "graphql" | "core" | "combined";
|
package/bin/types/escalate.d.mts
CHANGED
|
@@ -2,7 +2,7 @@ import type { AgentCheck, AgentComment, AgentThread } from "./report.mts";
|
|
|
2
2
|
import type { ResolveCommand } from "./iterate.mts";
|
|
3
3
|
import type { CheckStatus, Review } from "./github.mts";
|
|
4
4
|
import type { MergeQueueRemovalStatus } from "./merge-requirements.mts";
|
|
5
|
-
export type EscalateTrigger = "fix-thrash" | "base-branch-unknown" | "stall-timeout" | "required-checks-unreported" | "stall-state-unavailable" | "check-follow-up-unavailable" | "authorization-required" | "merge-queue-removed" | "merge-method-unavailable";
|
|
5
|
+
export type EscalateTrigger = "transport-unsupported" | "fix-thrash" | "base-branch-unknown" | "stall-timeout" | "required-checks-unreported" | "stall-state-unavailable" | "check-follow-up-unavailable" | "authorization-required" | "merge-queue-removed" | "merge-method-unavailable";
|
|
6
6
|
export interface AgentStalledCheck {
|
|
7
7
|
name: string;
|
|
8
8
|
status: CheckStatus;
|
package/bin/types/github.d.mts
CHANGED
|
@@ -24,11 +24,11 @@ type RepositoryPermission = "NONE" | "READ" | "TRIAGE" | "WRITE" | "MAINTAIN" |
|
|
|
24
24
|
/** Raw GitHub viewer fields used to gate remote actions. */
|
|
25
25
|
export interface ViewerAuthorization {
|
|
26
26
|
repositoryPermission: RepositoryPermission | null;
|
|
27
|
-
viewerCanAdminister
|
|
28
|
-
viewerDidAuthor
|
|
29
|
-
viewerCanUpdate
|
|
30
|
-
viewerCanEnableAutoMerge
|
|
31
|
-
viewerCanEditFiles
|
|
27
|
+
viewerCanAdminister?: boolean;
|
|
28
|
+
viewerDidAuthor?: boolean;
|
|
29
|
+
viewerCanUpdate?: boolean;
|
|
30
|
+
viewerCanEnableAutoMerge?: boolean;
|
|
31
|
+
viewerCanEditFiles?: boolean;
|
|
32
32
|
headRepositoryPermission: RepositoryPermission | null;
|
|
33
33
|
}
|
|
34
34
|
/** Raw relationship between a comment author and the repository, as reported by GitHub. */
|
|
@@ -60,12 +60,17 @@ export interface CheckRun {
|
|
|
60
60
|
workflowId?: string;
|
|
61
61
|
/** True when GraphQL reported at least one CheckRun annotation. Omitted when false. */
|
|
62
62
|
hasAnnotations?: boolean;
|
|
63
|
+
/**
|
|
64
|
+
* Every annotation on this run, when BatchPr's `annotations(first: 1)` page already held all
|
|
65
|
+
* of them. Internal: lets the check path skip `CheckRunAnnotationsBatch`; never rendered.
|
|
66
|
+
*/
|
|
67
|
+
inlineAnnotations?: import("./check-annotations.mts").CheckAnnotation[];
|
|
63
68
|
}
|
|
64
69
|
export interface ReviewThread {
|
|
65
70
|
id: string;
|
|
66
|
-
isResolved
|
|
67
|
-
isOutdated
|
|
68
|
-
isMinimized
|
|
71
|
+
isResolved?: boolean;
|
|
72
|
+
isOutdated?: boolean;
|
|
73
|
+
isMinimized?: boolean;
|
|
69
74
|
viewerCanReply?: boolean;
|
|
70
75
|
viewerCanResolve?: boolean;
|
|
71
76
|
path: string | null;
|
|
@@ -98,7 +103,7 @@ export interface SuggestionBlock {
|
|
|
98
103
|
}
|
|
99
104
|
export interface PrComment {
|
|
100
105
|
id: string;
|
|
101
|
-
isMinimized
|
|
106
|
+
isMinimized?: boolean;
|
|
102
107
|
viewerCanMinimize?: boolean;
|
|
103
108
|
author: string;
|
|
104
109
|
authorType: AuthorType;
|
|
@@ -146,6 +151,13 @@ export interface MergeStatusResult {
|
|
|
146
151
|
mergeRequirements?: MergeRequirements;
|
|
147
152
|
}
|
|
148
153
|
export interface BatchPrData extends BatchPrMergeFields {
|
|
154
|
+
/** Present when this snapshot was fetched through REST. */
|
|
155
|
+
transport?: "rest";
|
|
156
|
+
/** Fields unavailable from the selected transport; absence is never fabricated as false. */
|
|
157
|
+
transportUnavailable?: Array<{
|
|
158
|
+
field: string;
|
|
159
|
+
reason: string;
|
|
160
|
+
}>;
|
|
149
161
|
nodeId: string;
|
|
150
162
|
number: number;
|
|
151
163
|
state: "OPEN" | "CLOSED" | "MERGED";
|
|
@@ -172,6 +184,8 @@ export interface BatchPrData extends BatchPrMergeFields {
|
|
|
172
184
|
baseRefName: string;
|
|
173
185
|
/** Base commit GitHub recorded for this PR (`PullRequest.baseRefOid`), not the branch's live tip. */
|
|
174
186
|
baseRefOid?: string;
|
|
187
|
+
/** The base branch's live tip (`baseRef.target.oid`). GraphQL only; keys the BaseBehind cache. */
|
|
188
|
+
baseTipOid?: string;
|
|
175
189
|
reviewRequests: Array<{
|
|
176
190
|
login: string;
|
|
177
191
|
}>;
|
package/bin/types/iterate.d.mts
CHANGED
|
@@ -6,6 +6,7 @@ import type { EscalateDetails } from "./escalate.mts";
|
|
|
6
6
|
import type { MergeCommandPlan } from "./merge-action.mts";
|
|
7
7
|
import type { ProtectedRun } from "./protected-run.mts";
|
|
8
8
|
import type { ApiUsage, GraphqlQuotaWarning } from "./api-usage.mts";
|
|
9
|
+
import type { NextCheck } from "./next-check.mts";
|
|
9
10
|
export type ShepherdAction = "wait" | "ready" | "fix_code" | "mark_ready" | "merge" | "cancel" | "escalate";
|
|
10
11
|
export interface IterateResultSummary {
|
|
11
12
|
passing: number;
|
|
@@ -15,6 +16,11 @@ export interface IterateResultSummary {
|
|
|
15
16
|
superseded: number;
|
|
16
17
|
}
|
|
17
18
|
export interface IterateResultBase {
|
|
19
|
+
transport?: "rest";
|
|
20
|
+
transportUnavailable?: Array<{
|
|
21
|
+
field: string;
|
|
22
|
+
reason: string;
|
|
23
|
+
}>;
|
|
18
24
|
pr: number;
|
|
19
25
|
repo: string;
|
|
20
26
|
status: ShepherdStatus;
|
|
@@ -58,6 +64,10 @@ export interface IterateResultBase {
|
|
|
58
64
|
apiUsage?: ApiUsage;
|
|
59
65
|
quotaWarning?: GraphqlQuotaWarning;
|
|
60
66
|
fingerprintReused?: true;
|
|
67
|
+
/** Set to `event` when this result came from event mode: one tick, no sleeping. Omitted in poll mode. */
|
|
68
|
+
pollMode?: "event";
|
|
69
|
+
/** Event mode only: when to come back if no GitHub event arrives first. Omitted for terminal actions. */
|
|
70
|
+
nextCheck?: NextCheck;
|
|
61
71
|
/** Classification-rule resolve/minimize applied during this fetch. Omitted when nothing happened. */
|
|
62
72
|
ruleAutoResolve?: RuleAutoResolveReport;
|
|
63
73
|
}
|
|
@@ -79,9 +89,9 @@ interface IterateResultCancel extends IterateResultBase {
|
|
|
79
89
|
log: string;
|
|
80
90
|
}
|
|
81
91
|
export interface ResolveCommand {
|
|
82
|
-
/** Argv for spawn-style execution. May contain a `$DISMISS_MESSAGE` placeholder.
|
|
92
|
+
/** Argv for spawn-style execution. May contain a `$DISMISS_MESSAGE` placeholder. `--require-sha` is never in `argv` — `renderResolveCommand` (from `commands/iterate/render.mts`) appends `--require-sha HEAD` when rendering if `requiresHeadSha` is true. Use `renderResolveCommand` to render as a command string; don't naive-join. */
|
|
83
93
|
argv: string[];
|
|
84
|
-
/** When true, `renderResolveCommand` appends `--require-sha
|
|
94
|
+
/** When true, `renderResolveCommand` appends `--require-sha HEAD` to the rendered command. */
|
|
85
95
|
requiresHeadSha: boolean;
|
|
86
96
|
/** Whether the model must substitute $DISMISS_MESSAGE with a specific description of the fix. */
|
|
87
97
|
requiresDismissMessage: boolean;
|
|
@@ -175,8 +185,20 @@ export interface IterateCommandOptions extends GlobalOptions {
|
|
|
175
185
|
* the public `IterateInput` in api.mts.
|
|
176
186
|
*/
|
|
177
187
|
fingerprintCache?: boolean;
|
|
188
|
+
/**
|
|
189
|
+
* Internal. Event mode passes a sink that receives the CI-start stall deadline (oldest
|
|
190
|
+
* unstarted check age + stall timeout) so `nextCheck` can wake before it. Excluded from
|
|
191
|
+
* the public `IterateInput` in api.mts.
|
|
192
|
+
*/
|
|
193
|
+
stallDeadlineSink?: {
|
|
194
|
+
ciStartDeadlineSeconds?: number;
|
|
195
|
+
};
|
|
178
196
|
/** Shepherd through readiness and emit the exact merge/queue command when ready. */
|
|
179
197
|
merge?: boolean;
|
|
198
|
+
/** Override `poll.mode` for this call. `auto` resolves to `event` when `CLAUDE_CODE_REMOTE=true`. */
|
|
199
|
+
pollMode?: import("../config/load.mts").PollMode;
|
|
200
|
+
/** Override `iterate.instructions` for this call. */
|
|
201
|
+
instructions?: import("../config/load.mts").InstructionStyle;
|
|
180
202
|
/**
|
|
181
203
|
* Internal. Defers quota warnings until an until-terminal poll breaks. Set only by
|
|
182
204
|
* `runPollCore`; excluded from the public `IterateInput` in api.mts.
|