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
@@ -2,7 +2,11 @@ import { fetchPrBatch } from "../github/batch.mjs";
2
2
  import { headArrivalUnix, queueRemovalAppliesToHead } from "../github/queue-removal-freshness.mjs";
3
3
  import { readQueueRemovalAcknowledgment, matchesQueueRemovalAcknowledgment, isCiQueueRemovalReason, } from "../state/queue-removal-ack.mjs";
4
4
  import { storePrFingerprint } from "../state/pr-fingerprint.mjs";
5
- import { tryReuseFingerprintReport } from "./check-fingerprint.mjs";
5
+ import { storeRestSnapshotReport } from "../state/rest-snapshot-report.mjs";
6
+ import { restSnapshotState, withRestConditionalScope } from "../github/rest-conditional-scope.mjs";
7
+ import { fingerprintReuser, tryReuseRestSnapshotReport } from "./check-fingerprint.mjs";
8
+ import { readWaitDetectors, recordWaitDetectors } from "./check-wait-detectors.mjs";
9
+ import { withTrunkRequiredContexts } from "../merge-status/required-check-requirements.mjs";
6
10
  import { collectUnreportedRequired, refreshCachedUnreported } from "./check-unreported.mjs";
7
11
  import { getRepoInfo, getCurrentPrNumber } from "../github/client.mjs";
8
12
  import { classifyChecks, getCiVerdict } from "../checks/classify.mjs";
@@ -16,7 +20,10 @@ import { computeStatus } from "./check-status.mjs";
16
20
  import { annotationMarkerBody, attachAndMergeCheckAnnotations, hasCheckDrivenActionableWork, } from "./check-annotations.mjs";
17
21
  import { buildTerminalReport } from "./check-terminal-report.mjs";
18
22
  import { isBlockedByFilteredCheck, refreshReadyMergeability, refreshUnknownMergeability, } from "./ready-mergeability.mjs";
19
- import { loadSeenMap, markSeen, classifyItem } from "../state/seen-comments.mjs";
23
+ import { loadSeenMap, markSeen, classifyItem, mutationWasDenied } from "../state/seen-comments.mjs";
24
+ import { aliasThreadSeenMarkers } from "../github/rest-identities.mjs";
25
+ import { transportReadinessGaps } from "../github/transport-evidence.mjs";
26
+ import { canGenerateGithubMutation } from "../github/mutation-policy.mjs";
20
27
  import { threadTranscriptBody } from "../threads/transcript.mjs";
21
28
  import { classifyThreadVisibility } from "../comments/thread-visibility.mjs";
22
29
  import { classifyReviewsForDisplay, classifyChangesRequestedReviewsForDisplay, } from "../comments/review-visibility.mjs";
@@ -24,10 +31,21 @@ import { applySuppressedRuleAutoResolve } from "./rule-auto-resolve.mjs";
24
31
  import { markReviewInlineThreadMarkers } from "../comments/review-thread-markers.mjs";
25
32
  import { isConfiguredBotAuthor, isHumanAuthor, normalizeBotUsernames, } from "../comments/authors.mjs";
26
33
  import { buildThreadMutationRouting, threadHasAuthorizedMutation, } from "./iterate/thread-mutation-routing.mjs";
27
- import { discoverRuleFiles, loadRules } from "../classify/loader.mjs";
34
+ import { loadClassifyRules } from "../classify/loader.mjs";
28
35
  import { buildClassifyIndex, partitionBatch } from "../classify/apply.mjs";
29
36
  import { EXIT, ShepherdError } from "../exit-codes.mjs";
30
37
  import { getEffectiveCwd } from "../execution-context.mjs";
38
+ function enforceTransportReadiness(status, data) {
39
+ if (status !== "READY" || data.transport !== "rest")
40
+ return status;
41
+ if (data.reviewThreads.some((thread) => thread.isResolved === undefined ||
42
+ thread.isOutdated === undefined ||
43
+ thread.comments === undefined))
44
+ return "UNKNOWN";
45
+ if (transportReadinessGaps(data, data.mergeStateStatus).length > 0)
46
+ return "UNKNOWN";
47
+ return status;
48
+ }
31
49
  export async function runCheck(opts, context) {
32
50
  const repo = opts.targetRepository ?? (await getRepoInfo());
33
51
  const prNumber = opts.prNumber ?? (await getCurrentPrNumber());
@@ -35,20 +53,48 @@ export async function runCheck(opts, context) {
35
53
  throw new ShepherdError("No open PR found for current branch. Pass a PR number explicitly.", EXIT.UNAVAILABLE);
36
54
  }
37
55
  const stateKey = { owner: repo.owner, repo: repo.name, pr: prNumber };
56
+ // Named REST reads in this PR's check are conditional (ETag/304) and cached under its state dir.
57
+ return withRestConditionalScope(stateKey, () => runScopedCheck(opts, context, repo, prNumber, stateKey));
58
+ }
59
+ async function runScopedCheck(opts, context, repo, prNumber, stateKey) {
38
60
  const config = loadConfig();
39
61
  const reuseFingerprint = opts.fingerprintCache === true;
40
- if (reuseFingerprint) {
41
- const cached = await tryReuseFingerprintReport(prNumber, repo, stateKey, config);
42
- if (cached)
43
- return refreshCachedUnreported(cached, repo, context);
44
- }
45
62
  const paginateApprovedReviews = config.iterate.minimizeApprovals;
46
63
  const includeReceiptSummary = (await context?.wantsReceiptSummary(prNumber, repo)) ?? false;
47
- const result = await fetchPrBatch(prNumber, repo, {
64
+ // GraphQL transport: conditional REST change detectors can replay an idle report for free.
65
+ // Changed trunk rules invalidate a cached report; check again without reuse.
66
+ const refreshReused = async (cached, baseTipOid) => (await refreshCachedUnreported(cached, repo, context, baseTipOid)) ??
67
+ runScopedCheck({ ...opts, fingerprintCache: false }, context, repo, prNumber, stateKey);
68
+ const detectors = reuseFingerprint && !includeReceiptSummary
69
+ ? await readWaitDetectors(prNumber, repo, stateKey, config)
70
+ : undefined;
71
+ if (detectors && "reused" in detectors)
72
+ return refreshReused(detectors.reused, detectors.baseTipOid);
73
+ const reuse = reuseFingerprint
74
+ ? await fingerprintReuser(prNumber, repo, stateKey, config)
75
+ : undefined;
76
+ const batchOptions = {
48
77
  paginateApprovedReviews,
78
+ terminalShortCircuit: true,
49
79
  ...(includeReceiptSummary && { includeReceiptSummary: true }),
50
- });
80
+ };
81
+ const fetched = reuse
82
+ ? await fetchPrBatch(prNumber, repo, batchOptions, reuse)
83
+ : await fetchPrBatch(prNumber, repo, batchOptions);
84
+ if (fetched.stackEvidence)
85
+ context?.seedStack(prNumber, repo, fetched.stackEvidence);
86
+ if ("reused" in fetched) {
87
+ await recordWaitDetectors(stateKey, config, detectors, undefined);
88
+ return refreshReused(fetched.reused, fetched.baseTipOid);
89
+ }
90
+ const result = fetched;
51
91
  context?.setReceiptSummary(result.receiptSummary ?? null);
92
+ const restSnapshot = result.data.transport === "rest" ? restSnapshotState() : undefined;
93
+ if (reuseFingerprint) {
94
+ const reused = await tryReuseRestSnapshotReport(stateKey, config, restSnapshot);
95
+ if (reused)
96
+ return refreshReused(reused);
97
+ }
52
98
  let batchData = result.data;
53
99
  const unknownRefresh = await refreshUnknownMergeability(prNumber, repo, batchData);
54
100
  batchData = unknownRefresh.batchData;
@@ -67,7 +113,18 @@ export async function runCheck(opts, context) {
67
113
  ? []
68
114
  : await fetchStartupFailureChecks(repo, batchData.headRefOid, prNumber, stateKey, triageBudget);
69
115
  const allChecks = mergeStartupFailureChecks(batchData.checks, startupFailureChecks);
70
- const classifiedPrChecks = classifyChecks(allChecks);
116
+ const unreported = await collectUnreportedRequired({
117
+ batchData,
118
+ checks: allChecks,
119
+ suites: result.headWorkflowSuites ?? [],
120
+ owner: repo.owner,
121
+ name: repo.name,
122
+ pr: prNumber,
123
+ relevantEvents: config.checks.ciTriggerEvents,
124
+ }, context);
125
+ const classifiedPrChecks = classifyChecks(allChecks, {
126
+ requiredContexts: unreported.requiredContexts,
127
+ });
71
128
  const latestRemoval = batchData.latestMergeQueueRemoval;
72
129
  // `timelineItems(last: 1, ...)` returns the single most recent removal regardless of age, so
73
130
  // a PR removed from the queue once, long ago, and never re-added keeps returning that same
@@ -114,6 +171,7 @@ export async function runCheck(opts, context) {
114
171
  // synthetic queue-commit source.
115
172
  const classifiedQueueChecks = classifyChecks(queueRawChecks, {
116
173
  additionalRelevantEvents: ["merge_group"],
174
+ requiredContexts: unreported.requiredContexts,
117
175
  });
118
176
  const classifiedChecks = [...classifiedPrChecks, ...classifiedQueueChecks];
119
177
  const verdict = getCiVerdict(classifiedChecks);
@@ -129,23 +187,32 @@ export async function runCheck(opts, context) {
129
187
  triageBudget.throwIfSecondary();
130
188
  triageBudget.reportOmissionIfNeeded();
131
189
  const seenMap = await loadSeenMap(stateKey);
190
+ await aliasThreadSeenMarkers(repo, prNumber, batchData.reviewThreads.map((thread) => thread.id), seenMap);
191
+ const canGenerateItemMutation = (id, body, capability, operation) => !mutationWasDenied(id, body, seenMap) && canGenerateGithubMutation(capability, operation);
132
192
  const botUsernames = normalizeBotUsernames(config.botUsernames);
133
- const ruleSet = await loadRules(discoverRuleFiles(getEffectiveCwd()));
193
+ const ruleSet = await loadClassifyRules(getEffectiveCwd(), config.standardRules);
134
194
  const classifyIndex = buildClassifyIndex(ruleSet, batchData);
135
195
  const partition = partitionBatch(classifyIndex, batchData);
136
196
  const merged = await attachAndMergeCheckAnnotations({ passing, failing: triagedBase, skipped, filtered, ignored }, seenMap, prNumber, { stateKey, headSha: batchData.headRefOid });
137
197
  const ignoredAnnotated = merged.ignored.filter((c) => (c.annotations?.length ?? 0) > 0);
138
198
  const minimizedCommentCandidates = batchData.comments.filter((c) => c.isMinimized && !partition.suppressedCommentIds.has(c.id));
139
- const deniedRuleAutoResolveCommentIds = new Set(partition.ruleAutoResolveCommentIds.filter((id) => batchData.comments.find((comment) => comment.id === id)?.viewerCanMinimize !== true));
140
- const visibleCommentClassification = classifyVisibleComments(batchData.comments.filter((c) => !partition.suppressedCommentIds.has(c.id) || deniedRuleAutoResolveCommentIds.has(c.id)), seenMap, config.iterate.minimizeComments, botUsernames);
141
- const deniedRuleAutoResolveThreadIds = new Set(partition.ruleAutoResolveThreadIds.filter((id) => batchData.reviewThreads.find((thread) => thread.id === id)?.viewerCanResolve !== true));
199
+ const deniedRuleAutoResolveCommentIds = new Set(partition.ruleAutoResolveCommentIds.filter((id) => !canGenerateItemMutation(id, batchData.comments.find((comment) => comment.id === id)?.body ?? "", batchData.comments.find((comment) => comment.id === id)?.viewerCanMinimize, "minimize")));
200
+ const visibleCommentClassification = classifyVisibleComments(batchData.comments.filter((c) => !partition.suppressedCommentIds.has(c.id) || deniedRuleAutoResolveCommentIds.has(c.id)), seenMap, config.iterate.minimizeComments, botUsernames, new Set(batchData.comments
201
+ .filter((comment) => mutationWasDenied(comment.id, comment.body, seenMap))
202
+ .map((comment) => comment.id)));
203
+ const deniedRuleAutoResolveThreadIds = new Set(partition.ruleAutoResolveThreadIds.filter((id) => {
204
+ const thread = batchData.reviewThreads.find((candidate) => candidate.id === id);
205
+ return (thread?.isResolved !== false ||
206
+ !canGenerateItemMutation(thread.id, threadTranscriptBody(thread), thread.viewerCanResolve, "resolve"));
207
+ }));
142
208
  const visibleThreadCandidates = batchData.reviewThreads.filter((t) => !partition.suppressedThreadIds.has(t.id) || deniedRuleAutoResolveThreadIds.has(t.id));
143
209
  const resolveOtherHumanThreads = config.iterate.resolveOtherHumanThreads ?? "none";
144
210
  const threadMutationRouting = buildThreadMutationRouting(visibleThreadCandidates, botUsernames, partition.ruleAutoResolveThreadIds, resolveOtherHumanThreads);
145
211
  const replyThreadIds = new Set(threadMutationRouting.replyThreadIds);
146
212
  const resolveThreadIds = new Set(threadMutationRouting.resolveThreadIds);
147
213
  const repeatableThreadIds = new Set(visibleThreadCandidates
148
- .filter((thread) => threadHasAuthorizedMutation(thread, replyThreadIds, resolveThreadIds))
214
+ .filter((thread) => !mutationWasDenied(thread.id, threadTranscriptBody(thread), seenMap) &&
215
+ threadHasAuthorizedMutation(thread, replyThreadIds, resolveThreadIds))
149
216
  .map((thread) => thread.id));
150
217
  const threadVisibility = classifyThreadVisibility(visibleThreadCandidates, seenMap, botUsernames, repeatableThreadIds, resolveOtherHumanThreads);
151
218
  const firstLookComments = minimizedCommentCandidates.flatMap((c) => {
@@ -158,7 +225,7 @@ export async function runCheck(opts, context) {
158
225
  const firstLookSummaries = [];
159
226
  const editedSummaries = [];
160
227
  const seenSummaries = [];
161
- const deniedRuleAutoResolveReviewSummaryIds = new Set(partition.ruleAutoResolveReviewSummaryIds.filter((id) => batchData.reviewSummaries.find((review) => review.id === id)?.viewerCanMinimize !== true));
228
+ const deniedRuleAutoResolveReviewSummaryIds = new Set(partition.ruleAutoResolveReviewSummaryIds.filter((id) => !canGenerateItemMutation(id, batchData.reviewSummaries.find((review) => review.id === id)?.body ?? "", batchData.reviewSummaries.find((review) => review.id === id)?.viewerCanMinimize, "minimize")));
162
229
  const unseenReviewSummaries = batchData.reviewSummaries.filter((r) => !partition.suppressedReviewSummaryIds.has(r.id) ||
163
230
  deniedRuleAutoResolveReviewSummaryIds.has(r.id));
164
231
  for (const r of unseenReviewSummaries) {
@@ -167,10 +234,12 @@ export async function runCheck(opts, context) {
167
234
  firstLookSummaries.push(r);
168
235
  else if (cls === "edited")
169
236
  editedSummaries.push(r);
170
- else
237
+ else if (!mutationWasDenied(r.id, r.body, seenMap))
171
238
  seenSummaries.push(r);
172
239
  }
173
- const changesRequestedReviewVisibility = classifyChangesRequestedReviewsForDisplay(batchData.changesRequestedReviews.filter((r) => !partition.suppressedChangesRequestedIds.has(r.id)), seenMap, botUsernames, batchData.viewerAuthorization?.viewerCanAdminister === true);
240
+ const changesRequestedReviewVisibility = classifyChangesRequestedReviewsForDisplay(batchData.changesRequestedReviews.filter((r) => !partition.suppressedChangesRequestedIds.has(r.id)), seenMap, botUsernames, canGenerateGithubMutation(batchData.viewerAuthorization?.viewerCanAdminister, "dismiss"), new Set(batchData.changesRequestedReviews
241
+ .filter((review) => mutationWasDenied(review.id, review.body, seenMap))
242
+ .map((review) => review.id)));
174
243
  const approvedReviewVisibility = classifyReviewsForDisplay(batchData.approvedReviews, seenMap);
175
244
  const changesRequestedReviews = changesRequestedReviewVisibility.visible;
176
245
  const visibleChangesRequestedIds = new Set(changesRequestedReviews.map((review) => review.id));
@@ -179,20 +248,12 @@ export async function runCheck(opts, context) {
179
248
  return false;
180
249
  const isBot = !isHumanAuthor(review) || isConfiguredBotAuthor(review, botUsernames);
181
250
  return (!isBot ||
182
- batchData.viewerAuthorization?.viewerCanAdminister === true ||
251
+ canGenerateItemMutation(review.id, review.body, batchData.viewerAuthorization?.viewerCanAdminister, "dismiss") ||
183
252
  visibleChangesRequestedIds.has(review.id));
184
253
  }).length;
185
254
  const approvedReviews = approvedReviewVisibility.visible;
186
- const unreported = await collectUnreportedRequired({
187
- batchData,
188
- checks: allChecks,
189
- suites: result.headWorkflowSuites ?? [],
190
- owner: repo.owner,
191
- name: repo.name,
192
- pr: prNumber,
193
- relevantEvents: config.checks.ciTriggerEvents,
194
- }, context);
195
255
  let status = computeStatus(verdict, threadVisibility.activeThreads.length + threadVisibility.resolutionOnlyThreads.length, visibleCommentClassification.actionable.length, mergeStatus, changesRequestedReviewCount, unreported.hasUnreportedRequired);
256
+ status = enforceTransportReadiness(status, batchData);
196
257
  // Resolve any pending mergeability refresh (and the resulting MERGED/CLOSED short-circuit)
197
258
  // before deciding what to persist below — deferWhileQueued must see the same final,
198
259
  // possibly-refreshed mergeStatus that commands/iterate/index.mts acts on, not the pre-refresh
@@ -203,6 +264,7 @@ export async function runCheck(opts, context) {
203
264
  batchData = refreshed.batchData;
204
265
  mergeStatus = refreshed.mergeStatus;
205
266
  status = refreshed.status;
267
+ status = enforceTransportReadiness(status, batchData);
206
268
  if (mergeStatus.state === "MERGED" || mergeStatus.state === "CLOSED") {
207
269
  const terminal = buildTerminalReport(prNumber, repo, batchData, mergeStatus, mergeStatus.state);
208
270
  if (result.fingerprint) {
@@ -270,9 +332,13 @@ export async function runCheck(opts, context) {
270
332
  }
271
333
  const authorizedPartition = {
272
334
  ...partition,
273
- ruleAutoResolveThreadIds: partition.ruleAutoResolveThreadIds.filter((id) => batchData.reviewThreads.find((thread) => thread.id === id)?.viewerCanResolve === true),
274
- ruleAutoResolveCommentIds: partition.ruleAutoResolveCommentIds.filter((id) => batchData.comments.find((comment) => comment.id === id)?.viewerCanMinimize === true),
275
- ruleAutoResolveReviewSummaryIds: partition.ruleAutoResolveReviewSummaryIds.filter((id) => batchData.reviewSummaries.find((review) => review.id === id)?.viewerCanMinimize === true),
335
+ ruleAutoResolveThreadIds: partition.ruleAutoResolveThreadIds.filter((id) => {
336
+ const thread = batchData.reviewThreads.find((candidate) => candidate.id === id);
337
+ return (thread?.isResolved === false &&
338
+ canGenerateItemMutation(id, threadTranscriptBody(thread), thread.viewerCanResolve, "resolve"));
339
+ }),
340
+ ruleAutoResolveCommentIds: partition.ruleAutoResolveCommentIds.filter((id) => canGenerateItemMutation(id, batchData.comments.find((comment) => comment.id === id)?.body ?? "", batchData.comments.find((comment) => comment.id === id)?.viewerCanMinimize, "minimize")),
341
+ ruleAutoResolveReviewSummaryIds: partition.ruleAutoResolveReviewSummaryIds.filter((id) => canGenerateItemMutation(id, batchData.reviewSummaries.find((review) => review.id === id)?.body ?? "", batchData.reviewSummaries.find((review) => review.id === id)?.viewerCanMinimize, "minimize")),
276
342
  };
277
343
  if (opts.autoMinimizeSuppressed === true &&
278
344
  (authorizedPartition.ruleAutoResolveThreadIds.length > 0 ||
@@ -292,6 +358,7 @@ export async function runCheck(opts, context) {
292
358
  ...authorizedRuleAutoResolveThreadIds,
293
359
  ...[...deniedRuleAutoResolveThreadIds].filter((id) => visibleMutationThreadIds.has(id)),
294
360
  ];
361
+ mergeStatus = withTrunkRequiredContexts(mergeStatus, unreported.requiredContexts);
295
362
  const blockedByFilteredCheck = isBlockedByFilteredCheck(mergeStatus, verdict);
296
363
  const queueCommit = batchData.isInMergeQueue
297
364
  ? batchData.mergeQueueEntry?.headCommitOid
@@ -303,10 +370,16 @@ export async function runCheck(opts, context) {
303
370
  const report = {
304
371
  pr: prNumber,
305
372
  nodeId: batchData.nodeId,
373
+ ...(batchData.transport && { transport: batchData.transport }),
374
+ ...(batchData.transportUnavailable && {
375
+ transportUnavailable: batchData.transportUnavailable,
376
+ }),
306
377
  headSha: batchData.headRefOid,
307
378
  headRefName: unreported.headRefName,
308
379
  repo: `${repo.owner}/${repo.name}`,
309
- ...(batchData.viewerAuthorization && { viewerAuthorization: batchData.viewerAuthorization }),
380
+ ...(batchData.viewerAuthorization && {
381
+ viewerAuthorization: batchData.viewerAuthorization,
382
+ }),
310
383
  status,
311
384
  baseBranch: batchData.baseRefName,
312
385
  ...(batchData.baseRefOid && { baseRefOid: batchData.baseRefOid }),
@@ -321,8 +394,12 @@ export async function runCheck(opts, context) {
321
394
  ...(ignoredAnnotated.length > 0 && { ignored: ignoredAnnotated }),
322
395
  filteredNames: verdict.filteredNames,
323
396
  blockedByFilteredCheck,
324
- ...(verdict.ignoredNames.length > 0 && { ignoredNames: verdict.ignoredNames }),
325
- ...(verdict.supersededNames.length > 0 && { supersededNames: verdict.supersededNames }),
397
+ ...(verdict.ignoredNames.length > 0 && {
398
+ ignoredNames: verdict.ignoredNames,
399
+ }),
400
+ ...(verdict.supersededNames.length > 0 && {
401
+ supersededNames: verdict.supersededNames,
402
+ }),
326
403
  },
327
404
  threads: {
328
405
  actionable: threadVisibility.activeThreads,
@@ -358,7 +435,9 @@ export async function runCheck(opts, context) {
358
435
  mergeQueue: {
359
436
  enabled: Boolean(batchData.isMergeQueueEnabled),
360
437
  inQueue: Boolean(batchData.isInMergeQueue),
361
- ...(batchData.autoMergeRequest && { autoMergeRequest: batchData.autoMergeRequest }),
438
+ ...(batchData.autoMergeRequest && {
439
+ autoMergeRequest: batchData.autoMergeRequest,
440
+ }),
362
441
  ...(batchData.mergeQueueEntry && { entry: batchData.mergeQueueEntry }),
363
442
  ...(batchData.latestMergeQueueRemoval && {
364
443
  latestRemoval: batchData.latestMergeQueueRemoval,
@@ -369,7 +448,9 @@ export async function runCheck(opts, context) {
369
448
  : batchData.removedMergeQueueChecksIncomplete) && {
370
449
  checksIncomplete: true,
371
450
  }),
372
- ...(headUpdatedAfterRemoval && { headUpdatedAfterRemoval: true }),
451
+ ...(headUpdatedAfterRemoval && {
452
+ headUpdatedAfterRemoval: true,
453
+ }),
373
454
  ...(removalsOnHead > 1 && { removalsOnHead }),
374
455
  ...(removalAcknowledged && { removalAcknowledged: true }),
375
456
  },
@@ -377,15 +458,24 @@ export async function runCheck(opts, context) {
377
458
  ...(unreported.unreportedRequiredChecks && {
378
459
  unreportedRequiredChecks: unreported.unreportedRequiredChecks,
379
460
  }),
380
- ...(unreported.trunkBehindBy !== undefined && { trunkBehindBy: unreported.trunkBehindBy }),
381
- ...(unreported.baseBehindBy !== undefined && { baseBehindBy: unreported.baseBehindBy }),
461
+ ...(unreported.trunkBehindBy !== undefined && {
462
+ trunkBehindBy: unreported.trunkBehindBy,
463
+ }),
464
+ ...(unreported.baseBehindBy !== undefined && {
465
+ baseBehindBy: unreported.baseBehindBy,
466
+ }),
382
467
  ...(unreported.actionsWorkflowInProgress && {
383
468
  actionsWorkflowInProgress: true,
384
469
  }),
385
- ...(unreported.stackBottomPr !== undefined && { stackBottomPr: unreported.stackBottomPr }),
470
+ ...(unreported.stackBottomPr !== undefined && {
471
+ stackBottomPr: unreported.stackBottomPr,
472
+ }),
386
473
  };
387
474
  if (result.fingerprint) {
388
475
  await storePrFingerprint(stateKey, result.fingerprint, report, config);
476
+ await recordWaitDetectors(stateKey, config, detectors, result.fingerprint);
389
477
  }
478
+ if (restSnapshot)
479
+ await storeRestSnapshotReport(stateKey, restSnapshot.digest, report, config);
390
480
  return report;
391
481
  }
@@ -0,0 +1,11 @@
1
+ import type { NextCheck } from "../types/next-check.mts";
2
+ /**
3
+ * Steps that replace "iterate immediately" when Shepherd ran in event mode and the PR is
4
+ * waiting: end the turn, keep one wake-up, and let the next tick come from a PR event or the
5
+ * wake-up. Always printed after any state-specific sentence.
6
+ */
7
+ export declare function eventWaitSteps(next: NextCheck): string[];
8
+ /** Replaces the `[FIX_CODE]` "iterate immediately" line when the tick ran in event mode. */
9
+ export declare function eventFixContinuation(next: NextCheck): string;
10
+ /** One aggregate step: sessions still run first, and only then does the turn end. */
11
+ export declare function eventAggregateStep(next: NextCheck, waiting: boolean): string;
@@ -0,0 +1,27 @@
1
+ import { playbookPointer } from "./playbook-pointer.mjs";
2
+ function wakeUpClause(next) {
3
+ const kind = next.reason === "safety-net" ? "safety-net wake-up" : "wake-up";
4
+ return `Keep exactly one ${kind} at \`${next.at}\` (\`${next.reason}\`).`;
5
+ }
6
+ /**
7
+ * Steps that replace "iterate immediately" when Shepherd ran in event mode and the PR is
8
+ * waiting: end the turn, keep one wake-up, and let the next tick come from a PR event or the
9
+ * wake-up. Always printed after any state-specific sentence.
10
+ */
11
+ export function eventWaitSteps(next) {
12
+ return [
13
+ `Event mode: end this turn now without sleeping or polling. ${playbookPointer("Cloud event loop")}`,
14
+ `${wakeUpClause(next)} When a PR event or that wake-up arrives, rerun this command with the same options and act only on Shepherd's output, never on the event payload.`,
15
+ ];
16
+ }
17
+ /** Replaces the `[FIX_CODE]` "iterate immediately" line when the tick ran in event mode. */
18
+ export function eventFixContinuation(next) {
19
+ return `\`[FIX_CODE]\` is non-terminal. After the steps above, rerun this command once with the same options, then end the turn without sleeping. ${wakeUpClause(next)} ${playbookPointer("Cloud event loop")}`;
20
+ }
21
+ /** One aggregate step: sessions still run first, and only then does the turn end. */
22
+ export function eventAggregateStep(next, waiting) {
23
+ const lead = waiting
24
+ ? "Event mode: end this turn now without sleeping or polling."
25
+ : "Event mode: once the steps above are done and the rerun still waits, end the turn without sleeping.";
26
+ return `${lead} ${wakeUpClause(next)} When a PR event or that wake-up arrives, rerun this selector with the same options and act only on Shepherd's output, never on the event payload. ${playbookPointer("Cloud event loop")}`;
27
+ }
@@ -0,0 +1,14 @@
1
+ import type { IterateCommandOptions, IterateResult, PollSummaryCommandOptions, PollSummaryResult } from "../types.mts";
2
+ import { type PollCommandOptions } from "./poll.mts";
3
+ import { type AggregatePollCommandOptions } from "./poll-summary.mts";
4
+ /**
5
+ * One iterate tick. In event mode the tick runs exactly once with no settle window and no
6
+ * sleeping, persists seen markers immediately, and reports when to come back.
7
+ */
8
+ export declare function runIterateForMode(opts: IterateCommandOptions): Promise<IterateResult>;
9
+ /** One aggregate tick; event mode adds `pollMode` and `nextCheck`. */
10
+ export declare function runPollSummaryForMode(opts: PollSummaryCommandOptions): Promise<PollSummaryResult>;
11
+ /** The default poll command: the sleeping loop in poll mode, one tick in event mode. */
12
+ export declare function runPollForMode(opts: PollCommandOptions): Promise<IterateResult>;
13
+ /** The aggregate poll command: the sleeping loop in poll mode, one tick in event mode. */
14
+ export declare function runAggregatePollForMode(opts: AggregatePollCommandOptions): Promise<PollSummaryResult>;
@@ -0,0 +1,138 @@
1
+ import { loadConfig } from "../config/load.mjs";
2
+ import { readStallState } from "../state/iterate-stall.mjs";
3
+ import { runWithDurableState } from "../state/durable-state.mjs";
4
+ import { eventAggregateStep, eventFixContinuation } from "./event-instructions.mjs";
5
+ import { earliestNextCheck, MERGE_QUEUE_RECHECK_SECONDS, nextCheckCandidates, SAFETY_NET_SECONDS, } from "./next-check.mjs";
6
+ import { isFixCodeContinuation } from "./iterate/check-instructions.mjs";
7
+ import { isHandedOffSelection } from "./poll-summary-explicit-instructions.mjs";
8
+ import { runIterate } from "./iterate/index.mjs";
9
+ import { resolvePollMode } from "./poll-mode.mjs";
10
+ import { readStackStallDeadline } from "./stack-stall.mjs";
11
+ import { runPoll } from "./poll.mjs";
12
+ import { runAggregatePoll, runPollSummary, } from "./poll-summary.mjs";
13
+ function isEventMode(requested) {
14
+ return resolvePollMode(requested, loadConfig().poll.mode) === "event";
15
+ }
16
+ /**
17
+ * One iterate tick. In event mode the tick runs exactly once with no settle window and no
18
+ * sleeping, persists seen markers immediately, and reports when to come back.
19
+ */
20
+ export function runIterateForMode(opts) {
21
+ if (!isEventMode(opts.pollMode))
22
+ return runIterate(opts);
23
+ return runWithDurableState(() => runIterateEvent(opts));
24
+ }
25
+ /** One aggregate tick; event mode adds `pollMode` and `nextCheck`. */
26
+ export function runPollSummaryForMode(opts) {
27
+ if (!isEventMode(opts.pollMode))
28
+ return runPollSummary(opts);
29
+ const eventOpts = { ...opts, pollMode: "event" };
30
+ return runWithDurableState(async () => withAggregateNextCheck(await runPollSummary(eventOpts), eventOpts));
31
+ }
32
+ /** The default poll command: the sleeping loop in poll mode, one tick in event mode. */
33
+ export function runPollForMode(opts) {
34
+ if (!isEventMode(opts.pollMode))
35
+ return runPoll(opts);
36
+ const { intervalSeconds: _intervalSeconds, timeoutSeconds: _timeoutSeconds, debounceSeconds: _debounceSeconds, quietStatus: _quietStatus, untilTerminal: _untilTerminal, ...tick } = opts;
37
+ return runIterateForMode(tick);
38
+ }
39
+ /** The aggregate poll command: the sleeping loop in poll mode, one tick in event mode. */
40
+ export function runAggregatePollForMode(opts) {
41
+ return isEventMode(opts.pollMode) ? runPollSummaryForMode(opts) : runAggregatePoll(opts);
42
+ }
43
+ async function runIterateEvent(opts) {
44
+ const { pollMode: _pollMode, ...iterateOpts } = opts;
45
+ const sink = {};
46
+ const result = await runIterate({
47
+ ...iterateOpts,
48
+ persistSeen: true,
49
+ fingerprintCache: false,
50
+ stallDeadlineSink: sink,
51
+ });
52
+ const nowMs = Date.now();
53
+ const stallDeadlineSeconds = earliestDeadline(await readStallDeadline(result, opts.stallTimeoutSeconds), result.action === "wait" ? sink.ciStartDeadlineSeconds : undefined);
54
+ const nextCheck = earliestNextCheck(nextCheckCandidates({
55
+ action: result.action,
56
+ remainingSeconds: result.remainingSeconds,
57
+ queued: result.mergeQueue?.inQueue === true,
58
+ restMerge: result.action === "merge" && result.merge.mode === "rest",
59
+ stackDraftHold: result.action === "wait" && result.stackDraftHold !== undefined,
60
+ stallDeadlineSeconds,
61
+ }, nowMs), nowMs);
62
+ const tagged = { ...result, pollMode: "event", ...(nextCheck && { nextCheck }) };
63
+ if (tagged.action !== "fix_code" || !nextCheck)
64
+ return tagged;
65
+ return {
66
+ ...tagged,
67
+ fix: {
68
+ ...tagged.fix,
69
+ instructions: tagged.fix.instructions.map((step) => isFixCodeContinuation(step) ? eventFixContinuation(nextCheck) : step),
70
+ },
71
+ };
72
+ }
73
+ function earliestDeadline(...deadlines) {
74
+ const known = deadlines.filter((d) => d !== undefined);
75
+ return known.length > 0 ? Math.min(...known) : undefined;
76
+ }
77
+ /** When an unchanged state would trip the stall timeout, or undefined when no timer is running. */
78
+ async function readStallDeadline(result, stallTimeoutSeconds) {
79
+ if (!["wait", "fix_code"].includes(result.action))
80
+ return undefined;
81
+ const timeout = stallTimeoutSeconds ?? loadConfig().iterate.stallTimeoutMinutes * 60;
82
+ const [owner, repo] = result.repo.split("/");
83
+ if (timeout <= 0 || !owner || !repo)
84
+ return undefined;
85
+ const read = await readStallState({ owner, repo, pr: result.pr });
86
+ return read.ok && read.state ? read.state.firstSeenAt + timeout : undefined;
87
+ }
88
+ async function withAggregateNextCheck(result, opts) {
89
+ const tagged = { ...result, pollMode: "event" };
90
+ const stallDeadlineSeconds = await readStackStallDeadline(result, opts.stallTimeoutSeconds ?? loadConfig().iterate.stallTimeoutMinutes * 60);
91
+ const nextCheck = aggregateNextCheck(result, stallDeadlineSeconds);
92
+ if (!nextCheck)
93
+ return tagged;
94
+ const instructions = (result.instructions ?? []).map(withoutPollingCadence);
95
+ const waiting = result.reason === "waiting" || result.reason === "timeout";
96
+ return {
97
+ ...tagged,
98
+ nextCheck,
99
+ instructions: [
100
+ ...instructions,
101
+ `${instructions.length + 1}. ${eventAggregateStep(nextCheck, waiting)}`,
102
+ ],
103
+ };
104
+ }
105
+ /**
106
+ * Event mode replaces polling: the appended event step owns the next wake-up, so a stack
107
+ * step's "recheck at the configured polling cadence" would contradict it. A pending stack
108
+ * merge resumes on that wake-up instead.
109
+ */
110
+ function withoutPollingCadence(step) {
111
+ return step
112
+ .replace(/ Recheck(?: them)? at the configured polling cadence\./g, "")
113
+ .replace("rerun that command at the configured cadence to resume its UUID", "rerun that command after the wake-up below to resume its UUID");
114
+ }
115
+ function aggregateNextCheck(result, stallDeadlineSeconds) {
116
+ if (isHandedOffSelection(result) ||
117
+ result.reason === "all_terminal" ||
118
+ result.nextAction === "cancel" ||
119
+ result.nextAction === "escalate") {
120
+ return undefined;
121
+ }
122
+ const candidates = result.prs
123
+ .filter((item) => (item.remainingSeconds ?? 0) > 0)
124
+ .map((item) => ({ reason: "ready-delay", seconds: item.remainingSeconds }));
125
+ // A printed stack merge can stay pending, so recheck it like a REST merge. A mergeable
126
+ // stack that is queued or waiting prints no merge command, so it gets no such wake-up.
127
+ if (result.nextAction === "merge") {
128
+ candidates.push({ reason: "merge-pending", seconds: MERGE_QUEUE_RECHECK_SECONDS });
129
+ }
130
+ candidates.push(result.prs.some((item) => item.isInMergeQueue)
131
+ ? { reason: "merge-queue", seconds: MERGE_QUEUE_RECHECK_SECONDS }
132
+ : { reason: "safety-net", seconds: SAFETY_NET_SECONDS });
133
+ const nowMs = Date.now();
134
+ if (stallDeadlineSeconds !== undefined) {
135
+ candidates.push({ reason: "stall-timeout", seconds: stallDeadlineSeconds - nowMs / 1000 });
136
+ }
137
+ return earliestNextCheck(candidates, nowMs);
138
+ }
@@ -2,6 +2,8 @@ import { loadConfig } from "../../config/load.mjs";
2
2
  import { summarizeApiTelemetry } from "../../github/api-telemetry.mjs";
3
3
  import { selectQuotaWarning } from "../quota-selection.mjs";
4
4
  import { buildQuotaAwareContinuation } from "../../quota-warning.mjs";
5
+ import { getGithubTransport } from "../../github/transport.mjs";
6
+ import { isFixCodeContinuation } from "./check-instructions.mjs";
5
7
  function shouldWarn(result) {
6
8
  return ["wait", "mark_ready", "merge", "fix_code"].includes(result.action);
7
9
  }
@@ -18,7 +20,7 @@ export async function attachApiUsage(result, persistWarning, preservePersistedWa
18
20
  ...band,
19
21
  pollIntervalMinutes: Math.max(band.pollIntervalMinutes, minimumPollIntervalMinutes),
20
22
  }));
21
- quotaWarning = await selectQuotaWarning({ owner, repo }, bands, apiUsage, persistWarning);
23
+ quotaWarning = await selectQuotaWarning({ owner, repo }, bands, apiUsage, persistWarning, getGithubTransport());
22
24
  }
23
25
  }
24
26
  const { quotaWarning: _deferredWarning, ...baseResult } = result;
@@ -32,7 +34,7 @@ export async function attachApiUsage(result, persistWarning, preservePersistedWa
32
34
  const instructions = [...withWarning.fix.instructions];
33
35
  const completion = instructions.at(-1);
34
36
  if (completion !== undefined) {
35
- if (/\[FIX_CODE\].*non-terminal/i.test(completion)) {
37
+ if (isFixCodeContinuation(completion)) {
36
38
  instructions[instructions.length - 1] = buildQuotaAwareContinuation(quotaWarning, "`[FIX_CODE]` is non-terminal. After completing these steps,");
37
39
  }
38
40
  }
@@ -5,6 +5,10 @@ export function buildIterateBase(report, readyState) {
5
5
  return {
6
6
  pr: report.pr,
7
7
  repo: report.repo,
8
+ ...(report.transport && { transport: report.transport }),
9
+ ...(report.transportUnavailable?.length && {
10
+ transportUnavailable: report.transportUnavailable,
11
+ }),
8
12
  status: report.status,
9
13
  state: report.mergeStatus.state,
10
14
  mergeStateStatus: report.mergeStatus.mergeStateStatus,
@@ -1,6 +1,8 @@
1
1
  import { graphql } from "../../github/client.mjs";
2
2
  import { pollRateLimitRetryAfterMs } from "../poll-quota.mjs";
3
3
  import { clearCheckBlocker, formatCheckBlockerRef, readCheckBlockers, } from "../../state/check-blockers.mjs";
4
+ import { rest } from "../../github/http.mjs";
5
+ import { githubOperation } from "../../github/transport.mjs";
4
6
  const RATE_LIMIT = "cost limit nodeCount remaining resetAt used";
5
7
  const PULL_QUERY = `query CheckBlockerPull($owner: String!, $name: String!, $number: Int!) {
6
8
  _shepherdRateLimit: rateLimit { ${RATE_LIMIT} }
@@ -78,7 +80,10 @@ async function lookupBlocker(blocker) {
78
80
  try {
79
81
  const vars = { owner: blocker.owner, name: blocker.name, number: blocker.number };
80
82
  if (blocker.kind === "issue") {
81
- const { data } = await graphql(ISSUE_QUERY, vars);
83
+ const { data } = await githubOperation("CheckBlockerIssue", () => graphql(ISSUE_QUERY, vars), async () => {
84
+ const issue = await rest("GET", `/repos/${blocker.owner}/${blocker.name}/issues/${blocker.number}`);
85
+ return { data: { repository: { issue: { state: issue.state.toUpperCase() } } } };
86
+ });
82
87
  const issue = data.repository?.issue;
83
88
  if (!issue)
84
89
  return ignore(blocker, "not found");
@@ -88,7 +93,14 @@ async function lookupBlocker(blocker) {
88
93
  return "released";
89
94
  return ignore(blocker, `unexpected state ${issue.state}`);
90
95
  }
91
- const { data } = await graphql(PULL_QUERY, vars);
96
+ const { data } = await githubOperation("CheckBlockerPull", () => graphql(PULL_QUERY, vars), async () => {
97
+ const pull = await rest("GET", `/repos/${blocker.owner}/${blocker.name}/pulls/${blocker.number}`);
98
+ return {
99
+ data: {
100
+ repository: { pullRequest: { state: pull.state.toUpperCase(), merged: pull.merged } },
101
+ },
102
+ };
103
+ });
92
104
  const pull = data.repository?.pullRequest;
93
105
  if (!pull)
94
106
  return ignore(blocker, "not found");
@@ -1,5 +1,7 @@
1
1
  import type { AgentCheck, ResolveCommand, Review } from "../../types.mts";
2
- /** Build the stale-CR clause appended to the `## Changes-requested reviews` instruction. */
2
+ /** The `[FIX_CODE]` recurrence step, whether plain or rewritten by the quota warning. */
3
+ export declare function isFixCodeContinuation(step: string): boolean;
4
+ /** Build the stale-CR step for `## Changes-requested reviews`. Empty when no human CR is stale. */
3
5
  export declare function buildCrStaleClause(reviews: Review[]): string;
4
6
  /**
5
7
  * Build the optional branch-update hint. Empty unless the branch is behind or conflicts with its
@@ -24,16 +26,13 @@ export declare function buildRepeatedWorkflowBranchRecoveryInstructions(baseBran
24
26
  hasConflicts: boolean;
25
27
  }, stackRebase?: string): string[];
26
28
  /**
27
- * Build the `Run the apply review: command` instruction. Steps stay here (not in the skill)
28
- * whenever the *unmodified, as-printed* command is unsafe without them:
29
- *
30
- * - `$HEAD_SHA`/`$DISMISS_MESSAGE` substitution: without it, the printed command has an
31
- * empty `--message`/invalid `--require-sha` and `apply review` rejects the mutation.
32
- * Contrast with what *does* stay in the skill's "Review-mutation mechanics" playbook —
33
- * dismiss-ID retention. The pointer below is load-bearing: without it, nothing in CLI output
34
- * tells the agent that playbook exists.
29
+ * Build the `apply review` step, with the rendered command inline. The step stays here (not in
30
+ * the skill) because the *as-printed* command is invalid without `$DISMISS_MESSAGE`: an empty
31
+ * `--message` makes `apply review` reject the mutation. `--require-sha` needs no substitution:
32
+ * the rendered command reads the pushed HEAD itself. "Even if you changed no code": the
33
+ * command records each item's disposition either way.
35
34
  */
36
- export declare function buildResolveCommandInstruction(resolveCommand: ResolveCommand): string[];
35
+ export declare function buildResolveCommandInstruction(resolveCommand: ResolveCommand, rendered: string): string[];
37
36
  /** One pointer. Conclusion, rerun, and bare-check rules live in the CI playbook. */
38
37
  export declare function buildFailingCheckInstructions(checks: AgentCheck[]): string[];
39
38
  /** Update the PR branch after an external blocker merges or closes. Never rerun that job. */