pr-shepherd 0.46.3 → 0.46.5

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.46.3",
4
+ "version": "0.46.5",
5
5
  "author": {
6
6
  "name": "Jonathan Ong",
7
7
  "email": "jonathanrichardong@gmail.com"
package/README.md CHANGED
@@ -69,12 +69,12 @@ Conversations Resolved: No [Not Required]
69
69
  1. Review each item under `## Review threads` and `## Failing checks` and decide whether it needs a code change.
70
70
  2. Apply every warranted review fix in each file referenced above.
71
71
  3. Triage every failure under `## Failing checks`. See "CI failure triage" in the pr-shepherd skill for read-only inspection rules.
72
- 4. If you changed code, commit any remaining changes and push to the PR head branch, then run review mutations using the pushed commit SHA and iterate again with the same options. If you did not change code, do not commit and continue.
72
+ 4. If you changed code, commit any remaining changes and push to the PR head branch, then run review mutations using the pushed commit SHA and iterate immediately with the same options. If you did not change code, do not commit and continue.
73
73
  5. Run the generated thread IDs unchanged. A latest comment beginning `<!-- pr-shepherd -->` is an earlier Shepherd reply: a marked viewer-authored human thread is emitted resolve-only when authorized, while a marked other-human thread is already acknowledged and has no further mutation.
74
74
  6. If you did not change code, replace `$HEAD_SHA` with `$(git rev-parse HEAD)`, which must equal the current remote PR head. If you changed code, commit and push to the PR head branch first, then replace `$HEAD_SHA` with the pushed commit SHA.
75
75
  7. Replace `$DISMISS_MESSAGE` with one sentence describing what changed.
76
76
  8. Run the `apply review:` command shown above. See "Review-mutation mechanics" in the pr-shepherd skill for dismiss-ID retention.
77
- 9. `[FIX_CODE]` is non-terminal: if you changed code, commit and push to the PR head branch, then run review mutations using the pushed commit SHA and iterate again with the same options; without code changes, complete the authorized review mutations and iterate again.
77
+ 9. `[FIX_CODE]` is non-terminal: if you changed code, commit and push to the PR head branch, then run review mutations using the pushed commit SHA and iterate immediately with the same options; without code changes, complete the authorized review mutations and iterate immediately.
78
78
  ```
79
79
 
80
80
  See [docs/actions.md](docs/actions.md) for the complete output contract and [docs/escalations.md](docs/escalations.md) for the exact finite human-handoff boundary. Iterate/poll PR outcomes use exit codes `0` and `10`–`15`; command and GitHub failures use `sysexits.h` codes — [docs/exit-codes.md](docs/exit-codes.md).
@@ -18,7 +18,7 @@ export function formatQuotaWarning(warning) {
18
18
  `- Reset: ${resetTime(warning.resetAt)}`,
19
19
  `- Recommended poll interval: ${warning.pollIntervalMinutes} minutes`,
20
20
  `- Recommended bounded CLI timeout: ${warning.pollTimeoutMinutes} minutes`,
21
- "- Recommendation: prefer non-GraphQL `gh` CLI commands for PR operations until the reset above, then resume pr-shepherd",
21
+ "- Recommendation: keep polling pr-shepherd at the cadence above; for incidental PR operations prefer REST `gh` (`gh pr view`, `gh pr review`, `gh api`); do not substitute `gh pr checks`/`gh pr watch`",
22
22
  ].join("\n");
23
23
  }
24
24
  export function formatApiUsage(usage) {
@@ -10,7 +10,7 @@ export function buildSimpleIterateInstructions(result) {
10
10
  ];
11
11
  }
12
12
  return [
13
- "Non-terminal — no action needed this tick. Iterate again with the same options to continue.",
13
+ "Non-terminal — no action needed this tick. Iterate immediately with the same options to continue.",
14
14
  ];
15
15
  case "mark_ready":
16
16
  if (result.quotaWarning) {
@@ -19,7 +19,7 @@ export function buildSimpleIterateInstructions(result) {
19
19
  ];
20
20
  }
21
21
  return [
22
- "The CLI marked the PR ready for review. Iterate again with the same options to continue.",
22
+ "The CLI marked the PR ready for review. Iterate immediately with the same options to continue.",
23
23
  ];
24
24
  case "merge": {
25
25
  const instructions = [
@@ -33,7 +33,7 @@ export function buildSimpleIterateInstructions(result) {
33
33
  }
34
34
  instructions.push(result.quotaWarning
35
35
  ? buildQuotaAwareContinuation(result.quotaWarning, "After running the merge command.")
36
- : "Then iterate again with the same options to monitor until the PR merges or needs work.");
36
+ : "Then iterate immediately with the same options to monitor until the PR merges or needs work.");
37
37
  return instructions;
38
38
  }
39
39
  case "cancel":
@@ -11,6 +11,15 @@ export declare function buildCrStaleClause(reviews: Review[]): string;
11
11
  * discarding the rest of the user's config.
12
12
  */
13
13
  export declare function buildBehindBaseHintInstruction(baseBranch: string, hint: string, isBehind: boolean): string[];
14
+ /**
15
+ * Give one branch-refresh recovery path after Shepherd's single workflow rerun has failed.
16
+ * The fetched PR base branch is raw context; the caller still owns repository-specific git
17
+ * mechanics and decides whether the base contains a relevant fix.
18
+ */
19
+ export declare function buildRepeatedWorkflowBranchRecoveryInstructions(baseBranch: string, hasExhaustedWorkflowRerun: boolean, branch: {
20
+ isBehind: boolean;
21
+ hasConflicts: boolean;
22
+ }): string[];
14
23
  /**
15
24
  * Build the `Run the apply review: command` instruction. Steps stay here (not in the skill)
16
25
  * whenever the *unmodified, as-printed* command is unsafe without them:
@@ -20,6 +20,23 @@ export function buildBehindBaseHintInstruction(baseBranch, hint, isBehind) {
20
20
  return [];
21
21
  return [`The branch is behind PR base branch \`${baseBranch}\`. ${trimmedHint} before pushing.`];
22
22
  }
23
+ /**
24
+ * Give one branch-refresh recovery path after Shepherd's single workflow rerun has failed.
25
+ * The fetched PR base branch is raw context; the caller still owns repository-specific git
26
+ * mechanics and decides whether the base contains a relevant fix.
27
+ */
28
+ export function buildRepeatedWorkflowBranchRecoveryInstructions(baseBranch, hasExhaustedWorkflowRerun, branch) {
29
+ if (!hasExhaustedWorkflowRerun || (!branch.isBehind && !branch.hasConflicts))
30
+ return [];
31
+ const state = branch.hasConflicts ? "conflicts with" : "is behind";
32
+ const instructions = [
33
+ `The workflow rerun still fails while the branch ${state} PR base branch \`${baseBranch}\`. Inspect the current base branch for an existing fix before choosing a remediation.`,
34
+ ];
35
+ instructions.push(branch.hasConflicts
36
+ ? `Rebase or otherwise update the PR branch from \`${baseBranch}\` according to repository conventions, resolving conflicts as part of that update.`
37
+ : `Rebase or otherwise update the PR branch from \`${baseBranch}\` according to repository conventions.`);
38
+ return instructions;
39
+ }
23
40
  /**
24
41
  * Build the `Run the apply review: command` instruction. Steps stay here (not in the skill)
25
42
  * whenever the *unmodified, as-printed* command is unsafe without them:
@@ -73,12 +90,12 @@ export function buildFailingCheckInstructions(checks) {
73
90
  }
74
91
  export function buildFixCompletionInstruction(checks, hasConflicts = false, hasShaGatedReviewMutations = false) {
75
92
  if (hasConflicts)
76
- return "`[FIX_CODE]` is non-terminal: resolve the conflicts, commit, push to the PR head branch, then iterate again with the same options.";
93
+ return "`[FIX_CODE]` is non-terminal: resolve the conflicts, commit, push to the PR head branch, then iterate immediately with the same options.";
77
94
  if (hasShaGatedReviewMutations) {
78
- return "`[FIX_CODE]` is non-terminal: if you changed code, commit and push to the PR head branch, then run the review mutations using the pushed commit SHA and iterate again with the same options; if you did not change code, complete the authorized review mutations and iterate again with the same options.";
95
+ return "`[FIX_CODE]` is non-terminal: if you changed code, commit and push to the PR head branch, then run the review mutations using the pushed commit SHA and iterate immediately with the same options; if you did not change code, complete the authorized review mutations and iterate immediately with the same options.";
79
96
  }
80
97
  if (checks.some((check) => check.rerunCommand)) {
81
- return "`[FIX_CODE]` is non-terminal. Run any warranted reruns for `[rerun authorized]` checks (or apply code fixes for real failures), then iterate again with the same options to continue.";
98
+ return "`[FIX_CODE]` is non-terminal. Run any warranted reruns for `[rerun authorized]` checks (or apply code fixes for real failures), then iterate immediately with the same options to continue.";
82
99
  }
83
- return "`[FIX_CODE]` is non-terminal. After completing these steps, iterate again with the same options to continue.";
100
+ return "`[FIX_CODE]` is non-terminal. After completing these steps, iterate immediately with the same options to continue.";
84
101
  }
@@ -180,7 +180,10 @@ export async function handleFixCode(ctx) {
180
180
  const allCommentIds = [...commentMinimizeIds, ...reviewSummaryIds];
181
181
  const belongsToActiveWorkflowRun = (check) => check.runId !== null && inProgressWorkflowRunIds.has(check.runId);
182
182
  const manualFollowUpChecks = failingAgentChecks.filter((check) => !belongsToActiveWorkflowRun(check) && checkRequiresHumanFollowUp(check));
183
+ const exhaustedAttempts = manualFollowUpChecks.filter((check) => check.runAttempt !== undefined && check.runAttempt > 1);
184
+ const hasBehindBaseRecovery = isBehind && exhaustedAttempts.length > 0;
183
185
  const hasAutonomousWork = hasConflicts ||
186
+ hasBehindBaseRecovery ||
184
187
  threads.length > 0 ||
185
188
  resolutionOnlyThreads.length > 0 ||
186
189
  actionableComments.length > 0 ||
@@ -194,7 +197,6 @@ export async function handleFixCode(ctx) {
194
197
  checks.some((check) => (check.annotations?.length ?? 0) > 0) ||
195
198
  failingAgentChecks.some((check) => belongsToActiveWorkflowRun(check) || !checkRequiresHumanFollowUp(check));
196
199
  if (manualFollowUpChecks.length > 0 && !hasAutonomousWork) {
197
- const exhaustedAttempts = manualFollowUpChecks.filter((check) => check.runAttempt !== undefined && check.runAttempt > 1);
198
200
  const checkSuggestion = exhaustedAttempts.length > 0
199
201
  ? `GitHub reports a later workflow attempt (${exhaustedAttempts
200
202
  .map((check) => `${check.runId ?? check.name}: attempt ${check.runAttempt}`)
@@ -257,7 +259,7 @@ export async function handleFixCode(ctx) {
257
259
  }
258
260
  const firstLookThreads = report.threads.firstLook;
259
261
  const firstLookComments = report.comments.firstLook;
260
- const instructions = buildFixInstructions(threads, actionableComments, checks, changesRequestedReviews, baseLookup.branch, resolveCommand, hasConflicts, prReference, cancelled.length, firstLookThreads, firstLookComments, firstLookSummaries, editedSummaries, inProgressRunIds, resolutionOnlyThreads, resolveOnlyCommand, behindBaseHint, isBehind, report.viewerAuthorization?.viewerCanUpdate === true);
262
+ const instructions = buildFixInstructions(threads, actionableComments, checks, changesRequestedReviews, baseLookup.branch, resolveCommand, hasConflicts, prReference, cancelled.length, firstLookThreads, firstLookComments, firstLookSummaries, editedSummaries, inProgressRunIds, resolutionOnlyThreads, resolveOnlyCommand, behindBaseHint, isBehind, report.viewerAuthorization?.viewerCanUpdate === true, exhaustedAttempts.length > 0);
261
263
  const prospectiveResult = {
262
264
  ...base,
263
265
  baseBranch: baseLookup.branch,
@@ -2,4 +2,4 @@ import type { AgentThread, AgentComment, AgentCheck, Review, ResolveCommand, Fir
2
2
  /** Render a resolve command as a shell snippet. Appends `--require-sha "$HEAD_SHA"` when set. */
3
3
  export declare function renderResolveCommand(rc: ResolveCommand): string;
4
4
  export declare function buildFixInstructions(threads: AgentThread[], actionableComments: AgentComment[], checks: AgentCheck[], changesRequestedReviews: Review[], baseBranch: string, resolveCommand: ResolveCommand, hasConflicts: boolean, prReference: string | number, cancelledCount: number, firstLookThreads?: FirstLookThread[], firstLookComments?: FirstLookComment[], firstLookSummaries?: Review[], editedSummaries?: Review[], inProgressRunIds?: string[], resolutionOnlyThreads?: ReviewThread[], resolveOnlyCommand?: ResolveCommand, behindBaseHint?: string, // iterate.behindBaseHint — see buildBehindBaseHintInstruction
5
- isBehind?: boolean, viewerCanUpdate?: boolean): string[];
5
+ isBehind?: boolean, viewerCanUpdate?: boolean, hasExhaustedWorkflowRerun?: boolean): string[];
@@ -1,5 +1,5 @@
1
1
  import { renderShellCommand } from "../../cli/runner.mjs";
2
- import { buildFailingCheckInstructions, buildCrStaleClause, buildBehindBaseHintInstruction, buildResolveCommandInstruction, buildFixCompletionInstruction, } from "./check-instructions.mjs";
2
+ import { buildFailingCheckInstructions, buildCrStaleClause, buildBehindBaseHintInstruction, buildRepeatedWorkflowBranchRecoveryInstructions, buildResolveCommandInstruction, buildFixCompletionInstruction, } from "./check-instructions.mjs";
3
3
  import { SHEPHERD_JOURNAL_FIRST_LOOK_GUIDANCE, buildShepherdJournalInstruction, } from "../shepherd-journal.mjs";
4
4
  import { isFailingAgentCheck } from "../../checks/conclusions.mjs";
5
5
  import { buildCommitSuggestionInstruction } from "../commit-suggestion-instruction.mjs";
@@ -11,11 +11,16 @@ export function renderResolveCommand(rc) {
11
11
  return renderShellCommand(parts);
12
12
  }
13
13
  export function buildFixInstructions(threads, actionableComments, checks, changesRequestedReviews, baseBranch, resolveCommand, hasConflicts, prReference, cancelledCount, firstLookThreads = [], firstLookComments = [], firstLookSummaries = [], editedSummaries = [], inProgressRunIds = [], resolutionOnlyThreads = [], resolveOnlyCommand, behindBaseHint = "", // iterate.behindBaseHint — see buildBehindBaseHintInstruction
14
- isBehind = false, viewerCanUpdate = false) {
14
+ isBehind = false, viewerCanUpdate = false, hasExhaustedWorkflowRerun = false) {
15
15
  const instructions = [];
16
16
  const locatedThreads = threads.filter((thread) => thread.path !== null && thread.line !== null);
17
17
  const unlocatedThreads = threads.filter((thread) => thread.path === null || thread.line === null);
18
18
  const failingChecks = checks.filter((c) => isFailingAgentCheck(c));
19
+ const repeatedWorkflowBranchRecoveryInstructions = buildRepeatedWorkflowBranchRecoveryInstructions(baseBranch, hasExhaustedWorkflowRerun, {
20
+ isBehind,
21
+ hasConflicts,
22
+ });
23
+ const hasRepeatedWorkflowBranchRecovery = repeatedWorkflowBranchRecoveryInstructions.length > 0;
19
24
  const hasAnnotations = checks.some((c) => (c.annotations?.length ?? 0) > 0);
20
25
  const hasNonConflictHints = threads.length > 0 ||
21
26
  failingChecks.length > 0 ||
@@ -41,7 +46,7 @@ isBehind = false, viewerCanUpdate = false) {
41
46
  const sectionRef = actionableSections.length > 0 ? `under ${actionableSections.join(", ")}` : "above";
42
47
  instructions.push(`Review each item ${sectionRef} and decide whether it needs a code change.`);
43
48
  }
44
- if (hasConflicts) {
49
+ if (hasConflicts && !hasRepeatedWorkflowBranchRecovery) {
45
50
  instructions.push("The branch has merge conflicts (see `**branch**` above). Resolve them before committing.");
46
51
  }
47
52
  const firstLookTotal = firstLookThreads.length + firstLookComments.length;
@@ -76,7 +81,7 @@ isBehind = false, viewerCanUpdate = false) {
76
81
  if (resolutionOnlyThreads.length > 0) {
77
82
  instructions.push('Review the threads under `## Review threads to resolve` before running mutations. Use the generated commands as shown — see "Review-mutation routing" in the pr-shepherd skill for which flag applies to which ID.');
78
83
  }
79
- instructions.push(...buildFailingCheckInstructions(failingChecks));
84
+ instructions.push(...buildFailingCheckInstructions(failingChecks), ...repeatedWorkflowBranchRecoveryInstructions);
80
85
  if (hasAnnotations) {
81
86
  instructions.push("Inspect every referenced range under `## Check annotations` and apply any warranted change.");
82
87
  }
@@ -90,8 +95,11 @@ isBehind = false, viewerCanUpdate = false) {
90
95
  if (hasConflicts) {
91
96
  instructions.push(`Commit any remaining conflict-resolution changes and push to the PR head branch${mutationSuffix}.`);
92
97
  }
98
+ else if (hasRepeatedWorkflowBranchRecovery) {
99
+ instructions.push("Push the updated PR head branch before iterating immediately.");
100
+ }
93
101
  else if (hasNonConflictHints) {
94
- instructions.push("If you changed code, commit any remaining changes and push to the PR head branch, then run the remaining review mutations using the pushed commit SHA and iterate again with the same options. If you did not change code, do not commit and continue with the remaining steps.");
102
+ instructions.push("If you changed code, commit any remaining changes and push to the PR head branch, then run the remaining review mutations using the pushed commit SHA and iterate immediately with the same options. If you did not change code, do not commit and continue with the remaining steps.");
95
103
  }
96
104
  if (viewerCanUpdate &&
97
105
  (hasReviewMutations ||
@@ -2,5 +2,5 @@ export function buildQuotaAwareContinuation(warning, prefix) {
2
2
  const interval = `${warning.pollIntervalMinutes}m`;
3
3
  const timeout = `${warning.pollTimeoutMinutes}m`;
4
4
  const resetTime = new Date(warning.resetAt * 1000).toISOString();
5
- return `${prefix} GitHub's GraphQL API quota is low (crossed the ${warning.thresholdPercent}% remaining threshold). For the time being, prefer non-GraphQL \`gh\` CLI commands (e.g. \`gh pr view\`, \`gh pr checks\`, \`gh pr review\`, \`gh api\` REST endpoints) for PR operations — they draw on the separate REST budget, not the depleted GraphQL pool. Resume pr-shepherd after the GraphQL quota resets at ${resetTime}. If you must keep polling before then, poll no more often than every ${warning.pollIntervalMinutes} minutes. With a polling CLI command, preserve the other options, replace any existing interval and timeout flags with \`--interval ${interval} --timeout ${timeout}\`, and omit \`--timeout\` when using \`--until-terminal\`. With a single-tick CLI, API, or MCP call, wait at least ${warning.pollIntervalMinutes} minutes before the next tick.`;
5
+ return `${prefix} GitHub's GraphQL API quota is low (crossed the ${warning.thresholdPercent}% remaining threshold). Keep using pr-shepherd at the cadence below; for incidental PR operations that do not need Shepherd's full snapshot, prefer non-GraphQL \`gh\` CLI commands (e.g. \`gh pr view\`, \`gh pr review\`, \`gh api\` REST endpoints) — they draw on the separate REST budget, not the depleted GraphQL pool. Do not substitute \`gh pr checks\` or \`gh pr watch\` for the Shepherd loop. Resume full-cadence pr-shepherd after the GraphQL quota resets at ${resetTime}. If you must keep polling before then, poll no more often than every ${warning.pollIntervalMinutes} minutes. With a polling CLI command, preserve the other options, replace any existing interval and timeout flags with \`--interval ${interval} --timeout ${timeout}\`, and omit \`--timeout\` when using \`--until-terminal\`. With a single-tick CLI, API, or MCP call, wait at least ${warning.pollIntervalMinutes} minutes before the next tick.`;
6
6
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pr-shepherd",
3
- "version": "0.46.3",
3
+ "version": "0.46.5",
4
4
  "description": "Autonomous PR CI monitor and review-comment resolver for agentic coding tools",
5
5
  "keywords": [
6
6
  "automation",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pr-shepherd",
3
- "version": "0.46.3",
3
+ "version": "0.46.5",
4
4
  "description": "Autonomous PR CI monitor and review-comment resolver for Codex.",
5
5
  "author": {
6
6
  "name": "Jonathan Ong",
@@ -2,7 +2,7 @@
2
2
  "mcpServers": {
3
3
  "pr-shepherd": {
4
4
  "command": "npx",
5
- "args": ["--yes", "--package", "pr-shepherd@0.46.3", "pr-shepherd-mcp"]
5
+ "args": ["--yes", "--package", "pr-shepherd@0.46.5", "pr-shepherd-mcp"]
6
6
  }
7
7
  }
8
8
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "pr-shepherd": {
3
3
  "command": "npx",
4
- "args": ["--yes", "--package", "pr-shepherd@0.46.3", "pr-shepherd-mcp"]
4
+ "args": ["--yes", "--package", "pr-shepherd@0.46.5", "pr-shepherd-mcp"]
5
5
  }
6
6
  }
@@ -8,7 +8,7 @@ allowed-tools: ["MCP", "Bash", "Read", "Grep", "Glob", "Edit", "Write"]
8
8
 
9
9
  # pr-shepherd
10
10
 
11
- Thin dispatcher for creating or iterating a PR. Poll with the CLI; use MCP `iterate` only when the CLI is unavailable.
11
+ Thin dispatcher for creating or iterating a PR. Poll with the CLI; use MCP `iterate` only when the CLI is unavailable. Keep invoking until `[CANCEL]` or `[ESCALATE]`. Do not wait for CI to finish before the next Shepherd step — fetching check logs is fine; blocking on `gh pr checks`, `gh pr watch`, `gh run watch`, or equivalent GitHub MCP check waiters is not. Shepherd already returns CI plus review comments, and waiting for CI before applying review feedback wastes CI.
12
12
 
13
13
  ## PR creation authorization
14
14
 
@@ -20,11 +20,11 @@ If the requested PR does not exist yet, review and commit the in-scope changes,
20
20
 
21
21
  1. Parse an optional PR number, repository-qualified `owner/repo#N`, or GitHub PR URL and an optional `--merge` flag from `$ARGUMENTS`; otherwise let pr-shepherd infer the current branch PR. Reject any remaining argument. Follow the target repository's local `AGENTS.md` and `CLAUDE.md` standards while making changes.
22
22
 
23
- 2. For the CLI, convert supplied `owner/repo#N` to `https://github.com/owner/repo/pull/N`; otherwise pass the supplied URL or bare number unchanged, then run the poll command `pr-shepherd` with the optional PR argument and forward `--merge` when supplied. A qualified reference may name a fork or upstream repository: it is the GitHub target, while the current checkout continues to supply local git/config/rules context. Do not run `pr-shepherd iterate`. If the CLI is unavailable and the `iterate` MCP tool is available, first obtain a repository-qualified reference: use a supplied GitHub PR URL or `owner/repo#N` unchanged; for a bare number, run `gh pr view <number> --json url --jq .url`; when omitted, run `gh pr view --json url --jq .url`. If that does not produce one qualified PR reference, stop and report that MCP cannot safely determine the PR. Otherwise call `iterate` with that qualified reference, plus `merge: true` when `--merge` was supplied, and print its full result.
23
+ 2. For the CLI, convert supplied `owner/repo#N` to `https://github.com/owner/repo/pull/N`; otherwise pass the supplied URL or bare number unchanged, then run the canonical poll command `pr-shepherd [PR] --until-terminal`, omitting `[PR]` when none was supplied and appending `--merge` when requested. This command keeps ordinary `[WAIT]` and `[MARK_READY]` ticks inside the same invocation; it returns for agent-facing work, a quota warning, `[CANCEL]`, or `[ESCALATE]`. A qualified reference may name a fork or upstream repository: it is the GitHub target, while the current checkout continues to supply local git/config/rules context. Do not run `pr-shepherd iterate`. If the CLI is unavailable and the `iterate` MCP tool is available, first obtain a repository-qualified reference: use a supplied GitHub PR URL or `owner/repo#N` unchanged; for a bare number, run `gh pr view <number> --json url --jq .url`; when omitted, run `gh pr view --json url --jq .url`. If that does not produce one qualified PR reference, stop and report that MCP cannot safely determine the PR. Otherwise call `iterate` with that qualified reference, plus `merge: true` when `--merge` was supplied, and print its full result.
24
24
 
25
25
  3. Print the full result and follow every returned `## Instructions` step exactly. For CLI output, run each printed mutation command when instructed. For MCP output, use MCP `apply` and `build_suggestion_patches` with the same qualified PR reference; do not run a shell `pr-shepherd apply` command.
26
26
 
27
- 4. After completing the returned instructions, repeat step 2 unless the action is `[CANCEL]` or `[ESCALATE]`, or the human directs you to stop. `[FIX_CODE]` is always non-terminal: complete its instructions and rerun the same canonical command without asking whether to continue. Only `[ESCALATE]` hands work to a human.
27
+ 4. After completing the returned instructions, immediately repeat step 2 with the same target and canonical options unless the action is `[CANCEL]` or `[ESCALATE]`, or the human directs you to stop. Preserve `--until-terminal` and any requested `--merge`; apply any polling-cadence adjustment printed by the CLI. Every other action is non-terminal: complete its instructions and rerun without asking whether to continue. `[FIX_CODE]` is always non-terminal, and only `[ESCALATE]` hands work to a human. After a push or `rerun:`, do not wait for CI to finish first — you may pull check logs, but do not poll with `gh pr checks`, `gh pr watch`, `gh run watch`, or equivalent GitHub MCP check waiters.
28
28
 
29
29
  ## Playbooks
30
30
 
@@ -50,15 +50,15 @@ A `[rerun authorized]` tag with a `rerun:` command means the viewer's repository
50
50
 
51
51
  When several bullets share one runId (matrix jobs from the same run), the `rerun:` command is printed once, on the first bullet; every bullet for that runId still carries `[rerun authorized]` and is covered by that single command — do not run it more than once.
52
52
 
53
- | Tag / kind | Do |
54
- | ------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
55
- | GitHub Actions failure (has a run ID, not `CANCELLED`/`STARTUP_FAILURE`) | Read the included log excerpt. Apply a warranted code fix, or run the printed `rerun:` command when the evidence indicates a transient failure, then iterate again. Missing autonomous follow-up becomes `[ESCALATE]` when no other work remains. |
56
- | Transient infrastructure failure | Run the `rerun:` command when present, then iterate again once it completes. If no command is present, complete any other surfaced work and iterate; Shepherd owns any later `[ESCALATE]`. |
57
- | Real test or build failure | Apply a code fix — do not rerun, even if `[rerun authorized]` is shown. |
58
- | `[conclusion: CANCELLED]` | No log excerpt is rendered. Run the printed `rerun:` command, then iterate again once it completes. Without a command, complete any other work and iterate; Shepherd escalates when this remains the only blocker. |
59
- | `[conclusion: STARTUP_FAILURE]` | No log excerpt is rendered. Run the printed `rerun:` command, then iterate again once it completes. Without a command, complete any other work and iterate; Shepherd escalates when this remains the only blocker. |
60
- | `[conclusion: ACTION_REQUIRED]` | This appears in `[FIX_CODE]` only alongside other autonomous work. Complete that work and iterate; Shepherd returns `[ESCALATE]` if manual workflow approval remains necessary. |
61
- | `external` (no run ID, has a URL) | Treat the URL as an autonomous investigation path: inspect the provider or reproduce the failure locally, apply any warranted fix, and iterate. A non-empty external URL does not trigger `[ESCALATE]` by itself. |
53
+ | Tag / kind | Do |
54
+ | ------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
55
+ | GitHub Actions failure (has a run ID, not `CANCELLED`/`STARTUP_FAILURE`) | Read the included log excerpt. Apply a warranted code fix, or run the printed `rerun:` command when the evidence indicates a transient failure, then iterate immediately. Missing autonomous follow-up becomes `[ESCALATE]` when no other work remains. |
56
+ | Transient infrastructure failure | Run the `rerun:` command when present, then iterate immediately. Do not wait for the rerun to finish. If no command is present, complete any other surfaced work and iterate; Shepherd owns any later `[ESCALATE]`. |
57
+ | Real test or build failure | Apply a code fix — do not rerun, even if `[rerun authorized]` is shown. |
58
+ | `[conclusion: CANCELLED]` | No log excerpt is rendered. Run the printed `rerun:` command, then iterate immediately. Do not wait for the rerun to finish. Without a command, complete any other work and iterate; Shepherd escalates when this remains the only blocker. |
59
+ | `[conclusion: STARTUP_FAILURE]` | No log excerpt is rendered. Run the printed `rerun:` command, then iterate immediately. Do not wait for the rerun to finish. Without a command, complete any other work and iterate; Shepherd escalates when this remains the only blocker. |
60
+ | `[conclusion: ACTION_REQUIRED]` | This appears in `[FIX_CODE]` only alongside other autonomous work. Complete that work and iterate; Shepherd returns `[ESCALATE]` if manual workflow approval remains necessary. |
61
+ | `external` (no run ID, has a URL) | Treat the URL as an autonomous investigation path: inspect the provider or reproduce the failure locally, apply any warranted fix, and iterate. A non-empty external URL does not trigger `[ESCALATE]` by itself. |
62
62
 
63
63
  ### Review-mutation mechanics
64
64