pr-shepherd 0.56.6 → 0.57.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (421) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +150 -280
  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/codex-notices.d.mts +8 -0
  20. package/bin/classify/standard/codex-notices.mjs +34 -0
  21. package/bin/classify/standard/dependabot-notices.d.mts +4 -0
  22. package/bin/classify/standard/dependabot-notices.mjs +15 -0
  23. package/bin/classify/standard/gemini-notices.d.mts +4 -0
  24. package/bin/classify/standard/gemini-notices.mjs +13 -0
  25. package/bin/classify/standard/index.d.mts +7 -0
  26. package/bin/classify/standard/index.mjs +21 -0
  27. package/bin/classify/standard/kilo-no-issues.d.mts +4 -0
  28. package/bin/classify/standard/kilo-no-issues.mjs +17 -0
  29. package/bin/classify/standard/sourcery-notices.d.mts +8 -0
  30. package/bin/classify/standard/sourcery-notices.mjs +20 -0
  31. package/bin/cli/api-usage-formatter.mjs +6 -1
  32. package/bin/cli/apply-merge-handler.d.mts +3 -0
  33. package/bin/cli/apply-merge-handler.mjs +73 -0
  34. package/bin/cli/arg-flags.d.mts +2 -0
  35. package/bin/cli/arg-flags.mjs +47 -0
  36. package/bin/cli/args.mjs +10 -41
  37. package/bin/cli/clean-handler.d.mts +1 -0
  38. package/bin/cli/clean-handler.mjs +71 -0
  39. package/bin/cli/default-poll.mjs +3 -0
  40. package/bin/cli/event-state-scope.d.mts +19 -0
  41. package/bin/cli/event-state-scope.mjs +60 -0
  42. package/bin/cli/fix-formatter.mjs +5 -11
  43. package/bin/cli/handlers.d.mts +0 -1
  44. package/bin/cli/handlers.mjs +8 -64
  45. package/bin/cli/help-command-pages.d.mts +62 -18
  46. package/bin/cli/help-command-pages.mjs +59 -15
  47. package/bin/cli/help-iterate-poll-pages.d.mts +2 -2
  48. package/bin/cli/help-iterate-poll-pages.mjs +6 -0
  49. package/bin/cli/help-log-file-page.d.mts +1 -1
  50. package/bin/cli/help-log-file-page.mjs +4 -1
  51. package/bin/cli/help-top-page.d.mts +1 -1
  52. package/bin/cli/help-top-page.mjs +5 -0
  53. package/bin/cli/help.d.mts +63 -19
  54. package/bin/cli/help.mjs +2 -0
  55. package/bin/cli/iterate-branch-segment.d.mts +2 -0
  56. package/bin/cli/iterate-branch-segment.mjs +12 -7
  57. package/bin/cli/iterate-flags.d.mts +7 -1
  58. package/bin/cli/iterate-flags.mjs +38 -0
  59. package/bin/cli/iterate-formatter.d.mts +6 -6
  60. package/bin/cli/iterate-formatter.mjs +56 -22
  61. package/bin/cli/iterate-instructions.mjs +45 -15
  62. package/bin/cli/iterate-lean.mjs +39 -14
  63. package/bin/cli/iterate-merge-formatter.d.mts +6 -0
  64. package/bin/cli/iterate-merge-formatter.mjs +24 -14
  65. package/bin/cli/iterate-verbose.mjs +3 -6
  66. package/bin/cli/list-formatters.mjs +3 -1
  67. package/bin/cli/mutate-formatter.mjs +10 -5
  68. package/bin/cli/next-check-format.d.mts +7 -0
  69. package/bin/cli/next-check-format.mjs +10 -0
  70. package/bin/cli/playbook-handler.d.mts +5 -0
  71. package/bin/cli/playbook-handler.mjs +40 -0
  72. package/bin/cli/poll-handler.mjs +7 -6
  73. package/bin/cli/poll-summary-formatter.mjs +11 -2
  74. package/bin/cli/poll-targets.mjs +2 -0
  75. package/bin/cli/resolve-validators.mjs +2 -2
  76. package/bin/cli/runner.mjs +6 -1
  77. package/bin/cli/stack-layer-view.d.mts +33 -0
  78. package/bin/cli/stack-layer-view.mjs +1 -0
  79. package/bin/cli/stack-overview.d.mts +10 -28
  80. package/bin/cli/stack-overview.mjs +39 -14
  81. package/bin/cli/transport-args.d.mts +5 -0
  82. package/bin/cli/transport-args.mjs +23 -0
  83. package/bin/cli/transport-formatter.d.mts +9 -0
  84. package/bin/cli/transport-formatter.mjs +13 -0
  85. package/bin/cli-parser.mjs +62 -7
  86. package/bin/commands/apply-merge-options.d.mts +4 -0
  87. package/bin/commands/apply-merge-options.mjs +34 -0
  88. package/bin/commands/apply-merge-output.d.mts +7 -0
  89. package/bin/commands/apply-merge-output.mjs +20 -0
  90. package/bin/commands/apply-merge-pull.d.mts +20 -0
  91. package/bin/commands/apply-merge-pull.mjs +15 -0
  92. package/bin/commands/apply-merge.d.mts +18 -0
  93. package/bin/commands/apply-merge.mjs +126 -0
  94. package/bin/commands/apply-queue-removal.mjs +7 -0
  95. package/bin/commands/check-annotations.mjs +28 -18
  96. package/bin/commands/check-execution-context.d.mts +5 -0
  97. package/bin/commands/check-execution-context.mjs +16 -1
  98. package/bin/commands/check-fingerprint.d.mts +26 -2
  99. package/bin/commands/check-fingerprint.mjs +64 -28
  100. package/bin/commands/check-terminal-report.mjs +11 -1
  101. package/bin/commands/check-unreported.d.mts +6 -2
  102. package/bin/commands/check-unreported.mjs +32 -12
  103. package/bin/commands/check-wait-detectors.d.mts +35 -0
  104. package/bin/commands/check-wait-detectors.mjs +107 -0
  105. package/bin/commands/check.d.mts +4 -2
  106. package/bin/commands/check.mjs +130 -40
  107. package/bin/commands/event-instructions.d.mts +11 -0
  108. package/bin/commands/event-instructions.mjs +27 -0
  109. package/bin/commands/event-mode.d.mts +14 -0
  110. package/bin/commands/event-mode.mjs +138 -0
  111. package/bin/commands/iterate/api-usage.mjs +4 -2
  112. package/bin/commands/iterate/base.mjs +4 -0
  113. package/bin/commands/iterate/check-blocker-gate.mjs +14 -2
  114. package/bin/commands/iterate/check-instructions.d.mts +9 -10
  115. package/bin/commands/iterate/check-instructions.mjs +23 -22
  116. package/bin/commands/iterate/classify.mjs +19 -5
  117. package/bin/commands/iterate/escalate.mjs +13 -3
  118. package/bin/commands/iterate/fix-code.mjs +26 -9
  119. package/bin/commands/iterate/fix-instruction-threads.d.mts +0 -8
  120. package/bin/commands/iterate/fix-instruction-threads.mjs +0 -16
  121. package/bin/commands/iterate/fix-loop-instruction.d.mts +9 -0
  122. package/bin/commands/iterate/fix-loop-instruction.mjs +35 -0
  123. package/bin/commands/iterate/helpers.mjs +15 -2
  124. package/bin/commands/iterate/index.mjs +57 -16
  125. package/bin/commands/iterate/mark-ready.mjs +50 -10
  126. package/bin/commands/iterate/merge-plan.d.mts +19 -0
  127. package/bin/commands/iterate/merge-plan.mjs +88 -0
  128. package/bin/commands/iterate/merge-state.mjs +3 -0
  129. package/bin/commands/iterate/merge.d.mts +4 -15
  130. package/bin/commands/iterate/merge.mjs +18 -51
  131. package/bin/commands/iterate/merged-base-pull-requests.d.mts +2 -6
  132. package/bin/commands/iterate/merged-base-pull-requests.mjs +15 -11
  133. package/bin/commands/iterate/native-stack-rebase.mjs +11 -0
  134. package/bin/commands/iterate/render.d.mts +8 -2
  135. package/bin/commands/iterate/render.mjs +28 -42
  136. package/bin/commands/iterate/stack-trunk-conflict.mjs +17 -1
  137. package/bin/commands/iterate/stale-ancestry.mjs +4 -1
  138. package/bin/commands/iterate/stall.d.mts +5 -1
  139. package/bin/commands/iterate/stall.mjs +19 -7
  140. package/bin/commands/iterate/thread-mutation-routing.d.mts +1 -0
  141. package/bin/commands/iterate/thread-mutation-routing.mjs +4 -2
  142. package/bin/commands/iterate/unreported-required.mjs +5 -2
  143. package/bin/commands/mark-files-as-viewed.mjs +7 -0
  144. package/bin/commands/next-check.d.mts +31 -0
  145. package/bin/commands/next-check.mjs +46 -0
  146. package/bin/commands/playbook.d.mts +13 -0
  147. package/bin/commands/playbook.mjs +48 -0
  148. package/bin/commands/poll-mode.d.mts +8 -0
  149. package/bin/commands/poll-mode.mjs +14 -0
  150. package/bin/commands/poll-progress.d.mts +1 -0
  151. package/bin/commands/poll-progress.mjs +6 -3
  152. package/bin/commands/poll-quota.d.mts +3 -2
  153. package/bin/commands/poll-quota.mjs +7 -3
  154. package/bin/commands/poll-summary-explicit-instructions.d.mts +2 -0
  155. package/bin/commands/poll-summary-explicit-instructions.mjs +10 -1
  156. package/bin/commands/poll-summary-instructions.mjs +16 -17
  157. package/bin/commands/poll-summary.mjs +42 -17
  158. package/bin/commands/poll.d.mts +0 -1
  159. package/bin/commands/poll.mjs +11 -19
  160. package/bin/commands/quota-selection.d.mts +2 -1
  161. package/bin/commands/quota-selection.mjs +12 -7
  162. package/bin/commands/resolve-mutate.mjs +33 -5
  163. package/bin/commands/rest-stack-merge-readiness.d.mts +4 -0
  164. package/bin/commands/rest-stack-merge-readiness.mjs +74 -0
  165. package/bin/commands/rule-auto-resolve.mjs +9 -3
  166. package/bin/commands/shepherd-journal.d.mts +5 -4
  167. package/bin/commands/shepherd-journal.mjs +9 -8
  168. package/bin/commands/stack-drain.d.mts +1 -9
  169. package/bin/commands/stack-drain.mjs +50 -37
  170. package/bin/commands/stack-handoffs.d.mts +10 -0
  171. package/bin/commands/stack-handoffs.mjs +25 -0
  172. package/bin/commands/stack-stall.d.mts +5 -0
  173. package/bin/commands/stack-stall.mjs +11 -0
  174. package/bin/commands/stack-work.d.mts +5 -0
  175. package/bin/commands/stack-work.mjs +32 -0
  176. package/bin/comments/existing-reply-scan.d.mts +17 -0
  177. package/bin/comments/existing-reply-scan.mjs +41 -0
  178. package/bin/comments/rate-limit.mjs +2 -2
  179. package/bin/comments/resolve.d.mts +7 -1
  180. package/bin/comments/resolve.mjs +108 -18
  181. package/bin/comments/rest-reply.d.mts +9 -0
  182. package/bin/comments/rest-reply.mjs +95 -0
  183. package/bin/comments/rest-review-mutations.d.mts +22 -0
  184. package/bin/comments/rest-review-mutations.mjs +125 -0
  185. package/bin/comments/review-visibility.d.mts +1 -1
  186. package/bin/comments/review-visibility.mjs +3 -2
  187. package/bin/comments/sha-poll.mjs +7 -3
  188. package/bin/comments/thread-visibility.mjs +10 -5
  189. package/bin/comments/uncertain-replies.d.mts +11 -0
  190. package/bin/comments/uncertain-replies.mjs +121 -0
  191. package/bin/comments/uncertain-reply-guards.d.mts +10 -0
  192. package/bin/comments/uncertain-reply-guards.mjs +94 -0
  193. package/bin/comments/visible-comments.d.mts +1 -1
  194. package/bin/comments/visible-comments.mjs +4 -2
  195. package/bin/config/load.d.mts +37 -1
  196. package/bin/config/load.mjs +73 -1
  197. package/bin/config.json +6 -2
  198. package/bin/exit-codes.d.mts +3 -0
  199. package/bin/exit-codes.mjs +8 -0
  200. package/bin/github/api-telemetry-aggregate.d.mts +15 -4
  201. package/bin/github/api-telemetry-aggregate.mjs +57 -18
  202. package/bin/github/api-telemetry.d.mts +4 -0
  203. package/bin/github/api-telemetry.mjs +22 -8
  204. package/bin/github/apply-review-preflight.d.mts +20 -0
  205. package/bin/github/apply-review-preflight.mjs +61 -0
  206. package/bin/github/base-behind-cache.d.mts +15 -0
  207. package/bin/github/base-behind-cache.mjs +18 -0
  208. package/bin/github/batch-parse-suites.d.mts +4 -2
  209. package/bin/github/batch-parse-suites.mjs +6 -1
  210. package/bin/github/batch-parser-helpers.mjs +40 -2
  211. package/bin/github/batch-parsers.mjs +1 -0
  212. package/bin/github/batch-raw-rules.d.mts +26 -0
  213. package/bin/github/batch-raw-types.d.mts +8 -6
  214. package/bin/github/batch-stack-evidence.d.mts +28 -0
  215. package/bin/github/batch-stack-evidence.mjs +62 -0
  216. package/bin/github/batch.d.mts +15 -2
  217. package/bin/github/batch.mjs +34 -17
  218. package/bin/github/captured-proxy-agent.d.mts +3 -0
  219. package/bin/github/captured-proxy-agent.mjs +8 -0
  220. package/bin/github/check-annotations-batch.d.mts +0 -5
  221. package/bin/github/check-annotations-batch.mjs +25 -4
  222. package/bin/github/check-annotations.mjs +3 -1
  223. package/bin/github/client.d.mts +8 -19
  224. package/bin/github/client.mjs +45 -13
  225. package/bin/github/fingerprint-fields.d.mts +8 -0
  226. package/bin/github/fingerprint-fields.mjs +6 -1
  227. package/bin/github/fingerprint.d.mts +4 -2
  228. package/bin/github/fingerprint.mjs +6 -38
  229. package/bin/github/github-fetch.d.mts +2 -0
  230. package/bin/github/github-fetch.mjs +32 -0
  231. package/bin/github/gql/base-behind.gql +3 -0
  232. package/bin/github/gql/batch-pr-page.gql +16 -0
  233. package/bin/github/gql/batch-pr.gql +57 -0
  234. package/bin/github/gql/commit-check-contexts.gql +17 -0
  235. package/bin/github/gql/get-pr-body.gql +1 -0
  236. package/bin/github/gql/poll-stack-summary.gql +1 -0
  237. package/bin/github/gql/poll-summary-check-contexts.gql +5 -0
  238. package/bin/github/gql/poll-summary-fragment.gql +65 -15
  239. package/bin/github/gql/stack-handoff-check-runs.gql +42 -0
  240. package/bin/github/graphql-http.mjs +9 -4
  241. package/bin/github/graphql-internal-retry.d.mts +2 -0
  242. package/bin/github/graphql-internal-retry.mjs +1 -1
  243. package/bin/github/graphql-quota-probe.d.mts +6 -0
  244. package/bin/github/graphql-quota-probe.mjs +56 -0
  245. package/bin/github/graphql-session-refusal.d.mts +8 -0
  246. package/bin/github/graphql-session-refusal.mjs +14 -0
  247. package/bin/github/http-request.d.mts +1 -1
  248. package/bin/github/http-request.mjs +22 -6
  249. package/bin/github/http-utils.mjs +6 -0
  250. package/bin/github/legacy-dispatcher.d.mts +6 -0
  251. package/bin/github/legacy-dispatcher.mjs +44 -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.d.mts +6 -1
  272. package/bin/github/poll-summary-readiness.mjs +13 -4
  273. package/bin/github/poll-summary-rest-queue.d.mts +3 -0
  274. package/bin/github/poll-summary-rest-queue.mjs +12 -0
  275. package/bin/github/poll-summary-review.d.mts +1 -1
  276. package/bin/github/poll-summary-review.mjs +35 -20
  277. package/bin/github/poll-summary-route.mjs +19 -3
  278. package/bin/github/poll-summary-stack-page.d.mts +11 -0
  279. package/bin/github/poll-summary-stack-page.mjs +27 -0
  280. package/bin/github/poll-summary-unreported.d.mts +3 -1
  281. package/bin/github/poll-summary-unreported.mjs +6 -3
  282. package/bin/github/poll-summary.d.mts +4 -1
  283. package/bin/github/poll-summary.mjs +47 -33
  284. package/bin/github/queries.d.mts +7 -3
  285. package/bin/github/queries.mjs +8 -4
  286. package/bin/github/rate-limit-sample.d.mts +7 -0
  287. package/bin/github/rate-limit-sample.mjs +12 -0
  288. package/bin/github/reply-recovery-read.d.mts +50 -0
  289. package/bin/github/reply-recovery-read.mjs +118 -0
  290. package/bin/github/reply-thread-transcripts.d.mts +0 -1
  291. package/bin/github/reply-thread-transcripts.mjs +24 -1
  292. package/bin/github/rest-annotation-read.d.mts +5 -0
  293. package/bin/github/rest-annotation-read.mjs +44 -0
  294. package/bin/github/rest-batch-read.d.mts +12 -0
  295. package/bin/github/rest-batch-read.mjs +57 -0
  296. package/bin/github/rest-batch-snapshot.d.mts +30 -0
  297. package/bin/github/rest-batch-snapshot.mjs +115 -0
  298. package/bin/github/rest-batch-unavailable.d.mts +5 -0
  299. package/bin/github/rest-batch-unavailable.mjs +16 -0
  300. package/bin/github/rest-check-read.d.mts +34 -0
  301. package/bin/github/rest-check-read.mjs +124 -0
  302. package/bin/github/rest-comment-authorship.d.mts +7 -0
  303. package/bin/github/rest-comment-authorship.mjs +11 -0
  304. package/bin/github/rest-conditional-scope.d.mts +20 -0
  305. package/bin/github/rest-conditional-scope.mjs +44 -0
  306. package/bin/github/rest-explicit-summary-read.d.mts +3 -0
  307. package/bin/github/rest-explicit-summary-read.mjs +8 -0
  308. package/bin/github/rest-feedback-read.d.mts +36 -0
  309. package/bin/github/rest-feedback-read.mjs +147 -0
  310. package/bin/github/rest-feedback-validation.d.mts +5 -0
  311. package/bin/github/rest-feedback-validation.mjs +28 -0
  312. package/bin/github/rest-http.d.mts +12 -0
  313. package/bin/github/rest-http.mjs +108 -23
  314. package/bin/github/rest-identities.d.mts +22 -0
  315. package/bin/github/rest-identities.mjs +134 -0
  316. package/bin/github/rest-merge-stack-guard.d.mts +14 -0
  317. package/bin/github/rest-merge-stack-guard.mjs +41 -0
  318. package/bin/github/rest-merge.d.mts +29 -0
  319. package/bin/github/rest-merge.mjs +56 -0
  320. package/bin/github/rest-merged-base-read.d.mts +9 -0
  321. package/bin/github/rest-merged-base-read.mjs +48 -0
  322. package/bin/github/rest-pr-core.d.mts +52 -0
  323. package/bin/github/rest-pr-core.mjs +60 -0
  324. package/bin/github/rest-reader-core.d.mts +16 -0
  325. package/bin/github/rest-reader-core.mjs +154 -0
  326. package/bin/github/rest-review-read.d.mts +11 -0
  327. package/bin/github/rest-review-read.mjs +47 -0
  328. package/bin/github/rest-rules-read.d.mts +9 -0
  329. package/bin/github/rest-rules-read.mjs +154 -0
  330. package/bin/github/rest-session-refusal.d.mts +5 -0
  331. package/bin/github/rest-session-refusal.mjs +21 -0
  332. package/bin/github/rest-snapshot-context.d.mts +10 -0
  333. package/bin/github/rest-snapshot-context.mjs +1 -0
  334. package/bin/github/rest-stack-read.d.mts +21 -0
  335. package/bin/github/rest-stack-read.mjs +47 -0
  336. package/bin/github/rest-stack-summary.d.mts +8 -0
  337. package/bin/github/rest-stack-summary.mjs +62 -0
  338. package/bin/github/rest-summary-read.d.mts +7 -0
  339. package/bin/github/rest-summary-read.mjs +112 -0
  340. package/bin/github/rest-terminal-batch.d.mts +14 -0
  341. package/bin/github/rest-terminal-batch.mjs +45 -0
  342. package/bin/github/rest-text.mjs +7 -3
  343. package/bin/github/rest-thread-read.d.mts +4 -0
  344. package/bin/github/rest-thread-read.mjs +35 -0
  345. package/bin/github/rest-viewer-read.d.mts +2 -0
  346. package/bin/github/rest-viewer-read.mjs +41 -0
  347. package/bin/github/stack-bottom.d.mts +3 -0
  348. package/bin/github/stack-bottom.mjs +14 -0
  349. package/bin/github/stack-handoff-context.d.mts +25 -0
  350. package/bin/github/stack-handoff-context.mjs +12 -0
  351. package/bin/github/stack-handoff-page.d.mts +17 -0
  352. package/bin/github/stack-handoff-page.mjs +306 -0
  353. package/bin/github/stack-handoff.d.mts +16 -0
  354. package/bin/github/stack-handoff.mjs +115 -0
  355. package/bin/github/stack-order.d.mts +6 -0
  356. package/bin/github/stack-order.mjs +13 -0
  357. package/bin/github/stack-read.d.mts +7 -5
  358. package/bin/github/stack-read.mjs +22 -24
  359. package/bin/github/stack-size-hint.d.mts +3 -0
  360. package/bin/github/stack-size-hint.mjs +14 -0
  361. package/bin/github/suggestion-thread.mjs +27 -1
  362. package/bin/github/thread-comments.mjs +9 -1
  363. package/bin/github/transport-evidence.d.mts +20 -0
  364. package/bin/github/transport-evidence.mjs +26 -0
  365. package/bin/github/transport-mode.d.mts +2 -0
  366. package/bin/github/transport-mode.mjs +5 -0
  367. package/bin/github/transport.d.mts +19 -0
  368. package/bin/github/transport.mjs +126 -0
  369. package/bin/github/unsupported-rest.d.mts +4 -0
  370. package/bin/github/unsupported-rest.mjs +7 -0
  371. package/bin/mcp/server.mjs +52 -3
  372. package/bin/merge-status/derive.d.mts +2 -0
  373. package/bin/merge-status/derive.mjs +17 -13
  374. package/bin/merge-status/required-check-requirements.d.mts +7 -0
  375. package/bin/merge-status/required-check-requirements.mjs +15 -0
  376. package/bin/merge-status/requirements-format.d.mts +5 -0
  377. package/bin/merge-status/requirements-format.mjs +41 -12
  378. package/bin/merge-status/requirements.mjs +18 -7
  379. package/bin/state/base.mjs +41 -0
  380. package/bin/state/durable-state.d.mts +6 -0
  381. package/bin/state/durable-state.mjs +41 -0
  382. package/bin/state/graphql-quota-policy.mjs +23 -16
  383. package/bin/state/merge-request.d.mts +25 -0
  384. package/bin/state/merge-request.mjs +145 -0
  385. package/bin/state/pr-fingerprint.d.mts +15 -0
  386. package/bin/state/pr-fingerprint.mjs +22 -7
  387. package/bin/state/rest-cache.d.mts +3 -0
  388. package/bin/state/rest-cache.mjs +1 -0
  389. package/bin/state/rest-snapshot-report.d.mts +21 -0
  390. package/bin/state/rest-snapshot-report.mjs +52 -0
  391. package/bin/state/seen-comments.d.mts +3 -0
  392. package/bin/state/seen-comments.mjs +18 -0
  393. package/bin/types/api-usage.d.mts +9 -1
  394. package/bin/types/escalate.d.mts +1 -1
  395. package/bin/types/github.d.mts +23 -9
  396. package/bin/types/iterate.d.mts +24 -2
  397. package/bin/types/merge-action.d.mts +1 -1
  398. package/bin/types/merge-requirements.d.mts +7 -7
  399. package/bin/types/next-check.d.mts +12 -0
  400. package/bin/types/next-check.mjs +1 -0
  401. package/bin/types/poll-summary.d.mts +21 -0
  402. package/bin/types/report.d.mts +13 -1
  403. package/bin/types/review-thread.d.mts +1 -1
  404. package/bin/types.d.mts +1 -0
  405. package/bin/types.mjs +1 -0
  406. package/package.json +4 -3
  407. package/plugins/pr-shepherd/.codex-plugin/plugin.json +1 -1
  408. package/plugins/pr-shepherd/.codex.mcp.json +1 -1
  409. package/plugins/pr-shepherd/.mcp.json +1 -1
  410. package/plugins/pr-shepherd/skills/mark-files-as-viewed/SKILL.md +1 -1
  411. package/plugins/pr-shepherd/skills/pr-shepherd/SKILL.md +14 -51
  412. package/plugins/pr-shepherd/skills/pr-shepherd/references/ci-failure-triage.md +12 -16
  413. package/plugins/pr-shepherd/skills/pr-shepherd/references/cloud-event-loop.md +14 -0
  414. package/plugins/pr-shepherd/skills/pr-shepherd/references/create-pr.md +10 -0
  415. package/plugins/pr-shepherd/skills/pr-shepherd/references/fix-code-loop.md +10 -0
  416. package/plugins/pr-shepherd/skills/pr-shepherd/references/mcp-fallback.md +9 -0
  417. package/plugins/pr-shepherd/skills/reduce-pr-noise/references/classifiers.md +3 -1
  418. package/bin/github/gql/pr-fingerprint.gql +0 -75
  419. package/plugins/pr-shepherd/skills/pr-shepherd/references/journal.md +0 -7
  420. package/plugins/pr-shepherd/skills/pr-shepherd/references/review-mutations.md +0 -9
  421. package/plugins/pr-shepherd/skills/pr-shepherd/references/stack-merge.md +0 -7
@@ -1,9 +1,11 @@
1
+ import { mostConservativeSample } from "./rate-limit-sample.mjs";
1
2
  export function emptyAggregate() {
2
3
  return {
3
4
  eventCount: 0,
4
5
  credentialSources: new Map(),
5
6
  graphql: { requestCount: 0, measuredQueryCost: 0, unmeasuredRequestCount: 0, nodeCount: 0 },
6
7
  rest: new Map(),
8
+ unmeteredRestRequestCount: 0,
7
9
  };
8
10
  }
9
11
  export function aggregateEvents(events) {
@@ -25,12 +27,19 @@ export function aggregateEvents(events) {
25
27
  }
26
28
  continue;
27
29
  }
28
- const resource = event.rateLimit?.resource ?? "unknown";
29
- const group = aggregate.rest.get(resource) ?? { requestCount: 0 };
30
- group.requestCount += 1;
31
- if (event.rateLimit !== undefined) {
32
- group.rateLimit = selectAuthoritativeRateLimit(group.rateLimit, event.rateLimit);
30
+ if (event.rateLimit === undefined && !event.notModified) {
31
+ aggregate.unmeteredRestRequestCount += 1;
32
+ continue;
33
33
  }
34
+ const resource = event.rateLimit?.resource ?? (event.notModified ? "core" : "unknown");
35
+ const group = aggregate.rest.get(resource) ?? { requestCount: 0, notModifiedCount: 0 };
36
+ if (event.notModified)
37
+ group.notModifiedCount += 1;
38
+ else
39
+ group.requestCount += 1;
40
+ const sample = event.rateLimit;
41
+ if (sample)
42
+ adoptRestRateLimit(group, sample, event.credentialFingerprint, event.sequence);
34
43
  aggregate.rest.set(resource, group);
35
44
  }
36
45
  return aggregate;
@@ -47,14 +56,16 @@ export function mergeAggregate(target, source) {
47
56
  target.graphql.measuredQueryCost += source.graphql.measuredQueryCost;
48
57
  target.graphql.unmeasuredRequestCount += source.graphql.unmeasuredRequestCount;
49
58
  target.graphql.nodeCount += source.graphql.nodeCount;
59
+ target.unmeteredRestRequestCount += source.unmeteredRestRequestCount;
50
60
  if (source.graphql.rateLimit !== undefined) {
51
61
  adoptGraphqlRateLimit(target.graphql, source.graphql.rateLimit, source.graphql.credentialFingerprint);
52
62
  }
53
63
  for (const [resource, sourceGroup] of source.rest) {
54
- const targetGroup = target.rest.get(resource) ?? { requestCount: 0 };
64
+ const targetGroup = target.rest.get(resource) ?? { requestCount: 0, notModifiedCount: 0 };
55
65
  targetGroup.requestCount += sourceGroup.requestCount;
66
+ targetGroup.notModifiedCount += sourceGroup.notModifiedCount;
56
67
  if (sourceGroup.rateLimit !== undefined) {
57
- targetGroup.rateLimit = selectAuthoritativeRateLimit(targetGroup.rateLimit, sourceGroup.rateLimit);
68
+ adoptRestRateLimit(targetGroup, sourceGroup.rateLimit, sourceGroup.credentialFingerprint, sourceGroup.latestCredentialSequence, sourceGroup.latestCredentialFingerprint ?? sourceGroup.credentialFingerprint, sourceGroup.latestCredentialSequence);
58
69
  }
59
70
  target.rest.set(resource, targetGroup);
60
71
  }
@@ -66,25 +77,53 @@ export function aggregateStore(active) {
66
77
  return all;
67
78
  }
68
79
  function adoptGraphqlRateLimit(graphql, candidate, fingerprint) {
69
- const next = selectAuthoritativeRateLimit(graphql.rateLimit, candidate);
80
+ // Two credentials' samples are compared whole, never mixed: a mixed sample would be
81
+ // attributed to whichever credential supplied the changed half.
82
+ const sameCredential = fingerprint === undefined ||
83
+ graphql.credentialFingerprint === undefined ||
84
+ fingerprint === graphql.credentialFingerprint;
85
+ const next = selectAuthoritativeRateLimit(graphql.rateLimit, candidate, sameCredential);
70
86
  if (next !== graphql.rateLimit)
71
87
  graphql.credentialFingerprint = fingerprint;
72
88
  graphql.rateLimit = next;
73
89
  }
74
- function selectAuthoritativeRateLimit(current, candidate) {
90
+ function adoptRestRateLimit(group, candidate, fingerprint, sequence, latestCredentialFingerprint = fingerprint, latestCredentialSequence = sequence) {
91
+ const credentialChanged = group.latestCredentialFingerprint !== undefined &&
92
+ latestCredentialFingerprint !== undefined &&
93
+ group.latestCredentialFingerprint !== latestCredentialFingerprint;
94
+ const olderCredentialObservation = credentialChanged &&
95
+ group.latestCredentialSequence !== undefined &&
96
+ latestCredentialSequence !== undefined &&
97
+ latestCredentialSequence < group.latestCredentialSequence;
98
+ if (olderCredentialObservation)
99
+ return;
100
+ if (group.rateLimit === undefined || credentialChanged) {
101
+ group.rateLimit = { ...candidate };
102
+ group.credentialFingerprint = fingerprint;
103
+ }
104
+ else {
105
+ const next = selectAuthoritativeRateLimit(group.rateLimit, candidate);
106
+ if (next !== group.rateLimit) {
107
+ group.rateLimit = next;
108
+ group.credentialFingerprint = fingerprint;
109
+ }
110
+ }
111
+ if (latestCredentialFingerprint !== undefined &&
112
+ (group.latestCredentialSequence === undefined ||
113
+ latestCredentialSequence === undefined ||
114
+ latestCredentialSequence >= group.latestCredentialSequence)) {
115
+ group.latestCredentialFingerprint = latestCredentialFingerprint;
116
+ group.latestCredentialSequence = latestCredentialSequence;
117
+ }
118
+ }
119
+ function selectAuthoritativeRateLimit(current, candidate, sameCredential = true) {
75
120
  if (current === undefined)
76
121
  return { ...candidate };
77
122
  if (candidate.resetAt !== current.resetAt) {
78
123
  return candidate.resetAt > current.resetAt ? { ...candidate } : current;
79
124
  }
80
- if (candidate.remaining !== current.remaining) {
125
+ if (!sameCredential)
81
126
  return candidate.remaining < current.remaining ? { ...candidate } : current;
82
- }
83
- if (candidate.used !== current.used) {
84
- if (candidate.used === undefined)
85
- return current;
86
- if (current.used === undefined || candidate.used > current.used)
87
- return { ...candidate };
88
- }
89
- return current;
127
+ const merged = mostConservativeSample(current, candidate);
128
+ return merged.remaining === current.remaining && merged.used === current.used ? current : merged;
90
129
  }
@@ -7,6 +7,8 @@ export interface ApiTelemetryEvent {
7
7
  /** Truncated SHA-256 of the credential that produced this request. */
8
8
  credentialFingerprint?: string;
9
9
  rateLimit?: RateLimitInfo;
10
+ /** HTTP 304 to a conditional read: consumes no primary quota, so it is not a counted request. */
11
+ notModified?: boolean;
10
12
  }
11
13
  /** Isolates a top-level CLI/MCP command while allowing nested iterate ticks to aggregate. */
12
14
  export declare function withApiTelemetryScope<T>(fn: () => Promise<T>): Promise<T>;
@@ -14,4 +16,6 @@ export declare function recordApiTelemetry(event: ApiTelemetryEvent): void;
14
16
  export declare function mergeGraphqlRateLimit(headerRateLimit: RateLimitInfo | null, data: unknown): RateLimitInfo | null;
15
17
  /** Attach the credential fingerprint without adding it when this command has none. */
16
18
  export declare function withGraphqlCredentialFingerprint<T extends object>(sample: T): T;
19
+ /** Attach the REST core credential fingerprint without exposing it in API usage output. */
20
+ export declare function withRestCoreCredentialFingerprint<T extends object>(sample: T): T;
17
21
  export declare function summarizeApiTelemetry(): ApiUsage | undefined;
@@ -72,11 +72,23 @@ function graphqlQuotaCredentialFingerprint() {
72
72
  return undefined;
73
73
  return aggregateStore(active).graphql.credentialFingerprint;
74
74
  }
75
+ /** Fingerprint of the credential that owns the authoritative REST core sample. */
76
+ function restCoreQuotaCredentialFingerprint() {
77
+ const active = store();
78
+ if (active === undefined)
79
+ return undefined;
80
+ return aggregateStore(active).rest.get("core")?.credentialFingerprint;
81
+ }
75
82
  /** Attach the credential fingerprint without adding it when this command has none. */
76
83
  export function withGraphqlCredentialFingerprint(sample) {
77
84
  const credentialFingerprint = graphqlQuotaCredentialFingerprint();
78
85
  return credentialFingerprint === undefined ? sample : { ...sample, credentialFingerprint };
79
86
  }
87
+ /** Attach the REST core credential fingerprint without exposing it in API usage output. */
88
+ export function withRestCoreCredentialFingerprint(sample) {
89
+ const credentialFingerprint = restCoreQuotaCredentialFingerprint();
90
+ return credentialFingerprint === undefined ? sample : { ...sample, credentialFingerprint };
91
+ }
80
92
  export function summarizeApiTelemetry() {
81
93
  const active = store();
82
94
  if (active === undefined)
@@ -89,32 +101,34 @@ export function summarizeApiTelemetry() {
89
101
  .map(([source]) => source);
90
102
  const graphql = summarizeGraphql(selected.graphql);
91
103
  const rest = summarizeRest(selected.rest);
104
+ const unmeteredRestRequestCount = selected.unmeteredRestRequestCount;
92
105
  return {
93
106
  credentialSources,
94
107
  ...(graphql !== undefined && { graphql }),
95
108
  ...(rest.length > 0 && { rest }),
109
+ ...(unmeteredRestRequestCount > 0 && { unmeteredRestRequestCount }),
96
110
  };
97
111
  }
98
112
  function summarizeGraphql(selected) {
99
- const rateLimit = selected.rateLimit;
100
- if (rateLimit === undefined)
113
+ if (selected.rateLimit === undefined)
101
114
  return undefined;
102
115
  return {
103
- ...resourceUsage(rateLimit, selected.requestCount, "graphql"),
116
+ ...resourceUsage(selected.rateLimit, selected.requestCount, "graphql"),
104
117
  measuredQueryCost: selected.measuredQueryCost,
105
118
  unmeasuredRequestCount: selected.unmeasuredRequestCount,
106
119
  nodeCount: selected.nodeCount,
107
120
  };
108
121
  }
109
122
  function summarizeRest(selected) {
110
- return [...selected.entries()].flatMap(([resource, group]) => {
111
- return group.rateLimit ? [resourceUsage(group.rateLimit, group.requestCount, resource)] : [];
112
- });
123
+ return [...selected.entries()].flatMap(([resource, group]) => group.rateLimit
124
+ ? [resourceUsage(group.rateLimit, group.requestCount, resource, group.notModifiedCount)]
125
+ : []);
113
126
  }
114
- function resourceUsage(rateLimit, requestCount, fallbackResource) {
127
+ function resourceUsage(rateLimit, requestCount, fallbackResource, notModified = 0) {
115
128
  return {
116
129
  resource: rateLimit.resource ?? fallbackResource,
117
- requestCount,
130
+ ...(requestCount > 0 && { requestCount }),
131
+ ...(notModified > 0 && { notModified }),
118
132
  limit: rateLimit.limit,
119
133
  ...(rateLimit.used !== undefined && { used: rateLimit.used }),
120
134
  remaining: rateLimit.remaining,
@@ -0,0 +1,20 @@
1
+ import { type RepoInfo } from "./client.mts";
2
+ import { type ReplyEvidence } from "./reply-recovery-read.mts";
3
+ /**
4
+ * Reads, in one GraphQL request, what `apply review` with reply IDs otherwise reads three times:
5
+ * the PR head for `--require-sha`, the pre-reply transcripts for seen markers, and the reply
6
+ * recovery evidence for the first mutation batch. Every field is checked exactly as its
7
+ * standalone read checks it; anything missing is left to that read.
8
+ */
9
+ export interface ApplyReviewPreflight {
10
+ headSha?: string;
11
+ /** Pre-reply transcripts for threads whose comments fit on the first page. */
12
+ transcripts: Map<string, string>;
13
+ /** Complete recovery evidence per requested thread. */
14
+ evidence: Map<string, ReplyEvidence>;
15
+ }
16
+ /**
17
+ * GraphQL-only; returns null when the preflight does not apply (REST transport, no reply IDs,
18
+ * more than one transcript batch, or REST thread handles). Request errors propagate.
19
+ */
20
+ export declare function readApplyReviewPreflight(pr: number, repo: RepoInfo, replyIds: readonly string[]): Promise<ApplyReviewPreflight | null>;
@@ -0,0 +1,61 @@
1
+ import { graphqlWithRateLimit } from "./client.mjs";
2
+ import { githubOperation } from "./transport.mjs";
3
+ import { REPLY_EVIDENCE_THREAD_SELECTION, threadReplyEvidence, } from "./reply-recovery-read.mjs";
4
+ import { threadTranscriptBodies } from "../threads/transcript.mjs";
5
+ import { mapPool } from "../util/pool.mjs";
6
+ const MAX_IDS = 20;
7
+ const QUERY = `query ApplyReviewPreflight($owner: String!, $repo: String!, $pr: Int!, $ids: [ID!]!, $cursor: String) {
8
+ _shepherdRateLimit: rateLimit { cost limit nodeCount remaining resetAt used }
9
+ repository(owner: $owner, name: $repo) { pullRequest(number: $pr) { headRefOid } }
10
+ viewer { login }
11
+ nodes(ids: $ids) { __typename ${REPLY_EVIDENCE_THREAD_SELECTION} }
12
+ }`;
13
+ /**
14
+ * GraphQL-only; returns null when the preflight does not apply (REST transport, no reply IDs,
15
+ * more than one transcript batch, or REST thread handles). Request errors propagate.
16
+ */
17
+ export function readApplyReviewPreflight(pr, repo, replyIds) {
18
+ const ids = [...new Set(replyIds)];
19
+ if (ids.length === 0 || ids.length > MAX_IDS || ids.some((id) => id.startsWith("rest-thread-")))
20
+ return Promise.resolve(null);
21
+ return githubOperation("ApplyReviewPreflight", async () => {
22
+ const { data } = await graphqlWithRateLimit(QUERY, {
23
+ owner: repo.owner,
24
+ repo: repo.name,
25
+ pr,
26
+ ids,
27
+ cursor: null,
28
+ });
29
+ const headSha = data.repository?.pullRequest?.headRefOid;
30
+ const evidence = await mapPool(ids, 4, async (id) => {
31
+ try {
32
+ return [id, await threadReplyEvidence(repo, pr, id, data)];
33
+ }
34
+ catch {
35
+ return null;
36
+ }
37
+ });
38
+ return {
39
+ ...(typeof headSha === "string" && headSha && { headSha }),
40
+ transcripts: firstPageTranscripts(data, pr, repo, new Set(ids)),
41
+ evidence: new Map(evidence.filter((entry) => entry !== null)),
42
+ };
43
+ }, async () => null);
44
+ }
45
+ /** The same acceptance rules as `ReplyThreadTranscripts`, limited to single-page threads. */
46
+ function firstPageTranscripts(data, pr, repo, requested) {
47
+ const expectedRepo = `${repo.owner}/${repo.name}`.toLowerCase();
48
+ const transcripts = new Map();
49
+ for (const node of data.nodes) {
50
+ if (node?.__typename !== "PullRequestReviewThread" ||
51
+ !requested.has(node.id) ||
52
+ node.pullRequest?.number !== pr ||
53
+ node.pullRequest.repository.nameWithOwner.toLowerCase() !== expectedRepo ||
54
+ !node.comments ||
55
+ node.comments.pageInfo.hasNextPage ||
56
+ node.comments.nodes.length === 0)
57
+ continue;
58
+ transcripts.set(node.id, threadTranscriptBodies(node.comments.nodes.map((c) => c.body)));
59
+ }
60
+ return transcripts;
61
+ }
@@ -0,0 +1,15 @@
1
+ import { type StateKey } from "../state/rest-cache.mts";
2
+ /**
3
+ * Where a GraphQL BaseBehind result may be cached. The count is a pure function of two commits,
4
+ * so an entry keyed on the live base tip and the head commit never goes stale.
5
+ */
6
+ export interface BaseBehindCacheOptions {
7
+ stateKey: StateKey;
8
+ /** Live base tip from the same tick's `BatchPr`; without it the cache is not read. */
9
+ baseTipOid?: string;
10
+ /** Derived-state slot; a stack layer keeps its trunk compare apart from its base compare. */
11
+ slot?: "base-behind" | "trunk-behind";
12
+ }
13
+ export declare function cachedBaseBehind(cache: BaseBehindCacheOptions | undefined, headOid: string): Promise<number | undefined>;
14
+ /** Stores a compare under the base tip it actually used, which may be newer than BatchPr's. */
15
+ export declare function storeBaseBehind(cache: BaseBehindCacheOptions | undefined, baseTipOid: string | undefined, headOid: string, behindBy: number): Promise<void>;
@@ -0,0 +1,18 @@
1
+ import { loadDerived, storeDerived } from "../state/rest-cache.mjs";
2
+ const COMMIT_OID = /^[0-9a-f]{40}$/;
3
+ export async function cachedBaseBehind(cache, headOid) {
4
+ if (!cache?.baseTipOid || !COMMIT_OID.test(headOid))
5
+ return undefined;
6
+ const entry = await loadDerived(cache.stateKey, cache.slot ?? "base-behind");
7
+ const value = entry?.value;
8
+ if (value?.baseTipOid !== cache.baseTipOid || value.headOid !== headOid)
9
+ return undefined;
10
+ return Number.isSafeInteger(value.behindBy) && value.behindBy >= 0 ? value.behindBy : undefined;
11
+ }
12
+ /** Stores a compare under the base tip it actually used, which may be newer than BatchPr's. */
13
+ export async function storeBaseBehind(cache, baseTipOid, headOid, behindBy) {
14
+ if (!cache || !baseTipOid || !COMMIT_OID.test(headOid))
15
+ return;
16
+ const entry = { baseTipOid, headOid, behindBy };
17
+ await storeDerived(cache.stateKey, cache.slot ?? "base-behind", entry);
18
+ }
@@ -4,6 +4,8 @@ import type { RawPr } from "./batch-raw-types.mts";
4
4
  export declare function parseCheckSuitesComplete(raw: RawPr): boolean;
5
5
  /** True only when the head commit's suite page finished and listed nothing. */
6
6
  export declare function parseHeadCheckSuitesEmpty(raw: RawPr): boolean;
7
- /** Actions suites on the head. Third-party suites with no workflow run are omitted. */
8
- export declare function parseHeadWorkflowSuites(raw: RawPr): WorkflowSuiteSnapshot[];
9
7
  export declare function parseSuiteStartupFailures(raw: RawPr): CheckRun[];
8
+ /** `headWorkflowSuites`, omitted when the head has none. */
9
+ export declare function workflowSuites(raw: RawPr): {
10
+ headWorkflowSuites?: WorkflowSuiteSnapshot[];
11
+ };
@@ -7,7 +7,7 @@ export function parseHeadCheckSuitesEmpty(raw) {
7
7
  return suites?.pageInfo.hasNextPage === false && suites.nodes.length === 0;
8
8
  }
9
9
  /** Actions suites on the head. Third-party suites with no workflow run are omitted. */
10
- export function parseHeadWorkflowSuites(raw) {
10
+ function parseHeadWorkflowSuites(raw) {
11
11
  const nodes = raw.commits.nodes[0]?.commit.checkSuites?.nodes ?? [];
12
12
  return nodes.flatMap((node) => {
13
13
  if (!node.workflowRun)
@@ -43,3 +43,8 @@ export function parseSuiteStartupFailures(raw) {
43
43
  ];
44
44
  });
45
45
  }
46
+ /** `headWorkflowSuites`, omitted when the head has none. */
47
+ export function workflowSuites(raw) {
48
+ const suites = parseHeadWorkflowSuites(raw);
49
+ return suites.length > 0 ? { headWorkflowSuites: suites } : {};
50
+ }
@@ -1,3 +1,4 @@
1
+ import { toCheckAnnotation } from "./check-annotation-shape.mjs";
1
2
  import { normalizeAuthorType } from "../comments/authors.mjs";
2
3
  export function mapAuthorType(typeName, login) {
3
4
  return normalizeAuthorType(typeName, login);
@@ -21,7 +22,9 @@ export function mapCheckRunNode(node) {
21
22
  const event = node.checkSuite?.workflowRun?.event ?? null;
22
23
  const workflowName = node.checkSuite?.workflowRun?.workflow?.name?.trim() || undefined;
23
24
  const workflowId = resolveWorkflowId(node.checkSuite?.workflowRun?.workflow?.databaseId);
24
- const runId = extractRunId(node.detailsUrl);
25
+ const runId = node.checkSuite?.workflowRun?.databaseId != null
26
+ ? String(node.checkSuite.workflowRun.databaseId)
27
+ : extractRunId(node.detailsUrl);
25
28
  const summary = extractCheckRunSummary(node.title, node.summary);
26
29
  const rawCreatedAt = node.checkSuite?.workflowRun?.createdAt ?? node.checkSuite?.createdAt;
27
30
  const rawUpdatedAt = node.checkSuite?.workflowRun?.updatedAt ?? node.checkSuite?.updatedAt;
@@ -45,7 +48,42 @@ export function mapCheckRunNode(node) {
45
48
  ...(completedAtUnix !== undefined && { completedAtUnix }),
46
49
  ...(updatedAtUnix !== undefined && { updatedAtUnix }),
47
50
  ...(summary !== undefined && { summary }),
48
- ...((node.annotations?.nodes.length ?? 0) > 0 && { hasAnnotations: true }),
51
+ ...(((node.annotations?.totalCount ?? 0) > 0 || (node.annotations?.nodes.length ?? 0) > 0) && {
52
+ hasAnnotations: true,
53
+ }),
54
+ ...inlineAnnotationsField(node.id, node.annotations),
55
+ };
56
+ }
57
+ /**
58
+ * The complete annotation list when the first page already holds every annotation with its full
59
+ * selection. A partial page (more than one annotation, or a node without the full fields) yields
60
+ * nothing, so the caller still reads the run's annotations separately.
61
+ */
62
+ function inlineAnnotationsField(checkRunId, annotations) {
63
+ const total = annotations?.totalCount;
64
+ if (total === undefined || total < 1 || annotations.nodes.length !== total)
65
+ return {};
66
+ const raws = annotations.nodes.map(fullRawAnnotation);
67
+ if (raws.some((raw) => raw === null))
68
+ return {};
69
+ return {
70
+ inlineAnnotations: raws.map((raw) => toCheckAnnotation(checkRunId, raw)),
71
+ };
72
+ }
73
+ function fullRawAnnotation(node) {
74
+ if (typeof node.path !== "string" || typeof node.annotationLevel !== "string")
75
+ return null;
76
+ if (node.location === undefined || node.fullDatabaseId === undefined)
77
+ return null;
78
+ return {
79
+ fullDatabaseId: node.fullDatabaseId,
80
+ path: node.path,
81
+ annotationLevel: node.annotationLevel,
82
+ title: node.title ?? null,
83
+ message: node.message,
84
+ rawDetails: node.rawDetails ?? null,
85
+ blobUrl: node.blobUrl ?? null,
86
+ location: node.location,
49
87
  };
50
88
  }
51
89
  function extractCheckRunSummary(title, summary) {
@@ -138,6 +138,7 @@ export function parseRawPr(raw, rawThreadPages, rawCommentNodes, rawReviewNodes,
138
138
  },
139
139
  baseRefName: raw.baseRefName,
140
140
  ...(raw.baseRefOid && { baseRefOid: raw.baseRefOid }),
141
+ ...(raw.baseRef?.target?.oid && { baseTipOid: raw.baseRef.target.oid }),
141
142
  reviewRequests,
142
143
  latestReviews,
143
144
  reviewThreads,
@@ -13,6 +13,7 @@ export interface RawBranchProtectionRule {
13
13
  requiredDeploymentEnvironments?: string[] | null;
14
14
  }
15
15
  import type { RawContextNode } from "./batch-raw-types.mts";
16
+ import type { StackMemberRefs } from "./stack-read.mts";
16
17
  interface RawCheckCommit {
17
18
  oid: string;
18
19
  committedDate?: string;
@@ -60,6 +61,27 @@ interface RawStack {
60
61
  number: number;
61
62
  size: number;
62
63
  baseRefName: string;
64
+ /** Selected only by `BatchPr`: the topology `PollStackTopology` would otherwise read. */
65
+ id?: string;
66
+ entries?: {
67
+ pageInfo: {
68
+ hasNextPage: boolean;
69
+ endCursor: string | null;
70
+ };
71
+ nodes: Array<{
72
+ position: number;
73
+ pullRequest: StackMemberRefs | null;
74
+ } | null>;
75
+ };
76
+ /** Selected only by `BatchPr`: the bottom entry, whose base is normally the trunk. */
77
+ trunkEntry?: {
78
+ nodes: Array<{
79
+ pullRequest: {
80
+ baseRefName: string;
81
+ baseRef: RawBaseRef | null;
82
+ } | null;
83
+ } | null>;
84
+ };
63
85
  }
64
86
  export interface RawRepositoryRule {
65
87
  type: string;
@@ -87,6 +109,10 @@ export interface RawBaseRef {
87
109
  };
88
110
  nodes: RawRepositoryRule[];
89
111
  } | null;
112
+ /** Live base branch tip; selected only by `BatchPr`. */
113
+ target?: {
114
+ oid?: string;
115
+ } | null;
90
116
  }
91
117
  export interface RawPrMergeFields {
92
118
  isInMergeQueue?: boolean;
@@ -1,5 +1,6 @@
1
1
  import type { CommentAuthorAssociation } from "../types/github.mts";
2
2
  import type { RawPrMergeFields } from "./batch-raw-rules.mts";
3
+ import type { RawCheckAnnotation } from "./check-annotation-shape.mts";
3
4
  export interface RawBatchResponse {
4
5
  viewer?: {
5
6
  login: string | null;
@@ -140,7 +141,7 @@ interface RawAuthor {
140
141
  }
141
142
  export interface RawThreadComment {
142
143
  id: string;
143
- isMinimized: boolean;
144
+ isMinimized?: boolean;
144
145
  url: string;
145
146
  authorAssociation?: CommentAuthorAssociation;
146
147
  viewerDidAuthor?: boolean;
@@ -157,10 +158,10 @@ export interface RawThreadComment {
157
158
  }
158
159
  export interface RawThread {
159
160
  id: string;
160
- isResolved: boolean;
161
- isOutdated: boolean;
162
- viewerCanReply: boolean;
163
- viewerCanResolve: boolean;
161
+ isResolved?: boolean;
162
+ isOutdated?: boolean;
163
+ viewerCanReply?: boolean;
164
+ viewerCanResolve?: boolean;
164
165
  path?: string | null;
165
166
  line?: number | null;
166
167
  startLine?: number | null;
@@ -228,7 +229,7 @@ export type RawContextNode = {
228
229
  summary: string | null;
229
230
  annotations?: {
230
231
  totalCount?: number;
231
- nodes: Array<{
232
+ nodes: Array<Partial<RawCheckAnnotation> & {
232
233
  message: string;
233
234
  }>;
234
235
  };
@@ -236,6 +237,7 @@ export type RawContextNode = {
236
237
  createdAt?: string;
237
238
  updatedAt?: string;
238
239
  workflowRun: {
240
+ databaseId?: number;
239
241
  event: string;
240
242
  createdAt?: string;
241
243
  updatedAt?: string;
@@ -0,0 +1,28 @@
1
+ import type { RawPrMergeFields } from "./batch-raw-rules.mts";
2
+ import type { StackMemberRefs, StackRead } from "./stack-read.mts";
3
+ /** Required contexts and live tip of a native stack's trunk, read in `BatchPr`. */
4
+ export interface TrunkRules {
5
+ refName: string;
6
+ contexts: string[];
7
+ tipOid?: string;
8
+ }
9
+ /**
10
+ * Stack reads that `BatchPr`'s first page already answered. Each part is present
11
+ * only when complete; a caller falls back to the standalone query otherwise.
12
+ */
13
+ export interface BatchStackEvidence {
14
+ topology?: StackRead<StackMemberRefs>;
15
+ trunk?: TrunkRules;
16
+ }
17
+ /** Validate the embedded stack selections of one `BatchPr` first page. */
18
+ export declare function stackEvidenceFromRaw(raw: RawPrMergeFields & {
19
+ number: number;
20
+ baseRefName: string;
21
+ }, data: {
22
+ viewer?: {
23
+ login: string | null;
24
+ } | null;
25
+ repository?: {
26
+ viewerCanAdminister?: boolean;
27
+ } | null;
28
+ }): BatchStackEvidence | undefined;
@@ -0,0 +1,62 @@
1
+ import { parseBranchRules } from "./batch-parsers-rules.mjs";
2
+ import { orderedStackMembers } from "./stack-order.mjs";
3
+ /** Validate the embedded stack selections of one `BatchPr` first page. */
4
+ export function stackEvidenceFromRaw(raw, data) {
5
+ const stack = raw.stack;
6
+ if (!stack)
7
+ return undefined;
8
+ const viewer = {
9
+ login: data.viewer?.login ?? null,
10
+ canAdminister: data.repository?.viewerCanAdminister,
11
+ };
12
+ const topology = embeddedTopology(stack, raw.number, viewer);
13
+ const trunk = embeddedTrunkRules(stack, raw);
14
+ if (!topology && !trunk)
15
+ return undefined;
16
+ return { ...(topology && { topology }), ...(trunk && { trunk }) };
17
+ }
18
+ function embeddedTopology(stack, anchor, viewer) {
19
+ const entries = stack.entries;
20
+ // Past one page, PollStackTopology reads the full membership instead.
21
+ if (!entries || entries.pageInfo.hasNextPage)
22
+ return undefined;
23
+ const members = [];
24
+ for (const node of entries.nodes) {
25
+ if (!node?.pullRequest)
26
+ return undefined;
27
+ members.push({ position: node.position, pullRequest: node.pullRequest });
28
+ }
29
+ try {
30
+ return {
31
+ stackNumber: stack.number,
32
+ stackSize: stack.size,
33
+ viewerLogin: viewer.login,
34
+ ...(viewer.canAdminister !== undefined && { viewerCanAdminister: viewer.canAdminister }),
35
+ ordered: orderedStackMembers(members, anchor, stack.size),
36
+ };
37
+ }
38
+ catch {
39
+ return undefined;
40
+ }
41
+ }
42
+ /**
43
+ * The trunk's rules come from whichever ref in this page is the trunk itself:
44
+ * this PR's own base for the bottom layer, otherwise the first entry's base.
45
+ */
46
+ function embeddedTrunkRules(stack, raw) {
47
+ const trunk = stack.baseRefName;
48
+ if (raw.baseRefName === trunk && raw.baseRef)
49
+ return trunkRules(trunk, raw.baseRef);
50
+ const bottom = stack.trunkEntry?.nodes[0]?.pullRequest;
51
+ if (bottom?.baseRefName !== trunk || !bottom.baseRef)
52
+ return undefined;
53
+ return trunkRules(trunk, bottom.baseRef);
54
+ }
55
+ function trunkRules(refName, ref) {
56
+ const tipOid = ref.target?.oid;
57
+ return {
58
+ refName,
59
+ contexts: parseBranchRules(ref).requiredStatusCheckContexts,
60
+ ...(tipOid && { tipOid }),
61
+ };
62
+ }
@@ -3,7 +3,8 @@ import type { WorkflowSuiteSnapshot } from "../checks/unreported-required.mts";
3
3
  import type { RawSummaryPr } from "./poll-summary-raw.mts";
4
4
  import type { BatchPrData } from "../types.mts";
5
5
  import { type PrFingerprint } from "./fingerprint.mts";
6
- interface BatchResult {
6
+ import { type BatchStackEvidence } from "./batch-stack-evidence.mts";
7
+ export interface BatchResult {
7
8
  data: BatchPrData;
8
9
  fingerprint?: PrFingerprint;
9
10
  rateLimit?: RateLimitInfo;
@@ -15,7 +16,15 @@ interface BatchResult {
15
16
  headWorkflowSuites?: WorkflowSuiteSnapshot[];
16
17
  /** Internal READY-receipt evidence from the same request, if complete. */
17
18
  receiptSummary?: RawSummaryPr;
19
+ /** Native-stack topology and trunk rules from the same first page, if complete. */
20
+ stackEvidence?: BatchStackEvidence;
18
21
  }
22
+ /** A fingerprint hit, plus the live base tip and stack evidence from the same first page. */
23
+ type ReusedBatch<T> = {
24
+ reused: T;
25
+ baseTipOid?: string;
26
+ stackEvidence?: BatchStackEvidence;
27
+ };
19
28
  interface FetchPrBatchOptions {
20
29
  /**
21
30
  * When false (default), the first-page approvedReviews are returned but
@@ -27,9 +36,13 @@ interface FetchPrBatchOptions {
27
36
  */
28
37
  paginateApprovedReviews?: boolean;
29
38
  includeReceiptSummary?: boolean;
39
+ terminalShortCircuit?: boolean;
30
40
  }
31
41
  /**
32
- * Fetch all PR data needed for a `shepherd check` in one (or a few, if paginating) GraphQL requests.
42
+ * With `reuse`, the GraphQL first page's fingerprint is offered to it before any supplement
43
+ * runs; a non-null answer is returned as `{ reused }` instead of a snapshot. REST snapshots never
44
+ * call `reuse`: they have their own conditional-read reuse.
33
45
  */
46
+ export declare function fetchPrBatch<T>(pr: number, repo: RepoInfo, opts: FetchPrBatchOptions, reuse: (fingerprint: PrFingerprint) => Promise<T | null>): Promise<BatchResult | ReusedBatch<T>>;
34
47
  export declare function fetchPrBatch(pr: number, repo: RepoInfo, opts?: FetchPrBatchOptions): Promise<BatchResult>;
35
48
  export {};