pr-shepherd 0.47.0 → 0.49.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 (65) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +14 -0
  3. package/bin/api.d.mts +19 -3
  4. package/bin/api.mjs +57 -8
  5. package/bin/classify/apply.d.mts +2 -0
  6. package/bin/classify/apply.mjs +1 -1
  7. package/bin/cli/args.mjs +1 -0
  8. package/bin/cli/default-poll.mjs +2 -0
  9. package/bin/cli/help-command-pages.d.mts +1 -1
  10. package/bin/cli/help-iterate-poll-pages.d.mts +1 -1
  11. package/bin/cli/help-iterate-poll-pages.mjs +16 -10
  12. package/bin/cli/help-top-page.d.mts +1 -1
  13. package/bin/cli/help-top-page.mjs +10 -7
  14. package/bin/cli/help.d.mts +2 -2
  15. package/bin/cli/poll-handler.mjs +40 -11
  16. package/bin/cli/poll-summary-emitter.d.mts +4 -0
  17. package/bin/cli/poll-summary-emitter.mjs +22 -0
  18. package/bin/cli/poll-summary-formatter.d.mts +2 -0
  19. package/bin/cli/poll-summary-formatter.mjs +96 -0
  20. package/bin/cli/poll-targets.d.mts +15 -0
  21. package/bin/cli/poll-targets.mjs +114 -0
  22. package/bin/cli/validate-default-args.mjs +1 -4
  23. package/bin/commands/iterate/api-usage.d.mts +1 -1
  24. package/bin/commands/iterate/api-usage.mjs +6 -2
  25. package/bin/commands/iterate/run.mjs +1 -1
  26. package/bin/commands/poll-run.d.mts +1 -1
  27. package/bin/commands/poll-run.mjs +2 -2
  28. package/bin/commands/poll-summary.d.mts +10 -0
  29. package/bin/commands/poll-summary.mjs +163 -0
  30. package/bin/commands/poll.mjs +2 -1
  31. package/bin/commands/ready-delay.d.mts +3 -1
  32. package/bin/commands/ready-delay.mjs +3 -2
  33. package/bin/config/load.d.mts +7 -0
  34. package/bin/config/load.mjs +70 -19
  35. package/bin/config.json +9 -3
  36. package/bin/github/gql/poll-stack-summary.gql +33 -0
  37. package/bin/github/gql/poll-summary-fragment.gql +198 -0
  38. package/bin/github/poll-summary-checks.d.mts +3 -0
  39. package/bin/github/poll-summary-checks.mjs +61 -0
  40. package/bin/github/poll-summary-projector.d.mts +4 -0
  41. package/bin/github/poll-summary-projector.mjs +81 -0
  42. package/bin/github/poll-summary-raw.d.mts +134 -0
  43. package/bin/github/poll-summary-raw.mjs +1 -0
  44. package/bin/github/poll-summary-review.d.mts +4 -0
  45. package/bin/github/poll-summary-review.mjs +85 -0
  46. package/bin/github/poll-summary-route.d.mts +4 -0
  47. package/bin/github/poll-summary-route.mjs +47 -0
  48. package/bin/github/poll-summary.d.mts +7 -0
  49. package/bin/github/poll-summary.mjs +110 -0
  50. package/bin/github/queries.d.mts +4 -0
  51. package/bin/github/queries.mjs +4 -0
  52. package/bin/mcp/server.mjs +39 -6
  53. package/bin/pr-reference.d.mts +2 -0
  54. package/bin/pr-reference.mjs +4 -0
  55. package/bin/quota-warning.mjs +1 -1
  56. package/bin/types/iterate.d.mts +3 -3
  57. package/bin/types/poll-summary.d.mts +82 -0
  58. package/bin/types/poll-summary.mjs +1 -0
  59. package/bin/types.d.mts +1 -0
  60. package/bin/types.mjs +1 -0
  61. package/package.json +1 -1
  62. package/plugins/pr-shepherd/.codex-plugin/plugin.json +1 -1
  63. package/plugins/pr-shepherd/.codex.mcp.json +1 -1
  64. package/plugins/pr-shepherd/.mcp.json +1 -1
  65. package/plugins/pr-shepherd/skills/pr-shepherd/SKILL.md +2 -2
@@ -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.47.0",
4
+ "version": "0.49.0",
5
5
  "author": {
6
6
  "name": "Jonathan Ong",
7
7
  "email": "jonathanrichardong@gmail.com"
package/README.md CHANGED
@@ -135,8 +135,22 @@ pr-shepherd 42 --merge # request head-pinned auto-merge/queue;
135
135
  pr-shepherd iterate 42 # single tick
136
136
  pr-shepherd owner/repo#42 # poll a PR in an explicit repository
137
137
  pr-shepherd https://github.com/owner/repo/pull/42
138
+ pr-shepherd 42 43 44 # summarize an explicit same-repository set
139
+ pr-shepherd --stack 43 # summarize every PR in a native GitHub stack
138
140
  ```
139
141
 
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. Each actionable row includes
148
+ an exact single-PR `pollCommand`; run independent actionable rows, then invoke the aggregate selector again.
149
+ Stack rows are ordered bottom-to-top. API and MCP aggregate calls perform one summary tick and leave
150
+ recurrence to the caller.
151
+
152
+ 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.
153
+
140
154
  ### Apply Review And Journal Changes, Or Select Files
141
155
 
142
156
  Use `apply` with ordered operations to reply/resolve/minimize/dismiss review items, mark selected changed files as viewed, or append an idempotent Shepherd Journal item. Explicit operations are attempted and surface GitHub's per-operation results; generated iterate guidance remains capability-filtered. Use `build_suggestion_patches` to turn ordered review suggestions into checked patches and commit metadata; it never changes the worktree or git history.
package/bin/api.d.mts CHANGED
@@ -1,16 +1,29 @@
1
1
  import { type JournalResult } from "./commands/journal/index.mts";
2
2
  import { type MarkFilesAsViewedResult } from "./commands/mark-files-as-viewed.mts";
3
3
  import type { ResolveResult } from "./comments/resolve.mts";
4
- import type { BuildSuggestionPatchesResult, CommitSuggestionResult, IterateCommandOptions, IterateResult } from "./types.mts";
4
+ import type { BuildSuggestionPatchesResult, CommitSuggestionResult, IterateCommandOptions, IterateResult, PollSummaryResult } from "./types.mts";
5
5
  export interface CreatePrShepherdOptions {
6
6
  /** Working directory used for git, config, and classification-rule lookups. */
7
7
  cwd?: string;
8
8
  }
9
9
  /** A positive PR number, GitHub pull-request URL, or owner/repo#number reference. */
10
10
  export type PrReference = number | string;
11
- export type IterateInput = Omit<IterateCommandOptions, "format" | "prNumber" | "targetRepository" | "persistSeen" | "fingerprintCache" | "deferQuotaWarning"> & {
11
+ type IterateOptions = Omit<IterateCommandOptions, "format" | "prNumber" | "targetRepository" | "persistSeen" | "fingerprintCache" | "deferQuotaWarning" | "quotaWarningMinimumPollIntervalMinutes">;
12
+ export type SingleIterateInput = IterateOptions & {
12
13
  pr?: PrReference;
14
+ prs?: never;
15
+ stack?: never;
13
16
  };
17
+ export type AggregateIterateInput = IterateOptions & ({
18
+ prs: PrReference[];
19
+ pr?: never;
20
+ stack?: never;
21
+ } | {
22
+ stack: PrReference;
23
+ pr?: never;
24
+ prs?: never;
25
+ });
26
+ export type IterateInput = SingleIterateInput | AggregateIterateInput;
14
27
  export interface ReviewMutationsOperation {
15
28
  type: "review_mutations";
16
29
  resolveThreadIds?: string[];
@@ -69,7 +82,9 @@ export interface BuildSuggestionPatchesInput {
69
82
  suggestions: SuggestionPatchInput[];
70
83
  }
71
84
  export interface PrShepherd {
72
- iterate(input?: IterateInput): Promise<IterateResult>;
85
+ iterate(input?: SingleIterateInput): Promise<IterateResult>;
86
+ iterate(input: AggregateIterateInput): Promise<PollSummaryResult>;
87
+ iterate(input: IterateInput): Promise<IterateResult | PollSummaryResult>;
73
88
  apply(input: ApplyInput): Promise<ApplyResult>;
74
89
  buildSuggestionPatches(input: BuildSuggestionPatchesInput): Promise<BuildSuggestionPatchesResult>;
75
90
  /** Compatibility adapter; prefer buildSuggestionPatches. */
@@ -90,3 +105,4 @@ export declare class PartialApplyError extends Error {
90
105
  * read/plan, ordered apply, and suggestion-patch operations.
91
106
  */
92
107
  export declare function createPrShepherd(options?: CreatePrShepherdOptions): PrShepherd;
108
+ export {};
package/bin/api.mjs CHANGED
@@ -3,12 +3,14 @@ import { resolve } from "node:path";
3
3
  import { runCommitSuggestion } from "./commands/commit-suggestion.mjs";
4
4
  import { runSuggestionPatches } from "./commands/suggestion-patches.mjs";
5
5
  import { runIterate } from "./commands/iterate/index.mjs";
6
+ import { runPollSummary } from "./commands/poll-summary.mjs";
6
7
  import { runJournal } from "./commands/journal/index.mjs";
7
8
  import { validateJournalItem } from "./commands/journal/transform.mjs";
8
9
  import { runMarkFilesAsViewed, } from "./commands/mark-files-as-viewed.mjs";
9
10
  import { runResolveMutate } from "./commands/resolve-mutate.mjs";
10
11
  import { runWithExecutionCwd } from "./execution-context.mjs";
11
- import { parsePrReference, resolveParsedPrTarget, } from "./pr-reference.mjs";
12
+ import { parsePrReference, normalizeRepositoryIdentity, resolveParsedPrTarget, } from "./pr-reference.mjs";
13
+ import { getRepoInfo } from "./github/client.mjs";
12
14
  /** Raised before any API mutation when an input cannot be validated. */
13
15
  export class PrShepherdValidationError extends Error {
14
16
  constructor(message) {
@@ -34,14 +36,20 @@ export class PartialApplyError extends Error {
34
36
  */
35
37
  export function createPrShepherd(options = {}) {
36
38
  const cwd = options.cwd === undefined ? undefined : resolve(options.cwd);
39
+ function iterate(input = {}) {
40
+ return runWithExecutionCwd(cwd, async () => {
41
+ validateIterateSelectors(input);
42
+ if ("prs" in input || "stack" in input) {
43
+ const target = await resolveAggregateIterateInput(input);
44
+ return runPollSummary(target);
45
+ }
46
+ const { pr: _pr, ...iterateOptions } = input;
47
+ const target = resolvePrReference(input.pr);
48
+ return runIterate({ ...iterateOptions, ...target, format: "json" });
49
+ });
50
+ }
37
51
  return Object.freeze({
38
- iterate(input = {}) {
39
- const { pr: _pr, ...options } = input;
40
- return runWithExecutionCwd(cwd, async () => {
41
- const target = resolvePrReference(input.pr);
42
- return runIterate({ ...options, ...target, format: "json" });
43
- });
44
- },
52
+ iterate,
45
53
  apply(input) {
46
54
  return runWithExecutionCwd(cwd, async () => {
47
55
  validateApplyInput(input);
@@ -114,6 +122,47 @@ export function createPrShepherd(options = {}) {
114
122
  },
115
123
  });
116
124
  }
125
+ async function resolveAggregateIterateInput(input) {
126
+ const refs = "prs" in input ? input.prs : [input.stack];
127
+ if (!refs || refs.length === 0) {
128
+ throw new PrShepherdValidationError("iterate.prs must contain at least one PR reference");
129
+ }
130
+ const parsedRefs = refs.map((ref) => {
131
+ const parsed = parsePrReference(ref);
132
+ const prNumber = parsed?.number;
133
+ if (!parsed || !prNumber) {
134
+ throw new PrShepherdValidationError(`Invalid PR reference: ${String(ref)}`);
135
+ }
136
+ return { parsed, prNumber };
137
+ });
138
+ const checkout = parsedRefs.some(({ parsed }) => parsed?.repository === undefined)
139
+ ? await getRepoInfo()
140
+ : undefined;
141
+ let repository;
142
+ const numbers = [];
143
+ for (const { parsed, prNumber } of parsedRefs) {
144
+ const target = resolveParsedPrTarget(parsed);
145
+ const nextRepository = target.targetRepository ?? checkout;
146
+ if (repository &&
147
+ normalizeRepositoryIdentity(`${repository.owner}/${repository.name}`) !==
148
+ normalizeRepositoryIdentity(`${nextRepository.owner}/${nextRepository.name}`)) {
149
+ throw new PrShepherdValidationError("aggregate iterate only supports PRs from one repository");
150
+ }
151
+ repository = nextRepository;
152
+ if (!numbers.includes(prNumber))
153
+ numbers.push(prNumber);
154
+ }
155
+ const { pr: _pr, prs: _prs, stack: _stack, ...options } = input;
156
+ return "prs" in input
157
+ ? { ...options, prNumbers: numbers, targetRepository: repository }
158
+ : { ...options, stackPrNumber: numbers[0], targetRepository: repository };
159
+ }
160
+ function validateIterateSelectors(input) {
161
+ const selectorCount = ["pr", "prs", "stack"].filter((key) => key in input).length;
162
+ if (selectorCount > 1) {
163
+ throw new PrShepherdValidationError("iterate pr, prs, and stack selectors are mutually exclusive");
164
+ }
165
+ }
117
166
  function validateApplyInput(input) {
118
167
  if (!input || !Array.isArray(input.operations) || input.operations.length === 0) {
119
168
  throw new PrShepherdValidationError("apply requires a non-empty operations array");
@@ -1,4 +1,5 @@
1
1
  import type { BatchPrData } from "../types.mts";
2
+ import type { ClassifyItem, ClassifyAction } from "./types.mts";
2
3
  import type { LoadedRule } from "./loader.mts";
3
4
  export interface ClassifyIndex {
4
5
  suppressedIds: Set<string>;
@@ -14,5 +15,6 @@ export interface BatchPartition {
14
15
  /** COMMENTED review summary IDs — minimized without surfacing to the agent. */
15
16
  ruleAutoResolveReviewSummaryIds: string[];
16
17
  }
18
+ export declare function applyRules(rules: LoadedRule[], item: ClassifyItem): ClassifyAction;
17
19
  export declare function buildClassifyIndex(rules: LoadedRule[], batch: BatchPrData): ClassifyIndex;
18
20
  export declare function partitionBatch(index: ClassifyIndex, batch: BatchPrData): BatchPartition;
@@ -1,4 +1,4 @@
1
- function applyRules(rules, item) {
1
+ export function applyRules(rules, item) {
2
2
  let autoResolve = false;
3
3
  let suppress = false;
4
4
  for (const { rule, name } of rules) {
package/bin/cli/args.mjs CHANGED
@@ -30,6 +30,7 @@ const BOOLEAN_FLAGS = new Set([
30
30
  "--no-auto-mark-ready",
31
31
  "--no-auto-cancel-actionable",
32
32
  "--quiet-status",
33
+ "--no-quiet-status",
33
34
  "--until-terminal",
34
35
  "--merge",
35
36
  "--dry-run",
@@ -9,12 +9,14 @@ const DEFAULT_POLL_FLAGS_WITH_VALUES = new Set([
9
9
  "--interval",
10
10
  "--timeout",
11
11
  "--debounce",
12
+ "--stack",
12
13
  ]);
13
14
  const DEFAULT_POLL_BOOLEAN_FLAGS = new Set([
14
15
  "--verbose",
15
16
  "--no-auto-mark-ready",
16
17
  "--no-auto-cancel-actionable",
17
18
  "--quiet-status",
19
+ "--no-quiet-status",
18
20
  "--until-terminal",
19
21
  "--merge",
20
22
  ]);
@@ -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 WAIT ticks and during the FIX_CODE debounce window. Print only the\nfinal tick to stdout.\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 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\nPoll flags:\n --interval <duration> Sleep between WAIT ticks. Bare number = seconds. Default: 60s.\n --timeout <duration> Maximum wall-clock wait for WAIT ticks. Bare number = seconds. Default: 4.5m.\n --debounce <duration> Settle window after first FIX_CODE before returning. Bare number = seconds. Default: 60s. 0 disables.\n --quiet-status During WAIT polling, print only changed status snapshots.\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; --quiet-status prints only changed WAIT snapshots, 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 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).";
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 WAIT ticks and during the FIX_CODE debounce window. Print only the\nfinal tick to stdout.\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 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\nPoll flags:\n --interval <duration> Sleep between WAIT ticks. Bare number = seconds. Default: 60s.\n --timeout <duration> Maximum wall-clock wait for WAIT ticks. Bare number = seconds. Default: 4.5m.\n --debounce <duration> Settle window after first FIX_CODE before returning. Bare number = seconds. Default: 60s. 0 disables.\n --quiet-status During WAIT polling, print only changed status snapshots.\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; --quiet-status prints only changed WAIT snapshots, 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 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).";
3
3
  /** Public help page for the default PR polling invocation. */
4
4
  export declare const DEFAULT_USAGE: string;
@@ -37,21 +37,25 @@ Exit codes:
37
37
  A command/validation/GitHub failure exits with a sysexits.h code instead (see docs/exit-codes.md).`;
38
38
  export const POLL_USAGE = `pr-shepherd poll
39
39
 
40
- Run iterate repeatedly for WAIT ticks and during the FIX_CODE debounce window. Print only the
41
- final tick to stdout.
40
+ Run iterate repeatedly for one PR, or read compact summaries for an explicit PR set or native
41
+ GitHub stack. Aggregate mode returns when any row needs work, every row is terminal, or timeout.
42
42
  Poll exits as soon as iterate returns MARK_READY, CANCEL, or ESCALATE, or when timeout
43
- returns the last WAIT result. FIX_CODE starts a --debounce settle window (default 1m): poll keeps
43
+ returns the last WAIT result. FIX_CODE starts a --debounce settle window (default:
44
+ poll.debounceSeconds; built-in 1m): poll keeps
44
45
  iterating at --interval, then runs one more tick after the window and returns that result.
45
46
  With --until-terminal or --merge, poll also continues through MARK_READY.
46
47
 
47
48
  Usage:
48
- pr-shepherd poll [PR] [poll-flags] [iterate-flags]
49
+ pr-shepherd poll [PR ...] [poll-flags] [iterate-flags]
50
+ pr-shepherd poll --stack PR [poll-flags] [iterate-flags]
49
51
 
50
52
  Poll flags:
51
- --interval <duration> Sleep between WAIT ticks. Bare number = seconds. Default: 60s.
52
- --timeout <duration> Maximum wall-clock wait for WAIT ticks. Bare number = seconds. Default: 4.5m.
53
- --debounce <duration> Settle window after first FIX_CODE before returning. Bare number = seconds. Default: 60s. 0 disables.
54
- --quiet-status During WAIT polling, print only changed status snapshots.
53
+ --stack PR Select all entries in PR's native GitHub stack, bottom to top.
54
+ --interval <duration> Sleep between WAIT ticks. Bare number = seconds. Default: poll.intervalSeconds (built-in 60s).
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.
57
+ --quiet-status Print only changed WAIT snapshots. Overrides poll.quietStatus.
58
+ --no-quiet-status Print every WAIT snapshot. Overrides poll.quietStatus.
55
59
  --until-terminal Continue through WAIT/MARK_READY until FIX_CODE/CANCEL/ESCALATE.
56
60
 
57
61
  Forwarded iterate flags:
@@ -67,7 +71,7 @@ Forwarded iterate flags:
67
71
  Durations accept s/m/h suffixes: 30s, 4.5m, 1h. A bare number uses each flag's default unit (seconds
68
72
  for --interval/--timeout/--debounce, minutes for --ready-delay/--stall-timeout); decimals are allowed only with
69
73
  an explicit unit (4.5m).
70
- Each WAIT tick writes an explicit still-running line to stderr by default; --quiet-status prints only changed WAIT snapshots, and --verbose emits detailed per-tick lines.
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.
71
75
  FIX_CODE debounce writes a remaining-seconds line to stderr. --timeout does not cut an in-flight debounce short.
72
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.
73
77
 
@@ -81,4 +85,6 @@ Exit codes: same as iterate (the final tick's action/reason decides the code).
81
85
  15 MERGE
82
86
  A command/validation/GitHub failure exits with a sysexits.h code instead (see docs/exit-codes.md).`;
83
87
  /** Public help page for the default PR polling invocation. */
84
- export const DEFAULT_USAGE = POLL_USAGE.replace(/^pr-shepherd poll$/m, "pr-shepherd [PR]").replace(/^ {2}pr-shepherd poll \[PR\]/m, " pr-shepherd [PR]");
88
+ export const DEFAULT_USAGE = POLL_USAGE.replace(/^pr-shepherd poll$/m, "pr-shepherd [PR]")
89
+ .replace(/^ {2}pr-shepherd poll \[PR \.\.\.\]/m, " pr-shepherd [PR ...]")
90
+ .replace(/^ {2}pr-shepherd poll --stack/m, " pr-shepherd --stack");
@@ -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 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 until non-WAIT or timeout. This is the default command.\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 such as 42 or a GitHub pull request URL.\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: 60s.\n --timeout <duration> Poll wall-clock cap for WAIT ticks. Bare number = seconds. Default: 4.5m.\n --debounce <duration> Settle window after first FIX_CODE before returning. Bare number = seconds. Default: 60s. 0 disables.\n --quiet-status During WAIT polling, print only changed status snapshots.\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 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.";
@@ -5,7 +5,8 @@ Autonomous PR CI monitor and review-comment resolver for agentic coding tools.
5
5
  Usage:
6
6
  pr-shepherd --version | -v
7
7
  pr-shepherd --help | -h
8
- pr-shepherd [PR] [poll-flags] [iterate-flags]
8
+ pr-shepherd [PR ...] [poll-flags] [iterate-flags]
9
+ pr-shepherd --stack PR [poll-flags] [iterate-flags]
9
10
  pr-shepherd iterate [PR] [iterate-flags]
10
11
  pr-shepherd apply review [PR] [review-flags]
11
12
  pr-shepherd apply files [PR] [files...] [--tests] [--match REGEX]
@@ -16,7 +17,7 @@ Usage:
16
17
  pr-shepherd admin log-file [--format text|json]
17
18
 
18
19
  Commands:
19
- [PR] Poll until non-WAIT or timeout. This is the default command.
20
+ [PR ...] Poll one PR, an explicit same-repository set, or a native stack.
20
21
  iterate Run one iterate tick (single-tick alias).
21
22
  apply review Apply review-state mutations after fixes.
22
23
  apply files Mark selected changed files as viewed.
@@ -28,7 +29,8 @@ Commands:
28
29
  admin log-file Print the per-worktree debug log path.
29
30
 
30
31
  PR argument:
31
- PR may be a number such as 42 or a GitHub pull request URL.
32
+ PR may be a number, owner/repo#number, or a GitHub pull request URL.
33
+ Multiple PRs must name one repository. --stack PR selects every entry in PR's native stack.
32
34
  When omitted, pr-shepherd infers the current branch's pull request.
33
35
 
34
36
  Common flags:
@@ -44,10 +46,11 @@ Iterate flags:
44
46
  --merge Shepherd through readiness, then emit a merge or merge-queue command.
45
47
 
46
48
  Polling flags:
47
- --interval <duration> Delay between WAIT ticks. Bare number = seconds. Default: 60s.
48
- --timeout <duration> Poll wall-clock cap for WAIT ticks. Bare number = seconds. Default: 4.5m.
49
- --debounce <duration> Settle window after first FIX_CODE before returning. Bare number = seconds. Default: 60s. 0 disables.
50
- --quiet-status During WAIT polling, print only changed status snapshots.
49
+ --interval <duration> Delay between WAIT ticks. Bare number = seconds. Default: poll.intervalSeconds (built-in 60s).
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.
52
+ --quiet-status Print only changed WAIT snapshots. Overrides poll.quietStatus.
53
+ --no-quiet-status Print every WAIT snapshot. Overrides poll.quietStatus.
51
54
  --until-terminal Continue through WAIT/MARK_READY until FIX_CODE/CANCEL/ESCALATE.
52
55
 
53
56
  Clean variants:
@@ -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 WAIT ticks and during the FIX_CODE debounce window. Print only the\nfinal tick to stdout.\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 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\nPoll flags:\n --interval <duration> Sleep between WAIT ticks. Bare number = seconds. Default: 60s.\n --timeout <duration> Maximum wall-clock wait for WAIT ticks. Bare number = seconds. Default: 4.5m.\n --debounce <duration> Settle window after first FIX_CODE before returning. Bare number = seconds. Default: 60s. 0 disables.\n --quiet-status During WAIT polling, print only changed status snapshots.\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; --quiet-status prints only changed WAIT snapshots, 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 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).";
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 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 until non-WAIT or timeout. This is the default command.\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 such as 42 or a GitHub pull request URL.\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: 60s.\n --timeout <duration> Poll wall-clock cap for WAIT ticks. Bare number = seconds. Default: 4.5m.\n --debounce <duration> Settle window after first FIX_CODE before returning. Bare number = seconds. Default: 60s. 0 disables.\n --quiet-status During WAIT polling, print only changed status snapshots.\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 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.";
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;
@@ -5,11 +5,22 @@ import { parseDurationToSeconds } from "./duration.mjs";
5
5
  import { validateSecondsDurationFlag } from "./duration-flag.mjs";
6
6
  import { parseIterateFlags } from "./iterate-flags.mjs";
7
7
  import { emitIterateResult } from "./iterate-emitter.mjs";
8
- const DEFAULT_POLL_INTERVAL_SECONDS = 60;
9
- const DEFAULT_POLL_TIMEOUT_SECONDS = 270;
10
- const DEFAULT_POLL_DEBOUNCE_SECONDS = 60;
8
+ import { EXIT } from "../exit-codes.mjs";
9
+ import { runAggregatePoll } from "../commands/poll-summary.mjs";
10
+ import { emitPollSummaryResult } from "./poll-summary-emitter.mjs";
11
+ import { parsePollTargets, resolvePollTargets } from "./poll-targets.mjs";
11
12
  export async function handlePoll(args) {
12
- const { prNumber, global: globalOpts, extra } = parseCommonArgs(args);
13
+ const parsedTargets = parsePollTargets(args);
14
+ if (!parsedTargets)
15
+ return;
16
+ const needsResolution = parsedTargets.stack !== undefined || parsedTargets.refs.length > 1;
17
+ const resolvedTargets = needsResolution
18
+ ? await resolvePollTargets(parsedTargets)
19
+ : { prNumbers: [] };
20
+ const isAggregate = resolvedTargets.stackPrNumber !== undefined || resolvedTargets.prNumbers.length > 1;
21
+ const { prNumber, global: commonGlobalOpts, extra: commonExtra } = parseCommonArgs(args);
22
+ const globalOpts = isAggregate ? parsedTargets.global : commonGlobalOpts;
23
+ const extra = isAggregate ? parsedTargets.extra : commonExtra;
13
24
  const cfg = loadConfig();
14
25
  const flags = parseIterateFlags(extra, cfg);
15
26
  if (flags.readyDelaySuffix === null || flags.stallTimeoutSuffix === null)
@@ -18,20 +29,29 @@ export async function handlePoll(args) {
18
29
  const intervalSuffix = validateSecondsDurationFlag("pr-shepherd", "--interval", intervalStr, hasFlag(extra, "--interval"));
19
30
  if (intervalSuffix === null)
20
31
  return;
21
- const intervalSeconds = parseDurationToSeconds(intervalSuffix ?? "", DEFAULT_POLL_INTERVAL_SECONDS);
32
+ const intervalSeconds = parseDurationToSeconds(intervalSuffix ?? "", cfg.poll.intervalSeconds);
22
33
  const timeoutStr = getFlag(extra, "--timeout");
23
34
  const timeoutSuffix = validateSecondsDurationFlag("pr-shepherd", "--timeout", timeoutStr, hasFlag(extra, "--timeout"));
24
35
  if (timeoutSuffix === null)
25
36
  return;
26
- const timeoutSeconds = parseDurationToSeconds(timeoutSuffix ?? "", DEFAULT_POLL_TIMEOUT_SECONDS);
37
+ const timeoutSeconds = parseDurationToSeconds(timeoutSuffix ?? "", cfg.poll.timeoutSeconds);
27
38
  const debounceStr = getFlag(extra, "--debounce");
28
39
  const debounceSuffix = validateSecondsDurationFlag("pr-shepherd", "--debounce", debounceStr, hasFlag(extra, "--debounce"), { allowZero: true });
29
40
  if (debounceSuffix === null)
30
41
  return;
31
- const debounceSeconds = parseDurationToSeconds(debounceSuffix ?? "", DEFAULT_POLL_DEBOUNCE_SECONDS, { allowZero: true });
32
- const result = await runPoll({
42
+ const debounceSeconds = parseDurationToSeconds(debounceSuffix ?? "", cfg.poll.debounceSeconds, {
43
+ allowZero: true,
44
+ });
45
+ const quietStatusFlag = hasFlag(extra, "--quiet-status");
46
+ const noQuietStatusFlag = hasFlag(extra, "--no-quiet-status");
47
+ if (quietStatusFlag && noQuietStatusFlag) {
48
+ process.stderr.write("pr-shepherd: --quiet-status and --no-quiet-status cannot be used together\n");
49
+ process.exitCode = EXIT.USAGE;
50
+ return;
51
+ }
52
+ const quietStatus = quietStatusFlag || (!noQuietStatusFlag && cfg.poll.quietStatus);
53
+ const shared = {
33
54
  ...globalOpts,
34
- prNumber,
35
55
  readyDelaySeconds: flags.readyDelaySeconds,
36
56
  stallTimeoutSeconds: flags.stallTimeoutSeconds,
37
57
  noAutoMarkReady: flags.noAutoMarkReady,
@@ -40,9 +60,18 @@ export async function handlePoll(args) {
40
60
  intervalSeconds,
41
61
  timeoutSeconds,
42
62
  debounceSeconds,
43
- quietStatus: hasFlag(extra, "--quiet-status"),
63
+ quietStatus,
44
64
  untilTerminal: hasFlag(extra, "--until-terminal"),
45
- });
65
+ };
66
+ if (isAggregate) {
67
+ const result = await runAggregatePoll({
68
+ ...shared,
69
+ ...resolvedTargets,
70
+ });
71
+ emitPollSummaryResult(result, { format: globalOpts.format });
72
+ return;
73
+ }
74
+ const result = await runPoll({ ...shared, prNumber });
46
75
  emitIterateResult(result, {
47
76
  format: globalOpts.format,
48
77
  verbose: globalOpts.verbose ?? false,
@@ -0,0 +1,4 @@
1
+ import type { PollSummaryResult } from "../types.mts";
2
+ export declare function emitPollSummaryResult(result: PollSummaryResult, opts: {
3
+ format: "text" | "json";
4
+ }): void;
@@ -0,0 +1,22 @@
1
+ import { EXIT } from "../exit-codes.mjs";
2
+ import { formatPollSummaryResult } from "./poll-summary-formatter.mjs";
3
+ export function emitPollSummaryResult(result, opts) {
4
+ process.stdout.write(opts.format === "json" ? `${JSON.stringify(result)}\n` : `${formatPollSummaryResult(result)}\n`);
5
+ process.exitCode = pollSummaryExitCode(result);
6
+ }
7
+ function pollSummaryExitCode(result) {
8
+ const actions = new Set(result.prs.map((item) => item.action));
9
+ if (actions.has("escalate"))
10
+ return EXIT.ESCALATE;
11
+ if (actions.has("fix_code"))
12
+ return EXIT.FIX_CODE;
13
+ if (actions.has("merge"))
14
+ return EXIT.MERGE;
15
+ if (actions.has("mark_ready"))
16
+ return EXIT.MARK_READY;
17
+ if (actions.has("wait"))
18
+ return EXIT.WAIT;
19
+ if (result.prs.some((item) => item.reasons.includes("closed")))
20
+ return EXIT.CLOSED;
21
+ return EXIT.OK;
22
+ }
@@ -0,0 +1,2 @@
1
+ import type { PollSummaryResult } from "../types.mts";
2
+ export declare function formatPollSummaryResult(result: PollSummaryResult): string;