pr-shepherd 0.56.6 → 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 (420) 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 +26 -9
  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.mjs +11 -0
  138. package/bin/commands/iterate/render.d.mts +8 -2
  139. package/bin/commands/iterate/render.mjs +28 -42
  140. package/bin/commands/iterate/stack-trunk-conflict.mjs +17 -1
  141. package/bin/commands/iterate/stale-ancestry.mjs +4 -1
  142. package/bin/commands/iterate/stall.d.mts +5 -1
  143. package/bin/commands/iterate/stall.mjs +19 -7
  144. package/bin/commands/iterate/thread-mutation-routing.d.mts +1 -0
  145. package/bin/commands/iterate/thread-mutation-routing.mjs +4 -2
  146. package/bin/commands/iterate/unreported-required.mjs +5 -2
  147. package/bin/commands/mark-files-as-viewed.mjs +7 -0
  148. package/bin/commands/next-check.d.mts +31 -0
  149. package/bin/commands/next-check.mjs +46 -0
  150. package/bin/commands/playbook.d.mts +13 -0
  151. package/bin/commands/playbook.mjs +48 -0
  152. package/bin/commands/poll-mode.d.mts +8 -0
  153. package/bin/commands/poll-mode.mjs +14 -0
  154. package/bin/commands/poll-progress.d.mts +1 -0
  155. package/bin/commands/poll-progress.mjs +6 -3
  156. package/bin/commands/poll-quota.d.mts +3 -2
  157. package/bin/commands/poll-quota.mjs +7 -3
  158. package/bin/commands/poll-summary-explicit-instructions.d.mts +2 -0
  159. package/bin/commands/poll-summary-explicit-instructions.mjs +10 -1
  160. package/bin/commands/poll-summary-instructions.mjs +16 -17
  161. package/bin/commands/poll-summary.mjs +42 -17
  162. package/bin/commands/poll.d.mts +0 -1
  163. package/bin/commands/poll.mjs +11 -19
  164. package/bin/commands/quota-selection.d.mts +2 -1
  165. package/bin/commands/quota-selection.mjs +12 -7
  166. package/bin/commands/resolve-mutate.mjs +33 -5
  167. package/bin/commands/rest-stack-merge-readiness.d.mts +4 -0
  168. package/bin/commands/rest-stack-merge-readiness.mjs +74 -0
  169. package/bin/commands/rule-auto-resolve.mjs +9 -3
  170. package/bin/commands/shepherd-journal.d.mts +5 -4
  171. package/bin/commands/shepherd-journal.mjs +9 -8
  172. package/bin/commands/stack-drain.d.mts +1 -9
  173. package/bin/commands/stack-drain.mjs +50 -37
  174. package/bin/commands/stack-handoffs.d.mts +10 -0
  175. package/bin/commands/stack-handoffs.mjs +25 -0
  176. package/bin/commands/stack-stall.d.mts +5 -0
  177. package/bin/commands/stack-stall.mjs +11 -0
  178. package/bin/commands/stack-work.d.mts +5 -0
  179. package/bin/commands/stack-work.mjs +32 -0
  180. package/bin/comments/existing-reply-scan.d.mts +17 -0
  181. package/bin/comments/existing-reply-scan.mjs +41 -0
  182. package/bin/comments/rate-limit.mjs +2 -2
  183. package/bin/comments/resolve.d.mts +7 -1
  184. package/bin/comments/resolve.mjs +108 -18
  185. package/bin/comments/rest-reply.d.mts +9 -0
  186. package/bin/comments/rest-reply.mjs +95 -0
  187. package/bin/comments/rest-review-mutations.d.mts +22 -0
  188. package/bin/comments/rest-review-mutations.mjs +125 -0
  189. package/bin/comments/review-visibility.d.mts +1 -1
  190. package/bin/comments/review-visibility.mjs +3 -2
  191. package/bin/comments/sha-poll.mjs +7 -3
  192. package/bin/comments/thread-visibility.mjs +10 -5
  193. package/bin/comments/uncertain-replies.d.mts +11 -0
  194. package/bin/comments/uncertain-replies.mjs +121 -0
  195. package/bin/comments/uncertain-reply-guards.d.mts +10 -0
  196. package/bin/comments/uncertain-reply-guards.mjs +94 -0
  197. package/bin/comments/visible-comments.d.mts +1 -1
  198. package/bin/comments/visible-comments.mjs +4 -2
  199. package/bin/config/load.d.mts +37 -1
  200. package/bin/config/load.mjs +73 -1
  201. package/bin/config.json +6 -2
  202. package/bin/exit-codes.d.mts +3 -0
  203. package/bin/exit-codes.mjs +8 -0
  204. package/bin/github/api-telemetry-aggregate.d.mts +15 -4
  205. package/bin/github/api-telemetry-aggregate.mjs +57 -18
  206. package/bin/github/api-telemetry.d.mts +4 -0
  207. package/bin/github/api-telemetry.mjs +22 -8
  208. package/bin/github/apply-review-preflight.d.mts +20 -0
  209. package/bin/github/apply-review-preflight.mjs +61 -0
  210. package/bin/github/base-behind-cache.d.mts +15 -0
  211. package/bin/github/base-behind-cache.mjs +18 -0
  212. package/bin/github/batch-parse-suites.d.mts +4 -2
  213. package/bin/github/batch-parse-suites.mjs +6 -1
  214. package/bin/github/batch-parser-helpers.mjs +40 -2
  215. package/bin/github/batch-parsers.mjs +1 -0
  216. package/bin/github/batch-raw-rules.d.mts +26 -0
  217. package/bin/github/batch-raw-types.d.mts +8 -6
  218. package/bin/github/batch-stack-evidence.d.mts +28 -0
  219. package/bin/github/batch-stack-evidence.mjs +62 -0
  220. package/bin/github/batch.d.mts +15 -2
  221. package/bin/github/batch.mjs +34 -17
  222. package/bin/github/check-annotations-batch.d.mts +0 -5
  223. package/bin/github/check-annotations-batch.mjs +25 -4
  224. package/bin/github/check-annotations.mjs +3 -1
  225. package/bin/github/client.d.mts +8 -19
  226. package/bin/github/client.mjs +45 -13
  227. package/bin/github/fingerprint-fields.d.mts +8 -0
  228. package/bin/github/fingerprint-fields.mjs +6 -1
  229. package/bin/github/fingerprint.d.mts +4 -2
  230. package/bin/github/fingerprint.mjs +6 -38
  231. package/bin/github/github-fetch.d.mts +2 -0
  232. package/bin/github/github-fetch.mjs +19 -0
  233. package/bin/github/gql/base-behind.gql +3 -0
  234. package/bin/github/gql/batch-pr-page.gql +16 -0
  235. package/bin/github/gql/batch-pr.gql +57 -0
  236. package/bin/github/gql/commit-check-contexts.gql +17 -0
  237. package/bin/github/gql/get-pr-body.gql +1 -0
  238. package/bin/github/gql/poll-stack-summary.gql +1 -0
  239. package/bin/github/gql/poll-summary-check-contexts.gql +5 -0
  240. package/bin/github/gql/poll-summary-fragment.gql +65 -15
  241. package/bin/github/gql/stack-handoff-check-runs.gql +42 -0
  242. package/bin/github/graphql-http.mjs +9 -4
  243. package/bin/github/graphql-internal-retry.d.mts +2 -0
  244. package/bin/github/graphql-internal-retry.mjs +1 -1
  245. package/bin/github/graphql-quota-probe.d.mts +6 -0
  246. package/bin/github/graphql-quota-probe.mjs +56 -0
  247. package/bin/github/graphql-session-refusal.d.mts +8 -0
  248. package/bin/github/graphql-session-refusal.mjs +14 -0
  249. package/bin/github/http-request.d.mts +1 -1
  250. package/bin/github/http-request.mjs +22 -6
  251. package/bin/github/http-utils.mjs +6 -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.mjs +4 -1
  272. package/bin/github/poll-summary-rest-queue.d.mts +3 -0
  273. package/bin/github/poll-summary-rest-queue.mjs +12 -0
  274. package/bin/github/poll-summary-review.d.mts +1 -1
  275. package/bin/github/poll-summary-review.mjs +23 -11
  276. package/bin/github/poll-summary-route.mjs +19 -3
  277. package/bin/github/poll-summary-stack-page.d.mts +11 -0
  278. package/bin/github/poll-summary-stack-page.mjs +27 -0
  279. package/bin/github/poll-summary-unreported.d.mts +3 -1
  280. package/bin/github/poll-summary-unreported.mjs +6 -3
  281. package/bin/github/poll-summary.d.mts +4 -1
  282. package/bin/github/poll-summary.mjs +47 -33
  283. package/bin/github/queries.d.mts +7 -3
  284. package/bin/github/queries.mjs +8 -4
  285. package/bin/github/rate-limit-sample.d.mts +7 -0
  286. package/bin/github/rate-limit-sample.mjs +12 -0
  287. package/bin/github/reply-recovery-read.d.mts +50 -0
  288. package/bin/github/reply-recovery-read.mjs +118 -0
  289. package/bin/github/reply-thread-transcripts.d.mts +0 -1
  290. package/bin/github/reply-thread-transcripts.mjs +24 -1
  291. package/bin/github/rest-annotation-read.d.mts +5 -0
  292. package/bin/github/rest-annotation-read.mjs +44 -0
  293. package/bin/github/rest-batch-read.d.mts +12 -0
  294. package/bin/github/rest-batch-read.mjs +57 -0
  295. package/bin/github/rest-batch-snapshot.d.mts +30 -0
  296. package/bin/github/rest-batch-snapshot.mjs +115 -0
  297. package/bin/github/rest-batch-unavailable.d.mts +5 -0
  298. package/bin/github/rest-batch-unavailable.mjs +16 -0
  299. package/bin/github/rest-check-read.d.mts +34 -0
  300. package/bin/github/rest-check-read.mjs +124 -0
  301. package/bin/github/rest-comment-authorship.d.mts +7 -0
  302. package/bin/github/rest-comment-authorship.mjs +11 -0
  303. package/bin/github/rest-conditional-scope.d.mts +20 -0
  304. package/bin/github/rest-conditional-scope.mjs +44 -0
  305. package/bin/github/rest-explicit-summary-read.d.mts +3 -0
  306. package/bin/github/rest-explicit-summary-read.mjs +8 -0
  307. package/bin/github/rest-feedback-read.d.mts +36 -0
  308. package/bin/github/rest-feedback-read.mjs +147 -0
  309. package/bin/github/rest-feedback-validation.d.mts +5 -0
  310. package/bin/github/rest-feedback-validation.mjs +28 -0
  311. package/bin/github/rest-http.d.mts +12 -0
  312. package/bin/github/rest-http.mjs +108 -23
  313. package/bin/github/rest-identities.d.mts +22 -0
  314. package/bin/github/rest-identities.mjs +134 -0
  315. package/bin/github/rest-merge-stack-guard.d.mts +14 -0
  316. package/bin/github/rest-merge-stack-guard.mjs +41 -0
  317. package/bin/github/rest-merge.d.mts +29 -0
  318. package/bin/github/rest-merge.mjs +56 -0
  319. package/bin/github/rest-merged-base-read.d.mts +9 -0
  320. package/bin/github/rest-merged-base-read.mjs +48 -0
  321. package/bin/github/rest-pr-core.d.mts +52 -0
  322. package/bin/github/rest-pr-core.mjs +60 -0
  323. package/bin/github/rest-reader-core.d.mts +16 -0
  324. package/bin/github/rest-reader-core.mjs +154 -0
  325. package/bin/github/rest-review-read.d.mts +11 -0
  326. package/bin/github/rest-review-read.mjs +47 -0
  327. package/bin/github/rest-rules-read.d.mts +9 -0
  328. package/bin/github/rest-rules-read.mjs +154 -0
  329. package/bin/github/rest-session-refusal.d.mts +5 -0
  330. package/bin/github/rest-session-refusal.mjs +21 -0
  331. package/bin/github/rest-snapshot-context.d.mts +10 -0
  332. package/bin/github/rest-snapshot-context.mjs +1 -0
  333. package/bin/github/rest-stack-read.d.mts +21 -0
  334. package/bin/github/rest-stack-read.mjs +47 -0
  335. package/bin/github/rest-stack-summary.d.mts +8 -0
  336. package/bin/github/rest-stack-summary.mjs +62 -0
  337. package/bin/github/rest-summary-read.d.mts +7 -0
  338. package/bin/github/rest-summary-read.mjs +112 -0
  339. package/bin/github/rest-terminal-batch.d.mts +14 -0
  340. package/bin/github/rest-terminal-batch.mjs +45 -0
  341. package/bin/github/rest-text.mjs +7 -3
  342. package/bin/github/rest-thread-read.d.mts +4 -0
  343. package/bin/github/rest-thread-read.mjs +35 -0
  344. package/bin/github/rest-viewer-read.d.mts +2 -0
  345. package/bin/github/rest-viewer-read.mjs +41 -0
  346. package/bin/github/stack-bottom.d.mts +3 -0
  347. package/bin/github/stack-bottom.mjs +14 -0
  348. package/bin/github/stack-handoff-context.d.mts +25 -0
  349. package/bin/github/stack-handoff-context.mjs +12 -0
  350. package/bin/github/stack-handoff-page.d.mts +17 -0
  351. package/bin/github/stack-handoff-page.mjs +306 -0
  352. package/bin/github/stack-handoff.d.mts +16 -0
  353. package/bin/github/stack-handoff.mjs +115 -0
  354. package/bin/github/stack-order.d.mts +6 -0
  355. package/bin/github/stack-order.mjs +13 -0
  356. package/bin/github/stack-read.d.mts +7 -5
  357. package/bin/github/stack-read.mjs +22 -24
  358. package/bin/github/stack-size-hint.d.mts +3 -0
  359. package/bin/github/stack-size-hint.mjs +14 -0
  360. package/bin/github/suggestion-thread.mjs +27 -1
  361. package/bin/github/thread-comments.mjs +9 -1
  362. package/bin/github/transport-evidence.d.mts +20 -0
  363. package/bin/github/transport-evidence.mjs +26 -0
  364. package/bin/github/transport-mode.d.mts +2 -0
  365. package/bin/github/transport-mode.mjs +5 -0
  366. package/bin/github/transport.d.mts +19 -0
  367. package/bin/github/transport.mjs +126 -0
  368. package/bin/github/unsupported-rest.d.mts +4 -0
  369. package/bin/github/unsupported-rest.mjs +7 -0
  370. package/bin/mcp/server.mjs +52 -3
  371. package/bin/merge-status/derive.d.mts +2 -0
  372. package/bin/merge-status/derive.mjs +17 -13
  373. package/bin/merge-status/required-check-requirements.d.mts +7 -0
  374. package/bin/merge-status/required-check-requirements.mjs +15 -0
  375. package/bin/merge-status/requirements-format.d.mts +5 -0
  376. package/bin/merge-status/requirements-format.mjs +41 -12
  377. package/bin/merge-status/requirements.mjs +18 -7
  378. package/bin/state/base.mjs +41 -0
  379. package/bin/state/durable-state.d.mts +6 -0
  380. package/bin/state/durable-state.mjs +41 -0
  381. package/bin/state/graphql-quota-policy.mjs +23 -16
  382. package/bin/state/merge-request.d.mts +25 -0
  383. package/bin/state/merge-request.mjs +145 -0
  384. package/bin/state/pr-fingerprint.d.mts +15 -0
  385. package/bin/state/pr-fingerprint.mjs +22 -7
  386. package/bin/state/rest-cache.d.mts +3 -0
  387. package/bin/state/rest-cache.mjs +1 -0
  388. package/bin/state/rest-snapshot-report.d.mts +21 -0
  389. package/bin/state/rest-snapshot-report.mjs +52 -0
  390. package/bin/state/seen-comments.d.mts +3 -0
  391. package/bin/state/seen-comments.mjs +18 -0
  392. package/bin/types/api-usage.d.mts +9 -1
  393. package/bin/types/escalate.d.mts +1 -1
  394. package/bin/types/github.d.mts +23 -9
  395. package/bin/types/iterate.d.mts +24 -2
  396. package/bin/types/merge-action.d.mts +1 -1
  397. package/bin/types/merge-requirements.d.mts +7 -7
  398. package/bin/types/next-check.d.mts +12 -0
  399. package/bin/types/next-check.mjs +1 -0
  400. package/bin/types/poll-summary.d.mts +21 -0
  401. package/bin/types/report.d.mts +13 -1
  402. package/bin/types/review-thread.d.mts +1 -1
  403. package/bin/types.d.mts +1 -0
  404. package/bin/types.mjs +1 -0
  405. package/package.json +3 -2
  406. package/plugins/pr-shepherd/.codex-plugin/plugin.json +1 -1
  407. package/plugins/pr-shepherd/.codex.mcp.json +1 -1
  408. package/plugins/pr-shepherd/.mcp.json +1 -1
  409. package/plugins/pr-shepherd/skills/mark-files-as-viewed/SKILL.md +1 -1
  410. package/plugins/pr-shepherd/skills/pr-shepherd/SKILL.md +14 -51
  411. package/plugins/pr-shepherd/skills/pr-shepherd/references/ci-failure-triage.md +12 -16
  412. package/plugins/pr-shepherd/skills/pr-shepherd/references/cloud-event-loop.md +14 -0
  413. package/plugins/pr-shepherd/skills/pr-shepherd/references/create-pr.md +10 -0
  414. package/plugins/pr-shepherd/skills/pr-shepherd/references/fix-code-loop.md +10 -0
  415. package/plugins/pr-shepherd/skills/pr-shepherd/references/mcp-fallback.md +9 -0
  416. package/plugins/pr-shepherd/skills/reduce-pr-noise/references/classifiers.md +2 -0
  417. package/bin/github/gql/pr-fingerprint.gql +0 -75
  418. package/plugins/pr-shepherd/skills/pr-shepherd/references/journal.md +0 -7
  419. package/plugins/pr-shepherd/skills/pr-shepherd/references/review-mutations.md +0 -9
  420. package/plugins/pr-shepherd/skills/pr-shepherd/references/stack-merge.md +0 -7
@@ -1,11 +1,15 @@
1
1
  import { playbookPointer } from "../playbook-pointer.mjs";
2
- const FIX_CODE_CONTINUATION = "`[FIX_CODE]` is non-terminal. Iterate immediately with the same options.";
3
- /** Build the stale-CR clause appended to the `## Changes-requested reviews` instruction. */
2
+ // The `[FIX_CODE]` heading and the skill already say the action is non-terminal.
3
+ const FIX_CODE_CONTINUATION = "Rerun Shepherd now.";
4
+ /** The `[FIX_CODE]` recurrence step, whether plain or rewritten by the quota warning. */
5
+ export function isFixCodeContinuation(step) {
6
+ return step === FIX_CODE_CONTINUATION || /\[FIX_CODE\].*non-terminal/i.test(step);
7
+ }
8
+ /** Build the stale-CR step for `## Changes-requested reviews`. Empty when no human CR is stale. */
4
9
  export function buildCrStaleClause(reviews) {
5
- const human = reviews.some((r) => r.staleReview && !r.staleBotCr)
6
- ? " `[stale]` bullets are human CRs on an old commit. Ask the reviewer to re-review."
10
+ return reviews.some((r) => r.staleReview && !r.staleBotCr)
11
+ ? "`[stale]` changes-requested reviews are human CRs on an old commit. Ask the reviewer to re-review."
7
12
  : "";
8
- return human;
9
13
  }
10
14
  /**
11
15
  * Build the optional branch-update hint. Empty unless the branch is behind or conflicts with its
@@ -42,27 +46,21 @@ export function buildRepeatedWorkflowBranchRecoveryInstructions(baseBranch, hasE
42
46
  return instructions;
43
47
  }
44
48
  /**
45
- * Build the `Run the apply review: command` instruction. Steps stay here (not in the skill)
46
- * whenever the *unmodified, as-printed* command is unsafe without them:
47
- *
48
- * - `$HEAD_SHA`/`$DISMISS_MESSAGE` substitution: without it, the printed command has an
49
- * empty `--message`/invalid `--require-sha` and `apply review` rejects the mutation.
50
- * Contrast with what *does* stay in the skill's "Review-mutation mechanics" playbook —
51
- * dismiss-ID retention. The pointer below is load-bearing: without it, nothing in CLI output
52
- * tells the agent that playbook exists.
49
+ * Build the `apply review` step, with the rendered command inline. The step stays here (not in
50
+ * the skill) because the *as-printed* command is invalid without `$DISMISS_MESSAGE`: an empty
51
+ * `--message` makes `apply review` reject the mutation. `--require-sha` needs no substitution:
52
+ * the rendered command reads the pushed HEAD itself. "Even if you changed no code": the
53
+ * command records each item's disposition either way.
53
54
  */
54
- export function buildResolveCommandInstruction(resolveCommand) {
55
+ export function buildResolveCommandInstruction(resolveCommand, rendered) {
55
56
  if (!resolveCommand.hasMutations)
56
57
  return [];
57
- const instructions = [];
58
- if (resolveCommand.requiresHeadSha) {
59
- instructions.push("If you did not change code, replace `$HEAD_SHA` with `$(git rev-parse HEAD)` (it must equal the remote PR head). If you did, use the pushed SHA.");
60
- }
61
58
  if (resolveCommand.requiresDismissMessage) {
62
- instructions.push("Replace `$DISMISS_MESSAGE` with one sentence describing what changed.");
59
+ return [
60
+ `\`$DISMISS_MESSAGE\`: one sentence on what changed. Run even if no code changed: \`${rendered}\``,
61
+ ];
63
62
  }
64
- instructions.push(`Run the \`apply review:\` command above. ${playbookPointer("Review-mutation mechanics")}`);
65
- return instructions;
63
+ return [`Run, even if no code changed: \`${rendered}\``];
66
64
  }
67
65
  /** One pointer. Conclusion, rerun, and bare-check rules live in the CI playbook. */
68
66
  export function buildFailingCheckInstructions(checks) {
@@ -72,9 +70,12 @@ export function buildFailingCheckInstructions(checks) {
72
70
  }
73
71
  /** Update the PR branch after an external blocker merges or closes. Never rerun that job. */
74
72
  export function buildReleasedBlockerInstruction(prNumber) {
75
- return `Update this PR branch from its base with \`gh pr update-branch ${prNumber} --rebase\`. Do not rerun the job; a rerun retests the old merge ref.`;
73
+ return getGithubTransport() === "rest"
74
+ ? "Update this PR branch from its base using the repository's branch-update procedure. Do not rerun the job; a rerun retests the old merge ref."
75
+ : `Update this PR branch from its base with \`gh pr update-branch ${prNumber} --rebase\`. Do not rerun the job; a rerun retests the old merge ref.`;
76
76
  }
77
77
  /** Recurrence only. Commit, push, rerun, and SHA steps are earlier instructions. */
78
78
  export function buildFixCompletionInstruction() {
79
79
  return FIX_CODE_CONTINUATION;
80
80
  }
81
+ import { getGithubTransport } from "../../github/transport.mjs";
@@ -2,6 +2,8 @@ import { buildPrShepherdCommand } from "../../cli/runner.mjs";
2
2
  import { shouldMinimizeAuthor } from "../../comments/minimize-policy.mjs";
3
3
  import { isConfiguredBotAuthor, isHumanAuthor, } from "../../comments/authors.mjs";
4
4
  import { buildThreadMutationRouting } from "./thread-mutation-routing.mjs";
5
+ import { canGenerateGithubMutation } from "../../github/mutation-policy.mjs";
6
+ import { durableStateRequested } from "../../state/durable-state.mjs";
5
7
  function dedupeIds(ids) {
6
8
  const seen = new Set();
7
9
  const out = [];
@@ -15,7 +17,7 @@ function dedupeIds(ids) {
15
17
  }
16
18
  export function classifyReviewSummaries(summaries, approvals, minimizeApprovals, minimizeComments = "all", botUsernames = new Set(), unresolvedThreads = [], ruleAutoResolveIds = []) {
17
19
  const blockedReviewIds = new Set(unresolvedThreads.flatMap((t) => (t.reviewId !== undefined ? [t.reviewId] : [])));
18
- const eligible = (r) => r.viewerCanMinimize === true &&
20
+ const eligible = (r) => canGenerateGithubMutation(r.viewerCanMinimize, "minimize") &&
19
21
  shouldMinimizeAuthor(r.authorType, minimizeComments, r.author, botUsernames) &&
20
22
  !blockedReviewIds.has(r.id);
21
23
  // First-look summaries still need one tick to surface their body to the agent,
@@ -36,7 +38,7 @@ export function classifyReviewSummaries(summaries, approvals, minimizeApprovals,
36
38
  if (minimizeApprovals) {
37
39
  const surfacedApprovals = [];
38
40
  for (const r of approvals) {
39
- if (r.viewerCanMinimize === true &&
41
+ if (canGenerateGithubMutation(r.viewerCanMinimize, "minimize") &&
40
42
  shouldMinimizeAuthor(r.authorType, minimizeComments, r.author, botUsernames))
41
43
  minimizeIds.push(r.id);
42
44
  else
@@ -58,14 +60,24 @@ export function classifyReviewSummaries(summaries, approvals, minimizeApprovals,
58
60
  surfacedApprovals: approvals,
59
61
  };
60
62
  }
63
+ /**
64
+ * A durable-state session (event mode or a cloud session) can lose its uncertain-reply records
65
+ * between ticks. Its generated reply command opts in to adopting a reply GitHub already shows,
66
+ * so rerunning the same printed command cannot double-post. Direct apply requests never do.
67
+ */
68
+ function pushAdoptExistingReplies(argv, hasReply) {
69
+ if (hasReply && durableStateRequested())
70
+ argv.push("--adopt-existing-replies");
71
+ }
61
72
  export function buildResolveCommand(threads, resolutionOnlyThreads, allCommentIds, reviews, checks, prReference, botUsernames = new Set(), ruleAutoResolveThreadIds = [], viewerAuthorization, authorizationThreads = [], resolveOtherHumanThreads = "none") {
62
73
  const allThreads = [...threads, ...resolutionOnlyThreads];
63
74
  const routed = buildThreadMutationRouting(allThreads, botUsernames, ruleAutoResolveThreadIds, resolveOtherHumanThreads);
64
75
  const canReply = new Set(authorizationThreads
65
- .filter((thread) => thread.viewerCanReply === true)
76
+ .filter((thread) => canGenerateGithubMutation(thread.viewerCanReply, "reply"))
66
77
  .map((thread) => thread.id));
67
78
  const canResolve = new Set(authorizationThreads
68
- .filter((thread) => thread.viewerCanResolve === true)
79
+ .filter((thread) => thread.isResolved === false &&
80
+ canGenerateGithubMutation(thread.viewerCanResolve, "resolve"))
69
81
  .map((thread) => thread.id));
70
82
  const pairedResolveIds = new Set(routed.pairedResolveThreadIds);
71
83
  const replyThreadIds = routed.replyThreadIds.filter((id) => canReply.has(id) && (!pairedResolveIds.has(id) || canResolve.has(id)));
@@ -76,7 +88,7 @@ export function buildResolveCommand(threads, resolutionOnlyThreads, allCommentId
76
88
  const resolveThreadIds = dedupeIds([...pairedResolveThreadIds, ...standaloneResolveThreadIds]);
77
89
  // Bot/non-human CHANGES_REQUESTED reviews are auto-dismissed after the agent pushes a fix.
78
90
  // Human reviews are left for the reviewer to re-review or dismiss themselves.
79
- const dismissReviewIds = dedupeIds(viewerAuthorization?.viewerCanAdminister === true
91
+ const dismissReviewIds = dedupeIds(canGenerateGithubMutation(viewerAuthorization?.viewerCanAdminister, "dismiss")
80
92
  ? reviews
81
93
  .filter((r) => !isHumanAuthor(r) || isConfiguredBotAuthor(r, botUsernames))
82
94
  .map((r) => r.id)
@@ -99,6 +111,7 @@ export function buildResolveCommand(threads, resolutionOnlyThreads, allCommentId
99
111
  resolveArgv.push("--resolve-thread-ids", pairedResolveThreadIds.join(","));
100
112
  }
101
113
  resolveArgv.push("--message", "$DISMISS_MESSAGE");
114
+ pushAdoptExistingReplies(resolveArgv, hasReply);
102
115
  if (hasDismiss) {
103
116
  resolveArgv.push("--dismiss-review-ids", dismissReviewIds.join(","));
104
117
  }
@@ -139,6 +152,7 @@ export function buildResolveCommand(threads, resolutionOnlyThreads, allCommentId
139
152
  if (replyThreadIds.length > 0) {
140
153
  argv.push("--reply-thread-ids", replyThreadIds.join(","));
141
154
  argv.push("--message", "$DISMISS_MESSAGE");
155
+ pushAdoptExistingReplies(argv, true);
142
156
  }
143
157
  if (resolveThreadIds.length > 0) {
144
158
  argv.push("--resolve-thread-ids", resolveThreadIds.join(","));
@@ -151,9 +151,19 @@ export function buildEscalateHumanMessage(escalate, pr, opts) {
151
151
  const loc = t.path ? `\`${t.path}:${t.line ?? "?"}\`` : "(no location)";
152
152
  lines.push(`- thread \`${t.id}\` — ${loc} (${renderEscalateAuthor(t)}):`);
153
153
  lines.push("");
154
- for (const bodyLine of t.body.split("\n"))
155
- lines.push(` > ${bodyLine}`);
156
- lines.push("");
154
+ if (!t.comments?.length) {
155
+ for (const bodyLine of t.body.split("\n"))
156
+ lines.push(` > ${bodyLine}`);
157
+ lines.push("");
158
+ continue;
159
+ }
160
+ // The transcript starts with the root comment, whose body is the thread body.
161
+ for (const c of t.comments) {
162
+ lines.push(` - comment \`${c.id}\` (${renderEscalateAuthor(c)}):`, "");
163
+ for (const bodyLine of c.body.split("\n"))
164
+ lines.push(` > ${bodyLine}`);
165
+ lines.push("");
166
+ }
157
167
  }
158
168
  for (const r of escalate.changesRequestedReviews) {
159
169
  lines.push(`- review \`${r.id}\` (${renderEscalateAuthor(r)}):`);
@@ -20,6 +20,7 @@ import { annotationMarkerBody, checksWithActionableAnnotations } from "../check-
20
20
  import { threadTranscriptBody } from "../../threads/transcript.mjs";
21
21
  import { isHumanAuthor, isConfiguredBotAuthor } from "../../comments/authors.mjs";
22
22
  import { canRerunWorkflows } from "../../checks/conclusions.mjs";
23
+ import { canGenerateGithubMutation } from "../../github/mutation-policy.mjs";
23
24
  import { loadConfig } from "../../config/load.mjs";
24
25
  import { formatPrUrl } from "../../pr-reference.mjs";
25
26
  const EMPTY_RELEASED = new Set();
@@ -87,10 +88,12 @@ export async function handleFixCode(ctx) {
87
88
  const routedThreadMutations = buildThreadMutationRouting(allThreads, botUsernames, [...ruleAutoResolveIds], resolveOtherHumanThreads);
88
89
  const replyIdSet = new Set(routedThreadMutations.replyThreadIds);
89
90
  const resolveIdSet = new Set(routedThreadMutations.resolveThreadIds);
90
- const unauthorizedReplies = allThreads.filter((thread) => replyIdSet.has(thread.id) && thread.viewerCanReply !== true);
91
- const unauthorizedResolves = allThreads.filter((thread) => resolveIdSet.has(thread.id) && thread.viewerCanResolve !== true);
91
+ const unauthorizedReplies = allThreads.filter((thread) => replyIdSet.has(thread.id) && !canGenerateGithubMutation(thread.viewerCanReply, "reply"));
92
+ const unauthorizedResolves = allThreads.filter((thread) => resolveIdSet.has(thread.id) &&
93
+ (thread.isResolved !== false ||
94
+ !canGenerateGithubMutation(thread.viewerCanResolve, "resolve")));
92
95
  const unauthorizedDismissals = report.changesRequestedReviews.filter((review) => (!isHumanAuthor(review) || isConfiguredBotAuthor(review, botUsernames)) &&
93
- report.viewerAuthorization?.viewerCanAdminister !== true);
96
+ !canGenerateGithubMutation(report.viewerAuthorization?.viewerCanAdminister, "dismiss"));
94
97
  const skippedThreadIds = new Set([...unauthorizedReplies, ...unauthorizedResolves].map((thread) => thread.id));
95
98
  const mutationActionableThreads = report.threads.actionable.filter((thread) => !skippedThreadIds.has(thread.id) &&
96
99
  ((thread.path !== null && thread.line !== null) ||
@@ -98,6 +101,7 @@ export async function handleFixCode(ctx) {
98
101
  const retryableActionableThreads = mutationActionableThreads.filter((thread) => thread.path !== null && thread.line !== null);
99
102
  const protectedRuns = [];
100
103
  const stored = await readFixAttempts({ owner: repoOwner, repo: repoName, pr: prNumber });
104
+ await aliasThreadFixAttempts({ owner: repoOwner, name: repoName }, prNumber, retryableActionableThreads.map(({ id }) => id), stored);
101
105
  const countFixCodeAttempt = opts.persistSeen !== false;
102
106
  const priorThreadAttempts = previousFixAttempts(stored, retryableActionableThreads);
103
107
  const { threadAttempts, threadBodyHashes } = nextFixAttempts(stored, retryableActionableThreads, countFixCodeAttempt);
@@ -154,7 +158,7 @@ export async function handleFixCode(ctx) {
154
158
  const threads = report.threads.actionable.map(toAgentThread);
155
159
  const resolutionOnlyThreads = report.threads.resolutionOnly;
156
160
  const actionableComments = report.comments.actionable.map(toAgentComment);
157
- const rerunAuthorized = canRerunWorkflows(report.viewerAuthorization);
161
+ const rerunAuthorized = canRerunWorkflows(report.viewerAuthorization, report.transport);
158
162
  // A workflow run can only be rerun once it has fully completed; a runId still present among
159
163
  // in-progress checks (a sibling job from the same run) is not yet eligible.
160
164
  const inProgressWorkflowRunIds = new Set(report.checks.inProgress.flatMap((c) => (c.runId !== null ? [c.runId] : [])));
@@ -192,7 +196,7 @@ export async function handleFixCode(ctx) {
192
196
  const { changesRequestedReviews } = report;
193
197
  const hasConflicts = report.mergeStatus.status === "CONFLICTS";
194
198
  const isBehind = report.mergeStatus.status === "BEHIND";
195
- const { behindBaseHint } = loadConfig().iterate;
199
+ const { behindBaseHint, instructions: configuredInstructions } = loadConfig().iterate;
196
200
  // Only surface in-progress runs when a push is plausible — resolution-only and
197
201
  // summary-only iterations have no path to a push, so listing runs would prompt
198
202
  // unnecessary cancellation.
@@ -315,8 +319,12 @@ export async function handleFixCode(ctx) {
315
319
  bottomPr: report.stackBottomPr,
316
320
  })
317
321
  : undefined;
318
- const requeue = buildRemovedQueueRecovery(report, failingAgentChecks, opts.merge);
319
- const queueRemovalAcknowledgment = buildStackQueueRemovalAcknowledgment(report, failingAgentChecks);
322
+ const recovery = buildRemovedQueueRecovery(report, failingAgentChecks, opts.merge);
323
+ const requeue = recovery && !("unavailable" in recovery) ? recovery : undefined;
324
+ const acknowledgmentRecovery = buildStackQueueRemovalAcknowledgment(report, failingAgentChecks);
325
+ const queueRemovalAcknowledgment = acknowledgmentRecovery && !("unavailable" in acknowledgmentRecovery)
326
+ ? acknowledgmentRecovery
327
+ : undefined;
320
328
  const queueEjection = !currentEjectionCommit(report, failingAgentChecks)
321
329
  ? undefined
322
330
  : requeue
@@ -329,7 +337,11 @@ export async function handleFixCode(ctx) {
329
337
  const stackRebase = hasConflicts || (isBehind && exhaustedAttempts.length > 0) || queueEjection
330
338
  ? buildNativeStackLayerRebase(report.repo, { number: prNumber, baseBranch: baseLookup.branch }, stack, trunkConflict, report.stackBottomPr)
331
339
  : undefined;
332
- const instructions = buildFixInstructions(threads, actionableComments, checks, changesRequestedReviews, baseLookup.branch, resolveCommand, hasConflicts, prReference, cancelled.length, firstLookThreads, firstLookComments, firstLookSummaries, editedSummaries, inProgressRunIds, resolutionOnlyThreads, resolveOnlyCommand, behindBaseHint, isBehind, report.viewerAuthorization?.viewerCanUpdate === true, exhaustedAttempts.length > 0, stackRebase, queueEjection);
340
+ const instructions = buildFixInstructions(threads, actionableComments, checks, changesRequestedReviews, baseLookup.branch, resolveCommand, hasConflicts, prReference, cancelled.length, firstLookThreads, firstLookComments, firstLookSummaries, editedSummaries, inProgressRunIds, resolutionOnlyThreads, resolveOnlyCommand, behindBaseHint, isBehind, canGenerateGithubMutation(report.viewerAuthorization?.viewerCanUpdate, "ready"), exhaustedAttempts.length > 0, stackRebase, queueEjection, opts.instructions ?? configuredInstructions);
341
+ if (recovery && "unavailable" in recovery)
342
+ instructions.unshift(recovery.unavailable);
343
+ if (acknowledgmentRecovery && "unavailable" in acknowledgmentRecovery)
344
+ instructions.unshift(acknowledgmentRecovery.unavailable);
333
345
  if (failingAgentChecks.some((check) => releasedCheckNames.has(check.name))) {
334
346
  const completion = instructions.pop();
335
347
  instructions.push(buildReleasedBlockerInstruction(prNumber));
@@ -340,7 +352,11 @@ export async function handleFixCode(ctx) {
340
352
  instructions.unshift(...repairInstructions);
341
353
  }
342
354
  if (mergedBasePullRequests.length > 0) {
343
- instructions.unshift(`Inspect every PR under \`## Merged PRs matching the current base\`. If this PR is the remaining layer intended for a merged parent's base branch, run \`gh pr edit ${prReference} --base <verified-parent-base>\` after replacing \`<verified-parent-base>\` with that parent's shell-quoted base branch, then rerun Shepherd immediately and follow its fresh instructions instead of the remaining steps here. Otherwise keep the current base and follow the remaining conflict-resolution steps.`);
355
+ // `gh pr edit` goes through GraphQL; REST callers retarget with the pulls PATCH endpoint.
356
+ const retarget = report.transport === "rest"
357
+ ? `gh api --method PATCH repos/${report.repo}/pulls/${prNumber} -f base=<verified-parent-base>`
358
+ : `gh pr edit ${prReference} --base <verified-parent-base>`;
359
+ instructions.unshift(`Inspect every PR under \`## Merged PRs matching the current base\`. If this PR is the remaining layer intended for a merged parent's base branch, run \`${retarget}\` after replacing \`<verified-parent-base>\` with that parent's shell-quoted base branch, then rerun Shepherd immediately and follow its fresh instructions instead of the remaining steps here. Otherwise keep the current base and follow the remaining conflict-resolution steps.`);
344
360
  }
345
361
  const checkRunCount = countReportedChecks(report.checks);
346
362
  const suitesEmpty = report.headCheckSuitesEmpty === true;
@@ -390,3 +406,4 @@ export async function handleFixCode(ctx) {
390
406
  }
391
407
  return result;
392
408
  }
409
+ import { aliasThreadFixAttempts } from "../../github/rest-identities.mjs";
@@ -4,11 +4,3 @@ export declare function partitionFixThreads(threads: AgentThread[], resolveComma
4
4
  unlocatedMutatedThreads: AgentThread[];
5
5
  unlocatedThreads: AgentThread[];
6
6
  };
7
- export declare function reviewSectionRefs(input: {
8
- hasReviewThreads: boolean;
9
- hasUnlocatedSkipThreads: boolean;
10
- hasActionableComments: boolean;
11
- hasFailingChecks: boolean;
12
- hasAnnotations: boolean;
13
- hasChangesRequested: boolean;
14
- }): string[];
@@ -14,19 +14,3 @@ export function partitionFixThreads(threads, resolveCommand, resolveOnlyCommand)
14
14
  unlocatedThreads: threads.filter((thread) => (thread.path === null || thread.line === null) && !mutatedThreadIds.has(thread.id)),
15
15
  };
16
16
  }
17
- export function reviewSectionRefs(input) {
18
- const sections = [];
19
- if (input.hasReviewThreads)
20
- sections.push("`## Review threads`");
21
- if (input.hasUnlocatedSkipThreads)
22
- sections.push("`## Unlocated review threads (logged once — no mutation)`");
23
- if (input.hasActionableComments)
24
- sections.push("`## Actionable comments`");
25
- if (input.hasFailingChecks)
26
- sections.push("`## Failing checks`");
27
- if (input.hasAnnotations)
28
- sections.push("`## Check annotations`");
29
- if (input.hasChangesRequested)
30
- sections.push("`## Changes-requested reviews`");
31
- return sections;
32
- }
@@ -0,0 +1,9 @@
1
+ import type { InstructionStyle } from "../../config/load.mts";
2
+ /**
3
+ * The generic commit/push step and the journal step. Playbook style folds both into one pointer
4
+ * only when the generic push applies: the "Fix-code loop" playbook tells the caller to push to the
5
+ * PR head branch, which would conflict with a conflict or queue-recovery push step. Without the
6
+ * generic push, the journal step stays inline with its citation clause. The rerun
7
+ * step stays inline because it is the one step a halted loop cannot recover from.
8
+ */
9
+ export declare function buildPushJournalSteps(style: InstructionStyle, prReference: string | number, genericPush: boolean, wantsJournal: boolean): string[];
@@ -0,0 +1,35 @@
1
+ import { renderShellCommand } from "../../cli/runner.mjs";
2
+ import { loadConfig } from "../../config/load.mjs";
3
+ import { buildShepherdJournalCommand, buildShepherdJournalInstruction, } from "../shepherd-journal.mjs";
4
+ import { playbookPointer } from "../playbook-pointer.mjs";
5
+ const FIX_CODE_LOOP_PLAYBOOK = "Fix-code loop";
6
+ /**
7
+ * Playbook-style replacement for the invariant commit/push and journal steps. The
8
+ * procedure text lives in the "Fix-code loop" playbook; only the trigger and the concrete
9
+ * journal command stay in the per-tick output. `inline` style never calls this.
10
+ */
11
+ function buildFixLoopInstruction(prReference, journal) {
12
+ const parts = ["Commit and push any changes."];
13
+ if (journal)
14
+ parts.push(`Journal substantial decisions or rejections with \`${buildShepherdJournalCommand(prReference)}\`.`);
15
+ const cli = renderShellCommand([...loadConfig().cliCommand, "playbook", FIX_CODE_LOOP_PLAYBOOK]);
16
+ parts.push(`${playbookPointer(FIX_CODE_LOOP_PLAYBOOK)} CLI: \`${cli}\`.`);
17
+ return parts.join(" ");
18
+ }
19
+ /**
20
+ * The generic commit/push step and the journal step. Playbook style folds both into one pointer
21
+ * only when the generic push applies: the "Fix-code loop" playbook tells the caller to push to the
22
+ * PR head branch, which would conflict with a conflict or queue-recovery push step. Without the
23
+ * generic push, the journal step stays inline with its citation clause. The rerun
24
+ * step stays inline because it is the one step a halted loop cannot recover from.
25
+ */
26
+ export function buildPushJournalSteps(style, prReference, genericPush, wantsJournal) {
27
+ if (style === "playbook" && genericPush)
28
+ return [buildFixLoopInstruction(prReference, wantsJournal)];
29
+ const steps = [];
30
+ if (genericPush)
31
+ steps.push("Commit and push any changes.");
32
+ if (wantsJournal)
33
+ steps.push(buildShepherdJournalInstruction(prReference));
34
+ return steps;
35
+ }
@@ -23,6 +23,10 @@ export function buildTerminalCancelResult(report) {
23
23
  return {
24
24
  pr: report.pr,
25
25
  repo: report.repo,
26
+ ...(report.transport && { transport: report.transport }),
27
+ ...(report.transportUnavailable?.length && {
28
+ transportUnavailable: report.transportUnavailable,
29
+ }),
26
30
  status: report.status,
27
31
  mergeStateStatus: report.mergeStatus.mergeStateStatus,
28
32
  mergeStatus: report.mergeStatus.status,
@@ -103,7 +107,12 @@ export function buildActiveChecks(report) {
103
107
  }
104
108
  export function buildWaitLog(base) {
105
109
  const { summary, remainingSeconds } = base;
106
- const parts = [`WAIT: ${summary.passing} passing, ${summary.inProgress} in-progress`];
110
+ // Zero counts are trivial defaults; the line keeps whichever count is non-zero.
111
+ const counts = [
112
+ ...(summary.passing > 0 ? [`${summary.passing} passing`] : []),
113
+ ...(summary.inProgress > 0 ? [`${summary.inProgress} in-progress`] : []),
114
+ ];
115
+ const parts = counts.length > 0 ? [counts.join(", ")] : [];
107
116
  if ((base.inProgressChecks?.length ?? 0) > 0) {
108
117
  parts.push(`active checks: ${base
109
118
  .inProgressChecks.slice(0, 5)
@@ -133,7 +142,11 @@ export function buildWaitLog(base) {
133
142
  if (remainingSeconds > 0) {
134
143
  parts.push(`${remainingSeconds}s until auto-cancel`);
135
144
  }
136
- return parts.join(" — ");
145
+ if (parts.length > 0)
146
+ return `WAIT: ${parts.join(" — ")}`;
147
+ // Skipped, filtered, or superseded checks still count as reported CI data.
148
+ const reported = summary.skipped + summary.filtered + summary.superseded > 0;
149
+ return `WAIT: ${reported ? "no passing or in-progress checks" : "no checks reported"}`;
137
150
  }
138
151
  export function blockedCancelNote(base) {
139
152
  if (base.mergeStatus !== "BLOCKED")
@@ -28,6 +28,8 @@ import { findStaleNativeStackAncestry } from "./stale-ancestry.mjs";
28
28
  import { annotateBlockedWait, resolveCheckBlockerGate } from "./check-blocker-gate.mjs";
29
29
  import { buildUnreportedFixResult, planUnreportedRequired } from "./unreported-required.mjs";
30
30
  import { createCheckExecutionContext } from "../check-execution-context.mjs";
31
+ import { transportReadinessGaps } from "../../github/transport-evidence.mjs";
32
+ import { buildEscalateHumanMessage } from "./escalate.mjs";
31
33
  export function runIterate(opts) {
32
34
  return withIterateApiUsage(opts, () => runIterateCore(opts));
33
35
  }
@@ -194,7 +196,7 @@ async function runIterateCore(opts) {
194
196
  });
195
197
  }
196
198
  if (unreportedRepair.length > 0) {
197
- return annotateBlockedWait(await applyStallGuard(stallKey, stallTimeoutSeconds, headSha, base, prNumber, buildUnreportedFixResult(base, report, unreportedRepair), reportForWork, reviewSummaryIds), blockerGate);
199
+ return annotateBlockedWait(await applyStallGuard(stallKey, stallTimeoutSeconds, headSha, base, prNumber, buildUnreportedFixResult(base, report, unreportedRepair), reportForWork, reviewSummaryIds, opts.stallDeadlineSink), blockerGate);
198
200
  }
199
201
  const mergeStateResult = await handleActiveMergeState({
200
202
  enabled: opts.merge,
@@ -234,6 +236,24 @@ async function runIterateCore(opts) {
234
236
  const canMarkReady = report.status === "READY" &&
235
237
  report.mergeStatus.isDraft &&
236
238
  !report.mergeStatus.blockingBotReviewInProgress;
239
+ const gaps = transportReadinessGaps(report, report.mergeStatus.mergeStateStatus);
240
+ if (gaps.length > 0 &&
241
+ report.checks.inProgress.length === 0 &&
242
+ report.mergeStatus.mergeStateStatus !== "UNKNOWN") {
243
+ await clearReadyReceipt(receiptKey);
244
+ const escalation = {
245
+ triggers: ["transport-unsupported"],
246
+ unresolvedThreads: [],
247
+ ambiguousComments: [],
248
+ changesRequestedReviews: [],
249
+ suggestion: gaps.map(({ field, reason }) => `${field}: ${reason}`).join("; "),
250
+ };
251
+ return {
252
+ ...base,
253
+ action: "escalate",
254
+ escalate: { ...escalation, humanMessage: buildEscalateHumanMessage(escalation, report.pr) },
255
+ };
256
+ }
237
257
  const autoMarkReady = !opts.noAutoMarkReady && config.actions.autoMarkReady;
238
258
  const markReadyResult = await markReadyIfAuthorized(canMarkReady && autoMarkReady, base, report);
239
259
  if (markReadyResult)
@@ -250,7 +270,7 @@ async function runIterateCore(opts) {
250
270
  shouldCancel: false,
251
271
  log: `WAIT: PR #${base.pr} reached ready-delay but its stack readiness receipt could not be persisted`,
252
272
  };
253
- return annotateBlockedWait(await applyStallGuard(stallKey, stallTimeoutSeconds, headSha, base, prNumber, receiptWait, reportForWork, reviewSummaryIds), blockerGate);
273
+ return annotateBlockedWait(await applyStallGuard(stallKey, stallTimeoutSeconds, headSha, base, prNumber, receiptWait, reportForWork, reviewSummaryIds, opts.stallDeadlineSink), blockerGate);
254
274
  }
255
275
  await clearReadyDelay(report.pr, repoOwner, repoName);
256
276
  await clearStallState(stallKey);
@@ -283,14 +303,15 @@ async function runIterateCore(opts) {
283
303
  log: buildWaitLog(base),
284
304
  ...(hold && { stackDraftHold: hold }),
285
305
  };
286
- return annotateBlockedWait(await applyStallGuard(stallKey, stallTimeoutSeconds, headSha, base, prNumber, wait, reportForWork, reviewSummaryIds), blockerGate);
306
+ return annotateBlockedWait(await applyStallGuard(stallKey, stallTimeoutSeconds, headSha, base, prNumber, wait, reportForWork, reviewSummaryIds, opts.stallDeadlineSink), blockerGate);
287
307
  }
288
308
  async function recordReadyReceipt(key, report, context) {
289
309
  if (report.status !== "READY" ||
290
310
  report.mergeStatus.state !== "OPEN" ||
291
311
  report.mergeStatus.isDraft ||
292
312
  !report.headSha ||
293
- !report.baseRefOid)
313
+ !report.baseRefOid ||
314
+ transportReadinessGaps(report, report.mergeStatus.mergeStateStatus).length > 0)
294
315
  return false;
295
316
  try {
296
317
  const raw = await receiptSummaryForReport(key, report, context);
@@ -357,6 +378,7 @@ async function revalidateReadyReceipt(key, report, hasReadinessWork, context) {
357
378
  report.mergeStatus.isDraft ||
358
379
  !report.headSha ||
359
380
  !report.baseRefOid ||
381
+ transportReadinessGaps(report, report.mergeStatus.mergeStateStatus).length > 0 ||
360
382
  (!retainQueuedReceipt && (report.status !== "READY" || hasReadinessWork))) {
361
383
  await clearReadyReceipt(key);
362
384
  return false;
@@ -1,6 +1,14 @@
1
1
  import { graphql } from "../../github/http.mjs";
2
2
  import { MARK_PR_READY_MUTATION } from "../../github/queries.mjs";
3
3
  import { buildEscalateHumanMessage, buildEscalateSuggestion } from "./escalate.mjs";
4
+ import { rest } from "../../github/http.mjs";
5
+ import { githubOperation, isCcrTransport } from "../../github/transport.mjs";
6
+ import { canGenerateGithubMutation } from "../../github/mutation-policy.mjs";
7
+ import { UnsupportedRestOperationError } from "../../github/unsupported-rest.mjs";
8
+ import { GitHubRequestError } from "../../github/errors.mjs";
9
+ import { isRestSessionRefusal } from "../../github/rest-session-refusal.mjs";
10
+ import { rateLimitKind } from "../../github/rate-limit-kind.mjs";
11
+ import { nonDraftMergeStatus } from "../../merge-status/derive.mjs";
4
12
  export async function markReadyIfAuthorized(enabled, base, report) {
5
13
  if (!enabled)
6
14
  return null;
@@ -12,17 +20,49 @@ export async function markReadyIfAuthorized(enabled, base, report) {
12
20
  log: `READY: PR #${report.pr} is ready to mark; refresh required before converting draft`,
13
21
  };
14
22
  }
15
- if (report.viewerAuthorization?.viewerCanUpdate === true) {
16
- await graphql(MARK_PR_READY_MUTATION, { pullRequestId: report.nodeId });
17
- return {
18
- ...base,
19
- action: "mark_ready",
20
- markedReady: true,
21
- log: `MARKED READY: PR #${report.pr} converted from draft to ready for review`,
22
- };
23
+ let unsupported;
24
+ if (canGenerateGithubMutation(report.viewerAuthorization?.viewerCanUpdate, "ready")) {
25
+ try {
26
+ await githubOperation("MarkPrReady", async () => {
27
+ await graphql(MARK_PR_READY_MUTATION, { pullRequestId: report.nodeId });
28
+ }, async () => {
29
+ if (!isCcrTransport())
30
+ throw new UnsupportedRestOperationError("markPullRequestReadyForReview", "ready-for-review requires the cloud CCR proxy");
31
+ const response = await rest("POST", `/repos/${report.repo}/pulls/${report.pr}/ccr/ready_for_review`);
32
+ if (response?.draft !== false)
33
+ throw new Error("CCR ready_for_review did not confirm draft:false; refresh the PR before retrying");
34
+ }, { mutation: true });
35
+ // The snapshot predates the conversion; GitHub just confirmed the PR is no longer a draft.
36
+ // A raw DRAFT merge state has not been recomputed yet, so it becomes UNKNOWN.
37
+ const mergeStateStatus = base.mergeStateStatus === "DRAFT" ? "UNKNOWN" : base.mergeStateStatus;
38
+ return {
39
+ ...base,
40
+ isDraft: false,
41
+ mergeStateStatus,
42
+ ...(base.mergeStatus === "DRAFT" && { mergeStatus: nonDraftMergeStatus(mergeStateStatus) }),
43
+ action: "mark_ready",
44
+ markedReady: true,
45
+ log: `MARKED READY: PR #${report.pr} converted from draft to ready for review`,
46
+ };
47
+ }
48
+ catch (error) {
49
+ if (error instanceof UnsupportedRestOperationError ||
50
+ isRestSessionRefusal(error) ||
51
+ (error instanceof GitHubRequestError && error.status === 404))
52
+ unsupported = error.message;
53
+ else if (!(error instanceof GitHubRequestError) ||
54
+ error.status !== 403 ||
55
+ rateLimitKind(error) !== null)
56
+ throw error;
57
+ }
58
+ }
59
+ else if (report.transport === "rest" && report.viewerAuthorization?.viewerCanUpdate !== false) {
60
+ unsupported = "Ready-for-review requires the cloud CCR proxy";
23
61
  }
24
62
  const escalation = {
25
- triggers: ["authorization-required"],
63
+ triggers: [
64
+ unsupported ? "transport-unsupported" : "authorization-required",
65
+ ],
26
66
  unresolvedThreads: [],
27
67
  ambiguousComments: [],
28
68
  changesRequestedReviews: [],
@@ -33,7 +73,7 @@ export async function markReadyIfAuthorized(enabled, base, report) {
33
73
  reason: "denied-or-unverifiable",
34
74
  },
35
75
  ],
36
- suggestion: buildEscalateSuggestion(["authorization-required"]),
76
+ suggestion: unsupported ?? buildEscalateSuggestion(["authorization-required"]),
37
77
  };
38
78
  return {
39
79
  ...base,
@@ -0,0 +1,19 @@
1
+ import { type MergeMethod } from "../../config/merge-method.mts";
2
+ import type { MergeCommandPlan } from "../../types.mts";
3
+ import type { RestMergeStackGuard } from "../../github/rest-merge.mts";
4
+ export interface MergePlanInput {
5
+ transport?: "rest";
6
+ pr: number;
7
+ repo: string;
8
+ nodeId: string;
9
+ headSha: string;
10
+ queue: boolean;
11
+ queueKnown?: boolean;
12
+ /** Omitted when the batch did not select repository merge settings. */
13
+ allowedMergeMethods?: readonly MergeMethod[];
14
+ expectedStack?: RestMergeStackGuard;
15
+ }
16
+ export interface MergeMethodUnavailable {
17
+ unavailable: string;
18
+ }
19
+ export declare function buildMergeCommandPlan(input: MergePlanInput): MergeCommandPlan | MergeMethodUnavailable;