pr-shepherd 0.56.6 → 0.57.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (421) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +150 -280
  3. package/bin/api.d.mts +25 -3
  4. package/bin/api.mjs +51 -13
  5. package/bin/checks/classify.d.mts +1 -0
  6. package/bin/checks/classify.mjs +1 -1
  7. package/bin/checks/conclusions.d.mts +5 -1
  8. package/bin/checks/conclusions.mjs +8 -2
  9. package/bin/checks/superseded.d.mts +8 -2
  10. package/bin/checks/superseded.mjs +24 -7
  11. package/bin/classify/loader.d.mts +2 -0
  12. package/bin/classify/loader.mjs +15 -0
  13. package/bin/classify/standard/author.d.mts +3 -0
  14. package/bin/classify/standard/author.mjs +10 -0
  15. package/bin/classify/standard/codecov-report.d.mts +4 -0
  16. package/bin/classify/standard/codecov-report.mjs +10 -0
  17. package/bin/classify/standard/coderabbit-notices.d.mts +8 -0
  18. package/bin/classify/standard/coderabbit-notices.mjs +38 -0
  19. package/bin/classify/standard/codex-notices.d.mts +8 -0
  20. package/bin/classify/standard/codex-notices.mjs +34 -0
  21. package/bin/classify/standard/dependabot-notices.d.mts +4 -0
  22. package/bin/classify/standard/dependabot-notices.mjs +15 -0
  23. package/bin/classify/standard/gemini-notices.d.mts +4 -0
  24. package/bin/classify/standard/gemini-notices.mjs +13 -0
  25. package/bin/classify/standard/index.d.mts +7 -0
  26. package/bin/classify/standard/index.mjs +21 -0
  27. package/bin/classify/standard/kilo-no-issues.d.mts +4 -0
  28. package/bin/classify/standard/kilo-no-issues.mjs +17 -0
  29. package/bin/classify/standard/sourcery-notices.d.mts +8 -0
  30. package/bin/classify/standard/sourcery-notices.mjs +20 -0
  31. package/bin/cli/api-usage-formatter.mjs +6 -1
  32. package/bin/cli/apply-merge-handler.d.mts +3 -0
  33. package/bin/cli/apply-merge-handler.mjs +73 -0
  34. package/bin/cli/arg-flags.d.mts +2 -0
  35. package/bin/cli/arg-flags.mjs +47 -0
  36. package/bin/cli/args.mjs +10 -41
  37. package/bin/cli/clean-handler.d.mts +1 -0
  38. package/bin/cli/clean-handler.mjs +71 -0
  39. package/bin/cli/default-poll.mjs +3 -0
  40. package/bin/cli/event-state-scope.d.mts +19 -0
  41. package/bin/cli/event-state-scope.mjs +60 -0
  42. package/bin/cli/fix-formatter.mjs +5 -11
  43. package/bin/cli/handlers.d.mts +0 -1
  44. package/bin/cli/handlers.mjs +8 -64
  45. package/bin/cli/help-command-pages.d.mts +62 -18
  46. package/bin/cli/help-command-pages.mjs +59 -15
  47. package/bin/cli/help-iterate-poll-pages.d.mts +2 -2
  48. package/bin/cli/help-iterate-poll-pages.mjs +6 -0
  49. package/bin/cli/help-log-file-page.d.mts +1 -1
  50. package/bin/cli/help-log-file-page.mjs +4 -1
  51. package/bin/cli/help-top-page.d.mts +1 -1
  52. package/bin/cli/help-top-page.mjs +5 -0
  53. package/bin/cli/help.d.mts +63 -19
  54. package/bin/cli/help.mjs +2 -0
  55. package/bin/cli/iterate-branch-segment.d.mts +2 -0
  56. package/bin/cli/iterate-branch-segment.mjs +12 -7
  57. package/bin/cli/iterate-flags.d.mts +7 -1
  58. package/bin/cli/iterate-flags.mjs +38 -0
  59. package/bin/cli/iterate-formatter.d.mts +6 -6
  60. package/bin/cli/iterate-formatter.mjs +56 -22
  61. package/bin/cli/iterate-instructions.mjs +45 -15
  62. package/bin/cli/iterate-lean.mjs +39 -14
  63. package/bin/cli/iterate-merge-formatter.d.mts +6 -0
  64. package/bin/cli/iterate-merge-formatter.mjs +24 -14
  65. package/bin/cli/iterate-verbose.mjs +3 -6
  66. package/bin/cli/list-formatters.mjs +3 -1
  67. package/bin/cli/mutate-formatter.mjs +10 -5
  68. package/bin/cli/next-check-format.d.mts +7 -0
  69. package/bin/cli/next-check-format.mjs +10 -0
  70. package/bin/cli/playbook-handler.d.mts +5 -0
  71. package/bin/cli/playbook-handler.mjs +40 -0
  72. package/bin/cli/poll-handler.mjs +7 -6
  73. package/bin/cli/poll-summary-formatter.mjs +11 -2
  74. package/bin/cli/poll-targets.mjs +2 -0
  75. package/bin/cli/resolve-validators.mjs +2 -2
  76. package/bin/cli/runner.mjs +6 -1
  77. package/bin/cli/stack-layer-view.d.mts +33 -0
  78. package/bin/cli/stack-layer-view.mjs +1 -0
  79. package/bin/cli/stack-overview.d.mts +10 -28
  80. package/bin/cli/stack-overview.mjs +39 -14
  81. package/bin/cli/transport-args.d.mts +5 -0
  82. package/bin/cli/transport-args.mjs +23 -0
  83. package/bin/cli/transport-formatter.d.mts +9 -0
  84. package/bin/cli/transport-formatter.mjs +13 -0
  85. package/bin/cli-parser.mjs +62 -7
  86. package/bin/commands/apply-merge-options.d.mts +4 -0
  87. package/bin/commands/apply-merge-options.mjs +34 -0
  88. package/bin/commands/apply-merge-output.d.mts +7 -0
  89. package/bin/commands/apply-merge-output.mjs +20 -0
  90. package/bin/commands/apply-merge-pull.d.mts +20 -0
  91. package/bin/commands/apply-merge-pull.mjs +15 -0
  92. package/bin/commands/apply-merge.d.mts +18 -0
  93. package/bin/commands/apply-merge.mjs +126 -0
  94. package/bin/commands/apply-queue-removal.mjs +7 -0
  95. package/bin/commands/check-annotations.mjs +28 -18
  96. package/bin/commands/check-execution-context.d.mts +5 -0
  97. package/bin/commands/check-execution-context.mjs +16 -1
  98. package/bin/commands/check-fingerprint.d.mts +26 -2
  99. package/bin/commands/check-fingerprint.mjs +64 -28
  100. package/bin/commands/check-terminal-report.mjs +11 -1
  101. package/bin/commands/check-unreported.d.mts +6 -2
  102. package/bin/commands/check-unreported.mjs +32 -12
  103. package/bin/commands/check-wait-detectors.d.mts +35 -0
  104. package/bin/commands/check-wait-detectors.mjs +107 -0
  105. package/bin/commands/check.d.mts +4 -2
  106. package/bin/commands/check.mjs +130 -40
  107. package/bin/commands/event-instructions.d.mts +11 -0
  108. package/bin/commands/event-instructions.mjs +27 -0
  109. package/bin/commands/event-mode.d.mts +14 -0
  110. package/bin/commands/event-mode.mjs +138 -0
  111. package/bin/commands/iterate/api-usage.mjs +4 -2
  112. package/bin/commands/iterate/base.mjs +4 -0
  113. package/bin/commands/iterate/check-blocker-gate.mjs +14 -2
  114. package/bin/commands/iterate/check-instructions.d.mts +9 -10
  115. package/bin/commands/iterate/check-instructions.mjs +23 -22
  116. package/bin/commands/iterate/classify.mjs +19 -5
  117. package/bin/commands/iterate/escalate.mjs +13 -3
  118. package/bin/commands/iterate/fix-code.mjs +26 -9
  119. package/bin/commands/iterate/fix-instruction-threads.d.mts +0 -8
  120. package/bin/commands/iterate/fix-instruction-threads.mjs +0 -16
  121. package/bin/commands/iterate/fix-loop-instruction.d.mts +9 -0
  122. package/bin/commands/iterate/fix-loop-instruction.mjs +35 -0
  123. package/bin/commands/iterate/helpers.mjs +15 -2
  124. package/bin/commands/iterate/index.mjs +57 -16
  125. package/bin/commands/iterate/mark-ready.mjs +50 -10
  126. package/bin/commands/iterate/merge-plan.d.mts +19 -0
  127. package/bin/commands/iterate/merge-plan.mjs +88 -0
  128. package/bin/commands/iterate/merge-state.mjs +3 -0
  129. package/bin/commands/iterate/merge.d.mts +4 -15
  130. package/bin/commands/iterate/merge.mjs +18 -51
  131. package/bin/commands/iterate/merged-base-pull-requests.d.mts +2 -6
  132. package/bin/commands/iterate/merged-base-pull-requests.mjs +15 -11
  133. package/bin/commands/iterate/native-stack-rebase.mjs +11 -0
  134. package/bin/commands/iterate/render.d.mts +8 -2
  135. package/bin/commands/iterate/render.mjs +28 -42
  136. package/bin/commands/iterate/stack-trunk-conflict.mjs +17 -1
  137. package/bin/commands/iterate/stale-ancestry.mjs +4 -1
  138. package/bin/commands/iterate/stall.d.mts +5 -1
  139. package/bin/commands/iterate/stall.mjs +19 -7
  140. package/bin/commands/iterate/thread-mutation-routing.d.mts +1 -0
  141. package/bin/commands/iterate/thread-mutation-routing.mjs +4 -2
  142. package/bin/commands/iterate/unreported-required.mjs +5 -2
  143. package/bin/commands/mark-files-as-viewed.mjs +7 -0
  144. package/bin/commands/next-check.d.mts +31 -0
  145. package/bin/commands/next-check.mjs +46 -0
  146. package/bin/commands/playbook.d.mts +13 -0
  147. package/bin/commands/playbook.mjs +48 -0
  148. package/bin/commands/poll-mode.d.mts +8 -0
  149. package/bin/commands/poll-mode.mjs +14 -0
  150. package/bin/commands/poll-progress.d.mts +1 -0
  151. package/bin/commands/poll-progress.mjs +6 -3
  152. package/bin/commands/poll-quota.d.mts +3 -2
  153. package/bin/commands/poll-quota.mjs +7 -3
  154. package/bin/commands/poll-summary-explicit-instructions.d.mts +2 -0
  155. package/bin/commands/poll-summary-explicit-instructions.mjs +10 -1
  156. package/bin/commands/poll-summary-instructions.mjs +16 -17
  157. package/bin/commands/poll-summary.mjs +42 -17
  158. package/bin/commands/poll.d.mts +0 -1
  159. package/bin/commands/poll.mjs +11 -19
  160. package/bin/commands/quota-selection.d.mts +2 -1
  161. package/bin/commands/quota-selection.mjs +12 -7
  162. package/bin/commands/resolve-mutate.mjs +33 -5
  163. package/bin/commands/rest-stack-merge-readiness.d.mts +4 -0
  164. package/bin/commands/rest-stack-merge-readiness.mjs +74 -0
  165. package/bin/commands/rule-auto-resolve.mjs +9 -3
  166. package/bin/commands/shepherd-journal.d.mts +5 -4
  167. package/bin/commands/shepherd-journal.mjs +9 -8
  168. package/bin/commands/stack-drain.d.mts +1 -9
  169. package/bin/commands/stack-drain.mjs +50 -37
  170. package/bin/commands/stack-handoffs.d.mts +10 -0
  171. package/bin/commands/stack-handoffs.mjs +25 -0
  172. package/bin/commands/stack-stall.d.mts +5 -0
  173. package/bin/commands/stack-stall.mjs +11 -0
  174. package/bin/commands/stack-work.d.mts +5 -0
  175. package/bin/commands/stack-work.mjs +32 -0
  176. package/bin/comments/existing-reply-scan.d.mts +17 -0
  177. package/bin/comments/existing-reply-scan.mjs +41 -0
  178. package/bin/comments/rate-limit.mjs +2 -2
  179. package/bin/comments/resolve.d.mts +7 -1
  180. package/bin/comments/resolve.mjs +108 -18
  181. package/bin/comments/rest-reply.d.mts +9 -0
  182. package/bin/comments/rest-reply.mjs +95 -0
  183. package/bin/comments/rest-review-mutations.d.mts +22 -0
  184. package/bin/comments/rest-review-mutations.mjs +125 -0
  185. package/bin/comments/review-visibility.d.mts +1 -1
  186. package/bin/comments/review-visibility.mjs +3 -2
  187. package/bin/comments/sha-poll.mjs +7 -3
  188. package/bin/comments/thread-visibility.mjs +10 -5
  189. package/bin/comments/uncertain-replies.d.mts +11 -0
  190. package/bin/comments/uncertain-replies.mjs +121 -0
  191. package/bin/comments/uncertain-reply-guards.d.mts +10 -0
  192. package/bin/comments/uncertain-reply-guards.mjs +94 -0
  193. package/bin/comments/visible-comments.d.mts +1 -1
  194. package/bin/comments/visible-comments.mjs +4 -2
  195. package/bin/config/load.d.mts +37 -1
  196. package/bin/config/load.mjs +73 -1
  197. package/bin/config.json +6 -2
  198. package/bin/exit-codes.d.mts +3 -0
  199. package/bin/exit-codes.mjs +8 -0
  200. package/bin/github/api-telemetry-aggregate.d.mts +15 -4
  201. package/bin/github/api-telemetry-aggregate.mjs +57 -18
  202. package/bin/github/api-telemetry.d.mts +4 -0
  203. package/bin/github/api-telemetry.mjs +22 -8
  204. package/bin/github/apply-review-preflight.d.mts +20 -0
  205. package/bin/github/apply-review-preflight.mjs +61 -0
  206. package/bin/github/base-behind-cache.d.mts +15 -0
  207. package/bin/github/base-behind-cache.mjs +18 -0
  208. package/bin/github/batch-parse-suites.d.mts +4 -2
  209. package/bin/github/batch-parse-suites.mjs +6 -1
  210. package/bin/github/batch-parser-helpers.mjs +40 -2
  211. package/bin/github/batch-parsers.mjs +1 -0
  212. package/bin/github/batch-raw-rules.d.mts +26 -0
  213. package/bin/github/batch-raw-types.d.mts +8 -6
  214. package/bin/github/batch-stack-evidence.d.mts +28 -0
  215. package/bin/github/batch-stack-evidence.mjs +62 -0
  216. package/bin/github/batch.d.mts +15 -2
  217. package/bin/github/batch.mjs +34 -17
  218. package/bin/github/captured-proxy-agent.d.mts +3 -0
  219. package/bin/github/captured-proxy-agent.mjs +8 -0
  220. package/bin/github/check-annotations-batch.d.mts +0 -5
  221. package/bin/github/check-annotations-batch.mjs +25 -4
  222. package/bin/github/check-annotations.mjs +3 -1
  223. package/bin/github/client.d.mts +8 -19
  224. package/bin/github/client.mjs +45 -13
  225. package/bin/github/fingerprint-fields.d.mts +8 -0
  226. package/bin/github/fingerprint-fields.mjs +6 -1
  227. package/bin/github/fingerprint.d.mts +4 -2
  228. package/bin/github/fingerprint.mjs +6 -38
  229. package/bin/github/github-fetch.d.mts +2 -0
  230. package/bin/github/github-fetch.mjs +32 -0
  231. package/bin/github/gql/base-behind.gql +3 -0
  232. package/bin/github/gql/batch-pr-page.gql +16 -0
  233. package/bin/github/gql/batch-pr.gql +57 -0
  234. package/bin/github/gql/commit-check-contexts.gql +17 -0
  235. package/bin/github/gql/get-pr-body.gql +1 -0
  236. package/bin/github/gql/poll-stack-summary.gql +1 -0
  237. package/bin/github/gql/poll-summary-check-contexts.gql +5 -0
  238. package/bin/github/gql/poll-summary-fragment.gql +65 -15
  239. package/bin/github/gql/stack-handoff-check-runs.gql +42 -0
  240. package/bin/github/graphql-http.mjs +9 -4
  241. package/bin/github/graphql-internal-retry.d.mts +2 -0
  242. package/bin/github/graphql-internal-retry.mjs +1 -1
  243. package/bin/github/graphql-quota-probe.d.mts +6 -0
  244. package/bin/github/graphql-quota-probe.mjs +56 -0
  245. package/bin/github/graphql-session-refusal.d.mts +8 -0
  246. package/bin/github/graphql-session-refusal.mjs +14 -0
  247. package/bin/github/http-request.d.mts +1 -1
  248. package/bin/github/http-request.mjs +22 -6
  249. package/bin/github/http-utils.mjs +6 -0
  250. package/bin/github/legacy-dispatcher.d.mts +6 -0
  251. package/bin/github/legacy-dispatcher.mjs +44 -0
  252. package/bin/github/merge-target-rules.d.mts +12 -3
  253. package/bin/github/merge-target-rules.mjs +71 -34
  254. package/bin/github/mutation-policy.d.mts +6 -0
  255. package/bin/github/mutation-policy.mjs +21 -0
  256. package/bin/github/poll-summary-annotation-probe.mjs +17 -10
  257. package/bin/github/poll-summary-check-blockers.d.mts +1 -1
  258. package/bin/github/poll-summary-check-blockers.mjs +2 -2
  259. package/bin/github/poll-summary-check-hydration.mjs +25 -10
  260. package/bin/github/poll-summary-checks.d.mts +2 -2
  261. package/bin/github/poll-summary-checks.mjs +9 -6
  262. package/bin/github/poll-summary-command.d.mts +2 -0
  263. package/bin/github/poll-summary-command.mjs +24 -0
  264. package/bin/github/poll-summary-fingerprint.d.mts +1 -1
  265. package/bin/github/poll-summary-fingerprint.mjs +4 -2
  266. package/bin/github/poll-summary-handoff-fields.d.mts +3 -0
  267. package/bin/github/poll-summary-handoff-fields.mjs +73 -0
  268. package/bin/github/poll-summary-projector.d.mts +1 -1
  269. package/bin/github/poll-summary-projector.mjs +17 -24
  270. package/bin/github/poll-summary-raw.d.mts +70 -10
  271. package/bin/github/poll-summary-readiness.d.mts +6 -1
  272. package/bin/github/poll-summary-readiness.mjs +13 -4
  273. package/bin/github/poll-summary-rest-queue.d.mts +3 -0
  274. package/bin/github/poll-summary-rest-queue.mjs +12 -0
  275. package/bin/github/poll-summary-review.d.mts +1 -1
  276. package/bin/github/poll-summary-review.mjs +35 -20
  277. package/bin/github/poll-summary-route.mjs +19 -3
  278. package/bin/github/poll-summary-stack-page.d.mts +11 -0
  279. package/bin/github/poll-summary-stack-page.mjs +27 -0
  280. package/bin/github/poll-summary-unreported.d.mts +3 -1
  281. package/bin/github/poll-summary-unreported.mjs +6 -3
  282. package/bin/github/poll-summary.d.mts +4 -1
  283. package/bin/github/poll-summary.mjs +47 -33
  284. package/bin/github/queries.d.mts +7 -3
  285. package/bin/github/queries.mjs +8 -4
  286. package/bin/github/rate-limit-sample.d.mts +7 -0
  287. package/bin/github/rate-limit-sample.mjs +12 -0
  288. package/bin/github/reply-recovery-read.d.mts +50 -0
  289. package/bin/github/reply-recovery-read.mjs +118 -0
  290. package/bin/github/reply-thread-transcripts.d.mts +0 -1
  291. package/bin/github/reply-thread-transcripts.mjs +24 -1
  292. package/bin/github/rest-annotation-read.d.mts +5 -0
  293. package/bin/github/rest-annotation-read.mjs +44 -0
  294. package/bin/github/rest-batch-read.d.mts +12 -0
  295. package/bin/github/rest-batch-read.mjs +57 -0
  296. package/bin/github/rest-batch-snapshot.d.mts +30 -0
  297. package/bin/github/rest-batch-snapshot.mjs +115 -0
  298. package/bin/github/rest-batch-unavailable.d.mts +5 -0
  299. package/bin/github/rest-batch-unavailable.mjs +16 -0
  300. package/bin/github/rest-check-read.d.mts +34 -0
  301. package/bin/github/rest-check-read.mjs +124 -0
  302. package/bin/github/rest-comment-authorship.d.mts +7 -0
  303. package/bin/github/rest-comment-authorship.mjs +11 -0
  304. package/bin/github/rest-conditional-scope.d.mts +20 -0
  305. package/bin/github/rest-conditional-scope.mjs +44 -0
  306. package/bin/github/rest-explicit-summary-read.d.mts +3 -0
  307. package/bin/github/rest-explicit-summary-read.mjs +8 -0
  308. package/bin/github/rest-feedback-read.d.mts +36 -0
  309. package/bin/github/rest-feedback-read.mjs +147 -0
  310. package/bin/github/rest-feedback-validation.d.mts +5 -0
  311. package/bin/github/rest-feedback-validation.mjs +28 -0
  312. package/bin/github/rest-http.d.mts +12 -0
  313. package/bin/github/rest-http.mjs +108 -23
  314. package/bin/github/rest-identities.d.mts +22 -0
  315. package/bin/github/rest-identities.mjs +134 -0
  316. package/bin/github/rest-merge-stack-guard.d.mts +14 -0
  317. package/bin/github/rest-merge-stack-guard.mjs +41 -0
  318. package/bin/github/rest-merge.d.mts +29 -0
  319. package/bin/github/rest-merge.mjs +56 -0
  320. package/bin/github/rest-merged-base-read.d.mts +9 -0
  321. package/bin/github/rest-merged-base-read.mjs +48 -0
  322. package/bin/github/rest-pr-core.d.mts +52 -0
  323. package/bin/github/rest-pr-core.mjs +60 -0
  324. package/bin/github/rest-reader-core.d.mts +16 -0
  325. package/bin/github/rest-reader-core.mjs +154 -0
  326. package/bin/github/rest-review-read.d.mts +11 -0
  327. package/bin/github/rest-review-read.mjs +47 -0
  328. package/bin/github/rest-rules-read.d.mts +9 -0
  329. package/bin/github/rest-rules-read.mjs +154 -0
  330. package/bin/github/rest-session-refusal.d.mts +5 -0
  331. package/bin/github/rest-session-refusal.mjs +21 -0
  332. package/bin/github/rest-snapshot-context.d.mts +10 -0
  333. package/bin/github/rest-snapshot-context.mjs +1 -0
  334. package/bin/github/rest-stack-read.d.mts +21 -0
  335. package/bin/github/rest-stack-read.mjs +47 -0
  336. package/bin/github/rest-stack-summary.d.mts +8 -0
  337. package/bin/github/rest-stack-summary.mjs +62 -0
  338. package/bin/github/rest-summary-read.d.mts +7 -0
  339. package/bin/github/rest-summary-read.mjs +112 -0
  340. package/bin/github/rest-terminal-batch.d.mts +14 -0
  341. package/bin/github/rest-terminal-batch.mjs +45 -0
  342. package/bin/github/rest-text.mjs +7 -3
  343. package/bin/github/rest-thread-read.d.mts +4 -0
  344. package/bin/github/rest-thread-read.mjs +35 -0
  345. package/bin/github/rest-viewer-read.d.mts +2 -0
  346. package/bin/github/rest-viewer-read.mjs +41 -0
  347. package/bin/github/stack-bottom.d.mts +3 -0
  348. package/bin/github/stack-bottom.mjs +14 -0
  349. package/bin/github/stack-handoff-context.d.mts +25 -0
  350. package/bin/github/stack-handoff-context.mjs +12 -0
  351. package/bin/github/stack-handoff-page.d.mts +17 -0
  352. package/bin/github/stack-handoff-page.mjs +306 -0
  353. package/bin/github/stack-handoff.d.mts +16 -0
  354. package/bin/github/stack-handoff.mjs +115 -0
  355. package/bin/github/stack-order.d.mts +6 -0
  356. package/bin/github/stack-order.mjs +13 -0
  357. package/bin/github/stack-read.d.mts +7 -5
  358. package/bin/github/stack-read.mjs +22 -24
  359. package/bin/github/stack-size-hint.d.mts +3 -0
  360. package/bin/github/stack-size-hint.mjs +14 -0
  361. package/bin/github/suggestion-thread.mjs +27 -1
  362. package/bin/github/thread-comments.mjs +9 -1
  363. package/bin/github/transport-evidence.d.mts +20 -0
  364. package/bin/github/transport-evidence.mjs +26 -0
  365. package/bin/github/transport-mode.d.mts +2 -0
  366. package/bin/github/transport-mode.mjs +5 -0
  367. package/bin/github/transport.d.mts +19 -0
  368. package/bin/github/transport.mjs +126 -0
  369. package/bin/github/unsupported-rest.d.mts +4 -0
  370. package/bin/github/unsupported-rest.mjs +7 -0
  371. package/bin/mcp/server.mjs +52 -3
  372. package/bin/merge-status/derive.d.mts +2 -0
  373. package/bin/merge-status/derive.mjs +17 -13
  374. package/bin/merge-status/required-check-requirements.d.mts +7 -0
  375. package/bin/merge-status/required-check-requirements.mjs +15 -0
  376. package/bin/merge-status/requirements-format.d.mts +5 -0
  377. package/bin/merge-status/requirements-format.mjs +41 -12
  378. package/bin/merge-status/requirements.mjs +18 -7
  379. package/bin/state/base.mjs +41 -0
  380. package/bin/state/durable-state.d.mts +6 -0
  381. package/bin/state/durable-state.mjs +41 -0
  382. package/bin/state/graphql-quota-policy.mjs +23 -16
  383. package/bin/state/merge-request.d.mts +25 -0
  384. package/bin/state/merge-request.mjs +145 -0
  385. package/bin/state/pr-fingerprint.d.mts +15 -0
  386. package/bin/state/pr-fingerprint.mjs +22 -7
  387. package/bin/state/rest-cache.d.mts +3 -0
  388. package/bin/state/rest-cache.mjs +1 -0
  389. package/bin/state/rest-snapshot-report.d.mts +21 -0
  390. package/bin/state/rest-snapshot-report.mjs +52 -0
  391. package/bin/state/seen-comments.d.mts +3 -0
  392. package/bin/state/seen-comments.mjs +18 -0
  393. package/bin/types/api-usage.d.mts +9 -1
  394. package/bin/types/escalate.d.mts +1 -1
  395. package/bin/types/github.d.mts +23 -9
  396. package/bin/types/iterate.d.mts +24 -2
  397. package/bin/types/merge-action.d.mts +1 -1
  398. package/bin/types/merge-requirements.d.mts +7 -7
  399. package/bin/types/next-check.d.mts +12 -0
  400. package/bin/types/next-check.mjs +1 -0
  401. package/bin/types/poll-summary.d.mts +21 -0
  402. package/bin/types/report.d.mts +13 -1
  403. package/bin/types/review-thread.d.mts +1 -1
  404. package/bin/types.d.mts +1 -0
  405. package/bin/types.mjs +1 -0
  406. package/package.json +4 -3
  407. package/plugins/pr-shepherd/.codex-plugin/plugin.json +1 -1
  408. package/plugins/pr-shepherd/.codex.mcp.json +1 -1
  409. package/plugins/pr-shepherd/.mcp.json +1 -1
  410. package/plugins/pr-shepherd/skills/mark-files-as-viewed/SKILL.md +1 -1
  411. package/plugins/pr-shepherd/skills/pr-shepherd/SKILL.md +14 -51
  412. package/plugins/pr-shepherd/skills/pr-shepherd/references/ci-failure-triage.md +12 -16
  413. package/plugins/pr-shepherd/skills/pr-shepherd/references/cloud-event-loop.md +14 -0
  414. package/plugins/pr-shepherd/skills/pr-shepherd/references/create-pr.md +10 -0
  415. package/plugins/pr-shepherd/skills/pr-shepherd/references/fix-code-loop.md +10 -0
  416. package/plugins/pr-shepherd/skills/pr-shepherd/references/mcp-fallback.md +9 -0
  417. package/plugins/pr-shepherd/skills/reduce-pr-noise/references/classifiers.md +3 -1
  418. package/bin/github/gql/pr-fingerprint.gql +0 -75
  419. package/plugins/pr-shepherd/skills/pr-shepherd/references/journal.md +0 -7
  420. package/plugins/pr-shepherd/skills/pr-shepherd/references/review-mutations.md +0 -9
  421. package/plugins/pr-shepherd/skills/pr-shepherd/references/stack-merge.md +0 -7
@@ -1,32 +1,13 @@
1
- import type { PollSummaryItem, PollSummaryResult, PollSummaryStackAncestry, StackNextAction } from "../types.mts";
1
+ import type { PollSummaryResult, PollSummaryStackAncestry, StackNextAction } from "../types.mts";
2
2
  import type { ApiUsage, GraphqlQuotaWarning } from "../types/api-usage.mts";
3
- /** Agent-facing stack row. Planning keeps the richer PollSummaryItem. */
4
- interface StackLayerView {
5
- pr: number;
6
- title: string;
7
- url: string;
8
- state: PollSummaryItem["state"];
9
- shepherded: boolean;
10
- mergeable: boolean;
11
- blocker?: string;
12
- author?: string;
13
- owned?: true;
14
- isDraft?: true;
15
- isInMergeQueue?: true;
16
- position?: number;
17
- stackSize?: number;
18
- baseRefName: string;
19
- failing?: number;
20
- inProgress?: number;
21
- actionable?: number;
22
- checksIncomplete?: true;
23
- reviewIncomplete?: true;
24
- queueRemoval?: {
25
- reason: string | null;
26
- actor?: string;
27
- };
28
- }
3
+ import type { StackLayerView } from "./stack-layer-view.mts";
4
+ import type { NextCheck } from "../types/next-check.mts";
29
5
  export interface StackOverview {
6
+ transport?: "rest";
7
+ transportUnavailable?: Array<{
8
+ field: string;
9
+ reason: string;
10
+ }>;
30
11
  mode: "summary";
31
12
  repo: string;
32
13
  selection: Extract<PollSummaryResult["selection"], {
@@ -40,9 +21,10 @@ export interface StackOverview {
40
21
  instructions?: string[];
41
22
  apiUsage?: ApiUsage;
42
23
  quotaWarning?: GraphqlQuotaWarning;
24
+ pollMode?: "event";
25
+ nextCheck?: NextCheck;
43
26
  }
44
27
  /** Lean stack payload shared by Markdown, JSON, and MCP. */
45
28
  export declare function projectStackOverview(result: PollSummaryResult): StackOverview;
46
29
  /** Markdown for a stack overview. */
47
30
  export declare function formatStackOverview(overview: StackOverview): string;
48
- export {};
@@ -1,5 +1,8 @@
1
1
  import { stackLayerBlockReason } from "../commands/stack-layer-readiness.mjs";
2
2
  import { formatApiUsage, formatQuotaWarning } from "./api-usage-formatter.mjs";
3
+ import { formatTransportEvidence } from "./transport-formatter.mjs";
4
+ import { formatNextCheckLines } from "./next-check-format.mjs";
5
+ import { formatPrUrl } from "../pr-reference.mjs";
3
6
  /** Lean stack payload shared by Markdown, JSON, and MCP. */
4
7
  export function projectStackOverview(result) {
5
8
  const selection = result.selection;
@@ -10,32 +13,43 @@ export function projectStackOverview(result) {
10
13
  return {
11
14
  mode: "summary",
12
15
  repo: result.repo,
16
+ ...(result.transport && { transport: result.transport }),
17
+ ...(result.transportUnavailable?.length && {
18
+ transportUnavailable: result.transportUnavailable,
19
+ }),
13
20
  selection,
14
21
  reason: result.reason,
15
- ...(result.stackMergeable !== undefined && { stackMergeable: result.stackMergeable }),
22
+ ...(result.stackMergeable && { stackMergeable: true }),
16
23
  ...(result.nextAction && { nextAction: result.nextAction }),
17
24
  prs: result.prs.map((item) => projectLayer(item, stale.has(item.pr))),
18
25
  ...(result.stackAncestry?.length && { stackAncestry: result.stackAncestry }),
19
26
  ...(result.instructions?.length && { instructions: result.instructions }),
20
27
  ...(result.apiUsage && { apiUsage: result.apiUsage }),
21
28
  ...(result.quotaWarning && { quotaWarning: result.quotaWarning }),
29
+ ...(result.pollMode && { pollMode: result.pollMode }),
30
+ ...(result.nextCheck && { nextCheck: result.nextCheck }),
22
31
  };
23
32
  }
24
33
  function projectLayer(item, stale) {
25
- const blocker = layerBlocker(item, stale);
34
+ // A merged or closed layer's state already says it will not merge; skip the `closed` blocker.
35
+ const open = item.state === "OPEN";
36
+ const blocker = open ? layerBlocker(item, stale) : undefined;
26
37
  const removal = item.queueRemoval;
27
38
  return {
28
39
  pr: item.pr,
40
+ ...(item.transport && { transport: item.transport }),
41
+ ...(item.transportUnavailable?.length && { transportUnavailable: item.transportUnavailable }),
29
42
  title: item.title,
30
43
  url: item.url,
31
44
  state: item.state,
32
- shepherded: item.readyReceipt === true,
33
- mergeable: blocker === undefined,
45
+ ...(item.readyReceipt === true && { shepherded: true }),
46
+ ...(open && { mergeable: blocker === undefined }),
34
47
  ...(blocker && { blocker }),
35
48
  ...(item.authorLogin && { author: item.authorLogin }),
36
49
  ...(item.owned && { owned: true }),
37
50
  ...(item.isDraft && { isDraft: true }),
38
51
  ...(item.isInMergeQueue && { isInMergeQueue: true }),
52
+ ...(item.requiresMergeQueue !== undefined && { requiresMergeQueue: item.requiresMergeQueue }),
39
53
  ...(item.stack && { position: item.stack.position, stackSize: item.stack.size }),
40
54
  baseRefName: item.baseRefName,
41
55
  ...(blocker === "failing-checks" &&
@@ -80,15 +94,15 @@ export function formatStackOverview(overview) {
80
94
  const lines = [
81
95
  `# ${overview.repo} stack #${stack.stackNumber}${stackTerminalTag(overview.nextAction)} — ${overview.reason}`,
82
96
  "",
83
- `Stack: #${stack.stackNumber} · anchor PR #${stack.anchor} · ${stack.stackSize} layers · mode \`${overview.mode}\``,
84
- ...(overview.stackMergeable !== undefined
85
- ? [`stackMergeable: ${overview.stackMergeable}`]
86
- : []),
97
+ `anchor PR #${stack.anchor} · ${stack.stackSize} layers · mode \`${overview.mode}\`${overview.pollMode ? ` · pollMode \`${overview.pollMode}\`` : ""}`,
98
+ ...(overview.stackMergeable ? ["stackMergeable: true"] : []),
87
99
  ...(overview.nextAction ? [`nextAction: ${overview.nextAction}`] : []),
100
+ ...formatNextCheckLines(overview.nextCheck),
88
101
  "",
89
102
  "## Layers",
90
103
  "",
91
- ...overview.prs.flatMap(formatLayerLines),
104
+ ...overview.prs.flatMap((layer, i) => formatLayerLines(layer, i, overview)),
105
+ ...formatTransportEvidence(overview),
92
106
  ];
93
107
  if (overview.stackAncestry?.length) {
94
108
  lines.push("", "## Stack ancestry", "");
@@ -107,10 +121,16 @@ export function formatStackOverview(overview) {
107
121
  lines.push("", usage);
108
122
  return lines.join("\n");
109
123
  }
110
- function formatLayerLines(layer) {
124
+ // `inOrder`: the list index shows the position; a row links only a URL the repo/number don't imply.
125
+ function formatLayerLines(layer, index, overview) {
126
+ const inOrder = layer.position === index + 1 && layer.stackSize === overview.selection.stackSize;
111
127
  const facts = [
112
- layer.shepherded ? "shepherded" : "not shepherded",
113
- layer.mergeable ? "mergeable" : `not mergeable (\`${layer.blocker ?? "unknown"}\`)`,
128
+ layer.shepherded ? "shepherded" : undefined,
129
+ layer.mergeable === undefined
130
+ ? undefined
131
+ : layer.mergeable
132
+ ? "mergeable"
133
+ : `not mergeable (\`${layer.blocker ?? "unknown"}\`)`,
114
134
  layer.author ? `owner \`@${layer.author}\`` : undefined,
115
135
  layer.owned ? "owned" : undefined,
116
136
  ].filter((fact) => fact !== undefined);
@@ -118,7 +138,10 @@ function formatLayerLines(layer) {
118
138
  layer.state,
119
139
  layer.isDraft ? "draft" : undefined,
120
140
  layer.isInMergeQueue ? "in merge queue" : undefined,
121
- layer.position !== undefined && layer.stackSize !== undefined
141
+ layer.requiresMergeQueue !== undefined
142
+ ? `merge queue ${layer.requiresMergeQueue ? "required" : "not required"}`
143
+ : undefined,
144
+ !inOrder && layer.position !== undefined && layer.stackSize !== undefined
122
145
  ? `position ${layer.position}/${layer.stackSize}`
123
146
  : undefined,
124
147
  `base \`${layer.baseRefName}\``,
@@ -132,7 +155,9 @@ function formatLayerLines(layer) {
132
155
  : undefined,
133
156
  ].filter((detail) => detail !== undefined);
134
157
  return [
135
- `- [PR #${layer.pr}: ${layer.title}](${layer.url}) — ${facts.join(" · ")}`,
158
+ `- ${layer.url === formatPrUrl(overview.repo, layer.pr) ? `PR #${layer.pr}: ${layer.title}` : `[PR #${layer.pr}: ${layer.title}](${layer.url})`}${facts.length > 0 ? ` — ${facts.join(" · ")}` : ""}`,
136
159
  ` - ${details.join(" · ")}`,
160
+ ...(layer.transport ? [` - transport \`${layer.transport}\``] : []),
161
+ ...(layer.transportUnavailable ?? []).map(({ field, reason }) => ` - unavailable \`${field}\`: ${reason}`),
137
162
  ];
138
163
  }
@@ -0,0 +1,5 @@
1
+ /** Strip the global flag before subcommand detection; validation follows help. */
2
+ export declare function extractTransportArgs(raw: string[]): {
3
+ args: string[];
4
+ transport?: string;
5
+ };
@@ -0,0 +1,23 @@
1
+ /** Strip the global flag before subcommand detection; validation follows help. */
2
+ export function extractTransportArgs(raw) {
3
+ const args = [];
4
+ let transport;
5
+ for (let index = 0; index < raw.length; index += 1) {
6
+ const arg = raw[index];
7
+ if (arg.startsWith("--transport=")) {
8
+ transport = arg.slice("--transport=".length);
9
+ }
10
+ else if (arg === "--transport") {
11
+ const next = raw[index + 1];
12
+ transport = next && !next.startsWith("-") ? raw[++index] : "";
13
+ }
14
+ else {
15
+ args.push(arg);
16
+ // A flag-looking string can be another option's literal value.
17
+ if (FLAGS_WITH_VALUES.has(arg) && index + 1 < raw.length)
18
+ args.push(raw[++index]);
19
+ }
20
+ }
21
+ return { args, transport };
22
+ }
23
+ import { FLAGS_WITH_VALUES } from "./arg-flags.mjs";
@@ -0,0 +1,9 @@
1
+ interface TransportEvidence {
2
+ transport?: "rest";
3
+ transportUnavailable?: Array<{
4
+ field: string;
5
+ reason: string;
6
+ }>;
7
+ }
8
+ export declare function formatTransportEvidence(value: TransportEvidence): string[];
9
+ export {};
@@ -0,0 +1,13 @@
1
+ export function formatTransportEvidence(value) {
2
+ return [
3
+ ...(value.transport ? [`**transport** \`${value.transport}\``] : []),
4
+ ...(value.transportUnavailable?.length
5
+ ? [
6
+ "",
7
+ "## Unavailable transport fields",
8
+ "",
9
+ ...value.transportUnavailable.map(({ field, reason }) => `- \`${field}\`: ${reason}`),
10
+ ]
11
+ : []),
12
+ ];
13
+ }
@@ -1,25 +1,60 @@
1
1
  /* eslint-disable max-lines */
2
2
  /** CLI argument parsing and subcommand dispatch for pr-shepherd. See --help for usage. */
3
3
  import { readFileSync } from "node:fs";
4
- import { EXIT, errorToExitCode } from "./exit-codes.mjs";
4
+ import { EXIT, applyReviewResultToExitCode, errorToExitCode } from "./exit-codes.mjs";
5
5
  import { runResolveMutate } from "./commands/resolve.mjs";
6
6
  import { runLogFile } from "./commands/log-file.mjs";
7
+ import { handleClean } from "./cli/clean-handler.mjs";
7
8
  import { parseCommonArgs, getFlag, hasFlag, parseList } from "./cli/args.mjs";
8
9
  import { isDefaultPollInvocation, validateDefaultPollArgs } from "./cli/default-poll.mjs";
9
10
  import { USAGE, helpKeyForArgs, maybePrintHelp } from "./cli/help.mjs";
10
11
  import { formatMutateResult } from "./cli/formatters.mjs";
11
- import { handleClean, handleCommitSuggestion, handleSuggestionPatches, handleIterate, handleMarkFilesAsViewed, } from "./cli/handlers.mjs";
12
+ import { handleCommitSuggestion, handleSuggestionPatches, handleIterate, handleMarkFilesAsViewed, } from "./cli/handlers.mjs";
12
13
  import { handleJournal } from "./cli/journal-handler.mjs";
13
14
  import { handleJournalExtract } from "./cli/journal-extract-handler.mjs";
15
+ import { handlePlaybook } from "./cli/playbook-handler.mjs";
14
16
  import { handlePoll } from "./cli/poll-handler.mjs";
15
17
  import { warnPrrcThreadIds, validateRequireSha, rejectPrrcMinimizeIds, } from "./cli/resolve-validators.mjs";
16
18
  import { handleCheckBlocker } from "./cli/check-blocker-handler.mjs";
17
19
  import { handleQueueRemoval } from "./cli/queue-removal-handler.mjs";
18
20
  import { setupLog } from "./log/setup.mjs";
21
+ import { rejectInvalidPollModeFlag, runInEventStateScope, runInPollModeScope, } from "./cli/event-state-scope.mjs";
22
+ import { handleApplyMerge } from "./cli/apply-merge-handler.mjs";
23
+ import { getLocalHeadSha } from "./commands/suggestion-patch-git.mjs";
24
+ import { extractTransportArgs } from "./cli/transport-args.mjs";
25
+ import { parseGithubTransport } from "./github/transport-mode.mjs";
26
+ import { runWithGithubTransport } from "./github/transport.mjs";
19
27
  // ---------------------------------------------------------------------------
20
28
  // Entry
21
29
  // ---------------------------------------------------------------------------
22
30
  export async function main(argv) {
31
+ const extracted = extractTransportArgs(argv.slice(2));
32
+ const dispatched = [...argv.slice(0, 2), ...extracted.args];
33
+ // Help and version retain their I/O-free short circuit, even with invalid flags.
34
+ if (extracted.args.some((arg) => arg === "--help" || arg === "-h") ||
35
+ extracted.args[0] === "--version" ||
36
+ extracted.args[0] === "-v" ||
37
+ extracted.args[0] === "log-file" ||
38
+ extracted.args[0] === "playbook" ||
39
+ (extracted.args[0] === "admin" && extracted.args[1] === "log-file") ||
40
+ (extracted.args[0] === "journal" && extracted.args[1] === "extract")) {
41
+ return dispatch(dispatched);
42
+ }
43
+ let mode;
44
+ try {
45
+ mode =
46
+ extracted.transport === undefined ? undefined : parseGithubTransport(extracted.transport);
47
+ }
48
+ catch (error) {
49
+ process.stderr.write(`pr-shepherd: ${error instanceof Error ? error.message : String(error)}\n`);
50
+ process.exitCode = EXIT.USAGE;
51
+ return;
52
+ }
53
+ return runWithGithubTransport(mode, () => dispatch(dispatched), {
54
+ verbose: extracted.args.includes("--verbose"),
55
+ });
56
+ }
57
+ async function dispatch(argv) {
23
58
  const args = argv.slice(2); // strip node + script path
24
59
  const subcommand = args[0];
25
60
  if (subcommand === "--version" || subcommand === "-v") {
@@ -69,6 +104,17 @@ export async function main(argv) {
69
104
  if (subcommand !== undefined && legacyReplacement[subcommand] !== undefined) {
70
105
  warnLegacyAlias(subcommand, legacyReplacement[subcommand]);
71
106
  }
107
+ // A local read of shipped files: no GitHub, git, or log I/O.
108
+ if (subcommand === "playbook") {
109
+ handlePlaybook(args.slice(1));
110
+ return;
111
+ }
112
+ // Event mode enters the durable state scope before the log resolves its path.
113
+ return runInEventStateScope(args, () => dispatchLogged(argv));
114
+ }
115
+ async function dispatchLogged(argv) {
116
+ const args = argv.slice(2);
117
+ const subcommand = args[0];
72
118
  // Initialize the per-worktree log and install a stdout tee.
73
119
  await setupLog(argv);
74
120
  if (isDefaultPollInvocation(subcommand)) {
@@ -124,6 +170,9 @@ function warnLegacyAlias(alias, replacement) {
124
170
  async function handleApply(args) {
125
171
  const action = args[0];
126
172
  switch (action) {
173
+ case "merge":
174
+ await handleApplyMerge(args.slice(1));
175
+ return;
127
176
  case "review":
128
177
  await handleResolve(args.slice(1));
129
178
  return;
@@ -167,13 +216,16 @@ function readVersion() {
167
216
  async function handleLogFile(args, usageKey = "log-file") {
168
217
  if (maybePrintHelp(args, usageKey))
169
218
  return;
219
+ if (rejectInvalidPollModeFlag(args, usageKey))
220
+ return;
170
221
  const jsonOut = args.some((a) => a === "--format=json") ||
171
222
  (() => {
172
223
  const idx = args.indexOf("--format");
173
224
  return idx !== -1 && args[idx + 1] === "json";
174
225
  })();
175
226
  try {
176
- const result = await runLogFile();
227
+ // Report the log the matching tick writes: event mode keeps it in durable state.
228
+ const result = await runInPollModeScope(args, () => runLogFile());
177
229
  process.stdout.write(jsonOut ? `${JSON.stringify(result, null, 2)}\n` : `${result.path}\n`);
178
230
  }
179
231
  catch (e) {
@@ -189,6 +241,7 @@ async function handleResolve(args, command = "apply review") {
189
241
  const dismissReviewIds = parseList(getFlag(extra, "--dismiss-review-ids"));
190
242
  const dismissMessage = getFlag(extra, "--message") ?? undefined;
191
243
  const requireSha = getFlag(extra, "--require-sha") ?? undefined;
244
+ const adoptExistingReplies = hasFlag(extra, "--adopt-existing-replies");
192
245
  warnPrrcThreadIds(resolveThreadIds);
193
246
  if (!validateRequireSha(requireSha))
194
247
  return;
@@ -216,12 +269,14 @@ async function handleResolve(args, command = "apply review") {
216
269
  minimizeCommentIds,
217
270
  dismissReviewIds,
218
271
  dismissMessage,
219
- requireSha,
272
+ // `HEAD` reads this checkout's commit (read-only `git rev-parse HEAD`), as the shell would.
273
+ requireSha: requireSha === "HEAD" ? await getLocalHeadSha() : requireSha,
274
+ ...(adoptExistingReplies && { adoptExistingReplies }),
220
275
  });
221
276
  process.stdout.write(globalOpts.format === "json"
222
277
  ? `${JSON.stringify(result, null, 2)}\n`
223
278
  : `${formatMutateResult(result)}\n`);
224
- if (result.errors.length > 0) {
225
- process.exitCode = result.rateLimit ? EXIT.TEMPFAIL : EXIT.UNAVAILABLE;
226
- }
279
+ const exitCode = applyReviewResultToExitCode(result);
280
+ if (exitCode !== EXIT.OK)
281
+ process.exitCode = exitCode;
227
282
  }
@@ -0,0 +1,4 @@
1
+ import type { RestMergeOptions } from "../github/rest-merge.mts";
2
+ export declare function validateApplyMergeOptions(input: RestMergeOptions): void;
3
+ export declare function sameMergeOptions(left: RestMergeOptions, right: RestMergeOptions): boolean;
4
+ export declare function validateApplyMergeTarget(input: RestMergeOptions, pr: number | undefined): void;
@@ -0,0 +1,34 @@
1
+ import { EXIT, ShepherdError } from "../exit-codes.mjs";
2
+ import { restMergeStackGuardKey, validateRestMergeStackGuard, } from "../github/rest-merge-stack-guard.mjs";
3
+ export function validateApplyMergeOptions(input) {
4
+ if (input.expectedStack !== undefined) {
5
+ try {
6
+ validateRestMergeStackGuard(input.expectedStack);
7
+ }
8
+ catch (error) {
9
+ throw new ShepherdError(error instanceof Error ? error.message : String(error), EXIT.DATAERR);
10
+ }
11
+ if (input.expectedStack.prefix.at(-1)?.headRefOid !== input.requireSha)
12
+ throw new ShepherdError("expectedStack prefix head must match --require-sha", EXIT.DATAERR);
13
+ }
14
+ if (typeof input.requireSha !== "string" || !/^[0-9a-f]{40}$/.test(input.requireSha))
15
+ throw new ShepherdError("--require-sha must be a full 40-character lowercase hex SHA", EXIT.DATAERR);
16
+ if (!["direct_merge", "merge_queue", "default"].includes(input.mergeAction))
17
+ throw new ShepherdError("--merge-action must be direct_merge, merge_queue, or default", EXIT.USAGE);
18
+ if (input.mergeMethod !== undefined && !["merge", "squash", "rebase"].includes(input.mergeMethod))
19
+ throw new ShepherdError("--method must be merge, squash, or rebase", EXIT.USAGE);
20
+ if (input.mergeMethod !== undefined && input.mergeAction !== "direct_merge")
21
+ throw new ShepherdError("--method is supported only with --merge-action direct_merge", EXIT.USAGE);
22
+ }
23
+ export function sameMergeOptions(left, right) {
24
+ return (left.requireSha === right.requireSha &&
25
+ left.mergeAction === right.mergeAction &&
26
+ left.mergeMethod === right.mergeMethod &&
27
+ restMergeStackGuardKey(left.expectedStack) === restMergeStackGuardKey(right.expectedStack));
28
+ }
29
+ export function validateApplyMergeTarget(input, pr) {
30
+ if (pr !== undefined &&
31
+ input.expectedStack !== undefined &&
32
+ input.expectedStack.prefix.at(-1)?.pr !== pr)
33
+ throw new ShepherdError("expectedStack prefix must end at the requested PR", EXIT.DATAERR);
34
+ }
@@ -0,0 +1,7 @@
1
+ import type { RepoInfo } from "../github/client.mts";
2
+ import type { RestMergeOptions, RestMergeResponse } from "../github/rest-merge.mts";
3
+ import type { ApplyMergeResult } from "./apply-merge.mts";
4
+ export declare function mergeResultFactory(pr: number, repo: RepoInfo, options: RestMergeOptions): {
5
+ failed: (message: string, uncertain?: true) => ApplyMergeResult;
6
+ project: (response: RestMergeResponse) => ApplyMergeResult;
7
+ };
@@ -0,0 +1,20 @@
1
+ export function mergeResultFactory(pr, repo, options) {
2
+ const failed = (message, uncertain) => ({
3
+ pr,
4
+ repo: `${repo.owner}/${repo.name}`,
5
+ status: "failed",
6
+ details: {
7
+ message,
8
+ expected_head_sha: options.requireSha,
9
+ merge_action: options.mergeAction,
10
+ ...(options.mergeMethod && { merge_method: options.mergeMethod }),
11
+ },
12
+ ...(uncertain && { uncertain }),
13
+ });
14
+ const project = (response) => ({
15
+ pr,
16
+ repo: `${repo.owner}/${repo.name}`,
17
+ ...response,
18
+ });
19
+ return { failed, project };
20
+ }
@@ -0,0 +1,20 @@
1
+ import type { RestMergeResponse } from "../github/rest-merge.mts";
2
+ export interface PullState {
3
+ state: string;
4
+ merged: boolean;
5
+ draft: boolean;
6
+ head: {
7
+ sha: string;
8
+ };
9
+ merge_commit_sha?: string;
10
+ }
11
+ /**
12
+ * Read the pull conditionally: inside the PR's conditional scope an unchanged pull answers a free
13
+ * 304 whose stored body equals GitHub's current one.
14
+ */
15
+ export declare function readPull(repo: {
16
+ owner: string;
17
+ name: string;
18
+ }, pr: number): Promise<PullState>;
19
+ /** The merged outcome of a pull already merged, carrying its merge commit when GitHub reports one. */
20
+ export declare function mergedResponse(pull: PullState, message: string): RestMergeResponse;
@@ -0,0 +1,15 @@
1
+ import { readRest, restRepoPath } from "../github/rest-reader-core.mjs";
2
+ /**
3
+ * Read the pull conditionally: inside the PR's conditional scope an unchanged pull answers a free
4
+ * 304 whose stored body equals GitHub's current one.
5
+ */
6
+ export function readPull(repo, pr) {
7
+ return readRest("GET", `${restRepoPath(repo)}/pulls/${pr}`);
8
+ }
9
+ /** The merged outcome of a pull already merged, carrying its merge commit when GitHub reports one. */
10
+ export function mergedResponse(pull, message) {
11
+ return {
12
+ status: "merged",
13
+ details: { message, ...(pull.merge_commit_sha && { sha: pull.merge_commit_sha }) },
14
+ };
15
+ }
@@ -0,0 +1,18 @@
1
+ export { validateApplyMergeOptions } from "./apply-merge-options.mts";
2
+ import { type RestMergeOptions, type RestMergeResponse } from "../github/rest-merge.mts";
3
+ export interface ApplyMergeInput extends RestMergeOptions {
4
+ prNumber?: number;
5
+ targetRepository?: {
6
+ owner: string;
7
+ name: string;
8
+ };
9
+ }
10
+ export interface ApplyMergeResult extends RestMergeResponse {
11
+ pr: number;
12
+ repo: string;
13
+ /** A mutation may already have reached GitHub. No new request will be issued. */
14
+ uncertain?: true;
15
+ note?: string;
16
+ }
17
+ /** Submit one guarded asynchronous request, or resume its existing UUID without resubmitting. */
18
+ export declare function runApplyMerge(input: ApplyMergeInput): Promise<ApplyMergeResult>;
@@ -0,0 +1,126 @@
1
+ import { mergeResultFactory } from "./apply-merge-output.mjs";
2
+ import { validateApplyMergeOptions, validateApplyMergeTarget, sameMergeOptions, } from "./apply-merge-options.mjs";
3
+ import { validateRestStackMergeReadiness } from "./rest-stack-merge-readiness.mjs";
4
+ export { validateApplyMergeOptions } from "./apply-merge-options.mjs";
5
+ import { EXIT, ShepherdError } from "../exit-codes.mjs";
6
+ import { getCurrentPrNumber, getRepoInfo } from "../github/client.mjs";
7
+ import { GitHubRequestError } from "../github/errors.mjs";
8
+ import { mergedResponse, readPull } from "./apply-merge-pull.mjs";
9
+ import { withRestConditionalScope } from "../github/rest-conditional-scope.mjs";
10
+ import { getGithubTransport } from "../github/transport.mjs";
11
+ import { getRestMerge, matchesRestMergeRequest, requestRestMerge, } from "../github/rest-merge.mjs";
12
+ import { claimMergeRequest, replaceFailedMergeRequest, readMergeRequest, writeMergeRequest, writeMergeRequestStatus, } from "../state/merge-request.mjs";
13
+ /** Submit one guarded asynchronous request, or resume its existing UUID without resubmitting. */
14
+ export async function runApplyMerge(input) {
15
+ validateApplyMergeOptions(input);
16
+ validateApplyMergeTarget(input, input.prNumber);
17
+ if (getGithubTransport() !== "rest")
18
+ throw new ShepherdError("apply merge requires REST transport; pass --transport rest", EXIT.USAGE);
19
+ const repo = input.targetRepository ?? (await getRepoInfo());
20
+ const pr = input.prNumber ?? (await getCurrentPrNumber());
21
+ if (!pr)
22
+ throw new ShepherdError("No open PR found for current branch. Pass a PR explicitly.", EXIT.UNAVAILABLE);
23
+ validateApplyMergeTarget(input, pr);
24
+ const key = { owner: repo.owner, repo: repo.name, pr };
25
+ // The PR's own conditional scope: the pull and stack reads share the ETag cache the iterate tick
26
+ // that printed this command just filled, so an unchanged resource answers a free 304 whose
27
+ // stored body equals GitHub's current one. The merge-async GET and PUT stay unconditional.
28
+ return withRestConditionalScope(key, () => submitApplyMerge(input, repo, pr, key));
29
+ }
30
+ async function submitApplyMerge(input, repo, pr, key) {
31
+ const options = {
32
+ requireSha: input.requireSha,
33
+ mergeAction: input.mergeAction,
34
+ ...(input.mergeMethod && { mergeMethod: input.mergeMethod }),
35
+ ...(input.expectedStack && { expectedStack: input.expectedStack }),
36
+ };
37
+ const { failed, project } = mergeResultFactory(pr, repo, options);
38
+ const existing = await readMergeRequest(key);
39
+ const pull = await readPull(repo, pr);
40
+ if (pull.merged === true)
41
+ return project(mergedResponse(pull, "Pull request is merged."));
42
+ if (pull.head?.sha !== input.requireSha)
43
+ return failed("Current PR head does not match --require-sha; no merge request was submitted.");
44
+ if (pull.state !== "open" || pull.draft !== false)
45
+ return failed("Pull request must be open and ready for review.");
46
+ const replaceFailed = existing &&
47
+ !existing.uncertain &&
48
+ ((existing.response?.status === "failed" && !sameMergeOptions(existing.options, options)) ||
49
+ (existing.response?.status === "enqueued" &&
50
+ existing.options.requireSha !== options.requireSha));
51
+ if (existing && !replaceFailed) {
52
+ if (!sameMergeOptions(existing.options, options))
53
+ return failed("A persisted asynchronous merge request has different options; reconcile its outcome before requesting another merge.", true);
54
+ if (existing.response?.status !== undefined && existing.response.status !== "pending")
55
+ return {
56
+ ...project(existing.response),
57
+ ...(existing.uncertain && { uncertain: true }),
58
+ ...(existing.response.status === "enqueued" && {
59
+ note: "This is the recorded request outcome; current merge-queue membership is not confirmed.",
60
+ }),
61
+ };
62
+ if (!existing.uuid)
63
+ return failed("The previous merge submission has an unknown outcome and no UUID. The PR is not merged; no duplicate request was submitted.", true);
64
+ try {
65
+ const response = await getRestMerge(repo, pr, existing.uuid);
66
+ if (response.status === "pending" &&
67
+ (!matchesRestMergeRequest(response, options) || response.details.uuid !== existing.uuid))
68
+ return failed("Asynchronous merge status does not match the persisted guarded request.", true);
69
+ await writeMergeRequestStatus(key, { ...existing, response });
70
+ return project(response);
71
+ }
72
+ catch (error) {
73
+ if (error instanceof GitHubRequestError && error.status === 404) {
74
+ const latest = await readPull(repo, pr);
75
+ if (latest.merged === true)
76
+ return project(mergedResponse(latest, "Pull request is merged; its asynchronous request has expired."));
77
+ return failed("The asynchronous merge UUID expired or is unavailable. The PR is not merged; its request outcome is unknown and no duplicate was submitted.", true);
78
+ }
79
+ throw error;
80
+ }
81
+ }
82
+ let record = {
83
+ version: 1,
84
+ options,
85
+ startedAtUnix: Math.floor(Date.now() / 1000),
86
+ };
87
+ await validateRestStackMergeReadiness(pr, repo, input.requireSha, input.expectedStack);
88
+ if (!(existing
89
+ ? await replaceFailedMergeRequest(key, existing, record)
90
+ : await claimMergeRequest(key, record)))
91
+ return failed("Another merge request is already being submitted; resume this command after its outcome is recorded.", true);
92
+ // Recovery may have selected an older durable intent; retain its unique generation token.
93
+ record = (await readMergeRequest(key));
94
+ let response;
95
+ try {
96
+ response = await requestRestMerge(repo, pr, options);
97
+ }
98
+ catch (error) {
99
+ if (error instanceof GitHubRequestError && error.status >= 400 && error.status < 500) {
100
+ await writeMergeRequest(key, {
101
+ ...record,
102
+ response: {
103
+ status: "failed",
104
+ details: { message: error.responseMessage ?? error.message },
105
+ },
106
+ });
107
+ throw error;
108
+ }
109
+ return failed(`Merge submission outcome is unknown: ${error instanceof Error ? error.message : String(error)}. No duplicate request will be submitted.`, true);
110
+ }
111
+ if (response.status === "pending" && !matchesRestMergeRequest(response, options)) {
112
+ const mismatch = failed("GitHub returned a pending merge with different or unverifiable options; its UUID was not adopted.", true);
113
+ await writeMergeRequest(key, {
114
+ ...record,
115
+ response: { status: mismatch.status, details: mismatch.details },
116
+ uncertain: true,
117
+ });
118
+ return mismatch;
119
+ }
120
+ await writeMergeRequest(key, {
121
+ ...record,
122
+ ...(response.details.uuid && response.status === "pending" && { uuid: response.details.uuid }),
123
+ response,
124
+ });
125
+ return project(response);
126
+ }
@@ -2,15 +2,18 @@ import { EXIT, ShepherdError } from "../exit-codes.mjs";
2
2
  import { fetchPrBatch } from "../github/batch.mjs";
3
3
  import { queueRemovalAppliesToHead } from "../github/queue-removal-freshness.mjs";
4
4
  import { getCurrentPrNumber, getRepoInfo } from "../github/client.mjs";
5
+ import { getGithubTransport } from "../github/transport.mjs";
5
6
  import { isCiQueueRemovalReason, writeQueueRemovalAcknowledgment, } from "../state/queue-removal-ack.mjs";
6
7
  /** Validate an observed native-stack CI queue removal before recording the caller's acknowledgment. */
7
8
  export async function applyQueueRemovalAck(input) {
9
+ requireQueueRemovalTransport();
8
10
  const repo = input.targetRepository ?? (await getRepoInfo());
9
11
  const pr = input.prNumber ?? (await getCurrentPrNumber());
10
12
  if (pr === null) {
11
13
  throw new ShepherdError("No open PR found for current branch. Pass a PR number explicitly.", EXIT.UNAVAILABLE);
12
14
  }
13
15
  const { data } = await fetchPrBatch(pr, repo);
16
+ requireQueueRemovalTransport(data.transport);
14
17
  const removal = data.latestMergeQueueRemoval;
15
18
  if (data.state !== "OPEN" ||
16
19
  !data.stack ||
@@ -41,3 +44,7 @@ export async function applyQueueRemovalAck(input) {
41
44
  }
42
45
  return { pr, repo: `${repo.owner}/${repo.name}`, acknowledgment };
43
46
  }
47
+ function requireQueueRemovalTransport(snapshotTransport) {
48
+ if (snapshotTransport === "rest" || getGithubTransport() === "rest")
49
+ throw new ShepherdError("Queue-removal acknowledgment is transport-unsupported: REST cannot verify current merge-queue removal evidence.", EXIT.UNAVAILABLE);
50
+ }