pr-shepherd 0.56.5 → 0.57.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (423) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +44 -27
  3. package/bin/api.d.mts +25 -3
  4. package/bin/api.mjs +51 -13
  5. package/bin/checks/classify.d.mts +1 -0
  6. package/bin/checks/classify.mjs +1 -1
  7. package/bin/checks/conclusions.d.mts +5 -1
  8. package/bin/checks/conclusions.mjs +8 -2
  9. package/bin/checks/superseded.d.mts +8 -2
  10. package/bin/checks/superseded.mjs +24 -7
  11. package/bin/classify/loader.d.mts +2 -0
  12. package/bin/classify/loader.mjs +15 -0
  13. package/bin/classify/standard/author.d.mts +3 -0
  14. package/bin/classify/standard/author.mjs +10 -0
  15. package/bin/classify/standard/codecov-report.d.mts +4 -0
  16. package/bin/classify/standard/codecov-report.mjs +10 -0
  17. package/bin/classify/standard/coderabbit-notices.d.mts +8 -0
  18. package/bin/classify/standard/coderabbit-notices.mjs +38 -0
  19. package/bin/classify/standard/coderabbit-review-summary.d.mts +8 -0
  20. package/bin/classify/standard/coderabbit-review-summary.mjs +37 -0
  21. package/bin/classify/standard/codex-notices.d.mts +7 -0
  22. package/bin/classify/standard/codex-notices.mjs +36 -0
  23. package/bin/classify/standard/codex-review-summary.d.mts +7 -0
  24. package/bin/classify/standard/codex-review-summary.mjs +20 -0
  25. package/bin/classify/standard/dependabot-notices.d.mts +4 -0
  26. package/bin/classify/standard/dependabot-notices.mjs +15 -0
  27. package/bin/classify/standard/gemini-notices.d.mts +4 -0
  28. package/bin/classify/standard/gemini-notices.mjs +13 -0
  29. package/bin/classify/standard/index.d.mts +4 -0
  30. package/bin/classify/standard/index.mjs +22 -0
  31. package/bin/classify/standard/kilo-no-issues.d.mts +4 -0
  32. package/bin/classify/standard/kilo-no-issues.mjs +17 -0
  33. package/bin/classify/standard/sourcery-notices.d.mts +8 -0
  34. package/bin/classify/standard/sourcery-notices.mjs +20 -0
  35. package/bin/cli/api-usage-formatter.mjs +6 -1
  36. package/bin/cli/apply-merge-handler.d.mts +3 -0
  37. package/bin/cli/apply-merge-handler.mjs +73 -0
  38. package/bin/cli/arg-flags.d.mts +2 -0
  39. package/bin/cli/arg-flags.mjs +47 -0
  40. package/bin/cli/args.mjs +10 -41
  41. package/bin/cli/clean-handler.d.mts +1 -0
  42. package/bin/cli/clean-handler.mjs +71 -0
  43. package/bin/cli/default-poll.mjs +3 -0
  44. package/bin/cli/event-state-scope.d.mts +19 -0
  45. package/bin/cli/event-state-scope.mjs +60 -0
  46. package/bin/cli/fix-formatter.mjs +5 -11
  47. package/bin/cli/handlers.d.mts +0 -1
  48. package/bin/cli/handlers.mjs +8 -64
  49. package/bin/cli/help-command-pages.d.mts +62 -18
  50. package/bin/cli/help-command-pages.mjs +59 -15
  51. package/bin/cli/help-iterate-poll-pages.d.mts +2 -2
  52. package/bin/cli/help-iterate-poll-pages.mjs +6 -0
  53. package/bin/cli/help-log-file-page.d.mts +1 -1
  54. package/bin/cli/help-log-file-page.mjs +4 -1
  55. package/bin/cli/help-top-page.d.mts +1 -1
  56. package/bin/cli/help-top-page.mjs +5 -0
  57. package/bin/cli/help.d.mts +63 -19
  58. package/bin/cli/help.mjs +2 -0
  59. package/bin/cli/iterate-branch-segment.d.mts +2 -0
  60. package/bin/cli/iterate-branch-segment.mjs +12 -7
  61. package/bin/cli/iterate-flags.d.mts +7 -1
  62. package/bin/cli/iterate-flags.mjs +38 -0
  63. package/bin/cli/iterate-formatter.d.mts +6 -6
  64. package/bin/cli/iterate-formatter.mjs +56 -22
  65. package/bin/cli/iterate-instructions.mjs +45 -15
  66. package/bin/cli/iterate-lean.mjs +39 -14
  67. package/bin/cli/iterate-merge-formatter.d.mts +6 -0
  68. package/bin/cli/iterate-merge-formatter.mjs +24 -14
  69. package/bin/cli/iterate-verbose.mjs +3 -6
  70. package/bin/cli/list-formatters.mjs +3 -1
  71. package/bin/cli/mutate-formatter.mjs +10 -5
  72. package/bin/cli/next-check-format.d.mts +7 -0
  73. package/bin/cli/next-check-format.mjs +10 -0
  74. package/bin/cli/playbook-handler.d.mts +5 -0
  75. package/bin/cli/playbook-handler.mjs +40 -0
  76. package/bin/cli/poll-handler.mjs +7 -6
  77. package/bin/cli/poll-summary-formatter.mjs +11 -2
  78. package/bin/cli/poll-targets.mjs +2 -0
  79. package/bin/cli/resolve-validators.mjs +2 -2
  80. package/bin/cli/runner.mjs +6 -1
  81. package/bin/cli/stack-layer-view.d.mts +33 -0
  82. package/bin/cli/stack-layer-view.mjs +1 -0
  83. package/bin/cli/stack-overview.d.mts +10 -28
  84. package/bin/cli/stack-overview.mjs +39 -14
  85. package/bin/cli/transport-args.d.mts +5 -0
  86. package/bin/cli/transport-args.mjs +23 -0
  87. package/bin/cli/transport-formatter.d.mts +9 -0
  88. package/bin/cli/transport-formatter.mjs +13 -0
  89. package/bin/cli-parser.mjs +62 -7
  90. package/bin/commands/apply-merge-options.d.mts +4 -0
  91. package/bin/commands/apply-merge-options.mjs +34 -0
  92. package/bin/commands/apply-merge-output.d.mts +7 -0
  93. package/bin/commands/apply-merge-output.mjs +20 -0
  94. package/bin/commands/apply-merge-pull.d.mts +20 -0
  95. package/bin/commands/apply-merge-pull.mjs +15 -0
  96. package/bin/commands/apply-merge.d.mts +18 -0
  97. package/bin/commands/apply-merge.mjs +126 -0
  98. package/bin/commands/apply-queue-removal.mjs +7 -0
  99. package/bin/commands/check-annotations.mjs +28 -18
  100. package/bin/commands/check-execution-context.d.mts +5 -0
  101. package/bin/commands/check-execution-context.mjs +16 -1
  102. package/bin/commands/check-fingerprint.d.mts +26 -2
  103. package/bin/commands/check-fingerprint.mjs +64 -28
  104. package/bin/commands/check-terminal-report.mjs +11 -1
  105. package/bin/commands/check-unreported.d.mts +6 -2
  106. package/bin/commands/check-unreported.mjs +32 -12
  107. package/bin/commands/check-wait-detectors.d.mts +35 -0
  108. package/bin/commands/check-wait-detectors.mjs +107 -0
  109. package/bin/commands/check.d.mts +4 -2
  110. package/bin/commands/check.mjs +130 -40
  111. package/bin/commands/event-instructions.d.mts +11 -0
  112. package/bin/commands/event-instructions.mjs +27 -0
  113. package/bin/commands/event-mode.d.mts +14 -0
  114. package/bin/commands/event-mode.mjs +138 -0
  115. package/bin/commands/iterate/api-usage.mjs +4 -2
  116. package/bin/commands/iterate/base.mjs +4 -0
  117. package/bin/commands/iterate/check-blocker-gate.mjs +14 -2
  118. package/bin/commands/iterate/check-instructions.d.mts +9 -10
  119. package/bin/commands/iterate/check-instructions.mjs +23 -22
  120. package/bin/commands/iterate/classify.mjs +19 -5
  121. package/bin/commands/iterate/escalate.mjs +13 -3
  122. package/bin/commands/iterate/fix-code.mjs +40 -19
  123. package/bin/commands/iterate/fix-instruction-threads.d.mts +0 -8
  124. package/bin/commands/iterate/fix-instruction-threads.mjs +0 -16
  125. package/bin/commands/iterate/fix-loop-instruction.d.mts +9 -0
  126. package/bin/commands/iterate/fix-loop-instruction.mjs +35 -0
  127. package/bin/commands/iterate/helpers.mjs +15 -2
  128. package/bin/commands/iterate/index.mjs +26 -4
  129. package/bin/commands/iterate/mark-ready.mjs +50 -10
  130. package/bin/commands/iterate/merge-plan.d.mts +19 -0
  131. package/bin/commands/iterate/merge-plan.mjs +88 -0
  132. package/bin/commands/iterate/merge-state.mjs +3 -0
  133. package/bin/commands/iterate/merge.d.mts +4 -15
  134. package/bin/commands/iterate/merge.mjs +18 -51
  135. package/bin/commands/iterate/merged-base-pull-requests.d.mts +2 -6
  136. package/bin/commands/iterate/merged-base-pull-requests.mjs +15 -11
  137. package/bin/commands/iterate/native-stack-rebase.d.mts +6 -5
  138. package/bin/commands/iterate/native-stack-rebase.mjs +24 -7
  139. package/bin/commands/iterate/render.d.mts +8 -2
  140. package/bin/commands/iterate/render.mjs +28 -42
  141. package/bin/commands/iterate/stack-trunk-conflict.d.mts +4 -2
  142. package/bin/commands/iterate/stack-trunk-conflict.mjs +44 -19
  143. package/bin/commands/iterate/stale-ancestry.mjs +4 -1
  144. package/bin/commands/iterate/stall.d.mts +5 -1
  145. package/bin/commands/iterate/stall.mjs +19 -7
  146. package/bin/commands/iterate/thread-mutation-routing.d.mts +1 -0
  147. package/bin/commands/iterate/thread-mutation-routing.mjs +4 -2
  148. package/bin/commands/iterate/unreported-required.mjs +5 -2
  149. package/bin/commands/mark-files-as-viewed.mjs +7 -0
  150. package/bin/commands/next-check.d.mts +31 -0
  151. package/bin/commands/next-check.mjs +46 -0
  152. package/bin/commands/playbook.d.mts +13 -0
  153. package/bin/commands/playbook.mjs +48 -0
  154. package/bin/commands/poll-mode.d.mts +8 -0
  155. package/bin/commands/poll-mode.mjs +14 -0
  156. package/bin/commands/poll-progress.d.mts +1 -0
  157. package/bin/commands/poll-progress.mjs +6 -3
  158. package/bin/commands/poll-quota.d.mts +3 -2
  159. package/bin/commands/poll-quota.mjs +7 -3
  160. package/bin/commands/poll-summary-explicit-instructions.d.mts +2 -0
  161. package/bin/commands/poll-summary-explicit-instructions.mjs +10 -1
  162. package/bin/commands/poll-summary-instructions.mjs +16 -17
  163. package/bin/commands/poll-summary.mjs +42 -17
  164. package/bin/commands/poll.d.mts +0 -1
  165. package/bin/commands/poll.mjs +11 -19
  166. package/bin/commands/quota-selection.d.mts +2 -1
  167. package/bin/commands/quota-selection.mjs +12 -7
  168. package/bin/commands/resolve-mutate.mjs +33 -5
  169. package/bin/commands/rest-stack-merge-readiness.d.mts +4 -0
  170. package/bin/commands/rest-stack-merge-readiness.mjs +74 -0
  171. package/bin/commands/rule-auto-resolve.mjs +9 -3
  172. package/bin/commands/shepherd-journal.d.mts +5 -4
  173. package/bin/commands/shepherd-journal.mjs +9 -8
  174. package/bin/commands/stack-drain.d.mts +1 -9
  175. package/bin/commands/stack-drain.mjs +50 -37
  176. package/bin/commands/stack-handoffs.d.mts +10 -0
  177. package/bin/commands/stack-handoffs.mjs +25 -0
  178. package/bin/commands/stack-stall.d.mts +5 -0
  179. package/bin/commands/stack-stall.mjs +11 -0
  180. package/bin/commands/stack-work.d.mts +5 -0
  181. package/bin/commands/stack-work.mjs +32 -0
  182. package/bin/comments/existing-reply-scan.d.mts +17 -0
  183. package/bin/comments/existing-reply-scan.mjs +41 -0
  184. package/bin/comments/rate-limit.mjs +2 -2
  185. package/bin/comments/resolve.d.mts +7 -1
  186. package/bin/comments/resolve.mjs +108 -18
  187. package/bin/comments/rest-reply.d.mts +9 -0
  188. package/bin/comments/rest-reply.mjs +95 -0
  189. package/bin/comments/rest-review-mutations.d.mts +22 -0
  190. package/bin/comments/rest-review-mutations.mjs +125 -0
  191. package/bin/comments/review-visibility.d.mts +1 -1
  192. package/bin/comments/review-visibility.mjs +3 -2
  193. package/bin/comments/sha-poll.mjs +7 -3
  194. package/bin/comments/thread-visibility.mjs +10 -5
  195. package/bin/comments/uncertain-replies.d.mts +11 -0
  196. package/bin/comments/uncertain-replies.mjs +121 -0
  197. package/bin/comments/uncertain-reply-guards.d.mts +10 -0
  198. package/bin/comments/uncertain-reply-guards.mjs +94 -0
  199. package/bin/comments/visible-comments.d.mts +1 -1
  200. package/bin/comments/visible-comments.mjs +4 -2
  201. package/bin/config/load.d.mts +37 -1
  202. package/bin/config/load.mjs +73 -1
  203. package/bin/config.json +6 -2
  204. package/bin/exit-codes.d.mts +3 -0
  205. package/bin/exit-codes.mjs +8 -0
  206. package/bin/github/api-telemetry-aggregate.d.mts +15 -4
  207. package/bin/github/api-telemetry-aggregate.mjs +57 -18
  208. package/bin/github/api-telemetry.d.mts +4 -0
  209. package/bin/github/api-telemetry.mjs +22 -8
  210. package/bin/github/apply-review-preflight.d.mts +20 -0
  211. package/bin/github/apply-review-preflight.mjs +61 -0
  212. package/bin/github/base-behind-cache.d.mts +15 -0
  213. package/bin/github/base-behind-cache.mjs +18 -0
  214. package/bin/github/batch-parse-suites.d.mts +4 -2
  215. package/bin/github/batch-parse-suites.mjs +6 -1
  216. package/bin/github/batch-parser-helpers.mjs +40 -2
  217. package/bin/github/batch-parsers.mjs +1 -0
  218. package/bin/github/batch-raw-rules.d.mts +26 -0
  219. package/bin/github/batch-raw-types.d.mts +8 -6
  220. package/bin/github/batch-stack-evidence.d.mts +28 -0
  221. package/bin/github/batch-stack-evidence.mjs +62 -0
  222. package/bin/github/batch.d.mts +15 -2
  223. package/bin/github/batch.mjs +34 -17
  224. package/bin/github/check-annotations-batch.d.mts +0 -5
  225. package/bin/github/check-annotations-batch.mjs +25 -4
  226. package/bin/github/check-annotations.mjs +3 -1
  227. package/bin/github/client.d.mts +8 -19
  228. package/bin/github/client.mjs +45 -13
  229. package/bin/github/fingerprint-fields.d.mts +8 -0
  230. package/bin/github/fingerprint-fields.mjs +6 -1
  231. package/bin/github/fingerprint.d.mts +4 -2
  232. package/bin/github/fingerprint.mjs +6 -38
  233. package/bin/github/github-fetch.d.mts +2 -0
  234. package/bin/github/github-fetch.mjs +19 -0
  235. package/bin/github/gql/base-behind.gql +3 -0
  236. package/bin/github/gql/batch-pr-page.gql +16 -0
  237. package/bin/github/gql/batch-pr.gql +57 -0
  238. package/bin/github/gql/commit-check-contexts.gql +17 -0
  239. package/bin/github/gql/get-pr-body.gql +1 -0
  240. package/bin/github/gql/poll-stack-summary.gql +1 -0
  241. package/bin/github/gql/poll-summary-check-contexts.gql +5 -0
  242. package/bin/github/gql/poll-summary-fragment.gql +65 -15
  243. package/bin/github/gql/stack-handoff-check-runs.gql +42 -0
  244. package/bin/github/gql/upper-layer-conflict-target.gql +0 -15
  245. package/bin/github/graphql-http.mjs +9 -4
  246. package/bin/github/graphql-internal-retry.d.mts +2 -0
  247. package/bin/github/graphql-internal-retry.mjs +1 -1
  248. package/bin/github/graphql-quota-probe.d.mts +6 -0
  249. package/bin/github/graphql-quota-probe.mjs +56 -0
  250. package/bin/github/graphql-session-refusal.d.mts +8 -0
  251. package/bin/github/graphql-session-refusal.mjs +14 -0
  252. package/bin/github/http-request.d.mts +1 -1
  253. package/bin/github/http-request.mjs +22 -6
  254. package/bin/github/http-utils.mjs +6 -0
  255. package/bin/github/merge-target-rules.d.mts +16 -3
  256. package/bin/github/merge-target-rules.mjs +79 -44
  257. package/bin/github/mutation-policy.d.mts +6 -0
  258. package/bin/github/mutation-policy.mjs +21 -0
  259. package/bin/github/poll-summary-annotation-probe.mjs +17 -10
  260. package/bin/github/poll-summary-check-blockers.d.mts +1 -1
  261. package/bin/github/poll-summary-check-blockers.mjs +2 -2
  262. package/bin/github/poll-summary-check-hydration.mjs +25 -10
  263. package/bin/github/poll-summary-checks.d.mts +2 -2
  264. package/bin/github/poll-summary-checks.mjs +9 -6
  265. package/bin/github/poll-summary-command.d.mts +2 -0
  266. package/bin/github/poll-summary-command.mjs +24 -0
  267. package/bin/github/poll-summary-fingerprint.d.mts +1 -1
  268. package/bin/github/poll-summary-fingerprint.mjs +4 -2
  269. package/bin/github/poll-summary-handoff-fields.d.mts +3 -0
  270. package/bin/github/poll-summary-handoff-fields.mjs +73 -0
  271. package/bin/github/poll-summary-projector.d.mts +1 -1
  272. package/bin/github/poll-summary-projector.mjs +17 -24
  273. package/bin/github/poll-summary-raw.d.mts +70 -10
  274. package/bin/github/poll-summary-readiness.mjs +4 -1
  275. package/bin/github/poll-summary-rest-queue.d.mts +3 -0
  276. package/bin/github/poll-summary-rest-queue.mjs +12 -0
  277. package/bin/github/poll-summary-review.d.mts +1 -1
  278. package/bin/github/poll-summary-review.mjs +23 -11
  279. package/bin/github/poll-summary-route.mjs +19 -3
  280. package/bin/github/poll-summary-stack-page.d.mts +11 -0
  281. package/bin/github/poll-summary-stack-page.mjs +27 -0
  282. package/bin/github/poll-summary-unreported.d.mts +6 -3
  283. package/bin/github/poll-summary-unreported.mjs +15 -7
  284. package/bin/github/poll-summary.d.mts +4 -1
  285. package/bin/github/poll-summary.mjs +48 -34
  286. package/bin/github/queries.d.mts +9 -5
  287. package/bin/github/queries.mjs +10 -6
  288. package/bin/github/rate-limit-sample.d.mts +7 -0
  289. package/bin/github/rate-limit-sample.mjs +12 -0
  290. package/bin/github/reply-recovery-read.d.mts +50 -0
  291. package/bin/github/reply-recovery-read.mjs +118 -0
  292. package/bin/github/reply-thread-transcripts.d.mts +0 -1
  293. package/bin/github/reply-thread-transcripts.mjs +24 -1
  294. package/bin/github/rest-annotation-read.d.mts +5 -0
  295. package/bin/github/rest-annotation-read.mjs +44 -0
  296. package/bin/github/rest-batch-read.d.mts +12 -0
  297. package/bin/github/rest-batch-read.mjs +57 -0
  298. package/bin/github/rest-batch-snapshot.d.mts +30 -0
  299. package/bin/github/rest-batch-snapshot.mjs +115 -0
  300. package/bin/github/rest-batch-unavailable.d.mts +5 -0
  301. package/bin/github/rest-batch-unavailable.mjs +16 -0
  302. package/bin/github/rest-check-read.d.mts +34 -0
  303. package/bin/github/rest-check-read.mjs +124 -0
  304. package/bin/github/rest-comment-authorship.d.mts +7 -0
  305. package/bin/github/rest-comment-authorship.mjs +11 -0
  306. package/bin/github/rest-conditional-scope.d.mts +20 -0
  307. package/bin/github/rest-conditional-scope.mjs +44 -0
  308. package/bin/github/rest-explicit-summary-read.d.mts +3 -0
  309. package/bin/github/rest-explicit-summary-read.mjs +8 -0
  310. package/bin/github/rest-feedback-read.d.mts +36 -0
  311. package/bin/github/rest-feedback-read.mjs +147 -0
  312. package/bin/github/rest-feedback-validation.d.mts +5 -0
  313. package/bin/github/rest-feedback-validation.mjs +28 -0
  314. package/bin/github/rest-http.d.mts +12 -0
  315. package/bin/github/rest-http.mjs +108 -23
  316. package/bin/github/rest-identities.d.mts +22 -0
  317. package/bin/github/rest-identities.mjs +134 -0
  318. package/bin/github/rest-merge-stack-guard.d.mts +14 -0
  319. package/bin/github/rest-merge-stack-guard.mjs +41 -0
  320. package/bin/github/rest-merge.d.mts +29 -0
  321. package/bin/github/rest-merge.mjs +56 -0
  322. package/bin/github/rest-merged-base-read.d.mts +9 -0
  323. package/bin/github/rest-merged-base-read.mjs +48 -0
  324. package/bin/github/rest-pr-core.d.mts +52 -0
  325. package/bin/github/rest-pr-core.mjs +60 -0
  326. package/bin/github/rest-reader-core.d.mts +16 -0
  327. package/bin/github/rest-reader-core.mjs +154 -0
  328. package/bin/github/rest-review-read.d.mts +11 -0
  329. package/bin/github/rest-review-read.mjs +47 -0
  330. package/bin/github/rest-rules-read.d.mts +9 -0
  331. package/bin/github/rest-rules-read.mjs +154 -0
  332. package/bin/github/rest-session-refusal.d.mts +5 -0
  333. package/bin/github/rest-session-refusal.mjs +21 -0
  334. package/bin/github/rest-snapshot-context.d.mts +10 -0
  335. package/bin/github/rest-snapshot-context.mjs +1 -0
  336. package/bin/github/rest-stack-read.d.mts +21 -0
  337. package/bin/github/rest-stack-read.mjs +47 -0
  338. package/bin/github/rest-stack-summary.d.mts +8 -0
  339. package/bin/github/rest-stack-summary.mjs +62 -0
  340. package/bin/github/rest-summary-read.d.mts +7 -0
  341. package/bin/github/rest-summary-read.mjs +112 -0
  342. package/bin/github/rest-terminal-batch.d.mts +14 -0
  343. package/bin/github/rest-terminal-batch.mjs +45 -0
  344. package/bin/github/rest-text.mjs +7 -3
  345. package/bin/github/rest-thread-read.d.mts +4 -0
  346. package/bin/github/rest-thread-read.mjs +35 -0
  347. package/bin/github/rest-viewer-read.d.mts +2 -0
  348. package/bin/github/rest-viewer-read.mjs +41 -0
  349. package/bin/github/stack-bottom.d.mts +3 -0
  350. package/bin/github/stack-bottom.mjs +14 -0
  351. package/bin/github/stack-handoff-context.d.mts +25 -0
  352. package/bin/github/stack-handoff-context.mjs +12 -0
  353. package/bin/github/stack-handoff-page.d.mts +17 -0
  354. package/bin/github/stack-handoff-page.mjs +306 -0
  355. package/bin/github/stack-handoff.d.mts +16 -0
  356. package/bin/github/stack-handoff.mjs +115 -0
  357. package/bin/github/stack-order.d.mts +6 -0
  358. package/bin/github/stack-order.mjs +13 -0
  359. package/bin/github/stack-read.d.mts +7 -3
  360. package/bin/github/stack-read.mjs +22 -11
  361. package/bin/github/stack-size-hint.d.mts +3 -0
  362. package/bin/github/stack-size-hint.mjs +14 -0
  363. package/bin/github/suggestion-thread.mjs +27 -1
  364. package/bin/github/thread-comments.mjs +9 -1
  365. package/bin/github/transport-evidence.d.mts +20 -0
  366. package/bin/github/transport-evidence.mjs +26 -0
  367. package/bin/github/transport-mode.d.mts +2 -0
  368. package/bin/github/transport-mode.mjs +5 -0
  369. package/bin/github/transport.d.mts +19 -0
  370. package/bin/github/transport.mjs +126 -0
  371. package/bin/github/unsupported-rest.d.mts +4 -0
  372. package/bin/github/unsupported-rest.mjs +7 -0
  373. package/bin/mcp/server.mjs +52 -3
  374. package/bin/merge-status/derive.d.mts +2 -0
  375. package/bin/merge-status/derive.mjs +17 -13
  376. package/bin/merge-status/required-check-requirements.d.mts +7 -0
  377. package/bin/merge-status/required-check-requirements.mjs +15 -0
  378. package/bin/merge-status/requirements-format.d.mts +5 -0
  379. package/bin/merge-status/requirements-format.mjs +41 -12
  380. package/bin/merge-status/requirements.mjs +18 -7
  381. package/bin/state/base.mjs +41 -0
  382. package/bin/state/durable-state.d.mts +6 -0
  383. package/bin/state/durable-state.mjs +41 -0
  384. package/bin/state/graphql-quota-policy.mjs +23 -16
  385. package/bin/state/merge-request.d.mts +25 -0
  386. package/bin/state/merge-request.mjs +145 -0
  387. package/bin/state/pr-fingerprint.d.mts +15 -0
  388. package/bin/state/pr-fingerprint.mjs +22 -7
  389. package/bin/state/rest-cache.d.mts +3 -0
  390. package/bin/state/rest-cache.mjs +1 -0
  391. package/bin/state/rest-snapshot-report.d.mts +21 -0
  392. package/bin/state/rest-snapshot-report.mjs +52 -0
  393. package/bin/state/seen-comments.d.mts +3 -0
  394. package/bin/state/seen-comments.mjs +18 -0
  395. package/bin/types/api-usage.d.mts +9 -1
  396. package/bin/types/escalate.d.mts +1 -1
  397. package/bin/types/github.d.mts +23 -9
  398. package/bin/types/iterate.d.mts +24 -2
  399. package/bin/types/merge-action.d.mts +1 -1
  400. package/bin/types/merge-requirements.d.mts +7 -7
  401. package/bin/types/next-check.d.mts +12 -0
  402. package/bin/types/next-check.mjs +1 -0
  403. package/bin/types/poll-summary.d.mts +21 -0
  404. package/bin/types/report.d.mts +13 -1
  405. package/bin/types/review-thread.d.mts +1 -1
  406. package/bin/types.d.mts +1 -0
  407. package/bin/types.mjs +1 -0
  408. package/package.json +3 -2
  409. package/plugins/pr-shepherd/.codex-plugin/plugin.json +1 -1
  410. package/plugins/pr-shepherd/.codex.mcp.json +1 -1
  411. package/plugins/pr-shepherd/.mcp.json +1 -1
  412. package/plugins/pr-shepherd/skills/mark-files-as-viewed/SKILL.md +1 -1
  413. package/plugins/pr-shepherd/skills/pr-shepherd/SKILL.md +14 -51
  414. package/plugins/pr-shepherd/skills/pr-shepherd/references/ci-failure-triage.md +12 -16
  415. package/plugins/pr-shepherd/skills/pr-shepherd/references/cloud-event-loop.md +14 -0
  416. package/plugins/pr-shepherd/skills/pr-shepherd/references/create-pr.md +10 -0
  417. package/plugins/pr-shepherd/skills/pr-shepherd/references/fix-code-loop.md +10 -0
  418. package/plugins/pr-shepherd/skills/pr-shepherd/references/mcp-fallback.md +9 -0
  419. package/plugins/pr-shepherd/skills/reduce-pr-noise/references/classifiers.md +2 -0
  420. package/bin/github/gql/pr-fingerprint.gql +0 -75
  421. package/plugins/pr-shepherd/skills/pr-shepherd/references/journal.md +0 -7
  422. package/plugins/pr-shepherd/skills/pr-shepherd/references/review-mutations.md +0 -9
  423. package/plugins/pr-shepherd/skills/pr-shepherd/references/stack-merge.md +0 -7
@@ -3,4 +3,8 @@ export declare function applyStallGuard(stallKey: {
3
3
  owner: string;
4
4
  repo: string;
5
5
  pr: number;
6
- }, stallTimeoutSeconds: number, headSha: string, base: IterateResultBase, prNumber: number, prospectiveResult: IterateResult, report: ShepherdReport, reviewSummaryIds: string[]): Promise<IterateResult>;
6
+ }, stallTimeoutSeconds: number, headSha: string, base: IterateResultBase, prNumber: number, prospectiveResult: IterateResult, report: ShepherdReport, reviewSummaryIds: string[], stallDeadlineSink?: StallDeadlineSink): Promise<IterateResult>;
7
+ /** Internal out-parameter: when the earliest unstarted check would trip the CI-start stall. */
8
+ export interface StallDeadlineSink {
9
+ ciStartDeadlineSeconds?: number;
10
+ }
@@ -62,13 +62,14 @@ function computeStallFingerprint(action, headSha, base, report, reviewSummaryIds
62
62
  annotations,
63
63
  });
64
64
  }
65
- export async function applyStallGuard(stallKey, stallTimeoutSeconds, headSha, base, prNumber, prospectiveResult, report, reviewSummaryIds) {
65
+ export async function applyStallGuard(stallKey, stallTimeoutSeconds, headSha, base, prNumber, prospectiveResult, report, reviewSummaryIds, stallDeadlineSink) {
66
66
  const prReference = formatPrUrl(report.repo, prNumber);
67
67
  const nowSeconds = Math.floor(Date.now() / 1000);
68
- const stalledChecks = findCiStartStalledChecks(report.checks.inProgress, nowSeconds, {
69
- stallTimeoutSeconds,
70
- action: prospectiveResult.action,
71
- });
68
+ const ciStartOpts = { stallTimeoutSeconds, action: prospectiveResult.action };
69
+ const stalledChecks = findCiStartStalledChecks(report.checks.inProgress, nowSeconds, ciStartOpts);
70
+ if (stallDeadlineSink) {
71
+ stallDeadlineSink.ciStartDeadlineSeconds = ciStartStallDeadline(report.checks.inProgress, nowSeconds, ciStartOpts);
72
+ }
72
73
  if (stalledChecks.length > 0) {
73
74
  const pending = pendingReviewCommandsFromResult(prospectiveResult);
74
75
  const stalledDuration = formatDurationApprox(Math.max(...stalledChecks.map((c) => c.ageSeconds)));
@@ -168,13 +169,24 @@ function stallStateUnavailable(base, prReference, prospectiveResult, reason) {
168
169
  },
169
170
  };
170
171
  }
171
- function findCiStartStalledChecks(checks, nowSeconds, opts) {
172
+ function unstartedStallCandidates(checks, nowSeconds, opts) {
172
173
  if (opts.stallTimeoutSeconds <= 0 || opts.action !== "wait")
173
174
  return [];
174
175
  return checks
175
176
  .filter((c) => isUnstartedCheck(c))
176
177
  .map((c) => toAgentStalledCheck(c, nowSeconds))
177
- .filter((c) => c.createdAtUnix !== undefined && c.ageSeconds >= opts.stallTimeoutSeconds);
178
+ .filter((c) => c.createdAtUnix !== undefined);
179
+ }
180
+ function findCiStartStalledChecks(checks, nowSeconds, opts) {
181
+ return unstartedStallCandidates(checks, nowSeconds, opts).filter((c) => c.ageSeconds >= opts.stallTimeoutSeconds);
182
+ }
183
+ /** Unix seconds at which the oldest unstarted check reaches the stall timeout, if any. */
184
+ function ciStartStallDeadline(checks, nowSeconds, opts) {
185
+ const candidates = unstartedStallCandidates(checks, nowSeconds, opts);
186
+ if (candidates.length === 0)
187
+ return undefined;
188
+ const maxAge = Math.max(...candidates.map((c) => c.ageSeconds));
189
+ return nowSeconds - maxAge + opts.stallTimeoutSeconds;
178
190
  }
179
191
  function isUnstartedCheck(check) {
180
192
  if (check.source === "status_context")
@@ -10,6 +10,7 @@ export interface ThreadMutationRouting {
10
10
  }
11
11
  export declare function threadHasAuthorizedMutation(thread: {
12
12
  id: string;
13
+ isResolved?: boolean;
13
14
  viewerCanReply?: boolean;
14
15
  viewerCanResolve?: boolean;
15
16
  }, replyThreadIds: ReadonlySet<string>, resolveThreadIds: ReadonlySet<string>): boolean;
@@ -1,6 +1,7 @@
1
1
  import { isConfiguredBotAuthor, isHumanAuthor, isViewerAuthoredHuman, } from "../../comments/authors.mjs";
2
2
  import { threadEndedByShepherd } from "../../comments/marker.mjs";
3
3
  import { shouldResolveOtherHumanThread } from "../../comments/thread-resolve-policy.mjs";
4
+ import { canGenerateGithubMutation } from "../../github/mutation-policy.mjs";
4
5
  function dedupeIds(ids) {
5
6
  return [...new Set(ids)];
6
7
  }
@@ -19,9 +20,10 @@ export function threadHasAuthorizedMutation(thread, replyThreadIds, resolveThrea
19
20
  const inResolve = resolveThreadIds.has(thread.id);
20
21
  if (!inReply && !inResolve)
21
22
  return false;
22
- if (inReply && thread.viewerCanReply !== true)
23
+ if (inReply && !canGenerateGithubMutation(thread.viewerCanReply, "reply"))
23
24
  return false;
24
- if (inResolve && thread.viewerCanResolve !== true)
25
+ if (inResolve &&
26
+ (thread.isResolved !== false || !canGenerateGithubMutation(thread.viewerCanResolve, "resolve")))
25
27
  return false;
26
28
  return true;
27
29
  }
@@ -4,6 +4,7 @@ import { readCiRetrigger, sameCiRetrigger, writeCiRetrigger } from "../../state/
4
4
  import { buildFixCompletionInstruction } from "./check-instructions.mjs";
5
5
  import { buildEscalateHumanMessage, buildEscalateSuggestion } from "./escalate.mjs";
6
6
  import { buildNativeStackRebaseInstruction } from "./native-stack-rebase.mjs";
7
+ import { getGithubTransport } from "../../github/transport.mjs";
7
8
  /** Rebase when the trunk or the PR base is behind; otherwise close and reopen once. */
8
9
  function decideUnreportedRequired(input) {
9
10
  if (input.behind)
@@ -29,12 +30,14 @@ function buildUnreportedRequiredInstructions(input) {
29
30
  if (!input.behind) {
30
31
  return [
31
32
  facts,
32
- `Retrigger workflows once for this head with \`gh pr close ${input.pr} -R ${input.repo}\` then \`gh pr reopen ${input.pr} -R ${input.repo}\`.`,
33
+ getGithubTransport() === "rest"
34
+ ? `Retrigger workflows once for this head: run \`gh api --method PATCH repos/${input.repo}/pulls/${input.pr} -f state=closed\`, then run \`gh api --method PATCH repos/${input.repo}/pulls/${input.pr} -f state=open\`.`
35
+ : `Retrigger workflows once for this head with \`gh pr close ${input.pr} -R ${input.repo}\` then \`gh pr reopen ${input.pr} -R ${input.repo}\`.`,
33
36
  "If those checks are still missing on the next poll, investigate why the workflows did not start. Do not close the PR again. Shepherd escalates with `required-checks-unreported` when this remains the only blocker.",
34
37
  ];
35
38
  }
36
39
  const rebase = input.stackRebase
37
- ? `${input.stackRebase} Then push the rewritten stack with \`gh stack push\`.`
40
+ ? `${input.stackRebase} Then push the rewritten stack${getGithubTransport() === "rest" ? " using the repository's stack-update procedure" : " with `gh stack push`"}.`
38
41
  : `Rebase onto \`${input.baseBranch}\` and push.`;
39
42
  return [
40
43
  facts,
@@ -3,6 +3,8 @@ import { graphql, graphqlWithRateLimit, getCurrentPrNumber, getRepoInfo, } from
3
3
  import { paginateForward } from "../github/pagination.mjs";
4
4
  import { isRateLimitMessage, rateLimitFromError, rateLimitFromGraphQlResult, } from "../comments/rate-limit.mjs";
5
5
  import { EXIT, ShepherdError } from "../exit-codes.mjs";
6
+ import { githubOperation } from "../github/transport.mjs";
7
+ import { UnsupportedRestOperationError } from "../github/unsupported-rest.mjs";
6
8
  const FILES_QUERY = `query PullRequestFiles($owner: String!, $repo: String!, $pr: Int!, $filesCursor: String) {
7
9
  _shepherdRateLimit: rateLimit {
8
10
  cost
@@ -34,6 +36,11 @@ const TEST_FILE_RE = /(^|\/)(tests?|__tests__|spec)(\/|$)|\.(test|spec)\.[cm]?[j
34
36
  const MARK_FILES_CHUNK_SIZE = 10;
35
37
  /** @deprecated Hidden implementation for `mark-files-as-viewed`; use `apply files`. */
36
38
  export async function runMarkFilesAsViewed(opts) {
39
+ return githubOperation("markFileAsViewed", () => runGraphqlMarkFilesAsViewed(opts), async () => {
40
+ throw new UnsupportedRestOperationError("markFileAsViewed");
41
+ }, { mutation: true });
42
+ }
43
+ async function runGraphqlMarkFilesAsViewed(opts) {
37
44
  const repo = opts.targetRepository ?? (await getRepoInfo());
38
45
  const prNumber = opts.prNumber ?? (await getCurrentPrNumber());
39
46
  if (!prNumber) {
@@ -0,0 +1,31 @@
1
+ import type { NextCheck, NextCheckReason } from "../types/next-check.mts";
2
+ /** A queued PR is rechecked about every five minutes, matching the poll cadence for queue waits. */
3
+ export declare const MERGE_QUEUE_RECHECK_SECONDS = 300;
4
+ /** Backstop for a missed GitHub event; events normally wake the session long before this. */
5
+ export declare const SAFETY_NET_SECONDS = 3000;
6
+ export interface NextCheckCandidate {
7
+ reason: NextCheckReason;
8
+ seconds: number;
9
+ }
10
+ /** The earliest candidate as a `NextCheck`, with `at` rounded up to a whole minute. */
11
+ export declare function earliestNextCheck(candidates: NextCheckCandidate[], nowMs?: number): NextCheck | undefined;
12
+ interface NextCheckFacts {
13
+ /** The action Shepherd returned for this tick. */
14
+ action: "wait" | "ready" | "mark_ready" | "fix_code" | "merge" | "cancel" | "escalate";
15
+ /** Ready-delay seconds left; only meaningful for `ready`. */
16
+ remainingSeconds?: number;
17
+ queued?: boolean;
18
+ /** A `merge` tick whose printed command is a REST merge request, which can stay `pending`. */
19
+ restMerge?: boolean;
20
+ /** A native-stack draft hold: repeating this one-PR session cannot advance it. */
21
+ stackDraftHold?: boolean;
22
+ /** Unix seconds when an unchanged state would trip the stall timeout. */
23
+ stallDeadlineSeconds?: number;
24
+ }
25
+ /**
26
+ * Deadlines for one PR, or none when the caller must not wait for one. `mark_ready` has none:
27
+ * its tick saw a draft, so only an immediate rerun can start the ready-delay timer. A REST
28
+ * merge can return `pending`, so it keeps a wake-up to resume the recorded request.
29
+ */
30
+ export declare function nextCheckCandidates(facts: NextCheckFacts, nowMs: number): NextCheckCandidate[];
31
+ export {};
@@ -0,0 +1,46 @@
1
+ /** A queued PR is rechecked about every five minutes, matching the poll cadence for queue waits. */
2
+ export const MERGE_QUEUE_RECHECK_SECONDS = 300;
3
+ /** Backstop for a missed GitHub event; events normally wake the session long before this. */
4
+ export const SAFETY_NET_SECONDS = 3000;
5
+ /** The earliest candidate as a `NextCheck`, with `at` rounded up to a whole minute. */
6
+ export function earliestNextCheck(candidates, nowMs = Date.now()) {
7
+ const first = [...candidates].sort((a, b) => a.seconds - b.seconds)[0];
8
+ if (!first)
9
+ return undefined;
10
+ const atMs = Math.ceil((nowMs + Math.max(0, first.seconds) * 1000) / 60_000) * 60_000;
11
+ return {
12
+ at: new Date(atMs).toISOString().replace(/\.\d{3}Z$/, "Z"),
13
+ inSeconds: Math.max(0, Math.round((atMs - nowMs) / 1000)),
14
+ reason: first.reason,
15
+ };
16
+ }
17
+ /**
18
+ * Deadlines for one PR, or none when the caller must not wait for one. `mark_ready` has none:
19
+ * its tick saw a draft, so only an immediate rerun can start the ready-delay timer. A REST
20
+ * merge can return `pending`, so it keeps a wake-up to resume the recorded request.
21
+ */
22
+ export function nextCheckCandidates(facts, nowMs) {
23
+ if (facts.action === "merge" && facts.restMerge) {
24
+ return [{ reason: "merge-pending", seconds: MERGE_QUEUE_RECHECK_SECONDS }];
25
+ }
26
+ const terminal = ["cancel", "escalate", "merge", "mark_ready"].includes(facts.action);
27
+ if (terminal || facts.stackDraftHold)
28
+ return [];
29
+ const candidates = [];
30
+ if (facts.action === "ready" && (facts.remainingSeconds ?? 0) > 0) {
31
+ candidates.push({ reason: "ready-delay", seconds: facts.remainingSeconds });
32
+ }
33
+ else if (facts.queued && facts.action === "wait") {
34
+ candidates.push({ reason: "merge-queue", seconds: MERGE_QUEUE_RECHECK_SECONDS });
35
+ }
36
+ else {
37
+ candidates.push({ reason: "safety-net", seconds: SAFETY_NET_SECONDS });
38
+ }
39
+ if (facts.stallDeadlineSeconds !== undefined) {
40
+ candidates.push({
41
+ reason: "stall-timeout",
42
+ seconds: facts.stallDeadlineSeconds - nowMs / 1000,
43
+ });
44
+ }
45
+ return candidates;
46
+ }
@@ -0,0 +1,13 @@
1
+ export type PlaybookResult = {
2
+ playbooks: string[];
3
+ } | {
4
+ name: string;
5
+ content: string;
6
+ };
7
+ /**
8
+ * Resolve a playbook by name (case-insensitive; spaces and hyphens are equivalent) or file
9
+ * slug. With no name, list every playbook name. Unknown names fail with the available names.
10
+ */
11
+ export declare function runPlaybook(name?: string): PlaybookResult;
12
+ /** Markdown projection shared by the CLI text format and the MCP content channel. */
13
+ export declare function formatPlaybookResult(result: PlaybookResult): string;
@@ -0,0 +1,48 @@
1
+ import { readdirSync, readFileSync } from "node:fs";
2
+ import { EXIT, ShepherdError } from "../exit-codes.mjs";
3
+ /**
4
+ * Playbooks ship with the package under `plugins/pr-shepherd/skills/pr-shepherd/references/`
5
+ * (package.json `files` includes `plugins/**`), so a playbook pointer resolves
6
+ * with or without the skill installed. From `bin/commands/` or `src/commands/`, `../../` is
7
+ * the package root.
8
+ */
9
+ const REFERENCES_DIR = new URL("../../plugins/pr-shepherd/skills/pr-shepherd/references/", import.meta.url);
10
+ function normalize(value) {
11
+ return value.trim().toLowerCase().replace(/\s+/g, "-");
12
+ }
13
+ /** Every shipped playbook, sorted by name. */
14
+ function listPlaybooks() {
15
+ return readdirSync(REFERENCES_DIR)
16
+ .filter((file) => file.endsWith(".md"))
17
+ .map((file) => {
18
+ const content = readFileSync(new URL(file, REFERENCES_DIR), "utf8");
19
+ const slug = file.slice(0, -".md".length);
20
+ return {
21
+ name: /^# (.+)$/m.exec(content)?.[1]?.trim() ?? slug,
22
+ slug,
23
+ content,
24
+ };
25
+ })
26
+ .sort((a, b) => a.name.localeCompare(b.name));
27
+ }
28
+ /**
29
+ * Resolve a playbook by name (case-insensitive; spaces and hyphens are equivalent) or file
30
+ * slug. With no name, list every playbook name. Unknown names fail with the available names.
31
+ */
32
+ export function runPlaybook(name) {
33
+ const playbooks = listPlaybooks();
34
+ if (name === undefined || name.trim() === "")
35
+ return { playbooks: playbooks.map((p) => p.name) };
36
+ const wanted = normalize(name);
37
+ const match = playbooks.find((p) => normalize(p.name) === wanted || p.slug === wanted);
38
+ if (!match) {
39
+ throw new ShepherdError(`Unknown playbook ${JSON.stringify(name)}. Available: ${playbooks.map((p) => JSON.stringify(p.name)).join(", ")}`, EXIT.USAGE);
40
+ }
41
+ return { name: match.name, content: match.content };
42
+ }
43
+ /** Markdown projection shared by the CLI text format and the MCP content channel. */
44
+ export function formatPlaybookResult(result) {
45
+ return "playbooks" in result
46
+ ? result.playbooks.map((name) => `- ${name}`).join("\n")
47
+ : result.content.trimEnd();
48
+ }
@@ -0,0 +1,8 @@
1
+ import type { PollMode } from "../config/load.mts";
2
+ /** The mode a command actually runs in. `auto` has been resolved away. */
3
+ export type ResolvedPollMode = "poll" | "event";
4
+ /**
5
+ * Resolve `--poll-mode` / the `pollMode` option / `poll.mode`. An explicit request wins over
6
+ * config; `auto` becomes `event` only inside a cloud session. A missing value counts as `auto`.
7
+ */
8
+ export declare function resolvePollMode(requested: PollMode | undefined, configured: PollMode | undefined): ResolvedPollMode;
@@ -0,0 +1,14 @@
1
+ /** True when the process runs in a Claude Code cloud (remote) session. */
2
+ function isCloudSession() {
3
+ return process.env["CLAUDE_CODE_REMOTE"] === "true";
4
+ }
5
+ /**
6
+ * Resolve `--poll-mode` / the `pollMode` option / `poll.mode`. An explicit request wins over
7
+ * config; `auto` becomes `event` only inside a cloud session. A missing value counts as `auto`.
8
+ */
9
+ export function resolvePollMode(requested, configured) {
10
+ const mode = requested ?? configured ?? "auto";
11
+ if (mode === "auto")
12
+ return isCloudSession() ? "event" : "poll";
13
+ return mode;
14
+ }
@@ -12,4 +12,5 @@ export declare function writeWaitProgress(opts: {
12
12
  lastWaitSignature: string | null;
13
13
  }): string | null;
14
14
  export declare function writeDebounceProgress(tick: number, elapsedMs: number, remainingMs: number): void;
15
+ export declare function writeRuleAutoResolveProgress(result: IterateResult): void;
15
16
  export {};
@@ -40,9 +40,8 @@ export function writeWaitProgress(opts) {
40
40
  const sleepSeconds = Math.round(opts.sleepMs / 1000);
41
41
  if (!opts.quietStatus) {
42
42
  const reason = waitReason(opts.result);
43
- writeTickProgress(opts.tick, elapsedSeconds, opts.verbose
44
- ? `${reason} — sleeping ${sleepSeconds}s`
45
- : `${reason}; next tick in ${sleepSeconds}s`);
43
+ // The tick interval is the caller's own `--interval`; only verbose mode repeats it.
44
+ writeTickProgress(opts.tick, elapsedSeconds, opts.verbose ? `${reason} — sleeping ${sleepSeconds}s` : reason);
46
45
  return opts.lastWaitSignature;
47
46
  }
48
47
  const signature = waitSignature(opts.result);
@@ -56,3 +55,7 @@ export function writeDebounceProgress(tick, elapsedMs, remainingMs) {
56
55
  const remainingSeconds = Math.round(remainingMs / 1000);
57
56
  process.stderr.write(`[poll tick ${tick} / +${elapsedSeconds}s] FIX_CODE — debounce ${remainingSeconds}s remaining\n`);
58
57
  }
58
+ export function writeRuleAutoResolveProgress(result) {
59
+ if (result.ruleAutoResolve?.summary)
60
+ process.stderr.write(`${result.ruleAutoResolve.summary}\n`);
61
+ }
@@ -1,11 +1,12 @@
1
1
  import type { GraphqlQuotaWarningBand } from "../config/load.mts";
2
2
  import { type RateLimitKind } from "../github/rate-limit-kind.mts";
3
- import type { ApiResourceUsage, GraphqlApiUsage, PollSummaryResult } from "../types.mts";
3
+ import type { ApiResourceUsage, GraphqlApiUsage, GraphqlQuotaWarning, PollSummaryResult } from "../types.mts";
4
+ export declare function carryQuotaWarning(pending: GraphqlQuotaWarning | undefined, current: GraphqlQuotaWarning | undefined, transport?: "graphql" | "rest"): GraphqlQuotaWarning | undefined;
4
5
  /** Slow the poll for whichever of GraphQL or REST core is in a tighter band. */
5
6
  export declare function quotaPollIntervalMs(bands: GraphqlQuotaWarningBand[], usage: {
6
7
  graphql?: Pick<GraphqlApiUsage, "remaining" | "limit">;
7
8
  rest?: Pick<ApiResourceUsage, "resource" | "remaining" | "limit">[];
8
- } | undefined, fallbackMs: number, maxMs: number): number;
9
+ } | undefined, fallbackMs: number, maxMs: number, transport?: "graphql" | "rest"): number;
9
10
  export interface RateLimitRetry {
10
11
  ms: number;
11
12
  resource: string;
@@ -3,7 +3,11 @@ import { GitHubRequestError } from "../github/errors.mjs";
3
3
  import { rateLimitKind } from "../github/rate-limit-kind.mjs";
4
4
  import { exhaustedPrimaryLimitDelayMs } from "./poll-rate-limit-delay.mjs";
5
5
  import { selectQuotaWarning } from "./quota-selection.mjs";
6
+ import { getGithubTransport } from "../github/transport.mjs";
6
7
  const GRAPHQL_RETRY_AFTER_DEFAULT_MS = 60_000;
8
+ export function carryQuotaWarning(pending, current, transport = getGithubTransport()) {
9
+ return current ?? (transport === "rest" && pending?.resource !== "core" ? undefined : pending);
10
+ }
7
11
  /** Sleep at least `--interval`, and at least the active crossed quota band. */
8
12
  function graphqlQuotaPollIntervalMs(bands, usage, fallbackMs, maxMs) {
9
13
  if (usage === undefined || usage.limit <= 0 || bands.length === 0) {
@@ -17,9 +21,9 @@ function graphqlQuotaPollIntervalMs(bands, usage, fallbackMs, maxMs) {
17
21
  return Math.min(Math.max(fallbackMs, bandMs), maxMs);
18
22
  }
19
23
  /** Slow the poll for whichever of GraphQL or REST core is in a tighter band. */
20
- export function quotaPollIntervalMs(bands, usage, fallbackMs, maxMs) {
24
+ export function quotaPollIntervalMs(bands, usage, fallbackMs, maxMs, transport) {
21
25
  const core = usage?.rest?.find((item) => item.resource === "core");
22
- return Math.max(graphqlQuotaPollIntervalMs(bands, usage?.graphql, fallbackMs, maxMs), graphqlQuotaPollIntervalMs(bands, core, fallbackMs, maxMs));
26
+ return Math.max(graphqlQuotaPollIntervalMs(bands, transport === "rest" ? undefined : usage?.graphql, fallbackMs, maxMs), graphqlQuotaPollIntervalMs(bands, core, fallbackMs, maxMs));
23
27
  }
24
28
  /**
25
29
  * Retry delay for `--until-terminal` when GitHub exhausts a primary quota or
@@ -88,5 +92,5 @@ export async function aggregateQuotaWarning(result, bands, intervalSeconds) {
88
92
  return selectQuotaWarning({ owner, repo }, bands.map((band) => ({
89
93
  ...band,
90
94
  pollIntervalMinutes: Math.max(band.pollIntervalMinutes, intervalSeconds / 60),
91
- })), usage, true);
95
+ })), usage, true, getGithubTransport());
92
96
  }
@@ -1,2 +1,4 @@
1
1
  import type { PollSummaryResult } from "../types.mts";
2
+ /** Every selected row already returned `CANCEL` or `ESCALATE`: no autonomous work remains. */
3
+ export declare function isHandedOffSelection(result: PollSummaryResult): boolean;
2
4
  export declare function explicitInstructions(result: PollSummaryResult): string[];
@@ -1,4 +1,9 @@
1
1
  import { buildQuotaAwareContinuation } from "../quota-warning.mjs";
2
+ /** Every selected row already returned `CANCEL` or `ESCALATE`: no autonomous work remains. */
3
+ export function isHandedOffSelection(result) {
4
+ return (result.prs.length > 0 &&
5
+ result.prs.every((item) => item.action === "cancel" || item.action === "escalate"));
6
+ }
2
7
  export function explicitInstructions(result) {
3
8
  if (result.reason === "all_terminal")
4
9
  return ["1. Stop — every selected PR is terminal."];
@@ -15,7 +20,11 @@ export function explicitInstructions(result) {
15
20
  "2. Follow each selected one-PR poll's `## Instructions` until it returns `CANCEL` or `ESCALATE`.",
16
21
  "3. Run this aggregate poll again after selected work completes; one row's `ESCALATE` does not stop work on other rows.",
17
22
  ];
18
- if (result.quotaWarning) {
23
+ if (isHandedOffSelection(result)) {
24
+ instructions[2] =
25
+ "3. Stop after reporting each `ESCALATE` handoff; every selected row is `CANCEL` or `ESCALATE`, so do not run this aggregate poll again.";
26
+ }
27
+ else if (result.quotaWarning) {
19
28
  instructions[2] = buildQuotaAwareContinuation(result.quotaWarning, "3. After selected work completes,");
20
29
  }
21
30
  return instructions;
@@ -2,7 +2,7 @@
2
2
  import { buildQuotaAwareContinuation } from "../quota-warning.mjs";
3
3
  import { explicitInstructions } from "./poll-summary-explicit-instructions.mjs";
4
4
  import { appendAutonomousInstructions, appendHumanHandoffInstructions, findHumanHandoffs, idleWaitPlan, isStackLayerReady, planPrefixDrain, retargetWaitPlan, stackPosition, } from "./stack-drain.mjs";
5
- import { appendMarkReadyInstructions, splitStackWork } from "./stack-work.mjs";
5
+ import { appendMarkReadyInstructions, appendStackRerunInstruction, splitStackWork, } from "./stack-work.mjs";
6
6
  /** Keep aggregate JSON, Markdown, and MCP instructions on one projection. */
7
7
  export function withPollSummaryInstructions(result, mergeRequested) {
8
8
  return planPollSummary(result, mergeRequested).result;
@@ -85,6 +85,8 @@ function planStack(result, mergeRequested) {
85
85
  const runnableCandidates = work.sessions.filter((item) => item.pollCommand);
86
86
  const missingCommands = work.sessions.filter((item) => !item.pollCommand);
87
87
  const agentWork = runnableCandidates.length > 0 || work.markReady.length > 0;
88
+ // Only owned work defers a human handoff: rerunning cannot advance another author's layer.
89
+ const ownedWork = [...runnableCandidates, ...work.markReady].some((item) => item.owned);
88
90
  const drain = planPrefixDrain(result, mergeRequested);
89
91
  if (drain)
90
92
  return drain;
@@ -93,13 +95,13 @@ function planStack(result, mergeRequested) {
93
95
  const instructions = [];
94
96
  appendAutonomousInstructions(instructions, runnableCandidates);
95
97
  appendMarkReadyInstructions(instructions, work.markReady);
96
- const stop = !agentWork;
98
+ const stop = !ownedWork;
97
99
  appendHumanHandoffInstructions(instructions, handoffs, stop);
98
100
  for (const item of missingCommands) {
99
101
  instructions.push(`${instructions.length + 1}. PR #${item.pr} needs a one-PR session, but Shepherd could not produce its command. Ask for direction.`);
100
102
  }
101
- if (agentWork) {
102
- instructions.push(`${instructions.length + 1}. After the listed one-PR sessions, rerun this same \`--stack\` selector. Stop for the human handoff only when no autonomous shepherding remains.`);
103
+ if (ownedWork) {
104
+ appendStackRerunInstruction(instructions, [...runnableCandidates, ...work.markReady], "After the listed one-PR sessions, rerun this same `--stack` selector. Stop for the human handoff only when no autonomous shepherding remains.");
103
105
  }
104
106
  return { action: stop ? "escalate" : "shepherd", stackMergeable: false, instructions };
105
107
  }
@@ -116,20 +118,20 @@ function planStack(result, mergeRequested) {
116
118
  appendAutonomousInstructions(instructions, runnableCandidates);
117
119
  appendMarkReadyInstructions(instructions, work.markReady);
118
120
  for (const item of missingCommands) {
119
- instructions.push(`${instructions.length + 1}. PR #${item.pr} needs a one-PR session, but Shepherd could not produce its command. ${agentWork ? "After autonomous shepherding, ask" : "Stop and ask"} for direction.`);
121
+ instructions.push(`${instructions.length + 1}. PR #${item.pr} needs a one-PR session, but Shepherd could not produce its command. ${ownedWork ? "After autonomous shepherding, ask" : "Stop and ask"} for direction.`);
120
122
  }
121
- if (agentWork) {
122
- instructions.push(`${instructions.length + 1}. After the listed one-PR sessions, rerun this same \`--stack\` selector. Stop for the human handoff only when no autonomous shepherding remains.`);
123
+ if (ownedWork) {
124
+ appendStackRerunInstruction(instructions, [...runnableCandidates, ...work.markReady], "After the listed one-PR sessions, rerun this same `--stack` selector. Stop for the human handoff only when no autonomous shepherding remains.");
123
125
  }
124
- return { action: agentWork ? "shepherd" : "escalate", stackMergeable: false, instructions };
126
+ return { action: ownedWork ? "shepherd" : "escalate", stackMergeable: false, instructions };
125
127
  }
126
128
  if (!agentWork)
127
129
  return idleWaitPlan(work.idle);
128
130
  const instructions = [];
129
131
  appendAutonomousInstructions(instructions, work.sessions);
130
132
  appendMarkReadyInstructions(instructions, work.markReady);
131
- instructions.push(`${instructions.length + 1}. After the selected one-PR sessions, rerun this same \`--stack\` selector.`);
132
- return { action: "shepherd", stackMergeable: false, instructions };
133
+ const action = appendStackRerunInstruction(instructions, [...work.sessions, ...work.markReady], "After the selected one-PR sessions, rerun this same `--stack` selector.");
134
+ return { action, stackMergeable: false, instructions };
133
135
  }
134
136
  if (open.some((item) => item.isInMergeQueue)) {
135
137
  return {
@@ -150,13 +152,10 @@ function planStack(result, mergeRequested) {
150
152
  }
151
153
  if (!mergeRequested) {
152
154
  const instructions = [];
153
- appendAutonomousInstructions(instructions, open.filter((item) => item.action !== "cancel"));
154
- instructions.push(`${instructions.length + 1}. After the selected one-PR sessions, rerun this same \`--stack\` selector.`);
155
- return {
156
- action: "shepherd",
157
- stackMergeable,
158
- instructions,
159
- };
155
+ const sessions = open.filter((item) => item.action !== "cancel");
156
+ appendAutonomousInstructions(instructions, sessions);
157
+ const action = appendStackRerunInstruction(instructions, sessions, "After the selected one-PR sessions, rerun this same `--stack` selector.");
158
+ return { action, stackMergeable, instructions };
160
159
  }
161
160
  return retargetWaitPlan(open[0]);
162
161
  }
@@ -2,10 +2,12 @@
2
2
  import { loadConfig } from "../config/load.mjs";
3
3
  import { ShepherdError } from "../exit-codes.mjs";
4
4
  import { getRepoInfo } from "../github/client.mjs";
5
+ import { getGithubTransport } from "../github/transport.mjs";
5
6
  import { withApiTelemetryScope, summarizeApiTelemetry } from "../github/api-telemetry.mjs";
6
7
  import { fetchPollSummary } from "../github/poll-summary.mjs";
8
+ import { writeStackHandoffs } from "../github/stack-handoff.mjs";
7
9
  import { sleep } from "../util/sleep.mjs";
8
- import { aggregateQuotaWarning, quotaPollIntervalMs } from "./poll-quota.mjs";
10
+ import { aggregateQuotaWarning, carryQuotaWarning, quotaPollIntervalMs } from "./poll-quota.mjs";
9
11
  import { aggregateCancelFromPulls, aggregateRateLimitTargets } from "./poll-rate-limit-cancel.mjs";
10
12
  import { createUntilTerminalRateLimitRetry } from "./poll-rate-limit-wait.mjs";
11
13
  import { planPollSummary, withPollSummaryInstructions } from "./poll-summary-instructions.mjs";
@@ -14,12 +16,38 @@ import { applyStackStallGuard } from "./stack-stall.mjs";
14
16
  const MAX_TIMER_MS = 2 ** 31 - 1;
15
17
  const TIMER_DRIFT_TOLERANCE_MS = 500;
16
18
  export function runPollSummary(opts) {
17
- return withApiTelemetryScope(async () => attachUsage(await runPollSummaryCore(opts), opts.merge));
19
+ return withApiTelemetryScope(async () => {
20
+ const tick = await readPollSummaryTick(opts);
21
+ await handOffRoutedLayers(tick);
22
+ return attachUsage(tick.result, opts.merge);
23
+ });
18
24
  }
19
25
  export function runAggregatePoll(opts) {
20
26
  return withApiTelemetryScope(() => runAggregatePollCore(opts));
21
27
  }
22
28
  async function runPollSummaryCore(opts) {
29
+ return (await readPollSummaryTick(opts)).result;
30
+ }
31
+ /**
32
+ * The stack tick that routes owned layers to their one-PR sessions hands each
33
+ * routed layer this tick's read of it, so that session's first read is free.
34
+ * Every other tick clears earlier handoffs: a newer read supersedes them.
35
+ */
36
+ async function handOffRoutedLayers(tick) {
37
+ const { result, handoffSource } = tick;
38
+ if (!handoffSource)
39
+ return;
40
+ const routing = result.nextAction === "shepherd" && result.reason !== "waiting";
41
+ const routed = routing
42
+ ? result.prs
43
+ .filter((item) => item.state === "OPEN" &&
44
+ item.pollCommand !== undefined &&
45
+ !["cancel", "wait", "escalate"].includes(item.action))
46
+ .map((item) => item.pr)
47
+ : [];
48
+ await writeStackHandoffs(handoffSource, routed, tick.repo);
49
+ }
50
+ async function readPollSummaryTick(opts) {
23
51
  const repo = opts.targetRepository ?? (await getRepoInfo());
24
52
  const fetched = await fetchPollSummary(opts, repo);
25
53
  const allTerminal = fetched.prs.every((item) => item.action === "cancel");
@@ -33,7 +61,8 @@ async function runPollSummaryCore(opts) {
33
61
  ...(fetched.stackAncestry?.length && { stackAncestry: fetched.stackAncestry }),
34
62
  ...(fetched.allowedMergeMethods && { allowedMergeMethods: fetched.allowedMergeMethods }),
35
63
  }, opts.merge === true);
36
- return applyStackStallGuard(planned, repo, opts.stallTimeoutSeconds ?? loadConfig().iterate.stallTimeoutMinutes * 60);
64
+ const result = await applyStackStallGuard(planned, repo, opts.stallTimeoutSeconds ?? loadConfig().iterate.stallTimeoutMinutes * 60);
65
+ return { result, repo, ...(fetched.handoffSource && { handoffSource: fetched.handoffSource }) };
37
66
  }
38
67
  async function runAggregatePollCore(opts) {
39
68
  const intervalMs = Math.min(opts.intervalSeconds * 1000, MAX_TIMER_MS);
@@ -44,16 +73,19 @@ async function runAggregatePollCore(opts) {
44
73
  let tick = 0;
45
74
  let debounceUntil = null;
46
75
  let last;
76
+ let lastTick;
47
77
  let lastStatusSignature = null;
48
78
  const rateLimitRetry = createUntilTerminalRateLimitRetry();
49
79
  let pendingQuotaWarning;
50
80
  while (true) {
51
81
  tick += 1;
52
82
  try {
53
- last = await runPollSummaryCore(opts);
83
+ lastTick = await readPollSummaryTick(opts);
84
+ last = lastTick.result;
54
85
  rateLimitRetry.reset();
55
86
  }
56
87
  catch (error) {
88
+ pendingQuotaWarning = carryQuotaWarning(pendingQuotaWarning, undefined);
57
89
  if (last?.selection.kind === "stack" && isMissingStack(error)) {
58
90
  const explicit = await runPollSummaryCore({
59
91
  ...opts,
@@ -62,8 +94,7 @@ async function runAggregatePollCore(opts) {
62
94
  });
63
95
  if (explicit.prs.every((item) => item.state === "MERGED")) {
64
96
  const warning = await aggregateQuotaWarning(explicit, quotaBands, opts.intervalSeconds);
65
- if (warning)
66
- pendingQuotaWarning = warning;
97
+ pendingQuotaWarning = carryQuotaWarning(pendingQuotaWarning, warning);
67
98
  return attachUsage({
68
99
  ...explicit,
69
100
  selection: last.selection,
@@ -110,8 +141,7 @@ async function runAggregatePollCore(opts) {
110
141
  ? last.nextAction === "shepherd" && last.reason !== "waiting"
111
142
  : last.prs.some((item) => item.action === "fix_code");
112
143
  const warning = await aggregateQuotaWarning(last, quotaBands, opts.intervalSeconds);
113
- if (warning)
114
- pendingQuotaWarning = warning;
144
+ pendingQuotaWarning = carryQuotaWarning(pendingQuotaWarning, warning);
115
145
  if (allTerminal) {
116
146
  return attachUsage({
117
147
  ...last,
@@ -127,19 +157,14 @@ async function runAggregatePollCore(opts) {
127
157
  }, opts.merge);
128
158
  }
129
159
  if (hasFix) {
130
- if (debounceMs === 0)
131
- return attachUsage({
132
- ...last,
133
- reason: "actionable",
134
- ...(pendingQuotaWarning && { quotaWarning: pendingQuotaWarning }),
135
- }, opts.merge);
136
- debounceUntil ??= Date.now() + debounceMs;
137
- if (Date.now() >= debounceUntil)
160
+ if (debounceMs === 0 || Date.now() >= (debounceUntil ??= Date.now() + debounceMs)) {
161
+ await handOffRoutedLayers(lastTick);
138
162
  return attachUsage({
139
163
  ...last,
140
164
  reason: "actionable",
141
165
  ...(pendingQuotaWarning && { quotaWarning: pendingQuotaWarning }),
142
166
  }, opts.merge);
167
+ }
143
168
  }
144
169
  else {
145
170
  debounceUntil = null;
@@ -150,7 +175,7 @@ async function runAggregatePollCore(opts) {
150
175
  const elapsedMs = Date.now() - start;
151
176
  const sleepMs = debounceUntil
152
177
  ? Math.min(intervalMs, Math.max(debounceUntil - Date.now(), 0))
153
- : quotaPollIntervalMs(quotaBands, summarizeApiTelemetry(), intervalMs, MAX_TIMER_MS);
178
+ : quotaPollIntervalMs(quotaBands, summarizeApiTelemetry(), intervalMs, MAX_TIMER_MS, getGithubTransport());
154
179
  if (!opts.untilTerminal && debounceUntil === null) {
155
180
  const remainingMs = timeoutMs - elapsedMs;
156
181
  if (remainingMs <= 0 || remainingMs + TIMER_DRIFT_TOLERANCE_MS < sleepMs) {
@@ -7,5 +7,4 @@ export interface PollCommandOptions extends IterateCommandOptions {
7
7
  quietStatus?: boolean;
8
8
  untilTerminal?: boolean;
9
9
  }
10
- /** @deprecated Hidden implementation for the legacy `poll` alias. */
11
10
  export declare function runPoll(opts: PollCommandOptions): Promise<IterateResult>;