pr-shepherd 0.16.0 → 0.16.2

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.
@@ -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.16.0",
4
+ "version": "0.16.2",
5
5
  "author": {
6
6
  "name": "Jonathan Ong",
7
7
  "email": "jonathanrichardong@gmail.com"
package/README.md CHANGED
@@ -20,7 +20,7 @@ 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 sleep/timeout instructions for all runtimes: pick a fresh sleep/timeout between 30 seconds and 4 minutes and rerun the configured pr-shepherd command. Codex is detected with `AGENT=codex` or the current Codex CLI signal `CODEX_CI=1`. Generated commands use `cli.runner` from `.pr-shepherdrc.yml`: `auto` (default), `npx`, `pnpm`, or `yarn`.
23
+ The CLI emits runtime-specific retry instructions. Claude-compatible output schedules exactly one next session-only iteration after a fresh delay between 30 seconds and 4 minutes, then ends the turn. Codex-compatible output sleeps inline for that delay, then reruns the configured pr-shepherd command. Codex is detected with `AGENT=codex` or the current Codex CLI signal `CODEX_CI=1`. Generated commands use `cli.runner` from `.pr-shepherdrc.yml`: `auto` (default), `npx`, `pnpm`, or `yarn`.
24
24
 
25
25
  At a high level, the skill invokes `pr-shepherd <PR>` through the selected package runner, which provides actionable feedback directly to the agent:
26
26
 
@@ -61,7 +61,7 @@ _(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. Pick a fresh sleep/timeout between 30 seconds and 4 minutes, wait that long, then rerun `npx pr-shepherd 123` to recheck.
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.
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:
@@ -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 with a fresh sleep/timeout between 30 seconds and 4 minutes before each rerun 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 the reusable command until Shepherd emits `[CANCEL]` for ready-delay completion or merged/closed, or `[ESCALATE]` (including `stall-timeout` for repeated unchanged CI failures). Claude schedules one next session-only iteration per tick; Codex sleeps inline and reruns.
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 to pick a fresh 30s–4m delay and rerun; the loop continues until `[CANCEL]` or `[ESCALATE]`.
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]`.
118
118
 
119
119
  Claude Code (via `pr-shepherd` skill):
120
120
 
@@ -134,7 +134,7 @@ npx pr-shepherd iterate 42 # legacy-compatible spelling
134
134
 
135
135
  ## Iterate decision loop
136
136
 
137
- On each tick: fetch PR state in one GraphQL batch → classify CI, comments, and merge status → take one action (`fix_code`, `mark_ready`, `cancel`, `escalate`, or `wait`). All runtimes pick a fresh sleep/timeout between 30 seconds and 4 minutes for each nonterminal recurrence. See [docs/iterate-flow.md](docs/iterate-flow.md) for the decision table and [docs/flow.md](docs/flow.md) for the end-to-end flow diagram.
137
+ On each tick: fetch PR state in one GraphQL batch → classify CI, comments, and merge status → take one action (`fix_code`, `mark_ready`, `cancel`, `escalate`, or `wait`). Claude schedules one next session-only iteration after a fresh 30s-4m delay; Codex sleeps inline for that delay and reruns. See [docs/iterate-flow.md](docs/iterate-flow.md) for the decision table and [docs/flow.md](docs/flow.md) for the end-to-end flow diagram.
138
138
 
139
139
  ## Install
140
140
 
@@ -216,7 +216,7 @@ Then iterate a PR from Codex with the target repository's package runner:
216
216
 
217
217
  For example, a repo like `~/filaments` that declares `packageManager: "pnpm@..."` and has `pnpm-lock.yaml` should use `pnpm exec pr-shepherd iterate 42`. For npm repos, use `npx pr-shepherd iterate 42`.
218
218
 
219
- Or ask Codex to use the `pr-shepherd` skill, for example: `run pr-shepherd until this PR is ready`. Follow the output's `## Instructions`. The skill runs one tick and the instructions tell you to pick a fresh sleep/timeout between 30 seconds and 4 minutes before the next rerun. Continue until Shepherd emits `[CANCEL]` or `[ESCALATE]` (including `stall-timeout` for repeated unchanged CI failures). `pr-shepherd iterate 42` remains supported for existing workflows.
219
+ Or ask Codex to use the `pr-shepherd` skill, for example: `run pr-shepherd until this PR is ready`. Follow the output's `## Instructions`. The skill runs one tick and Codex-compatible instructions tell you to pick a fresh sleep/timeout between 30 seconds and 4 minutes before the next rerun. Continue until Shepherd emits `[CANCEL]` or `[ESCALATE]` (including `stall-timeout` for repeated unchanged CI failures). `pr-shepherd iterate 42` remains supported for existing workflows.
220
220
 
221
221
  ### As a global CLI
222
222
 
@@ -259,10 +259,6 @@ Full reference: [docs/README.md](docs/README.md) — CLI usage, skills, configur
259
259
 
260
260
  See [docs/architecture.md](docs/architecture.md) for the module map and dependency rules.
261
261
 
262
- ## Forking
263
-
264
- See [docs/forking.md](docs/forking.md) if you want to customize pr-shepherd for your own use or team.
265
-
266
262
  ## License
267
263
 
268
264
  [MIT](LICENSE)
@@ -11,6 +11,8 @@ export function parseDurationToMinutes(s, defaultMinutes) {
11
11
  }
12
12
  export function statusToExitCode(status) {
13
13
  switch (status) {
14
+ case "MERGED":
15
+ case "CLOSED":
14
16
  case "READY":
15
17
  return 0;
16
18
  case "IN_PROGRESS":
@@ -1,16 +1,19 @@
1
1
  import { FIX_INSTRUCTION_STOP_AFTER_PUSH, FIX_INSTRUCTION_STOP_BEFORE_NEXT_TICK, } from "../commands/iterate/render.mjs";
2
2
  import { buildPrShepherdCommand } from "./runner.mjs";
3
- function buildRecheckInstruction(rerunCommand, tail) {
4
- return `Pick a fresh sleep/timeout between 30 seconds and 4 minutes, wait that long, then rerun \`${rerunCommand}\` ${tail}`;
3
+ function buildRecheckInstruction(runtime, rerunCommand, purpose) {
4
+ if (runtime === "codex") {
5
+ return `Pick a fresh sleep/timeout between 30 seconds and 4 minutes, wait that long, then rerun \`${rerunCommand}\` to ${purpose}.`;
6
+ }
7
+ return `Schedule one session-only follow-up task to run \`${rerunCommand}\` to ${purpose} once after a fresh delay between 30 seconds and 4 minutes, then end this turn. Do not sleep or rerun inline.`;
5
8
  }
6
- export function buildSimpleIterateInstructions(result, _runtime, readyDelaySuffix, runner) {
7
- const rerunCommand = buildCodexIterateCommand(result.pr, readyDelaySuffix, runner);
9
+ export function buildSimpleIterateInstructions(result, runtime, readyDelaySuffix, runner) {
10
+ const rerunCommand = buildIterateCommand(result.pr, readyDelaySuffix, runner);
8
11
  switch (result.action) {
9
12
  case "wait":
10
- return [buildRecheckInstruction(rerunCommand, "to continue the active goal.")];
13
+ return [buildRecheckInstruction(runtime, rerunCommand, "continue the active goal")];
11
14
  case "mark_ready":
12
15
  return [
13
- `The CLI already marked the PR ready for review. ${buildRecheckInstruction(rerunCommand, "to recheck.")}`,
16
+ `The CLI already marked the PR ready for review. ${buildRecheckInstruction(runtime, rerunCommand, "recheck")}`,
14
17
  ];
15
18
  case "cancel":
16
19
  return ["Stop — the active goal is complete."];
@@ -18,14 +21,14 @@ export function buildSimpleIterateInstructions(result, _runtime, readyDelaySuffi
18
21
  return ["Stop — the PR needs human direction before iterating can resume."];
19
22
  }
20
23
  }
21
- export function adaptFixCodeInstructions(instructions, pr, _runtime, readyDelaySuffix, runner) {
22
- const rerunCommand = buildCodexIterateCommand(pr, readyDelaySuffix, runner);
24
+ export function adaptFixCodeInstructions(instructions, pr, runtime, readyDelaySuffix, runner) {
25
+ const rerunCommand = buildIterateCommand(pr, readyDelaySuffix, runner);
23
26
  return instructions.map((instruction) => {
24
27
  if (instruction === FIX_INSTRUCTION_STOP_AFTER_PUSH) {
25
- return `CI needs time to run on the new push. ${buildRecheckInstruction(rerunCommand, "to recheck.")}`;
28
+ return `CI needs time to run on the new push. ${buildRecheckInstruction(runtime, rerunCommand, "recheck")}`;
26
29
  }
27
30
  if (instruction === FIX_INSTRUCTION_STOP_BEFORE_NEXT_TICK) {
28
- return buildRecheckInstruction(rerunCommand, "to recheck.");
31
+ return buildRecheckInstruction(runtime, rerunCommand, "recheck");
29
32
  }
30
33
  return instruction;
31
34
  });
@@ -33,7 +36,7 @@ export function adaptFixCodeInstructions(instructions, pr, _runtime, readyDelayS
33
36
  export function adaptIterateLog(log, _runtime) {
34
37
  return log.replace(/\s+—\s+\d+s until auto-cancel/g, "");
35
38
  }
36
- export function buildCodexIterateCommand(pr, readyDelaySuffix, runner) {
39
+ export function buildIterateCommand(pr, readyDelaySuffix, runner) {
37
40
  const suffix = readyDelaySuffix?.trim();
38
41
  return buildPrShepherdCommand([String(pr), ...(suffix ? ["--ready-delay", suffix] : [])], {
39
42
  runner,
@@ -0,0 +1,35 @@
1
+ export function buildTerminalReport(prNumber, repo, batchData, mergeStatus, status) {
2
+ return {
3
+ pr: prNumber,
4
+ nodeId: batchData.nodeId,
5
+ repo: `${repo.owner}/${repo.name}`,
6
+ status,
7
+ baseBranch: batchData.baseRefName,
8
+ mergeStatus,
9
+ checks: {
10
+ passing: [],
11
+ failing: [],
12
+ inProgress: [],
13
+ skipped: [],
14
+ filtered: [],
15
+ filteredNames: [],
16
+ blockedByFilteredCheck: false,
17
+ },
18
+ threads: {
19
+ actionable: [],
20
+ resolutionOnly: [],
21
+ autoResolved: [],
22
+ autoResolveErrors: [],
23
+ firstLook: [],
24
+ },
25
+ comments: {
26
+ actionable: [],
27
+ firstLook: [],
28
+ },
29
+ changesRequestedReviews: [],
30
+ reviewSummaries: [],
31
+ firstLookSummaries: [],
32
+ editedSummaries: [],
33
+ approvedReviews: [],
34
+ };
35
+ }
@@ -9,6 +9,7 @@ import { deriveMergeStatus } from "../merge-status/derive.mjs";
9
9
  import { loadConfig } from "../config/load.mjs";
10
10
  import { classifyVisibleComments } from "../comments/visible-comments.mjs";
11
11
  import { computeStatus } from "./check-status.mjs";
12
+ import { buildTerminalReport } from "./check-terminal-report.mjs";
12
13
  import { loadSeenMap, markSeen, classifyItem } from "../state/seen-comments.mjs";
13
14
  export async function runCheck(opts) {
14
15
  const repo = await getRepoInfo();
@@ -30,6 +31,10 @@ export async function runCheck(opts) {
30
31
  mergeStateStatus: restState.mergeStateStatus ?? batchData.mergeStateStatus,
31
32
  };
32
33
  }
34
+ const mergeStatus = deriveMergeStatus(batchData);
35
+ if (mergeStatus.state === "MERGED" || mergeStatus.state === "CLOSED") {
36
+ return buildTerminalReport(prNumber, repo, batchData, mergeStatus, mergeStatus.state);
37
+ }
33
38
  const startupFailureChecks = await fetchStartupFailureChecks(repo, batchData.headRefOid, prNumber);
34
39
  const allChecks = mergeStartupFailureChecks(batchData.checks, startupFailureChecks);
35
40
  const classifiedChecks = classifyChecks(allChecks);
@@ -111,7 +116,6 @@ export async function runCheck(opts) {
111
116
  ...[...firstLookSummaries, ...editedSummaries].map((r) => markSeen(stateKey, r.id, r.body)),
112
117
  ]);
113
118
  const resolutionOnlyThreads = unresolvedThreads.filter((t) => !autoResolvedIds.has(t.id) && (t.isOutdated || t.isMinimized));
114
- const mergeStatus = deriveMergeStatus(batchData);
115
119
  const blockedByFilteredCheck = mergeStatus.status === "BLOCKED" &&
116
120
  !verdict.anyFailing &&
117
121
  !verdict.anyInProgress &&
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pr-shepherd",
3
- "version": "0.16.0",
3
+ "version": "0.16.2",
4
4
  "description": "Autonomous PR CI monitor and review-comment resolver for agentic coding tools",
5
5
  "license": "MIT",
6
6
  "author": "Jonathan Ong",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pr-shepherd",
3
- "version": "0.16.0",
3
+ "version": "0.16.2",
4
4
  "description": "Autonomous PR CI monitor and review-comment resolver for Codex.",
5
5
  "author": {
6
6
  "name": "Jonathan Ong",
@@ -45,4 +45,4 @@ One-tick dispatcher for iterating a PR to completion.
45
45
  5. **Stop conditions:**
46
46
  - Stop when the CLI emits `[CANCEL]` (ready-delay completed, or PR merged/closed).
47
47
  - Stop when the CLI emits `[ESCALATE]`, including `stall-timeout` for repeated unchanged CI failures.
48
- - All other actions (`[WAIT]`, `[MARK_READY]`, `[FIX_CODE]`) are non-terminal: follow the `## Instructions` to sleep/wait and rerun.
48
+ - All other actions (`[WAIT]`, `[MARK_READY]`, `[FIX_CODE]`) are non-terminal: follow the `## Instructions`. For Claude, schedule exactly one next session-only iteration and end the turn; do not sleep inline and do not create a recurring cron.