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
@@ -31,7 +31,7 @@ export function classifyReviewsForDisplay(reviews, seenMap) {
31
31
  *
32
32
  * Human-authored CR reviews continue to flow through the standard seen-gate.
33
33
  */
34
- export function classifyChangesRequestedReviewsForDisplay(reviews, seenMap, botUsernames, repeatBotReviews = true) {
34
+ export function classifyChangesRequestedReviewsForDisplay(reviews, seenMap, botUsernames, repeatBotReviews = true, deniedDismissalIds = new Set()) {
35
35
  const visible = [];
36
36
  const toMarkSeen = [];
37
37
  for (const review of reviews) {
@@ -39,8 +39,9 @@ export function classifyChangesRequestedReviewsForDisplay(reviews, seenMap, botU
39
39
  const cls = classifyItem(review.id, review.body, seenMap);
40
40
  if (isBot) {
41
41
  if (cls === "unchanged") {
42
- if (repeatBotReviews)
42
+ if (repeatBotReviews && !deniedDismissalIds.has(review.id)) {
43
43
  visible.push({ ...review, staleBotCr: true });
44
+ }
44
45
  }
45
46
  else if (cls === "edited") {
46
47
  visible.push({ ...review, edited: true });
@@ -3,11 +3,12 @@ import { loadConfig } from "../config/load.mjs";
3
3
  import { sleep } from "../util/sleep.mjs";
4
4
  export async function waitForSha(pr, repo, expectedSha) {
5
5
  const { intervalMs: SHA_POLL_INTERVAL_MS, maxAttempts: SHA_POLL_MAX_ATTEMPTS } = loadConfig().resolve.shaPoll;
6
+ let lastSha;
6
7
  for (let attempt = 0; attempt < SHA_POLL_MAX_ATTEMPTS; attempt++) {
7
8
  try {
8
9
  // eslint-disable-next-line no-await-in-loop
9
- const currentSha = await getPrHeadSha(pr, repo.owner, repo.name);
10
- if (currentSha === expectedSha)
10
+ lastSha = await getPrHeadSha(pr, repo.owner, repo.name);
11
+ if (lastSha === expectedSha)
11
12
  return;
12
13
  }
13
14
  catch (err) {
@@ -19,5 +20,8 @@ export async function waitForSha(pr, repo, expectedSha) {
19
20
  await sleep(SHA_POLL_INTERVAL_MS);
20
21
  }
21
22
  }
22
- throw new Error(`Timeout: GitHub PR #${pr} head SHA has not updated to ${expectedSha} after ${((SHA_POLL_MAX_ATTEMPTS - 1) * SHA_POLL_INTERVAL_MS) / 1000}s. Push may still be in transit — retry shortly.`);
23
+ // Iterate prints a `--require-sha` that reads the current checkout's HEAD. Name that cause,
24
+ // so a run from another checkout can recover from this error alone.
25
+ const current = lastSha ? ` (currently ${lastSha})` : "";
26
+ throw new Error(`Timeout: GitHub PR #${pr} head SHA has not updated to ${expectedSha} after ${((SHA_POLL_MAX_ATTEMPTS - 1) * SHA_POLL_INTERVAL_MS) / 1000}s. Push may still be in transit — retry shortly. If this command ran outside the PR head checkout, rerun it with \`--require-sha\` set to the PR head SHA you pushed${current}.`);
23
27
  }
@@ -23,9 +23,10 @@ function classifyFirstLookThread(thread, seenMap, firstLookStatus) {
23
23
  export function classifyThreadVisibility(threads, seenMap, botUsernames = new Set(), repeatableThreadIds, resolveOtherHumanThreads = "none") {
24
24
  const shouldRepeat = (thread) => repeatableThreadIds?.has(thread.id) ?? true;
25
25
  const isOrdinaryHuman = (thread) => isHumanAuthor(thread) && !isConfiguredBotAuthor(thread, botUsernames);
26
- const unresolvedThreads = threads.filter((t) => !t.isResolved);
26
+ const unknownStatusThreads = threads.filter((thread) => thread.isResolved === undefined || thread.isOutdated === undefined);
27
+ const unresolvedThreads = threads.filter((thread) => thread.isResolved === false && thread.isOutdated !== undefined);
27
28
  const activeThreads = unresolvedThreads
28
- .filter((t) => !t.isOutdated && !t.isMinimized)
29
+ .filter((t) => t.isOutdated === false && t.isMinimized !== true)
29
30
  .flatMap((t) => {
30
31
  if (threadEndedByShepherd(t))
31
32
  return [];
@@ -56,23 +57,27 @@ export function classifyThreadVisibility(threads, seenMap, botUsernames = new Se
56
57
  });
57
58
  const firstLookThreads = [
58
59
  ...threads.flatMap((t) => {
59
- if (!t.isOutdated)
60
+ if (t.isOutdated !== true || t.isResolved === undefined)
60
61
  return [];
61
62
  const visible = classifyFirstLookThread(t, seenMap, "outdated");
62
63
  return visible ? [visible] : [];
63
64
  }),
64
65
  ...threads.flatMap((t) => {
65
- if (!t.isResolved || t.isOutdated)
66
+ if (t.isResolved !== true || t.isOutdated !== false)
66
67
  return [];
67
68
  const visible = classifyFirstLookThread(t, seenMap, "resolved");
68
69
  return visible ? [visible] : [];
69
70
  }),
70
71
  ...threads.flatMap((t) => {
71
- if (!t.isMinimized || t.isResolved || t.isOutdated)
72
+ if (!t.isMinimized || t.isResolved !== false || t.isOutdated !== false)
72
73
  return [];
73
74
  const visible = classifyFirstLookThread(t, seenMap, "minimized");
74
75
  return visible ? [visible] : [];
75
76
  }),
77
+ ...unknownStatusThreads.flatMap((thread) => {
78
+ const visible = classifyFirstLookThread(thread, seenMap, "unknown");
79
+ return visible ? [visible] : [];
80
+ }),
76
81
  ];
77
82
  const seenMarkerIds = new Set();
78
83
  const toMarkSeen = [...activeThreads, ...resolutionOnlyThreads, ...firstLookThreads].filter((thread) => {
@@ -0,0 +1,11 @@
1
+ import type { RepoInfo } from "../github/client.mts";
2
+ import { type ReplyEvidence } from "../github/reply-recovery-read.mts";
3
+ interface ReplyContext {
4
+ repo: RepoInfo;
5
+ pr: number;
6
+ }
7
+ export declare function trackAdoptedReplyThreads(result: object, ids: readonly string[]): void;
8
+ export declare function adoptedReplyThreads(result: object): readonly string[];
9
+ export declare function assertReplyOutcomeKnown(context: ReplyContext, ids: string[], message: string): Promise<string[]>;
10
+ export declare function rememberUncertainReplies(context: ReplyContext, ids: string[], message: string, evidence?: Map<string, ReplyEvidence>): Promise<void>;
11
+ export {};
@@ -0,0 +1,121 @@
1
+ import { createHash } from "node:crypto";
2
+ import { readFile, mkdir, writeFile } from "node:fs/promises";
3
+ import { dirname } from "node:path";
4
+ import { resolvePrStatePath } from "../state/base.mjs";
5
+ import { resolveRestThreadRoot } from "../github/rest-identities.mjs";
6
+ import { readReplyRecoveryEvidence } from "../github/reply-recovery-read.mjs";
7
+ import { addPrShepherdMarker } from "./marker.mjs";
8
+ import { prepareUnmappedIntents, rememberUnmappedIntent } from "./uncertain-reply-guards.mjs";
9
+ const adoptedReplies = new WeakMap();
10
+ export function trackAdoptedReplyThreads(result, ids) {
11
+ adoptedReplies.set(result, ids);
12
+ }
13
+ export function adoptedReplyThreads(result) {
14
+ return adoptedReplies.get(result) ?? [];
15
+ }
16
+ async function paths(context, id, message) {
17
+ const identities = [id];
18
+ try {
19
+ identities.push(`rest-thread-${await resolveRestThreadRoot(context.repo, context.pr, id)}`);
20
+ }
21
+ catch { }
22
+ return [...new Set(identities)].map((identity) => resolvePrStatePath({ owner: context.repo.owner, repo: context.repo.name, pr: context.pr }, "uncertain-replies", `${createHash("sha256").update(`${identity}\n${message}`).digest("hex")}.json`));
23
+ }
24
+ export async function assertReplyOutcomeKnown(context, ids, message) {
25
+ await prepareUnmappedIntents(context, ids, message, paths);
26
+ const pending = new Map();
27
+ for (const id of ids) {
28
+ const present = [];
29
+ let record;
30
+ for (const path of await paths(context, id, message)) {
31
+ try {
32
+ const raw = await readFile(path, "utf8");
33
+ present.push(path);
34
+ const parsed = JSON.parse(raw);
35
+ if (parsed && typeof parsed.id === "string" && typeof parsed.message === "string")
36
+ record = parsed;
37
+ }
38
+ catch (error) {
39
+ if (error instanceof SyntaxError)
40
+ continue;
41
+ if (error.code !== "ENOENT")
42
+ throw error;
43
+ }
44
+ }
45
+ if (present.length)
46
+ pending.set(id, { paths: present, record: record ?? { id, message } });
47
+ }
48
+ const adopted = [];
49
+ const pendingIds = [...pending.keys()].filter((id) => {
50
+ const record = pending.get(id).record;
51
+ if (typeof record.adoptedCommentId === "string" &&
52
+ record.adoptedCommentId &&
53
+ record.message === message) {
54
+ adopted.push(id);
55
+ return false;
56
+ }
57
+ return true;
58
+ });
59
+ for (let offset = 0; offset < pendingIds.length; offset += 10) {
60
+ let evidence = new Map();
61
+ try {
62
+ evidence = await readReplyRecoveryEvidence(context.repo, context.pr, pendingIds.slice(offset, offset + 10));
63
+ }
64
+ catch {
65
+ /* Failed reads cannot authorize a duplicate reply. */
66
+ }
67
+ for (const id of pendingIds.slice(offset, offset + 10)) {
68
+ const item = pending.get(id);
69
+ const current = evidence.get(id);
70
+ const record = item.record;
71
+ const before = new Set(Array.isArray(record.beforeIds) ? record.beforeIds : []);
72
+ const matches = record.id &&
73
+ record.message === message &&
74
+ typeof record.viewer === "string" &&
75
+ record.viewer &&
76
+ Array.isArray(record.beforeIds) &&
77
+ record.beforeIds.every((value) => typeof value === "string") &&
78
+ current?.viewer.toLowerCase() === record.viewer.toLowerCase()
79
+ ? current.comments.filter((comment) => !before.has(comment.id) &&
80
+ comment.body === addPrShepherdMarker(message) &&
81
+ comment.author?.toLowerCase() === record.viewer.toLowerCase())
82
+ : [];
83
+ if (matches.length !== 1) {
84
+ const recoveryPaths = [
85
+ ...new Set([...item.paths, ...(await paths(context, record.id, message))]),
86
+ ];
87
+ const command = `rm -- ${recoveryPaths.map((path) => `'${path.replaceAll("'", "'\\''")}'`).join(" ")}`;
88
+ throw new Error(`Previous GraphQL reply outcome is uncertain for ${id}; no duplicate reply was sent.\n` +
89
+ "1. Inspect the complete thread transcript and verify whether the previous reply was delivered.\n" +
90
+ "2. If you verified non-delivery and no request remains in flight, clear only this reply's markers and run:\n" +
91
+ ` ${command}\n` +
92
+ "3. Rerun the original reply command; do not clear all PR state or change the disposition to bypass this check.");
93
+ }
94
+ // Keep confirmed outcomes durable: another pending ID or a later resolve can still fail.
95
+ const outcomePaths = new Set([...item.paths, ...(await paths(context, record.id, message))]);
96
+ for (const path of outcomePaths)
97
+ await writeFile(path, JSON.stringify({ ...record, adoptedCommentId: matches[0].id }));
98
+ adopted.push(id);
99
+ }
100
+ }
101
+ await prepareUnmappedIntents(context, ids, message, paths);
102
+ return adopted;
103
+ }
104
+ export async function rememberUncertainReplies(context, ids, message, evidence = new Map()) {
105
+ for (const id of ids) {
106
+ for (const path of await paths(context, id, message)) {
107
+ await mkdir(dirname(path), { recursive: true });
108
+ const baseline = evidence.get(id);
109
+ await writeFile(path, JSON.stringify({
110
+ id,
111
+ message,
112
+ recordedAt: Date.now(),
113
+ ...(baseline && {
114
+ viewer: baseline.viewer,
115
+ beforeIds: baseline.comments.map((comment) => comment.id),
116
+ }),
117
+ }));
118
+ }
119
+ await rememberUnmappedIntent(context, id, message);
120
+ }
121
+ }
@@ -0,0 +1,10 @@
1
+ import type { RepoInfo } from "../github/client.mts";
2
+ interface Context {
3
+ repo: RepoInfo;
4
+ pr: number;
5
+ }
6
+ type Paths = (context: Context, id: string, message: string) => Promise<string[]>;
7
+ /** One guard per unresolved intent: clearing one never clears another pending reply. */
8
+ export declare function rememberUnmappedIntent(context: Context, id: string, message: string): Promise<void>;
9
+ export declare function prepareUnmappedIntents(context: Context, ids: string[], message: string, paths: Paths): Promise<void>;
10
+ export {};
@@ -0,0 +1,94 @@
1
+ import { createHash } from "node:crypto";
2
+ import { mkdir, readFile, readdir, unlink, writeFile } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+ import { resolvePrStatePath } from "../state/base.mjs";
5
+ import { resolveRestThreadRoot } from "../github/rest-identities.mjs";
6
+ import { readReplyRecoveryEvidence } from "../github/reply-recovery-read.mjs";
7
+ const hash = (value) => createHash("sha256").update(value).digest("hex");
8
+ function directory(context, message) {
9
+ return resolvePrStatePath({ owner: context.repo.owner, repo: context.repo.name, pr: context.pr }, "unmapped-reply-intents", hash(message));
10
+ }
11
+ /** One guard per unresolved intent: clearing one never clears another pending reply. */
12
+ export async function rememberUnmappedIntent(context, id, message) {
13
+ try {
14
+ await resolveRestThreadRoot(context.repo, context.pr, id);
15
+ return;
16
+ }
17
+ catch { }
18
+ const dir = directory(context, message);
19
+ await mkdir(dir, { recursive: true });
20
+ await writeFile(join(dir, `${hash(id)}.json`), JSON.stringify({ id, message }));
21
+ }
22
+ export async function prepareUnmappedIntents(context, ids, message, paths) {
23
+ const dir = directory(context, message);
24
+ let entries;
25
+ try {
26
+ entries = await readdir(dir);
27
+ }
28
+ catch (error) {
29
+ if (error.code === "ENOENT")
30
+ return;
31
+ throw error;
32
+ }
33
+ if (entries.length > 1000)
34
+ throw new Error("Too many unresolved reply intents; inspect this PR's state before replying");
35
+ const requestedHaveMarkers = (await Promise.all(ids.map(async (id) => {
36
+ for (const path of await paths(context, id, message)) {
37
+ try {
38
+ await readFile(path);
39
+ return true;
40
+ }
41
+ catch (error) {
42
+ if (error.code !== "ENOENT")
43
+ throw error;
44
+ }
45
+ }
46
+ return false;
47
+ }))).every(Boolean);
48
+ for (const entry of entries) {
49
+ const guard = join(dir, entry);
50
+ const intent = JSON.parse(await readFile(guard, "utf8"));
51
+ if (typeof intent.id !== "string" || intent.message !== message)
52
+ throw new Error("Invalid unresolved reply intent");
53
+ let root;
54
+ try {
55
+ root = await resolveRestThreadRoot(context.repo, context.pr, intent.id);
56
+ }
57
+ catch { }
58
+ if (!root && !requestedHaveMarkers) {
59
+ try {
60
+ await readReplyRecoveryEvidence(context.repo, context.pr, [intent.id]);
61
+ root = await resolveRestThreadRoot(context.repo, context.pr, intent.id);
62
+ }
63
+ catch { }
64
+ }
65
+ if (!root) {
66
+ if (requestedHaveMarkers)
67
+ continue;
68
+ const files = [guard, ...(await paths(context, intent.id, message))];
69
+ const command = `rm -- ${files.map((file) => `'${file.replaceAll("'", "'\\''")}'`).join(" ")}`;
70
+ throw new Error(`Previous GraphQL reply outcome is uncertain for ${intent.id}; its REST identity is unknown. ` +
71
+ "This PR's identical reply message is blocked through unproven aliases; other messages remain independent.\n" +
72
+ "1. Inspect the original thread's complete transcript and verify non-delivery and that no request remains in flight.\n" +
73
+ `2. Only after that verification, clear this original intent's markers and guard: ${command}\n` +
74
+ "3. Rerun the original command.");
75
+ }
76
+ const locations = await paths(context, intent.id, message);
77
+ let record;
78
+ for (const path of locations) {
79
+ try {
80
+ record = await readFile(path, "utf8");
81
+ break;
82
+ }
83
+ catch (error) {
84
+ if (error.code !== "ENOENT")
85
+ throw error;
86
+ }
87
+ }
88
+ // A removed marker is explicit targeted recovery; otherwise preserve the original outcome.
89
+ if (record)
90
+ for (const path of locations)
91
+ await writeFile(path, record);
92
+ await unlink(guard);
93
+ }
94
+ }
@@ -7,5 +7,5 @@ interface VisibleCommentClassification {
7
7
  minimizeIds: string[];
8
8
  toMarkSeen: ActionableComment[];
9
9
  }
10
- export declare function classifyVisibleComments(comments: PrComment[], seenMap: Map<string, SeenMarker>, minimizeComments: MinimizeCommentsPolicy | undefined, botUsernames?: NormalizedBotUsernames): VisibleCommentClassification;
10
+ export declare function classifyVisibleComments(comments: PrComment[], seenMap: Map<string, SeenMarker>, minimizeComments: MinimizeCommentsPolicy | undefined, botUsernames?: NormalizedBotUsernames, deniedMinimizeIds?: ReadonlySet<string>): VisibleCommentClassification;
11
11
  export {};
@@ -1,11 +1,13 @@
1
1
  import { shouldMinimizeAuthor } from "./minimize-policy.mjs";
2
2
  import { classifyItem } from "../state/seen-comments.mjs";
3
- export function classifyVisibleComments(comments, seenMap, minimizeComments, botUsernames = new Set()) {
3
+ import { canGenerateGithubMutation } from "../github/mutation-policy.mjs";
4
+ export function classifyVisibleComments(comments, seenMap, minimizeComments, botUsernames = new Set(), deniedMinimizeIds = new Set()) {
4
5
  const actionable = [];
5
6
  const minimizeIds = [];
6
7
  const toMarkSeen = [];
7
8
  for (const c of comments.filter((comment) => !comment.isMinimized)) {
8
- if (c.viewerCanMinimize === true &&
9
+ if (!deniedMinimizeIds.has(c.id) &&
10
+ canGenerateGithubMutation(c.viewerCanMinimize, "minimize") &&
9
11
  shouldMinimizeAuthor(c.authorType, minimizeComments, c.author, botUsernames)) {
10
12
  actionable.push(c);
11
13
  minimizeIds.push(c.id);
@@ -1,8 +1,23 @@
1
1
  import { type MergeMethod } from "./merge-method.mts";
2
+ import { type GithubTransport } from "../github/transport-mode.mts";
2
3
  declare const MINIMIZE_COMMENTS_POLICIES: readonly ["all", "bots", "users", "none"];
3
4
  export type MinimizeCommentsPolicy = (typeof MINIMIZE_COMMENTS_POLICIES)[number];
4
5
  declare const RESOLVE_OTHER_HUMAN_THREADS: readonly ["none", "outdated", "always"];
5
6
  export type ResolveOtherHumanThreads = (typeof RESOLVE_OTHER_HUMAN_THREADS)[number];
7
+ export declare const INSTRUCTION_STYLES: readonly ["playbook", "inline"];
8
+ /**
9
+ * How `fix_code` `## Instructions` render invariant procedure text. `inline` (default)
10
+ * prints every step each tick; `playbook` points at the shipped "Fix-code loop" playbook.
11
+ */
12
+ export type InstructionStyle = (typeof INSTRUCTION_STYLES)[number];
13
+ export declare function isInstructionStyle(value: unknown): value is InstructionStyle;
14
+ export declare const POLL_MODES: readonly ["auto", "poll", "event"];
15
+ /**
16
+ * `poll` blocks and loops. `event` runs one tick and reports `nextCheck`. `auto` resolves to
17
+ * `event` when `CLAUDE_CODE_REMOTE=true` and to `poll` everywhere else.
18
+ */
19
+ export type PollMode = (typeof POLL_MODES)[number];
20
+ export declare function isPollMode(value: unknown): value is PollMode;
6
21
  export interface GraphqlQuotaWarningBand {
7
22
  remainingPercent: number;
8
23
  pollIntervalMinutes: number;
@@ -14,10 +29,25 @@ interface PollConfig {
14
29
  timeoutSeconds: number;
15
30
  debounceSeconds: number;
16
31
  quietStatus: boolean;
32
+ mode: PollMode;
33
+ /**
34
+ * Longest gap between GraphQL snapshots while conditional REST reads report an idle PR
35
+ * unchanged. `0` disables the REST change detectors on the GraphQL transport.
36
+ */
37
+ reconcileSeconds: number;
17
38
  }
18
39
  export interface PrShepherdConfig {
19
- /** Optional user classification configuration; preserved for rule consumers. */
40
+ /** GitHub API settings; auto prefers GraphQL outside Claude Code cloud sessions. */
41
+ github?: {
42
+ transport?: GithubTransport;
43
+ };
44
+ /**
45
+ * Optional user classification configuration; preserved for rule consumers. Shepherd reads
46
+ * only `classify.standard`, a map of bundled rule name (or `all`) to boolean.
47
+ */
20
48
  classify?: unknown;
49
+ /** Bundled classification rules enabled by `classify.standard`, resolved and sorted by name. */
50
+ standardRules: string[];
21
51
  /** Argv prefix for every pr-shepherd command Shepherd emits, e.g. `["pnpm", "exec", "pr-shepherd"]`. */
22
52
  cliCommand: string[];
23
53
  /** GitHub logins that should be treated as bots even when GitHub reports User/Unknown. */
@@ -50,6 +80,12 @@ export interface PrShepherdConfig {
50
80
  * also resolves when GitHub reports `isOutdated`; `always` pairs reply-and-resolve.
51
81
  */
52
82
  resolveOtherHumanThreads: ResolveOtherHumanThreads;
83
+ /**
84
+ * `inline` (default) prints the commit/push and journal steps of `fix_code` instructions
85
+ * every tick; `playbook` folds them into one `Playbook: "Fix-code loop"` pointer. The
86
+ * default is inline because the token bench measured playbook as a net cost: it adds a read. The `--instructions` flag and MCP `instructions` input override per call.
87
+ */
88
+ instructions: InstructionStyle;
53
89
  };
54
90
  poll: PollConfig;
55
91
  watch: {
@@ -7,8 +7,18 @@ import builtins from "../config.json" with { type: "json" };
7
7
  import { getEffectiveCwd } from "../execution-context.mjs";
8
8
  import { findMergeStrategies } from "./merge-command-args.mjs";
9
9
  import { parseMergeMethod } from "./merge-method.mjs";
10
+ import { parseGithubTransport } from "../github/transport-mode.mjs";
11
+ import { STANDARD_RULE_NAMES } from "../classify/standard/index.mjs";
10
12
  const MINIMIZE_COMMENTS_POLICIES = ["all", "bots", "users", "none"];
11
13
  const RESOLVE_OTHER_HUMAN_THREADS = ["none", "outdated", "always"];
14
+ export const INSTRUCTION_STYLES = ["playbook", "inline"];
15
+ export function isInstructionStyle(value) {
16
+ return INSTRUCTION_STYLES.some((style) => style === value);
17
+ }
18
+ export const POLL_MODES = ["auto", "poll", "event"];
19
+ export function isPollMode(value) {
20
+ return POLL_MODES.some((mode) => mode === value);
21
+ }
12
22
  const RC_FILENAME = ".pr-shepherdrc.yml";
13
23
  /**
14
24
  * Collect `.pr-shepherdrc.yml` files from `startDir` toward `$HOME` (closest first).
@@ -77,6 +87,11 @@ function parseResolveOtherHumanThreads(value) {
77
87
  }
78
88
  throw new Error(`Invalid config: iterate.resolveOtherHumanThreads must be one of "none", "outdated", or "always", got ${JSON.stringify(value)}`);
79
89
  }
90
+ function parseInstructionStyle(value) {
91
+ if (isInstructionStyle(value))
92
+ return value;
93
+ throw new Error(`Invalid config: iterate.instructions must be "playbook" or "inline", got ${JSON.stringify(value)}`);
94
+ }
80
95
  function parseCliCommand(value) {
81
96
  if (!Array.isArray(value) ||
82
97
  value.length === 0 ||
@@ -154,13 +169,26 @@ function parsePollConfig(value) {
154
169
  const intervalSeconds = parsePollDuration(record["intervalSeconds"], "intervalSeconds");
155
170
  const timeoutSeconds = parsePollDuration(record["timeoutSeconds"], "timeoutSeconds");
156
171
  const debounceSeconds = parsePollDuration(record["debounceSeconds"], "debounceSeconds", true);
172
+ const reconcileSeconds = parsePollDuration(record["reconcileSeconds"], "reconcileSeconds", true);
157
173
  const stackIntervalFactor = parseStackIntervalFactor(record["stackIntervalFactor"]);
158
174
  assertAggregateIntervalFits(intervalSeconds, stackIntervalFactor);
159
175
  const quietStatus = record["quietStatus"];
160
176
  if (typeof quietStatus !== "boolean") {
161
177
  throw new Error(`Invalid config: poll.quietStatus must be a boolean, got ${JSON.stringify(quietStatus)}`);
162
178
  }
163
- return { intervalSeconds, stackIntervalFactor, timeoutSeconds, debounceSeconds, quietStatus };
179
+ const mode = record["mode"];
180
+ if (!isPollMode(mode)) {
181
+ throw new Error(`Invalid config: poll.mode must be one of ${POLL_MODES.join(", ")}, got ${JSON.stringify(mode)}`);
182
+ }
183
+ return {
184
+ intervalSeconds,
185
+ stackIntervalFactor,
186
+ timeoutSeconds,
187
+ debounceSeconds,
188
+ quietStatus,
189
+ mode,
190
+ reconcileSeconds,
191
+ };
164
192
  }
165
193
  function parseStackIntervalFactor(value) {
166
194
  if (typeof value !== "number" || !Number.isFinite(value) || value < 1) {
@@ -247,6 +275,7 @@ function parseGraphqlQuotaWarnings(value, pollIntervalSeconds) {
247
275
  return parsed;
248
276
  }
249
277
  const KNOWN_CONFIG_KEYS = new Set([
278
+ "github",
250
279
  "classify",
251
280
  "cliCommand",
252
281
  "botUsernames",
@@ -261,6 +290,7 @@ const KNOWN_CONFIG_KEYS = new Set([
261
290
  "actions",
262
291
  ]);
263
292
  const KNOWN_NESTED_KEYS = {
293
+ github: new Set(["transport"]),
264
294
  iterate: new Set([
265
295
  "fixAttemptsPerThread",
266
296
  "stallTimeoutMinutes",
@@ -268,6 +298,7 @@ const KNOWN_NESTED_KEYS = {
268
298
  "minimizeComments",
269
299
  "behindBaseHint",
270
300
  "resolveOtherHumanThreads",
301
+ "instructions",
271
302
  ]),
272
303
  poll: new Set([
273
304
  "intervalSeconds",
@@ -275,6 +306,8 @@ const KNOWN_NESTED_KEYS = {
275
306
  "timeoutSeconds",
276
307
  "debounceSeconds",
277
308
  "quietStatus",
309
+ "mode",
310
+ "reconcileSeconds",
278
311
  ]),
279
312
  watch: new Set(["readyDelayMinutes", "graphqlQuotaWarnings"]),
280
313
  resolve: new Set(["shaPoll"]),
@@ -314,8 +347,45 @@ function warnUnknownConfigKeys(config) {
314
347
  }
315
348
  }
316
349
  const rawDefaults = builtins;
350
+ /**
351
+ * Resolves `classify.standard` (`{ all?: boolean, <rule>?: boolean }`) to enabled rule names.
352
+ * A named entry overrides `all`. Unknown names and non-boolean values warn and are ignored.
353
+ */
354
+ function parseStandardRules(classify) {
355
+ if (classify === null || typeof classify !== "object" || Array.isArray(classify))
356
+ return [];
357
+ const standard = classify["standard"];
358
+ if (standard === undefined)
359
+ return [];
360
+ if (standard === null || typeof standard !== "object" || Array.isArray(standard)) {
361
+ process.stderr.write("pr-shepherd: config: classify.standard must be a map of rule name to boolean; ignored.\n");
362
+ return [];
363
+ }
364
+ const entries = standard;
365
+ const flags = new Map();
366
+ for (const [name, value] of Object.entries(entries)) {
367
+ if (name !== "all" && !STANDARD_RULE_NAMES.includes(name)) {
368
+ process.stderr.write(`pr-shepherd: config: unknown standard classification rule "classify.standard.${name}" ignored.\n`);
369
+ continue;
370
+ }
371
+ if (typeof value !== "boolean") {
372
+ process.stderr.write(`pr-shepherd: config: classify.standard.${name} must be a boolean; ignored.\n`);
373
+ continue;
374
+ }
375
+ flags.set(name, value);
376
+ }
377
+ const all = flags.get("all") ?? false;
378
+ return STANDARD_RULE_NAMES.filter((name) => flags.get(name) ?? all).sort();
379
+ }
317
380
  function parseConfig(value, normalizeMergeArgs = true) {
318
381
  const config = value;
382
+ const github = value["github"];
383
+ if (github === null || typeof github !== "object" || Array.isArray(github)) {
384
+ throw new Error("Invalid config: github must be a plain object");
385
+ }
386
+ config.github = {
387
+ transport: parseGithubTransport(github["transport"] ?? "auto"),
388
+ };
319
389
  config.cliCommand = parseCliCommand(config.cliCommand);
320
390
  config.botUsernames = parseBotUsernames(config.botUsernames);
321
391
  config.ignoreChecks = parseIgnoreChecks(config.ignoreChecks);
@@ -333,7 +403,9 @@ function parseConfig(value, normalizeMergeArgs = true) {
333
403
  config.watch.graphqlQuotaWarnings = parseGraphqlQuotaWarnings(config.watch.graphqlQuotaWarnings, config.poll.intervalSeconds);
334
404
  config.iterate.minimizeComments = parseMinimizeCommentsPolicy(config.iterate.minimizeComments);
335
405
  config.iterate.resolveOtherHumanThreads = parseResolveOtherHumanThreads(config.iterate.resolveOtherHumanThreads);
406
+ config.iterate.instructions = parseInstructionStyle(config.iterate.instructions);
336
407
  config.checks.ignoreLogLines = parseIgnoreLogLines(config.checks.ignoreLogLines);
408
+ config.standardRules = parseStandardRules(config.classify);
337
409
  return config;
338
410
  }
339
411
  const defaults = parseConfig(structuredClone(rawDefaults), false);
package/bin/config.json CHANGED
@@ -1,4 +1,5 @@
1
1
  {
2
+ "github": { "transport": "auto" },
2
3
  "cliCommand": ["pr-shepherd"],
3
4
  "botUsernames": [
4
5
  "chatgpt-connector",
@@ -21,14 +22,17 @@
21
22
  "minimizeApprovals": false,
22
23
  "minimizeComments": "all",
23
24
  "behindBaseHint": "",
24
- "resolveOtherHumanThreads": "none"
25
+ "resolveOtherHumanThreads": "none",
26
+ "instructions": "inline"
25
27
  },
26
28
  "poll": {
27
29
  "intervalSeconds": 60,
28
30
  "stackIntervalFactor": 2,
29
31
  "timeoutSeconds": 270,
30
32
  "debounceSeconds": 60,
31
- "quietStatus": false
33
+ "quietStatus": false,
34
+ "mode": "auto",
35
+ "reconcileSeconds": 900
32
36
  },
33
37
  "watch": {
34
38
  "readyDelayMinutes": 10,
@@ -10,6 +10,7 @@
10
10
  * ran to completion and is reporting PR state. See docs/exit-codes.md.
11
11
  */
12
12
  import type { IterateResult } from "./types.mts";
13
+ import type { ResolveResult } from "./comments/resolve.mts";
13
14
  export declare const EXIT: Readonly<{
14
15
  /** `cancel` + `merged` or `ready-delay-elapsed` — shepherd finished cleanly. */
15
16
  readonly OK: 0;
@@ -52,4 +53,6 @@ export declare class ShepherdError extends Error {
52
53
  });
53
54
  }
54
55
  export declare function iterateResultToExitCode(result: IterateResult): number;
56
+ /** Exit code for a completed `apply review` run: 77 on a session refusal, 75/69 on errors, else 0. */
57
+ export declare function applyReviewResultToExitCode(result: Pick<ResolveResult, "sessionRefusal" | "errors" | "rateLimit">): number;
55
58
  export declare function errorToExitCode(err: unknown): number;
@@ -74,6 +74,14 @@ export function iterateResultToExitCode(result) {
74
74
  return EXIT.MERGE;
75
75
  }
76
76
  }
77
+ /** Exit code for a completed `apply review` run: 77 on a session refusal, 75/69 on errors, else 0. */
78
+ export function applyReviewResultToExitCode(result) {
79
+ if (result.sessionRefusal)
80
+ return EXIT.NOPERM;
81
+ if (result.errors.length > 0)
82
+ return result.rateLimit ? EXIT.TEMPFAIL : EXIT.UNAVAILABLE;
83
+ return EXIT.OK;
84
+ }
77
85
  export function errorToExitCode(err) {
78
86
  if (err instanceof ShepherdError)
79
87
  return err.exitCode;
@@ -1,5 +1,16 @@
1
1
  import type { ApiTelemetryEvent } from "./api-telemetry.mts";
2
2
  import type { RateLimitInfo } from "./http-utils.mts";
3
+ interface RestTelemetryGroup {
4
+ /** Quota-consuming requests; 304 Not Modified responses are tallied separately. */
5
+ requestCount: number;
6
+ notModifiedCount: number;
7
+ rateLimit?: RateLimitInfo;
8
+ /** Fingerprint for the credential that produced the selected quota sample. */
9
+ credentialFingerprint?: string;
10
+ /** Newest observed credential and sequence, even if its sample did not win quota selection. */
11
+ latestCredentialFingerprint?: string;
12
+ latestCredentialSequence?: number;
13
+ }
3
14
  export interface TelemetryAggregate {
4
15
  eventCount: number;
5
16
  credentialSources: Map<string, number>;
@@ -11,10 +22,9 @@ export interface TelemetryAggregate {
11
22
  rateLimit?: RateLimitInfo;
12
23
  credentialFingerprint?: string;
13
24
  };
14
- rest: Map<string, {
15
- requestCount: number;
16
- rateLimit?: RateLimitInfo;
17
- }>;
25
+ rest: Map<string, RestTelemetryGroup>;
26
+ /** Headerless REST responses (not 304s), kept out of every resource group: the proxy's `ccr` routes. */
27
+ unmeteredRestRequestCount: number;
18
28
  }
19
29
  export interface SequencedApiTelemetryEvent extends ApiTelemetryEvent {
20
30
  sequence: number;
@@ -31,3 +41,4 @@ export declare function emptyAggregate(): TelemetryAggregate;
31
41
  export declare function aggregateEvents(events: SequencedApiTelemetryEvent[]): TelemetryAggregate;
32
42
  export declare function mergeAggregate(target: TelemetryAggregate, source: TelemetryAggregate): void;
33
43
  export declare function aggregateStore(active: TelemetryStore): TelemetryAggregate;
44
+ export {};