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.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +23 -56
- package/bin/checks/triage.test-support.mjs +1 -2
- package/bin/cli/default-poll.mjs +32 -0
- package/bin/cli/fix-formatter.mjs +3 -3
- package/bin/cli/formatters.mjs +5 -4
- package/bin/cli/handlers.mjs +0 -1
- package/bin/cli/help-command-pages.mjs +153 -0
- package/bin/cli/help-top-page.mjs +59 -0
- package/bin/cli/help.mjs +4 -30
- package/bin/cli/iterate-emitter.mjs +0 -1
- package/bin/cli/iterate-formatter.mjs +4 -6
- package/bin/cli/iterate-instructions.mjs +6 -8
- package/bin/cli/iterate-lean.mjs +4 -6
- package/bin/cli/iterate-lean.test-support.mjs +0 -2
- package/bin/cli/list-formatters.mjs +3 -0
- package/bin/cli/poll-handler.mjs +0 -1
- package/bin/cli/runner.mjs +2 -103
- package/bin/cli/validate-default-args.mjs +32 -0
- package/bin/cli-parser.clean.test-support.mjs +0 -1
- package/bin/cli-parser.commit-suggestion.test-support.mjs +2 -3
- package/bin/cli-parser.iterate-fix.test-support.mjs +1 -2
- package/bin/cli-parser.iterate-fixtures.mjs +1 -1
- package/bin/cli-parser.iterate.test-support.mjs +1 -2
- package/bin/cli-parser.mjs +7 -7
- package/bin/cli-parser.test-support.mjs +1 -2
- package/bin/commands/check.mjs +2 -1
- package/bin/commands/check.test-support.mjs +1 -2
- package/bin/commands/clean.test-support.mjs +0 -1
- package/bin/commands/commit-suggestion-instruction.mjs +2 -2
- package/bin/commands/commit-suggestion.apply.test-support.mjs +1 -2
- package/bin/commands/commit-suggestion.mjs +6 -2
- package/bin/commands/commit-suggestion.test-support.mjs +2 -3
- package/bin/commands/iterate/classify.mjs +2 -2
- package/bin/commands/iterate/fix-code.mjs +3 -3
- package/bin/commands/iterate/index.mjs +0 -1
- package/bin/commands/iterate/render.mjs +4 -3
- package/bin/commands/iterate-stall.test-support.mjs +0 -1
- package/bin/commands/iterate.fix-code-in-progress.test-support.mjs +2 -2
- package/bin/commands/poll.mjs +12 -3
- package/bin/commands/resolve-instructions.mjs +6 -7
- package/bin/commands/resolve.mjs +1 -1
- package/bin/commands/resolve.test-support.mjs +2 -2
- package/bin/commands/shepherd-journal.test-support.mjs +0 -2
- package/bin/comments/resolve.test-support.mjs +1 -2
- package/bin/comments/visible-comments.mjs +3 -2
- package/bin/config/load.mjs +0 -7
- package/bin/config.json +0 -3
- package/bin/github/batch-parsers.test-support.mjs +1 -2
- package/bin/github/batch.test-support.mjs +1 -2
- package/bin/github/http.test-support.mjs +1 -2
- package/bin/reporters/agent.mjs +1 -0
- package/bin/suggestions/patch.test-support.mjs +0 -2
- package/package.json +1 -1
- package/plugins/pr-shepherd/.codex-plugin/plugin.json +1 -1
- package/plugins/pr-shepherd/skills/pr-shepherd/SKILL.md +11 -27
- package/bin/cli/default-iterate.mjs +0 -50
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
|
|
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>`
|
|
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
|
-
>
|
|
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
|
-
>
|
|
47
|
+
> oxfmt
|
|
48
48
|
|
|
49
49
|
## Post-fix push
|
|
50
50
|
|
|
51
51
|
- base: `main`
|
|
52
|
-
- resolve: `
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
112
|
+
sections.push(numberInstructions(adaptFixCodeInstructions(result.fix.instructions, result.pr, readyDelaySuffix)));
|
|
113
113
|
return joinSections(sections);
|
|
114
114
|
}
|
|
115
115
|
function blockquote(body) {
|
package/bin/cli/formatters.mjs
CHANGED
|
@@ -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
|
|
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})`);
|
package/bin/cli/handlers.mjs
CHANGED
|
@@ -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:
|
|
4
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
7
|
-
const rerunCommand = buildIterateCommand(result.pr, readyDelaySuffix
|
|
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
|
|
24
|
-
const rerunCommand = buildIterateCommand(pr, readyDelaySuffix
|
|
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
|
|
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");
|