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
@@ -3,11 +3,13 @@ import { appendEntry, nextEntry } from "../log/log-file.mjs";
3
3
  import { formatRequestEntry, formatResponseEntry } from "../log/session.mjs";
4
4
  import { loadEtagEntry, storeEtagEntry } from "../state/rest-cache.mjs";
5
5
  import { GitHubRequestError } from "./errors.mjs";
6
+ import { githubFetch } from "./github-fetch.mjs";
6
7
  import { makeAuthHeaders } from "./http-auth.mjs";
7
8
  import { requestWithTokenRetry } from "./http-request.mjs";
8
9
  import { parseRateLimit, parseRetryAfter, redactToken, sanitizeBody, } from "./http-utils.mjs";
9
10
  import { recordApiTelemetry } from "./api-telemetry.mjs";
10
11
  import { recordIntermediateResponse } from "./http-intermediate.mjs";
12
+ import { isRestSessionRefusalResponse } from "./rest-session-refusal.mjs";
11
13
  const BASE_URL = "https://api.github.com";
12
14
  const SAFE_GITHUB_REST_PATH = /^\/[A-Za-z0-9._~!$&'()*+,;=:@%/?-]*$/;
13
15
  function githubApiUrl(path) {
@@ -34,13 +36,15 @@ export async function restWithRateLimit(method, path, body, opts) {
34
36
  appendEntry(formatRequestEntry({ n, kind: "REST", method, url, body }));
35
37
  const t0 = performance.now();
36
38
  let authSource = "unknown";
37
- const cached = opts?.conditional
39
+ let credentialFingerprint;
40
+ const cached = opts?.conditional && !opts.conditional.revalidate
38
41
  ? await loadEtagEntry(opts.conditional.key, opts.conditional.name)
39
42
  : null;
40
43
  const { res, attempt, retryT0 } = await requestWithTokenRetry(async () => {
41
44
  const auth = await makeAuthHeaders(cached ? { "If-None-Match": cached.etag } : undefined);
42
45
  authSource = auth.source;
43
- return fetch(url, {
46
+ credentialFingerprint = auth.fingerprint;
47
+ return githubFetch(url, {
44
48
  method,
45
49
  headers: auth.headers,
46
50
  body: body !== undefined ? JSON.stringify(body) : undefined,
@@ -53,7 +57,8 @@ export async function restWithRateLimit(method, path, body, opts) {
53
57
  response,
54
58
  durationMs,
55
59
  authSource,
56
- }));
60
+ credentialFingerprint,
61
+ }), method === "GET" || method === "HEAD");
57
62
  const durationMs = Math.round(performance.now() - retryT0);
58
63
  const rateLimit = parseRateLimit(res.headers) ?? undefined;
59
64
  const retryAfterSeconds = parseRetryAfter(res.headers);
@@ -73,11 +78,25 @@ export async function restWithRateLimit(method, path, body, opts) {
73
78
  rateLimit,
74
79
  retryAfterSeconds,
75
80
  }));
76
- recordApiTelemetry({ kind: "REST", method, authSource, rateLimit });
77
- return { data: cached.body, rateLimit };
81
+ // A 304 consumes no quota: telemetry counts it as notModified, not as a request.
82
+ recordApiTelemetry({
83
+ kind: "REST",
84
+ method,
85
+ authSource,
86
+ credentialFingerprint,
87
+ rateLimit,
88
+ notModified: true,
89
+ });
90
+ return {
91
+ data: cached.body,
92
+ rateLimit,
93
+ status: res.status,
94
+ etag: cached.etag,
95
+ link: res.headers.get("link") ?? cached.link,
96
+ };
78
97
  }
79
98
  const ct = res.headers.get("content-type") ?? "";
80
- if (!res.ok) {
99
+ if (!res.ok && !opts?.acceptStatuses?.includes(res.status)) {
81
100
  const text = await res.text();
82
101
  appendEntry(formatResponseEntry({
83
102
  n,
@@ -92,17 +111,63 @@ export async function restWithRateLimit(method, path, body, opts) {
92
111
  rateLimit,
93
112
  retryAfterSeconds,
94
113
  }));
95
- recordApiTelemetry({ kind: "REST", method, authSource, rateLimit });
96
- throw new GitHubRequestError(`GitHub REST ${method} ${path} failed: ${res.status} ${sanitizeBody(text)}`, {
114
+ recordApiTelemetry({
115
+ kind: "REST",
116
+ method,
117
+ authSource,
118
+ credentialFingerprint,
119
+ rateLimit,
120
+ });
121
+ const responseMessage = isRestSessionRefusalResponse(res.status, text)
122
+ ? redactToken(text)
123
+ : sanitizeBody(text);
124
+ throw new GitHubRequestError(`GitHub REST ${method} ${path} failed: ${res.status} ${responseMessage}`, {
97
125
  status: res.status,
98
126
  rateLimit,
99
127
  retryAfterSeconds,
100
128
  authSource,
101
- responseMessage: sanitizeBody(text),
129
+ responseMessage,
102
130
  });
103
131
  }
104
132
  if (ct.includes("application/json")) {
105
- const json = (await res.json());
133
+ let json;
134
+ try {
135
+ json = (await res.json());
136
+ }
137
+ catch (err) {
138
+ const detail = err instanceof Error ? `: ${err.message}` : "";
139
+ appendEntry(formatResponseEntry({
140
+ n,
141
+ kind: "REST",
142
+ method,
143
+ url,
144
+ status: res.status,
145
+ durationMs,
146
+ contentType: ct,
147
+ textBody: `Invalid JSON response${detail}`,
148
+ attempt: attempt > 1 ? attempt : undefined,
149
+ authSource,
150
+ rateLimit,
151
+ retryAfterSeconds,
152
+ }));
153
+ recordApiTelemetry({
154
+ kind: "REST",
155
+ method,
156
+ authSource,
157
+ credentialFingerprint,
158
+ rateLimit,
159
+ });
160
+ if (!res.ok) {
161
+ throw new GitHubRequestError(`GitHub REST ${method} ${path} failed: ${res.status} Invalid JSON response${detail}`, {
162
+ status: res.status,
163
+ rateLimit,
164
+ retryAfterSeconds,
165
+ authSource,
166
+ responseMessage: `Invalid JSON response${detail}`,
167
+ });
168
+ }
169
+ throw err;
170
+ }
106
171
  appendEntry(formatResponseEntry({
107
172
  n,
108
173
  kind: "REST",
@@ -117,18 +182,32 @@ export async function restWithRateLimit(method, path, body, opts) {
117
182
  rateLimit,
118
183
  retryAfterSeconds,
119
184
  }));
120
- recordApiTelemetry({ kind: "REST", method, authSource, rateLimit });
121
- if (opts?.conditional) {
122
- const etag = res.headers.get("etag");
123
- if (etag) {
124
- await storeEtagEntry(opts.conditional.key, opts.conditional.name, {
125
- etag,
126
- body: json,
127
- ...(opts.conditional.headSha !== undefined && { headSha: opts.conditional.headSha }),
128
- });
129
- }
185
+ recordApiTelemetry({
186
+ kind: "REST",
187
+ method,
188
+ authSource,
189
+ credentialFingerprint,
190
+ rateLimit,
191
+ });
192
+ const etag = opts?.conditional ? (res.headers.get("etag") ?? undefined) : undefined;
193
+ const link = res.headers.get("link") ?? undefined;
194
+ if (opts?.conditional && etag && (opts.conditional.shouldStore?.(json) ?? true)) {
195
+ await storeEtagEntry(opts.conditional.key, opts.conditional.name, {
196
+ etag,
197
+ body: json,
198
+ ...(opts.conditional.headSha !== undefined && {
199
+ headSha: opts.conditional.headSha,
200
+ }),
201
+ ...(link !== undefined && { link }),
202
+ });
130
203
  }
131
- return { data: json, rateLimit };
204
+ return {
205
+ data: json,
206
+ rateLimit,
207
+ status: res.status,
208
+ ...(etag !== undefined && { etag }),
209
+ link,
210
+ };
132
211
  }
133
212
  appendEntry(formatResponseEntry({
134
213
  n,
@@ -143,6 +222,12 @@ export async function restWithRateLimit(method, path, body, opts) {
143
222
  rateLimit,
144
223
  retryAfterSeconds,
145
224
  }));
146
- recordApiTelemetry({ kind: "REST", method, authSource, rateLimit });
147
- return { data: undefined, rateLimit };
225
+ recordApiTelemetry({
226
+ kind: "REST",
227
+ method,
228
+ authSource,
229
+ credentialFingerprint,
230
+ rateLimit,
231
+ });
232
+ return { data: undefined, rateLimit, status: res.status };
148
233
  }
@@ -0,0 +1,22 @@
1
+ import type { SeenMarker } from "../state/seen-comments.mts";
2
+ import type { RepoInfo } from "./client.mts";
3
+ import type { FixAttemptsState } from "../state/fix-attempts.mts";
4
+ type IdentityKind = "check" | "review" | "comment" | "pull" | "thread";
5
+ interface RestIdentity {
6
+ repo: RepoInfo;
7
+ pr: number;
8
+ numericId: string;
9
+ kind: IdentityKind;
10
+ graphqlThreadId?: string;
11
+ }
12
+ export declare function restThreadId(rootCommentId: string | number): string;
13
+ export declare function recordRestIdentity(repo: RepoInfo, pr: number, nodeId: string, numericId: string | number, kind: IdentityKind): Promise<void>;
14
+ export declare function resolveRestIdentity(id: string, kind?: IdentityKind): Promise<RestIdentity>;
15
+ export declare function recordThreadIdentity(repo: RepoInfo, pr: number, graphqlThreadId: string, rootCommentId: string | number): Promise<void>;
16
+ export declare function resolveRestThreadRoot(repo: RepoInfo, pr: number, id: string): Promise<string>;
17
+ export declare function resolveGraphqlThreadId(id: string): Promise<string>;
18
+ /** Preserve previously displayed feedback when the adapter changes its thread handle. */
19
+ export declare function aliasThreadSeenMarkers(repo: RepoInfo, pr: number, ids: readonly string[], seen: Map<string, SeenMarker>): Promise<void>;
20
+ /** Keep fix-attempt counts tied to the same feedback when its transport handle changes. */
21
+ export declare function aliasThreadFixAttempts(repo: RepoInfo, pr: number, ids: readonly string[], state: FixAttemptsState | null): Promise<void>;
22
+ export {};
@@ -0,0 +1,134 @@
1
+ import { createHash } from "node:crypto";
2
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+ import { resolveStateBase } from "../state/base.mjs";
5
+ function identityPath(id) {
6
+ const hash = createHash("sha256").update(id).digest("hex");
7
+ return join(resolveStateBase(), "github-identities", `${hash}.json`);
8
+ }
9
+ function numeric(value) {
10
+ const result = String(value);
11
+ if (!/^[1-9][0-9]*$/.test(result))
12
+ throw new Error(`Invalid GitHub numeric ID: ${result}`);
13
+ return result;
14
+ }
15
+ export function restThreadId(rootCommentId) {
16
+ return `rest-thread-${numeric(rootCommentId)}`;
17
+ }
18
+ async function storeIdentity(id, entry) {
19
+ await mkdir(join(resolveStateBase(), "github-identities"), { recursive: true });
20
+ await writeFile(identityPath(id), JSON.stringify({ id, ...entry }));
21
+ }
22
+ export async function recordRestIdentity(repo, pr, nodeId, numericId, kind) {
23
+ if (!nodeId)
24
+ return;
25
+ await storeIdentity(nodeId, { repo, pr, numericId: numeric(numericId), kind });
26
+ }
27
+ export async function resolveRestIdentity(id, kind) {
28
+ let entry;
29
+ try {
30
+ entry = JSON.parse(await readFile(identityPath(id), "utf8"));
31
+ }
32
+ catch {
33
+ throw new Error(`No recorded REST identity for ${id}; refresh this PR with REST transport first`);
34
+ }
35
+ if (entry.id !== id ||
36
+ !entry.repo?.owner ||
37
+ !entry.repo.name ||
38
+ !Number.isSafeInteger(entry.pr) ||
39
+ entry.pr < (entry.kind === "check" ? 0 : 1) ||
40
+ !/^[1-9][0-9]*$/.test(entry.numericId) ||
41
+ (kind !== undefined && entry.kind !== kind))
42
+ throw new Error(`Invalid REST identity for ${id}`);
43
+ return entry;
44
+ }
45
+ export async function recordThreadIdentity(repo, pr, graphqlThreadId, rootCommentId) {
46
+ const numericId = numeric(rootCommentId);
47
+ const canonical = restThreadId(numericId);
48
+ let existing;
49
+ try {
50
+ existing = await resolveRestIdentity(canonical, "thread");
51
+ }
52
+ catch { }
53
+ const genuineId = graphqlThreadId.startsWith("rest-thread-")
54
+ ? existing?.graphqlThreadId
55
+ : graphqlThreadId;
56
+ const entry = {
57
+ repo,
58
+ pr,
59
+ numericId,
60
+ kind: "thread",
61
+ ...(genuineId && { graphqlThreadId: genuineId }),
62
+ };
63
+ if (genuineId)
64
+ await storeIdentity(genuineId, entry);
65
+ await storeIdentity(canonical, entry);
66
+ }
67
+ function samePr(entry, repo, pr) {
68
+ return (entry.pr === pr &&
69
+ entry.repo.owner.toLowerCase() === repo.owner.toLowerCase() &&
70
+ entry.repo.name.toLowerCase() === repo.name.toLowerCase());
71
+ }
72
+ export async function resolveRestThreadRoot(repo, pr, id) {
73
+ const match = /^rest-thread-([1-9][0-9]*)$/.exec(id);
74
+ if (match)
75
+ return match[1];
76
+ const entry = await resolveRestIdentity(id, "thread");
77
+ if (!samePr(entry, repo, pr))
78
+ throw new Error(`Thread ${id} does not belong to ${repo.owner}/${repo.name}#${pr}`);
79
+ return entry.numericId;
80
+ }
81
+ export async function resolveGraphqlThreadId(id) {
82
+ if (!id.startsWith("rest-thread-"))
83
+ return id;
84
+ const entry = await resolveRestIdentity(id, "thread");
85
+ if (!entry.graphqlThreadId)
86
+ throw new Error(`No GraphQL thread identity for ${id}; use --transport rest`);
87
+ return entry.graphqlThreadId;
88
+ }
89
+ /** Preserve previously displayed feedback when the adapter changes its thread handle. */
90
+ export async function aliasThreadSeenMarkers(repo, pr, ids, seen) {
91
+ await Promise.all(ids.map(async (id) => {
92
+ try {
93
+ const entry = await resolveRestIdentity(id, "thread");
94
+ if (!samePr(entry, repo, pr))
95
+ return;
96
+ const aliases = [id, restThreadId(entry.numericId), entry.graphqlThreadId].filter((alias) => alias !== undefined);
97
+ const marker = aliases
98
+ .map((alias) => seen.get(alias))
99
+ .find((candidate) => candidate !== undefined);
100
+ if (marker)
101
+ for (const alias of aliases)
102
+ if (!seen.has(alias))
103
+ seen.set(alias, marker);
104
+ }
105
+ catch {
106
+ // Old snapshots may have no association. Surface again rather than hide an unseen item.
107
+ }
108
+ }));
109
+ }
110
+ /** Keep fix-attempt counts tied to the same feedback when its transport handle changes. */
111
+ export async function aliasThreadFixAttempts(repo, pr, ids, state) {
112
+ if (!state?.threadBodyHashes)
113
+ return;
114
+ await Promise.all(ids.map(async (id) => {
115
+ try {
116
+ const entry = await resolveRestIdentity(id, "thread");
117
+ if (!samePr(entry, repo, pr))
118
+ return;
119
+ const aliases = [id, restThreadId(entry.numericId), entry.graphqlThreadId].filter((alias) => alias !== undefined);
120
+ const source = aliases.find((alias) => state.threadBodyHashes?.[alias] !== undefined);
121
+ if (!source)
122
+ return;
123
+ const bodyHash = state.threadBodyHashes[source];
124
+ const attempts = Math.max(...aliases
125
+ .filter((alias) => state.threadBodyHashes?.[alias] === bodyHash)
126
+ .map((alias) => state.threadAttempts[alias] ?? 0));
127
+ state.threadBodyHashes[id] = bodyHash;
128
+ state.threadAttempts[id] = attempts;
129
+ }
130
+ catch {
131
+ /* Unmapped older handles retain their ordinary first-look behavior. */
132
+ }
133
+ }));
134
+ }
@@ -0,0 +1,14 @@
1
+ /** Native stack and exact lower prefix observed when the merge command was generated. */
2
+ export interface RestMergeStackGuard {
3
+ number: number;
4
+ baseRefName: string;
5
+ prefix: Array<{
6
+ pr: number;
7
+ headRefName: string;
8
+ headRefOid: string;
9
+ baseRefName: string;
10
+ }>;
11
+ }
12
+ export declare function validateRestMergeStackGuard(value: unknown): asserts value is RestMergeStackGuard;
13
+ /** Stable equality ignores object key order and unknown caller fields. */
14
+ export declare function restMergeStackGuardKey(guard: RestMergeStackGuard | undefined): string;
@@ -0,0 +1,41 @@
1
+ export function validateRestMergeStackGuard(value) {
2
+ const guard = value;
3
+ if (!guard ||
4
+ typeof guard !== "object" ||
5
+ Array.isArray(guard) ||
6
+ !Number.isSafeInteger(guard.number) ||
7
+ guard.number < 1 ||
8
+ typeof guard.baseRefName !== "string" ||
9
+ !guard.baseRefName ||
10
+ !Array.isArray(guard.prefix) ||
11
+ guard.prefix.length === 0 ||
12
+ guard.prefix.length > 100_000 ||
13
+ guard.prefix.some((member) => !member ||
14
+ typeof member !== "object" ||
15
+ Array.isArray(member) ||
16
+ !Number.isSafeInteger(member.pr) ||
17
+ member.pr < 1 ||
18
+ typeof member.headRefName !== "string" ||
19
+ !member.headRefName ||
20
+ typeof member.baseRefName !== "string" ||
21
+ !member.baseRefName ||
22
+ typeof member.headRefOid !== "string" ||
23
+ !/^[0-9a-f]{40}$/.test(member.headRefOid)) ||
24
+ new Set(guard.prefix.map((member) => member.pr)).size !== guard.prefix.length)
25
+ throw new Error("expectedStack must bind a positive stack number, trunk, and unique ordered PR prefix with full head SHAs and branch refs");
26
+ }
27
+ /** Stable equality ignores object key order and unknown caller fields. */
28
+ export function restMergeStackGuardKey(guard) {
29
+ return JSON.stringify(guard
30
+ ? [
31
+ guard.number,
32
+ guard.baseRefName,
33
+ guard.prefix.map((member) => [
34
+ member.pr,
35
+ member.headRefName,
36
+ member.headRefOid,
37
+ member.baseRefName,
38
+ ]),
39
+ ]
40
+ : null);
41
+ }
@@ -0,0 +1,29 @@
1
+ import type { RepoInfo } from "./client.mts";
2
+ import type { MergeMethod } from "../config/merge-method.mts";
3
+ import type { RestMergeStackGuard } from "./rest-merge-stack-guard.mts";
4
+ export type { RestMergeStackGuard } from "./rest-merge-stack-guard.mts";
5
+ export type RestMergeAction = "direct_merge" | "merge_queue" | "default";
6
+ export interface RestMergeOptions {
7
+ requireSha: string;
8
+ mergeAction: RestMergeAction;
9
+ mergeMethod?: MergeMethod;
10
+ expectedStack?: RestMergeStackGuard;
11
+ }
12
+ export interface RestMergeResponse {
13
+ status: "pending" | "enqueued" | "merged" | "failed";
14
+ details: {
15
+ message?: string;
16
+ uuid?: string;
17
+ expected_head_sha?: string;
18
+ merge_action?: RestMergeAction;
19
+ merge_method?: MergeMethod;
20
+ bypass_rules?: boolean;
21
+ sha?: string;
22
+ [key: string]: unknown;
23
+ };
24
+ }
25
+ /** This named mutation is never retried after a network failure or server error. */
26
+ export declare function requestRestMerge(repo: RepoInfo, pr: number, options: RestMergeOptions): Promise<RestMergeResponse>;
27
+ export declare function getRestMerge(repo: RepoInfo, pr: number, uuid: string): Promise<RestMergeResponse>;
28
+ /** Conflict requests must match the caller's guarded options before their UUID can be adopted. */
29
+ export declare function matchesRestMergeRequest(result: RestMergeResponse, options: RestMergeOptions): boolean;
@@ -0,0 +1,56 @@
1
+ import { restWithRateLimit } from "./rest-http.mjs";
2
+ import { restRepoPath } from "./rest-reader-core.mjs";
3
+ import { GitHubRequestError } from "./errors.mjs";
4
+ import { sanitizeBody } from "./http-utils.mjs";
5
+ function parseRestMergeResponse(raw) {
6
+ if (raw === null || typeof raw !== "object")
7
+ throw new Error("Invalid asynchronous merge response");
8
+ const value = raw;
9
+ if (!["pending", "enqueued", "merged", "failed"].includes(String(value["status"])) ||
10
+ value["details"] === null ||
11
+ typeof value["details"] !== "object" ||
12
+ Array.isArray(value["details"])) {
13
+ throw new Error("Invalid asynchronous merge response");
14
+ }
15
+ return value;
16
+ }
17
+ /** This named mutation is never retried after a network failure or server error. */
18
+ export async function requestRestMerge(repo, pr, options) {
19
+ const { data, status, rateLimit } = await restWithRateLimit("PUT", `${restRepoPath(repo)}/pulls/${pr}/merge-async`, {
20
+ sha: options.requireSha,
21
+ merge_action: options.mergeAction,
22
+ bypass_rules: false,
23
+ ...(options.mergeAction === "direct_merge" && options.mergeMethod
24
+ ? { merge_method: options.mergeMethod }
25
+ : {}),
26
+ }, { acceptStatuses: [400, 409] });
27
+ try {
28
+ return parseRestMergeResponse(data);
29
+ }
30
+ catch (error) {
31
+ // Accepted 400/409 responses may contain an ordinary API error envelope,
32
+ // rather than merge state. Retain their definite refusal for intent recovery.
33
+ if (status !== undefined && status >= 400 && status < 500) {
34
+ const responseMessage = sanitizeBody(JSON.stringify(data) ?? "");
35
+ throw new GitHubRequestError(`Invalid asynchronous merge response: ${status} ${responseMessage}`, { status, rateLimit, responseMessage });
36
+ }
37
+ // A malformed acknowledgement may follow a successful submission.
38
+ throw error;
39
+ }
40
+ }
41
+ export async function getRestMerge(repo, pr, uuid) {
42
+ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(uuid))
43
+ throw new Error("Invalid asynchronous merge UUID");
44
+ const { data } = await restWithRateLimit("GET", `${restRepoPath(repo)}/pulls/${pr}/merge-async/${uuid}`);
45
+ return parseRestMergeResponse(data);
46
+ }
47
+ /** Conflict requests must match the caller's guarded options before their UUID can be adopted. */
48
+ export function matchesRestMergeRequest(result, options) {
49
+ const details = result.details;
50
+ return (details.expected_head_sha === options.requireSha &&
51
+ details.merge_action === options.mergeAction &&
52
+ details.bypass_rules === false &&
53
+ (options.mergeMethod === undefined || details.merge_method === options.mergeMethod) &&
54
+ typeof details.uuid === "string" &&
55
+ /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(details.uuid));
56
+ }
@@ -0,0 +1,9 @@
1
+ import type { MergedBasePullRequest } from "../types.mts";
2
+ export interface MergedBaseLookupInput {
3
+ owner: string;
4
+ repo: string;
5
+ baseRefName: string;
6
+ baseRefOid: string;
7
+ }
8
+ /** A reused branch name or a fork is insufficient evidence of a merged parent. */
9
+ export declare function readRestMergedBasePullRequests(input: MergedBaseLookupInput): Promise<MergedBasePullRequest[]>;
@@ -0,0 +1,48 @@
1
+ import { readRestPages, restRepoPath, restObject, restString, restNumber, malformedRest, } from "./rest-reader-core.mjs";
2
+ /** A reused branch name or a fork is insufficient evidence of a merged parent. */
3
+ export async function readRestMergedBasePullRequests(input) {
4
+ const repo = { owner: input.owner, name: input.repo };
5
+ const head = encodeURIComponent(`${input.owner}:${input.baseRefName}`);
6
+ const pulls = await readRestPages(`${restRepoPath(repo)}/pulls?state=closed&head=${head}&sort=updated&direction=desc`);
7
+ const candidates = [];
8
+ const ids = new Set();
9
+ for (const value of pulls.nodes) {
10
+ const pull = restObject(value, "closed pull request");
11
+ const number = restNumber(pull.number, "closed pull number");
12
+ if (number === 0 || ids.has(number))
13
+ malformedRest("closed pull number missing or repeated");
14
+ ids.add(number);
15
+ const state = restString(pull.state, "closed pull state");
16
+ if (!["open", "closed"].includes(state))
17
+ malformedRest("closed pull state");
18
+ if (pull.merged_at !== null && typeof pull.merged_at !== "string")
19
+ malformedRest("closed pull merged_at");
20
+ if (state !== "closed" || pull.merged_at === null)
21
+ continue;
22
+ if (!Number.isFinite(Date.parse(pull.merged_at)))
23
+ malformedRest("closed pull merged_at");
24
+ const source = restObject(pull.head, "closed pull head");
25
+ const headRefName = restString(source.ref, "closed pull head ref");
26
+ const headRefOid = restString(source.sha, "closed pull head SHA");
27
+ if (headRefName !== input.baseRefName || headRefOid !== input.baseRefOid)
28
+ continue;
29
+ if (source.repo === null)
30
+ continue;
31
+ const headRepository = restObject(source.repo, "closed pull head repository");
32
+ const nameWithOwner = restString(headRepository.full_name, "closed pull head repository name");
33
+ if (nameWithOwner.toLowerCase() !== `${input.owner}/${input.repo}`.toLowerCase())
34
+ continue;
35
+ const base = restObject(pull.base, "closed pull base");
36
+ candidates.push({
37
+ number,
38
+ url: restString(pull.html_url, "closed pull URL"),
39
+ state: "MERGED",
40
+ headRefName,
41
+ headRefOid,
42
+ baseRefName: restString(base.ref, "closed pull base ref"),
43
+ mergedAt: pull.merged_at,
44
+ headRepository: { nameWithOwner },
45
+ });
46
+ }
47
+ return candidates;
48
+ }
@@ -0,0 +1,52 @@
1
+ import type { RepoInfo } from "./client.mts";
2
+ export interface RestPull {
3
+ id: number;
4
+ node_id: string;
5
+ number: number;
6
+ title: string;
7
+ body: string | null;
8
+ html_url: string;
9
+ state: string;
10
+ merged_at: string | null;
11
+ draft: boolean;
12
+ mergeable: boolean | null;
13
+ mergeable_state: string;
14
+ updated_at: string;
15
+ user: {
16
+ login: string;
17
+ type?: string;
18
+ } | null;
19
+ head: {
20
+ ref: string;
21
+ sha: string;
22
+ repo: {
23
+ full_name: string;
24
+ } | null;
25
+ };
26
+ base: {
27
+ ref: string;
28
+ sha: string;
29
+ };
30
+ requested_reviewers: Array<{
31
+ login: string;
32
+ }>;
33
+ requested_teams: Array<{
34
+ name: string;
35
+ }>;
36
+ auto_merge?: {
37
+ enabled_by?: {
38
+ login: string;
39
+ };
40
+ merge_method: string;
41
+ } | null;
42
+ }
43
+ export declare function readRestPull(pr: number, repo: RepoInfo): Promise<RestPull>;
44
+ export declare function restPullRefs(pull: RestPull): {
45
+ number: number;
46
+ state: string;
47
+ headRefName: string;
48
+ headRefOid: string;
49
+ baseRefName: string;
50
+ baseRefOid: string;
51
+ };
52
+ export declare function restPullRevision(pull: RestPull): string;
@@ -0,0 +1,60 @@
1
+ import { readRest as rest } from "./rest-reader-core.mjs";
2
+ import { restRepoPath, restObject, restString, restNumber, restBoolean, restArray, malformedRest, } from "./rest-reader-core.mjs";
3
+ import { recordRestIdentity } from "./rest-identities.mjs";
4
+ export async function readRestPull(pr, repo) {
5
+ const value = await rest("GET", `${restRepoPath(repo)}/pulls/${pr}`);
6
+ const data = restObject(value, "pull request");
7
+ const head = restObject(data.head, "pull request head");
8
+ const base = restObject(data.base, "pull request base");
9
+ restNumber(data.id, "pull request id");
10
+ restString(data.node_id, "pull request node_id");
11
+ if (restNumber(data.number, "pull request number") !== pr)
12
+ throw new Error("REST pull request identity changed");
13
+ restString(data.title, "pull request title");
14
+ restString(data.html_url, "pull request html_url");
15
+ if (data.body !== null && typeof data.body !== "string")
16
+ malformedRest("pull request body");
17
+ if (data.merged_at !== null && typeof data.merged_at !== "string")
18
+ malformedRest("pull request merged_at");
19
+ restArray(data.requested_reviewers, "requested reviewers");
20
+ restArray(data.requested_teams, "requested teams");
21
+ restString(data.state, "pull request state");
22
+ restBoolean(data.draft, "pull request draft");
23
+ restString(head.sha, "pull request head SHA");
24
+ restString(head.ref, "pull request head ref");
25
+ restString(base.sha, "pull request base SHA");
26
+ restString(base.ref, "pull request base ref");
27
+ restString(data.updated_at, "pull request updated_at");
28
+ if (!Number.isFinite(Date.parse(data.updated_at)))
29
+ malformedRest("pull request revision timestamp");
30
+ restString(data.mergeable_state, "pull request mergeable_state");
31
+ if (data.mergeable !== null)
32
+ restBoolean(data.mergeable, "pull request mergeable");
33
+ await recordRestIdentity(repo, pr, data.node_id, String(data.id), "pull");
34
+ return data;
35
+ }
36
+ export function restPullRefs(pull) {
37
+ return {
38
+ number: pull.number,
39
+ state: pull.merged_at ? "MERGED" : pull.state.toUpperCase(),
40
+ headRefName: pull.head.ref,
41
+ headRefOid: pull.head.sha,
42
+ baseRefName: pull.base.ref,
43
+ baseRefOid: pull.base.sha,
44
+ };
45
+ }
46
+ export function restPullRevision(pull) {
47
+ return JSON.stringify([
48
+ pull.head.ref,
49
+ pull.head.sha,
50
+ pull.base.ref,
51
+ pull.base.sha,
52
+ pull.updated_at,
53
+ pull.state,
54
+ pull.draft,
55
+ pull.merged_at,
56
+ pull.mergeable,
57
+ pull.mergeable_state,
58
+ pull.auto_merge ?? null,
59
+ ]);
60
+ }