pr-shepherd 0.56.5 → 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 (423) 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 +40 -19
  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.d.mts +6 -5
  138. package/bin/commands/iterate/native-stack-rebase.mjs +24 -7
  139. package/bin/commands/iterate/render.d.mts +8 -2
  140. package/bin/commands/iterate/render.mjs +28 -42
  141. package/bin/commands/iterate/stack-trunk-conflict.d.mts +4 -2
  142. package/bin/commands/iterate/stack-trunk-conflict.mjs +44 -19
  143. package/bin/commands/iterate/stale-ancestry.mjs +4 -1
  144. package/bin/commands/iterate/stall.d.mts +5 -1
  145. package/bin/commands/iterate/stall.mjs +19 -7
  146. package/bin/commands/iterate/thread-mutation-routing.d.mts +1 -0
  147. package/bin/commands/iterate/thread-mutation-routing.mjs +4 -2
  148. package/bin/commands/iterate/unreported-required.mjs +5 -2
  149. package/bin/commands/mark-files-as-viewed.mjs +7 -0
  150. package/bin/commands/next-check.d.mts +31 -0
  151. package/bin/commands/next-check.mjs +46 -0
  152. package/bin/commands/playbook.d.mts +13 -0
  153. package/bin/commands/playbook.mjs +48 -0
  154. package/bin/commands/poll-mode.d.mts +8 -0
  155. package/bin/commands/poll-mode.mjs +14 -0
  156. package/bin/commands/poll-progress.d.mts +1 -0
  157. package/bin/commands/poll-progress.mjs +6 -3
  158. package/bin/commands/poll-quota.d.mts +3 -2
  159. package/bin/commands/poll-quota.mjs +7 -3
  160. package/bin/commands/poll-summary-explicit-instructions.d.mts +2 -0
  161. package/bin/commands/poll-summary-explicit-instructions.mjs +10 -1
  162. package/bin/commands/poll-summary-instructions.mjs +16 -17
  163. package/bin/commands/poll-summary.mjs +42 -17
  164. package/bin/commands/poll.d.mts +0 -1
  165. package/bin/commands/poll.mjs +11 -19
  166. package/bin/commands/quota-selection.d.mts +2 -1
  167. package/bin/commands/quota-selection.mjs +12 -7
  168. package/bin/commands/resolve-mutate.mjs +33 -5
  169. package/bin/commands/rest-stack-merge-readiness.d.mts +4 -0
  170. package/bin/commands/rest-stack-merge-readiness.mjs +74 -0
  171. package/bin/commands/rule-auto-resolve.mjs +9 -3
  172. package/bin/commands/shepherd-journal.d.mts +5 -4
  173. package/bin/commands/shepherd-journal.mjs +9 -8
  174. package/bin/commands/stack-drain.d.mts +1 -9
  175. package/bin/commands/stack-drain.mjs +50 -37
  176. package/bin/commands/stack-handoffs.d.mts +10 -0
  177. package/bin/commands/stack-handoffs.mjs +25 -0
  178. package/bin/commands/stack-stall.d.mts +5 -0
  179. package/bin/commands/stack-stall.mjs +11 -0
  180. package/bin/commands/stack-work.d.mts +5 -0
  181. package/bin/commands/stack-work.mjs +32 -0
  182. package/bin/comments/existing-reply-scan.d.mts +17 -0
  183. package/bin/comments/existing-reply-scan.mjs +41 -0
  184. package/bin/comments/rate-limit.mjs +2 -2
  185. package/bin/comments/resolve.d.mts +7 -1
  186. package/bin/comments/resolve.mjs +108 -18
  187. package/bin/comments/rest-reply.d.mts +9 -0
  188. package/bin/comments/rest-reply.mjs +95 -0
  189. package/bin/comments/rest-review-mutations.d.mts +22 -0
  190. package/bin/comments/rest-review-mutations.mjs +125 -0
  191. package/bin/comments/review-visibility.d.mts +1 -1
  192. package/bin/comments/review-visibility.mjs +3 -2
  193. package/bin/comments/sha-poll.mjs +7 -3
  194. package/bin/comments/thread-visibility.mjs +10 -5
  195. package/bin/comments/uncertain-replies.d.mts +11 -0
  196. package/bin/comments/uncertain-replies.mjs +121 -0
  197. package/bin/comments/uncertain-reply-guards.d.mts +10 -0
  198. package/bin/comments/uncertain-reply-guards.mjs +94 -0
  199. package/bin/comments/visible-comments.d.mts +1 -1
  200. package/bin/comments/visible-comments.mjs +4 -2
  201. package/bin/config/load.d.mts +37 -1
  202. package/bin/config/load.mjs +73 -1
  203. package/bin/config.json +6 -2
  204. package/bin/exit-codes.d.mts +3 -0
  205. package/bin/exit-codes.mjs +8 -0
  206. package/bin/github/api-telemetry-aggregate.d.mts +15 -4
  207. package/bin/github/api-telemetry-aggregate.mjs +57 -18
  208. package/bin/github/api-telemetry.d.mts +4 -0
  209. package/bin/github/api-telemetry.mjs +22 -8
  210. package/bin/github/apply-review-preflight.d.mts +20 -0
  211. package/bin/github/apply-review-preflight.mjs +61 -0
  212. package/bin/github/base-behind-cache.d.mts +15 -0
  213. package/bin/github/base-behind-cache.mjs +18 -0
  214. package/bin/github/batch-parse-suites.d.mts +4 -2
  215. package/bin/github/batch-parse-suites.mjs +6 -1
  216. package/bin/github/batch-parser-helpers.mjs +40 -2
  217. package/bin/github/batch-parsers.mjs +1 -0
  218. package/bin/github/batch-raw-rules.d.mts +26 -0
  219. package/bin/github/batch-raw-types.d.mts +8 -6
  220. package/bin/github/batch-stack-evidence.d.mts +28 -0
  221. package/bin/github/batch-stack-evidence.mjs +62 -0
  222. package/bin/github/batch.d.mts +15 -2
  223. package/bin/github/batch.mjs +34 -17
  224. package/bin/github/check-annotations-batch.d.mts +0 -5
  225. package/bin/github/check-annotations-batch.mjs +25 -4
  226. package/bin/github/check-annotations.mjs +3 -1
  227. package/bin/github/client.d.mts +8 -19
  228. package/bin/github/client.mjs +45 -13
  229. package/bin/github/fingerprint-fields.d.mts +8 -0
  230. package/bin/github/fingerprint-fields.mjs +6 -1
  231. package/bin/github/fingerprint.d.mts +4 -2
  232. package/bin/github/fingerprint.mjs +6 -38
  233. package/bin/github/github-fetch.d.mts +2 -0
  234. package/bin/github/github-fetch.mjs +19 -0
  235. package/bin/github/gql/base-behind.gql +3 -0
  236. package/bin/github/gql/batch-pr-page.gql +16 -0
  237. package/bin/github/gql/batch-pr.gql +57 -0
  238. package/bin/github/gql/commit-check-contexts.gql +17 -0
  239. package/bin/github/gql/get-pr-body.gql +1 -0
  240. package/bin/github/gql/poll-stack-summary.gql +1 -0
  241. package/bin/github/gql/poll-summary-check-contexts.gql +5 -0
  242. package/bin/github/gql/poll-summary-fragment.gql +65 -15
  243. package/bin/github/gql/stack-handoff-check-runs.gql +42 -0
  244. package/bin/github/gql/upper-layer-conflict-target.gql +0 -15
  245. package/bin/github/graphql-http.mjs +9 -4
  246. package/bin/github/graphql-internal-retry.d.mts +2 -0
  247. package/bin/github/graphql-internal-retry.mjs +1 -1
  248. package/bin/github/graphql-quota-probe.d.mts +6 -0
  249. package/bin/github/graphql-quota-probe.mjs +56 -0
  250. package/bin/github/graphql-session-refusal.d.mts +8 -0
  251. package/bin/github/graphql-session-refusal.mjs +14 -0
  252. package/bin/github/http-request.d.mts +1 -1
  253. package/bin/github/http-request.mjs +22 -6
  254. package/bin/github/http-utils.mjs +6 -0
  255. package/bin/github/merge-target-rules.d.mts +16 -3
  256. package/bin/github/merge-target-rules.mjs +79 -44
  257. package/bin/github/mutation-policy.d.mts +6 -0
  258. package/bin/github/mutation-policy.mjs +21 -0
  259. package/bin/github/poll-summary-annotation-probe.mjs +17 -10
  260. package/bin/github/poll-summary-check-blockers.d.mts +1 -1
  261. package/bin/github/poll-summary-check-blockers.mjs +2 -2
  262. package/bin/github/poll-summary-check-hydration.mjs +25 -10
  263. package/bin/github/poll-summary-checks.d.mts +2 -2
  264. package/bin/github/poll-summary-checks.mjs +9 -6
  265. package/bin/github/poll-summary-command.d.mts +2 -0
  266. package/bin/github/poll-summary-command.mjs +24 -0
  267. package/bin/github/poll-summary-fingerprint.d.mts +1 -1
  268. package/bin/github/poll-summary-fingerprint.mjs +4 -2
  269. package/bin/github/poll-summary-handoff-fields.d.mts +3 -0
  270. package/bin/github/poll-summary-handoff-fields.mjs +73 -0
  271. package/bin/github/poll-summary-projector.d.mts +1 -1
  272. package/bin/github/poll-summary-projector.mjs +17 -24
  273. package/bin/github/poll-summary-raw.d.mts +70 -10
  274. package/bin/github/poll-summary-readiness.mjs +4 -1
  275. package/bin/github/poll-summary-rest-queue.d.mts +3 -0
  276. package/bin/github/poll-summary-rest-queue.mjs +12 -0
  277. package/bin/github/poll-summary-review.d.mts +1 -1
  278. package/bin/github/poll-summary-review.mjs +23 -11
  279. package/bin/github/poll-summary-route.mjs +19 -3
  280. package/bin/github/poll-summary-stack-page.d.mts +11 -0
  281. package/bin/github/poll-summary-stack-page.mjs +27 -0
  282. package/bin/github/poll-summary-unreported.d.mts +6 -3
  283. package/bin/github/poll-summary-unreported.mjs +15 -7
  284. package/bin/github/poll-summary.d.mts +4 -1
  285. package/bin/github/poll-summary.mjs +48 -34
  286. package/bin/github/queries.d.mts +9 -5
  287. package/bin/github/queries.mjs +10 -6
  288. package/bin/github/rate-limit-sample.d.mts +7 -0
  289. package/bin/github/rate-limit-sample.mjs +12 -0
  290. package/bin/github/reply-recovery-read.d.mts +50 -0
  291. package/bin/github/reply-recovery-read.mjs +118 -0
  292. package/bin/github/reply-thread-transcripts.d.mts +0 -1
  293. package/bin/github/reply-thread-transcripts.mjs +24 -1
  294. package/bin/github/rest-annotation-read.d.mts +5 -0
  295. package/bin/github/rest-annotation-read.mjs +44 -0
  296. package/bin/github/rest-batch-read.d.mts +12 -0
  297. package/bin/github/rest-batch-read.mjs +57 -0
  298. package/bin/github/rest-batch-snapshot.d.mts +30 -0
  299. package/bin/github/rest-batch-snapshot.mjs +115 -0
  300. package/bin/github/rest-batch-unavailable.d.mts +5 -0
  301. package/bin/github/rest-batch-unavailable.mjs +16 -0
  302. package/bin/github/rest-check-read.d.mts +34 -0
  303. package/bin/github/rest-check-read.mjs +124 -0
  304. package/bin/github/rest-comment-authorship.d.mts +7 -0
  305. package/bin/github/rest-comment-authorship.mjs +11 -0
  306. package/bin/github/rest-conditional-scope.d.mts +20 -0
  307. package/bin/github/rest-conditional-scope.mjs +44 -0
  308. package/bin/github/rest-explicit-summary-read.d.mts +3 -0
  309. package/bin/github/rest-explicit-summary-read.mjs +8 -0
  310. package/bin/github/rest-feedback-read.d.mts +36 -0
  311. package/bin/github/rest-feedback-read.mjs +147 -0
  312. package/bin/github/rest-feedback-validation.d.mts +5 -0
  313. package/bin/github/rest-feedback-validation.mjs +28 -0
  314. package/bin/github/rest-http.d.mts +12 -0
  315. package/bin/github/rest-http.mjs +108 -23
  316. package/bin/github/rest-identities.d.mts +22 -0
  317. package/bin/github/rest-identities.mjs +134 -0
  318. package/bin/github/rest-merge-stack-guard.d.mts +14 -0
  319. package/bin/github/rest-merge-stack-guard.mjs +41 -0
  320. package/bin/github/rest-merge.d.mts +29 -0
  321. package/bin/github/rest-merge.mjs +56 -0
  322. package/bin/github/rest-merged-base-read.d.mts +9 -0
  323. package/bin/github/rest-merged-base-read.mjs +48 -0
  324. package/bin/github/rest-pr-core.d.mts +52 -0
  325. package/bin/github/rest-pr-core.mjs +60 -0
  326. package/bin/github/rest-reader-core.d.mts +16 -0
  327. package/bin/github/rest-reader-core.mjs +154 -0
  328. package/bin/github/rest-review-read.d.mts +11 -0
  329. package/bin/github/rest-review-read.mjs +47 -0
  330. package/bin/github/rest-rules-read.d.mts +9 -0
  331. package/bin/github/rest-rules-read.mjs +154 -0
  332. package/bin/github/rest-session-refusal.d.mts +5 -0
  333. package/bin/github/rest-session-refusal.mjs +21 -0
  334. package/bin/github/rest-snapshot-context.d.mts +10 -0
  335. package/bin/github/rest-snapshot-context.mjs +1 -0
  336. package/bin/github/rest-stack-read.d.mts +21 -0
  337. package/bin/github/rest-stack-read.mjs +47 -0
  338. package/bin/github/rest-stack-summary.d.mts +8 -0
  339. package/bin/github/rest-stack-summary.mjs +62 -0
  340. package/bin/github/rest-summary-read.d.mts +7 -0
  341. package/bin/github/rest-summary-read.mjs +112 -0
  342. package/bin/github/rest-terminal-batch.d.mts +14 -0
  343. package/bin/github/rest-terminal-batch.mjs +45 -0
  344. package/bin/github/rest-text.mjs +7 -3
  345. package/bin/github/rest-thread-read.d.mts +4 -0
  346. package/bin/github/rest-thread-read.mjs +35 -0
  347. package/bin/github/rest-viewer-read.d.mts +2 -0
  348. package/bin/github/rest-viewer-read.mjs +41 -0
  349. package/bin/github/stack-bottom.d.mts +3 -0
  350. package/bin/github/stack-bottom.mjs +14 -0
  351. package/bin/github/stack-handoff-context.d.mts +25 -0
  352. package/bin/github/stack-handoff-context.mjs +12 -0
  353. package/bin/github/stack-handoff-page.d.mts +17 -0
  354. package/bin/github/stack-handoff-page.mjs +306 -0
  355. package/bin/github/stack-handoff.d.mts +16 -0
  356. package/bin/github/stack-handoff.mjs +115 -0
  357. package/bin/github/stack-order.d.mts +6 -0
  358. package/bin/github/stack-order.mjs +13 -0
  359. package/bin/github/stack-read.d.mts +7 -3
  360. package/bin/github/stack-read.mjs +22 -11
  361. package/bin/github/stack-size-hint.d.mts +3 -0
  362. package/bin/github/stack-size-hint.mjs +14 -0
  363. package/bin/github/suggestion-thread.mjs +27 -1
  364. package/bin/github/thread-comments.mjs +9 -1
  365. package/bin/github/transport-evidence.d.mts +20 -0
  366. package/bin/github/transport-evidence.mjs +26 -0
  367. package/bin/github/transport-mode.d.mts +2 -0
  368. package/bin/github/transport-mode.mjs +5 -0
  369. package/bin/github/transport.d.mts +19 -0
  370. package/bin/github/transport.mjs +126 -0
  371. package/bin/github/unsupported-rest.d.mts +4 -0
  372. package/bin/github/unsupported-rest.mjs +7 -0
  373. package/bin/mcp/server.mjs +52 -3
  374. package/bin/merge-status/derive.d.mts +2 -0
  375. package/bin/merge-status/derive.mjs +17 -13
  376. package/bin/merge-status/required-check-requirements.d.mts +7 -0
  377. package/bin/merge-status/required-check-requirements.mjs +15 -0
  378. package/bin/merge-status/requirements-format.d.mts +5 -0
  379. package/bin/merge-status/requirements-format.mjs +41 -12
  380. package/bin/merge-status/requirements.mjs +18 -7
  381. package/bin/state/base.mjs +41 -0
  382. package/bin/state/durable-state.d.mts +6 -0
  383. package/bin/state/durable-state.mjs +41 -0
  384. package/bin/state/graphql-quota-policy.mjs +23 -16
  385. package/bin/state/merge-request.d.mts +25 -0
  386. package/bin/state/merge-request.mjs +145 -0
  387. package/bin/state/pr-fingerprint.d.mts +15 -0
  388. package/bin/state/pr-fingerprint.mjs +22 -7
  389. package/bin/state/rest-cache.d.mts +3 -0
  390. package/bin/state/rest-cache.mjs +1 -0
  391. package/bin/state/rest-snapshot-report.d.mts +21 -0
  392. package/bin/state/rest-snapshot-report.mjs +52 -0
  393. package/bin/state/seen-comments.d.mts +3 -0
  394. package/bin/state/seen-comments.mjs +18 -0
  395. package/bin/types/api-usage.d.mts +9 -1
  396. package/bin/types/escalate.d.mts +1 -1
  397. package/bin/types/github.d.mts +23 -9
  398. package/bin/types/iterate.d.mts +24 -2
  399. package/bin/types/merge-action.d.mts +1 -1
  400. package/bin/types/merge-requirements.d.mts +7 -7
  401. package/bin/types/next-check.d.mts +12 -0
  402. package/bin/types/next-check.mjs +1 -0
  403. package/bin/types/poll-summary.d.mts +21 -0
  404. package/bin/types/report.d.mts +13 -1
  405. package/bin/types/review-thread.d.mts +1 -1
  406. package/bin/types.d.mts +1 -0
  407. package/bin/types.mjs +1 -0
  408. package/package.json +3 -2
  409. package/plugins/pr-shepherd/.codex-plugin/plugin.json +1 -1
  410. package/plugins/pr-shepherd/.codex.mcp.json +1 -1
  411. package/plugins/pr-shepherd/.mcp.json +1 -1
  412. package/plugins/pr-shepherd/skills/mark-files-as-viewed/SKILL.md +1 -1
  413. package/plugins/pr-shepherd/skills/pr-shepherd/SKILL.md +14 -51
  414. package/plugins/pr-shepherd/skills/pr-shepherd/references/ci-failure-triage.md +12 -16
  415. package/plugins/pr-shepherd/skills/pr-shepherd/references/cloud-event-loop.md +14 -0
  416. package/plugins/pr-shepherd/skills/pr-shepherd/references/create-pr.md +10 -0
  417. package/plugins/pr-shepherd/skills/pr-shepherd/references/fix-code-loop.md +10 -0
  418. package/plugins/pr-shepherd/skills/pr-shepherd/references/mcp-fallback.md +9 -0
  419. package/plugins/pr-shepherd/skills/reduce-pr-noise/references/classifiers.md +2 -0
  420. package/bin/github/gql/pr-fingerprint.gql +0 -75
  421. package/plugins/pr-shepherd/skills/pr-shepherd/references/journal.md +0 -7
  422. package/plugins/pr-shepherd/skills/pr-shepherd/references/review-mutations.md +0 -9
  423. package/plugins/pr-shepherd/skills/pr-shepherd/references/stack-merge.md +0 -7
@@ -0,0 +1,306 @@
1
+ /**
2
+ * A routed stack layer's `BatchPr` first page, rebuilt from the stack summary
3
+ * that routed it. See `stack-handoff.mts` for when a handoff is stored and read.
4
+ */
5
+ const MAX_THREAD_COMMENTS = 5;
6
+ const MAX_REVIEW_BUCKET = 50;
7
+ const MAX_LATEST_REVIEWS = 50;
8
+ const MAX_CONTEXTS = 100;
9
+ const MAX_STACK_ENTRIES = 50;
10
+ function headCommit(layer) {
11
+ return layer.commits.nodes[0]?.commit;
12
+ }
13
+ export function checkRunIds(layer) {
14
+ return (headCommit(layer)?.statusCheckRollup?.contexts.nodes ?? []).flatMap((node) => node.__typename === "CheckRun" && node.id ? [node.id] : []);
15
+ }
16
+ function reviewsIn(layer, state) {
17
+ return layer.reviews.nodes.filter((review) => review.state === state);
18
+ }
19
+ /** True when every window `BatchPr`'s first page reads is complete in this summary. */
20
+ export function summaryComplete(layer, source) {
21
+ const { read } = source;
22
+ if (!read.stack || read.viewerPermission === undefined || !read.mergeSettings)
23
+ return false;
24
+ if (source.layers.length > MAX_STACK_ENTRIES || !layer.stack || !layer.stackEntry)
25
+ return false;
26
+ if (layer.state !== "OPEN" || layer.id === undefined || layer.viewerDidAuthor === undefined)
27
+ return false;
28
+ if (layer.isInMergeQueue || layer.mergeQueueEntry || layer.mergeQueueRemovals?.nodes.length)
29
+ return false;
30
+ if (layer.comments.pageInfo.hasPreviousPage ||
31
+ layer.reviews.pageInfo.hasPreviousPage ||
32
+ layer.reviewThreads.pageInfo.hasPreviousPage ||
33
+ layer.reviewThreads.nodes.some((thread) => thread.comments.pageInfo.hasPreviousPage ||
34
+ thread.comments.nodes.length > MAX_THREAD_COMMENTS))
35
+ return false;
36
+ if (["CHANGES_REQUESTED", "COMMENTED", "APPROVED"].some((state) => reviewsIn(layer, state).length > MAX_REVIEW_BUCKET))
37
+ return false;
38
+ if ((layer.latestReviews?.nodes.length ?? MAX_LATEST_REVIEWS) >= MAX_LATEST_REVIEWS)
39
+ return false;
40
+ if (!layer.reviewRequests || layer.commits.totalCount === undefined)
41
+ return false;
42
+ const commit = headCommit(layer);
43
+ if (!commit || commit.oid !== layer.headRefOid || !commit.checkSuites?.pageInfo)
44
+ return false;
45
+ const contexts = commit.statusCheckRollup?.contexts;
46
+ if (contexts &&
47
+ (contexts.pageInfo.hasPreviousPage ||
48
+ contexts.totalCount > MAX_CONTEXTS ||
49
+ contexts.nodes.length !== contexts.totalCount))
50
+ return false;
51
+ return true;
52
+ }
53
+ const firstPage = { hasPreviousPage: false, startCursor: null };
54
+ const lastPage = { hasNextPage: false, endCursor: null };
55
+ /** The `BatchPr` first page this layer's summary answers, field by field; null if incomplete. */
56
+ export function batchResponseFromSummary(layer, source, runs) {
57
+ if (!summaryComplete(layer, source))
58
+ return null;
59
+ const commit = headCommit(layer);
60
+ const rollup = commit.statusCheckRollup;
61
+ const contexts = [];
62
+ for (const node of rollup?.contexts.nodes ?? []) {
63
+ if (node.__typename === "StatusContext") {
64
+ contexts.push({
65
+ __typename: "StatusContext",
66
+ context: node.context,
67
+ state: node.state,
68
+ ...(node.createdAt !== undefined && { createdAt: node.createdAt }),
69
+ targetUrl: node.targetUrl ?? null,
70
+ description: node.description ?? null,
71
+ });
72
+ continue;
73
+ }
74
+ const run = node.id ? runs.get(node.id) : undefined;
75
+ if (!run)
76
+ return null;
77
+ const suite = node.checkSuite;
78
+ const workflowRun = suite?.workflowRun;
79
+ contexts.push({
80
+ __typename: "CheckRun",
81
+ id: node.id,
82
+ name: node.name,
83
+ status: node.status,
84
+ conclusion: node.conclusion,
85
+ detailsUrl: node.detailsUrl ?? null,
86
+ completedAt: node.completedAt ?? null,
87
+ startedAt: node.startedAt ?? null,
88
+ title: run.title,
89
+ summary: run.summary,
90
+ ...(run.annotations && { annotations: run.annotations }),
91
+ checkSuite: suite
92
+ ? {
93
+ ...(suite.createdAt && { createdAt: suite.createdAt }),
94
+ ...(suite.updatedAt && { updatedAt: suite.updatedAt }),
95
+ workflowRun: workflowRun
96
+ ? {
97
+ event: workflowRun.event,
98
+ ...(workflowRun.createdAt && { createdAt: workflowRun.createdAt }),
99
+ ...(workflowRun.updatedAt && { updatedAt: workflowRun.updatedAt }),
100
+ workflow: workflowRun.workflow
101
+ ? {
102
+ name: workflowRun.workflow.name,
103
+ databaseId: workflowRun.workflow.databaseId,
104
+ }
105
+ : null,
106
+ }
107
+ : null,
108
+ }
109
+ : null,
110
+ });
111
+ }
112
+ const { read } = source;
113
+ const stack = read.stack;
114
+ const bottom = source.layers[0];
115
+ const reviewSummary = (review) => ({
116
+ id: review.id,
117
+ isMinimized: review.isMinimized === true,
118
+ viewerCanMinimize: review.viewerCanMinimize === true,
119
+ authorAssociation: review.authorAssociation,
120
+ author: review.author,
121
+ body: review.body,
122
+ createdAt: review.createdAt,
123
+ url: review.url,
124
+ });
125
+ const pullRequest = {
126
+ id: layer.id,
127
+ number: layer.number,
128
+ updatedAt: layer.updatedAt,
129
+ state: layer.state,
130
+ isDraft: layer.isDraft,
131
+ viewerDidAuthor: layer.viewerDidAuthor === true,
132
+ viewerCanUpdate: layer.viewerCanUpdate === true,
133
+ viewerCanEnableAutoMerge: layer.viewerCanEnableAutoMerge === true,
134
+ viewerCanEditFiles: layer.viewerCanEditFiles === true,
135
+ mergeable: layer.mergeable,
136
+ mergeStateStatus: layer.mergeStateStatus,
137
+ reviewDecision: layer.reviewDecision,
138
+ headRefOid: layer.headRefOid,
139
+ headRefName: layer.headRefName,
140
+ headRepository: layer.headRepository ?? null,
141
+ baseRefName: layer.baseRefName,
142
+ baseRefOid: layer.baseRefOid,
143
+ baseRef: layer.baseRef ?? null,
144
+ isInMergeQueue: false,
145
+ isMergeQueueEnabled: layer.isMergeQueueEnabled === true,
146
+ autoMergeRequest: layer.batchAutoMergeRequest ?? null,
147
+ mergeQueueEntry: null,
148
+ mergeQueueAdditions: layer.mergeQueueAdditions ?? { nodes: [] },
149
+ mergeQueueRemovals: { nodes: [] },
150
+ mergeQueueRemovalTimes: { nodes: [] },
151
+ headRefForcePushes: layer.headRefForcePushes ?? { nodes: [] },
152
+ stack: {
153
+ id: stack.id,
154
+ number: layer.stack.number,
155
+ size: layer.stack.size,
156
+ baseRefName: layer.stack.baseRefName,
157
+ entries: {
158
+ pageInfo: lastPage,
159
+ nodes: source.layers.map((member, index) => ({
160
+ position: stack.positions[index],
161
+ pullRequest: {
162
+ number: member.number,
163
+ state: member.state,
164
+ headRefName: member.headRefName,
165
+ headRefOid: member.headRefOid,
166
+ baseRefName: member.baseRefName,
167
+ baseRefOid: member.baseRefOid,
168
+ },
169
+ })),
170
+ },
171
+ trunkEntry: {
172
+ nodes: [
173
+ { pullRequest: { baseRefName: bottom.baseRefName, baseRef: bottom.baseRef ?? null } },
174
+ ],
175
+ },
176
+ },
177
+ stackEntry: layer.stackEntry,
178
+ reviewRequests: {
179
+ nodes: layer.reviewRequests.nodes.map(({ requestedReviewer: reviewer }) => ({
180
+ requestedReviewer: !reviewer
181
+ ? null
182
+ : reviewer.__typename === "Team"
183
+ ? { name: reviewer.name }
184
+ : reviewer.__typename === "User" || reviewer.__typename === "Bot"
185
+ ? { login: reviewer.login }
186
+ : {},
187
+ })),
188
+ },
189
+ latestReviews: {
190
+ nodes: layer.latestReviews.nodes.map((review) => ({
191
+ author: review.author,
192
+ state: review.state,
193
+ })),
194
+ },
195
+ reviewThreads: {
196
+ totalCount: layer.reviewThreads.totalCount,
197
+ pageInfo: firstPage,
198
+ nodes: layer.reviewThreads.nodes.map((thread) => ({
199
+ id: thread.id,
200
+ isResolved: thread.isResolved,
201
+ isOutdated: thread.isOutdated,
202
+ viewerCanReply: thread.viewerCanReply,
203
+ viewerCanResolve: thread.viewerCanResolve,
204
+ path: thread.path,
205
+ line: thread.line ?? null,
206
+ startLine: thread.startLine ?? null,
207
+ comments: {
208
+ pageInfo: lastPage,
209
+ nodes: thread.comments.nodes.map((comment) => ({
210
+ id: comment.id,
211
+ isMinimized: comment.isMinimized,
212
+ url: comment.url,
213
+ authorAssociation: comment.authorAssociation,
214
+ viewerDidAuthor: comment.viewerDidAuthor,
215
+ author: comment.author,
216
+ pullRequestReview: comment.pullRequestReview ?? null,
217
+ body: comment.body,
218
+ path: comment.path ?? null,
219
+ line: comment.line ?? null,
220
+ startLine: comment.startLine ?? null,
221
+ createdAt: comment.createdAt,
222
+ updatedAt: comment.updatedAt,
223
+ })),
224
+ },
225
+ })),
226
+ },
227
+ comments: {
228
+ totalCount: layer.comments.totalCount,
229
+ pageInfo: firstPage,
230
+ nodes: layer.comments.nodes.map((comment) => ({
231
+ id: comment.id,
232
+ isMinimized: comment.isMinimized === true,
233
+ viewerCanMinimize: comment.viewerCanMinimize === true,
234
+ url: comment.url,
235
+ authorAssociation: comment.authorAssociation,
236
+ author: comment.author,
237
+ body: comment.body,
238
+ createdAt: comment.createdAt,
239
+ updatedAt: comment.updatedAt,
240
+ })),
241
+ },
242
+ changesRequestedReviews: {
243
+ pageInfo: firstPage,
244
+ nodes: reviewsIn(layer, "CHANGES_REQUESTED").map((review) => ({
245
+ id: review.id,
246
+ authorAssociation: review.authorAssociation,
247
+ author: review.author,
248
+ body: review.body,
249
+ createdAt: review.createdAt,
250
+ ...(review.commit && { commit: review.commit }),
251
+ })),
252
+ },
253
+ reviewSummaries: {
254
+ pageInfo: firstPage,
255
+ nodes: reviewsIn(layer, "COMMENTED").map(reviewSummary),
256
+ },
257
+ allReviews: {
258
+ totalCount: layer.reviews.totalCount,
259
+ nodes: layer.reviews.nodes.map((review) => ({ id: review.id, updatedAt: review.updatedAt })),
260
+ },
261
+ approvedReviews: {
262
+ pageInfo: firstPage,
263
+ nodes: reviewsIn(layer, "APPROVED").map(reviewSummary),
264
+ },
265
+ commits: {
266
+ totalCount: layer.commits.totalCount,
267
+ nodes: [
268
+ {
269
+ commit: {
270
+ oid: commit.oid,
271
+ committedDate: commit.committedDate,
272
+ checkSuites: {
273
+ pageInfo: commit.checkSuites.pageInfo,
274
+ nodes: commit.checkSuites.nodes.map((suite) => ({
275
+ id: suite.id,
276
+ status: suite.status,
277
+ conclusion: suite.conclusion,
278
+ workflowRun: suite.workflowRun
279
+ ? {
280
+ databaseId: suite.workflowRun.databaseId ?? null,
281
+ event: suite.workflowRun.event,
282
+ url: suite.workflowRun.url ?? null,
283
+ workflow: suite.workflowRun.workflow ?? null,
284
+ }
285
+ : null,
286
+ })),
287
+ },
288
+ statusCheckRollup: rollup
289
+ ? { state: rollup.state ?? null, contexts: { pageInfo: lastPage, nodes: contexts } }
290
+ : null,
291
+ },
292
+ },
293
+ ],
294
+ },
295
+ };
296
+ return {
297
+ viewer: { login: read.viewerLogin },
298
+ repository: {
299
+ viewerPermission: read.viewerPermission ?? null,
300
+ viewerCanAdminister: read.viewerCanAdminister === true,
301
+ ...read.mergeSettings,
302
+ pullRequest,
303
+ receiptSummary: structuredClone(layer),
304
+ },
305
+ };
306
+ }
@@ -0,0 +1,16 @@
1
+ import { type RepoInfo } from "./client.mts";
2
+ import type { RawBatchResponse } from "./batch-raw-types.mts";
3
+ import type { RawSummaryPr } from "./poll-summary-raw.mts";
4
+ import { type StackHandoffSource } from "./stack-handoff-page.mts";
5
+ import type { StackRead } from "./stack-read.mts";
6
+ /** Snapshot the summary read so later projection cannot change what a handoff carries. */
7
+ export declare function stackHandoffSource(read: StackRead<RawSummaryPr>): StackHandoffSource;
8
+ /**
9
+ * Store a handoff for each routed layer whose summary is complete, and clear any
10
+ * earlier handoff for every other layer this read covers. Check-run titles,
11
+ * summaries, and first annotations are not in the summary, so one `nodes(ids:)`
12
+ * read per 100 runs fills them in. Best effort: a failed read stores nothing.
13
+ */
14
+ export declare function writeStackHandoffs(source: StackHandoffSource, routed: number[], repo: RepoInfo): Promise<void>;
15
+ /** The stored first page for `pr`, once: the read deletes it. Null when absent or stale. */
16
+ export declare function takeStackHandoff(repo: RepoInfo, pr: number, now?: number): Promise<RawBatchResponse | null>;
@@ -0,0 +1,115 @@
1
+ import { loadDerived, storeDerived } from "../state/rest-cache.mjs";
2
+ import { graphqlWithRateLimit } from "./client.mjs";
3
+ import { STACK_HANDOFF_CHECK_RUNS_QUERY } from "./queries.mjs";
4
+ import { batchResponseFromSummary, checkRunIds, summaryComplete, } from "./stack-handoff-page.mjs";
5
+ import { getGithubTransport } from "./transport.mjs";
6
+ /**
7
+ * Routed stack layers reuse the stack summary's read.
8
+ *
9
+ * The stack tick that routes owned layers to their one-PR sessions already read
10
+ * each layer's head, reviews, threads, comments, and checks. It stores that
11
+ * read, reshaped into a `BatchPr` first page, under each routed layer. The
12
+ * layer's next `BatchPr` read consumes it once instead of asking GitHub again.
13
+ *
14
+ * Freshness: a handoff is the routing tick's own read of the layer's current
15
+ * head. It is consumed by the first read only, deleted on that read, and
16
+ * ignored once it is older than `HANDOFF_TTL_MS`. Every later read, including
17
+ * the one-PR poll's debounce re-read, asks GitHub.
18
+ *
19
+ * Completeness: a layer gets a handoff only when every window `BatchPr` reads
20
+ * on its first page is complete in the summary. Anything wider, queued, or
21
+ * removed from a queue reads `BatchPr` as before.
22
+ */
23
+ const HANDOFF = "stack-handoff";
24
+ const HANDOFF_TTL_MS = 2 * 60_000;
25
+ const MAX_RUNS_PER_QUERY = 100;
26
+ const stateKey = (repo, pr) => ({
27
+ owner: repo.owner,
28
+ repo: repo.name,
29
+ pr,
30
+ });
31
+ /** Snapshot the summary read so later projection cannot change what a handoff carries. */
32
+ export function stackHandoffSource(read) {
33
+ const { ordered, ...rest } = read;
34
+ return { read: rest, layers: structuredClone(ordered), readAt: Date.now() };
35
+ }
36
+ /**
37
+ * Store a handoff for each routed layer whose summary is complete, and clear any
38
+ * earlier handoff for every other layer this read covers. Check-run titles,
39
+ * summaries, and first annotations are not in the summary, so one `nodes(ids:)`
40
+ * read per 100 runs fills them in. Best effort: a failed read stores nothing.
41
+ */
42
+ export async function writeStackHandoffs(source, routed, repo) {
43
+ if (getGithubTransport() === "rest")
44
+ return;
45
+ const candidates = source.layers.filter((layer) => routed.includes(layer.number) && summaryComplete(layer, source));
46
+ // This read supersedes every earlier handoff, including for layers no longer routed.
47
+ for (const layer of source.layers) {
48
+ if (!candidates.includes(layer))
49
+ await clearStackHandoff(repo, layer.number);
50
+ }
51
+ if (candidates.length === 0)
52
+ return;
53
+ const runIds = [...new Set(candidates.flatMap(checkRunIds))];
54
+ let runs;
55
+ try {
56
+ runs = await readCheckRuns(runIds);
57
+ }
58
+ catch {
59
+ for (const layer of candidates)
60
+ await clearStackHandoff(repo, layer.number);
61
+ return;
62
+ }
63
+ for (const layer of candidates) {
64
+ const response = batchResponseFromSummary(layer, source, runs);
65
+ if (!response) {
66
+ await clearStackHandoff(repo, layer.number);
67
+ continue;
68
+ }
69
+ const stored = {
70
+ readAt: source.readAt,
71
+ headRefOid: layer.headRefOid,
72
+ response,
73
+ };
74
+ await storeDerived(stateKey(repo, layer.number), HANDOFF, stored, layer.headRefOid);
75
+ }
76
+ }
77
+ /** The stored first page for `pr`, once: the read deletes it. Null when absent or stale. */
78
+ export async function takeStackHandoff(repo, pr, now = Date.now()) {
79
+ const entry = await loadDerived(stateKey(repo, pr), HANDOFF);
80
+ const stored = entry?.value;
81
+ if (!stored)
82
+ return null;
83
+ await clearStackHandoff(repo, pr);
84
+ const age = now - stored.readAt;
85
+ if (!(age >= 0 && age <= HANDOFF_TTL_MS))
86
+ return null;
87
+ const head = stored.response.repository?.pullRequest?.headRefOid;
88
+ if (!head || head !== stored.headRefOid)
89
+ return null;
90
+ return stored.response;
91
+ }
92
+ async function clearStackHandoff(repo, pr) {
93
+ const entry = await loadDerived(stateKey(repo, pr), HANDOFF);
94
+ if (entry?.value)
95
+ await storeDerived(stateKey(repo, pr), HANDOFF, null);
96
+ }
97
+ async function readCheckRuns(ids) {
98
+ const runs = new Map();
99
+ for (let offset = 0; offset < ids.length; offset += MAX_RUNS_PER_QUERY) {
100
+ const chunk = ids.slice(offset, offset + MAX_RUNS_PER_QUERY);
101
+ const result = await graphqlWithRateLimit(STACK_HANDOFF_CHECK_RUNS_QUERY, {
102
+ ids: chunk,
103
+ });
104
+ for (const node of result.data.nodes) {
105
+ if (node?.__typename !== "CheckRun" || !node.id || !node.annotations)
106
+ continue;
107
+ runs.set(node.id, {
108
+ title: node.title ?? null,
109
+ summary: node.summary ?? null,
110
+ annotations: node.annotations,
111
+ });
112
+ }
113
+ }
114
+ return runs;
115
+ }
@@ -0,0 +1,6 @@
1
+ import type { StackMemberRefs } from "./stack-read.mts";
2
+ /** Bottom-to-top members, or an error unless every member and the anchor were observed. */
3
+ export declare function orderedStackMembers<Pr extends StackMemberRefs>(entries: ReadonlyArray<{
4
+ position: number;
5
+ pullRequest: Pr;
6
+ }>, anchor: number, stackSize: number): Pr[];
@@ -0,0 +1,13 @@
1
+ import { EXIT, ShepherdError } from "../exit-codes.mjs";
2
+ /** Bottom-to-top members, or an error unless every member and the anchor were observed. */
3
+ export function orderedStackMembers(entries, anchor, stackSize) {
4
+ const unique = new Map();
5
+ for (const entry of entries)
6
+ unique.set(entry.pullRequest.number, entry);
7
+ if (!unique.has(anchor) || unique.size !== stackSize) {
8
+ throw new ShepherdError(`GitHub returned incomplete stack membership (${unique.size} of ${stackSize} entries)`, EXIT.TEMPFAIL);
9
+ }
10
+ return [...unique.values()]
11
+ .sort((left, right) => left.position - right.position)
12
+ .map((entry) => entry.pullRequest);
13
+ }
@@ -1,6 +1,8 @@
1
1
  import { type MergeMethod } from "../config/merge-method.mts";
2
2
  import type { PollSummaryStackAncestry } from "../types.mts";
3
3
  import { type RepoInfo } from "./client.mts";
4
+ import { type StackHandoffContext } from "./stack-handoff-context.mts";
5
+ export { verifiedBottomOpenLayer } from "./stack-bottom.mts";
4
6
  /** GitHub's `first` ceiling for one native-stack entries page. */
5
7
  export declare const MAX_STACK_ENTRIES_PER_PAGE = 50;
6
8
  /** Fields every native-stack read needs to validate membership and adjacent ancestry. */
@@ -13,11 +15,11 @@ export interface StackMemberRefs {
13
15
  baseRefOid: string;
14
16
  }
15
17
  /** One complete, validated native-stack membership read. */
16
- export interface StackRead<Pr extends StackMemberRefs> {
18
+ export interface StackRead<Pr extends StackMemberRefs> extends StackHandoffContext {
17
19
  stackNumber: number;
18
20
  stackSize: number;
19
21
  viewerLogin: string | null;
20
- viewerCanAdminister: boolean;
22
+ viewerCanAdminister?: boolean;
21
23
  /** Every member, bottom-to-top, each observed in the same paged read. */
22
24
  ordered: Pr[];
23
25
  /** Present when the query selected repository merge settings, including an empty list. */
@@ -28,7 +30,9 @@ export interface StackRead<Pr extends StackMemberRefs> {
28
30
  * membership: a missing repository, PR, or stack; a changed stack identity; a
29
31
  * null entry; a page without a cursor; or fewer unique members than `size`.
30
32
  */
31
- export declare function readStack<Pr extends StackMemberRefs>(query: string, anchor: number, repo: RepoInfo, variables?: Record<string, unknown>): Promise<StackRead<Pr>>;
33
+ export declare function readStack<Pr extends StackMemberRefs>(query: string, anchor: number, repo: RepoInfo, initialVariables?: Record<string, unknown>,
34
+ /** Largest `first` a later page may request; defaults to the first page's `first`. */
35
+ pageCeiling?: number): Promise<StackRead<Pr>>;
32
36
  /** Membership and linking refs only — no per-PR CI or review hydration. */
33
37
  export declare function readStackTopology(anchor: number, repo: RepoInfo): Promise<StackRead<StackMemberRefs>>;
34
38
  /**
@@ -1,8 +1,13 @@
1
+ import { githubOperation } from "./transport.mjs";
2
+ import { readRestStackTopology } from "./rest-stack-read.mjs";
1
3
  import { readAllowedMergeMethods } from "../config/merge-method.mjs";
2
4
  import { EXIT, ShepherdError } from "../exit-codes.mjs";
3
5
  import { graphqlWithRateLimit } from "./client.mjs";
4
6
  import { missingRepositoryError } from "./errors.mjs";
5
7
  import { POLL_STACK_TOPOLOGY_QUERY } from "./queries.mjs";
8
+ import { orderedStackMembers } from "./stack-order.mjs";
9
+ import { stackHandoffContext } from "./stack-handoff-context.mjs";
10
+ export { verifiedBottomOpenLayer } from "./stack-bottom.mjs";
6
11
  /** GitHub's `first` ceiling for one native-stack entries page. */
7
12
  export const MAX_STACK_ENTRIES_PER_PAGE = 50;
8
13
  /**
@@ -10,13 +15,17 @@ export const MAX_STACK_ENTRIES_PER_PAGE = 50;
10
15
  * membership: a missing repository, PR, or stack; a changed stack identity; a
11
16
  * null entry; a page without a cursor; or fewer unique members than `size`.
12
17
  */
13
- export async function readStack(query, anchor, repo, variables = {}) {
18
+ export async function readStack(query, anchor, repo, initialVariables = {},
19
+ /** Largest `first` a later page may request; defaults to the first page's `first`. */
20
+ pageCeiling) {
21
+ let variables = initialVariables;
14
22
  let after = null;
15
23
  let stackId = null;
16
24
  let stackNumber = 0;
17
25
  let stackSize = 0;
18
26
  let viewerCanAdminister = false;
19
27
  let viewerLogin = null;
28
+ let settings = null;
20
29
  let allowedMergeMethods;
21
30
  const entries = [];
22
31
  do {
@@ -26,6 +35,7 @@ export async function readStack(query, anchor, repo, variables = {}) {
26
35
  if (!repository)
27
36
  throw missingRepositoryError(repo);
28
37
  viewerCanAdminister = repository.viewerCanAdminister;
38
+ settings = repository;
29
39
  allowedMergeMethods = readAllowedMergeMethods(repository) ?? allowedMergeMethods;
30
40
  if (!repository.pullRequest) {
31
41
  throw new ShepherdError(`PR #${anchor} not found`, EXIT.UNAVAILABLE);
@@ -54,16 +64,16 @@ export async function readStack(query, anchor, repo, variables = {}) {
54
64
  throw new ShepherdError("GitHub stack pagination returned a repeated end cursor", EXIT.TEMPFAIL);
55
65
  }
56
66
  after = pageInfo.hasNextPage ? pageInfo.endCursor : null;
67
+ // A later page never asks for more slots than remain: GitHub prices `first`.
68
+ if (after !== null && typeof variables.first === "number") {
69
+ const ceiling = pageCeiling ?? variables.first;
70
+ variables = {
71
+ ...variables,
72
+ first: Math.max(1, Math.min(ceiling, stackSize - entries.length)),
73
+ };
74
+ }
57
75
  } while (after !== null);
58
- const unique = new Map();
59
- for (const entry of entries)
60
- unique.set(entry.pullRequest.number, entry);
61
- if (!unique.has(anchor) || unique.size !== stackSize) {
62
- throw new ShepherdError(`GitHub returned incomplete stack membership (${unique.size} of ${stackSize} entries)`, EXIT.TEMPFAIL);
63
- }
64
- const ordered = [...unique.values()]
65
- .sort((left, right) => left.position - right.position)
66
- .map((entry) => entry.pullRequest);
76
+ const ordered = orderedStackMembers(entries, anchor, stackSize);
67
77
  return {
68
78
  stackNumber,
69
79
  stackSize,
@@ -71,11 +81,12 @@ export async function readStack(query, anchor, repo, variables = {}) {
71
81
  viewerCanAdminister,
72
82
  ordered,
73
83
  ...(allowedMergeMethods && { allowedMergeMethods }),
84
+ ...stackHandoffContext(settings, stackId, entries, ordered),
74
85
  };
75
86
  }
76
87
  /** Membership and linking refs only — no per-PR CI or review hydration. */
77
88
  export function readStackTopology(anchor, repo) {
78
- return readStack(POLL_STACK_TOPOLOGY_QUERY, anchor, repo);
89
+ return githubOperation("PollStackTopology", () => readStack(POLL_STACK_TOPOLOGY_QUERY, anchor, repo), () => readRestStackTopology(anchor, repo));
79
90
  }
80
91
  /**
81
92
  * Adjacent entries, both open, whose child base no longer matches the parent
@@ -0,0 +1,3 @@
1
+ import { type StateKey } from "../state/rest-cache.mts";
2
+ export declare function loadStackSizeHint(key: StateKey): Promise<number>;
3
+ export declare function storeStackSizeHint(key: StateKey, stackSize: number): Promise<void>;
@@ -0,0 +1,14 @@
1
+ import { loadDerived, storeDerived } from "../state/rest-cache.mjs";
2
+ /**
3
+ * The stack size the anchor's last summary observed. It only sizes the next
4
+ * summary page, so a stale value costs a page or a few unused slots, never a
5
+ * wrong answer: `readStack` still validates the full membership.
6
+ */
7
+ const STACK_SIZE_HINT = "stack-size";
8
+ export async function loadStackSizeHint(key) {
9
+ const value = (await loadDerived(key, STACK_SIZE_HINT))?.value;
10
+ return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : 0;
11
+ }
12
+ export async function storeStackSizeHint(key, stackSize) {
13
+ await storeDerived(key, STACK_SIZE_HINT, stackSize);
14
+ }
@@ -1,7 +1,11 @@
1
+ import { githubOperation } from "./transport.mjs";
2
+ import { readRestFeedback } from "./rest-feedback-read.mjs";
3
+ import { readRestPull } from "./rest-pr-core.mjs";
4
+ import { resolveRestThreadRoot, restThreadId } from "./rest-identities.mjs";
1
5
  import { graphql } from "./client.mjs";
2
6
  import { SUGGESTION_THREADS_QUERY } from "./queries.mjs";
3
7
  import { mapAuthorType, parseCreatedAt } from "./batch-parser-helpers.mjs";
4
- export async function fetchSuggestionThreads(pr, repo, threadIds) {
8
+ async function fetchGraphqlSuggestionThreads(pr, repo, threadIds) {
5
9
  const result = await graphql(SUGGESTION_THREADS_QUERY, {
6
10
  owner: repo.owner,
7
11
  repo: repo.name,
@@ -47,3 +51,25 @@ function parseThread(raw, pr, threadId) {
47
51
  createdAtUnix: comment?.createdAt ? parseCreatedAt(comment.createdAt) : 0,
48
52
  };
49
53
  }
54
+ export function fetchSuggestionThreads(pr, repo, threadIds) {
55
+ return githubOperation("SuggestionThreads", () => fetchGraphqlSuggestionThreads(pr, repo, threadIds), async () => {
56
+ const pull = await readRestPull(pr, repo);
57
+ const feedback = await readRestFeedback(pr, repo);
58
+ const threads = await Promise.all(threadIds.map(async (id) => {
59
+ try {
60
+ const root = await resolveRestThreadRoot(repo, pr, id);
61
+ const found = feedback.threads.find((thread) => thread.id === restThreadId(root));
62
+ return found ? { ...found, id } : null;
63
+ }
64
+ catch {
65
+ return null;
66
+ }
67
+ }));
68
+ return {
69
+ headRefOid: pull.head.sha,
70
+ headRefName: pull.head.ref,
71
+ headRepoWithOwner: pull.head.repo?.full_name ?? null,
72
+ threads,
73
+ };
74
+ });
75
+ }
@@ -1,10 +1,12 @@
1
+ import { githubOperation } from "./transport.mjs";
2
+ import { readRestRawThread } from "./rest-thread-read.mjs";
1
3
  import { graphqlWithRateLimit } from "./client.mjs";
2
4
  import { GitHubRequestError } from "./errors.mjs";
3
5
  import { paginateForward } from "./pagination.mjs";
4
6
  import { REVIEW_THREAD_COMMENTS_QUERY } from "./queries.mjs";
5
7
  import { mapPool } from "../util/pool.mjs";
6
8
  const THREAD_COMMENT_PAGE_CONCURRENCY = 4;
7
- export async function hydrateThreadCommentPages(threads, initialRateLimit) {
9
+ async function hydrateGraphqlThreadCommentPages(threads, initialRateLimit) {
8
10
  const gate = {
9
11
  rateLimit: initialRateLimit,
10
12
  exhausted: initialRateLimit?.remaining === 0,
@@ -60,3 +62,9 @@ async function hydrateThreadCommentPage(thread, gate) {
60
62
  },
61
63
  };
62
64
  }
65
+ export function hydrateThreadCommentPages(threads, initialRateLimit) {
66
+ return githubOperation("ReviewThreadComments", () => hydrateGraphqlThreadCommentPages(threads, initialRateLimit), async () => ({
67
+ threads: await mapPool(threads, THREAD_COMMENT_PAGE_CONCURRENCY, async (thread) => thread.comments.pageInfo?.hasNextPage ? readRestRawThread(thread.id) : thread),
68
+ rateLimit: initialRateLimit,
69
+ }));
70
+ }