pr-shepherd 0.4.1 → 0.5.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.
@@ -10,7 +10,7 @@
10
10
  {
11
11
  "name": "pr-shepherd",
12
12
  "description": "Autonomous PR CI monitor and review-comment resolver",
13
- "source": "./.claude-plugin",
13
+ "source": "./plugin",
14
14
  "category": "productivity",
15
15
  "homepage": "https://github.com/jonathanong/pr-shepherd"
16
16
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pr-shepherd",
3
3
  "description": "Autonomous PR CI monitor and review-comment resolver for Claude Code",
4
- "version": "0.4.1",
4
+ "version": "0.5.0",
5
5
  "author": {
6
6
  "name": "Jonathan Ong",
7
7
  "email": "jonathanrichardong@gmail.com"
package/README.md CHANGED
@@ -21,6 +21,22 @@ Autonomous PR CI monitor and review-comment resolver for Claude Code.
21
21
 
22
22
  ## Install
23
23
 
24
+ > **Note:** Skill and plugin install methods add the skill definitions only — they do not install the `pr-shepherd` CLI. The skills invoke `npx pr-shepherd`, 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 `npx` resolves it without prompting:
25
+ >
26
+ > ```bash
27
+ > npm install --save-dev pr-shepherd
28
+ > ```
29
+ >
30
+ > 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`.
31
+
32
+ ### As individual skills via `npx skills`
33
+
34
+ ```bash
35
+ npx skills add jonathanong/pr-shepherd
36
+ ```
37
+
38
+ Installs the three skills (`check`, `monitor`, `resolve`) 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).
39
+
24
40
  ### As a Claude Code plugin (recommended)
25
41
 
26
42
  ```bash
@@ -149,14 +165,160 @@ pr-shepherd iterate [PR] [--cooldown-seconds N] [--ready-delay Nm] [--last-push-
149
165
  pr-shepherd status PR1 [PR2 …] # multi-PR table
150
166
  ```
151
167
 
152
- Common flags:
168
+ Common flags (all subcommands):
169
+
170
+ | Flag | Default | Description |
171
+ | --------------------- | ------- | ------------------------------------------------------------------------------ |
172
+ | `--format text\|json` | `text` | Output format |
173
+ | `--no-cache` | false | Bypass the 5-minute file cache |
174
+ | `--cache-ttl N` | `300` | Cache TTL in seconds; `PR_SHEPHERD_CACHE_TTL_SECONDS` env var takes precedence |
175
+
176
+ ### pr-shepherd check [PR]
177
+
178
+ Read-only PR status snapshot. Fetches CI results, merge state, and review comments in one GraphQL batch. PR number is inferred from the current branch when omitted.
179
+
180
+ ```sh
181
+ pr-shepherd check # infer PR from current branch
182
+ pr-shepherd check 42
183
+ pr-shepherd check 42 --format=json
184
+ pr-shepherd check 42 --no-cache
185
+ ```
186
+
187
+ Exit codes: `0` READY · `2` IN_PROGRESS · `3` UNRESOLVED_COMMENTS · `1` all other statuses
188
+
189
+ **Example output:**
190
+
191
+ ```
192
+ PR #42 — owner/repo
193
+ Status: UNRESOLVED_COMMENTS
194
+
195
+ Merge Status: CLEAN
196
+ mergeStateStatus: CLEAN
197
+ mergeable: MERGEABLE
198
+ reviewDecision: APPROVED
199
+ isDraft: false
200
+ copilotReviewInProgress:false
201
+
202
+ CI Checks: 3/3 passed
203
+
204
+ Actionable Review Threads (1):
205
+ - threadId=RT_kwDOBxyz123 src/api.ts:47 (@reviewer)
206
+ Please add error handling here
207
+
208
+ Summary: 1 actionable item(s) remaining
209
+ ```
210
+
211
+ ### pr-shepherd resolve [PR]
212
+
213
+ Two modes: **fetch** (default) auto-resolves outdated threads and returns actionable items; **mutate** resolves/minimizes/dismisses specific IDs after you push fixes.
214
+
215
+ **Fetch mode:**
216
+
217
+ ```sh
218
+ pr-shepherd resolve # fetch + auto-resolve outdated threads
219
+ pr-shepherd resolve 42 --fetch --format=json
220
+ ```
221
+
222
+ ```
223
+ Actionable Review Threads (2):
224
+ - threadId=RT_kwDOabc src/api.ts:47 (@reviewer): Please add error handling here
225
+ - threadId=RT_kwDOdef src/utils.ts:12 (@bot): Consider using a const here
226
+
227
+ Summary: 2 actionable item(s)
228
+ ```
229
+
230
+ **Mutate mode** (after pushing fixes):
231
+
232
+ ```sh
233
+ pr-shepherd resolve 42 \
234
+ --resolve-thread-ids RT_kwDOabc,RT_kwDOdef \
235
+ --minimize-comment-ids IC_kwDOxyz \
236
+ --dismiss-review-ids PRR_kwDO123 \
237
+ --message "Switched query to parameterized form in src/db.ts" \
238
+ --require-sha $(git rev-parse HEAD)
239
+ ```
240
+
241
+ ```
242
+ Resolved threads (2): RT_kwDOabc, RT_kwDOdef
243
+ Minimized comments (1): IC_kwDOxyz
244
+ Dismissed reviews (1): PRR_kwDO123
245
+ ```
246
+
247
+ `--require-sha` polls GitHub until the PR head matches the SHA before mutating — ensures reviewers see the fix before threads are closed. Exit code: always `0`. `--message` is required only when `--dismiss-review-ids` is set, and should describe the specific fix — it is shown to the reviewer on GitHub.
248
+
249
+ ### pr-shepherd iterate [PR]
250
+
251
+ One monitor tick: classifies current PR state and emits a single action. Used by the cron loop; the monitor skill calls this every 4 minutes and acts on the result. See [docs/iterate-flow.md](docs/iterate-flow.md) for the full decision tree.
252
+
253
+ ```sh
254
+ pr-shepherd iterate 42 --no-cache --format=json \
255
+ --ready-delay 10m \
256
+ --last-push-time "$(git log -1 --format=%ct HEAD)"
257
+ ```
258
+
259
+ Flags:
260
+
261
+ | Flag | Default | Description |
262
+ | ----------------------------- | ------- | ------------------------------------------------- |
263
+ | `--ready-delay Nm` | `10m` | Settle window before the loop cancels after READY |
264
+ | `--cooldown-seconds N` | `30` | Wait after a push before reading CI |
265
+ | `--last-push-time N` | — | Unix timestamp hint embedded in the result |
266
+ | `--no-auto-rerun` | false | Return `wait` instead of rerunning transient CI |
267
+ | `--no-auto-mark-ready` | false | Skip converting draft → ready-for-review |
268
+ | `--no-auto-cancel-actionable` | false | Skip cancelling actionable failing runs |
269
+
270
+ **Text output** (one line per action):
271
+
272
+ ```
273
+ PR #42 [COOLDOWN] status=UNKNOWN merge=UNKNOWN (cooldown: CI still starting)
274
+ PR #42 [WAIT] status=READY merge=CLEAN (540s until cancel)
275
+ PR #42 [RERUN_CI] status=FAILING merge=UNSTABLE reran=12345,67890
276
+ PR #42 [FIX_CODE] status=UNRESOLVED_COMMENTS merge=BLOCKED threads=2 comments=0 checks=1 cancelled=1
277
+ PR #42 [REBASE] status=FAILING merge=BEHIND (branch is behind main)
278
+ PR #42 [MARK_READY] status=READY merge=CLEAN markedReady=true
279
+ PR #42 [CANCEL] status=READY merge=CLEAN (ready-delay elapsed)
280
+ PR #42 [ESCALATE] status=UNRESOLVED_COMMENTS merge=BLOCKED triggers=fix-thrash — Same thread(s) attempted multiple times without resolution — fix manually then rerun /pr-shepherd:monitor
281
+ ```
282
+
283
+ **JSON output** (`--format=json`, compact single line):
284
+
285
+ ```json
286
+ {
287
+ "pr": 42,
288
+ "repo": "owner/repo",
289
+ "status": "READY",
290
+ "state": "OPEN",
291
+ "mergeStateStatus": "CLEAN",
292
+ "copilotReviewInProgress": false,
293
+ "isDraft": false,
294
+ "shouldCancel": false,
295
+ "remainingSeconds": 540,
296
+ "summary": { "passing": 3, "skipped": 0, "filtered": 0, "inProgress": 0 },
297
+ "action": "wait"
298
+ }
299
+ ```
300
+
301
+ Exit codes: `0` wait/cooldown/rerun_ci/mark_ready · `1` fix_code/rebase · `2` cancel · `3` escalate
302
+
303
+ ### pr-shepherd status PR1 [PR2 …]
304
+
305
+ Multi-PR summary table. One lightweight GraphQL query per PR, run in parallel.
306
+
307
+ ```sh
308
+ pr-shepherd status 41 42 43
309
+ pr-shepherd status 100 --format=json
310
+ ```
311
+
312
+ ```
313
+
314
+ # owner/repo — PR status (3)
315
+
316
+ PR #41 Add new feature for user authentication READY SUCCESS
317
+ PR #42 Refactor internal module IN PROGRESS PENDING
318
+ PR #43 Fix edge case in parser BLOCKED SUCCESS (threads truncated — run shepherd check for full count)
319
+ ```
153
320
 
154
- | Flag | Default | Description |
155
- | --------------------- | ------- | ------------------------------- |
156
- | `--format text\|json` | `text` | Output format |
157
- | `--no-cache` | false | Bypass the 5-minute file cache |
158
- | `--cache-ttl N` | 300 | Cache TTL in seconds |
159
- | `--ready-delay Nm` | `10m` | Settle window before loop exits |
321
+ Exit code: `0` if every PR is READY, `1` otherwise.
160
322
 
161
323
  ## Configuration
162
324
 
@@ -178,7 +340,7 @@ See [docs/configuration.md](docs/configuration.md) for all options.
178
340
 
179
341
  ## Requirements
180
342
 
181
- - Node.js ≥ 24.0.0
343
+ - Node.js ≥ 22.0.0
182
344
  - `gh` CLI authenticated (`gh auth login`); `repo` scope is required for private repositories (public repositories may not need it)
183
345
  - `git`
184
346
 
@@ -62,7 +62,7 @@ export async function runCheck(opts) {
62
62
  // Triage failures (fetch logs) — skipped when caller will short-circuit before needing failureKind.
63
63
  const triaged = failing.length > 0 && !opts.skipTriage ? await triageFailingChecks(failing) : failing;
64
64
  // Resolve threads and comments.
65
- const unresolvedThreads = batchData.reviewThreads.filter((t) => !t.isResolved);
65
+ const unresolvedThreads = batchData.reviewThreads.filter((t) => !t.isResolved && !t.isMinimized);
66
66
  const visibleComments = batchData.comments.filter((c) => !c.isMinimized);
67
67
  // Auto-resolve outdated threads.
68
68
  const outdated = getOutdatedThreads(unresolvedThreads);
@@ -28,7 +28,7 @@ export async function runResolveFetch(opts) {
28
28
  }
29
29
  // Always bypass cache for resolve — we need fresh data before mutating.
30
30
  const { data } = await fetchPrBatch(prNumber, repo);
31
- const unresolvedThreads = data.reviewThreads.filter((t) => !t.isResolved);
31
+ const unresolvedThreads = data.reviewThreads.filter((t) => !t.isResolved && !t.isMinimized);
32
32
  const visibleComments = data.comments.filter((c) => !c.isMinimized);
33
33
  // Auto-resolve outdated.
34
34
  const outdated = getOutdatedThreads(unresolvedThreads);
@@ -117,6 +117,7 @@ function parseRawPr(raw, rawThreadPages, rawCommentNodes, rawReviewNodes, rawChe
117
117
  id: t.id,
118
118
  isResolved: t.isResolved,
119
119
  isOutdated: t.isOutdated,
120
+ isMinimized: comment?.isMinimized ?? false,
120
121
  path: comment?.path ?? null,
121
122
  line: comment?.line ?? null,
122
123
  author: comment?.author?.login ?? "unknown",
@@ -53,6 +53,7 @@ query BatchPr(
53
53
  comments(first: 1) {
54
54
  nodes {
55
55
  id
56
+ isMinimized
56
57
  author {
57
58
  login
58
59
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pr-shepherd",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
4
4
  "description": "Autonomous PR CI monitor and review-comment resolver for Claude Code",
5
5
  "license": "MIT",
6
6
  "author": "Jonathan Ong",
@@ -10,7 +10,7 @@
10
10
  },
11
11
  "files": [
12
12
  "bin/**",
13
- "skills/**",
13
+ "plugin/**",
14
14
  ".claude-plugin/**",
15
15
  "marketplace.json",
16
16
  "README.md",
@@ -35,9 +35,9 @@
35
35
  "prepare": "npm run build",
36
36
  "prepublishOnly": "npm run typecheck && npm test && npm run build",
37
37
  "typecheck": "tsc --noEmit",
38
- "lint": "oxlint src/ skills/",
39
- "format": "oxfmt src/ skills/ docs/ README.md",
40
- "format:check": "oxfmt --check src/ skills/ docs/ README.md",
38
+ "lint": "oxlint src/ plugin/skills/",
39
+ "format": "oxfmt src/ plugin/skills/ docs/ README.md",
40
+ "format:check": "oxfmt --check src/ plugin/skills/ docs/ README.md",
41
41
  "test": "vitest run",
42
42
  "test:coverage": "vitest run --coverage",
43
43
  "test:watch": "vitest"
@@ -76,15 +76,26 @@ Parse the `action` field and act:
76
76
  After fixing manually, rerun /pr-shepherd:monitor <PR> to resume.
77
77
 
78
78
  - `fix_code` → do the following, then stop this iteration (CI needs time):
79
- 1. For each item in `fix.threads` and `fix.comments`: read the referenced file/line and apply the fix (Edit/Write tools).
79
+ 0. **Triage `fix.comments`** into two buckets before taking any action:
80
+ - **Noise** (`NOISE_COMMENT_IDS`): bot-authored comments with no actionable code feedback — e.g. quota/rate-limit warnings ("you have reached your daily quota", "please wait up to N hours"), "resuming" notices, bare acknowledgements, or any comment whose body contains no file path, line number, or concrete code suggestion. Collect their `id`s.
81
+ - **Actionable**: everything else. When in doubt, treat as actionable.
82
+ All items in `fix.threads` are always actionable (they carry a file path and line by construction).
83
+ 1. For each item in `fix.threads` and each **actionable** `fix.comments`: read the referenced file/line and apply the fix (Edit/Write tools).
80
84
  2. For each item in `fix.checks`:
81
85
  - If `runId` is non-null: fetch the failure log with `gh run view <runId> --log-failed` (dangerouslyDisableSandbox: true), scan the output to identify the failure (e.g. grep for `FAIL` for test failures, `error:` for type/compile errors, lint rule names for lint failures), then read the relevant file and apply the fix (Edit/Write tools).
82
86
  - If `runId` is null: the failed check is an external status check that cannot be inspected via run logs. Escalate — tell the user to open `detailsUrl` in the PR checks UI, inspect the failure manually, and rerun `/pr-shepherd:monitor <PR_NUMBER>` after addressing it. Do not attempt to fix these automatically.
83
87
  3. For each item in `fix.changesRequestedReviews`: read the review body and apply the requested changes.
84
88
  4. If files were changed, `git add <files> && git commit -m "<appropriate commit message>"`
85
- 5. `git fetch origin && git rebase origin/<BASE_BRANCH> && git push --force-with-lease` (dangerouslyDisableSandbox: true)
86
- 6. `HEAD_SHA=$(git rev-parse HEAD)`
87
- 7. `npx pr-shepherd resolve <PR_NUMBER> --resolve-thread-ids <IDs> --minimize-comment-ids <IDs> --dismiss-review-ids <IDs> --message "address review comments" --require-sha "$HEAD_SHA"` (dangerouslyDisableSandbox: true). Omit any flag whose ID list is empty.
89
+ 5. If files were changed: `git fetch origin && git rebase origin/<BASE_BRANCH> && git push --force-with-lease` (dangerouslyDisableSandbox: true), then `HEAD_SHA=$(git rev-parse HEAD)`.
90
+ 6. If **only noise** was found (no files changed, no threads/checks/reviews to act on): skip commit/push and omit `--require-sha` in the next step.
91
+ 7. Resolve the items on GitHub (dangerouslyDisableSandbox: true). Build the command from the non-empty ID lists only — always start with:
92
+ `npx pr-shepherd resolve <PR_NUMBER>`
93
+ Then append:
94
+ - `--resolve-thread-ids <IDs>` only if `fix.threads` was non-empty.
95
+ - `--minimize-comment-ids <IDs>` if any comments exist (use `NOISE_COMMENT_IDS` plus IDs of any other comments to minimize).
96
+ - `--dismiss-review-ids <IDs> --message "<specific description of what you changed>"` only if `fix.changesRequestedReviews` was non-empty. The message is shown to the reviewer on GitHub — write one sentence describing the actual fix (e.g. `"Switched to parameterized query in src/db.ts"`). Never use generic text like `"address review comments"`.
97
+ - `--require-sha "$HEAD_SHA"` only if a push occurred (omit when only noise was handled).
98
+ Omit any flag whose ID list is empty.
88
99
 
89
100
  ````
90
101
 
@@ -62,17 +62,19 @@ Resolve unresolved review threads and minimize PR comments on the current PR —
62
62
  - `git fetch origin && git rebase origin/$BASE_BRANCH && git push --force-with-lease`
63
63
  - Cancel stale CI runs: `gh run list --branch "$BRANCH" --status in_progress --json databaseId --jq '.[].databaseId' | xargs -I{} gh run cancel {}`
64
64
 
65
- 6. **Resolve all verified items** — **only after the push:**
65
+ 6. **Resolve all verified items** — **only after the push, and only if at least one of the three ID lists is non-empty.** If all lists are empty, skip this step entirely (running resolve with no mutation IDs enters fetch mode as a side effect). Build the command from the non-empty ID lists; omit any flag whose list is empty:
66
66
 
67
67
  ```bash
68
68
  npx pr-shepherd resolve <N> \
69
69
  --resolve-thread-ids <comma-separated-IDs> \
70
70
  --minimize-comment-ids <comma-separated-IDs> \
71
71
  --dismiss-review-ids <comma-separated-IDs> \
72
- --message "Addressed in $(git rev-parse HEAD)" \
72
+ --message "<specific description of the fix that addressed this review>" \
73
73
  --require-sha $(git rev-parse HEAD)
74
74
  ```
75
75
 
76
+ `--message` belongs **only** with `--dismiss-review-ids`. Omit it entirely when not dismissing a review. When you are dismissing, write one sentence describing the actual fix — the text is sent to GitHub as the dismissal reason and is shown to the reviewer. Generic text like `"Addressed in <SHA>"` or `"address review comments"` is not acceptable.
77
+
76
78
  The `--require-sha` flag ensures pr-shepherd verifies GitHub has the new commit before resolving.
77
79
 
78
80
  7. **Report results** from the CLI output.
@@ -82,4 +84,4 @@ Resolve unresolved review threads and minimize PR comments on the current PR —
82
84
  - NEVER resolve threads before pushing fixes (use `--require-sha`).
83
85
  - NEVER blindly resolve items — always read and verify first.
84
86
  - Resolve from ALL authors — bots, AI reviewers, and humans alike.
85
- - `--message` is required when using `--dismiss-review-ids`. The CLI will throw if it is missing.
87
+ - `--message` is required when using `--dismiss-review-ids`, and must NOT be passed otherwise. The CLI throws if it is missing during dismissal. The message must describe the specific change that addressed the review (e.g. `"Added null check in handler.ts:42"`); generic boilerplate like `"address review comments"` or `"Addressed in <SHA>"` is reviewer-hostile and forbidden.
File without changes