pr-shepherd 0.19.0 → 0.21.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 (57) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +23 -56
  3. package/bin/checks/triage.test-support.mjs +1 -2
  4. package/bin/cli/default-poll.mjs +32 -0
  5. package/bin/cli/fix-formatter.mjs +3 -3
  6. package/bin/cli/formatters.mjs +5 -4
  7. package/bin/cli/handlers.mjs +0 -1
  8. package/bin/cli/help-command-pages.mjs +153 -0
  9. package/bin/cli/help-top-page.mjs +59 -0
  10. package/bin/cli/help.mjs +4 -30
  11. package/bin/cli/iterate-emitter.mjs +0 -1
  12. package/bin/cli/iterate-formatter.mjs +4 -6
  13. package/bin/cli/iterate-instructions.mjs +6 -8
  14. package/bin/cli/iterate-lean.mjs +4 -6
  15. package/bin/cli/iterate-lean.test-support.mjs +0 -2
  16. package/bin/cli/list-formatters.mjs +3 -0
  17. package/bin/cli/poll-handler.mjs +0 -1
  18. package/bin/cli/runner.mjs +2 -103
  19. package/bin/cli/validate-default-args.mjs +32 -0
  20. package/bin/cli-parser.clean.test-support.mjs +0 -1
  21. package/bin/cli-parser.commit-suggestion.test-support.mjs +2 -3
  22. package/bin/cli-parser.iterate-fix.test-support.mjs +1 -2
  23. package/bin/cli-parser.iterate-fixtures.mjs +1 -1
  24. package/bin/cli-parser.iterate.test-support.mjs +1 -2
  25. package/bin/cli-parser.mjs +7 -7
  26. package/bin/cli-parser.test-support.mjs +1 -2
  27. package/bin/commands/check.mjs +2 -1
  28. package/bin/commands/check.test-support.mjs +1 -2
  29. package/bin/commands/clean.test-support.mjs +0 -1
  30. package/bin/commands/commit-suggestion-instruction.mjs +2 -2
  31. package/bin/commands/commit-suggestion.apply.test-support.mjs +1 -2
  32. package/bin/commands/commit-suggestion.mjs +6 -2
  33. package/bin/commands/commit-suggestion.test-support.mjs +2 -3
  34. package/bin/commands/iterate/classify.mjs +2 -2
  35. package/bin/commands/iterate/fix-code.mjs +3 -3
  36. package/bin/commands/iterate/index.mjs +0 -1
  37. package/bin/commands/iterate/render.mjs +4 -3
  38. package/bin/commands/iterate-stall.test-support.mjs +0 -1
  39. package/bin/commands/iterate.fix-code-in-progress.test-support.mjs +2 -2
  40. package/bin/commands/poll.mjs +12 -3
  41. package/bin/commands/resolve-instructions.mjs +6 -7
  42. package/bin/commands/resolve.mjs +1 -1
  43. package/bin/commands/resolve.test-support.mjs +2 -2
  44. package/bin/commands/shepherd-journal.test-support.mjs +0 -2
  45. package/bin/comments/resolve.test-support.mjs +1 -2
  46. package/bin/comments/visible-comments.mjs +3 -2
  47. package/bin/config/load.mjs +0 -7
  48. package/bin/config.json +0 -3
  49. package/bin/github/batch-parsers.test-support.mjs +1 -2
  50. package/bin/github/batch.test-support.mjs +1 -2
  51. package/bin/github/http.test-support.mjs +1 -2
  52. package/bin/reporters/agent.mjs +1 -0
  53. package/bin/suggestions/patch.test-support.mjs +0 -2
  54. package/package.json +1 -1
  55. package/plugins/pr-shepherd/.codex-plugin/plugin.json +1 -1
  56. package/plugins/pr-shepherd/skills/pr-shepherd/SKILL.md +11 -27
  57. package/bin/cli/default-iterate.mjs +0 -50
@@ -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.19.0",
4
+ "version": "0.21.0",
5
5
  "author": {
6
6
  "name": "Jonathan Ong",
7
7
  "email": "jonathanrichardong@gmail.com"
package/README.md CHANGED
@@ -20,12 +20,12 @@ Example Workflow:
20
20
 
21
21
  `pr-shepherd` optimizes token management, rate limits, and agentic orchestration by moving **ALL** deterministic logic and prompts to code via a CLI tool, enshrining what would be a large skill or command prompt (of which the agent would inevitably make mistakes) into the code and returning a clear, actionable prompt.
22
22
 
23
- The CLI emits unified recheck instructions. Generated commands use `cli.runner` from `.pr-shepherdrc.yml`: `auto` (default), `npx`, `pnpm`, `yarn`, or `bun`.
23
+ The CLI emits unified recheck instructions. Generated commands call `pr-shepherd` directly.
24
24
 
25
- At a high level, the skill invokes `pr-shepherd <PR>` through the selected package runner, which provides actionable feedback directly to the agent:
25
+ At a high level, the skill invokes `pr-shepherd <PR>` (which polls by default), providing actionable feedback directly to the agent:
26
26
 
27
- ```
28
- > npx pr-shepherd 123
27
+ ```text
28
+ > pr-shepherd 123
29
29
 
30
30
  # PR #123 [FIX_CODE]
31
31
 
@@ -44,12 +44,12 @@ At a high level, the skill invokes `pr-shepherd <PR>` through the selected packa
44
44
  ## Failing checks
45
45
 
46
46
  - `24697658766` — `CI › lint / typecheck / test (22.x)`
47
- > npx oxfmt
47
+ > oxfmt
48
48
 
49
49
  ## Post-fix push
50
50
 
51
51
  - base: `main`
52
- - resolve: `npx pr-shepherd resolve 123 --resolve-thread-ids PRRT_kwDOSGizTs58XB1L --minimize-comment-ids IC_kwDOSGizTs7_ajT8,IC_kwDOSGizTs7_ajT9 --dismiss-review-ids PRR_kwDOSGizTs58XB1R --message "$DISMISS_MESSAGE" --require-sha "$HEAD_SHA"`
52
+ - resolve: `pr-shepherd resolve 123 --resolve-thread-ids PRRT_kwDOSGizTs58XB1L --minimize-comment-ids IC_kwDOSGizTs7_ajT8,IC_kwDOSGizTs7_ajT9 --dismiss-review-ids PRR_kwDOSGizTs58XB1R --message "$DISMISS_MESSAGE" --require-sha "$HEAD_SHA"`
53
53
 
54
54
  ## Instructions
55
55
 
@@ -61,13 +61,13 @@ _(schematic — actual steps depend on PR state)_
61
61
  4. Rebase and push: `git fetch origin && git rebase origin/main && git push --force-with-lease` — capture `HEAD_SHA=$(git rev-parse HEAD)`.
62
62
  5. Run the `resolve:` command above, substituting `"$HEAD_SHA"`.
63
63
  6. Add or update a `## Shepherd Journal` section in the PR description for any large decisions made, appending under the existing heading if it already exists.
64
- 7. CI needs time to run on the new push. Schedule one session-only follow-up task to run `npx pr-shepherd 123` to recheck once after a fresh delay between 30 seconds and 4 minutes, then end this turn. Do not sleep or rerun inline.
64
+ 7. CI needs time to run on the new push. Run `pr-shepherd 123` again to continue until Shepherd returns `[CANCEL]` or `[ESCALATE]`.
65
65
  ```
66
66
 
67
67
  On every iteration, a command is returned to instruct the agent exactly what to do. No guessing, no thinking, as few agentic turns as possible:
68
68
 
69
- ```
70
- npx pr-shepherd resolve 123 --resolve-thread-ids PRRT_kwDOSGizTs58XB1L --minimize-comment-ids IC_kwDOSGizTs7_ajT8,IC_kwDOSGizTs7_ajT9 --dismiss-review-ids PRR_kwDOSGizTs58XB1R --message "$DISMISS_MESSAGE" --require-sha "$HEAD_SHA"
69
+ ```text
70
+ pr-shepherd resolve 123 --resolve-thread-ids PRRT_kwDOSGizTs58XB1L --minimize-comment-ids IC_kwDOSGizTs7_ajT8,IC_kwDOSGizTs7_ajT9 --dismiss-review-ids PRR_kwDOSGizTs58XB1R --message "$DISMISS_MESSAGE" --require-sha "$HEAD_SHA"
71
71
  ```
72
72
 
73
73
  ## Workflow
@@ -97,7 +97,7 @@ Some other workflow improvements:
97
97
 
98
98
  Recommendations:
99
99
 
100
- - Run `pr-shepherd` on all your PRs before you go to sleep so that you wake up to reviewable PRs. Keep an active goal cycling the reusable command until Shepherd emits `[CANCEL]` for ready-delay completion or merged/closed, or `[ESCALATE]` (including `stall-timeout` for repeated unchanged CI failures).
100
+ - Run `pr-shepherd` on all your PRs before you go to sleep so that you wake up to reviewable PRs. Keep an active goal cycling `pr-shepherd <PR>` until Shepherd emits `[CANCEL]` for ready-delay completion or merged/closed, or `[ESCALATE]` (including `stall-timeout` for repeated unchanged CI failures).
101
101
  - Instruct your agents to write comments in a single review (comment, changes requested, or approved). This allows the review's comments/threads to be minimized or resolved together, keeping your pull request history clean. If you write inline comments outside of a review, each comment would still show up in the pull request history and take up space.
102
102
  - Avoid sticky comments as they will continue to be hidden. Instead, just make a new comment, especially on reviews. If you really want sticky comments, instruct your agent to unhide/unminimize them when updating them.
103
103
  - Avoid having automation edit comments, reviews, or threads in place because updated items get minimized. Instead, always make a new review, comment, thread, etc.
@@ -114,7 +114,7 @@ Recommendations:
114
114
 
115
115
  ### Iterate a PR to completion
116
116
 
117
- One-tick dispatcher — checks CI and review comments, fixes issues, and marks the PR ready for review when clean. Each non-terminal tick emits an `## Instructions` section telling the agent how to run the next tick; the loop continues until `[CANCEL]` or `[ESCALATE]`.
117
+ Poll dispatcher — checks CI and review comments, fixes issues, and marks the PR ready for review when clean. Each non-terminal tick emits an `## Instructions` section; the skill follows it, then invokes `pr-shepherd <PR>` again until `[CANCEL]` or `[ESCALATE]`.
118
118
 
119
119
  Claude Code (via `pr-shepherd` skill):
120
120
 
@@ -127,9 +127,10 @@ Claude Code (via `pr-shepherd` skill):
127
127
  Codex or direct CLI:
128
128
 
129
129
  ```sh
130
- npx pr-shepherd 42
131
- npx pr-shepherd 42 --ready-delay 15m
132
- npx pr-shepherd iterate 42 # legacy-compatible spelling
130
+ pr-shepherd 42
131
+ pr-shepherd 42 --interval 45s --timeout 4m
132
+ pr-shepherd 42 --ready-delay 15m
133
+ pr-shepherd iterate 42 # single tick
133
134
  ```
134
135
 
135
136
  ## Iterate decision loop
@@ -141,25 +142,16 @@ On each tick: fetch PR state in one GraphQL batch → classify CI, comments, and
141
142
  `pr-shepherd` accumulates state under `$PR_SHEPHERD_STATE_DIR` (seen markers, fix-attempt counters, stall fingerprints, etc.). To reset it:
142
143
 
143
144
  ```sh
144
- npx pr-shepherd clean current # remove state for the current branch's PR
145
- npx pr-shepherd clean repo # remove all state for this repo
146
- npx pr-shepherd clean all # remove all pr-shepherd state
145
+ pr-shepherd clean current # remove state for the current branch's PR
146
+ pr-shepherd clean repo # remove all state for this repo
147
+ pr-shepherd clean all # remove all pr-shepherd state
147
148
  ```
148
149
 
149
150
  Add `--dry-run` to preview what would be removed. See [docs/cli-usage.md](docs/cli-usage.md) for the full `clean` reference.
150
151
 
151
152
  ## Install
152
153
 
153
- > **Note:** Skill and plugin install methods add the skill definitions only — they do not install the `pr-shepherd` CLI. The skills invoke `pr-shepherd` through the repo package runner, so you also need the CLI available. If you're using `pr-shepherd` as development tooling for your repo, install it as a dev dependency so the selected runner resolves it without prompting:
154
- >
155
- > ```bash
156
- > pnpm add -D pr-shepherd # pnpm repos
157
- > yarn add -D pr-shepherd # yarn repos
158
- > bun add -d pr-shepherd # bun repos
159
- > npm install --save-dev pr-shepherd
160
- > ```
161
- >
162
- > A plain `npm install pr-shepherd` adds it to regular dependencies instead; use that only if you specifically want it under `dependencies`. Or install globally: `npm install -g pr-shepherd`.
154
+ > **Note:** Skill and plugin install methods add the skill definitions only — they do not install the `pr-shepherd` CLI. Ensure `pr-shepherd` is available on `PATH` wherever the skill runs.
163
155
 
164
156
  ### Claude Code
165
157
 
@@ -170,15 +162,7 @@ claude /plugin marketplace add jonathanong/pr-shepherd
170
162
  claude /plugin install pr-shepherd
171
163
  ```
172
164
 
173
- This repo ships two `marketplace.json` files that serve different Claude install flows: the root `marketplace.json` resolves the plugin from the npm registry (used by the `claude /plugin marketplace add` command above); `.claude-plugin/marketplace.json` is the owner-level registry manifest that resolves the plugin from the local plugin directory (used when Claude Code installs from a local or git-based source). Both files are needed to support these two install paths.
174
-
175
- Alternatively, install the Claude skills individually via `npx skills`:
176
-
177
- ```bash
178
- npx skills add jonathanong/pr-shepherd
179
- ```
180
-
181
- Installs the `pr-shepherd` skill into your agent's skill directory (`.claude/skills/` for project scope, `~/.claude/skills/` with `-g` for global scope). Powered by [skills.sh](https://skills.sh).
165
+ This repo ships two `marketplace.json` files that serve different Claude install flows: the root `marketplace.json` resolves the published plugin (used by the `claude /plugin marketplace add` command above); `.claude-plugin/marketplace.json` is the owner-level registry manifest that resolves the plugin from the local plugin directory (used when Claude Code installs from a local or git-based source). Both files are needed to support these two install paths.
182
166
 
183
167
  ### Codex
184
168
 
@@ -205,33 +189,16 @@ codex plugin marketplace add ~/.codex/plugin-sources/pr-shepherd
205
189
 
206
190
  After adding the marketplace, open the Codex plugin directory, choose the `jonathanong` marketplace, and install/enable `pr-shepherd`. The marketplace root must contain `.agents/plugins/marketplace.json` and `plugins/pr-shepherd/`.
207
191
 
208
- Install the CLI where Codex will run it:
192
+ Install the CLI where Codex will run it so `pr-shepherd` is available on `PATH`. The plugin only installs the skill; it does not install the CLI into target repositories.
209
193
 
210
- ```bash
211
- pnpm add -D pr-shepherd # pnpm repos
212
- yarn add -D pr-shepherd # yarn repos
213
- bun add -d pr-shepherd # bun repos
214
- npm install --save-dev pr-shepherd
215
- ```
216
-
217
- The plugin only installs the skill; it does not install the CLI into target repositories. To install the CLI globally instead, use `npm install -g pr-shepherd`.
218
-
219
- Iterate a PR from Codex with the target repository's package runner:
194
+ Iterate a PR from Codex:
220
195
 
221
196
  ```bash
222
- <runner> pr-shepherd iterate 42
197
+ pr-shepherd iterate 42
223
198
  ```
224
199
 
225
- For example, a repo like `~/filaments` that declares `packageManager: "pnpm@..."` and has `pnpm-lock.yaml` should use `pnpm exec pr-shepherd iterate 42`. For Bun repos (with `bun.lock` or `bun.lockb`), use `bunx pr-shepherd iterate 42`. For npm repos, use `npx pr-shepherd iterate 42`.
226
-
227
200
  Or ask Codex to use the `pr-shepherd` skill, for example: `run pr-shepherd until this PR is ready`. Follow the output's `## Instructions`. Continue until Shepherd emits `[CANCEL]` or `[ESCALATE]` (including `stall-timeout` for repeated unchanged CI failures). `pr-shepherd iterate 42` remains supported for existing workflows.
228
201
 
229
- ### As a global CLI
230
-
231
- ```bash
232
- npm install -g pr-shepherd
233
- ```
234
-
235
202
  ## Configuration
236
203
 
237
204
  Create a `.pr-shepherdrc.yml` in your project root (or any parent directory) to override defaults. The loader walks up from `cwd` to `$HOME` (if `$HOME` is on that path) or the filesystem root; the first match wins.
@@ -1,5 +1,4 @@
1
- // @ts-nocheck
2
- import { describe, it, expect, vi, beforeEach } from "vitest";
1
+ import { vi, beforeEach } from "vitest";
3
2
  // ---------------------------------------------------------------------------
4
3
  // Stub fetch globally so http.mts uses our mock.
5
4
  // ---------------------------------------------------------------------------
@@ -0,0 +1,32 @@
1
+ import { parsePrNumber } from "./args.mjs";
2
+ import { validateDefaultArgs } from "./validate-default-args.mjs";
3
+ import { USAGE } from "./help.mjs";
4
+ const DEFAULT_POLL_FLAGS_WITH_VALUES = new Set([
5
+ "--format",
6
+ "--ready-delay",
7
+ "--stall-timeout",
8
+ "--interval",
9
+ "--timeout",
10
+ ]);
11
+ const DEFAULT_POLL_BOOLEAN_FLAGS = new Set([
12
+ "--verbose",
13
+ "--no-auto-mark-ready",
14
+ "--no-auto-cancel-actionable",
15
+ ]);
16
+ export function isDefaultPollInvocation(subcommand) {
17
+ if (subcommand === "--help" || subcommand === "-h")
18
+ return false;
19
+ return (subcommand === undefined || parsePrNumber(subcommand) !== null || isDefaultPollFlag(subcommand));
20
+ }
21
+ export function validateDefaultPollArgs(args) {
22
+ return validateDefaultArgs(args, DEFAULT_POLL_FLAGS_WITH_VALUES, DEFAULT_POLL_BOOLEAN_FLAGS, writeDefaultUsageError);
23
+ }
24
+ function isDefaultPollFlag(arg) {
25
+ const name = arg.split("=", 1)[0];
26
+ return DEFAULT_POLL_FLAGS_WITH_VALUES.has(name) || DEFAULT_POLL_BOOLEAN_FLAGS.has(arg);
27
+ }
28
+ function writeDefaultUsageError(arg) {
29
+ process.stderr.write(`Unknown subcommand: ${arg}\n`);
30
+ process.stderr.write(`${USAGE.top}\n`);
31
+ process.exitCode = 1;
32
+ }
@@ -5,7 +5,6 @@ import { renderThreadBullet, renderReviewBullet, renderThreadResolutionStatusTag
5
5
  import { adaptFixCodeInstructions, numberInstructions } from "./iterate-instructions.mjs";
6
6
  export function formatFixCodeResult(header, result, opts) {
7
7
  const readyDelaySuffix = opts?.readyDelaySuffix;
8
- const runner = opts?.runner;
9
8
  const sections = [header];
10
9
  if (result.fix.threads.length > 0) {
11
10
  sections.push("## Review threads");
@@ -31,7 +30,8 @@ export function formatFixCodeResult(header, result, opts) {
31
30
  sections.push("## Actionable comments");
32
31
  for (const c of result.fix.actionableComments) {
33
32
  const heading = c.url ? `[commentId=${c.id}](${c.url})` : `\`commentId=${c.id}\``;
34
- sections.push(`### ${heading} (${renderAuthor(c.author, c.authorType)})`);
33
+ const editedMarker = c.edited ? " [edited since first look]" : "";
34
+ sections.push(`### ${heading} (${renderAuthor(c.author, c.authorType)})${editedMarker}`);
35
35
  sections.push(blockquote(c.body));
36
36
  }
37
37
  }
@@ -109,7 +109,7 @@ export function formatFixCodeResult(header, result, opts) {
109
109
  }
110
110
  sections.push(postFixLines.join("\n"));
111
111
  sections.push("## Instructions");
112
- sections.push(numberInstructions(adaptFixCodeInstructions(result.fix.instructions, result.pr, readyDelaySuffix, runner)));
112
+ sections.push(numberInstructions(adaptFixCodeInstructions(result.fix.instructions, result.pr, readyDelaySuffix)));
113
113
  return joinSections(sections);
114
114
  }
115
115
  function blockquote(body) {
@@ -2,7 +2,7 @@ export { formatIterateResult } from "./iterate-formatter.mjs";
2
2
  export { projectIterateLean, projectIterateVerbose } from "./iterate-lean.mjs";
3
3
  export { formatCleanResult } from "./clean-formatter.mjs";
4
4
  import { safeFence } from "./fence.mjs";
5
- import { renderThreadBullet, renderCommentBullet, renderReviewBullet, renderThreadResolutionStatusTag, buildFirstLookBullets, } from "./list-formatters.mjs";
5
+ import { renderThreadBullet, renderCommentBullet, renderReviewBullet, renderThreadResolutionStatusTag, buildFirstLookBullets, renderEditedCommentTag, } from "./list-formatters.mjs";
6
6
  import { joinSections } from "../util/markdown.mjs";
7
7
  export function formatFetchResult(result) {
8
8
  const activeTotal = result.actionableThreads.length +
@@ -18,8 +18,7 @@ export function formatFetchResult(result) {
18
18
  if (firstLookTotal > 0)
19
19
  headingParts.push(`${firstLookTotal} first-look`);
20
20
  const headingSuffix = headingParts.length > 0 ? headingParts.join(", ") : "0 actionable";
21
- const sections = [];
22
- sections.push(`# PR #${result.prNumber} — Resolve fetch (${headingSuffix})`);
21
+ const sections = [`# PR #${result.prNumber} — Resolve fetch (${headingSuffix})`];
23
22
  if (result.actionableThreads.length > 0) {
24
23
  sections.push(`## Actionable Review Threads (${result.actionableThreads.length})` +
25
24
  (result.commitSuggestionsEnabled ? " [commit-suggestions: enabled]" : ""));
@@ -35,7 +34,9 @@ export function formatFetchResult(result) {
35
34
  }
36
35
  if (result.actionableComments.length > 0) {
37
36
  sections.push(`## Actionable PR Comments (${result.actionableComments.length})`);
38
- sections.push(result.actionableComments.map((c) => renderCommentBullet(c)).join("\n"));
37
+ sections.push(result.actionableComments
38
+ .map((c) => renderCommentBullet(c, { statusTag: renderEditedCommentTag(c) }))
39
+ .join("\n"));
39
40
  }
40
41
  if (result.changesRequestedReviews.length > 0) {
41
42
  sections.push(`## Pending CHANGES_REQUESTED reviews (${result.changesRequestedReviews.length})`);
@@ -106,6 +106,5 @@ export async function handleIterate(args) {
106
106
  format: globalOpts.format,
107
107
  verbose: globalOpts.verbose ?? false,
108
108
  readyDelaySuffix: flags.readyDelaySuffix ?? undefined,
109
- runner: cfg.cli?.runner,
110
109
  });
111
110
  }
@@ -0,0 +1,153 @@
1
+ export const COMMAND_USAGE = {
2
+ resolve: `pr-shepherd resolve
3
+
4
+ Fetch actionable review items or apply GitHub review-state mutations after fixes.
5
+
6
+ Usage:
7
+ pr-shepherd resolve [PR] [--fetch] [--format text|json]
8
+ pr-shepherd resolve [PR] --resolve-thread-ids A,B [--minimize-comment-ids X,Y]
9
+ [--dismiss-review-ids Q] [--message MSG]
10
+ [--require-sha SHA] [--format text|json]
11
+
12
+ Modes:
13
+ fetch Default when no mutation IDs are provided. Fetches review threads,
14
+ comments, first-look items, and instructions.
15
+ mutate Runs when any mutation ID flag is present. Resolves threads,
16
+ minimizes comments/review summaries, and dismisses reviews.
17
+
18
+ Flags:
19
+ --fetch Force fetch mode.
20
+ --resolve-thread-ids <ids> Comma-separated review thread IDs to resolve.
21
+ --minimize-comment-ids <ids> Comma-separated issue/review comment IDs to minimize.
22
+ --dismiss-review-ids <ids> Comma-separated CHANGES_REQUESTED review IDs to dismiss.
23
+ --message <text> Dismiss message. Required with --dismiss-review-ids.
24
+ --require-sha <sha> Wait until GitHub reports this PR head SHA before mutating.
25
+ --format text|json Output format. Default: text.
26
+ --help, -h Print this help and exit before GitHub I/O.
27
+
28
+ PR may be a number or GitHub pull request URL. When omitted, the current branch PR is inferred.
29
+ Exit code: 0 on success; 1 on validation, lookup, or mutation failure.`,
30
+ "commit-suggestion": `pr-shepherd commit-suggestion
31
+
32
+ Build a patch and commit instructions for one GitHub review thread containing a suggestion block.
33
+ The command does not edit files or mutate git history.
34
+
35
+ Usage:
36
+ pr-shepherd commit-suggestion [PR] --thread-id ID --message MSG
37
+ [--description DESC] [--format text|json]
38
+
39
+ Flags:
40
+ --thread-id <id> Review thread ID containing exactly one suggestion to apply. Required.
41
+ --message <text> Suggested commit subject. Required and must be non-empty.
42
+ --description <text> Optional longer commit body.
43
+ --format text|json Output format. Default: text.
44
+ --help, -h Print this help and exit before GitHub, git, config, or log I/O.
45
+
46
+ Preconditions:
47
+ The current branch must match the PR head ref, and local HEAD must match the PR head SHA.
48
+
49
+ Exit codes:
50
+ 0 suggestion patch and instructions produced
51
+ 1 validation, lookup, precondition, or suggestion parsing failure`,
52
+ iterate: `pr-shepherd iterate
53
+
54
+ Run one iterate tick for a pull request. The no-subcommand form polls; use this subcommand for a single tick.
55
+ The output contains one action and an action-specific ## Instructions section.
56
+
57
+ Usage:
58
+ pr-shepherd iterate [PR] [iterate-flags]
59
+
60
+ Iterate flags:
61
+ --ready-delay <duration> Settle window before a clean PR cancels. Example: 15m.
62
+ --stall-timeout <duration> Escalate repeated unchanged failures after this duration.
63
+ --no-auto-mark-ready Do not convert draft PRs to ready for review.
64
+ --no-auto-cancel-actionable Do not cancel in-progress runs before actionable fixes.
65
+ --format text|json Output Markdown text or JSON. Default: text.
66
+ --verbose Include verbose iterate fields.
67
+ --help, -h Print this help and exit before GitHub, git, config, or log I/O.
68
+
69
+ Actions:
70
+ WAIT No immediate code action; recheck later or use pr-shepherd poll.
71
+ MARK_READY Draft PR was marked ready for review.
72
+ FIX_CODE Apply fixes, commit, push, and run the printed resolve command.
73
+ CANCEL Terminal state: merged/closed or ready-delay elapsed.
74
+ ESCALATE Terminal state requiring human direction.
75
+
76
+ Exit codes:
77
+ 0 WAIT or MARK_READY
78
+ 1 FIX_CODE, or a command/validation error
79
+ 2 CANCEL
80
+ 3 ESCALATE`,
81
+ poll: `pr-shepherd poll
82
+
83
+ Run iterate repeatedly while the action is WAIT. Print only the final tick to stdout.
84
+ Poll exits as soon as iterate returns MARK_READY, FIX_CODE, CANCEL, or ESCALATE, or when timeout
85
+ returns the last WAIT result.
86
+
87
+ Usage:
88
+ pr-shepherd poll [PR] [poll-flags] [iterate-flags]
89
+
90
+ Poll flags:
91
+ --interval <duration> Sleep between WAIT ticks. Default: 30s.
92
+ --timeout <duration> Maximum wall-clock wait. Default: 5m.
93
+
94
+ Forwarded iterate flags:
95
+ --ready-delay <duration> Settle window before a clean PR cancels. Example: 15m.
96
+ --stall-timeout <duration> Escalate repeated unchanged failures after this duration.
97
+ --no-auto-mark-ready Do not convert draft PRs to ready for review.
98
+ --no-auto-cancel-actionable Do not cancel in-progress runs before actionable fixes.
99
+ --format text|json Output Markdown text or JSON. Default: text.
100
+ --verbose Include verbose iterate fields and detailed per-tick lines.
101
+ --help, -h Print this help and exit before GitHub, git, config, or log I/O.
102
+
103
+ Durations accept seconds, minutes, or hours: 30s, 2m, 1h, or bare seconds.
104
+ Each WAIT tick writes a single dot to stderr; --verbose emits the detailed per-tick line.
105
+
106
+ Exit codes:
107
+ 0 WAIT timeout or MARK_READY
108
+ 1 FIX_CODE, or a command/validation error
109
+ 2 CANCEL
110
+ 3 ESCALATE`,
111
+ clean: `pr-shepherd clean
112
+
113
+ Remove pr-shepherd state files from PR_SHEPHERD_STATE_DIR.
114
+
115
+ Usage:
116
+ pr-shepherd clean pr [number] [--dry-run] [--format text|json]
117
+ pr-shepherd clean branch [name] [--dry-run] [--format text|json]
118
+ pr-shepherd clean current [--dry-run] [--format text|json]
119
+ pr-shepherd clean repo [--dry-run] [--format text|json]
120
+ pr-shepherd clean all [--dry-run] [--format text|json]
121
+
122
+ Variants:
123
+ pr [number] Remove state for one PR. Defaults to current branch PR.
124
+ branch [name] Resolve a branch to its open PR, then remove that PR's state.
125
+ Defaults to current branch.
126
+ current Alias for branch against the current branch.
127
+ repo Remove all state for the current repository, including worktree logs.
128
+ all Remove all pr-shepherd state.
129
+
130
+ Flags:
131
+ --dry-run Preview paths without removing them.
132
+ --format text|json Output format. Default: text.
133
+ --help, -h Print this help and exit before any cleanup.
134
+
135
+ Exit code: 0 on success; 1 on validation or cleanup failure.`,
136
+ "log-file": `pr-shepherd log-file
137
+
138
+ Print the per-worktree append-only debug log path for the current repository.
139
+ The log is created by the first non-help pr-shepherd command that initializes logging.
140
+
141
+ Usage:
142
+ pr-shepherd log-file [--format text|json]
143
+
144
+ Flags:
145
+ --format text|json Print a raw path or {"path": "..."} JSON. Default: text.
146
+ --help, -h Print this help and exit before logging setup.
147
+
148
+ Environment:
149
+ PR_SHEPHERD_LOG_DISABLED=1 disables logging.
150
+ PR_SHEPHERD_STATE_DIR overrides the base state directory.
151
+
152
+ Exit code: 0 on success; 1 if repository identity cannot be resolved.`,
153
+ };
@@ -0,0 +1,59 @@
1
+ export const TOP_USAGE = `pr-shepherd
2
+
3
+ Autonomous PR CI monitor and review-comment resolver for agentic coding tools.
4
+
5
+ Usage:
6
+ pr-shepherd --version | -v
7
+ pr-shepherd --help | -h
8
+ pr-shepherd [PR] [poll-flags] [iterate-flags]
9
+ pr-shepherd iterate [PR] [iterate-flags]
10
+ pr-shepherd poll [PR] [poll-flags] [iterate-flags]
11
+ pr-shepherd resolve [PR] [resolve-flags]
12
+ pr-shepherd commit-suggestion [PR] --thread-id ID --message MSG [flags]
13
+ pr-shepherd clean <pr|branch|current|repo|all> [value] [flags]
14
+ pr-shepherd log-file [--format text|json]
15
+
16
+ Commands:
17
+ [PR] Poll until non-WAIT or timeout. This is the default command.
18
+ iterate Run one iterate tick (single-tick alias).
19
+ poll Re-run iterate while the action is WAIT, then print the final tick.
20
+ resolve Fetch actionable review items, or resolve/minimize/dismiss IDs.
21
+ commit-suggestion Convert one GitHub suggestion thread into a patch and commit instructions.
22
+ clean Remove pr-shepherd state files.
23
+ log-file Print the per-worktree debug log path.
24
+
25
+ PR argument:
26
+ PR may be a number such as 42 or a GitHub pull request URL.
27
+ When omitted, pr-shepherd infers the current branch's pull request.
28
+
29
+ Common flags:
30
+ --format text|json Output Markdown text or JSON. Default: text.
31
+ --verbose Include verbose iterate fields and detailed poll-tick lines.
32
+ --help, -h Print help and exit before any GitHub, git, config, or log I/O.
33
+
34
+ Iterate flags:
35
+ --ready-delay <duration> Settle window before a clean PR cancels. Example: 15m.
36
+ --stall-timeout <duration> Escalate repeated unchanged failures after this duration.
37
+ --no-auto-mark-ready Do not convert draft PRs to ready for review.
38
+ --no-auto-cancel-actionable Do not cancel in-progress runs before actionable fixes.
39
+
40
+ Poll flags:
41
+ --interval <duration> Sleep between WAIT ticks. Default: 30s.
42
+ --timeout <duration> Maximum poll wall-clock wait. Default: 5m.
43
+
44
+ Clean variants:
45
+ pr [number] Remove state for one PR. Defaults to current branch PR.
46
+ branch [name] Remove state for a branch's PR. Defaults to current branch.
47
+ current Alias for branch against the current branch.
48
+ repo Remove all state for the current repository.
49
+ all Remove all pr-shepherd state.
50
+
51
+ Exit codes for iterate and poll:
52
+ 0 WAIT or MARK_READY
53
+ 1 FIX_CODE, or a command/validation error
54
+ 2 CANCEL
55
+ 3 ESCALATE
56
+
57
+ Durations accept seconds, minutes, or hours: 30s, 2m, 1h, or bare seconds.
58
+
59
+ Run 'pr-shepherd <command> --help' for command-specific details.`;
package/bin/cli/help.mjs CHANGED
@@ -1,35 +1,9 @@
1
1
  import { hasFlag } from "./args.mjs";
2
+ import { COMMAND_USAGE } from "./help-command-pages.mjs";
3
+ import { TOP_USAGE } from "./help-top-page.mjs";
2
4
  export const USAGE = {
3
- top: "Usage:\n" +
4
- " pr-shepherd --version | -v\n" +
5
- " pr-shepherd [PR] [--format text|json] [--ready-delay Nm]\n" +
6
- " [--stall-timeout <duration>] [--no-auto-mark-ready]\n" +
7
- " [--no-auto-cancel-actionable]\n" +
8
- " pr-shepherd resolve [PR] [--fetch] [--resolve-thread-ids A,B] [--minimize-comment-ids X,Y]\n" +
9
- " [--dismiss-review-ids Q] [--message MSG] [--require-sha SHA]\n" +
10
- " pr-shepherd commit-suggestion [PR] --thread-id ID --message MSG [--description DESC]\n" +
11
- " [--format text|json]\n" +
12
- " pr-shepherd iterate [PR] [--format text|json] [--ready-delay Nm]\n" +
13
- " [--stall-timeout <duration>] [--no-auto-mark-ready]\n" +
14
- " [--no-auto-cancel-actionable]\n" +
15
- " pr-shepherd poll [PR] [--interval 30s] [--timeout 5m] [--format text|json] [--ready-delay Nm]\n" +
16
- " [--stall-timeout <duration>] [--no-auto-mark-ready]\n" +
17
- " [--no-auto-cancel-actionable]\n" +
18
- " pr-shepherd clean <pr|branch|current|repo|all> [value] [--dry-run] [--format text|json]\n" +
19
- " pr-shepherd log-file [--format text|json]",
20
- resolve: "Usage: pr-shepherd resolve [PR] [--fetch] [--resolve-thread-ids A,B]\n" +
21
- " [--minimize-comment-ids X,Y] [--dismiss-review-ids Q]\n" +
22
- " [--message MSG] [--require-sha SHA]",
23
- "commit-suggestion": "Usage: pr-shepherd commit-suggestion [PR] --thread-id ID --message MSG\n" +
24
- " [--description DESC] [--format text|json]",
25
- iterate: "Usage: pr-shepherd iterate [PR] [--format text|json] [--ready-delay Nm]\n" +
26
- " [--stall-timeout <duration>] [--no-auto-mark-ready]\n" +
27
- " [--no-auto-cancel-actionable]",
28
- poll: "Usage: pr-shepherd poll [PR] [--interval 30s] [--timeout 5m] [--format text|json]\n" +
29
- " [--ready-delay Nm] [--stall-timeout <duration>]\n" +
30
- " [--no-auto-mark-ready] [--no-auto-cancel-actionable]",
31
- clean: "Usage: pr-shepherd clean <pr|branch|current|repo|all> [value] [--dry-run] [--format text|json]",
32
- "log-file": "Usage: pr-shepherd log-file [--format text|json]",
5
+ top: TOP_USAGE,
6
+ ...COMMAND_USAGE,
33
7
  };
34
8
  /** Prints usage for `key` to stdout and returns true if `--help` or `-h` is in args. */
35
9
  export function maybePrintHelp(args, key) {
@@ -3,7 +3,6 @@ import { formatIterateResult, projectIterateLean, projectIterateVerbose } from "
3
3
  export function emitIterateResult(result, opts) {
4
4
  const projectionOpts = {
5
5
  readyDelaySuffix: opts.readyDelaySuffix,
6
- runner: opts.runner,
7
6
  };
8
7
  if (opts.format === "json") {
9
8
  const output = opts.verbose
@@ -18,7 +18,6 @@ import { adaptIterateLog, buildSimpleIterateInstructions, numberInstructions, }
18
18
  export function formatIterateResult(result, opts) {
19
19
  const verbose = opts?.verbose ?? false;
20
20
  const readyDelaySuffix = opts?.readyDelaySuffix;
21
- const runner = opts?.runner;
22
21
  const heading = `# PR #${result.pr} [${result.action.toUpperCase()}]`;
23
22
  const reviewDecisionSeg = result.mergeStatus === "BLOCKED" && result.reviewDecision
24
23
  ? ` · **reviewDecision** \`${result.reviewDecision}\``
@@ -87,13 +86,13 @@ export function formatIterateResult(result, opts) {
87
86
  return joinSections([
88
87
  header,
89
88
  adaptIterateLog(result.log),
90
- `## Instructions\n\n${numberInstructions(buildSimpleIterateInstructions(result, readyDelaySuffix, runner))}`,
89
+ `## Instructions\n\n${numberInstructions(buildSimpleIterateInstructions(result, readyDelaySuffix))}`,
91
90
  ]);
92
91
  case "mark_ready":
93
92
  return joinSections([
94
93
  header,
95
94
  adaptIterateLog(result.log),
96
- `## Instructions\n\n${numberInstructions(buildSimpleIterateInstructions(result, readyDelaySuffix, runner))}`,
95
+ `## Instructions\n\n${numberInstructions(buildSimpleIterateInstructions(result, readyDelaySuffix))}`,
97
96
  ]);
98
97
  case "cancel": {
99
98
  const cancelHeaderLines = [`${heading} — ${result.reason}`, "", baseLine, summaryLine];
@@ -102,19 +101,18 @@ export function formatIterateResult(result, opts) {
102
101
  return joinSections([
103
102
  cancelHeaderLines.join("\n"),
104
103
  adaptIterateLog(result.log),
105
- `## Instructions\n\n${numberInstructions(buildSimpleIterateInstructions(result, readyDelaySuffix, runner))}`,
104
+ `## Instructions\n\n${numberInstructions(buildSimpleIterateInstructions(result, readyDelaySuffix))}`,
106
105
  ]);
107
106
  }
108
107
  case "escalate":
109
108
  return joinSections([
110
109
  header,
111
110
  result.escalate.humanMessage,
112
- `## Instructions\n\n${numberInstructions(buildSimpleIterateInstructions(result, readyDelaySuffix, runner))}`,
111
+ `## Instructions\n\n${numberInstructions(buildSimpleIterateInstructions(result, readyDelaySuffix))}`,
113
112
  ]);
114
113
  case "fix_code":
115
114
  return formatFixCodeResult(header, result, {
116
115
  readyDelaySuffix,
117
- runner,
118
116
  });
119
117
  }
120
118
  }
@@ -3,8 +3,8 @@ import { buildPrShepherdCommand } from "./runner.mjs";
3
3
  function buildRecheckInstruction(rerunCommand, purpose) {
4
4
  return `Recheck: rerun \`${rerunCommand}\` to ${purpose} once after a fresh 30s–4m delay.`;
5
5
  }
6
- export function buildSimpleIterateInstructions(result, readyDelaySuffix, runner) {
7
- const rerunCommand = buildIterateCommand(result.pr, readyDelaySuffix, runner);
6
+ export function buildSimpleIterateInstructions(result, readyDelaySuffix) {
7
+ const rerunCommand = buildIterateCommand(result.pr, readyDelaySuffix);
8
8
  switch (result.action) {
9
9
  case "wait":
10
10
  return [buildRecheckInstruction(rerunCommand, "continue the active goal")];
@@ -20,8 +20,8 @@ export function buildSimpleIterateInstructions(result, readyDelaySuffix, runner)
20
20
  ];
21
21
  }
22
22
  }
23
- export function adaptFixCodeInstructions(instructions, pr, readyDelaySuffix, runner) {
24
- const rerunCommand = buildIterateCommand(pr, readyDelaySuffix, runner);
23
+ export function adaptFixCodeInstructions(instructions, pr, readyDelaySuffix) {
24
+ const rerunCommand = buildIterateCommand(pr, readyDelaySuffix);
25
25
  return instructions.map((instruction) => {
26
26
  if (instruction === FIX_INSTRUCTION_STOP) {
27
27
  return `${instruction} ${buildRecheckInstruction(rerunCommand, "recheck")}`;
@@ -32,11 +32,9 @@ export function adaptFixCodeInstructions(instructions, pr, readyDelaySuffix, run
32
32
  export function adaptIterateLog(log) {
33
33
  return log.replace(/\s+—\s+\d+s until auto-cancel/g, "");
34
34
  }
35
- export function buildIterateCommand(pr, readyDelaySuffix, runner) {
35
+ export function buildIterateCommand(pr, readyDelaySuffix) {
36
36
  const suffix = readyDelaySuffix?.trim();
37
- return buildPrShepherdCommand([String(pr), ...(suffix ? ["--ready-delay", suffix] : [])], {
38
- runner,
39
- }).text;
37
+ return buildPrShepherdCommand([String(pr), ...(suffix ? ["--ready-delay", suffix] : [])]).text;
40
38
  }
41
39
  export function numberInstructions(instructions) {
42
40
  return instructions.map((instruction, i) => `${i + 1}. ${instruction}`).join("\n");