pr-shepherd 0.56.5 → 0.57.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (423) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +44 -27
  3. package/bin/api.d.mts +25 -3
  4. package/bin/api.mjs +51 -13
  5. package/bin/checks/classify.d.mts +1 -0
  6. package/bin/checks/classify.mjs +1 -1
  7. package/bin/checks/conclusions.d.mts +5 -1
  8. package/bin/checks/conclusions.mjs +8 -2
  9. package/bin/checks/superseded.d.mts +8 -2
  10. package/bin/checks/superseded.mjs +24 -7
  11. package/bin/classify/loader.d.mts +2 -0
  12. package/bin/classify/loader.mjs +15 -0
  13. package/bin/classify/standard/author.d.mts +3 -0
  14. package/bin/classify/standard/author.mjs +10 -0
  15. package/bin/classify/standard/codecov-report.d.mts +4 -0
  16. package/bin/classify/standard/codecov-report.mjs +10 -0
  17. package/bin/classify/standard/coderabbit-notices.d.mts +8 -0
  18. package/bin/classify/standard/coderabbit-notices.mjs +38 -0
  19. package/bin/classify/standard/coderabbit-review-summary.d.mts +8 -0
  20. package/bin/classify/standard/coderabbit-review-summary.mjs +37 -0
  21. package/bin/classify/standard/codex-notices.d.mts +7 -0
  22. package/bin/classify/standard/codex-notices.mjs +36 -0
  23. package/bin/classify/standard/codex-review-summary.d.mts +7 -0
  24. package/bin/classify/standard/codex-review-summary.mjs +20 -0
  25. package/bin/classify/standard/dependabot-notices.d.mts +4 -0
  26. package/bin/classify/standard/dependabot-notices.mjs +15 -0
  27. package/bin/classify/standard/gemini-notices.d.mts +4 -0
  28. package/bin/classify/standard/gemini-notices.mjs +13 -0
  29. package/bin/classify/standard/index.d.mts +4 -0
  30. package/bin/classify/standard/index.mjs +22 -0
  31. package/bin/classify/standard/kilo-no-issues.d.mts +4 -0
  32. package/bin/classify/standard/kilo-no-issues.mjs +17 -0
  33. package/bin/classify/standard/sourcery-notices.d.mts +8 -0
  34. package/bin/classify/standard/sourcery-notices.mjs +20 -0
  35. package/bin/cli/api-usage-formatter.mjs +6 -1
  36. package/bin/cli/apply-merge-handler.d.mts +3 -0
  37. package/bin/cli/apply-merge-handler.mjs +73 -0
  38. package/bin/cli/arg-flags.d.mts +2 -0
  39. package/bin/cli/arg-flags.mjs +47 -0
  40. package/bin/cli/args.mjs +10 -41
  41. package/bin/cli/clean-handler.d.mts +1 -0
  42. package/bin/cli/clean-handler.mjs +71 -0
  43. package/bin/cli/default-poll.mjs +3 -0
  44. package/bin/cli/event-state-scope.d.mts +19 -0
  45. package/bin/cli/event-state-scope.mjs +60 -0
  46. package/bin/cli/fix-formatter.mjs +5 -11
  47. package/bin/cli/handlers.d.mts +0 -1
  48. package/bin/cli/handlers.mjs +8 -64
  49. package/bin/cli/help-command-pages.d.mts +62 -18
  50. package/bin/cli/help-command-pages.mjs +59 -15
  51. package/bin/cli/help-iterate-poll-pages.d.mts +2 -2
  52. package/bin/cli/help-iterate-poll-pages.mjs +6 -0
  53. package/bin/cli/help-log-file-page.d.mts +1 -1
  54. package/bin/cli/help-log-file-page.mjs +4 -1
  55. package/bin/cli/help-top-page.d.mts +1 -1
  56. package/bin/cli/help-top-page.mjs +5 -0
  57. package/bin/cli/help.d.mts +63 -19
  58. package/bin/cli/help.mjs +2 -0
  59. package/bin/cli/iterate-branch-segment.d.mts +2 -0
  60. package/bin/cli/iterate-branch-segment.mjs +12 -7
  61. package/bin/cli/iterate-flags.d.mts +7 -1
  62. package/bin/cli/iterate-flags.mjs +38 -0
  63. package/bin/cli/iterate-formatter.d.mts +6 -6
  64. package/bin/cli/iterate-formatter.mjs +56 -22
  65. package/bin/cli/iterate-instructions.mjs +45 -15
  66. package/bin/cli/iterate-lean.mjs +39 -14
  67. package/bin/cli/iterate-merge-formatter.d.mts +6 -0
  68. package/bin/cli/iterate-merge-formatter.mjs +24 -14
  69. package/bin/cli/iterate-verbose.mjs +3 -6
  70. package/bin/cli/list-formatters.mjs +3 -1
  71. package/bin/cli/mutate-formatter.mjs +10 -5
  72. package/bin/cli/next-check-format.d.mts +7 -0
  73. package/bin/cli/next-check-format.mjs +10 -0
  74. package/bin/cli/playbook-handler.d.mts +5 -0
  75. package/bin/cli/playbook-handler.mjs +40 -0
  76. package/bin/cli/poll-handler.mjs +7 -6
  77. package/bin/cli/poll-summary-formatter.mjs +11 -2
  78. package/bin/cli/poll-targets.mjs +2 -0
  79. package/bin/cli/resolve-validators.mjs +2 -2
  80. package/bin/cli/runner.mjs +6 -1
  81. package/bin/cli/stack-layer-view.d.mts +33 -0
  82. package/bin/cli/stack-layer-view.mjs +1 -0
  83. package/bin/cli/stack-overview.d.mts +10 -28
  84. package/bin/cli/stack-overview.mjs +39 -14
  85. package/bin/cli/transport-args.d.mts +5 -0
  86. package/bin/cli/transport-args.mjs +23 -0
  87. package/bin/cli/transport-formatter.d.mts +9 -0
  88. package/bin/cli/transport-formatter.mjs +13 -0
  89. package/bin/cli-parser.mjs +62 -7
  90. package/bin/commands/apply-merge-options.d.mts +4 -0
  91. package/bin/commands/apply-merge-options.mjs +34 -0
  92. package/bin/commands/apply-merge-output.d.mts +7 -0
  93. package/bin/commands/apply-merge-output.mjs +20 -0
  94. package/bin/commands/apply-merge-pull.d.mts +20 -0
  95. package/bin/commands/apply-merge-pull.mjs +15 -0
  96. package/bin/commands/apply-merge.d.mts +18 -0
  97. package/bin/commands/apply-merge.mjs +126 -0
  98. package/bin/commands/apply-queue-removal.mjs +7 -0
  99. package/bin/commands/check-annotations.mjs +28 -18
  100. package/bin/commands/check-execution-context.d.mts +5 -0
  101. package/bin/commands/check-execution-context.mjs +16 -1
  102. package/bin/commands/check-fingerprint.d.mts +26 -2
  103. package/bin/commands/check-fingerprint.mjs +64 -28
  104. package/bin/commands/check-terminal-report.mjs +11 -1
  105. package/bin/commands/check-unreported.d.mts +6 -2
  106. package/bin/commands/check-unreported.mjs +32 -12
  107. package/bin/commands/check-wait-detectors.d.mts +35 -0
  108. package/bin/commands/check-wait-detectors.mjs +107 -0
  109. package/bin/commands/check.d.mts +4 -2
  110. package/bin/commands/check.mjs +130 -40
  111. package/bin/commands/event-instructions.d.mts +11 -0
  112. package/bin/commands/event-instructions.mjs +27 -0
  113. package/bin/commands/event-mode.d.mts +14 -0
  114. package/bin/commands/event-mode.mjs +138 -0
  115. package/bin/commands/iterate/api-usage.mjs +4 -2
  116. package/bin/commands/iterate/base.mjs +4 -0
  117. package/bin/commands/iterate/check-blocker-gate.mjs +14 -2
  118. package/bin/commands/iterate/check-instructions.d.mts +9 -10
  119. package/bin/commands/iterate/check-instructions.mjs +23 -22
  120. package/bin/commands/iterate/classify.mjs +19 -5
  121. package/bin/commands/iterate/escalate.mjs +13 -3
  122. package/bin/commands/iterate/fix-code.mjs +40 -19
  123. package/bin/commands/iterate/fix-instruction-threads.d.mts +0 -8
  124. package/bin/commands/iterate/fix-instruction-threads.mjs +0 -16
  125. package/bin/commands/iterate/fix-loop-instruction.d.mts +9 -0
  126. package/bin/commands/iterate/fix-loop-instruction.mjs +35 -0
  127. package/bin/commands/iterate/helpers.mjs +15 -2
  128. package/bin/commands/iterate/index.mjs +26 -4
  129. package/bin/commands/iterate/mark-ready.mjs +50 -10
  130. package/bin/commands/iterate/merge-plan.d.mts +19 -0
  131. package/bin/commands/iterate/merge-plan.mjs +88 -0
  132. package/bin/commands/iterate/merge-state.mjs +3 -0
  133. package/bin/commands/iterate/merge.d.mts +4 -15
  134. package/bin/commands/iterate/merge.mjs +18 -51
  135. package/bin/commands/iterate/merged-base-pull-requests.d.mts +2 -6
  136. package/bin/commands/iterate/merged-base-pull-requests.mjs +15 -11
  137. package/bin/commands/iterate/native-stack-rebase.d.mts +6 -5
  138. package/bin/commands/iterate/native-stack-rebase.mjs +24 -7
  139. package/bin/commands/iterate/render.d.mts +8 -2
  140. package/bin/commands/iterate/render.mjs +28 -42
  141. package/bin/commands/iterate/stack-trunk-conflict.d.mts +4 -2
  142. package/bin/commands/iterate/stack-trunk-conflict.mjs +44 -19
  143. package/bin/commands/iterate/stale-ancestry.mjs +4 -1
  144. package/bin/commands/iterate/stall.d.mts +5 -1
  145. package/bin/commands/iterate/stall.mjs +19 -7
  146. package/bin/commands/iterate/thread-mutation-routing.d.mts +1 -0
  147. package/bin/commands/iterate/thread-mutation-routing.mjs +4 -2
  148. package/bin/commands/iterate/unreported-required.mjs +5 -2
  149. package/bin/commands/mark-files-as-viewed.mjs +7 -0
  150. package/bin/commands/next-check.d.mts +31 -0
  151. package/bin/commands/next-check.mjs +46 -0
  152. package/bin/commands/playbook.d.mts +13 -0
  153. package/bin/commands/playbook.mjs +48 -0
  154. package/bin/commands/poll-mode.d.mts +8 -0
  155. package/bin/commands/poll-mode.mjs +14 -0
  156. package/bin/commands/poll-progress.d.mts +1 -0
  157. package/bin/commands/poll-progress.mjs +6 -3
  158. package/bin/commands/poll-quota.d.mts +3 -2
  159. package/bin/commands/poll-quota.mjs +7 -3
  160. package/bin/commands/poll-summary-explicit-instructions.d.mts +2 -0
  161. package/bin/commands/poll-summary-explicit-instructions.mjs +10 -1
  162. package/bin/commands/poll-summary-instructions.mjs +16 -17
  163. package/bin/commands/poll-summary.mjs +42 -17
  164. package/bin/commands/poll.d.mts +0 -1
  165. package/bin/commands/poll.mjs +11 -19
  166. package/bin/commands/quota-selection.d.mts +2 -1
  167. package/bin/commands/quota-selection.mjs +12 -7
  168. package/bin/commands/resolve-mutate.mjs +33 -5
  169. package/bin/commands/rest-stack-merge-readiness.d.mts +4 -0
  170. package/bin/commands/rest-stack-merge-readiness.mjs +74 -0
  171. package/bin/commands/rule-auto-resolve.mjs +9 -3
  172. package/bin/commands/shepherd-journal.d.mts +5 -4
  173. package/bin/commands/shepherd-journal.mjs +9 -8
  174. package/bin/commands/stack-drain.d.mts +1 -9
  175. package/bin/commands/stack-drain.mjs +50 -37
  176. package/bin/commands/stack-handoffs.d.mts +10 -0
  177. package/bin/commands/stack-handoffs.mjs +25 -0
  178. package/bin/commands/stack-stall.d.mts +5 -0
  179. package/bin/commands/stack-stall.mjs +11 -0
  180. package/bin/commands/stack-work.d.mts +5 -0
  181. package/bin/commands/stack-work.mjs +32 -0
  182. package/bin/comments/existing-reply-scan.d.mts +17 -0
  183. package/bin/comments/existing-reply-scan.mjs +41 -0
  184. package/bin/comments/rate-limit.mjs +2 -2
  185. package/bin/comments/resolve.d.mts +7 -1
  186. package/bin/comments/resolve.mjs +108 -18
  187. package/bin/comments/rest-reply.d.mts +9 -0
  188. package/bin/comments/rest-reply.mjs +95 -0
  189. package/bin/comments/rest-review-mutations.d.mts +22 -0
  190. package/bin/comments/rest-review-mutations.mjs +125 -0
  191. package/bin/comments/review-visibility.d.mts +1 -1
  192. package/bin/comments/review-visibility.mjs +3 -2
  193. package/bin/comments/sha-poll.mjs +7 -3
  194. package/bin/comments/thread-visibility.mjs +10 -5
  195. package/bin/comments/uncertain-replies.d.mts +11 -0
  196. package/bin/comments/uncertain-replies.mjs +121 -0
  197. package/bin/comments/uncertain-reply-guards.d.mts +10 -0
  198. package/bin/comments/uncertain-reply-guards.mjs +94 -0
  199. package/bin/comments/visible-comments.d.mts +1 -1
  200. package/bin/comments/visible-comments.mjs +4 -2
  201. package/bin/config/load.d.mts +37 -1
  202. package/bin/config/load.mjs +73 -1
  203. package/bin/config.json +6 -2
  204. package/bin/exit-codes.d.mts +3 -0
  205. package/bin/exit-codes.mjs +8 -0
  206. package/bin/github/api-telemetry-aggregate.d.mts +15 -4
  207. package/bin/github/api-telemetry-aggregate.mjs +57 -18
  208. package/bin/github/api-telemetry.d.mts +4 -0
  209. package/bin/github/api-telemetry.mjs +22 -8
  210. package/bin/github/apply-review-preflight.d.mts +20 -0
  211. package/bin/github/apply-review-preflight.mjs +61 -0
  212. package/bin/github/base-behind-cache.d.mts +15 -0
  213. package/bin/github/base-behind-cache.mjs +18 -0
  214. package/bin/github/batch-parse-suites.d.mts +4 -2
  215. package/bin/github/batch-parse-suites.mjs +6 -1
  216. package/bin/github/batch-parser-helpers.mjs +40 -2
  217. package/bin/github/batch-parsers.mjs +1 -0
  218. package/bin/github/batch-raw-rules.d.mts +26 -0
  219. package/bin/github/batch-raw-types.d.mts +8 -6
  220. package/bin/github/batch-stack-evidence.d.mts +28 -0
  221. package/bin/github/batch-stack-evidence.mjs +62 -0
  222. package/bin/github/batch.d.mts +15 -2
  223. package/bin/github/batch.mjs +34 -17
  224. package/bin/github/check-annotations-batch.d.mts +0 -5
  225. package/bin/github/check-annotations-batch.mjs +25 -4
  226. package/bin/github/check-annotations.mjs +3 -1
  227. package/bin/github/client.d.mts +8 -19
  228. package/bin/github/client.mjs +45 -13
  229. package/bin/github/fingerprint-fields.d.mts +8 -0
  230. package/bin/github/fingerprint-fields.mjs +6 -1
  231. package/bin/github/fingerprint.d.mts +4 -2
  232. package/bin/github/fingerprint.mjs +6 -38
  233. package/bin/github/github-fetch.d.mts +2 -0
  234. package/bin/github/github-fetch.mjs +19 -0
  235. package/bin/github/gql/base-behind.gql +3 -0
  236. package/bin/github/gql/batch-pr-page.gql +16 -0
  237. package/bin/github/gql/batch-pr.gql +57 -0
  238. package/bin/github/gql/commit-check-contexts.gql +17 -0
  239. package/bin/github/gql/get-pr-body.gql +1 -0
  240. package/bin/github/gql/poll-stack-summary.gql +1 -0
  241. package/bin/github/gql/poll-summary-check-contexts.gql +5 -0
  242. package/bin/github/gql/poll-summary-fragment.gql +65 -15
  243. package/bin/github/gql/stack-handoff-check-runs.gql +42 -0
  244. package/bin/github/gql/upper-layer-conflict-target.gql +0 -15
  245. package/bin/github/graphql-http.mjs +9 -4
  246. package/bin/github/graphql-internal-retry.d.mts +2 -0
  247. package/bin/github/graphql-internal-retry.mjs +1 -1
  248. package/bin/github/graphql-quota-probe.d.mts +6 -0
  249. package/bin/github/graphql-quota-probe.mjs +56 -0
  250. package/bin/github/graphql-session-refusal.d.mts +8 -0
  251. package/bin/github/graphql-session-refusal.mjs +14 -0
  252. package/bin/github/http-request.d.mts +1 -1
  253. package/bin/github/http-request.mjs +22 -6
  254. package/bin/github/http-utils.mjs +6 -0
  255. package/bin/github/merge-target-rules.d.mts +16 -3
  256. package/bin/github/merge-target-rules.mjs +79 -44
  257. package/bin/github/mutation-policy.d.mts +6 -0
  258. package/bin/github/mutation-policy.mjs +21 -0
  259. package/bin/github/poll-summary-annotation-probe.mjs +17 -10
  260. package/bin/github/poll-summary-check-blockers.d.mts +1 -1
  261. package/bin/github/poll-summary-check-blockers.mjs +2 -2
  262. package/bin/github/poll-summary-check-hydration.mjs +25 -10
  263. package/bin/github/poll-summary-checks.d.mts +2 -2
  264. package/bin/github/poll-summary-checks.mjs +9 -6
  265. package/bin/github/poll-summary-command.d.mts +2 -0
  266. package/bin/github/poll-summary-command.mjs +24 -0
  267. package/bin/github/poll-summary-fingerprint.d.mts +1 -1
  268. package/bin/github/poll-summary-fingerprint.mjs +4 -2
  269. package/bin/github/poll-summary-handoff-fields.d.mts +3 -0
  270. package/bin/github/poll-summary-handoff-fields.mjs +73 -0
  271. package/bin/github/poll-summary-projector.d.mts +1 -1
  272. package/bin/github/poll-summary-projector.mjs +17 -24
  273. package/bin/github/poll-summary-raw.d.mts +70 -10
  274. package/bin/github/poll-summary-readiness.mjs +4 -1
  275. package/bin/github/poll-summary-rest-queue.d.mts +3 -0
  276. package/bin/github/poll-summary-rest-queue.mjs +12 -0
  277. package/bin/github/poll-summary-review.d.mts +1 -1
  278. package/bin/github/poll-summary-review.mjs +23 -11
  279. package/bin/github/poll-summary-route.mjs +19 -3
  280. package/bin/github/poll-summary-stack-page.d.mts +11 -0
  281. package/bin/github/poll-summary-stack-page.mjs +27 -0
  282. package/bin/github/poll-summary-unreported.d.mts +6 -3
  283. package/bin/github/poll-summary-unreported.mjs +15 -7
  284. package/bin/github/poll-summary.d.mts +4 -1
  285. package/bin/github/poll-summary.mjs +48 -34
  286. package/bin/github/queries.d.mts +9 -5
  287. package/bin/github/queries.mjs +10 -6
  288. package/bin/github/rate-limit-sample.d.mts +7 -0
  289. package/bin/github/rate-limit-sample.mjs +12 -0
  290. package/bin/github/reply-recovery-read.d.mts +50 -0
  291. package/bin/github/reply-recovery-read.mjs +118 -0
  292. package/bin/github/reply-thread-transcripts.d.mts +0 -1
  293. package/bin/github/reply-thread-transcripts.mjs +24 -1
  294. package/bin/github/rest-annotation-read.d.mts +5 -0
  295. package/bin/github/rest-annotation-read.mjs +44 -0
  296. package/bin/github/rest-batch-read.d.mts +12 -0
  297. package/bin/github/rest-batch-read.mjs +57 -0
  298. package/bin/github/rest-batch-snapshot.d.mts +30 -0
  299. package/bin/github/rest-batch-snapshot.mjs +115 -0
  300. package/bin/github/rest-batch-unavailable.d.mts +5 -0
  301. package/bin/github/rest-batch-unavailable.mjs +16 -0
  302. package/bin/github/rest-check-read.d.mts +34 -0
  303. package/bin/github/rest-check-read.mjs +124 -0
  304. package/bin/github/rest-comment-authorship.d.mts +7 -0
  305. package/bin/github/rest-comment-authorship.mjs +11 -0
  306. package/bin/github/rest-conditional-scope.d.mts +20 -0
  307. package/bin/github/rest-conditional-scope.mjs +44 -0
  308. package/bin/github/rest-explicit-summary-read.d.mts +3 -0
  309. package/bin/github/rest-explicit-summary-read.mjs +8 -0
  310. package/bin/github/rest-feedback-read.d.mts +36 -0
  311. package/bin/github/rest-feedback-read.mjs +147 -0
  312. package/bin/github/rest-feedback-validation.d.mts +5 -0
  313. package/bin/github/rest-feedback-validation.mjs +28 -0
  314. package/bin/github/rest-http.d.mts +12 -0
  315. package/bin/github/rest-http.mjs +108 -23
  316. package/bin/github/rest-identities.d.mts +22 -0
  317. package/bin/github/rest-identities.mjs +134 -0
  318. package/bin/github/rest-merge-stack-guard.d.mts +14 -0
  319. package/bin/github/rest-merge-stack-guard.mjs +41 -0
  320. package/bin/github/rest-merge.d.mts +29 -0
  321. package/bin/github/rest-merge.mjs +56 -0
  322. package/bin/github/rest-merged-base-read.d.mts +9 -0
  323. package/bin/github/rest-merged-base-read.mjs +48 -0
  324. package/bin/github/rest-pr-core.d.mts +52 -0
  325. package/bin/github/rest-pr-core.mjs +60 -0
  326. package/bin/github/rest-reader-core.d.mts +16 -0
  327. package/bin/github/rest-reader-core.mjs +154 -0
  328. package/bin/github/rest-review-read.d.mts +11 -0
  329. package/bin/github/rest-review-read.mjs +47 -0
  330. package/bin/github/rest-rules-read.d.mts +9 -0
  331. package/bin/github/rest-rules-read.mjs +154 -0
  332. package/bin/github/rest-session-refusal.d.mts +5 -0
  333. package/bin/github/rest-session-refusal.mjs +21 -0
  334. package/bin/github/rest-snapshot-context.d.mts +10 -0
  335. package/bin/github/rest-snapshot-context.mjs +1 -0
  336. package/bin/github/rest-stack-read.d.mts +21 -0
  337. package/bin/github/rest-stack-read.mjs +47 -0
  338. package/bin/github/rest-stack-summary.d.mts +8 -0
  339. package/bin/github/rest-stack-summary.mjs +62 -0
  340. package/bin/github/rest-summary-read.d.mts +7 -0
  341. package/bin/github/rest-summary-read.mjs +112 -0
  342. package/bin/github/rest-terminal-batch.d.mts +14 -0
  343. package/bin/github/rest-terminal-batch.mjs +45 -0
  344. package/bin/github/rest-text.mjs +7 -3
  345. package/bin/github/rest-thread-read.d.mts +4 -0
  346. package/bin/github/rest-thread-read.mjs +35 -0
  347. package/bin/github/rest-viewer-read.d.mts +2 -0
  348. package/bin/github/rest-viewer-read.mjs +41 -0
  349. package/bin/github/stack-bottom.d.mts +3 -0
  350. package/bin/github/stack-bottom.mjs +14 -0
  351. package/bin/github/stack-handoff-context.d.mts +25 -0
  352. package/bin/github/stack-handoff-context.mjs +12 -0
  353. package/bin/github/stack-handoff-page.d.mts +17 -0
  354. package/bin/github/stack-handoff-page.mjs +306 -0
  355. package/bin/github/stack-handoff.d.mts +16 -0
  356. package/bin/github/stack-handoff.mjs +115 -0
  357. package/bin/github/stack-order.d.mts +6 -0
  358. package/bin/github/stack-order.mjs +13 -0
  359. package/bin/github/stack-read.d.mts +7 -3
  360. package/bin/github/stack-read.mjs +22 -11
  361. package/bin/github/stack-size-hint.d.mts +3 -0
  362. package/bin/github/stack-size-hint.mjs +14 -0
  363. package/bin/github/suggestion-thread.mjs +27 -1
  364. package/bin/github/thread-comments.mjs +9 -1
  365. package/bin/github/transport-evidence.d.mts +20 -0
  366. package/bin/github/transport-evidence.mjs +26 -0
  367. package/bin/github/transport-mode.d.mts +2 -0
  368. package/bin/github/transport-mode.mjs +5 -0
  369. package/bin/github/transport.d.mts +19 -0
  370. package/bin/github/transport.mjs +126 -0
  371. package/bin/github/unsupported-rest.d.mts +4 -0
  372. package/bin/github/unsupported-rest.mjs +7 -0
  373. package/bin/mcp/server.mjs +52 -3
  374. package/bin/merge-status/derive.d.mts +2 -0
  375. package/bin/merge-status/derive.mjs +17 -13
  376. package/bin/merge-status/required-check-requirements.d.mts +7 -0
  377. package/bin/merge-status/required-check-requirements.mjs +15 -0
  378. package/bin/merge-status/requirements-format.d.mts +5 -0
  379. package/bin/merge-status/requirements-format.mjs +41 -12
  380. package/bin/merge-status/requirements.mjs +18 -7
  381. package/bin/state/base.mjs +41 -0
  382. package/bin/state/durable-state.d.mts +6 -0
  383. package/bin/state/durable-state.mjs +41 -0
  384. package/bin/state/graphql-quota-policy.mjs +23 -16
  385. package/bin/state/merge-request.d.mts +25 -0
  386. package/bin/state/merge-request.mjs +145 -0
  387. package/bin/state/pr-fingerprint.d.mts +15 -0
  388. package/bin/state/pr-fingerprint.mjs +22 -7
  389. package/bin/state/rest-cache.d.mts +3 -0
  390. package/bin/state/rest-cache.mjs +1 -0
  391. package/bin/state/rest-snapshot-report.d.mts +21 -0
  392. package/bin/state/rest-snapshot-report.mjs +52 -0
  393. package/bin/state/seen-comments.d.mts +3 -0
  394. package/bin/state/seen-comments.mjs +18 -0
  395. package/bin/types/api-usage.d.mts +9 -1
  396. package/bin/types/escalate.d.mts +1 -1
  397. package/bin/types/github.d.mts +23 -9
  398. package/bin/types/iterate.d.mts +24 -2
  399. package/bin/types/merge-action.d.mts +1 -1
  400. package/bin/types/merge-requirements.d.mts +7 -7
  401. package/bin/types/next-check.d.mts +12 -0
  402. package/bin/types/next-check.mjs +1 -0
  403. package/bin/types/poll-summary.d.mts +21 -0
  404. package/bin/types/report.d.mts +13 -1
  405. package/bin/types/review-thread.d.mts +1 -1
  406. package/bin/types.d.mts +1 -0
  407. package/bin/types.mjs +1 -0
  408. package/package.json +3 -2
  409. package/plugins/pr-shepherd/.codex-plugin/plugin.json +1 -1
  410. package/plugins/pr-shepherd/.codex.mcp.json +1 -1
  411. package/plugins/pr-shepherd/.mcp.json +1 -1
  412. package/plugins/pr-shepherd/skills/mark-files-as-viewed/SKILL.md +1 -1
  413. package/plugins/pr-shepherd/skills/pr-shepherd/SKILL.md +14 -51
  414. package/plugins/pr-shepherd/skills/pr-shepherd/references/ci-failure-triage.md +12 -16
  415. package/plugins/pr-shepherd/skills/pr-shepherd/references/cloud-event-loop.md +14 -0
  416. package/plugins/pr-shepherd/skills/pr-shepherd/references/create-pr.md +10 -0
  417. package/plugins/pr-shepherd/skills/pr-shepherd/references/fix-code-loop.md +10 -0
  418. package/plugins/pr-shepherd/skills/pr-shepherd/references/mcp-fallback.md +9 -0
  419. package/plugins/pr-shepherd/skills/reduce-pr-noise/references/classifiers.md +2 -0
  420. package/bin/github/gql/pr-fingerprint.gql +0 -75
  421. package/plugins/pr-shepherd/skills/pr-shepherd/references/journal.md +0 -7
  422. package/plugins/pr-shepherd/skills/pr-shepherd/references/review-mutations.md +0 -9
  423. package/plugins/pr-shepherd/skills/pr-shepherd/references/stack-merge.md +0 -7
@@ -0,0 +1,16 @@
1
+ import type { RepoInfo, RateLimitInfo } from "./client.mts";
2
+ export declare function restRepoPath(repo: RepoInfo): string;
3
+ export declare function malformedRest(detail: string): never;
4
+ export declare function restObject(value: unknown, label: string): Record<string, unknown>;
5
+ export declare function restString(value: unknown, label: string): string;
6
+ export declare function restNumber(value: unknown, label: string): number;
7
+ export declare function restBoolean(value: unknown, label: string): boolean;
8
+ export declare function restArray(value: unknown, label: string): unknown[];
9
+ /** Counted REST envelopes must include the count as well as their list. */
10
+ export declare function restCollection(value: unknown, key: string): unknown[];
11
+ /** Complete REST connections; follow GitHub's Link, never infer completeness from a short page. */
12
+ export declare function readRestPages<T>(path: string, select?: (body: unknown) => T[]): Promise<{
13
+ nodes: T[];
14
+ rateLimit?: RateLimitInfo;
15
+ }>;
16
+ export declare function readRest<T>(method: string, path: string, body?: unknown): Promise<T>;
@@ -0,0 +1,154 @@
1
+ import { restWithRateLimit } from "./http.mjs";
2
+ import { GitHubRequestError } from "./errors.mjs";
3
+ import { EXIT } from "../exit-codes.mjs";
4
+ import { currentRestConditionalKey, recordRestConditionalRead } from "./rest-conditional-scope.mjs";
5
+ export function restRepoPath(repo) {
6
+ return `/repos/${encodeURIComponent(repo.owner)}/${encodeURIComponent(repo.name)}`;
7
+ }
8
+ export function malformedRest(detail) {
9
+ throw new GitHubRequestError(`Malformed or incomplete GitHub REST response: ${detail}`, {
10
+ status: 200,
11
+ exitCodeOverride: EXIT.SOFTWARE,
12
+ });
13
+ }
14
+ export function restObject(value, label) {
15
+ if (!value || typeof value !== "object" || Array.isArray(value))
16
+ malformedRest(label);
17
+ return value;
18
+ }
19
+ export function restString(value, label) {
20
+ if (typeof value !== "string" || !value)
21
+ malformedRest(label);
22
+ return value;
23
+ }
24
+ export function restNumber(value, label) {
25
+ if (!Number.isSafeInteger(value) || value < 0)
26
+ malformedRest(label);
27
+ return value;
28
+ }
29
+ export function restBoolean(value, label) {
30
+ if (typeof value !== "boolean")
31
+ malformedRest(label);
32
+ return value;
33
+ }
34
+ export function restArray(value, label) {
35
+ if (!Array.isArray(value))
36
+ malformedRest(label);
37
+ return value;
38
+ }
39
+ /** Counted REST envelopes must include the count as well as their list. */
40
+ export function restCollection(value, key) {
41
+ const record = restObject(value, key);
42
+ restNumber(record.total_count, `${key}.total_count`);
43
+ return restArray(record[key], key);
44
+ }
45
+ const REST_PAGE_SIZE = 100;
46
+ /** Complete REST connections; follow GitHub's Link, never infer completeness from a short page. */
47
+ export async function readRestPages(path, select = (body) => restArray(body, path)) {
48
+ let next = `${path}${path.includes("?") ? "&" : "?"}per_page=${REST_PAGE_SIZE}`;
49
+ const visited = new Set();
50
+ const nodes = [];
51
+ let rateLimit;
52
+ let total;
53
+ while (next) {
54
+ if (visited.has(next) || visited.size >= 1_000)
55
+ malformedRest(`pagination cycle or limit at ${next}`);
56
+ visited.add(next);
57
+ let result = await readRestRequest("GET", next);
58
+ let page = select(result.data);
59
+ if (!Array.isArray(page))
60
+ malformedRest(`missing page at ${next}`);
61
+ // A full page's body (and ETag) stays the same when an item is appended past it; only its
62
+ // Link gains rel="next". A 304 can't prove the cached terminal Link is still current, so a
63
+ // full page without a next link is re-read fresh rather than hiding items on a later page.
64
+ if (result.status === 304 &&
65
+ page.length >= REST_PAGE_SIZE &&
66
+ nextRestPage(result.link, path) === undefined) {
67
+ result = await readRestRequest("GET", next, undefined, true);
68
+ page = select(result.data);
69
+ if (!Array.isArray(page))
70
+ malformedRest(`missing page at ${next}`);
71
+ }
72
+ rateLimit = result.rateLimit ?? rateLimit;
73
+ nodes.push(...page);
74
+ if (result.data &&
75
+ typeof result.data === "object" &&
76
+ !Array.isArray(result.data) &&
77
+ "total_count" in result.data) {
78
+ const count = restNumber(result.data.total_count, `${path}.total_count`);
79
+ if (total !== undefined && total !== count)
80
+ malformedRest(`${path} changed during pagination`);
81
+ total = count;
82
+ }
83
+ next = nextRestPage(result.link, path);
84
+ if (next && page.length === 0)
85
+ malformedRest(`${path}: empty page with next link`);
86
+ }
87
+ if (total !== undefined && nodes.length !== total)
88
+ malformedRest(`${path}: ${nodes.length} of ${total} items`);
89
+ return { nodes, rateLimit };
90
+ }
91
+ function nextRestPage(link, original) {
92
+ if (!link)
93
+ return undefined;
94
+ const entries = link.split(",");
95
+ const nextEntries = entries.filter((entry) => /rel="next"/.test(entry));
96
+ if (nextEntries.length === 0)
97
+ return undefined;
98
+ if (nextEntries.length !== 1)
99
+ malformedRest(`multiple next links for ${original}`);
100
+ const match = /^\s*<([^>]+)>;\s*rel="next"\s*$/.exec(nextEntries[0]);
101
+ if (!match)
102
+ malformedRest(`invalid pagination link for ${original}`);
103
+ const url = new URL(match[1], "https://api.github.com");
104
+ if (url.origin !== "https://api.github.com" || url.pathname !== original.split("?")[0])
105
+ malformedRest(`unsafe pagination link for ${original}`);
106
+ if (url.searchParams.get("per_page") !== "100")
107
+ malformedRest(`changed page size for ${original}`);
108
+ const source = new URL(original, "https://api.github.com");
109
+ for (const key of new Set(source.searchParams.keys())) {
110
+ if (key !== "page" &&
111
+ key !== "per_page" &&
112
+ JSON.stringify(url.searchParams.getAll(key)) !==
113
+ JSON.stringify(source.searchParams.getAll(key)))
114
+ malformedRest(`changed pagination filter for ${original}`);
115
+ }
116
+ return `${url.pathname}${url.search}`;
117
+ }
118
+ /** A pull request whose mergeability GitHub is still computing must be re-read, never replayed. */
119
+ function isSettledBody(body) {
120
+ if (body === null || typeof body !== "object" || Array.isArray(body))
121
+ return true;
122
+ const record = body;
123
+ return record.mergeable !== null && record.mergeable_state !== "unknown";
124
+ }
125
+ let activeRequests = 0;
126
+ const pendingRequests = [];
127
+ /** Bound independent snapshots and nested connection reads to four HTTP requests together. */
128
+ async function readRestRequest(method, path, body, revalidate = false) {
129
+ if (activeRequests >= 4)
130
+ await new Promise((resolve) => pendingRequests.push(resolve));
131
+ else
132
+ activeRequests++;
133
+ try {
134
+ const key = method === "GET" ? currentRestConditionalKey() : undefined;
135
+ if (key === undefined)
136
+ return await restWithRateLimit(method, path, body);
137
+ const result = await restWithRateLimit(method, path, body, {
138
+ conditional: { key, name: path, shouldStore: isSettledBody, revalidate },
139
+ });
140
+ const unvalidated = result.status !== 304 && result.etag === undefined && isSettledBody(result.data);
141
+ recordRestConditionalRead(path, result.status === 304, result.etag, unvalidated ? result.data : undefined);
142
+ return result;
143
+ }
144
+ finally {
145
+ const waiting = pendingRequests.shift();
146
+ if (waiting)
147
+ waiting();
148
+ else
149
+ activeRequests--;
150
+ }
151
+ }
152
+ export async function readRest(method, path, body) {
153
+ return (await readRestRequest(method, path, body)).data;
154
+ }
@@ -0,0 +1,11 @@
1
+ import type { RestReview } from "./rest-feedback-read.mts";
2
+ export declare function validateRestReviews(reviews: RestReview[]): void;
3
+ /** Draft reviews carry no submitted decision and cannot replace earlier submitted evidence. */
4
+ export declare function restLatestReviews(reviews: RestReview[]): Map<string, {
5
+ login: string;
6
+ state: string;
7
+ }>;
8
+ export declare function restPendingReviews(reviews: RestReview[]): {
9
+ login: string;
10
+ state: string;
11
+ }[];
@@ -0,0 +1,47 @@
1
+ import { malformedRest, restString } from "./rest-reader-core.mjs";
2
+ const REVIEW_STATES = new Set([
3
+ "APPROVED",
4
+ "CHANGES_REQUESTED",
5
+ "COMMENTED",
6
+ "DISMISSED",
7
+ "PENDING",
8
+ ]);
9
+ export function validateRestReviews(reviews) {
10
+ for (const review of reviews) {
11
+ if (!REVIEW_STATES.has(restString(review.state, "review state")))
12
+ malformedRest("unknown review state");
13
+ restString(review.commit_id, "review commit_id");
14
+ if (review.state === "PENDING")
15
+ continue;
16
+ const submitted = restString(review.submitted_at, "review submitted_at");
17
+ if (!Number.isFinite(Date.parse(submitted)))
18
+ malformedRest("review submitted_at");
19
+ }
20
+ }
21
+ /** Draft reviews carry no submitted decision and cannot replace earlier submitted evidence. */
22
+ export function restLatestReviews(reviews) {
23
+ const latest = new Map();
24
+ const submitted = reviews.filter((review) => review.state !== "PENDING");
25
+ submitted.sort((a, b) => Date.parse(a.submitted_at) - Date.parse(b.submitted_at) || a.id - b.id);
26
+ for (const review of submitted) {
27
+ if (!review.user?.login)
28
+ continue;
29
+ latest.set(review.user.login.toLowerCase(), {
30
+ login: review.user.login,
31
+ state: review.state,
32
+ });
33
+ }
34
+ return latest;
35
+ }
36
+ export function restPendingReviews(reviews) {
37
+ const pending = new Map();
38
+ for (const review of reviews) {
39
+ if (review.state !== "PENDING" || !review.user?.login)
40
+ continue;
41
+ pending.set(review.user.login.toLowerCase(), {
42
+ login: review.user.login,
43
+ state: review.state,
44
+ });
45
+ }
46
+ return [...pending.values()];
47
+ }
@@ -0,0 +1,9 @@
1
+ import type { RepoInfo } from "./client.mts";
2
+ import type { RawBaseRef } from "./batch-raw-rules.mts";
3
+ export declare function readRestBranchRules(repo: RepoInfo, branch: string): Promise<{
4
+ baseRef?: RawBaseRef;
5
+ unavailable: Array<{
6
+ field: string;
7
+ reason: string;
8
+ }>;
9
+ }>;
@@ -0,0 +1,154 @@
1
+ import { readRest as rest } from "./rest-reader-core.mjs";
2
+ import { GitHubRequestError } from "./errors.mjs";
3
+ import { rateLimitKind } from "./rate-limit-kind.mjs";
4
+ import { isRestSessionRefusal } from "./rest-session-refusal.mjs";
5
+ import { recordRestConditionalRead } from "./rest-conditional-scope.mjs";
6
+ import { readRestPages, restRepoPath, restObject, restString, restArray, restNumber, } from "./rest-reader-core.mjs";
7
+ export async function readRestBranchRules(repo, branch) {
8
+ const path = `${restRepoPath(repo)}/branches/${encodeURIComponent(branch)}/protection`;
9
+ let classic = null;
10
+ const unavailable = [];
11
+ if (!(await classicProtectionDisabled(repo, branch))) {
12
+ try {
13
+ const data = restObject(await rest("GET", path), "branch protection");
14
+ const approvals = data.required_pull_request_reviews == null
15
+ ? null
16
+ : restObject(data.required_pull_request_reviews, "required reviews");
17
+ const checks = data.required_status_checks == null
18
+ ? null
19
+ : restObject(data.required_status_checks, "required checks");
20
+ classic = {
21
+ requiresApprovingReviews: approvals !== null,
22
+ requiredApprovingReviewCount: approvals
23
+ ? restNumber(approvals.required_approving_review_count, "required approval count")
24
+ : 0,
25
+ requiresConversationResolution: enabled(data.required_conversation_resolution),
26
+ requiresStatusChecks: checks !== null,
27
+ requiredStatusCheckContexts: checks
28
+ ? restArray(checks.contexts, "check contexts").map((context) => restString(context, "check context"))
29
+ : [],
30
+ requiresCodeOwnerReviews: approvals?.require_code_owner_reviews === true,
31
+ requireLastPushApproval: approvals?.require_last_push_approval === true,
32
+ requiresCommitSignatures: enabled(data.required_signatures),
33
+ requiresLinearHistory: enabled(data.required_linear_history),
34
+ requiresStrictStatusChecks: checks?.strict === true,
35
+ };
36
+ }
37
+ catch (error) {
38
+ if (!isUnavailablePolicy(error))
39
+ throw error;
40
+ // Only GitHub's explicit unprotected response proves absence. A generic
41
+ // 404 may hide protection from a token without administration access.
42
+ if (!isUnprotectedBranch(error))
43
+ unavailable.push({
44
+ field: "branchProtection",
45
+ reason: `Classic branch protection unavailable (HTTP ${error.status})`,
46
+ });
47
+ }
48
+ }
49
+ let rules;
50
+ try {
51
+ const data = await readRestPages(`${restRepoPath(repo)}/rules/branches/${encodeURIComponent(branch)}`);
52
+ rules = data.nodes.map((rule) => {
53
+ const type = restString(rule.type, "branch rule type").toUpperCase();
54
+ const p = rule.parameters == null ? {} : restObject(rule.parameters, "branch rule parameters");
55
+ return {
56
+ type,
57
+ parameters: {
58
+ ...(p.required_approving_review_count !== undefined && {
59
+ requiredApprovingReviewCount: restNumber(p.required_approving_review_count, "ruleset approval count"),
60
+ }),
61
+ ...(p.required_review_thread_resolution !== undefined && {
62
+ requiredReviewThreadResolution: p.required_review_thread_resolution === true,
63
+ }),
64
+ ...(p.require_code_owner_review !== undefined && {
65
+ requireCodeOwnerReview: p.require_code_owner_review === true,
66
+ }),
67
+ ...(p.require_last_push_approval !== undefined && {
68
+ requireLastPushApproval: p.require_last_push_approval === true,
69
+ }),
70
+ ...(p.strict_required_status_checks_policy !== undefined && {
71
+ strictRequiredStatusChecksPolicy: p.strict_required_status_checks_policy === true,
72
+ }),
73
+ ...(p.required_status_checks !== undefined && {
74
+ requiredStatusChecks: restArray(p.required_status_checks, "ruleset status checks").map((check) => ({
75
+ context: restString(restObject(check, "status check").context, "required context"),
76
+ })),
77
+ }),
78
+ ...(p.required_deployment_environments !== undefined && {
79
+ requiredDeploymentEnvironments: restArray(p.required_deployment_environments, "required deployments").map((environment) => restString(environment, "deployment environment")),
80
+ }),
81
+ },
82
+ };
83
+ });
84
+ }
85
+ catch (error) {
86
+ if (!isUnavailablePolicy(error))
87
+ throw error;
88
+ unavailable.push({
89
+ field: "branchRules",
90
+ reason: `Branch rules unavailable (HTTP ${error.status})`,
91
+ });
92
+ }
93
+ // Preserve partial known policy, but keep incompleteness explicit to READY consumers.
94
+ const baseRef = classic !== null || rules !== undefined
95
+ ? {
96
+ branchProtectionRule: classic,
97
+ rules: rules === undefined ? null : { pageInfo: { hasNextPage: false }, nodes: rules },
98
+ }
99
+ : undefined;
100
+ return { ...(baseRef && { baseRef }), unavailable };
101
+ }
102
+ /**
103
+ * The branch summary is conditional (a 304 costs no quota) while an unprotected branch's
104
+ * `/protection` 404 is charged on every read. Its `commit.sha` also makes a base push visible
105
+ * to REST snapshot reuse. `protection.enabled: false` is GitHub's own
106
+ * "Branch not protected" answer, so the charged read is skipped. Any other shape, including a
107
+ * summary hidden from the token, falls through to the authoritative `/protection` read.
108
+ */
109
+ async function classicProtectionDisabled(repo, branch) {
110
+ const path = `${restRepoPath(repo)}/branches/${encodeURIComponent(branch)}`;
111
+ let summary;
112
+ try {
113
+ summary = await rest("GET", path);
114
+ }
115
+ catch (error) {
116
+ if (!isUnavailablePolicy(error))
117
+ throw error;
118
+ // The summary's `commit.sha` is what tells snapshot reuse the base moved; without it, an
119
+ // unchanged pull body cannot prove mergeability, so the snapshot must not be replayed.
120
+ recordRestConditionalRead(path, false, undefined);
121
+ return false;
122
+ }
123
+ if (!summary || typeof summary !== "object" || Array.isArray(summary))
124
+ return false;
125
+ const protection = summary.protection;
126
+ if (!protection || typeof protection !== "object" || Array.isArray(protection))
127
+ return false;
128
+ return protection.enabled === false;
129
+ }
130
+ function isUnavailablePolicy(error) {
131
+ return (error instanceof GitHubRequestError &&
132
+ [403, 404].includes(error.status) &&
133
+ rateLimitKind(error) === null &&
134
+ !isRestSessionRefusal(error));
135
+ }
136
+ function isUnprotectedBranch(error) {
137
+ if (error.status !== 404)
138
+ return false;
139
+ try {
140
+ const body = JSON.parse(error.responseMessage ?? "");
141
+ return (body !== null &&
142
+ typeof body === "object" &&
143
+ !Array.isArray(body) &&
144
+ body["message"] === "Branch not protected");
145
+ }
146
+ catch {
147
+ return false;
148
+ }
149
+ }
150
+ function enabled(value) {
151
+ return (value !== null &&
152
+ value !== undefined &&
153
+ restObject(value, "protection requirement").enabled === true);
154
+ }
@@ -0,0 +1,5 @@
1
+ import { GitHubRequestError } from "./errors.mts";
2
+ /** A cloud proxy refusal says nothing about the viewer's GitHub authorization. */
3
+ export declare function isRestSessionRefusal(error: unknown): error is GitHubRequestError;
4
+ /** Inspect the response before normal error-message truncation hides its evidence. */
5
+ export declare function isRestSessionRefusalResponse(status: number, raw: string): boolean;
@@ -0,0 +1,21 @@
1
+ import { GitHubRequestError } from "./errors.mjs";
2
+ /** A cloud proxy refusal says nothing about the viewer's GitHub authorization. */
3
+ export function isRestSessionRefusal(error) {
4
+ return (error instanceof GitHubRequestError &&
5
+ isRestSessionRefusalResponse(error.status, error.responseMessage ?? error.message));
6
+ }
7
+ /** Inspect the response before normal error-message truncation hides its evidence. */
8
+ export function isRestSessionRefusalResponse(status, raw) {
9
+ if (status !== 403)
10
+ return false;
11
+ if (/not enabled for this session|sessions are bound/i.test(raw))
12
+ return true;
13
+ try {
14
+ const payload = JSON.parse(raw);
15
+ return (typeof payload?.documentation_url === "string" &&
16
+ new URL(payload.documentation_url).hostname === "docs.anthropic.com");
17
+ }
18
+ catch {
19
+ return false;
20
+ }
21
+ }
@@ -0,0 +1,10 @@
1
+ import type { readRestStackMembership } from "./rest-stack-read.mts";
2
+ import type { readRestBranchRules } from "./rest-rules-read.mts";
3
+ /** Shared evidence belongs to one stack summary read, never to a later polling tick. */
4
+ export interface RestSnapshotContext {
5
+ stack: Awaited<ReturnType<typeof readRestStackMembership>>;
6
+ repository: Record<string, unknown>;
7
+ viewerLogin: string | null;
8
+ readBranchRules(branch: string): ReturnType<typeof readRestBranchRules>;
9
+ recordPullRevision(pr: number, revision: string): void;
10
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,21 @@
1
+ import type { RepoInfo } from "./client.mts";
2
+ import type { StackRead, StackMemberRefs } from "./stack-read.mts";
3
+ interface RestStack {
4
+ number: number;
5
+ node_id: string;
6
+ base: {
7
+ ref: string;
8
+ };
9
+ pull_requests: Array<{
10
+ number: number;
11
+ }>;
12
+ }
13
+ /** The official native-stack endpoint is authoritative; missing API access cannot prove standalone membership. */
14
+ export declare function readRestStackMembership(anchor: number, repo: RepoInfo): Promise<RestStack | null>;
15
+ /**
16
+ * Topology reads are conditional. Outside a PR snapshot (iterate's stale-ancestry check runs after
17
+ * the snapshot returns) they use the anchor PR's ETag cache, so reads the snapshot already made
18
+ * (the stack list, the stack, the anchor's pull, `/user`) and the repeat membership read answer 304.
19
+ */
20
+ export declare function readRestStackTopology(anchor: number, repo: RepoInfo): Promise<StackRead<StackMemberRefs>>;
21
+ export {};
@@ -0,0 +1,47 @@
1
+ import { readRest as rest } from "./rest-reader-core.mjs";
2
+ import { readRestPages, restRepoPath, restObject, restString, restNumber, restArray, malformedRest, } from "./rest-reader-core.mjs";
3
+ import { readRestPull, restPullRefs } from "./rest-pr-core.mjs";
4
+ import { mapPool } from "../util/pool.mjs";
5
+ import { readRestViewerLogin } from "./rest-viewer-read.mjs";
6
+ import { currentRestConditionalKey, withRestConditionalScope } from "./rest-conditional-scope.mjs";
7
+ /** The official native-stack endpoint is authoritative; missing API access cannot prove standalone membership. */
8
+ export async function readRestStackMembership(anchor, repo) {
9
+ const listed = (await readRestPages(`${restRepoPath(repo)}/stacks?pull_request=${anchor}`)).nodes;
10
+ if (listed.length === 0)
11
+ return null;
12
+ if (listed.length !== 1)
13
+ malformedRest("multiple native stacks for one PR");
14
+ const number = restNumber(listed[0].number, "stack number");
15
+ const stack = restObject(await rest("GET", `${restRepoPath(repo)}/stacks/${number}`), "stack");
16
+ if (restNumber(stack.number, "stack number") !== number)
17
+ malformedRest("stack identity drift");
18
+ restString(stack.node_id, "stack node_id");
19
+ restString(stack.base?.ref, "stack base ref");
20
+ restArray(stack.pull_requests, "stack pull_requests");
21
+ const numbers = stack.pull_requests.map((pull) => restNumber(pull.number, "stack member number"));
22
+ if (!numbers.includes(anchor) || new Set(numbers).size !== numbers.length)
23
+ malformedRest("stack membership omitted anchor or repeated member");
24
+ return stack;
25
+ }
26
+ /**
27
+ * Topology reads are conditional. Outside a PR snapshot (iterate's stale-ancestry check runs after
28
+ * the snapshot returns) they use the anchor PR's ETag cache, so reads the snapshot already made
29
+ * (the stack list, the stack, the anchor's pull, `/user`) and the repeat membership read answer 304.
30
+ */
31
+ export function readRestStackTopology(anchor, repo) {
32
+ if (currentRestConditionalKey() !== undefined)
33
+ return readTopology(anchor, repo);
34
+ return withRestConditionalScope({ owner: repo.owner, repo: repo.name, pr: anchor }, () => readTopology(anchor, repo));
35
+ }
36
+ async function readTopology(anchor, repo) {
37
+ const stack = await readRestStackMembership(anchor, repo);
38
+ if (!stack)
39
+ throw new Error(`PR #${anchor} is not part of a native GitHub stack`);
40
+ const ordered = await mapPool(stack.pull_requests, 4, async (pull) => restPullRefs(await readRestPull(pull.number, repo)));
41
+ const verified = await readRestStackMembership(anchor, repo);
42
+ if (!verified || JSON.stringify(verified) !== JSON.stringify(stack))
43
+ malformedRest("native stack changed during topology read");
44
+ // Installation tokens may read native stacks without supporting authenticated-user identity.
45
+ const viewerLogin = await readRestViewerLogin();
46
+ return { stackNumber: stack.number, stackSize: ordered.length, ordered, viewerLogin };
47
+ }
@@ -0,0 +1,8 @@
1
+ import type { RepoInfo } from "./client.mts";
2
+ export declare function readRestStackSummary(anchor: number, repo: RepoInfo): Promise<{
3
+ stackNumber: number;
4
+ stackSize: number;
5
+ viewerLogin: string | null;
6
+ ordered: import("./poll-summary-raw.mts").RawSummaryPr[];
7
+ allowedMergeMethods?: import("../config/merge-method.mts").MergeMethod[] | undefined;
8
+ }>;
@@ -0,0 +1,62 @@
1
+ import { readRest, restRepoPath, restObject } from "./rest-reader-core.mjs";
2
+ import { readRestViewerLogin } from "./rest-viewer-read.mjs";
3
+ import { readAllowedMergeMethods } from "../config/merge-method.mjs";
4
+ import { readRestStackMembership } from "./rest-stack-read.mjs";
5
+ import { fetchRestRawSummaryPr } from "./rest-batch-read.mjs";
6
+ import { mapPool } from "../util/pool.mjs";
7
+ import { EXIT, ShepherdError } from "../exit-codes.mjs";
8
+ import { GitHubRequestError } from "./errors.mjs";
9
+ import { readRestPull, restPullRevision } from "./rest-pr-core.mjs";
10
+ import { readRestBranchRules } from "./rest-rules-read.mjs";
11
+ export async function readRestStackSummary(anchor, repo) {
12
+ const stack = await readRestStackMembership(anchor, repo);
13
+ if (!stack)
14
+ throw new ShepherdError(`PR #${anchor} is not part of a native GitHub stack`, EXIT.UNAVAILABLE);
15
+ const settings = restObject(await readRest("GET", restRepoPath(repo)), "repository merge settings");
16
+ const viewerLogin = await readRestViewerLogin();
17
+ const branchRules = new Map();
18
+ const pullRevisions = new Map();
19
+ const context = {
20
+ stack,
21
+ repository: settings,
22
+ viewerLogin,
23
+ readBranchRules(branch) {
24
+ let rules = branchRules.get(branch);
25
+ if (!rules) {
26
+ rules = readRestBranchRules(repo, branch);
27
+ branchRules.set(branch, rules);
28
+ }
29
+ return rules;
30
+ },
31
+ recordPullRevision(pr, revision) {
32
+ pullRevisions.set(pr, revision);
33
+ },
34
+ };
35
+ const ordered = await mapPool(stack.pull_requests, 4, (member) => fetchRestRawSummaryPr(member.number, repo, context));
36
+ await mapPool(ordered, 4, async (member) => {
37
+ const latest = await readRestPull(member.number, repo);
38
+ if (restPullRevision(latest) !== pullRevisions.get(member.number))
39
+ stackChanged();
40
+ });
41
+ const fresh = await readRestStackMembership(anchor, repo);
42
+ if (!fresh || JSON.stringify(fresh) !== JSON.stringify(stack))
43
+ stackChanged();
44
+ const allowedMergeMethods = readAllowedMergeMethods({
45
+ mergeCommitAllowed: typeof settings.allow_merge_commit === "boolean" ? settings.allow_merge_commit : undefined,
46
+ squashMergeAllowed: typeof settings.allow_squash_merge === "boolean" ? settings.allow_squash_merge : undefined,
47
+ rebaseMergeAllowed: typeof settings.allow_rebase_merge === "boolean" ? settings.allow_rebase_merge : undefined,
48
+ });
49
+ return {
50
+ stackNumber: stack.number,
51
+ stackSize: ordered.length,
52
+ viewerLogin,
53
+ ordered,
54
+ ...(allowedMergeMethods && { allowedMergeMethods }),
55
+ };
56
+ }
57
+ function stackChanged() {
58
+ throw new GitHubRequestError("Native stack changed during REST summary read; retry", {
59
+ status: 409,
60
+ exitCodeOverride: EXIT.TEMPFAIL,
61
+ });
62
+ }
@@ -0,0 +1,7 @@
1
+ import type { BatchPrData } from "../types.mts";
2
+ import type { RawSummaryPr } from "./poll-summary-raw.mts";
3
+ import type { RestPull } from "./rest-pr-core.mts";
4
+ import type { readRestFeedback } from "./rest-feedback-read.mts";
5
+ import type { readRestCommitChecks } from "./rest-check-read.mts";
6
+ import type { readRestBranchRules } from "./rest-rules-read.mts";
7
+ export declare function restSummary(data: BatchPrData, pull: RestPull, feedback: Awaited<ReturnType<typeof readRestFeedback>>, checks: Awaited<ReturnType<typeof readRestCommitChecks>>, baseRef: Awaited<ReturnType<typeof readRestBranchRules>>["baseRef"]): RawSummaryPr;