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
@@ -1,14 +1,16 @@
1
1
  import { normalizeBotUsernames } from "../comments/authors.mjs";
2
2
  import { applyRules } from "../classify/apply.mjs";
3
- import { discoverRuleFiles, loadRules } from "../classify/loader.mjs";
3
+ import { loadClassifyRules } from "../classify/loader.mjs";
4
4
  import { loadConfig } from "../config/load.mjs";
5
5
  import { getEffectiveCwd } from "../execution-context.mjs";
6
6
  import { classifyItem } from "../state/seen-comments.mjs";
7
+ import { mutationWasDenied } from "../state/seen-comments.mjs";
8
+ import { canGenerateGithubMutation } from "./mutation-policy.mjs";
7
9
  const THREAD_COMMENT_SEPARATOR = "\n\n--- thread comment ---\n\n";
8
10
  export async function summarizePollSummaryReview(raw, seen, viewerCanAdminister) {
9
11
  const config = loadConfig();
10
12
  const bots = normalizeBotUsernames(config.botUsernames);
11
- const rules = await loadRules(discoverRuleFiles(getEffectiveCwd()));
13
+ const rules = await loadClassifyRules(getEffectiveCwd(), config.standardRules);
12
14
  let actionable = 0;
13
15
  let incomplete = raw.comments.pageInfo.hasPreviousPage ||
14
16
  raw.reviews.pageInfo.hasPreviousPage ||
@@ -37,24 +39,31 @@ export async function summarizePollSummaryReview(raw, seen, viewerCanAdminister)
37
39
  if (isSuppressed(rules, "changes-requested", review))
38
40
  continue;
39
41
  const isBot = review.author?.__typename === "Bot" || bots.has(review.author?.login.toLowerCase() ?? "");
40
- if ((isBot && viewerCanAdminister) ||
42
+ if ((isBot &&
43
+ canGenerateGithubMutation(viewerCanAdminister, "dismiss") &&
44
+ !mutationWasDenied(review.id, review.body, seen)) ||
41
45
  classifyItem(review.id, review.body, seen) !== "unchanged")
42
46
  actionable += 1;
43
47
  }
44
48
  for (const thread of raw.reviewThreads.nodes) {
45
- if (thread.comments.pageInfo.hasPreviousPage)
49
+ // The window holds the first comments. Past it, a new reply leaves the window
50
+ // unchanged, so a long thread cannot prove it was seen.
51
+ const truncated = thread.comments.pageInfo.hasPreviousPage;
52
+ if (truncated)
46
53
  incomplete = true;
47
- const root = thread.rootComments?.nodes[0] ?? thread.comments.nodes[0];
54
+ const root = thread.comments.nodes[0];
48
55
  const transcript = thread.comments.nodes
49
56
  .map((comment) => comment.body)
50
57
  .join(THREAD_COMMENT_SEPARATOR);
51
58
  if (isSuppressed(rules, "review-thread", root, thread.id, thread.path, transcript))
52
59
  continue;
53
- const repeatable = root?.viewerDidAuthor === true ||
54
- root?.author?.__typename === "Bot" ||
55
- bots.has(root?.author?.login.toLowerCase() ?? "");
56
- const unseen = classifyItem(thread.id, transcript, seen) !== "unchanged";
57
- if ((!thread.isResolved && (repeatable || unseen)) ||
60
+ const repeatable = !mutationWasDenied(thread.id, transcript, seen) &&
61
+ (root?.viewerDidAuthor === true ||
62
+ root?.author?.__typename === "Bot" ||
63
+ bots.has(root?.author?.login.toLowerCase() ?? ""));
64
+ const unseen = truncated || classifyItem(thread.id, transcript, seen) !== "unchanged";
65
+ if ((thread.isResolved === false && (repeatable || unseen)) ||
66
+ (thread.isResolved === undefined && unseen) ||
58
67
  ((thread.isResolved || thread.isOutdated) && unseen))
59
68
  actionable += 1;
60
69
  }
@@ -81,5 +90,8 @@ function isSuppressed(rules, kind, raw, id = raw?.id ?? "unknown", path, body =
81
90
  ...(raw?.url && { url: raw.url }),
82
91
  ...(kind === "review-thread" && { path }),
83
92
  };
84
- return applyRules(rules, item).suppress === true;
93
+ const action = applyRules(rules, item);
94
+ // An auto-resolve match is hidden only once the full check minimizes or resolves it, or
95
+ // surfaces a denied mutation once; until then it stays actionable so that check runs.
96
+ return action.suppress === true && (action.autoResolve !== true || kind === "changes-requested");
85
97
  }
@@ -1,4 +1,7 @@
1
1
  import { loadConfig } from "../config/load.mjs";
2
+ import { isKnownMergeStateStatus, transportReadinessGaps } from "./transport-evidence.mjs";
3
+ import { canGenerateGithubMutation } from "./mutation-policy.mjs";
4
+ import { getGithubTransport, isCcrTransport } from "./transport.mjs";
2
5
  export function routePollSummary(raw, checks, review, opts) {
3
6
  const actions = loadConfig().actions;
4
7
  const state = normalizePollSummaryState(raw.state);
@@ -22,7 +25,13 @@ export function routePollSummary(raw, checks, review, opts) {
22
25
  return { action: "fix_code", reasons: ["unreported-required-checks"] };
23
26
  }
24
27
  if ((checks.inProgress ?? 0) > 0 ||
28
+ !isKnownMergeStateStatus(raw.mergeStateStatus) ||
25
29
  raw.mergeable === "UNKNOWN" ||
30
+ raw.mergeStateStatus === "UNKNOWN")
31
+ return { action: "wait", reasons: ["pending-or-unknown"] };
32
+ if (transportReadinessGaps(raw, raw.mergeStateStatus).length > 0)
33
+ return { action: "escalate", reasons: ["transport-unsupported"] };
34
+ if (raw.mergeable === "UNKNOWN" ||
26
35
  raw.mergeStateStatus === "UNKNOWN" ||
27
36
  raw.mergeStateStatus === "BEHIND" ||
28
37
  raw.mergeStateStatus === "BLOCKED" ||
@@ -33,15 +42,22 @@ export function routePollSummary(raw, checks, review, opts) {
33
42
  const autoMarkReadyDisabled = opts.noAutoMarkReady || actions.autoMarkReady === false;
34
43
  // The stack selector asks the agent to mark a disabled draft ready, so
35
44
  // that transition needs the same capability as the automatic one.
36
- if (!raw.viewerCanUpdate && (!autoMarkReadyDisabled || opts.stackPrNumber !== undefined)) {
45
+ if (!canGenerateGithubMutation(raw.viewerCanUpdate, "ready") &&
46
+ (!autoMarkReadyDisabled || opts.stackPrNumber !== undefined)) {
47
+ const unsupported = getGithubTransport() === "rest" && raw.viewerCanUpdate !== false && !isCcrTransport();
48
+ if (unsupported)
49
+ return { action: "escalate", reasons: ["transport-unsupported"] };
37
50
  return { action: "escalate", reasons: ["mark-ready-authorization-required"] };
38
51
  }
39
52
  return autoMarkReadyDisabled
40
53
  ? { action: "wait", reasons: ["draft-auto-mark-ready-disabled"] }
41
54
  : { action: "mark_ready", reasons: ["draft-appears-ready"] };
42
55
  }
43
- if (opts.merge && raw.isInMergeQueue) {
44
- return { action: "wait", reasons: ["already-in-merge-queue"] };
56
+ if (opts.merge && (raw.isInMergeQueue || raw.autoMergeRequest)) {
57
+ return {
58
+ action: "wait",
59
+ reasons: [raw.isInMergeQueue ? "already-in-merge-queue" : "already-auto-merging"],
60
+ };
45
61
  }
46
62
  if (opts.merge && raw.stack && opts.stackPrNumber === undefined) {
47
63
  return { action: "fix_code", reasons: ["authoritative-poll-required"] };
@@ -0,0 +1,11 @@
1
+ import type { RepoInfo } from "./client.mts";
2
+ import type { RawSummaryPr } from "./poll-summary-raw.mts";
3
+ import { type StackRead } from "./stack-read.mts";
4
+ /**
5
+ * Read the hydrated stack in pages of at most `stackSize` entries.
6
+ * A wide check matrix can blow GitHub's per-query resource limit on the full
7
+ * page. Halve `first` down to one entry and let `readStack` follow `after`.
8
+ * The shared summary fragment stays intact: one layer is the same shape as an
9
+ * explicit summary, and check hydration still completes windows past 100.
10
+ */
11
+ export declare function readGraphqlStackSummary(anchor: number, repo: RepoInfo, stackSize: number): Promise<StackRead<RawSummaryPr>>;
@@ -0,0 +1,27 @@
1
+ import { GitHubRequestError, isRetryableGraphQlResourceLimit } from "./errors.mjs";
2
+ import { POLL_STACK_SUMMARY_QUERY } from "./queries.mjs";
3
+ import { MAX_STACK_ENTRIES_PER_PAGE, readStack } from "./stack-read.mjs";
4
+ /**
5
+ * Read the hydrated stack in pages of at most `stackSize` entries.
6
+ * A wide check matrix can blow GitHub's per-query resource limit on the full
7
+ * page. Halve `first` down to one entry and let `readStack` follow `after`.
8
+ * The shared summary fragment stays intact: one layer is the same shape as an
9
+ * explicit summary, and check hydration still completes windows past 100.
10
+ */
11
+ export async function readGraphqlStackSummary(anchor, repo, stackSize) {
12
+ let ceiling = MAX_STACK_ENTRIES_PER_PAGE;
13
+ for (;;) {
14
+ const first = Math.max(1, Math.min(stackSize, ceiling));
15
+ try {
16
+ return await readStack(POLL_STACK_SUMMARY_QUERY, anchor, repo, { first }, ceiling);
17
+ }
18
+ catch (err) {
19
+ if (!(err instanceof GitHubRequestError) ||
20
+ !isRetryableGraphQlResourceLimit(err.graphqlErrors) ||
21
+ first <= 1) {
22
+ throw err;
23
+ }
24
+ ceiling = Math.floor(first / 2);
25
+ }
26
+ }
27
+ }
@@ -3,5 +3,7 @@ import type { PollSummaryChecks } from "../types.mts";
3
3
  import type { RawSummaryPr } from "./poll-summary-raw.mts";
4
4
  /** Current trunk rules may remain available on a merged layer's base ref. */
5
5
  export declare function trunkRequiredContexts(ordered: readonly RawSummaryPr[], repo: RepoInfo): Promise<string[] | undefined>;
6
+ /** Required contexts of the layer's merge target: the trunk's for a stack layer when known. */
7
+ export declare function summaryRequiredContexts(raw: RawSummaryPr, mergeTargetContexts: readonly string[] | undefined): string[];
6
8
  /** Record missing required contexts. A running Actions workflow does not clear the names. */
7
- export declare function applyUnreportedRequiredChecks(raw: RawSummaryPr, checks: PollSummaryChecks, mergeTargetContexts: readonly string[] | undefined): void;
9
+ export declare function applyUnreportedRequiredChecks(raw: RawSummaryPr, checks: PollSummaryChecks, required: readonly string[]): void;
@@ -16,15 +16,18 @@ export async function trunkRequiredContexts(ordered, repo) {
16
16
  const loaded = await loadRefRules(repo.owner, repo.name, `refs/heads/${trunk}`, bottom.headRefOid);
17
17
  return loaded.contexts;
18
18
  }
19
- /** Record missing required contexts. A running Actions workflow does not clear the names. */
20
- export function applyUnreportedRequiredChecks(raw, checks, mergeTargetContexts) {
19
+ /** Required contexts of the layer's merge target: the trunk's for a stack layer when known. */
20
+ export function summaryRequiredContexts(raw, mergeTargetContexts) {
21
21
  const trunk = raw.stack?.baseRefName;
22
- const required = selectMergeTargetContexts({
22
+ return selectMergeTargetContexts({
23
23
  localContexts: parseBranchRules(raw.baseRef).requiredStatusCheckContexts,
24
24
  ...(mergeTargetContexts !== undefined && { trunkContexts: mergeTargetContexts }),
25
25
  baseRefName: raw.baseRefName,
26
26
  ...(trunk !== undefined && mergeTargetContexts !== undefined && { trunkRefName: trunk }),
27
27
  });
28
+ }
29
+ /** Record missing required contexts. A running Actions workflow does not clear the names. */
30
+ export function applyUnreportedRequiredChecks(raw, checks, required) {
28
31
  const missing = unreportedRequiredContexts(required, reportedNames(raw));
29
32
  if (missing.length === 0)
30
33
  return;
@@ -1,12 +1,15 @@
1
1
  import type { PollSummaryCommandOptions, PollSummaryItem, PollSummarySelection, PollSummaryStackAncestry } from "../types.mts";
2
2
  import { type RepoInfo } from "./client.mts";
3
3
  import type { RawSummaryPr } from "./poll-summary-raw.mts";
4
+ import type { StackHandoffSource } from "./stack-handoff-page.mts";
4
5
  export interface FetchedPollSummary {
5
6
  selection: PollSummarySelection;
6
7
  prs: PollSummaryItem[];
7
8
  stackAncestry?: PollSummaryStackAncestry[];
8
9
  allowedMergeMethods?: import("../config/merge-method.mts").MergeMethod[];
10
+ /** GraphQL stack reads only: the raw layers a routing tick hands to its one-PR sessions. */
11
+ handoffSource?: StackHandoffSource;
9
12
  }
10
- export declare function fetchPollSummary(opts: PollSummaryCommandOptions, repo: RepoInfo): Promise<FetchedPollSummary>;
11
13
  /** Fresh, read-only snapshot used to bind a one-PR READY receipt to stack routing. */
12
14
  export declare function fetchRawSummaryPr(pr: number, repo: RepoInfo): Promise<RawSummaryPr>;
15
+ export declare function fetchPollSummary(opts: PollSummaryCommandOptions, repo: RepoInfo): Promise<FetchedPollSummary>;
@@ -1,22 +1,29 @@
1
+ import { readRestStackSummary } from "./rest-stack-summary.mjs";
2
+ import { githubOperation, getGithubTransport, runWithGithubTransport } from "./transport.mjs";
3
+ import { readRestExplicitSummary } from "./rest-explicit-summary-read.mjs";
4
+ import { withRestConditionalScope } from "./rest-conditional-scope.mjs";
1
5
  import { EXIT, ShepherdError } from "../exit-codes.mjs";
2
6
  import { graphqlWithRateLimit } from "./client.mjs";
3
- import { GitHubRequestError, isRetryableGraphQlResourceLimit, missingRepositoryError, } from "./errors.mjs";
7
+ import { missingRepositoryError } from "./errors.mjs";
4
8
  import { hydratePollSummaryChecks } from "./poll-summary-check-hydration.mjs";
5
9
  import { refreshUnknownSummaryMergeability } from "./poll-summary-mergeability.mjs";
6
10
  import { summarizePollSummaryPr } from "./poll-summary-projector.mjs";
7
11
  import { trunkRequiredContexts } from "./poll-summary-unreported.mjs";
8
- import { POLL_STACK_SUMMARY_QUERY, POLL_SUMMARY_FRAGMENT } from "./queries.mjs";
9
- import { MAX_STACK_ENTRIES_PER_PAGE, readStack, readStackTopology, stackAncestryGaps, } from "./stack-read.mjs";
12
+ import { loadStackSizeHint, storeStackSizeHint } from "./stack-size-hint.mjs";
13
+ import { POLL_SUMMARY_FRAGMENT } from "./queries.mjs";
14
+ import { stackAncestryGaps } from "./stack-read.mjs";
15
+ import { readGraphqlStackSummary } from "./poll-summary-stack-page.mjs";
16
+ import { stackHandoffSource } from "./stack-handoff.mjs";
10
17
  const MAX_EXPLICIT_PRS_PER_QUERY = 50;
11
- export async function fetchPollSummary(opts, repo) {
18
+ async function fetchSelectedPollSummary(opts, repo) {
12
19
  if (opts.stackPrNumber !== undefined)
13
20
  return fetchStackSummary(opts, repo);
14
- const requested = deduplicate(opts.prNumbers ?? []);
21
+ const requested = [...new Set(opts.prNumbers ?? [])];
15
22
  if (requested.length === 0) {
16
23
  throw new ShepherdError("Aggregate poll requires at least two PRs or --stack <PR>", EXIT.USAGE);
17
24
  }
18
25
  const raw = [];
19
- let viewerCanAdminister = false;
26
+ let viewerCanAdminister;
20
27
  for (let offset = 0; offset < requested.length; offset += MAX_EXPLICIT_PRS_PER_QUERY) {
21
28
  const chunk = requested.slice(offset, offset + MAX_EXPLICIT_PRS_PER_QUERY);
22
29
  const fetched = await fetchExplicitChunk(chunk, repo);
@@ -33,7 +40,7 @@ export async function fetchRawSummaryPr(pr, repo) {
33
40
  const fetched = await fetchExplicitChunk([pr], repo);
34
41
  return fetched.prs[0];
35
42
  }
36
- async function fetchExplicitChunk(prs, repo) {
43
+ async function fetchGraphqlExplicitChunk(prs, repo) {
37
44
  const declarations = prs.map((_, index) => `$pr${index}: Int!`).join(", ");
38
45
  const aliases = prs
39
46
  .map((_, index) => `pr${index}: pullRequest(number: $pr${index}) { ...PollSummaryPr }`)
@@ -59,18 +66,39 @@ async function fetchExplicitChunk(prs, repo) {
59
66
  }
60
67
  return { prs: rawPrs, viewerCanAdminister: result.data.repository.viewerCanAdminister };
61
68
  }
69
+ async function fetchExplicitChunk(prs, repo) {
70
+ return githubOperation("PollSummary", () => runWithGithubTransport("graphql", () => fetchGraphqlExplicitChunk(prs, repo)), async () => ({
71
+ prs: await withSummaryConditionalScope(repo, prs[0], () => readRestExplicitSummary(prs, repo)),
72
+ }));
73
+ }
74
+ /**
75
+ * Entries on an unsized first summary page. GitHub prices `first`, not the
76
+ * nodes returned, and four layers are the most that still price at 1 point.
77
+ * Larger stacks page on, sized to the entries that remain.
78
+ */
79
+ const UNSIZED_STACK_SUMMARY_FIRST = 4;
62
80
  /**
63
- * Read the stack's topology first so the hydrated page requests only as many
64
- * entries as the stack holds: GitHub prices `first`, not the nodes returned.
81
+ * Size the hydrated page to the stack so it requests only as many entries as the
82
+ * stack holds. The size comes from the anchor's last summary; the first summary
83
+ * reads four entries and pages on, which never costs more than reading the
84
+ * topology first. A stack that has since grown pages on, and one that shrank
85
+ * overpays once.
65
86
  */
66
87
  async function fetchStackSummary(opts, repo) {
67
88
  const anchor = opts.stackPrNumber;
68
- const topology = await readStackTopology(anchor, repo);
69
- const { stackNumber, stackSize, viewerLogin, viewerCanAdminister, ordered, allowedMergeMethods } = await readStackSummary(anchor, repo, topology.stackSize);
89
+ const stateKey = { owner: repo.owner, repo: repo.name, pr: anchor };
90
+ const graphql = getGithubTransport() !== "rest";
91
+ const hint = graphql ? await loadStackSizeHint(stateKey) : 0;
92
+ const sizeHint = hint || UNSIZED_STACK_SUMMARY_FIRST;
93
+ const read = await readStackSummary(anchor, repo, sizeHint);
94
+ const { stackNumber, stackSize, viewerLogin, viewerCanAdminister, ordered, allowedMergeMethods } = read;
95
+ if (graphql && stackSize !== hint)
96
+ await storeStackSizeHint(stateKey, stackSize);
70
97
  for (const pr of ordered) {
71
98
  await hydratePollSummaryChecks(pr, repo);
72
99
  await refreshUnknownSummaryMergeability(pr, repo);
73
100
  }
101
+ const handoffSource = getGithubTransport() === "rest" ? undefined : stackHandoffSource(read);
74
102
  const stackAncestry = stackAncestryGaps(ordered);
75
103
  const required = await trunkRequiredContexts(ordered, repo);
76
104
  return {
@@ -78,30 +106,16 @@ async function fetchStackSummary(opts, repo) {
78
106
  prs: await Promise.all(ordered.map((pr) => summarizePollSummaryPr(pr, repo, opts, viewerCanAdminister, required, viewerLogin))),
79
107
  ...(stackAncestry.length > 0 && { stackAncestry }),
80
108
  ...(allowedMergeMethods && { allowedMergeMethods }),
109
+ ...(handoffSource && { handoffSource }),
81
110
  };
82
111
  }
83
- /**
84
- * A wide check matrix can blow GitHub's per-query resource limit on the full
85
- * page. Halve `first` down to one entry and let `readStack` follow `after`.
86
- * The shared summary fragment stays intact: one layer is the same shape as an
87
- * explicit summary, and check hydration still completes windows past 100.
88
- */
89
112
  async function readStackSummary(anchor, repo, stackSize) {
90
- let first = Math.min(stackSize, MAX_STACK_ENTRIES_PER_PAGE);
91
- for (;;) {
92
- try {
93
- return await readStack(POLL_STACK_SUMMARY_QUERY, anchor, repo, { first });
94
- }
95
- catch (err) {
96
- if (!(err instanceof GitHubRequestError) ||
97
- !isRetryableGraphQlResourceLimit(err.graphqlErrors) ||
98
- first <= 1) {
99
- throw err;
100
- }
101
- first = Math.floor(first / 2);
102
- }
103
- }
113
+ return githubOperation("PollStackSummary", () => readGraphqlStackSummary(anchor, repo, stackSize), () => withSummaryConditionalScope(repo, anchor, () => readRestStackSummary(anchor, repo)));
114
+ }
115
+ /** REST summary reads are conditional under the anchor PR's state: an unchanged body is a free 304. */
116
+ function withSummaryConditionalScope(repo, pr, read) {
117
+ return withRestConditionalScope({ owner: repo.owner, repo: repo.name, pr }, read);
104
118
  }
105
- function deduplicate(values) {
106
- return [...new Set(values)];
119
+ export function fetchPollSummary(opts, repo) {
120
+ return githubOperation("PollSummarySelection", () => runWithGithubTransport("graphql", () => fetchSelectedPollSummary(opts, repo)), () => fetchSelectedPollSummary(opts, repo));
107
121
  }
@@ -10,11 +10,13 @@
10
10
  export declare const BATCH_PR_QUERY: string;
11
11
  /** Slim @include follow-up for outstanding batch-query connections. */
12
12
  export declare const BATCH_PR_PAGE_QUERY: string;
13
- /** Cheap PR fingerprint used to skip an unchanged BatchPr snapshot. */
14
- export declare const PR_FINGERPRINT_QUERY: string;
15
13
  /** Compact per-PR fields shared by explicit-list and native-stack summary queries. */
16
14
  export declare const POLL_SUMMARY_FRAGMENT: string;
17
- /** Exact compact summary sibling only when a one-PR READY receipt needs evidence. */
15
+ /**
16
+ * Exact compact summary sibling only when a one-PR READY receipt needs evidence.
17
+ * The batch's first context page shrinks to 50 so the sibling still prices at 1 point;
18
+ * `BatchPrPage` reads any further contexts.
19
+ */
18
20
  export declare const BATCH_PR_RECEIPT_QUERY: string;
19
21
  /** Trunk branch rules plus how far `headRef` is behind that branch. */
20
22
  export declare const REF_RULES_QUERY: string;
@@ -68,3 +70,5 @@ export declare const MARK_PR_READY_MUTATION: string;
68
70
  export declare const GET_PR_BODY_QUERY: string;
69
71
  /** Update the PR body. */
70
72
  export declare const UPDATE_PR_BODY_MUTATION: string;
73
+ /** CheckRun titles, summaries, and first annotations for routed stack-layer handoffs. */
74
+ export declare const STACK_HANDOFF_CHECK_RUNS_QUERY: string;
@@ -14,13 +14,15 @@ const withSharedFragments = (query) => `${gql("ref-rules.gql")}\n${gql("pr-merge
14
14
  export const BATCH_PR_QUERY = withSharedFragments(gql("batch-pr.gql"));
15
15
  /** Slim @include follow-up for outstanding batch-query connections. */
16
16
  export const BATCH_PR_PAGE_QUERY = gql("batch-pr-page.gql");
17
- /** Cheap PR fingerprint used to skip an unchanged BatchPr snapshot. */
18
- export const PR_FINGERPRINT_QUERY = withSharedFragments(gql("pr-fingerprint.gql"));
19
17
  const POLL_SUMMARY_CHECK_CONTEXTS_FRAGMENT = gql("poll-summary-check-contexts.gql");
20
18
  /** Compact per-PR fields shared by explicit-list and native-stack summary queries. */
21
19
  export const POLL_SUMMARY_FRAGMENT = `${gql("ref-rules.gql")}\n${POLL_SUMMARY_CHECK_CONTEXTS_FRAGMENT}\n${gql("poll-summary-fragment.gql")}`;
22
- /** Exact compact summary sibling only when a one-PR READY receipt needs evidence. */
23
- export const BATCH_PR_RECEIPT_QUERY = `${POLL_SUMMARY_CHECK_CONTEXTS_FRAGMENT}\n${gql("poll-summary-fragment.gql")}\n${BATCH_PR_QUERY.replace(" pullRequest(number: $pr) {", " receiptSummary: pullRequest(number: $pr) { ...PollSummaryPr }\n pullRequest(number: $pr) {")}`;
20
+ /**
21
+ * Exact compact summary sibling only when a one-PR READY receipt needs evidence.
22
+ * The batch's first context page shrinks to 50 so the sibling still prices at 1 point;
23
+ * `BatchPrPage` reads any further contexts.
24
+ */
25
+ export const BATCH_PR_RECEIPT_QUERY = `${POLL_SUMMARY_CHECK_CONTEXTS_FRAGMENT}\n${gql("poll-summary-fragment.gql")}\n${BATCH_PR_QUERY.replace(" pullRequest(number: $pr) {", " receiptSummary: pullRequest(number: $pr) { ...PollSummaryPr }\n pullRequest(number: $pr) {").replace("contexts(first: 100) {", "contexts(first: 50) {")}`;
24
26
  /** Trunk branch rules plus how far `headRef` is behind that branch. */
25
27
  export const REF_RULES_QUERY = `${gql("ref-rules.gql")}\n${gql("ref-rules-query.gql")}`;
26
28
  /** How far a non-stack head is behind its PR base. One compare, no rules payload. */
@@ -73,3 +75,5 @@ export const MARK_PR_READY_MUTATION = gql("mark-pr-ready.gql");
73
75
  export const GET_PR_BODY_QUERY = gql("get-pr-body.gql");
74
76
  /** Update the PR body. */
75
77
  export const UPDATE_PR_BODY_MUTATION = gql("update-pr-body.gql");
78
+ /** CheckRun titles, summaries, and first annotations for routed stack-layer handoffs. */
79
+ export const STACK_HANDOFF_CHECK_RUNS_QUERY = gql("stack-handoff-check-runs.gql");
@@ -0,0 +1,7 @@
1
+ import type { RateLimitInfo } from "./http-utils.mts";
2
+ /**
3
+ * Same reset window: the lowest remaining and the highest used, taken independently. The Claude
4
+ * Code proxy can answer one tick's routes with `X-RateLimit-Used` values that disagree with each
5
+ * other and with `X-RateLimit-Remaining`, so neither half of one sample is trusted over the other.
6
+ */
7
+ export declare function mostConservativeSample<T extends Pick<RateLimitInfo, "remaining" | "used">>(current: T, candidate: T): T;
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Same reset window: the lowest remaining and the highest used, taken independently. The Claude
3
+ * Code proxy can answer one tick's routes with `X-RateLimit-Used` values that disagree with each
4
+ * other and with `X-RateLimit-Remaining`, so neither half of one sample is trusted over the other.
5
+ */
6
+ export function mostConservativeSample(current, candidate) {
7
+ const base = candidate.remaining < current.remaining ? candidate : current;
8
+ const used = current.used === undefined || candidate.used === undefined
9
+ ? (current.used ?? candidate.used)
10
+ : Math.max(current.used, candidate.used);
11
+ return { ...base, ...(used !== undefined && { used }) };
12
+ }
@@ -0,0 +1,50 @@
1
+ import { type RepoInfo } from "./client.mts";
2
+ export interface ReplyEvidence {
3
+ viewer: string;
4
+ comments: Array<{
5
+ id: string;
6
+ body: string;
7
+ author: string | null;
8
+ }>;
9
+ }
10
+ interface Page {
11
+ totalCount: number;
12
+ pageInfo: {
13
+ hasNextPage: boolean;
14
+ endCursor: string | null;
15
+ };
16
+ nodes: Array<{
17
+ id: string;
18
+ databaseId: number;
19
+ body: string;
20
+ author: {
21
+ login: string;
22
+ } | null;
23
+ }>;
24
+ }
25
+ interface Node {
26
+ id: string;
27
+ pullRequest: {
28
+ number: number;
29
+ repository: {
30
+ nameWithOwner: string;
31
+ };
32
+ };
33
+ comments: Page;
34
+ }
35
+ export interface ReplyEvidenceResponse {
36
+ viewer: {
37
+ login: string;
38
+ } | null;
39
+ nodes: Array<Node | null>;
40
+ }
41
+ /** Thread selection shared with the `apply review` preflight, which needs `$cursor: String`. */
42
+ export declare const REPLY_EVIDENCE_THREAD_SELECTION = "... on PullRequestReviewThread {\n id pullRequest { number repository { nameWithOwner } }\n comments(first: 100, after: $cursor) {\n totalCount\n pageInfo { hasNextPage endCursor }\n nodes { id databaseId body author { login } }\n }\n }";
43
+ /**
44
+ * Complete evidence for one GraphQL thread, starting from a response that already holds its first
45
+ * comment page. Later pages use `ReplyRecoveryEvidence`. Throws when anything is unverifiable.
46
+ */
47
+ export declare function threadReplyEvidence(repo: RepoInfo, pr: number, id: string, first: ReplyEvidenceResponse): Promise<ReplyEvidence>;
48
+ /** Complete, bounded evidence for one mutation batch; unavailable evidence never certifies a retry. */
49
+ export declare function readReplyRecoveryEvidence(repo: RepoInfo, pr: number, requestedIds: readonly string[]): Promise<Map<string, ReplyEvidence>>;
50
+ export {};
@@ -0,0 +1,118 @@
1
+ import { graphqlWithRateLimit } from "./client.mjs";
2
+ import { githubOperation } from "./transport.mjs";
3
+ import { recordThreadIdentity, resolveGraphqlThreadId, resolveRestThreadRoot, } from "./rest-identities.mjs";
4
+ import { readRest, readRestPages, restRepoPath } from "./rest-reader-core.mjs";
5
+ import { mapPool } from "../util/pool.mjs";
6
+ /** Thread selection shared with the `apply review` preflight, which needs `$cursor: String`. */
7
+ export const REPLY_EVIDENCE_THREAD_SELECTION = `... on PullRequestReviewThread {
8
+ id pullRequest { number repository { nameWithOwner } }
9
+ comments(first: 100, after: $cursor) {
10
+ totalCount
11
+ pageInfo { hasNextPage endCursor }
12
+ nodes { id databaseId body author { login } }
13
+ }
14
+ }`;
15
+ const QUERY = `query ReplyRecoveryEvidence($ids: [ID!]!, $cursor: String) {
16
+ _shepherdRateLimit: rateLimit { cost limit nodeCount remaining resetAt used }
17
+ viewer { login }
18
+ nodes(ids: $ids) { ${REPLY_EVIDENCE_THREAD_SELECTION} }
19
+ }`;
20
+ const readEvidencePage = (ids, cursor) => graphqlWithRateLimit(QUERY, { ids, cursor });
21
+ /**
22
+ * Complete evidence for one GraphQL thread, starting from a response that already holds its first
23
+ * comment page. Later pages use `ReplyRecoveryEvidence`. Throws when anything is unverifiable.
24
+ */
25
+ export async function threadReplyEvidence(repo, pr, id, first) {
26
+ const viewer = first.viewer?.login;
27
+ if (typeof viewer !== "string" || !viewer)
28
+ throw new Error("Reply recovery viewer is unavailable");
29
+ let node = first.nodes.find((item) => item?.id === id);
30
+ const comments = [];
31
+ const cursors = new Set();
32
+ const total = node?.comments?.totalCount;
33
+ const root = node?.comments?.nodes?.[0]?.databaseId;
34
+ const seenIds = new Set();
35
+ for (let page = 0; page < 100; page++) {
36
+ if (!node ||
37
+ node.pullRequest?.number !== pr ||
38
+ node.pullRequest.repository.nameWithOwner.toLowerCase() !==
39
+ `${repo.owner}/${repo.name}`.toLowerCase() ||
40
+ !node.comments?.pageInfo ||
41
+ typeof node.comments.pageInfo.hasNextPage !== "boolean" ||
42
+ !Array.isArray(node.comments.nodes) ||
43
+ !Number.isSafeInteger(total) ||
44
+ total < 1 ||
45
+ node.comments.totalCount !== total)
46
+ throw new Error("Reply recovery transcript is unavailable");
47
+ if (page === 0) {
48
+ if (!Number.isSafeInteger(root) || root < 1)
49
+ throw new Error("Reply recovery root identity is unavailable");
50
+ await recordThreadIdentity(repo, pr, id, root);
51
+ }
52
+ for (const comment of node.comments.nodes) {
53
+ if (typeof comment.id !== "string" ||
54
+ !comment.id ||
55
+ seenIds.has(comment.id) ||
56
+ typeof comment.body !== "string" ||
57
+ (comment.author !== null && typeof comment.author?.login !== "string"))
58
+ throw new Error("Reply recovery comment is incomplete");
59
+ seenIds.add(comment.id);
60
+ comments.push({ id: comment.id, body: comment.body, author: comment.author?.login ?? null });
61
+ }
62
+ if (!node.comments.pageInfo.hasNextPage && comments.length === total)
63
+ return { viewer, comments };
64
+ if (!node.comments.pageInfo.hasNextPage)
65
+ break;
66
+ const cursor = node.comments.pageInfo.endCursor;
67
+ if (!cursor || cursors.has(cursor))
68
+ break;
69
+ cursors.add(cursor);
70
+ const next = await readEvidencePage([id], cursor);
71
+ if (next.data.viewer?.login !== viewer)
72
+ throw new Error("Reply recovery viewer changed");
73
+ node = next.data.nodes.find((item) => item?.id === id);
74
+ }
75
+ throw new Error("Reply recovery transcript pagination is incomplete");
76
+ }
77
+ /** Complete, bounded evidence for one mutation batch; unavailable evidence never certifies a retry. */
78
+ export async function readReplyRecoveryEvidence(repo, pr, requestedIds) {
79
+ if (requestedIds.length === 0)
80
+ return new Map();
81
+ if (requestedIds.length > 10)
82
+ throw new Error("Reply recovery batch exceeds 10 threads");
83
+ return githubOperation("ReplyRecoveryEvidence", async () => {
84
+ const ids = await Promise.all(requestedIds.map(resolveGraphqlThreadId));
85
+ const first = await readEvidencePage(ids, null);
86
+ const viewer = first.data.viewer?.login;
87
+ if (typeof viewer !== "string" || !viewer)
88
+ throw new Error("Reply recovery viewer is unavailable");
89
+ const entries = await mapPool(ids, 4, async (id, index) => [requestedIds[index], await threadReplyEvidence(repo, pr, id, first.data)]);
90
+ return new Map(entries);
91
+ }, async () => {
92
+ const viewer = await readRest("GET", "/user");
93
+ if (typeof viewer.login !== "string" || !viewer.login)
94
+ throw new Error("Reply recovery viewer is unavailable");
95
+ const inline = (await readRestPages(`${restRepoPath(repo)}/pulls/${pr}/comments`)).nodes;
96
+ const entries = await Promise.all(requestedIds.map(async (id) => {
97
+ const root = await resolveRestThreadRoot(repo, pr, id);
98
+ if (!inline.some((comment) => String(comment.id) === root))
99
+ throw new Error("Reply recovery thread is unavailable");
100
+ const comments = inline
101
+ .filter((comment) => String(comment.id) === root || String(comment.in_reply_to_id) === root)
102
+ .map((comment) => {
103
+ if (typeof comment.node_id !== "string" ||
104
+ !comment.node_id ||
105
+ typeof comment.body !== "string" ||
106
+ (comment.user !== null && typeof comment.user?.login !== "string"))
107
+ throw new Error("Reply recovery comment is incomplete");
108
+ return {
109
+ id: comment.node_id,
110
+ body: comment.body,
111
+ author: comment.user?.login ?? null,
112
+ };
113
+ });
114
+ return [id, { viewer: viewer.login, comments }];
115
+ }));
116
+ return new Map(entries);
117
+ });
118
+ }
@@ -1,3 +1,2 @@
1
1
  import { type RepoInfo } from "./client.mts";
2
- /** Best-effort transcript evidence; it never filters user-supplied mutation IDs. */
3
2
  export declare function fetchReplyThreadTranscripts(pr: number, repo: RepoInfo, requestedIds: readonly string[]): Promise<Map<string, string>>;
@@ -1,3 +1,6 @@
1
+ import { githubOperation } from "./transport.mjs";
2
+ import { readRestFeedback } from "./rest-feedback-read.mjs";
3
+ import { resolveRestThreadRoot, restThreadId } from "./rest-identities.mjs";
1
4
  import { graphqlWithRateLimit } from "./client.mjs";
2
5
  import { GitHubRequestError } from "./errors.mjs";
3
6
  import { rateLimitKind } from "./rate-limit-kind.mjs";
@@ -8,7 +11,7 @@ const IDS_PER_REQUEST = 20;
8
11
  const CONCURRENCY = 4;
9
12
  const MAX_COMMENT_PAGES = 100;
10
13
  /** Best-effort transcript evidence; it never filters user-supplied mutation IDs. */
11
- export async function fetchReplyThreadTranscripts(pr, repo, requestedIds) {
14
+ async function fetchGraphqlReplyThreadTranscripts(pr, repo, requestedIds) {
12
15
  const ids = [...new Set(requestedIds)];
13
16
  if (ids.length === 0)
14
17
  return new Map();
@@ -87,3 +90,23 @@ async function completeThread(thread, isStopped, stop) {
87
90
  function isThrottle(error) {
88
91
  return error instanceof GitHubRequestError && rateLimitKind(error) !== null;
89
92
  }
93
+ export function fetchReplyThreadTranscripts(pr, repo, requestedIds) {
94
+ if (requestedIds.length === 0)
95
+ return Promise.resolve(new Map());
96
+ return githubOperation("ReplyThreadTranscripts", () => fetchGraphqlReplyThreadTranscripts(pr, repo, requestedIds), async () => {
97
+ const feedback = await readRestFeedback(pr, repo);
98
+ const result = new Map();
99
+ for (const id of new Set(requestedIds)) {
100
+ try {
101
+ const root = await resolveRestThreadRoot(repo, pr, id);
102
+ const thread = feedback.threads.find((thread) => thread.id === restThreadId(root));
103
+ if (thread)
104
+ result.set(id, threadTranscriptBodies((thread.comments ?? []).map((comment) => comment.body)));
105
+ }
106
+ catch {
107
+ /* Transcript evidence never prohibits an explicit mutation. */
108
+ }
109
+ }
110
+ return result;
111
+ });
112
+ }
@@ -0,0 +1,5 @@
1
+ import type { CheckAnnotation } from "../types.mts";
2
+ import type { AnnotationCacheOptions } from "./check-annotation-cache.mts";
3
+ /** An annotation fallback can follow a GraphQL snapshot with no persisted REST check IDs. */
4
+ export declare function ensureRestCheckIdentities(checkRunIds: string[], scope?: AnnotationCacheOptions): Promise<void>;
5
+ export declare function readRestCheckAnnotations(checkRunId: string, scope?: AnnotationCacheOptions): Promise<CheckAnnotation[]>;