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.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +6 -10
- package/bin/cli/exit-codes.mjs +2 -0
- package/bin/cli/iterate-instructions.mjs +14 -11
- package/bin/commands/check-terminal-report.mjs +35 -0
- package/bin/commands/check.mjs +5 -1
- package/package.json +1 -1
- package/plugins/pr-shepherd/.codex-plugin/plugin.json +1 -1
- package/plugins/pr-shepherd/skills/pr-shepherd/SKILL.md +1 -1
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
|
|
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.
|
|
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
|
|
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
|
|
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`).
|
|
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
|
|
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)
|
package/bin/cli/exit-codes.mjs
CHANGED
|
@@ -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,
|
|
4
|
-
|
|
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,
|
|
7
|
-
const rerunCommand =
|
|
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, "
|
|
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, "
|
|
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,
|
|
22
|
-
const rerunCommand =
|
|
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, "
|
|
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, "
|
|
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
|
|
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
|
+
}
|
package/bin/commands/check.mjs
CHANGED
|
@@ -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
|
@@ -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
|
|
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.
|