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.
Files changed (420) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +44 -27
  3. package/bin/api.d.mts +25 -3
  4. package/bin/api.mjs +51 -13
  5. package/bin/checks/classify.d.mts +1 -0
  6. package/bin/checks/classify.mjs +1 -1
  7. package/bin/checks/conclusions.d.mts +5 -1
  8. package/bin/checks/conclusions.mjs +8 -2
  9. package/bin/checks/superseded.d.mts +8 -2
  10. package/bin/checks/superseded.mjs +24 -7
  11. package/bin/classify/loader.d.mts +2 -0
  12. package/bin/classify/loader.mjs +15 -0
  13. package/bin/classify/standard/author.d.mts +3 -0
  14. package/bin/classify/standard/author.mjs +10 -0
  15. package/bin/classify/standard/codecov-report.d.mts +4 -0
  16. package/bin/classify/standard/codecov-report.mjs +10 -0
  17. package/bin/classify/standard/coderabbit-notices.d.mts +8 -0
  18. package/bin/classify/standard/coderabbit-notices.mjs +38 -0
  19. package/bin/classify/standard/coderabbit-review-summary.d.mts +8 -0
  20. package/bin/classify/standard/coderabbit-review-summary.mjs +37 -0
  21. package/bin/classify/standard/codex-notices.d.mts +7 -0
  22. package/bin/classify/standard/codex-notices.mjs +36 -0
  23. package/bin/classify/standard/codex-review-summary.d.mts +7 -0
  24. package/bin/classify/standard/codex-review-summary.mjs +20 -0
  25. package/bin/classify/standard/dependabot-notices.d.mts +4 -0
  26. package/bin/classify/standard/dependabot-notices.mjs +15 -0
  27. package/bin/classify/standard/gemini-notices.d.mts +4 -0
  28. package/bin/classify/standard/gemini-notices.mjs +13 -0
  29. package/bin/classify/standard/index.d.mts +4 -0
  30. package/bin/classify/standard/index.mjs +22 -0
  31. package/bin/classify/standard/kilo-no-issues.d.mts +4 -0
  32. package/bin/classify/standard/kilo-no-issues.mjs +17 -0
  33. package/bin/classify/standard/sourcery-notices.d.mts +8 -0
  34. package/bin/classify/standard/sourcery-notices.mjs +20 -0
  35. package/bin/cli/api-usage-formatter.mjs +6 -1
  36. package/bin/cli/apply-merge-handler.d.mts +3 -0
  37. package/bin/cli/apply-merge-handler.mjs +73 -0
  38. package/bin/cli/arg-flags.d.mts +2 -0
  39. package/bin/cli/arg-flags.mjs +47 -0
  40. package/bin/cli/args.mjs +10 -41
  41. package/bin/cli/clean-handler.d.mts +1 -0
  42. package/bin/cli/clean-handler.mjs +71 -0
  43. package/bin/cli/default-poll.mjs +3 -0
  44. package/bin/cli/event-state-scope.d.mts +19 -0
  45. package/bin/cli/event-state-scope.mjs +60 -0
  46. package/bin/cli/fix-formatter.mjs +5 -11
  47. package/bin/cli/handlers.d.mts +0 -1
  48. package/bin/cli/handlers.mjs +8 -64
  49. package/bin/cli/help-command-pages.d.mts +62 -18
  50. package/bin/cli/help-command-pages.mjs +59 -15
  51. package/bin/cli/help-iterate-poll-pages.d.mts +2 -2
  52. package/bin/cli/help-iterate-poll-pages.mjs +6 -0
  53. package/bin/cli/help-log-file-page.d.mts +1 -1
  54. package/bin/cli/help-log-file-page.mjs +4 -1
  55. package/bin/cli/help-top-page.d.mts +1 -1
  56. package/bin/cli/help-top-page.mjs +5 -0
  57. package/bin/cli/help.d.mts +63 -19
  58. package/bin/cli/help.mjs +2 -0
  59. package/bin/cli/iterate-branch-segment.d.mts +2 -0
  60. package/bin/cli/iterate-branch-segment.mjs +12 -7
  61. package/bin/cli/iterate-flags.d.mts +7 -1
  62. package/bin/cli/iterate-flags.mjs +38 -0
  63. package/bin/cli/iterate-formatter.d.mts +6 -6
  64. package/bin/cli/iterate-formatter.mjs +56 -22
  65. package/bin/cli/iterate-instructions.mjs +45 -15
  66. package/bin/cli/iterate-lean.mjs +39 -14
  67. package/bin/cli/iterate-merge-formatter.d.mts +6 -0
  68. package/bin/cli/iterate-merge-formatter.mjs +24 -14
  69. package/bin/cli/iterate-verbose.mjs +3 -6
  70. package/bin/cli/list-formatters.mjs +3 -1
  71. package/bin/cli/mutate-formatter.mjs +10 -5
  72. package/bin/cli/next-check-format.d.mts +7 -0
  73. package/bin/cli/next-check-format.mjs +10 -0
  74. package/bin/cli/playbook-handler.d.mts +5 -0
  75. package/bin/cli/playbook-handler.mjs +40 -0
  76. package/bin/cli/poll-handler.mjs +7 -6
  77. package/bin/cli/poll-summary-formatter.mjs +11 -2
  78. package/bin/cli/poll-targets.mjs +2 -0
  79. package/bin/cli/resolve-validators.mjs +2 -2
  80. package/bin/cli/runner.mjs +6 -1
  81. package/bin/cli/stack-layer-view.d.mts +33 -0
  82. package/bin/cli/stack-layer-view.mjs +1 -0
  83. package/bin/cli/stack-overview.d.mts +10 -28
  84. package/bin/cli/stack-overview.mjs +39 -14
  85. package/bin/cli/transport-args.d.mts +5 -0
  86. package/bin/cli/transport-args.mjs +23 -0
  87. package/bin/cli/transport-formatter.d.mts +9 -0
  88. package/bin/cli/transport-formatter.mjs +13 -0
  89. package/bin/cli-parser.mjs +62 -7
  90. package/bin/commands/apply-merge-options.d.mts +4 -0
  91. package/bin/commands/apply-merge-options.mjs +34 -0
  92. package/bin/commands/apply-merge-output.d.mts +7 -0
  93. package/bin/commands/apply-merge-output.mjs +20 -0
  94. package/bin/commands/apply-merge-pull.d.mts +20 -0
  95. package/bin/commands/apply-merge-pull.mjs +15 -0
  96. package/bin/commands/apply-merge.d.mts +18 -0
  97. package/bin/commands/apply-merge.mjs +126 -0
  98. package/bin/commands/apply-queue-removal.mjs +7 -0
  99. package/bin/commands/check-annotations.mjs +28 -18
  100. package/bin/commands/check-execution-context.d.mts +5 -0
  101. package/bin/commands/check-execution-context.mjs +16 -1
  102. package/bin/commands/check-fingerprint.d.mts +26 -2
  103. package/bin/commands/check-fingerprint.mjs +64 -28
  104. package/bin/commands/check-terminal-report.mjs +11 -1
  105. package/bin/commands/check-unreported.d.mts +6 -2
  106. package/bin/commands/check-unreported.mjs +32 -12
  107. package/bin/commands/check-wait-detectors.d.mts +35 -0
  108. package/bin/commands/check-wait-detectors.mjs +107 -0
  109. package/bin/commands/check.d.mts +4 -2
  110. package/bin/commands/check.mjs +130 -40
  111. package/bin/commands/event-instructions.d.mts +11 -0
  112. package/bin/commands/event-instructions.mjs +27 -0
  113. package/bin/commands/event-mode.d.mts +14 -0
  114. package/bin/commands/event-mode.mjs +138 -0
  115. package/bin/commands/iterate/api-usage.mjs +4 -2
  116. package/bin/commands/iterate/base.mjs +4 -0
  117. package/bin/commands/iterate/check-blocker-gate.mjs +14 -2
  118. package/bin/commands/iterate/check-instructions.d.mts +9 -10
  119. package/bin/commands/iterate/check-instructions.mjs +23 -22
  120. package/bin/commands/iterate/classify.mjs +19 -5
  121. package/bin/commands/iterate/escalate.mjs +13 -3
  122. package/bin/commands/iterate/fix-code.mjs +26 -9
  123. package/bin/commands/iterate/fix-instruction-threads.d.mts +0 -8
  124. package/bin/commands/iterate/fix-instruction-threads.mjs +0 -16
  125. package/bin/commands/iterate/fix-loop-instruction.d.mts +9 -0
  126. package/bin/commands/iterate/fix-loop-instruction.mjs +35 -0
  127. package/bin/commands/iterate/helpers.mjs +15 -2
  128. package/bin/commands/iterate/index.mjs +26 -4
  129. package/bin/commands/iterate/mark-ready.mjs +50 -10
  130. package/bin/commands/iterate/merge-plan.d.mts +19 -0
  131. package/bin/commands/iterate/merge-plan.mjs +88 -0
  132. package/bin/commands/iterate/merge-state.mjs +3 -0
  133. package/bin/commands/iterate/merge.d.mts +4 -15
  134. package/bin/commands/iterate/merge.mjs +18 -51
  135. package/bin/commands/iterate/merged-base-pull-requests.d.mts +2 -6
  136. package/bin/commands/iterate/merged-base-pull-requests.mjs +15 -11
  137. package/bin/commands/iterate/native-stack-rebase.mjs +11 -0
  138. package/bin/commands/iterate/render.d.mts +8 -2
  139. package/bin/commands/iterate/render.mjs +28 -42
  140. package/bin/commands/iterate/stack-trunk-conflict.mjs +17 -1
  141. package/bin/commands/iterate/stale-ancestry.mjs +4 -1
  142. package/bin/commands/iterate/stall.d.mts +5 -1
  143. package/bin/commands/iterate/stall.mjs +19 -7
  144. package/bin/commands/iterate/thread-mutation-routing.d.mts +1 -0
  145. package/bin/commands/iterate/thread-mutation-routing.mjs +4 -2
  146. package/bin/commands/iterate/unreported-required.mjs +5 -2
  147. package/bin/commands/mark-files-as-viewed.mjs +7 -0
  148. package/bin/commands/next-check.d.mts +31 -0
  149. package/bin/commands/next-check.mjs +46 -0
  150. package/bin/commands/playbook.d.mts +13 -0
  151. package/bin/commands/playbook.mjs +48 -0
  152. package/bin/commands/poll-mode.d.mts +8 -0
  153. package/bin/commands/poll-mode.mjs +14 -0
  154. package/bin/commands/poll-progress.d.mts +1 -0
  155. package/bin/commands/poll-progress.mjs +6 -3
  156. package/bin/commands/poll-quota.d.mts +3 -2
  157. package/bin/commands/poll-quota.mjs +7 -3
  158. package/bin/commands/poll-summary-explicit-instructions.d.mts +2 -0
  159. package/bin/commands/poll-summary-explicit-instructions.mjs +10 -1
  160. package/bin/commands/poll-summary-instructions.mjs +16 -17
  161. package/bin/commands/poll-summary.mjs +42 -17
  162. package/bin/commands/poll.d.mts +0 -1
  163. package/bin/commands/poll.mjs +11 -19
  164. package/bin/commands/quota-selection.d.mts +2 -1
  165. package/bin/commands/quota-selection.mjs +12 -7
  166. package/bin/commands/resolve-mutate.mjs +33 -5
  167. package/bin/commands/rest-stack-merge-readiness.d.mts +4 -0
  168. package/bin/commands/rest-stack-merge-readiness.mjs +74 -0
  169. package/bin/commands/rule-auto-resolve.mjs +9 -3
  170. package/bin/commands/shepherd-journal.d.mts +5 -4
  171. package/bin/commands/shepherd-journal.mjs +9 -8
  172. package/bin/commands/stack-drain.d.mts +1 -9
  173. package/bin/commands/stack-drain.mjs +50 -37
  174. package/bin/commands/stack-handoffs.d.mts +10 -0
  175. package/bin/commands/stack-handoffs.mjs +25 -0
  176. package/bin/commands/stack-stall.d.mts +5 -0
  177. package/bin/commands/stack-stall.mjs +11 -0
  178. package/bin/commands/stack-work.d.mts +5 -0
  179. package/bin/commands/stack-work.mjs +32 -0
  180. package/bin/comments/existing-reply-scan.d.mts +17 -0
  181. package/bin/comments/existing-reply-scan.mjs +41 -0
  182. package/bin/comments/rate-limit.mjs +2 -2
  183. package/bin/comments/resolve.d.mts +7 -1
  184. package/bin/comments/resolve.mjs +108 -18
  185. package/bin/comments/rest-reply.d.mts +9 -0
  186. package/bin/comments/rest-reply.mjs +95 -0
  187. package/bin/comments/rest-review-mutations.d.mts +22 -0
  188. package/bin/comments/rest-review-mutations.mjs +125 -0
  189. package/bin/comments/review-visibility.d.mts +1 -1
  190. package/bin/comments/review-visibility.mjs +3 -2
  191. package/bin/comments/sha-poll.mjs +7 -3
  192. package/bin/comments/thread-visibility.mjs +10 -5
  193. package/bin/comments/uncertain-replies.d.mts +11 -0
  194. package/bin/comments/uncertain-replies.mjs +121 -0
  195. package/bin/comments/uncertain-reply-guards.d.mts +10 -0
  196. package/bin/comments/uncertain-reply-guards.mjs +94 -0
  197. package/bin/comments/visible-comments.d.mts +1 -1
  198. package/bin/comments/visible-comments.mjs +4 -2
  199. package/bin/config/load.d.mts +37 -1
  200. package/bin/config/load.mjs +73 -1
  201. package/bin/config.json +6 -2
  202. package/bin/exit-codes.d.mts +3 -0
  203. package/bin/exit-codes.mjs +8 -0
  204. package/bin/github/api-telemetry-aggregate.d.mts +15 -4
  205. package/bin/github/api-telemetry-aggregate.mjs +57 -18
  206. package/bin/github/api-telemetry.d.mts +4 -0
  207. package/bin/github/api-telemetry.mjs +22 -8
  208. package/bin/github/apply-review-preflight.d.mts +20 -0
  209. package/bin/github/apply-review-preflight.mjs +61 -0
  210. package/bin/github/base-behind-cache.d.mts +15 -0
  211. package/bin/github/base-behind-cache.mjs +18 -0
  212. package/bin/github/batch-parse-suites.d.mts +4 -2
  213. package/bin/github/batch-parse-suites.mjs +6 -1
  214. package/bin/github/batch-parser-helpers.mjs +40 -2
  215. package/bin/github/batch-parsers.mjs +1 -0
  216. package/bin/github/batch-raw-rules.d.mts +26 -0
  217. package/bin/github/batch-raw-types.d.mts +8 -6
  218. package/bin/github/batch-stack-evidence.d.mts +28 -0
  219. package/bin/github/batch-stack-evidence.mjs +62 -0
  220. package/bin/github/batch.d.mts +15 -2
  221. package/bin/github/batch.mjs +34 -17
  222. package/bin/github/check-annotations-batch.d.mts +0 -5
  223. package/bin/github/check-annotations-batch.mjs +25 -4
  224. package/bin/github/check-annotations.mjs +3 -1
  225. package/bin/github/client.d.mts +8 -19
  226. package/bin/github/client.mjs +45 -13
  227. package/bin/github/fingerprint-fields.d.mts +8 -0
  228. package/bin/github/fingerprint-fields.mjs +6 -1
  229. package/bin/github/fingerprint.d.mts +4 -2
  230. package/bin/github/fingerprint.mjs +6 -38
  231. package/bin/github/github-fetch.d.mts +2 -0
  232. package/bin/github/github-fetch.mjs +19 -0
  233. package/bin/github/gql/base-behind.gql +3 -0
  234. package/bin/github/gql/batch-pr-page.gql +16 -0
  235. package/bin/github/gql/batch-pr.gql +57 -0
  236. package/bin/github/gql/commit-check-contexts.gql +17 -0
  237. package/bin/github/gql/get-pr-body.gql +1 -0
  238. package/bin/github/gql/poll-stack-summary.gql +1 -0
  239. package/bin/github/gql/poll-summary-check-contexts.gql +5 -0
  240. package/bin/github/gql/poll-summary-fragment.gql +65 -15
  241. package/bin/github/gql/stack-handoff-check-runs.gql +42 -0
  242. package/bin/github/graphql-http.mjs +9 -4
  243. package/bin/github/graphql-internal-retry.d.mts +2 -0
  244. package/bin/github/graphql-internal-retry.mjs +1 -1
  245. package/bin/github/graphql-quota-probe.d.mts +6 -0
  246. package/bin/github/graphql-quota-probe.mjs +56 -0
  247. package/bin/github/graphql-session-refusal.d.mts +8 -0
  248. package/bin/github/graphql-session-refusal.mjs +14 -0
  249. package/bin/github/http-request.d.mts +1 -1
  250. package/bin/github/http-request.mjs +22 -6
  251. package/bin/github/http-utils.mjs +6 -0
  252. package/bin/github/merge-target-rules.d.mts +12 -3
  253. package/bin/github/merge-target-rules.mjs +71 -34
  254. package/bin/github/mutation-policy.d.mts +6 -0
  255. package/bin/github/mutation-policy.mjs +21 -0
  256. package/bin/github/poll-summary-annotation-probe.mjs +17 -10
  257. package/bin/github/poll-summary-check-blockers.d.mts +1 -1
  258. package/bin/github/poll-summary-check-blockers.mjs +2 -2
  259. package/bin/github/poll-summary-check-hydration.mjs +25 -10
  260. package/bin/github/poll-summary-checks.d.mts +2 -2
  261. package/bin/github/poll-summary-checks.mjs +9 -6
  262. package/bin/github/poll-summary-command.d.mts +2 -0
  263. package/bin/github/poll-summary-command.mjs +24 -0
  264. package/bin/github/poll-summary-fingerprint.d.mts +1 -1
  265. package/bin/github/poll-summary-fingerprint.mjs +4 -2
  266. package/bin/github/poll-summary-handoff-fields.d.mts +3 -0
  267. package/bin/github/poll-summary-handoff-fields.mjs +73 -0
  268. package/bin/github/poll-summary-projector.d.mts +1 -1
  269. package/bin/github/poll-summary-projector.mjs +17 -24
  270. package/bin/github/poll-summary-raw.d.mts +70 -10
  271. package/bin/github/poll-summary-readiness.mjs +4 -1
  272. package/bin/github/poll-summary-rest-queue.d.mts +3 -0
  273. package/bin/github/poll-summary-rest-queue.mjs +12 -0
  274. package/bin/github/poll-summary-review.d.mts +1 -1
  275. package/bin/github/poll-summary-review.mjs +23 -11
  276. package/bin/github/poll-summary-route.mjs +19 -3
  277. package/bin/github/poll-summary-stack-page.d.mts +11 -0
  278. package/bin/github/poll-summary-stack-page.mjs +27 -0
  279. package/bin/github/poll-summary-unreported.d.mts +3 -1
  280. package/bin/github/poll-summary-unreported.mjs +6 -3
  281. package/bin/github/poll-summary.d.mts +4 -1
  282. package/bin/github/poll-summary.mjs +47 -33
  283. package/bin/github/queries.d.mts +7 -3
  284. package/bin/github/queries.mjs +8 -4
  285. package/bin/github/rate-limit-sample.d.mts +7 -0
  286. package/bin/github/rate-limit-sample.mjs +12 -0
  287. package/bin/github/reply-recovery-read.d.mts +50 -0
  288. package/bin/github/reply-recovery-read.mjs +118 -0
  289. package/bin/github/reply-thread-transcripts.d.mts +0 -1
  290. package/bin/github/reply-thread-transcripts.mjs +24 -1
  291. package/bin/github/rest-annotation-read.d.mts +5 -0
  292. package/bin/github/rest-annotation-read.mjs +44 -0
  293. package/bin/github/rest-batch-read.d.mts +12 -0
  294. package/bin/github/rest-batch-read.mjs +57 -0
  295. package/bin/github/rest-batch-snapshot.d.mts +30 -0
  296. package/bin/github/rest-batch-snapshot.mjs +115 -0
  297. package/bin/github/rest-batch-unavailable.d.mts +5 -0
  298. package/bin/github/rest-batch-unavailable.mjs +16 -0
  299. package/bin/github/rest-check-read.d.mts +34 -0
  300. package/bin/github/rest-check-read.mjs +124 -0
  301. package/bin/github/rest-comment-authorship.d.mts +7 -0
  302. package/bin/github/rest-comment-authorship.mjs +11 -0
  303. package/bin/github/rest-conditional-scope.d.mts +20 -0
  304. package/bin/github/rest-conditional-scope.mjs +44 -0
  305. package/bin/github/rest-explicit-summary-read.d.mts +3 -0
  306. package/bin/github/rest-explicit-summary-read.mjs +8 -0
  307. package/bin/github/rest-feedback-read.d.mts +36 -0
  308. package/bin/github/rest-feedback-read.mjs +147 -0
  309. package/bin/github/rest-feedback-validation.d.mts +5 -0
  310. package/bin/github/rest-feedback-validation.mjs +28 -0
  311. package/bin/github/rest-http.d.mts +12 -0
  312. package/bin/github/rest-http.mjs +108 -23
  313. package/bin/github/rest-identities.d.mts +22 -0
  314. package/bin/github/rest-identities.mjs +134 -0
  315. package/bin/github/rest-merge-stack-guard.d.mts +14 -0
  316. package/bin/github/rest-merge-stack-guard.mjs +41 -0
  317. package/bin/github/rest-merge.d.mts +29 -0
  318. package/bin/github/rest-merge.mjs +56 -0
  319. package/bin/github/rest-merged-base-read.d.mts +9 -0
  320. package/bin/github/rest-merged-base-read.mjs +48 -0
  321. package/bin/github/rest-pr-core.d.mts +52 -0
  322. package/bin/github/rest-pr-core.mjs +60 -0
  323. package/bin/github/rest-reader-core.d.mts +16 -0
  324. package/bin/github/rest-reader-core.mjs +154 -0
  325. package/bin/github/rest-review-read.d.mts +11 -0
  326. package/bin/github/rest-review-read.mjs +47 -0
  327. package/bin/github/rest-rules-read.d.mts +9 -0
  328. package/bin/github/rest-rules-read.mjs +154 -0
  329. package/bin/github/rest-session-refusal.d.mts +5 -0
  330. package/bin/github/rest-session-refusal.mjs +21 -0
  331. package/bin/github/rest-snapshot-context.d.mts +10 -0
  332. package/bin/github/rest-snapshot-context.mjs +1 -0
  333. package/bin/github/rest-stack-read.d.mts +21 -0
  334. package/bin/github/rest-stack-read.mjs +47 -0
  335. package/bin/github/rest-stack-summary.d.mts +8 -0
  336. package/bin/github/rest-stack-summary.mjs +62 -0
  337. package/bin/github/rest-summary-read.d.mts +7 -0
  338. package/bin/github/rest-summary-read.mjs +112 -0
  339. package/bin/github/rest-terminal-batch.d.mts +14 -0
  340. package/bin/github/rest-terminal-batch.mjs +45 -0
  341. package/bin/github/rest-text.mjs +7 -3
  342. package/bin/github/rest-thread-read.d.mts +4 -0
  343. package/bin/github/rest-thread-read.mjs +35 -0
  344. package/bin/github/rest-viewer-read.d.mts +2 -0
  345. package/bin/github/rest-viewer-read.mjs +41 -0
  346. package/bin/github/stack-bottom.d.mts +3 -0
  347. package/bin/github/stack-bottom.mjs +14 -0
  348. package/bin/github/stack-handoff-context.d.mts +25 -0
  349. package/bin/github/stack-handoff-context.mjs +12 -0
  350. package/bin/github/stack-handoff-page.d.mts +17 -0
  351. package/bin/github/stack-handoff-page.mjs +306 -0
  352. package/bin/github/stack-handoff.d.mts +16 -0
  353. package/bin/github/stack-handoff.mjs +115 -0
  354. package/bin/github/stack-order.d.mts +6 -0
  355. package/bin/github/stack-order.mjs +13 -0
  356. package/bin/github/stack-read.d.mts +7 -5
  357. package/bin/github/stack-read.mjs +22 -24
  358. package/bin/github/stack-size-hint.d.mts +3 -0
  359. package/bin/github/stack-size-hint.mjs +14 -0
  360. package/bin/github/suggestion-thread.mjs +27 -1
  361. package/bin/github/thread-comments.mjs +9 -1
  362. package/bin/github/transport-evidence.d.mts +20 -0
  363. package/bin/github/transport-evidence.mjs +26 -0
  364. package/bin/github/transport-mode.d.mts +2 -0
  365. package/bin/github/transport-mode.mjs +5 -0
  366. package/bin/github/transport.d.mts +19 -0
  367. package/bin/github/transport.mjs +126 -0
  368. package/bin/github/unsupported-rest.d.mts +4 -0
  369. package/bin/github/unsupported-rest.mjs +7 -0
  370. package/bin/mcp/server.mjs +52 -3
  371. package/bin/merge-status/derive.d.mts +2 -0
  372. package/bin/merge-status/derive.mjs +17 -13
  373. package/bin/merge-status/required-check-requirements.d.mts +7 -0
  374. package/bin/merge-status/required-check-requirements.mjs +15 -0
  375. package/bin/merge-status/requirements-format.d.mts +5 -0
  376. package/bin/merge-status/requirements-format.mjs +41 -12
  377. package/bin/merge-status/requirements.mjs +18 -7
  378. package/bin/state/base.mjs +41 -0
  379. package/bin/state/durable-state.d.mts +6 -0
  380. package/bin/state/durable-state.mjs +41 -0
  381. package/bin/state/graphql-quota-policy.mjs +23 -16
  382. package/bin/state/merge-request.d.mts +25 -0
  383. package/bin/state/merge-request.mjs +145 -0
  384. package/bin/state/pr-fingerprint.d.mts +15 -0
  385. package/bin/state/pr-fingerprint.mjs +22 -7
  386. package/bin/state/rest-cache.d.mts +3 -0
  387. package/bin/state/rest-cache.mjs +1 -0
  388. package/bin/state/rest-snapshot-report.d.mts +21 -0
  389. package/bin/state/rest-snapshot-report.mjs +52 -0
  390. package/bin/state/seen-comments.d.mts +3 -0
  391. package/bin/state/seen-comments.mjs +18 -0
  392. package/bin/types/api-usage.d.mts +9 -1
  393. package/bin/types/escalate.d.mts +1 -1
  394. package/bin/types/github.d.mts +23 -9
  395. package/bin/types/iterate.d.mts +24 -2
  396. package/bin/types/merge-action.d.mts +1 -1
  397. package/bin/types/merge-requirements.d.mts +7 -7
  398. package/bin/types/next-check.d.mts +12 -0
  399. package/bin/types/next-check.mjs +1 -0
  400. package/bin/types/poll-summary.d.mts +21 -0
  401. package/bin/types/report.d.mts +13 -1
  402. package/bin/types/review-thread.d.mts +1 -1
  403. package/bin/types.d.mts +1 -0
  404. package/bin/types.mjs +1 -0
  405. package/package.json +3 -2
  406. package/plugins/pr-shepherd/.codex-plugin/plugin.json +1 -1
  407. package/plugins/pr-shepherd/.codex.mcp.json +1 -1
  408. package/plugins/pr-shepherd/.mcp.json +1 -1
  409. package/plugins/pr-shepherd/skills/mark-files-as-viewed/SKILL.md +1 -1
  410. package/plugins/pr-shepherd/skills/pr-shepherd/SKILL.md +14 -51
  411. package/plugins/pr-shepherd/skills/pr-shepherd/references/ci-failure-triage.md +12 -16
  412. package/plugins/pr-shepherd/skills/pr-shepherd/references/cloud-event-loop.md +14 -0
  413. package/plugins/pr-shepherd/skills/pr-shepherd/references/create-pr.md +10 -0
  414. package/plugins/pr-shepherd/skills/pr-shepherd/references/fix-code-loop.md +10 -0
  415. package/plugins/pr-shepherd/skills/pr-shepherd/references/mcp-fallback.md +9 -0
  416. package/plugins/pr-shepherd/skills/reduce-pr-noise/references/classifiers.md +2 -0
  417. package/bin/github/gql/pr-fingerprint.gql +0 -75
  418. package/plugins/pr-shepherd/skills/pr-shepherd/references/journal.md +0 -7
  419. package/plugins/pr-shepherd/skills/pr-shepherd/references/review-mutations.md +0 -9
  420. package/plugins/pr-shepherd/skills/pr-shepherd/references/stack-merge.md +0 -7
@@ -45,10 +45,11 @@ export async function attachAndMergeCheckAnnotations(buckets, seenMap, prNumber,
45
45
  const annotated = await attachUnseenCheckAnnotations(candidates, seenMap, prNumber, cacheOpts);
46
46
  const byId = new Map(annotated.flatMap((c) => (c.id != null ? [[c.id, c]] : [])));
47
47
  const apply = (list) => list.map((c) => {
48
- if (c.id == null)
49
- return c;
50
- const next = byId.get(c.id);
51
- return next !== undefined ? next : c;
48
+ const next = (c.id == null ? undefined : byId.get(c.id)) ?? c;
49
+ if (next.inlineAnnotations === undefined)
50
+ return next;
51
+ const { inlineAnnotations: _inline, ...rest } = next;
52
+ return rest;
52
53
  });
53
54
  return {
54
55
  passing: apply(buckets.passing),
@@ -61,22 +62,31 @@ export async function attachAndMergeCheckAnnotations(buckets, seenMap, prNumber,
61
62
  async function attachUnseenCheckAnnotations(checks, seenMap, prNumber, cacheOpts) {
62
63
  if (checks.length === 0)
63
64
  return checks;
64
- const ids = checks.flatMap((check) => (check.id == null ? [] : [check.id]));
65
- let batch;
66
- try {
67
- batch = await fetchCheckRunAnnotationsBatch(ids, cacheOpts);
65
+ const annotations = new Map();
66
+ for (const check of checks) {
67
+ if (check.id != null && check.inlineAnnotations)
68
+ annotations.set(check.id, check.inlineAnnotations);
68
69
  }
69
- catch (err) {
70
- if (pollRateLimitRetryAfterMs(err) !== null)
71
- throw err;
72
- writeAnnotationFailureSummary(prNumber, ids.length, err);
73
- return checks;
74
- }
75
- const firstFailure = batch.failures[0];
76
- if (firstFailure !== undefined) {
77
- writeAnnotationFailureSummary(prNumber, batch.failures.length, firstFailure.error);
70
+ const ids = checks.flatMap((check) => check.id == null || annotations.has(check.id) ? [] : [check.id]);
71
+ if (ids.length > 0) {
72
+ let batch;
73
+ try {
74
+ batch = await fetchCheckRunAnnotationsBatch(ids, cacheOpts);
75
+ }
76
+ catch (err) {
77
+ if (pollRateLimitRetryAfterMs(err) !== null)
78
+ throw err;
79
+ writeAnnotationFailureSummary(prNumber, ids.length, err);
80
+ return checks.map((check) => withUnseenAnnotations(check, annotations, seenMap));
81
+ }
82
+ const firstFailure = batch.failures[0];
83
+ if (firstFailure !== undefined) {
84
+ writeAnnotationFailureSummary(prNumber, batch.failures.length, firstFailure.error);
85
+ }
86
+ for (const [id, list] of batch.annotations)
87
+ annotations.set(id, list);
78
88
  }
79
- return checks.map((check) => withUnseenAnnotations(check, batch.annotations, seenMap));
89
+ return checks.map((check) => withUnseenAnnotations(check, annotations, seenMap));
80
90
  }
81
91
  function withUnseenAnnotations(check, annotations, seenMap) {
82
92
  const fetched = check.id == null ? undefined : annotations.get(check.id);
@@ -1,9 +1,14 @@
1
1
  import type { RepoInfo } from "../github/client.mts";
2
2
  import { readStackTopology } from "../github/stack-read.mts";
3
+ import type { BatchStackEvidence, TrunkRules } from "../github/batch-stack-evidence.mts";
3
4
  import type { RawSummaryPr } from "../github/poll-summary-raw.mts";
4
5
  /** Fresh for each iterate tick; never persisted or exposed in CLI output. */
5
6
  export declare function createCheckExecutionContext(readyDelaySeconds?: number): {
6
7
  readStackTopology(pr: number, repo: RepoInfo): ReturnType<typeof readStackTopology>;
8
+ /** Adopt the stack reads that this tick's `BatchPr` first page already answered. */
9
+ seedStack(pr: number, repo: RepoInfo, evidence: BatchStackEvidence): void;
10
+ /** Trunk rules from this tick's `BatchPr`, only when they are for `refName`. */
11
+ trunkRules(pr: number, repo: RepoInfo, refName: string): TrunkRules | undefined;
7
12
  wantsReceiptSummary(pr: number, repo: RepoInfo): Promise<boolean>;
8
13
  setReceiptSummary(raw: RawSummaryPr | null): void;
9
14
  getReceiptSummary(): RawSummaryPr | null;
@@ -4,11 +4,13 @@ import { readyDelayElapsed } from "./ready-delay.mjs";
4
4
  /** Fresh for each iterate tick; never persisted or exposed in CLI output. */
5
5
  export function createCheckExecutionContext(readyDelaySeconds) {
6
6
  const topologies = new Map();
7
+ const trunks = new Map();
8
+ const keyOf = (pr, repo) => `${repo.owner.toLowerCase()}/${repo.name.toLowerCase()}#${pr}`;
7
9
  let receiptSummary = null;
8
10
  let receiptSummaryInvalidated = false;
9
11
  return {
10
12
  readStackTopology(pr, repo) {
11
- const key = `${repo.owner.toLowerCase()}/${repo.name.toLowerCase()}#${pr}`;
13
+ const key = keyOf(pr, repo);
12
14
  let pending = topologies.get(key);
13
15
  if (!pending) {
14
16
  pending = readStackTopology(pr, repo);
@@ -16,6 +18,19 @@ export function createCheckExecutionContext(readyDelaySeconds) {
16
18
  }
17
19
  return pending;
18
20
  },
21
+ /** Adopt the stack reads that this tick's `BatchPr` first page already answered. */
22
+ seedStack(pr, repo, evidence) {
23
+ const key = keyOf(pr, repo);
24
+ if (evidence.topology)
25
+ topologies.set(key, Promise.resolve(evidence.topology));
26
+ if (evidence.trunk)
27
+ trunks.set(key, evidence.trunk);
28
+ },
29
+ /** Trunk rules from this tick's `BatchPr`, only when they are for `refName`. */
30
+ trunkRules(pr, repo, refName) {
31
+ const trunk = trunks.get(keyOf(pr, repo));
32
+ return trunk?.refName === refName ? trunk : undefined;
33
+ },
19
34
  async wantsReceiptSummary(pr, repo) {
20
35
  if (readyDelaySeconds === undefined)
21
36
  return false;
@@ -1,8 +1,32 @@
1
1
  import { type RepoInfo } from "../github/client.mts";
2
+ import { type PrFingerprint } from "../github/fingerprint.mts";
3
+ import type { RestSnapshotState } from "../github/rest-conditional-scope.mts";
2
4
  import type { PrShepherdConfig } from "../config/load.mts";
3
5
  import type { ShepherdReport } from "../types.mts";
4
- export declare function tryReuseFingerprintReport(prNumber: number, repo: RepoInfo, stateKey: {
6
+ export declare function reportAllowsFingerprintSkip(report: ShepherdReport): boolean;
7
+ /** Whether a fingerprint's windows cover everything it summarizes, so equality means unchanged. */
8
+ export declare function fingerprintIsComplete(fingerprint: PrFingerprint): boolean;
9
+ /**
10
+ * GraphQL fingerprint reuse. Returns a decider for `fetchPrBatch`'s first page, or `undefined`
11
+ * when the stored report cannot be reused whatever GitHub says. The decider replays the stored
12
+ * report only when the first page's fingerprint equals the stored one, so a hit costs the one
13
+ * BatchPr request and skips every supplement, and a miss continues the same snapshot.
14
+ */
15
+ export declare function fingerprintReuser(prNumber: number, repo: RepoInfo, stateKey: {
5
16
  owner: string;
6
17
  repo: string;
7
18
  pr: number;
8
- }, config: PrShepherdConfig): Promise<ShepherdReport | null>;
19
+ }, config: PrShepherdConfig): Promise<((live: PrFingerprint) => Promise<ShepherdReport | null>) | undefined>;
20
+ /**
21
+ * REST counterpart of `fingerprintReuser`: after the snapshot reads, reuse the previous
22
+ * report only when every conditional read came back 304 with the same validators it was built
23
+ * from. No separate mergeability read is made. Mergeability moves when the head or the base
24
+ * moves: the head is in the pull body, and the base branch summary read by
25
+ * `readRestBranchRules` carries the base's `commit.sha`, so a base push answers 200 and blocks
26
+ * reuse. An unsettled `mergeable: null` pull body is never cached, so it cannot answer 304.
27
+ */
28
+ export declare function tryReuseRestSnapshotReport(stateKey: {
29
+ owner: string;
30
+ repo: string;
31
+ pr: number;
32
+ }, config: PrShepherdConfig, snapshot: RestSnapshotState | undefined): Promise<ShepherdReport | null>;
@@ -1,9 +1,11 @@
1
1
  import { getMergeableState } from "../github/client.mjs";
2
- import { fetchPrFingerprint, fingerprintsEqual, } from "../github/fingerprint.mjs";
2
+ import { fingerprintsEqual } from "../github/fingerprint.mjs";
3
3
  import { fingerprintInputDigest, loadPrFingerprint } from "../state/pr-fingerprint.mjs";
4
+ import { loadRestSnapshotReport } from "../state/rest-snapshot-report.mjs";
4
5
  import { hasCheckDrivenActionableWork } from "./check-annotations.mjs";
5
6
  import { stripReplayedRuleAutoResolve } from "./rule-auto-resolve-format.mjs";
6
- function reportAllowsFingerprintSkip(report) {
7
+ import { getGithubTransport } from "../github/transport.mjs";
8
+ export function reportAllowsFingerprintSkip(report) {
7
9
  if (report.status === "READY")
8
10
  return false;
9
11
  if (report.mergeStatus.state !== "OPEN")
@@ -26,40 +28,74 @@ function reportAllowsFingerprintSkip(report) {
26
28
  (report.ruleAutoResolveReviewSummaryIds?.length ?? 0) === 0 &&
27
29
  !hasCheckDrivenActionableWork(report.checks, report.mergeStatus.status));
28
30
  }
29
- export async function tryReuseFingerprintReport(prNumber, repo, stateKey, config) {
31
+ /** Whether a fingerprint's windows cover everything it summarizes, so equality means unchanged. */
32
+ export function fingerprintIsComplete(fingerprint) {
33
+ return (!fingerprint.isInMergeQueue &&
34
+ fingerprint.checkSuitesComplete &&
35
+ fingerprint.commentCount <= 100 &&
36
+ fingerprint.reviewCount <= 100 &&
37
+ fingerprint.threadCount <= 20 &&
38
+ !fingerprint.hasMultiCommentThreads &&
39
+ fingerprint.rulesComplete);
40
+ }
41
+ /**
42
+ * GraphQL fingerprint reuse. Returns a decider for `fetchPrBatch`'s first page, or `undefined`
43
+ * when the stored report cannot be reused whatever GitHub says. The decider replays the stored
44
+ * report only when the first page's fingerprint equals the stored one, so a hit costs the one
45
+ * BatchPr request and skips every supplement, and a miss continues the same snapshot.
46
+ */
47
+ export async function fingerprintReuser(prNumber, repo, stateKey, config) {
48
+ if (getGithubTransport() === "rest")
49
+ return undefined;
30
50
  const cached = await loadPrFingerprint(stateKey);
31
51
  if (cached?.inputDigest == null || cached.inputDigest !== fingerprintInputDigest(config)) {
32
- return null;
52
+ return undefined;
33
53
  }
34
54
  if (!reportAllowsFingerprintSkip(cached.report))
55
+ return undefined;
56
+ if (!fingerprintIsComplete(cached.fingerprint))
57
+ return undefined;
58
+ return async (live) => {
59
+ if (!fingerprintIsComplete(live) || !fingerprintsEqual(cached.fingerprint, live))
60
+ return null;
61
+ try {
62
+ const survives = await cachedReportSurvivesMergeabilityRefresh(prNumber, repo, cached.report, cached.fingerprint);
63
+ if (!survives)
64
+ return null;
65
+ }
66
+ catch {
67
+ // This REST read runs inside the GraphQL BatchPr operation. Its failure must not read as a
68
+ // GraphQL failure (which could switch transports), so build the full snapshot instead.
69
+ return null;
70
+ }
71
+ return {
72
+ ...stripReplayedRuleAutoResolve(cached.report),
73
+ fingerprintReused: true,
74
+ };
75
+ };
76
+ }
77
+ /**
78
+ * REST counterpart of `fingerprintReuser`: after the snapshot reads, reuse the previous
79
+ * report only when every conditional read came back 304 with the same validators it was built
80
+ * from. No separate mergeability read is made. Mergeability moves when the head or the base
81
+ * moves: the head is in the pull body, and the base branch summary read by
82
+ * `readRestBranchRules` carries the base's `commit.sha`, so a base push answers 200 and blocks
83
+ * reuse. An unsettled `mergeable: null` pull body is never cached, so it cannot answer 304.
84
+ */
85
+ export async function tryReuseRestSnapshotReport(stateKey, config, snapshot) {
86
+ if (snapshot === undefined || !snapshot.allNotModified)
35
87
  return null;
36
- if (cached.fingerprint.isInMergeQueue)
37
- return null;
38
- if (!cached.fingerprint.checkSuitesComplete)
39
- return null;
40
- if (cached.fingerprint.commentCount > 100)
41
- return null;
42
- if (cached.fingerprint.reviewCount > 100)
43
- return null;
44
- if (cached.fingerprint.threadCount > 20)
45
- return null;
46
- if (cached.fingerprint.hasMultiCommentThreads)
47
- return null;
48
- if (!cached.fingerprint.rulesComplete)
49
- return null;
50
- const live = await fetchPrFingerprint(prNumber, repo);
51
- if (live.isInMergeQueue || !live.checkSuitesComplete)
52
- return null;
53
- if (live.commentCount > 100 || live.reviewCount > 100 || live.threadCount > 20)
54
- return null;
55
- if (live.hasMultiCommentThreads || !live.rulesComplete)
88
+ const cached = await loadRestSnapshotReport(stateKey);
89
+ if (cached === null || cached.snapshotDigest !== snapshot.digest)
56
90
  return null;
57
- if (!fingerprintsEqual(cached.fingerprint, live))
91
+ if (cached.inputDigest !== fingerprintInputDigest(config))
58
92
  return null;
59
- if (!(await cachedReportSurvivesMergeabilityRefresh(prNumber, repo, cached.report, cached.fingerprint))) {
93
+ if (!reportAllowsFingerprintSkip(cached.report))
60
94
  return null;
61
- }
62
- return { ...stripReplayedRuleAutoResolve(cached.report), fingerprintReused: true };
95
+ return {
96
+ ...stripReplayedRuleAutoResolve(cached.report),
97
+ fingerprintReused: true,
98
+ };
63
99
  }
64
100
  async function cachedReportSurvivesMergeabilityRefresh(prNumber, repo, report, fingerprint) {
65
101
  const restDerived = fingerprint.mergeable !== report.mergeStatus.mergeable ||
@@ -1,12 +1,22 @@
1
+ /** A merged PR answered from its first REST pull read never read reviews, threads or rules. */
2
+ const requirementsUnread = (batchData) => (batchData.transportUnavailable ?? []).some(({ field }) => field === "mergeRequirements");
3
+ function withoutRequirements(mergeStatus) {
4
+ const { mergeRequirements: _unread, ...rest } = mergeStatus;
5
+ return rest;
6
+ }
1
7
  export function buildTerminalReport(prNumber, repo, batchData, mergeStatus, status) {
2
8
  return {
3
9
  pr: prNumber,
4
10
  nodeId: batchData.nodeId,
11
+ ...(batchData.transport && { transport: batchData.transport }),
12
+ ...(batchData.transportUnavailable?.length && {
13
+ transportUnavailable: batchData.transportUnavailable,
14
+ }),
5
15
  headSha: batchData.headRefOid,
6
16
  repo: `${repo.owner}/${repo.name}`,
7
17
  status,
8
18
  baseBranch: batchData.baseRefName,
9
- mergeStatus,
19
+ mergeStatus: requirementsUnread(batchData) ? withoutRequirements(mergeStatus) : mergeStatus,
10
20
  checks: {
11
21
  passing: [],
12
22
  failing: [],
@@ -3,6 +3,8 @@ import type { BatchPrData, CheckRun, ShepherdReport } from "../types.mts";
3
3
  import type { RepoInfo } from "../github/client.mts";
4
4
  import type { CheckExecutionContext } from "./check-execution-context.mts";
5
5
  export interface UnreportedRequiredFields {
6
+ /** Required contexts of the merge target: the stack trunk for a native-stack layer. */
7
+ requiredContexts: string[];
6
8
  unreportedRequiredChecks?: string[];
7
9
  trunkBehindBy?: number;
8
10
  /** Commits on the PR base that this head does not contain. Omitted when zero or a stack. */
@@ -24,6 +26,8 @@ export declare function collectUnreportedRequired(input: {
24
26
  }, context?: CheckExecutionContext): Promise<UnreportedRequiredFields>;
25
27
  /**
26
28
  * A fingerprint hit can keep a stale trunk `behindBy`. Refresh the trunk compare
27
- * and the required-context diff without refetching the whole batch.
29
+ * and the required-context diff without refetching the whole batch. Returns null
30
+ * when the trunk's required contexts changed: they decide how checks classify, so
31
+ * the cached report cannot be patched and the caller must check afresh.
28
32
  */
29
- export declare function refreshCachedUnreported(report: ShepherdReport, repo: RepoInfo, context?: CheckExecutionContext): Promise<ShepherdReport>;
33
+ export declare function refreshCachedUnreported(report: ShepherdReport, repo: RepoInfo, context?: CheckExecutionContext, baseTipOid?: string): Promise<ShepherdReport | null>;
@@ -11,18 +11,24 @@ export async function collectUnreportedRequired(input, context) {
11
11
  pr: input.pr,
12
12
  baseRefName: input.batchData.baseRefName,
13
13
  headRefName: input.batchData.headRefName,
14
+ headRefOid: input.batchData.headRefOid,
14
15
  localContexts,
15
16
  stack: input.batchData.stack,
16
17
  }, context);
17
18
  const unreported = unreportedRequiredContexts(target.contexts, reportedCheckNames(input.checks));
19
+ // Like `baseBehindBy`, the trunk compare matters only to an unreported required check.
20
+ const trunkBehindBy = unreported.length > 0 && target.trunkBehindBy ? await target.trunkBehindBy() : 0;
18
21
  const actionsRunning = actionsWorkflowInProgress(input.suites, new Set(input.relevantEvents));
19
22
  const baseBehindBy = unreported.length > 0 && !input.batchData.stack
20
- ? await loadBaseBehindBy(input.owner, input.name, input.batchData.baseRefName, input.batchData.headRefOid)
23
+ ? await loadBaseBehindBy(input.owner, input.name, input.batchData.baseRefName, input.batchData.headRefOid, {
24
+ stateKey: { owner: input.owner, repo: input.name, pr: input.pr },
25
+ ...(input.batchData.baseTipOid && { baseTipOid: input.batchData.baseTipOid }),
26
+ })
21
27
  : 0;
22
28
  return {
29
+ requiredContexts: target.contexts,
23
30
  ...(unreported.length > 0 && { unreportedRequiredChecks: unreported }),
24
- ...(target.trunkBehindBy !== undefined &&
25
- target.trunkBehindBy > 0 && { trunkBehindBy: target.trunkBehindBy }),
31
+ ...(trunkBehindBy > 0 && { trunkBehindBy }),
26
32
  ...(baseBehindBy > 0 && { baseBehindBy }),
27
33
  ...(actionsRunning && { actionsWorkflowInProgress: true }),
28
34
  ...(target.stackBottomPr !== undefined && { stackBottomPr: target.stackBottomPr }),
@@ -32,32 +38,38 @@ export async function collectUnreportedRequired(input, context) {
32
38
  }
33
39
  /**
34
40
  * A fingerprint hit can keep a stale trunk `behindBy`. Refresh the trunk compare
35
- * and the required-context diff without refetching the whole batch.
41
+ * and the required-context diff without refetching the whole batch. Returns null
42
+ * when the trunk's required contexts changed: they decide how checks classify, so
43
+ * the cached report cannot be patched and the caller must check afresh.
36
44
  */
37
- export async function refreshCachedUnreported(report, repo, context) {
45
+ export async function refreshCachedUnreported(report, repo, context, baseTipOid) {
38
46
  const stack = report.mergeStatus?.mergeRequirements?.stack;
39
47
  if (!report.headRefName)
40
48
  return report;
41
49
  if (!stack)
42
- return refreshBaseBehind(report, repo);
50
+ return refreshBaseBehind(report, repo, baseTipOid);
51
+ const cached = report.mergeStatus.mergeRequirements?.requiredStatusChecks?.contexts ?? [];
43
52
  const target = await loadMergeTargetStatus({
44
53
  owner: repo.owner,
45
54
  name: repo.name,
46
55
  pr: report.pr,
47
56
  baseRefName: report.baseBranch,
48
57
  headRefName: report.headRefName,
49
- localContexts: report.mergeStatus.mergeRequirements?.requiredStatusChecks?.contexts ?? [],
58
+ ...(report.headSha && { headRefOid: report.headSha }),
59
+ localContexts: cached,
50
60
  stack,
51
61
  }, context);
62
+ if (!sameContexts(cached, target.contexts))
63
+ return null;
52
64
  const unreported = unreportedRequiredContexts(target.contexts, reportedCheckNames(reportChecks(report)));
53
65
  const next = { ...report };
54
66
  if (unreported.length > 0)
55
67
  next.unreportedRequiredChecks = unreported;
56
68
  else
57
69
  delete next.unreportedRequiredChecks;
58
- if (target.trunkBehindBy !== undefined && target.trunkBehindBy > 0) {
59
- next.trunkBehindBy = target.trunkBehindBy;
60
- }
70
+ const trunkBehindBy = unreported.length > 0 && target.trunkBehindBy ? await target.trunkBehindBy() : 0;
71
+ if (trunkBehindBy > 0)
72
+ next.trunkBehindBy = trunkBehindBy;
61
73
  else
62
74
  delete next.trunkBehindBy;
63
75
  if (target.stackBottomPr !== undefined)
@@ -67,7 +79,7 @@ export async function refreshCachedUnreported(report, repo, context) {
67
79
  return next;
68
80
  }
69
81
  /** A fingerprint hit can keep a stale base compare after main moves. */
70
- async function refreshBaseBehind(report, repo) {
82
+ async function refreshBaseBehind(report, repo, baseTipOid) {
71
83
  if ((report.unreportedRequiredChecks?.length ?? 0) === 0) {
72
84
  if (report.baseBehindBy === undefined)
73
85
  return report;
@@ -80,7 +92,10 @@ async function refreshBaseBehind(report, repo) {
80
92
  const headOid = report.headSha;
81
93
  if (!headOid)
82
94
  return report;
83
- const behind = await loadBaseBehindBy(repo.owner, repo.name, report.baseBranch, headOid);
95
+ const behind = await loadBaseBehindBy(repo.owner, repo.name, report.baseBranch, headOid, {
96
+ stateKey: { owner: repo.owner, repo: repo.name, pr: report.pr },
97
+ ...(baseTipOid && { baseTipOid }),
98
+ });
84
99
  const next = { ...report };
85
100
  if (behind > 0)
86
101
  next.baseBehindBy = behind;
@@ -99,3 +114,8 @@ function reportChecks(report) {
99
114
  ...(report.checks.supersededNames ?? []).map((name) => ({ name })),
100
115
  ];
101
116
  }
117
+ function sameContexts(a, b) {
118
+ const left = new Set(a.map((c) => c.trim()));
119
+ const right = new Set(b.map((c) => c.trim()));
120
+ return left.size === right.size && [...right].every((c) => left.has(c));
121
+ }
@@ -0,0 +1,35 @@
1
+ import type { RepoInfo } from "../github/client.mts";
2
+ import { type PrFingerprint } from "../github/fingerprint.mts";
3
+ import type { PrShepherdConfig } from "../config/load.mts";
4
+ import type { ShepherdReport } from "../types.mts";
5
+ type StateKey = {
6
+ owner: string;
7
+ repo: string;
8
+ pr: number;
9
+ };
10
+ /**
11
+ * Outcome of the GraphQL transport's REST change detectors. `reused` replays the stored report
12
+ * without any GraphQL request. Otherwise `digest` is what the detectors read before this tick's
13
+ * GraphQL snapshot, recorded beside the new report by `recordWaitDetectors`.
14
+ */
15
+ export type WaitDetectorResult = {
16
+ reused: ShepherdReport;
17
+ baseTipOid: string;
18
+ } | {
19
+ digest: string;
20
+ readAt: number;
21
+ };
22
+ /**
23
+ * On the GraphQL transport, run the REST change detectors for an idle stored report. Returns
24
+ * `undefined` when the detectors do not apply. A detector failure never reaches the GraphQL
25
+ * transport's fallback: the tick just takes its ordinary GraphQL path. A secondary rate limit
26
+ * is rethrown so the tick aborts and polling backs off.
27
+ */
28
+ export declare function readWaitDetectors(prNumber: number, repo: RepoInfo, stateKey: StateKey, config: PrShepherdConfig, now?: number): Promise<WaitDetectorResult | undefined>;
29
+ /**
30
+ * After a GraphQL tick, store the digest its detectors read beside the report that tick built.
31
+ * `fingerprint` is the tick's live fingerprint; the stored entry must be the one it wrote (or
32
+ * the unchanged entry a fingerprint hit replayed), so a stale report never gains a digest.
33
+ */
34
+ export declare function recordWaitDetectors(stateKey: StateKey, config: PrShepherdConfig, detectors: WaitDetectorResult | undefined, fingerprint: PrFingerprint | undefined): Promise<void>;
35
+ export {};
@@ -0,0 +1,107 @@
1
+ import { fingerprintsEqual } from "../github/fingerprint.mjs";
2
+ import { readRest, readRestPages, restCollection, restObject, restRepoPath, restString, } from "../github/rest-reader-core.mjs";
3
+ import { restSnapshotState, withRestConditionalScope } from "../github/rest-conditional-scope.mjs";
4
+ import { getGithubTransport } from "../github/transport.mjs";
5
+ import { GitHubRequestError } from "../github/errors.mjs";
6
+ import { rateLimitKind } from "../github/rate-limit-kind.mjs";
7
+ import { attachWaitDetectors, fingerprintInputDigest, loadPrFingerprint, } from "../state/pr-fingerprint.mjs";
8
+ import { fingerprintIsComplete, reportAllowsFingerprintSkip } from "./check-fingerprint.mjs";
9
+ import { stripReplayedRuleAutoResolve } from "./rule-auto-resolve-format.mjs";
10
+ /**
11
+ * Whether conditional REST reads can stand in for the GraphQL snapshot of this stored report.
12
+ * Only an idle wait qualifies: nothing to act on, no check still running, a settled single-PR
13
+ * merge state from GraphQL, and a fingerprint whose windows cover everything it summarizes.
14
+ */
15
+ function detectorsCover(stored) {
16
+ const { report, fingerprint } = stored;
17
+ return (report.transport === undefined &&
18
+ report.headSha !== undefined &&
19
+ reportAllowsFingerprintSkip(report) &&
20
+ fingerprintIsComplete(fingerprint) &&
21
+ report.checks.inProgress.length === 0 &&
22
+ report.actionsWorkflowInProgress !== true &&
23
+ report.mergeStatus.status !== "UNKNOWN" &&
24
+ report.mergeStatus.mergeRequirements?.stack === undefined &&
25
+ fingerprint.stackKey === "" &&
26
+ fingerprint.mergeable === report.mergeStatus.mergeable &&
27
+ fingerprint.mergeStateStatus === report.mergeStatus.mergeStateStatus);
28
+ }
29
+ /**
30
+ * Read the PR's REST change detectors: the pull, the head's check runs, check suites and
31
+ * statuses, the three feedback lists, and the base branch. Every read is conditional, so an
32
+ * unchanged resource answers a free 304.
33
+ */
34
+ async function readDetectors(prNumber, repo, report) {
35
+ const prefix = restRepoPath(repo);
36
+ const head = encodeURIComponent(report.headSha);
37
+ await readRest("GET", `${prefix}/pulls/${prNumber}`);
38
+ await readRestPages(`${prefix}/commits/${head}/check-runs?filter=latest`, (body) => restCollection(body, "check_runs"));
39
+ await readRestPages(`${prefix}/commits/${head}/check-suites`, (body) => restCollection(body, "check_suites"));
40
+ await readRestPages(`${prefix}/commits/${head}/statuses`);
41
+ await readRestPages(`${prefix}/pulls/${prNumber}/reviews`);
42
+ await readRestPages(`${prefix}/issues/${prNumber}/comments`);
43
+ await readRestPages(`${prefix}/pulls/${prNumber}/comments`);
44
+ const branch = restObject(await readRest("GET", `${prefix}/branches/${encodeURIComponent(report.baseBranch)}`), "base branch");
45
+ return restString(restObject(branch.commit, "base branch commit").sha, "base branch sha");
46
+ }
47
+ /**
48
+ * On the GraphQL transport, run the REST change detectors for an idle stored report. Returns
49
+ * `undefined` when the detectors do not apply. A detector failure never reaches the GraphQL
50
+ * transport's fallback: the tick just takes its ordinary GraphQL path. A secondary rate limit
51
+ * is rethrown so the tick aborts and polling backs off.
52
+ */
53
+ export async function readWaitDetectors(prNumber, repo, stateKey, config, now = Date.now()) {
54
+ const reconcileMs = config.poll.reconcileSeconds * 1000;
55
+ if (reconcileMs === 0 || getGithubTransport() === "rest")
56
+ return undefined;
57
+ const stored = await loadPrFingerprint(stateKey);
58
+ if (stored === null || stored.inputDigest !== fingerprintInputDigest(config))
59
+ return undefined;
60
+ if (!detectorsCover(stored))
61
+ return undefined;
62
+ let observed;
63
+ try {
64
+ observed = await withRestConditionalScope(stateKey, async () => {
65
+ const baseTipOid = await readDetectors(prNumber, repo, stored.report);
66
+ return { baseTipOid, state: restSnapshotState() };
67
+ });
68
+ }
69
+ catch (error) {
70
+ // A secondary limit aborts the tick, as at the other REST call sites, so polling backs off.
71
+ if (error instanceof GitHubRequestError && rateLimitKind(error) === "secondary")
72
+ throw error;
73
+ return undefined;
74
+ }
75
+ const state = observed.state;
76
+ const previous = stored.detectors;
77
+ if (state.allNotModified &&
78
+ previous?.digest === state.digest &&
79
+ typeof previous.fullAt === "number" &&
80
+ now - previous.fullAt < reconcileMs) {
81
+ return {
82
+ reused: { ...stripReplayedRuleAutoResolve(stored.report), fingerprintReused: true },
83
+ baseTipOid: observed.baseTipOid,
84
+ };
85
+ }
86
+ return { digest: state.digest, readAt: now };
87
+ }
88
+ /**
89
+ * After a GraphQL tick, store the digest its detectors read beside the report that tick built.
90
+ * `fingerprint` is the tick's live fingerprint; the stored entry must be the one it wrote (or
91
+ * the unchanged entry a fingerprint hit replayed), so a stale report never gains a digest.
92
+ */
93
+ export async function recordWaitDetectors(stateKey, config, detectors, fingerprint) {
94
+ if (detectors === undefined || "reused" in detectors)
95
+ return;
96
+ const stored = await loadPrFingerprint(stateKey);
97
+ if (stored === null || stored.inputDigest !== fingerprintInputDigest(config))
98
+ return;
99
+ if (fingerprint !== undefined && !fingerprintsEqual(stored.fingerprint, fingerprint))
100
+ return;
101
+ if (!detectorsCover(stored))
102
+ return;
103
+ await attachWaitDetectors(stateKey, stored, {
104
+ digest: detectors.digest,
105
+ fullAt: detectors.readAt,
106
+ });
107
+ }
@@ -1,10 +1,12 @@
1
1
  import type { CheckExecutionContext } from "./check-execution-context.mts";
2
2
  import type { GlobalOptions, ShepherdReport } from "../types.mts";
3
- export declare function runCheck(opts: GlobalOptions & {
3
+ type CheckOptions = GlobalOptions & {
4
4
  autoResolve?: boolean;
5
5
  autoMinimizeSuppressed?: boolean;
6
6
  skipTriage?: boolean;
7
7
  persistSeen?: boolean;
8
8
  fingerprintCache?: boolean;
9
9
  merge?: boolean;
10
- }, context?: CheckExecutionContext): Promise<ShepherdReport>;
10
+ };
11
+ export declare function runCheck(opts: CheckOptions, context?: CheckExecutionContext): Promise<ShepherdReport>;
12
+ export {};