pr-shepherd 0.51.1 → 0.52.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +29 -18
  3. package/bin/cli/help-command-pages.d.mts +1 -1
  4. package/bin/cli/help-iterate-poll-pages.d.mts +1 -1
  5. package/bin/cli/help-iterate-poll-pages.mjs +4 -3
  6. package/bin/cli/help-top-page.d.mts +1 -1
  7. package/bin/cli/help-top-page.mjs +3 -2
  8. package/bin/cli/help.d.mts +2 -2
  9. package/bin/cli/iterate-instructions.mjs +41 -0
  10. package/bin/cli/iterate-lean.mjs +1 -3
  11. package/bin/cli/poll-summary-emitter.mjs +2 -3
  12. package/bin/cli/poll-summary-formatter.mjs +12 -3
  13. package/bin/cli/runner.d.mts +1 -0
  14. package/bin/cli/runner.mjs +3 -1
  15. package/bin/commands/check.mjs +14 -4
  16. package/bin/commands/clean.mjs +7 -13
  17. package/bin/commands/iterate/check-instructions.d.mts +2 -2
  18. package/bin/commands/iterate/check-instructions.mjs +11 -7
  19. package/bin/commands/iterate/escalate.mjs +4 -13
  20. package/bin/commands/iterate/fix-code.d.mts +2 -0
  21. package/bin/commands/iterate/fix-code.mjs +20 -43
  22. package/bin/commands/iterate/helpers.d.mts +0 -1
  23. package/bin/commands/iterate/helpers.mjs +0 -15
  24. package/bin/commands/iterate/index.mjs +178 -11
  25. package/bin/commands/iterate/merge-state.mjs +35 -18
  26. package/bin/commands/iterate/native-stack-rebase.d.mts +34 -0
  27. package/bin/commands/iterate/native-stack-rebase.mjs +43 -0
  28. package/bin/commands/iterate/parent-first.d.mts +25 -0
  29. package/bin/commands/iterate/parent-first.mjs +70 -0
  30. package/bin/commands/iterate/render.d.mts +1 -1
  31. package/bin/commands/iterate/render.mjs +7 -12
  32. package/bin/commands/iterate/stale-ancestry.d.mts +22 -0
  33. package/bin/commands/iterate/stale-ancestry.mjs +40 -0
  34. package/bin/commands/iterate/stall.mjs +40 -3
  35. package/bin/commands/poll-progress.d.mts +5 -1
  36. package/bin/commands/poll-progress.mjs +7 -1
  37. package/bin/commands/poll-summary-instructions.d.mts +6 -1
  38. package/bin/commands/poll-summary-instructions.mjs +151 -107
  39. package/bin/commands/poll-summary.mjs +32 -6
  40. package/bin/commands/poll.mjs +3 -1
  41. package/bin/commands/ready-delay.d.mts +9 -4
  42. package/bin/commands/ready-delay.mjs +34 -20
  43. package/bin/commands/shepherd-journal.mjs +4 -1
  44. package/bin/commands/stack-drain.d.mts +35 -0
  45. package/bin/commands/stack-drain.mjs +129 -0
  46. package/bin/commands/stack-layer-readiness.d.mts +7 -0
  47. package/bin/commands/stack-layer-readiness.mjs +35 -0
  48. package/bin/commands/stack-stall.d.mts +14 -0
  49. package/bin/commands/stack-stall.mjs +69 -0
  50. package/bin/commands/stack-work.d.mts +32 -0
  51. package/bin/commands/stack-work.mjs +39 -0
  52. package/bin/config/load.d.mts +2 -0
  53. package/bin/config/load.mjs +10 -0
  54. package/bin/config.json +1 -0
  55. package/bin/exit-codes.d.mts +2 -0
  56. package/bin/exit-codes.mjs +2 -0
  57. package/bin/github/batch-parsers.mjs +1 -0
  58. package/bin/github/batch-raw-types.d.mts +2 -0
  59. package/bin/github/errors.d.mts +5 -0
  60. package/bin/github/errors.mjs +4 -0
  61. package/bin/github/gql/batch-pr.gql +1 -0
  62. package/bin/github/gql/poll-stack-summary.gql +8 -2
  63. package/bin/github/gql/poll-stack-topology.gql +38 -0
  64. package/bin/github/gql/poll-summary-check-contexts.gql +34 -0
  65. package/bin/github/gql/poll-summary-check-page.gql +23 -0
  66. package/bin/github/gql/poll-summary-fragment.gql +53 -56
  67. package/bin/github/merge-queue-checks.mjs +10 -1
  68. package/bin/github/poll-summary-check-hydration.d.mts +12 -0
  69. package/bin/github/poll-summary-check-hydration.mjs +55 -0
  70. package/bin/github/poll-summary-fingerprint.d.mts +8 -0
  71. package/bin/github/poll-summary-fingerprint.mjs +48 -0
  72. package/bin/github/poll-summary-projector.mjs +70 -16
  73. package/bin/github/poll-summary-queue-removal.d.mts +5 -0
  74. package/bin/github/poll-summary-queue-removal.mjs +25 -0
  75. package/bin/github/poll-summary-raw.d.mts +52 -31
  76. package/bin/github/poll-summary-readiness.d.mts +6 -0
  77. package/bin/github/poll-summary-readiness.mjs +25 -0
  78. package/bin/github/poll-summary-route.mjs +8 -5
  79. package/bin/github/poll-summary.d.mts +3 -0
  80. package/bin/github/poll-summary.mjs +21 -73
  81. package/bin/github/queries.d.mts +7 -0
  82. package/bin/github/queries.mjs +9 -1
  83. package/bin/github/queue-removal-freshness.d.mts +16 -0
  84. package/bin/github/queue-removal-freshness.mjs +26 -0
  85. package/bin/github/stack-read.d.mts +34 -0
  86. package/bin/github/stack-read.mjs +92 -0
  87. package/bin/log/log-file.d.mts +1 -1
  88. package/bin/log/log-file.mjs +4 -17
  89. package/bin/state/base.d.mts +18 -1
  90. package/bin/state/base.mjs +65 -13
  91. package/bin/state/fix-attempts.d.mts +1 -1
  92. package/bin/state/fix-attempts.mjs +1 -1
  93. package/bin/state/graphql-quota-warnings.mjs +2 -6
  94. package/bin/state/iterate-stall.d.mts +7 -15
  95. package/bin/state/iterate-stall.mjs +6 -64
  96. package/bin/state/ready-receipts.d.mts +45 -0
  97. package/bin/state/ready-receipts.mjs +86 -0
  98. package/bin/state/rest-cache.d.mts +1 -1
  99. package/bin/state/rest-cache.mjs +1 -1
  100. package/bin/state/stack-stall.d.mts +16 -0
  101. package/bin/state/stack-stall.mjs +12 -0
  102. package/bin/state/stall-state-store.d.mts +37 -0
  103. package/bin/state/stall-state-store.mjs +74 -0
  104. package/bin/types/escalate.d.mts +2 -3
  105. package/bin/types/github.d.mts +2 -0
  106. package/bin/types/iterate.d.mts +2 -1
  107. package/bin/types/merge-requirements.d.mts +19 -0
  108. package/bin/types/poll-summary.d.mts +17 -2
  109. package/bin/types/report.d.mts +2 -0
  110. package/package.json +2 -2
  111. package/plugins/pr-shepherd/.codex-plugin/plugin.json +1 -1
  112. package/plugins/pr-shepherd/.codex.mcp.json +1 -1
  113. package/plugins/pr-shepherd/.mcp.json +1 -1
  114. package/plugins/pr-shepherd/skills/pr-shepherd/SKILL.md +3 -3
  115. package/bin/state/bot-cr-seen.d.mts +0 -51
  116. package/bin/state/bot-cr-seen.mjs +0 -100
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pr-shepherd",
3
3
  "description": "Autonomous PR CI monitor and review-comment resolver for agentic coding tools",
4
- "version": "0.51.1",
4
+ "version": "0.52.1",
5
5
  "author": {
6
6
  "name": "Jonathan Ong",
7
7
  "email": "jonathanrichardong@gmail.com"
package/README.md CHANGED
@@ -36,7 +36,10 @@ Each tick returns exactly one action:
36
36
  - `FIX_CODE` — agent work is required; complete it, push when needed, then continue polling. Push access to the PR head branch is a usage precondition.
37
37
  - `MERGE` — run the emitted head-pinned auto-merge or queue command. Ordinary merges include a plain-merge fallback; queue merges include a GraphQL enqueue fallback. GitHub is authoritative for the result and reports any authorization failure.
38
38
  - `CANCEL` — stop polling because the PR merged, closed, or completed its ready-delay.
39
- - `ESCALATE` — stop polling until a human provides direction.
39
+ - `ESCALATE` — stop polling until a human provides direction. Native stacks reach this only after their autonomous one-PR sessions are exhausted.
40
+
41
+ Native-stack summaries additionally use stack-level `SHEPHERD`: run the listed one-PR sessions,
42
+ then recheck the stack. It is not a per-PR `FIX_CODE` action.
40
43
 
41
44
  Example shape:
42
45
 
@@ -79,7 +82,7 @@ Conversations Resolved: No [Not Required]
79
82
  9. `[FIX_CODE]` is non-terminal: if you changed code, commit and push to the PR head branch, then run review mutations using the pushed commit SHA and iterate immediately with the same options; without code changes, complete the authorized review mutations and iterate immediately.
80
83
  ```
81
84
 
82
- See [docs/actions.md](docs/actions.md) for the complete output contract and [docs/escalations.md](docs/escalations.md) for the exact finite human-handoff boundary. Iterate/poll PR outcomes use exit codes `0` and `10`–`15`; command and GitHub failures use `sysexits.h` codes — [docs/exit-codes.md](docs/exit-codes.md).
85
+ See [docs/actions.md](docs/actions.md) for the complete output contract and [docs/escalations.md](docs/escalations.md) for the exact finite human-handoff boundary. Iterate/poll PR outcomes use exit codes `0` and `10`–`16`; command and GitHub failures use `sysexits.h` codes — [docs/exit-codes.md](docs/exit-codes.md).
83
86
 
84
87
  ## Workflow Assumptions
85
88
 
@@ -129,7 +132,7 @@ pr-shepherd 42 # poll until non-WAIT or timeout
129
132
  pr-shepherd 42 --interval 60s --timeout 270s
130
133
  pr-shepherd 42 --quiet-status # print only changed WAIT status snapshots
131
134
  pr-shepherd 42 --until-terminal # continue through WAIT/MARK_READY until work or terminal state
132
- pr-shepherd 42 --debounce 5m # wait 5m after first FIX_CODE, then return one batched tick
135
+ pr-shepherd 42 --debounce 5m # wait 5m after first FIX_CODE or stack SHEPHERD, then return one batched tick
133
136
  pr-shepherd 42 --ready-delay 15m
134
137
  pr-shepherd 42 --merge # request head-pinned auto-merge/queue; GitHub reports the result
135
138
  pr-shepherd iterate 42 # single tick
@@ -139,20 +142,27 @@ pr-shepherd 42 43 44 # summarize an explicit same-repository s
139
142
  pr-shepherd --stack 43 # summarize every PR in a native GitHub stack
140
143
  ```
141
144
 
142
- Multi-PR and `--stack` polling use compact, read-only GraphQL summaries. They return when any row
143
- needs agent work, all rows are terminal, the bounded timeout expires, or `--until-terminal` crosses
144
- a configured GraphQL quota-warning band. Check counts use the same ignored, protected-run,
145
- superseded-run, and event rules as singular iteration and include active merge-queue commit checks.
146
- Bounded review/check overflow remains visible without permanently forcing work, and clean rows use
147
- the configured ready-delay before becoming terminal. Explicit PR sets give each actionable row an
148
- exact single-PR `pollCommand`, so independent rows can proceed before the next aggregate poll.
149
- Stack rows are ordered bottom-to-top and follow the one ordered stack instruction block instead.
150
- The summary also checks that every open child was based on
151
- its direct parent's current head. A stale child/parent OID pair is actionable even when GitHub
152
- reports both PRs clean: without `--merge`, rebase the upstack branches from their parent and push
153
- them with the emitted `gh stack` commands; with `--merge`, finish the contiguous ready lower
154
- layers with the emitted `gh stack merge --squash` command, recheck, then repair the child. API and
155
- MCP aggregate calls perform one summary tick and leave recurrence to the caller.
145
+ Multi-PR and `--stack` polling use compact, read-only GraphQL summaries. They return when work is
146
+ needed, every selected PR is complete, the bounded timeout expires, or `--until-terminal` crosses a
147
+ configured GraphQL quota-warning band. Explicit PR sets give each actionable row an exact single-PR
148
+ `pollCommand`, so independent rows can proceed before the next aggregate poll.
149
+
150
+ Native-stack rows are ordered bottom-to-top. `--stack` never performs a mutation itself; only
151
+ `--stack --merge` can emit a bottom-layer merge command for the agent. An unready layer (draft, missing a READY
152
+ receipt, conflicting, failing, or stale) returns stack-level `SHEPHERD` with one-PR Shepherd instructions for
153
+ the affected layers. A draft or other unready lower layer marks every higher open layer with
154
+ `blockedByPr`; review and CI sessions on independent layers may proceed concurrently, but an upper
155
+ draft cannot transition to ready until every lower layer has its READY receipt. With automatic
156
+ mark-ready disabled, the instructions ask the agent to mark a clean, unblocked draft layer ready. A
157
+ queued stack, or one whose remaining layers can only wait, returns `WAIT`; an idle `WAIT` that stays unchanged past the stall timeout returns `ESCALATE` with `stall-timeout`. A terminal READY or fully merged stack returns `CANCEL`. Closed or unverified
158
+ topology returns `ESCALATE` for human direction after any other shepherdable PRs are handled;
159
+ until then, `SHEPHERD` remains the immediate action and lists the human blockers too.
160
+
161
+ With `--stack --merge`, a READY bottom open layer that GitHub has retargeted onto the stack base
162
+ returns `MERGE` with `gh stack merge <PR number> --yes --squash`, which merges or enqueues that layer
163
+ alone; unready upper layers keep their one-PR sessions alongside it. After each merge, GitHub
164
+ retargets the next layer, so the rerun drains the stack layer by layer until it returns `CANCEL`. API and MCP aggregate calls perform one summary tick and leave recurrence to the
165
+ caller.
156
166
 
157
167
  Polling defaults can be set under `poll` in `.pr-shepherdrc.yml`: `intervalSeconds`, `timeoutSeconds`, `debounceSeconds`, and `quietStatus`. Explicit flags override configuration, including `--no-quiet-status` when a shared config enables quiet output. Quiet status remains off by default.
158
168
 
@@ -191,7 +201,7 @@ Unsupported platforms fail closed with that same exit code.
191
201
 
192
202
  ### Clean Local State
193
203
 
194
- `pr-shepherd` stores seen markers, fix-attempt counters, stall fingerprints, ready-delay markers, and logs under `$PR_SHEPHERD_STATE_DIR` (default `$TMPDIR/pr-shepherd-state`).
204
+ `pr-shepherd` stores seen markers, fix-attempt counters, stall fingerprints, ready-delay markers, and logs under `$PR_SHEPHERD_STATE_DIR` (default: `pr-shepherd-state` in the per-user temp dir; see [configuration](docs/configuration.md#environment-variables)).
195
205
 
196
206
  ```sh
197
207
  pr-shepherd admin clean current
@@ -263,6 +273,7 @@ Replace `<version>` with a published version. Full config-file examples, tool sc
263
273
  Create `.pr-shepherdrc.yml` in your project root, an ancestor directory, or `$HOME`. Every file on the walk is deep-merged; closer directories override farther ones.
264
274
 
265
275
  ```yaml
276
+ cliCommand: [pnpm, exec, pr-shepherd] # launcher for emitted commands; defaults to [pr-shepherd]
266
277
  ignoreChecks:
267
278
  - "Kilo Code Review"
268
279
  iterate:
@@ -194,7 +194,7 @@ Flags:
194
194
  PR may be a number or GitHub pull request URL. When omitted, the current branch PR is inferred.
195
195
  Exit code: 0 on success; nonzero on failure (sysexits.h — see docs/exit-codes.md).`;
196
196
  readonly iterate: "pr-shepherd iterate\n\nRun one iterate tick for a pull request. The no-subcommand form polls; use this subcommand for a single tick.\nThe output contains one action and an action-specific ## Instructions section.\n\nUsage:\n pr-shepherd iterate [PR] [iterate-flags]\n\nIterate flags:\n --ready-delay <duration> Settle window before a clean PR cancels. Bare number = minutes. Example: 15m.\n --stall-timeout <duration> Escalate repeated unchanged failures after this duration. Bare number = minutes. 0 disables.\n --no-auto-mark-ready Do not convert draft PRs to ready for review.\n --no-auto-cancel-actionable Legacy no-op; workflow runs are never cancelled.\n --merge Shepherd through readiness, then emit a merge or merge-queue command.\n --format text|json Output Markdown text or JSON. Default: text.\n --verbose Include verbose iterate fields.\n --help, -h Print this help and exit before GitHub, git, config, or log I/O.\n\nDurations accept s/m/h suffixes: 30s, 4.5m, 1h. A bare number is minutes; decimals are allowed only with an explicit unit (4.5m).\n\nActions:\n WAIT No immediate action; continue with the next poll.\n MARK_READY Draft PR was marked ready; continue with the next poll.\n FIX_CODE Agent action is required; follow the instructions, then continue polling.\n CANCEL Stop polling: merged/closed or ready-delay elapsed.\n ESCALATE Stop polling until a human provides direction.\n MERGE Run the emitted merge/queue command, then continue monitoring.\n\nExit codes:\n 0 CANCEL (merged or ready-delay elapsed)\n 10 WAIT\n 11 MARK_READY\n 12 FIX_CODE\n 13 ESCALATE\n 14 CANCEL (closed without merging)\n 15 MERGE\n A command/validation/GitHub failure exits with a sysexits.h code instead (see docs/exit-codes.md).";
197
- readonly poll: "pr-shepherd poll\n\nRun iterate repeatedly for one PR, or read compact summaries for an explicit PR set or native\nGitHub stack. Aggregate mode returns when any row needs work, every row is terminal, or timeout.\nPoll exits as soon as iterate returns MARK_READY, CANCEL, or ESCALATE, or when timeout\nreturns the last WAIT result. FIX_CODE starts a --debounce settle window (default:\npoll.debounceSeconds; built-in 1m): poll keeps\niterating at --interval, then runs one more tick after the window and returns that result.\nWith --until-terminal or --merge, poll also continues through MARK_READY.\n\nUsage:\n pr-shepherd poll [PR ...] [poll-flags] [iterate-flags]\n pr-shepherd poll --stack PR [poll-flags] [iterate-flags]\n\nPoll flags:\n --stack PR Select all entries in PR's native GitHub stack, bottom to top.\n --interval <duration> Sleep between WAIT ticks. Bare number = seconds. Default: poll.intervalSeconds (built-in 60s).\n --timeout <duration> Maximum wall-clock wait for WAIT ticks. Bare number = seconds. Default: poll.timeoutSeconds (built-in 4.5m).\n --debounce <duration> Settle window after first FIX_CODE before returning. Bare number = seconds. Default: poll.debounceSeconds (built-in 60s). 0 disables.\n --quiet-status Print only changed WAIT snapshots. Overrides poll.quietStatus.\n --no-quiet-status Print every WAIT snapshot. Overrides poll.quietStatus.\n --until-terminal Continue through WAIT/MARK_READY until FIX_CODE/CANCEL/ESCALATE.\n\nForwarded iterate flags:\n --ready-delay <duration> Settle window before a clean PR cancels. Bare number = minutes. Example: 15m.\n --stall-timeout <duration> Escalate repeated unchanged failures after this duration. Bare number = minutes. 0 disables.\n --no-auto-mark-ready Do not convert draft PRs to ready for review.\n --no-auto-cancel-actionable Legacy no-op; workflow runs are never cancelled.\n --merge Shepherd through readiness, then emit a merge or merge-queue command.\n --format text|json Output Markdown text or JSON. Default: text.\n --verbose Include verbose iterate fields and detailed per-tick lines.\n --help, -h Print this help and exit before GitHub, git, config, or log I/O.\n\nDurations accept s/m/h suffixes: 30s, 4.5m, 1h. A bare number uses each flag's default unit (seconds\nfor --interval/--timeout/--debounce, minutes for --ready-delay/--stall-timeout); decimals are allowed only with\nan explicit unit (4.5m).\nEach WAIT tick writes an explicit still-running line to stderr by default; poll.quietStatus can change that default, --quiet-status/--no-quiet-status override it, and --verbose emits detailed per-tick lines.\nFIX_CODE debounce writes a remaining-seconds line to stderr. --timeout does not cut an in-flight debounce short.\nWith --until-terminal, --timeout is ignored for WAIT ticks and polling continues until FIX_CODE, MERGE, CANCEL, or ESCALATE. With --merge, --timeout still bounds WAIT ticks; it only continues through MARK_READY while polling remains within that timeout.\n\nExit codes: same as iterate (the final tick's action/reason decides the code).\n 0 CANCEL (merged or ready-delay elapsed)\n 10 WAIT (including a WAIT returned by --timeout)\n 11 MARK_READY\n 12 FIX_CODE\n 13 ESCALATE\n 14 CANCEL (closed without merging)\n 15 MERGE\n A command/validation/GitHub failure exits with a sysexits.h code instead (see docs/exit-codes.md).";
197
+ readonly poll: "pr-shepherd poll\n\nRun iterate repeatedly for one PR, or read compact summaries for an explicit PR set or native\nGitHub stack. Aggregate mode returns when any row needs work, every row is terminal, or timeout.\nPoll exits as soon as iterate returns MARK_READY, CANCEL, or ESCALATE, or when timeout\nreturns the last WAIT result. FIX_CODE starts a --debounce settle window (default:\npoll.debounceSeconds; built-in 1m): poll keeps\niterating at --interval, then runs one more tick after the window and returns that result.\nWith --until-terminal or --merge, poll also continues through MARK_READY.\n\nUsage:\n pr-shepherd poll [PR ...] [poll-flags] [iterate-flags]\n pr-shepherd poll --stack PR [poll-flags] [iterate-flags]\n\nPoll flags:\n --stack PR Select all entries in PR's native GitHub stack, bottom to top.\n --interval <duration> Sleep between WAIT ticks. Bare number = seconds. Default: poll.intervalSeconds (built-in 60s).\n --timeout <duration> Maximum wall-clock wait for WAIT ticks. Bare number = seconds. Default: poll.timeoutSeconds (built-in 4.5m).\n --debounce <duration> Settle window after first FIX_CODE or stack SHEPHERD before returning. Bare number = seconds. Default: poll.debounceSeconds (built-in 60s). 0 disables.\n --quiet-status Print only changed WAIT snapshots. Overrides poll.quietStatus.\n --no-quiet-status Print every WAIT snapshot. Overrides poll.quietStatus.\n --until-terminal Continue through WAIT/MARK_READY until FIX_CODE/MERGE/CANCEL/ESCALATE or stack SHEPHERD.\n\nForwarded iterate flags:\n --ready-delay <duration> Settle window before a clean PR cancels. Bare number = minutes. Example: 15m.\n --stall-timeout <duration> Escalate repeated unchanged failures after this duration. Bare number = minutes. 0 disables.\n --no-auto-mark-ready Do not convert draft PRs to ready for review.\n --no-auto-cancel-actionable Legacy no-op; workflow runs are never cancelled.\n --merge Shepherd through readiness, then emit a merge or merge-queue command.\n --format text|json Output Markdown text or JSON. Default: text.\n --verbose Include verbose iterate fields and detailed per-tick lines.\n --help, -h Print this help and exit before GitHub, git, config, or log I/O.\n\nDurations accept s/m/h suffixes: 30s, 4.5m, 1h. A bare number uses each flag's default unit (seconds\nfor --interval/--timeout/--debounce, minutes for --ready-delay/--stall-timeout); decimals are allowed only with\nan explicit unit (4.5m).\nEach WAIT tick writes a stderr line naming what it is waiting on by default; poll.quietStatus can change that default, --quiet-status/--no-quiet-status override it, and --verbose emits detailed per-tick lines.\nFIX_CODE debounce writes a remaining-seconds line to stderr. --timeout does not cut an in-flight debounce short.\nWith --until-terminal, --timeout is ignored for WAIT ticks and polling continues until FIX_CODE, MERGE, CANCEL, or ESCALATE. With --merge, --timeout still bounds WAIT ticks; it only continues through MARK_READY while polling remains within that timeout.\n\nExit codes: same as iterate (the final tick's action/reason decides the code).\n 0 CANCEL (merged or ready-delay elapsed)\n 10 WAIT (including a WAIT returned by --timeout)\n 11 MARK_READY\n 12 FIX_CODE\n 13 ESCALATE\n 14 CANCEL (closed without merging)\n 15 MERGE\n 16 SHEPHERD (--stack only: run the listed one-PR sessions, then rerun the selector)\n A command/validation/GitHub failure exits with a sysexits.h code instead (see docs/exit-codes.md).";
198
198
  readonly clean: `pr-shepherd clean
199
199
 
200
200
  Remove pr-shepherd state files from PR_SHEPHERD_STATE_DIR.
@@ -1,4 +1,4 @@
1
1
  export declare const ITERATE_USAGE = "pr-shepherd iterate\n\nRun one iterate tick for a pull request. The no-subcommand form polls; use this subcommand for a single tick.\nThe output contains one action and an action-specific ## Instructions section.\n\nUsage:\n pr-shepherd iterate [PR] [iterate-flags]\n\nIterate flags:\n --ready-delay <duration> Settle window before a clean PR cancels. Bare number = minutes. Example: 15m.\n --stall-timeout <duration> Escalate repeated unchanged failures after this duration. Bare number = minutes. 0 disables.\n --no-auto-mark-ready Do not convert draft PRs to ready for review.\n --no-auto-cancel-actionable Legacy no-op; workflow runs are never cancelled.\n --merge Shepherd through readiness, then emit a merge or merge-queue command.\n --format text|json Output Markdown text or JSON. Default: text.\n --verbose Include verbose iterate fields.\n --help, -h Print this help and exit before GitHub, git, config, or log I/O.\n\nDurations accept s/m/h suffixes: 30s, 4.5m, 1h. A bare number is minutes; decimals are allowed only with an explicit unit (4.5m).\n\nActions:\n WAIT No immediate action; continue with the next poll.\n MARK_READY Draft PR was marked ready; continue with the next poll.\n FIX_CODE Agent action is required; follow the instructions, then continue polling.\n CANCEL Stop polling: merged/closed or ready-delay elapsed.\n ESCALATE Stop polling until a human provides direction.\n MERGE Run the emitted merge/queue command, then continue monitoring.\n\nExit codes:\n 0 CANCEL (merged or ready-delay elapsed)\n 10 WAIT\n 11 MARK_READY\n 12 FIX_CODE\n 13 ESCALATE\n 14 CANCEL (closed without merging)\n 15 MERGE\n A command/validation/GitHub failure exits with a sysexits.h code instead (see docs/exit-codes.md).";
2
- export declare const POLL_USAGE = "pr-shepherd poll\n\nRun iterate repeatedly for one PR, or read compact summaries for an explicit PR set or native\nGitHub stack. Aggregate mode returns when any row needs work, every row is terminal, or timeout.\nPoll exits as soon as iterate returns MARK_READY, CANCEL, or ESCALATE, or when timeout\nreturns the last WAIT result. FIX_CODE starts a --debounce settle window (default:\npoll.debounceSeconds; built-in 1m): poll keeps\niterating at --interval, then runs one more tick after the window and returns that result.\nWith --until-terminal or --merge, poll also continues through MARK_READY.\n\nUsage:\n pr-shepherd poll [PR ...] [poll-flags] [iterate-flags]\n pr-shepherd poll --stack PR [poll-flags] [iterate-flags]\n\nPoll flags:\n --stack PR Select all entries in PR's native GitHub stack, bottom to top.\n --interval <duration> Sleep between WAIT ticks. Bare number = seconds. Default: poll.intervalSeconds (built-in 60s).\n --timeout <duration> Maximum wall-clock wait for WAIT ticks. Bare number = seconds. Default: poll.timeoutSeconds (built-in 4.5m).\n --debounce <duration> Settle window after first FIX_CODE before returning. Bare number = seconds. Default: poll.debounceSeconds (built-in 60s). 0 disables.\n --quiet-status Print only changed WAIT snapshots. Overrides poll.quietStatus.\n --no-quiet-status Print every WAIT snapshot. Overrides poll.quietStatus.\n --until-terminal Continue through WAIT/MARK_READY until FIX_CODE/CANCEL/ESCALATE.\n\nForwarded iterate flags:\n --ready-delay <duration> Settle window before a clean PR cancels. Bare number = minutes. Example: 15m.\n --stall-timeout <duration> Escalate repeated unchanged failures after this duration. Bare number = minutes. 0 disables.\n --no-auto-mark-ready Do not convert draft PRs to ready for review.\n --no-auto-cancel-actionable Legacy no-op; workflow runs are never cancelled.\n --merge Shepherd through readiness, then emit a merge or merge-queue command.\n --format text|json Output Markdown text or JSON. Default: text.\n --verbose Include verbose iterate fields and detailed per-tick lines.\n --help, -h Print this help and exit before GitHub, git, config, or log I/O.\n\nDurations accept s/m/h suffixes: 30s, 4.5m, 1h. A bare number uses each flag's default unit (seconds\nfor --interval/--timeout/--debounce, minutes for --ready-delay/--stall-timeout); decimals are allowed only with\nan explicit unit (4.5m).\nEach WAIT tick writes an explicit still-running line to stderr by default; poll.quietStatus can change that default, --quiet-status/--no-quiet-status override it, and --verbose emits detailed per-tick lines.\nFIX_CODE debounce writes a remaining-seconds line to stderr. --timeout does not cut an in-flight debounce short.\nWith --until-terminal, --timeout is ignored for WAIT ticks and polling continues until FIX_CODE, MERGE, CANCEL, or ESCALATE. With --merge, --timeout still bounds WAIT ticks; it only continues through MARK_READY while polling remains within that timeout.\n\nExit codes: same as iterate (the final tick's action/reason decides the code).\n 0 CANCEL (merged or ready-delay elapsed)\n 10 WAIT (including a WAIT returned by --timeout)\n 11 MARK_READY\n 12 FIX_CODE\n 13 ESCALATE\n 14 CANCEL (closed without merging)\n 15 MERGE\n A command/validation/GitHub failure exits with a sysexits.h code instead (see docs/exit-codes.md).";
2
+ export declare const POLL_USAGE = "pr-shepherd poll\n\nRun iterate repeatedly for one PR, or read compact summaries for an explicit PR set or native\nGitHub stack. Aggregate mode returns when any row needs work, every row is terminal, or timeout.\nPoll exits as soon as iterate returns MARK_READY, CANCEL, or ESCALATE, or when timeout\nreturns the last WAIT result. FIX_CODE starts a --debounce settle window (default:\npoll.debounceSeconds; built-in 1m): poll keeps\niterating at --interval, then runs one more tick after the window and returns that result.\nWith --until-terminal or --merge, poll also continues through MARK_READY.\n\nUsage:\n pr-shepherd poll [PR ...] [poll-flags] [iterate-flags]\n pr-shepherd poll --stack PR [poll-flags] [iterate-flags]\n\nPoll flags:\n --stack PR Select all entries in PR's native GitHub stack, bottom to top.\n --interval <duration> Sleep between WAIT ticks. Bare number = seconds. Default: poll.intervalSeconds (built-in 60s).\n --timeout <duration> Maximum wall-clock wait for WAIT ticks. Bare number = seconds. Default: poll.timeoutSeconds (built-in 4.5m).\n --debounce <duration> Settle window after first FIX_CODE or stack SHEPHERD before returning. Bare number = seconds. Default: poll.debounceSeconds (built-in 60s). 0 disables.\n --quiet-status Print only changed WAIT snapshots. Overrides poll.quietStatus.\n --no-quiet-status Print every WAIT snapshot. Overrides poll.quietStatus.\n --until-terminal Continue through WAIT/MARK_READY until FIX_CODE/MERGE/CANCEL/ESCALATE or stack SHEPHERD.\n\nForwarded iterate flags:\n --ready-delay <duration> Settle window before a clean PR cancels. Bare number = minutes. Example: 15m.\n --stall-timeout <duration> Escalate repeated unchanged failures after this duration. Bare number = minutes. 0 disables.\n --no-auto-mark-ready Do not convert draft PRs to ready for review.\n --no-auto-cancel-actionable Legacy no-op; workflow runs are never cancelled.\n --merge Shepherd through readiness, then emit a merge or merge-queue command.\n --format text|json Output Markdown text or JSON. Default: text.\n --verbose Include verbose iterate fields and detailed per-tick lines.\n --help, -h Print this help and exit before GitHub, git, config, or log I/O.\n\nDurations accept s/m/h suffixes: 30s, 4.5m, 1h. A bare number uses each flag's default unit (seconds\nfor --interval/--timeout/--debounce, minutes for --ready-delay/--stall-timeout); decimals are allowed only with\nan explicit unit (4.5m).\nEach WAIT tick writes a stderr line naming what it is waiting on by default; poll.quietStatus can change that default, --quiet-status/--no-quiet-status override it, and --verbose emits detailed per-tick lines.\nFIX_CODE debounce writes a remaining-seconds line to stderr. --timeout does not cut an in-flight debounce short.\nWith --until-terminal, --timeout is ignored for WAIT ticks and polling continues until FIX_CODE, MERGE, CANCEL, or ESCALATE. With --merge, --timeout still bounds WAIT ticks; it only continues through MARK_READY while polling remains within that timeout.\n\nExit codes: same as iterate (the final tick's action/reason decides the code).\n 0 CANCEL (merged or ready-delay elapsed)\n 10 WAIT (including a WAIT returned by --timeout)\n 11 MARK_READY\n 12 FIX_CODE\n 13 ESCALATE\n 14 CANCEL (closed without merging)\n 15 MERGE\n 16 SHEPHERD (--stack only: run the listed one-PR sessions, then rerun the selector)\n A command/validation/GitHub failure exits with a sysexits.h code instead (see docs/exit-codes.md).";
3
3
  /** Public help page for the default PR polling invocation. */
4
4
  export declare const DEFAULT_USAGE: string;
@@ -53,10 +53,10 @@ Poll flags:
53
53
  --stack PR Select all entries in PR's native GitHub stack, bottom to top.
54
54
  --interval <duration> Sleep between WAIT ticks. Bare number = seconds. Default: poll.intervalSeconds (built-in 60s).
55
55
  --timeout <duration> Maximum wall-clock wait for WAIT ticks. Bare number = seconds. Default: poll.timeoutSeconds (built-in 4.5m).
56
- --debounce <duration> Settle window after first FIX_CODE before returning. Bare number = seconds. Default: poll.debounceSeconds (built-in 60s). 0 disables.
56
+ --debounce <duration> Settle window after first FIX_CODE or stack SHEPHERD before returning. Bare number = seconds. Default: poll.debounceSeconds (built-in 60s). 0 disables.
57
57
  --quiet-status Print only changed WAIT snapshots. Overrides poll.quietStatus.
58
58
  --no-quiet-status Print every WAIT snapshot. Overrides poll.quietStatus.
59
- --until-terminal Continue through WAIT/MARK_READY until FIX_CODE/CANCEL/ESCALATE.
59
+ --until-terminal Continue through WAIT/MARK_READY until FIX_CODE/MERGE/CANCEL/ESCALATE or stack SHEPHERD.
60
60
 
61
61
  Forwarded iterate flags:
62
62
  --ready-delay <duration> Settle window before a clean PR cancels. Bare number = minutes. Example: 15m.
@@ -71,7 +71,7 @@ Forwarded iterate flags:
71
71
  Durations accept s/m/h suffixes: 30s, 4.5m, 1h. A bare number uses each flag's default unit (seconds
72
72
  for --interval/--timeout/--debounce, minutes for --ready-delay/--stall-timeout); decimals are allowed only with
73
73
  an explicit unit (4.5m).
74
- Each WAIT tick writes an explicit still-running line to stderr by default; poll.quietStatus can change that default, --quiet-status/--no-quiet-status override it, and --verbose emits detailed per-tick lines.
74
+ Each WAIT tick writes a stderr line naming what it is waiting on by default; poll.quietStatus can change that default, --quiet-status/--no-quiet-status override it, and --verbose emits detailed per-tick lines.
75
75
  FIX_CODE debounce writes a remaining-seconds line to stderr. --timeout does not cut an in-flight debounce short.
76
76
  With --until-terminal, --timeout is ignored for WAIT ticks and polling continues until FIX_CODE, MERGE, CANCEL, or ESCALATE. With --merge, --timeout still bounds WAIT ticks; it only continues through MARK_READY while polling remains within that timeout.
77
77
 
@@ -83,6 +83,7 @@ Exit codes: same as iterate (the final tick's action/reason decides the code).
83
83
  13 ESCALATE
84
84
  14 CANCEL (closed without merging)
85
85
  15 MERGE
86
+ 16 SHEPHERD (--stack only: run the listed one-PR sessions, then rerun the selector)
86
87
  A command/validation/GitHub failure exits with a sysexits.h code instead (see docs/exit-codes.md).`;
87
88
  /** Public help page for the default PR polling invocation. */
88
89
  export const DEFAULT_USAGE = POLL_USAGE.replace(/^pr-shepherd poll$/m, "pr-shepherd [PR]")
@@ -1 +1 @@
1
- export declare const TOP_USAGE = "pr-shepherd\n\nAutonomous PR CI monitor and review-comment resolver for agentic coding tools.\n\nUsage:\n pr-shepherd --version | -v\n pr-shepherd --help | -h\n pr-shepherd [PR ...] [poll-flags] [iterate-flags]\n pr-shepherd --stack PR [poll-flags] [iterate-flags]\n pr-shepherd iterate [PR] [iterate-flags]\n pr-shepherd apply review [PR] [review-flags]\n pr-shepherd apply files [PR] [files...] [--tests] [--match REGEX]\n pr-shepherd apply journal [PR] <item> [--dry-run] [--format text|json]\n pr-shepherd journal extract --body-file <path>\n pr-shepherd build-suggestion-patches [PR] --thread-id ID --message MSG [groups...]\n pr-shepherd admin clean <pr|branch|current|repo|all> [value] [flags]\n pr-shepherd admin log-file [--format text|json]\n\nCommands:\n [PR ...] Poll one PR, an explicit same-repository set, or a native stack.\n iterate Run one iterate tick (single-tick alias).\n apply review Apply review-state mutations after fixes.\n apply files Mark selected changed files as viewed.\n apply journal Append a list item to the Shepherd Journal details block of a PR body.\n journal extract Extract a validated Shepherd Journal from a local PR-body file as JSON.\n build-suggestion-patches\n Convert ordered GitHub suggestion threads into patches and commit instructions.\n admin clean Remove pr-shepherd state files.\n admin log-file Print the per-worktree debug log path.\n\nPR argument:\n PR may be a number, owner/repo#number, or a GitHub pull request URL.\n Multiple PRs must name one repository. --stack PR selects every entry in PR's native stack.\n When omitted, pr-shepherd infers the current branch's pull request.\n\nCommon flags:\n --format text|json Output Markdown text or JSON. Default: text.\n --verbose Include verbose iterate fields and detailed poll-tick lines.\n --help, -h Print help and exit before any GitHub, git, config, or log I/O.\n\nIterate flags:\n --ready-delay <duration> Settle window before a clean PR cancels. Bare number = minutes. Example: 15m.\n --stall-timeout <duration> Escalate repeated unchanged failures after this duration. Bare number = minutes. 0 disables.\n --no-auto-mark-ready Do not convert draft PRs to ready for review.\n --no-auto-cancel-actionable Legacy no-op; workflow runs are never cancelled.\n --merge Shepherd through readiness, then emit a merge or merge-queue command.\n\nPolling flags:\n --interval <duration> Delay between WAIT ticks. Bare number = seconds. Default: poll.intervalSeconds (built-in 60s).\n --timeout <duration> Poll wall-clock cap for WAIT ticks. Bare number = seconds. Default: poll.timeoutSeconds (built-in 4.5m).\n --debounce <duration> Settle window after first FIX_CODE before returning. Bare number = seconds. Default: poll.debounceSeconds (built-in 60s). 0 disables.\n --quiet-status Print only changed WAIT snapshots. Overrides poll.quietStatus.\n --no-quiet-status Print every WAIT snapshot. Overrides poll.quietStatus.\n --until-terminal Continue through WAIT/MARK_READY until FIX_CODE/CANCEL/ESCALATE.\n\nClean variants:\n pr [number] Remove state for one PR. Defaults to current branch PR.\n branch [name] Remove state for a branch's PR. Defaults to current branch.\n current Alias for branch against the current branch.\n repo Remove all state for the current repository.\n all Remove all pr-shepherd state.\n\nExit codes: 0 done, 10-19 PR state, 64-78 shepherd failed (sysexits.h).\n 0 CANCEL (merged or ready-delay elapsed)\n 10 WAIT\n 11 MARK_READY\n 12 FIX_CODE\n 13 ESCALATE\n 14 CANCEL (closed without merging)\n 15 MERGE\nSee docs/exit-codes.md for the full sysexits.h error-code table.\n\nDuration examples: 30s, 4.5m, 1h. A bare number uses each flag's default unit (see above); decimals are allowed with an explicit unit (4.5m).\n\nRun 'pr-shepherd <command> --help' for command-specific details.";
1
+ export declare const TOP_USAGE = "pr-shepherd\n\nAutonomous PR CI monitor and review-comment resolver for agentic coding tools.\n\nUsage:\n pr-shepherd --version | -v\n pr-shepherd --help | -h\n pr-shepherd [PR ...] [poll-flags] [iterate-flags]\n pr-shepherd --stack PR [poll-flags] [iterate-flags]\n pr-shepherd iterate [PR] [iterate-flags]\n pr-shepherd apply review [PR] [review-flags]\n pr-shepherd apply files [PR] [files...] [--tests] [--match REGEX]\n pr-shepherd apply journal [PR] <item> [--dry-run] [--format text|json]\n pr-shepherd journal extract --body-file <path>\n pr-shepherd build-suggestion-patches [PR] --thread-id ID --message MSG [groups...]\n pr-shepherd admin clean <pr|branch|current|repo|all> [value] [flags]\n pr-shepherd admin log-file [--format text|json]\n\nCommands:\n [PR ...] Poll one PR, an explicit same-repository set, or a native stack.\n iterate Run one iterate tick (single-tick alias).\n apply review Apply review-state mutations after fixes.\n apply files Mark selected changed files as viewed.\n apply journal Append a list item to the Shepherd Journal details block of a PR body.\n journal extract Extract a validated Shepherd Journal from a local PR-body file as JSON.\n build-suggestion-patches\n Convert ordered GitHub suggestion threads into patches and commit instructions.\n admin clean Remove pr-shepherd state files.\n admin log-file Print the per-worktree debug log path.\n\nPR argument:\n PR may be a number, owner/repo#number, or a GitHub pull request URL.\n Multiple PRs must name one repository. --stack PR selects every entry in PR's native stack.\n When omitted, pr-shepherd infers the current branch's pull request.\n\nCommon flags:\n --format text|json Output Markdown text or JSON. Default: text.\n --verbose Include verbose iterate fields and detailed poll-tick lines.\n --help, -h Print help and exit before any GitHub, git, config, or log I/O.\n\nIterate flags:\n --ready-delay <duration> Settle window before a clean PR cancels. Bare number = minutes. Example: 15m.\n --stall-timeout <duration> Escalate repeated unchanged failures after this duration. Bare number = minutes. 0 disables.\n --no-auto-mark-ready Do not convert draft PRs to ready for review.\n --no-auto-cancel-actionable Legacy no-op; workflow runs are never cancelled.\n --merge Shepherd through readiness, then emit a merge or merge-queue command.\n\nPolling flags:\n --interval <duration> Delay between WAIT ticks. Bare number = seconds. Default: poll.intervalSeconds (built-in 60s).\n --timeout <duration> Poll wall-clock cap for WAIT ticks. Bare number = seconds. Default: poll.timeoutSeconds (built-in 4.5m).\n --debounce <duration> Settle window after first FIX_CODE or stack SHEPHERD before returning. Bare number = seconds. Default: poll.debounceSeconds (built-in 60s). 0 disables.\n --quiet-status Print only changed WAIT snapshots. Overrides poll.quietStatus.\n --no-quiet-status Print every WAIT snapshot. Overrides poll.quietStatus.\n --until-terminal Continue through WAIT/MARK_READY until FIX_CODE/MERGE/CANCEL/ESCALATE or stack SHEPHERD.\n\nClean variants:\n pr [number] Remove state for one PR. Defaults to current branch PR.\n branch [name] Remove state for a branch's PR. Defaults to current branch.\n current Alias for branch against the current branch.\n repo Remove all state for the current repository.\n all Remove all pr-shepherd state.\n\nExit codes: 0 done, 10-19 PR state, 64-78 shepherd failed (sysexits.h).\n 0 CANCEL (merged or ready-delay elapsed)\n 10 WAIT\n 11 MARK_READY\n 12 FIX_CODE\n 13 ESCALATE\n 14 CANCEL (closed without merging)\n 15 MERGE\n 16 SHEPHERD (--stack only: run the listed one-PR sessions, then rerun the selector)\nSee docs/exit-codes.md for the full sysexits.h error-code table.\n\nDuration examples: 30s, 4.5m, 1h. A bare number uses each flag's default unit (see above); decimals are allowed with an explicit unit (4.5m).\n\nRun 'pr-shepherd <command> --help' for command-specific details.";
@@ -48,10 +48,10 @@ Iterate flags:
48
48
  Polling flags:
49
49
  --interval <duration> Delay between WAIT ticks. Bare number = seconds. Default: poll.intervalSeconds (built-in 60s).
50
50
  --timeout <duration> Poll wall-clock cap for WAIT ticks. Bare number = seconds. Default: poll.timeoutSeconds (built-in 4.5m).
51
- --debounce <duration> Settle window after first FIX_CODE before returning. Bare number = seconds. Default: poll.debounceSeconds (built-in 60s). 0 disables.
51
+ --debounce <duration> Settle window after first FIX_CODE or stack SHEPHERD before returning. Bare number = seconds. Default: poll.debounceSeconds (built-in 60s). 0 disables.
52
52
  --quiet-status Print only changed WAIT snapshots. Overrides poll.quietStatus.
53
53
  --no-quiet-status Print every WAIT snapshot. Overrides poll.quietStatus.
54
- --until-terminal Continue through WAIT/MARK_READY until FIX_CODE/CANCEL/ESCALATE.
54
+ --until-terminal Continue through WAIT/MARK_READY until FIX_CODE/MERGE/CANCEL/ESCALATE or stack SHEPHERD.
55
55
 
56
56
  Clean variants:
57
57
  pr [number] Remove state for one PR. Defaults to current branch PR.
@@ -68,6 +68,7 @@ Exit codes: 0 done, 10-19 PR state, 64-78 shepherd failed (sysexits.h).
68
68
  13 ESCALATE
69
69
  14 CANCEL (closed without merging)
70
70
  15 MERGE
71
+ 16 SHEPHERD (--stack only: run the listed one-PR sessions, then rerun the selector)
71
72
  See docs/exit-codes.md for the full sysexits.h error-code table.
72
73
 
73
74
  Duration examples: 30s, 4.5m, 1h. A bare number uses each flag's default unit (see above); decimals are allowed with an explicit unit (4.5m).
@@ -194,7 +194,7 @@ Flags:
194
194
  PR may be a number or GitHub pull request URL. When omitted, the current branch PR is inferred.
195
195
  Exit code: 0 on success; nonzero on failure (sysexits.h — see docs/exit-codes.md).`;
196
196
  readonly iterate: "pr-shepherd iterate\n\nRun one iterate tick for a pull request. The no-subcommand form polls; use this subcommand for a single tick.\nThe output contains one action and an action-specific ## Instructions section.\n\nUsage:\n pr-shepherd iterate [PR] [iterate-flags]\n\nIterate flags:\n --ready-delay <duration> Settle window before a clean PR cancels. Bare number = minutes. Example: 15m.\n --stall-timeout <duration> Escalate repeated unchanged failures after this duration. Bare number = minutes. 0 disables.\n --no-auto-mark-ready Do not convert draft PRs to ready for review.\n --no-auto-cancel-actionable Legacy no-op; workflow runs are never cancelled.\n --merge Shepherd through readiness, then emit a merge or merge-queue command.\n --format text|json Output Markdown text or JSON. Default: text.\n --verbose Include verbose iterate fields.\n --help, -h Print this help and exit before GitHub, git, config, or log I/O.\n\nDurations accept s/m/h suffixes: 30s, 4.5m, 1h. A bare number is minutes; decimals are allowed only with an explicit unit (4.5m).\n\nActions:\n WAIT No immediate action; continue with the next poll.\n MARK_READY Draft PR was marked ready; continue with the next poll.\n FIX_CODE Agent action is required; follow the instructions, then continue polling.\n CANCEL Stop polling: merged/closed or ready-delay elapsed.\n ESCALATE Stop polling until a human provides direction.\n MERGE Run the emitted merge/queue command, then continue monitoring.\n\nExit codes:\n 0 CANCEL (merged or ready-delay elapsed)\n 10 WAIT\n 11 MARK_READY\n 12 FIX_CODE\n 13 ESCALATE\n 14 CANCEL (closed without merging)\n 15 MERGE\n A command/validation/GitHub failure exits with a sysexits.h code instead (see docs/exit-codes.md).";
197
- readonly poll: "pr-shepherd poll\n\nRun iterate repeatedly for one PR, or read compact summaries for an explicit PR set or native\nGitHub stack. Aggregate mode returns when any row needs work, every row is terminal, or timeout.\nPoll exits as soon as iterate returns MARK_READY, CANCEL, or ESCALATE, or when timeout\nreturns the last WAIT result. FIX_CODE starts a --debounce settle window (default:\npoll.debounceSeconds; built-in 1m): poll keeps\niterating at --interval, then runs one more tick after the window and returns that result.\nWith --until-terminal or --merge, poll also continues through MARK_READY.\n\nUsage:\n pr-shepherd poll [PR ...] [poll-flags] [iterate-flags]\n pr-shepherd poll --stack PR [poll-flags] [iterate-flags]\n\nPoll flags:\n --stack PR Select all entries in PR's native GitHub stack, bottom to top.\n --interval <duration> Sleep between WAIT ticks. Bare number = seconds. Default: poll.intervalSeconds (built-in 60s).\n --timeout <duration> Maximum wall-clock wait for WAIT ticks. Bare number = seconds. Default: poll.timeoutSeconds (built-in 4.5m).\n --debounce <duration> Settle window after first FIX_CODE before returning. Bare number = seconds. Default: poll.debounceSeconds (built-in 60s). 0 disables.\n --quiet-status Print only changed WAIT snapshots. Overrides poll.quietStatus.\n --no-quiet-status Print every WAIT snapshot. Overrides poll.quietStatus.\n --until-terminal Continue through WAIT/MARK_READY until FIX_CODE/CANCEL/ESCALATE.\n\nForwarded iterate flags:\n --ready-delay <duration> Settle window before a clean PR cancels. Bare number = minutes. Example: 15m.\n --stall-timeout <duration> Escalate repeated unchanged failures after this duration. Bare number = minutes. 0 disables.\n --no-auto-mark-ready Do not convert draft PRs to ready for review.\n --no-auto-cancel-actionable Legacy no-op; workflow runs are never cancelled.\n --merge Shepherd through readiness, then emit a merge or merge-queue command.\n --format text|json Output Markdown text or JSON. Default: text.\n --verbose Include verbose iterate fields and detailed per-tick lines.\n --help, -h Print this help and exit before GitHub, git, config, or log I/O.\n\nDurations accept s/m/h suffixes: 30s, 4.5m, 1h. A bare number uses each flag's default unit (seconds\nfor --interval/--timeout/--debounce, minutes for --ready-delay/--stall-timeout); decimals are allowed only with\nan explicit unit (4.5m).\nEach WAIT tick writes an explicit still-running line to stderr by default; poll.quietStatus can change that default, --quiet-status/--no-quiet-status override it, and --verbose emits detailed per-tick lines.\nFIX_CODE debounce writes a remaining-seconds line to stderr. --timeout does not cut an in-flight debounce short.\nWith --until-terminal, --timeout is ignored for WAIT ticks and polling continues until FIX_CODE, MERGE, CANCEL, or ESCALATE. With --merge, --timeout still bounds WAIT ticks; it only continues through MARK_READY while polling remains within that timeout.\n\nExit codes: same as iterate (the final tick's action/reason decides the code).\n 0 CANCEL (merged or ready-delay elapsed)\n 10 WAIT (including a WAIT returned by --timeout)\n 11 MARK_READY\n 12 FIX_CODE\n 13 ESCALATE\n 14 CANCEL (closed without merging)\n 15 MERGE\n A command/validation/GitHub failure exits with a sysexits.h code instead (see docs/exit-codes.md).";
197
+ readonly poll: "pr-shepherd poll\n\nRun iterate repeatedly for one PR, or read compact summaries for an explicit PR set or native\nGitHub stack. Aggregate mode returns when any row needs work, every row is terminal, or timeout.\nPoll exits as soon as iterate returns MARK_READY, CANCEL, or ESCALATE, or when timeout\nreturns the last WAIT result. FIX_CODE starts a --debounce settle window (default:\npoll.debounceSeconds; built-in 1m): poll keeps\niterating at --interval, then runs one more tick after the window and returns that result.\nWith --until-terminal or --merge, poll also continues through MARK_READY.\n\nUsage:\n pr-shepherd poll [PR ...] [poll-flags] [iterate-flags]\n pr-shepherd poll --stack PR [poll-flags] [iterate-flags]\n\nPoll flags:\n --stack PR Select all entries in PR's native GitHub stack, bottom to top.\n --interval <duration> Sleep between WAIT ticks. Bare number = seconds. Default: poll.intervalSeconds (built-in 60s).\n --timeout <duration> Maximum wall-clock wait for WAIT ticks. Bare number = seconds. Default: poll.timeoutSeconds (built-in 4.5m).\n --debounce <duration> Settle window after first FIX_CODE or stack SHEPHERD before returning. Bare number = seconds. Default: poll.debounceSeconds (built-in 60s). 0 disables.\n --quiet-status Print only changed WAIT snapshots. Overrides poll.quietStatus.\n --no-quiet-status Print every WAIT snapshot. Overrides poll.quietStatus.\n --until-terminal Continue through WAIT/MARK_READY until FIX_CODE/MERGE/CANCEL/ESCALATE or stack SHEPHERD.\n\nForwarded iterate flags:\n --ready-delay <duration> Settle window before a clean PR cancels. Bare number = minutes. Example: 15m.\n --stall-timeout <duration> Escalate repeated unchanged failures after this duration. Bare number = minutes. 0 disables.\n --no-auto-mark-ready Do not convert draft PRs to ready for review.\n --no-auto-cancel-actionable Legacy no-op; workflow runs are never cancelled.\n --merge Shepherd through readiness, then emit a merge or merge-queue command.\n --format text|json Output Markdown text or JSON. Default: text.\n --verbose Include verbose iterate fields and detailed per-tick lines.\n --help, -h Print this help and exit before GitHub, git, config, or log I/O.\n\nDurations accept s/m/h suffixes: 30s, 4.5m, 1h. A bare number uses each flag's default unit (seconds\nfor --interval/--timeout/--debounce, minutes for --ready-delay/--stall-timeout); decimals are allowed only with\nan explicit unit (4.5m).\nEach WAIT tick writes a stderr line naming what it is waiting on by default; poll.quietStatus can change that default, --quiet-status/--no-quiet-status override it, and --verbose emits detailed per-tick lines.\nFIX_CODE debounce writes a remaining-seconds line to stderr. --timeout does not cut an in-flight debounce short.\nWith --until-terminal, --timeout is ignored for WAIT ticks and polling continues until FIX_CODE, MERGE, CANCEL, or ESCALATE. With --merge, --timeout still bounds WAIT ticks; it only continues through MARK_READY while polling remains within that timeout.\n\nExit codes: same as iterate (the final tick's action/reason decides the code).\n 0 CANCEL (merged or ready-delay elapsed)\n 10 WAIT (including a WAIT returned by --timeout)\n 11 MARK_READY\n 12 FIX_CODE\n 13 ESCALATE\n 14 CANCEL (closed without merging)\n 15 MERGE\n 16 SHEPHERD (--stack only: run the listed one-PR sessions, then rerun the selector)\n A command/validation/GitHub failure exits with a sysexits.h code instead (see docs/exit-codes.md).";
198
198
  readonly clean: `pr-shepherd clean
199
199
 
200
200
  Remove pr-shepherd state files from PR_SHEPHERD_STATE_DIR.
@@ -258,7 +258,7 @@ On POSIX, the final body-file path entry must be a readable regular file in a tr
258
258
  symlinks, FIFOs, devices, and unreadable paths exit 66. Unsupported platforms fail closed with exit 66.
259
259
  --help, -h Print this help and exit before any I/O.`;
260
260
  readonly "log-file": "pr-shepherd log-file\n\nPrint the per-worktree append-only debug log path for the current repository.\nThe log is created by the first non-help pr-shepherd command that initializes logging.\n\nUsage:\n pr-shepherd log-file [--format text|json]\n\nFlags:\n --format text|json Print a raw path or {\"path\": \"...\"} JSON. Default: text.\n --help, -h Print this help and exit before logging setup.\n\nEnvironment:\n PR_SHEPHERD_LOG_DISABLED=1 disables logging.\n PR_SHEPHERD_STATE_DIR overrides the base state directory.\n\nExit code: 0 on success; 1 if repository identity cannot be resolved.";
261
- readonly top: "pr-shepherd\n\nAutonomous PR CI monitor and review-comment resolver for agentic coding tools.\n\nUsage:\n pr-shepherd --version | -v\n pr-shepherd --help | -h\n pr-shepherd [PR ...] [poll-flags] [iterate-flags]\n pr-shepherd --stack PR [poll-flags] [iterate-flags]\n pr-shepherd iterate [PR] [iterate-flags]\n pr-shepherd apply review [PR] [review-flags]\n pr-shepherd apply files [PR] [files...] [--tests] [--match REGEX]\n pr-shepherd apply journal [PR] <item> [--dry-run] [--format text|json]\n pr-shepherd journal extract --body-file <path>\n pr-shepherd build-suggestion-patches [PR] --thread-id ID --message MSG [groups...]\n pr-shepherd admin clean <pr|branch|current|repo|all> [value] [flags]\n pr-shepherd admin log-file [--format text|json]\n\nCommands:\n [PR ...] Poll one PR, an explicit same-repository set, or a native stack.\n iterate Run one iterate tick (single-tick alias).\n apply review Apply review-state mutations after fixes.\n apply files Mark selected changed files as viewed.\n apply journal Append a list item to the Shepherd Journal details block of a PR body.\n journal extract Extract a validated Shepherd Journal from a local PR-body file as JSON.\n build-suggestion-patches\n Convert ordered GitHub suggestion threads into patches and commit instructions.\n admin clean Remove pr-shepherd state files.\n admin log-file Print the per-worktree debug log path.\n\nPR argument:\n PR may be a number, owner/repo#number, or a GitHub pull request URL.\n Multiple PRs must name one repository. --stack PR selects every entry in PR's native stack.\n When omitted, pr-shepherd infers the current branch's pull request.\n\nCommon flags:\n --format text|json Output Markdown text or JSON. Default: text.\n --verbose Include verbose iterate fields and detailed poll-tick lines.\n --help, -h Print help and exit before any GitHub, git, config, or log I/O.\n\nIterate flags:\n --ready-delay <duration> Settle window before a clean PR cancels. Bare number = minutes. Example: 15m.\n --stall-timeout <duration> Escalate repeated unchanged failures after this duration. Bare number = minutes. 0 disables.\n --no-auto-mark-ready Do not convert draft PRs to ready for review.\n --no-auto-cancel-actionable Legacy no-op; workflow runs are never cancelled.\n --merge Shepherd through readiness, then emit a merge or merge-queue command.\n\nPolling flags:\n --interval <duration> Delay between WAIT ticks. Bare number = seconds. Default: poll.intervalSeconds (built-in 60s).\n --timeout <duration> Poll wall-clock cap for WAIT ticks. Bare number = seconds. Default: poll.timeoutSeconds (built-in 4.5m).\n --debounce <duration> Settle window after first FIX_CODE before returning. Bare number = seconds. Default: poll.debounceSeconds (built-in 60s). 0 disables.\n --quiet-status Print only changed WAIT snapshots. Overrides poll.quietStatus.\n --no-quiet-status Print every WAIT snapshot. Overrides poll.quietStatus.\n --until-terminal Continue through WAIT/MARK_READY until FIX_CODE/CANCEL/ESCALATE.\n\nClean variants:\n pr [number] Remove state for one PR. Defaults to current branch PR.\n branch [name] Remove state for a branch's PR. Defaults to current branch.\n current Alias for branch against the current branch.\n repo Remove all state for the current repository.\n all Remove all pr-shepherd state.\n\nExit codes: 0 done, 10-19 PR state, 64-78 shepherd failed (sysexits.h).\n 0 CANCEL (merged or ready-delay elapsed)\n 10 WAIT\n 11 MARK_READY\n 12 FIX_CODE\n 13 ESCALATE\n 14 CANCEL (closed without merging)\n 15 MERGE\nSee docs/exit-codes.md for the full sysexits.h error-code table.\n\nDuration examples: 30s, 4.5m, 1h. A bare number uses each flag's default unit (see above); decimals are allowed with an explicit unit (4.5m).\n\nRun 'pr-shepherd <command> --help' for command-specific details.";
261
+ readonly top: "pr-shepherd\n\nAutonomous PR CI monitor and review-comment resolver for agentic coding tools.\n\nUsage:\n pr-shepherd --version | -v\n pr-shepherd --help | -h\n pr-shepherd [PR ...] [poll-flags] [iterate-flags]\n pr-shepherd --stack PR [poll-flags] [iterate-flags]\n pr-shepherd iterate [PR] [iterate-flags]\n pr-shepherd apply review [PR] [review-flags]\n pr-shepherd apply files [PR] [files...] [--tests] [--match REGEX]\n pr-shepherd apply journal [PR] <item> [--dry-run] [--format text|json]\n pr-shepherd journal extract --body-file <path>\n pr-shepherd build-suggestion-patches [PR] --thread-id ID --message MSG [groups...]\n pr-shepherd admin clean <pr|branch|current|repo|all> [value] [flags]\n pr-shepherd admin log-file [--format text|json]\n\nCommands:\n [PR ...] Poll one PR, an explicit same-repository set, or a native stack.\n iterate Run one iterate tick (single-tick alias).\n apply review Apply review-state mutations after fixes.\n apply files Mark selected changed files as viewed.\n apply journal Append a list item to the Shepherd Journal details block of a PR body.\n journal extract Extract a validated Shepherd Journal from a local PR-body file as JSON.\n build-suggestion-patches\n Convert ordered GitHub suggestion threads into patches and commit instructions.\n admin clean Remove pr-shepherd state files.\n admin log-file Print the per-worktree debug log path.\n\nPR argument:\n PR may be a number, owner/repo#number, or a GitHub pull request URL.\n Multiple PRs must name one repository. --stack PR selects every entry in PR's native stack.\n When omitted, pr-shepherd infers the current branch's pull request.\n\nCommon flags:\n --format text|json Output Markdown text or JSON. Default: text.\n --verbose Include verbose iterate fields and detailed poll-tick lines.\n --help, -h Print help and exit before any GitHub, git, config, or log I/O.\n\nIterate flags:\n --ready-delay <duration> Settle window before a clean PR cancels. Bare number = minutes. Example: 15m.\n --stall-timeout <duration> Escalate repeated unchanged failures after this duration. Bare number = minutes. 0 disables.\n --no-auto-mark-ready Do not convert draft PRs to ready for review.\n --no-auto-cancel-actionable Legacy no-op; workflow runs are never cancelled.\n --merge Shepherd through readiness, then emit a merge or merge-queue command.\n\nPolling flags:\n --interval <duration> Delay between WAIT ticks. Bare number = seconds. Default: poll.intervalSeconds (built-in 60s).\n --timeout <duration> Poll wall-clock cap for WAIT ticks. Bare number = seconds. Default: poll.timeoutSeconds (built-in 4.5m).\n --debounce <duration> Settle window after first FIX_CODE or stack SHEPHERD before returning. Bare number = seconds. Default: poll.debounceSeconds (built-in 60s). 0 disables.\n --quiet-status Print only changed WAIT snapshots. Overrides poll.quietStatus.\n --no-quiet-status Print every WAIT snapshot. Overrides poll.quietStatus.\n --until-terminal Continue through WAIT/MARK_READY until FIX_CODE/MERGE/CANCEL/ESCALATE or stack SHEPHERD.\n\nClean variants:\n pr [number] Remove state for one PR. Defaults to current branch PR.\n branch [name] Remove state for a branch's PR. Defaults to current branch.\n current Alias for branch against the current branch.\n repo Remove all state for the current repository.\n all Remove all pr-shepherd state.\n\nExit codes: 0 done, 10-19 PR state, 64-78 shepherd failed (sysexits.h).\n 0 CANCEL (merged or ready-delay elapsed)\n 10 WAIT\n 11 MARK_READY\n 12 FIX_CODE\n 13 ESCALATE\n 14 CANCEL (closed without merging)\n 15 MERGE\n 16 SHEPHERD (--stack only: run the listed one-PR sessions, then rerun the selector)\nSee docs/exit-codes.md for the full sysexits.h error-code table.\n\nDuration examples: 30s, 4.5m, 1h. A bare number uses each flag's default unit (see above); decimals are allowed with an explicit unit (4.5m).\n\nRun 'pr-shepherd <command> --help' for command-specific details.";
262
262
  };
263
263
  /** Resolve help keys for nested public commands before any command I/O. */
264
264
  export declare function helpKeyForArgs(args: string[]): keyof typeof USAGE;
@@ -1,9 +1,26 @@
1
1
  import { renderMergeCommand } from "../commands/iterate/merge.mjs";
2
2
  import { inlineCode } from "../util/markdown.mjs";
3
3
  import { buildQuotaAwareContinuation } from "../quota-warning.mjs";
4
+ import { formatPrUrl } from "../pr-reference.mjs";
5
+ import { AUTO_MARK_READY_DISABLED_HOLD } from "../commands/stack-work.mjs";
6
+ import { buildPrShepherdCommand } from "./runner.mjs";
7
+ const STACK_LAYER_BLOCK_REASONS = {
8
+ closed: "was closed without merging",
9
+ draft: "is still a draft",
10
+ conflicting: "has merge conflicts",
11
+ "queue-removal": "has an unacknowledged merge-queue removal",
12
+ "failing-checks": "has failing checks",
13
+ "review-work": "has unresolved review work",
14
+ "checks-in-progress": "has checks in progress",
15
+ "merge-state": "is not in a mergeable state",
16
+ "no-ready-receipt": "has no current Shepherd READY receipt",
17
+ "stale-ancestry": "is not rebased onto its parent layer's current head",
18
+ };
4
19
  export function buildSimpleIterateInstructions(result) {
5
20
  switch (result.action) {
6
21
  case "wait":
22
+ if (result.stackDraftHold)
23
+ return [buildStackDraftHoldInstruction(result, result.stackDraftHold)];
7
24
  if (result.quotaWarning) {
8
25
  return [
9
26
  buildQuotaAwareContinuation(result.quotaWarning, "Non-terminal — no action needed this tick."),
@@ -49,6 +66,30 @@ export function buildSimpleIterateInstructions(result) {
49
66
  }
50
67
  }
51
68
  }
69
+ /**
70
+ * A held stack draft cannot advance by repeating its one-PR session, so hand control back
71
+ * to the stack selector instead of asking for another immediate iteration.
72
+ */
73
+ function buildStackDraftHoldInstruction(result, hold) {
74
+ const stackCommand = buildPrShepherdCommand([
75
+ "--stack",
76
+ formatPrUrl(result.repo, result.pr),
77
+ "--until-terminal",
78
+ ]).text;
79
+ const lowerLayer = hold.kind === "lower-layer-not-ready" ? hold.lowerLayer : undefined;
80
+ const handoff = `a \`--stack\` selector listed this session, finish that selector's remaining steps and rerun it with its original flags; otherwise run ${inlineCode(stackCommand)}, adding \`--merge\` when merging was requested.`;
81
+ const instruction = lowerLayer
82
+ ? `PR #${result.pr} stays in draft because lower stack layer PR #${lowerLayer.pr} ${STACK_LAYER_BLOCK_REASONS[lowerLayer.reason]}, so repeating this one-PR session cannot advance it. Advance PR #${lowerLayer.pr} first: if ${handoff}`
83
+ : `PR #${result.pr} stays in draft because ${holdReason(hold)}, so repeating this one-PR session cannot advance it. If ${handoff}`;
84
+ return result.quotaWarning
85
+ ? buildQuotaAwareContinuation(result.quotaWarning, instruction)
86
+ : instruction;
87
+ }
88
+ function holdReason(hold) {
89
+ return hold.kind === "auto-mark-ready-disabled"
90
+ ? AUTO_MARK_READY_DISABLED_HOLD
91
+ : "its lower stack layers could not be verified";
92
+ }
52
93
  export function adaptIterateLog(log) {
53
94
  return log.replace(/\s+—\s+\d+s until auto-cancel/g, "");
54
95
  }
@@ -72,6 +72,7 @@ export function projectIterateLean(result, opts) {
72
72
  return {
73
73
  ...base,
74
74
  ...(result.deferredWork && { deferredWork: result.deferredWork }),
75
+ ...(result.stackDraftHold && { stackDraftHold: result.stackDraftHold }),
75
76
  log: adaptIterateLog(result.log),
76
77
  instructions: simpleInstructions(result),
77
78
  };
@@ -183,9 +184,6 @@ export function projectIterateLean(result, opts) {
183
184
  ...(result.escalate.mergeQueueRemoval && {
184
185
  mergeQueueRemoval: result.escalate.mergeQueueRemoval,
185
186
  }),
186
- ...(result.escalate.stack && {
187
- stack: result.escalate.stack,
188
- }),
189
187
  ...(result.escalate.authorization &&
190
188
  result.escalate.authorization.length > 0 && {
191
189
  authorization: result.escalate.authorization,
@@ -12,13 +12,12 @@ function pollSummaryExitCode(result) {
12
12
  if (result.nextAction) {
13
13
  const stackExitCode = {
14
14
  escalate: EXIT.ESCALATE,
15
- fix_code: EXIT.FIX_CODE,
15
+ shepherd: EXIT.SHEPHERD,
16
16
  merge: EXIT.MERGE,
17
- mark_ready: EXIT.MARK_READY,
18
17
  wait: EXIT.WAIT,
19
18
  cancel: EXIT.OK,
20
19
  };
21
- return stackExitCode[result.nextAction] ?? EXIT.OK;
20
+ return stackExitCode[result.nextAction];
22
21
  }
23
22
  const actions = new Set(result.prs.map((item) => item.action));
24
23
  if (actions.has("escalate"))
@@ -7,7 +7,7 @@ export function formatPollSummaryResult(result) {
7
7
  const lines = [
8
8
  `# Poll summary [${result.reason.toUpperCase()}]`,
9
9
  "",
10
- `**repo** \`${result.repo}\` · **selection** ${selection} · **mode** \`${result.mode}\`${result.nextAction ? ` · **next action** \`${result.nextAction}\`` : ""}`,
10
+ `**repo** \`${result.repo}\` · **selection** ${selection} · **mode** \`${result.mode}\`${result.stackMergeable !== undefined ? ` · **stack mergeable** \`${result.stackMergeable}\`` : ""}${result.nextAction ? ` · **next action** \`${result.nextAction}\`` : ""}`,
11
11
  "",
12
12
  "## Pull requests",
13
13
  "",
@@ -42,11 +42,13 @@ function formatItem(item) {
42
42
  ? " · blocking reviewer `in progress`"
43
43
  : "";
44
44
  const readyDelay = item.remainingSeconds !== undefined ? ` · ready delay \`${item.remainingSeconds}s\`` : "";
45
+ const readyReceipt = item.readyReceipt ? " · Shepherd READY completion `verified`" : "";
46
+ const blockedBy = item.blockedByPr ? ` · stack blocked by PR #${item.blockedByPr}` : "";
45
47
  const checks = item.checks;
46
48
  const review = item.review;
47
49
  return [
48
50
  `- [PR #${item.pr}: ${escapeMarkdownText(item.title)}](${item.url}) [${item.action.toUpperCase()}]`,
49
- ` - state \`${item.state}\` · mergeable \`${item.mergeable}\` · merge \`${item.mergeStateStatus}\`${reviewDecision}${stateFlags}${blockingReviewer}${readyDelay}${stack}`,
51
+ ` - state \`${item.state}\` · mergeable \`${item.mergeable}\` · merge \`${item.mergeStateStatus}\`${reviewDecision}${stateFlags}${blockingReviewer}${readyDelay}${readyReceipt}${blockedBy}${stack}`,
50
52
  ` - head \`${item.headRefName}\` at \`${item.headRefOid}\` · base \`${item.baseRefName}\``,
51
53
  ...(checks
52
54
  ? [` - checks: ${formatCounts(checks, checks.incomplete ? ", incomplete" : "")}`]
@@ -54,8 +56,15 @@ function formatItem(item) {
54
56
  ...(review
55
57
  ? [` - review: ${formatCounts(review, review.incomplete ? ", incomplete" : "")}`]
56
58
  : []),
59
+ ...(item.queueRemoval
60
+ ? [
61
+ ` - queue removal: reason \`${item.queueRemoval.reason ?? "UNKNOWN"}\` · at \`${item.queueRemoval.createdAtUnix}\`${item.queueRemoval.actor ? ` · actor \`@${item.queueRemoval.actor}\`` : ""}${item.queueRemoval.beforeCommitOid ? ` · commit \`${item.queueRemoval.beforeCommitOid}\`` : ""}${item.queueRemoval.beforeCommitParentOids?.length ? ` · parents \`${item.queueRemoval.beforeCommitParentOids.join(",")}\`` : ""}`,
62
+ ]
63
+ : []),
57
64
  ` - reasons: ${item.reasons.map((reason) => `\`${reason}\``).join(", ")}`,
58
- ...(item.pollCommand ? [` - pollCommand: \`${item.pollCommand}\``] : []),
65
+ ...(item.pollCommand
66
+ ? [` - pollCommand: \`${item.pollCommand}\`${item.pollProbe ? " · bounded probe" : ""}`]
67
+ : []),
59
68
  ].join("\n");
60
69
  }
61
70
  function formatCounts(counts, suffix) {
@@ -2,6 +2,7 @@ interface PrShepherdCommand {
2
2
  argv: string[];
3
3
  text: string;
4
4
  }
5
+ /** Every emitted pr-shepherd command starts with the configured `cliCommand` launcher. */
5
6
  export declare function buildPrShepherdCommand(args: string[]): PrShepherdCommand;
6
7
  export declare function renderShellCommand(argv: string[]): string;
7
8
  export {};
@@ -1,5 +1,7 @@
1
+ import { loadConfig } from "../config/load.mjs";
2
+ /** Every emitted pr-shepherd command starts with the configured `cliCommand` launcher. */
1
3
  export function buildPrShepherdCommand(args) {
2
- const argv = ["pr-shepherd", ...args];
4
+ const argv = [...loadConfig().cliCommand, ...args];
3
5
  return { argv, text: renderShellCommand(argv) };
4
6
  }
5
7
  export function renderShellCommand(argv) {
@@ -1,4 +1,5 @@
1
1
  import { fetchPrBatch } from "../github/batch.mjs";
2
+ import { queueRemovalAppliesToHead } from "../github/queue-removal-freshness.mjs";
2
3
  import { storePrFingerprint } from "../state/pr-fingerprint.mjs";
3
4
  import { tryReuseFingerprintReport } from "./check-fingerprint.mjs";
4
5
  import { getRepoInfo, getCurrentPrNumber } from "../github/client.mjs";
@@ -64,11 +65,19 @@ export async function runCheck(opts) {
64
65
  // historical event forever. When GitHub omits the removed queue commit for that old event
65
66
  // (e.g. after the synthetic commit is garbage collected), freshness is unverifiable — treat
66
67
  // it as stale/updated rather than as still current, so Shepherd doesn't escalate
67
- // `merge-queue-removed` permanently on data it can no longer check. The raw removal fields
68
- // still render in the merge-queue header regardless of this flag.
68
+ // `merge-queue-removed` permanently on data it can no longer check. A squash or rebase
69
+ // queue commit has one parent and does not list the PR head; that removal stays current
70
+ // until the head's committer time is later than the removal. The raw removal fields still
71
+ // render in the merge-queue header regardless of this flag.
72
+ const headCommittedAtUnix = batchData.activity?.latestCommitCommittedAtUnix;
69
73
  const headUpdatedAfterRemoval = Boolean(latestRemoval &&
70
- (!latestRemoval.beforeCommitParentOids ||
71
- !latestRemoval.beforeCommitParentOids.includes(batchData.headRefOid)));
74
+ !queueRemovalAppliesToHead({
75
+ parentOids: latestRemoval.beforeCommitParentOids,
76
+ headOid: batchData.headRefOid,
77
+ ...(headCommittedAtUnix !== undefined &&
78
+ headCommittedAtUnix !== null && { headCommittedAtUnix }),
79
+ removedAtUnix: latestRemoval.createdAtUnix,
80
+ }));
72
81
  const queueRawChecks = batchData.isInMergeQueue
73
82
  ? (batchData.mergeQueueChecks ?? [])
74
83
  : latestRemoval && !headUpdatedAfterRemoval
@@ -249,6 +258,7 @@ export async function runCheck(opts) {
249
258
  ...(batchData.viewerAuthorization && { viewerAuthorization: batchData.viewerAuthorization }),
250
259
  status,
251
260
  baseBranch: batchData.baseRefName,
261
+ ...(batchData.baseRefOid && { baseRefOid: batchData.baseRefOid }),
252
262
  mergeStatus,
253
263
  checks: {
254
264
  passing: merged.passing,
@@ -1,8 +1,7 @@
1
1
  import { rm, readdir, realpath, stat } from "node:fs/promises";
2
2
  import { join } from "node:path";
3
- import { resolveStateBase } from "../state/base.mjs";
3
+ import { resolveRepoStateDir, resolveStateBase } from "../state/base.mjs";
4
4
  import { getRepoInfo, getCurrentPrNumber, getCurrentBranch, getPrNumberForBranch, } from "../github/client.mjs";
5
- import { SAFE_SEGMENT } from "../util/path-segment.mjs";
6
5
  export async function runClean(opts) {
7
6
  const dryRun = opts.dryRun ?? false;
8
7
  const rawBase = resolveStateBase();
@@ -107,20 +106,15 @@ async function resolveTarget(base, opts) {
107
106
  }
108
107
  const repo = await getRepoInfo();
109
108
  const { owner, name } = repo;
110
- for (const [field, val] of [
111
- ["owner", owner],
112
- ["repo", name],
113
- ]) {
114
- if (!SAFE_SEGMENT.test(val)) {
115
- throw new Error(`Invalid repository segment "${field}": ${val}`);
116
- }
117
- }
118
- const ownerRepo = `${owner}-${name}`;
109
+ // Same segment rules as every other state path. The directory itself stays under
110
+ // `base`, which runClean has already realpath'd.
111
+ resolveRepoStateDir({ owner, repo: name });
112
+ const repoDir = join(base, owner, name);
119
113
  if (variant === "repo") {
120
114
  if (value !== undefined) {
121
115
  throw new Error(`"clean repo" does not accept a positional argument; got "${value}". Did you mean "clean pr" or "clean branch"?`);
122
116
  }
123
- return join(base, ownerRepo);
117
+ return repoDir;
124
118
  }
125
119
  let prNumber;
126
120
  if (variant === "pr") {
@@ -152,5 +146,5 @@ async function resolveTarget(base, opts) {
152
146
  throw new Error(`No open PR found for branch: ${branchName}`);
153
147
  prNumber = n;
154
148
  }
155
- return join(base, ownerRepo, String(prNumber));
149
+ return join(repoDir, String(prNumber));
156
150
  }
@@ -19,7 +19,7 @@ export declare function buildBehindBaseHintInstruction(baseBranch: string, hint:
19
19
  export declare function buildRepeatedWorkflowBranchRecoveryInstructions(baseBranch: string, hasExhaustedWorkflowRerun: boolean, branch: {
20
20
  isBehind: boolean;
21
21
  hasConflicts: boolean;
22
- }): string[];
22
+ }, stackRebase?: string): string[];
23
23
  /**
24
24
  * Build the `Run the apply review: command` instruction. Steps stay here (not in the skill)
25
25
  * whenever the *unmodified, as-printed* command is unsafe without them:
@@ -33,4 +33,4 @@ export declare function buildRepeatedWorkflowBranchRecoveryInstructions(baseBran
33
33
  export declare function buildResolveCommandInstruction(resolveCommand: ResolveCommand): string[];
34
34
  /** Build the CI-triage pointer; the skill limits follow-up actions to included evidence. */
35
35
  export declare function buildFailingCheckInstructions(checks: AgentCheck[]): string[];
36
- export declare function buildFixCompletionInstruction(checks: AgentCheck[], hasConflicts?: boolean, hasShaGatedReviewMutations?: boolean): string;
36
+ export declare function buildFixCompletionInstruction(checks: AgentCheck[], hasConflicts?: boolean, hasShaGatedReviewMutations?: boolean, pushesRewrittenStack?: boolean): string;
@@ -25,16 +25,17 @@ export function buildBehindBaseHintInstruction(baseBranch, hint, isBehind) {
25
25
  * The fetched PR base branch is raw context; the caller still owns repository-specific git
26
26
  * mechanics and decides whether the base contains a relevant fix.
27
27
  */
28
- export function buildRepeatedWorkflowBranchRecoveryInstructions(baseBranch, hasExhaustedWorkflowRerun, branch) {
28
+ export function buildRepeatedWorkflowBranchRecoveryInstructions(baseBranch, hasExhaustedWorkflowRerun, branch, stackRebase) {
29
29
  if (!hasExhaustedWorkflowRerun || (!branch.isBehind && !branch.hasConflicts))
30
30
  return [];
31
31
  const state = branch.hasConflicts ? "conflicts with" : "is behind";
32
32
  const instructions = [
33
33
  `The workflow rerun still fails while the branch ${state} PR base branch \`${baseBranch}\`. Inspect the current base branch for an existing fix before choosing a remediation.`,
34
34
  ];
35
- instructions.push(branch.hasConflicts
36
- ? `Rebase or otherwise update the PR branch from \`${baseBranch}\` according to repository conventions, resolving conflicts as part of that update.`
37
- : `Rebase or otherwise update the PR branch from \`${baseBranch}\` according to repository conventions.`);
35
+ instructions.push(stackRebase ??
36
+ (branch.hasConflicts
37
+ ? `Rebase or otherwise update the PR branch from \`${baseBranch}\` according to repository conventions, resolving conflicts as part of that update.`
38
+ : `Rebase or otherwise update the PR branch from \`${baseBranch}\` according to repository conventions.`));
38
39
  return instructions;
39
40
  }
40
41
  /**
@@ -79,11 +80,14 @@ export function buildFailingCheckInstructions(checks) {
79
80
  }
80
81
  return instructions;
81
82
  }
82
- export function buildFixCompletionInstruction(checks, hasConflicts = false, hasShaGatedReviewMutations = false) {
83
+ export function buildFixCompletionInstruction(checks, hasConflicts = false, hasShaGatedReviewMutations = false, pushesRewrittenStack = false) {
84
+ const push = pushesRewrittenStack
85
+ ? "push the rewritten stack with `gh stack push`"
86
+ : "push to the PR head branch";
83
87
  if (hasConflicts)
84
- return "`[FIX_CODE]` is non-terminal: resolve the conflicts, commit, push to the PR head branch, then iterate immediately with the same options.";
88
+ return `\`[FIX_CODE]\` is non-terminal: resolve the conflicts, commit, ${push}, then iterate immediately with the same options.`;
85
89
  if (hasShaGatedReviewMutations) {
86
- return "`[FIX_CODE]` is non-terminal: if you changed code, commit and push to the PR head branch, then run the review mutations using the pushed commit SHA and iterate immediately with the same options; if you did not change code, complete the authorized review mutations and iterate immediately with the same options.";
90
+ return `\`[FIX_CODE]\` is non-terminal: if you changed code, commit and ${push}, then run the review mutations using the pushed commit SHA and iterate immediately with the same options; if you did not change code, complete the authorized review mutations and iterate immediately with the same options.`;
87
91
  }
88
92
  if (checks.some((check) => check.rerunCommand)) {
89
93
  return "`[FIX_CODE]` is non-terminal. Run any warranted reruns for `[rerun authorized]` checks (or apply code fixes for real failures), then iterate immediately with the same options to continue.";