pr-shepherd 0.15.0 → 0.15.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.15.0",
4
+ "version": "0.15.2",
5
5
  "author": {
6
6
  "name": "Jonathan Ong",
7
7
  "email": "jonathanrichardong@gmail.com"
package/README.md CHANGED
@@ -89,7 +89,7 @@ _(schematic — actual steps depend on PR state)_
89
89
  3. Commit changed files.
90
90
  4. Rebase and push: `git fetch origin && git rebase origin/main && git push --force-with-lease` — capture `HEAD_SHA=$(git rev-parse HEAD)`.
91
91
  5. Run the `resolve:` command above, substituting `"$HEAD_SHA"`.
92
- 6. Add a `## Shepherd Journal` entry to the PR description for any large decisions made.
92
+ 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.
93
93
  7. Stop this iteration.
94
94
  ```
95
95
 
@@ -126,7 +126,7 @@ Some other workflow improvements:
126
126
 
127
127
  Recommendations:
128
128
 
129
- - Run `pr-shepherd` on all your PRs before you go to sleep so that you wake up to reviewable PRs. In Claude Code, `/pr-shepherd:monitor` uses `/loop` and continues working when your rate limit window is reset. In Codex, keep an active goal cycling the reusable command every `watch.interval` (default 4m) until Shepherd emits `[CANCEL]` for ready-delay completion or merged/closed, or `[ESCALATE]` (including `stall-timeout` for repeated unchanged CI failures).
129
+ - Run `pr-shepherd` on all your PRs before you go to sleep so that you wake up to reviewable PRs. In Claude Code, `/pr-shepherd:monitor` uses `/loop` and continues working when your rate limit window is reset. In Codex, keep an active goal cycling the reusable command with a fresh 1-4 minute sleep before each rerun until Shepherd emits `[CANCEL]` for ready-delay completion or merged/closed, or `[ESCALATE]` (including `stall-timeout` for repeated unchanged CI failures).
130
130
  - 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.
131
131
  - 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.
132
132
  - Avoid having automation edit comments, reviews, or threads in place because updated items get minimized. Instead, always make a new review, comment, thread, etc.
@@ -279,7 +279,7 @@ Or ask Codex to use the `pr-shepherd` skill, for example: `run pr-shepherd until
279
279
  <runner> pr-shepherd 42
280
280
  ```
281
281
 
282
- For an active Codex goal, rerun that command every `watch.interval` (default 4m) until Shepherd emits `[CANCEL]` for ready-delay completion or merged/closed, or `[ESCALATE]` (including `stall-timeout` for repeated unchanged CI failures). `pr-shepherd iterate 42` remains supported for existing workflows. There is no background `/loop` scheduler in Codex.
282
+ For an active Codex goal, rerun that command after picking a fresh 1-4 minute sleep until Shepherd emits `[CANCEL]` for ready-delay completion or merged/closed, or `[ESCALATE]` (including `stall-timeout` for repeated unchanged CI failures). `pr-shepherd iterate 42` remains supported for existing workflows. There is no background `/loop` scheduler in Codex.
283
283
 
284
284
  ### Without the plugin
285
285
 
@@ -6,7 +6,6 @@ import { adaptFixCodeInstructions, numberInstructions } from "./iterate-instruct
6
6
  export function formatFixCodeResult(header, result, opts) {
7
7
  const runtime = opts?.runtime ?? "claude";
8
8
  const readyDelaySuffix = opts?.readyDelaySuffix;
9
- const retryInterval = opts?.retryInterval;
10
9
  const runner = opts?.runner;
11
10
  const sections = [header];
12
11
  if (result.fix.threads.length > 0) {
@@ -118,7 +117,7 @@ export function formatFixCodeResult(header, result, opts) {
118
117
  }
119
118
  sections.push(postFixLines.join("\n"));
120
119
  sections.push("## Instructions");
121
- sections.push(numberInstructions(adaptFixCodeInstructions(result.fix.instructions, result.pr, runtime, readyDelaySuffix, retryInterval, runner)));
120
+ sections.push(numberInstructions(adaptFixCodeInstructions(result.fix.instructions, result.pr, runtime, readyDelaySuffix, runner)));
122
121
  return joinSections(sections);
123
122
  }
124
123
  function blockquote(body) {
@@ -66,7 +66,6 @@ export async function handleIterate(args) {
66
66
  const projectionOpts = {
67
67
  runtime,
68
68
  readyDelaySuffix,
69
- retryInterval: cfg.watch.interval,
70
69
  runner: cfg.cli?.runner,
71
70
  };
72
71
  if (globalOpts.format === "json") {
@@ -20,7 +20,6 @@ export function formatIterateResult(result, opts) {
20
20
  const verbose = opts?.verbose ?? false;
21
21
  const runtime = opts?.runtime ?? "claude";
22
22
  const readyDelaySuffix = opts?.readyDelaySuffix;
23
- const retryInterval = opts?.retryInterval;
24
23
  const runner = opts?.runner;
25
24
  const heading = `# PR #${result.pr} [${result.action.toUpperCase()}]`;
26
25
  const reviewDecisionSeg = result.mergeStatus === "BLOCKED" && result.reviewDecision
@@ -57,37 +56,36 @@ export function formatIterateResult(result, opts) {
57
56
  return joinSections([
58
57
  verbose ? header : heading,
59
58
  adaptIterateLog(result.log, runtime),
60
- `## Instructions\n\n${numberInstructions(buildSimpleIterateInstructions(result, runtime, readyDelaySuffix, retryInterval, runner))}`,
59
+ `## Instructions\n\n${numberInstructions(buildSimpleIterateInstructions(result, runtime, readyDelaySuffix, runner))}`,
61
60
  ]);
62
61
  case "wait":
63
62
  return joinSections([
64
63
  header,
65
64
  adaptIterateLog(result.log, runtime),
66
- `## Instructions\n\n${numberInstructions(buildSimpleIterateInstructions(result, runtime, readyDelaySuffix, retryInterval, runner))}`,
65
+ `## Instructions\n\n${numberInstructions(buildSimpleIterateInstructions(result, runtime, readyDelaySuffix, runner))}`,
67
66
  ]);
68
67
  case "mark_ready":
69
68
  return joinSections([
70
69
  header,
71
70
  adaptIterateLog(result.log, runtime),
72
- `## Instructions\n\n${numberInstructions(buildSimpleIterateInstructions(result, runtime, readyDelaySuffix, retryInterval, runner))}`,
71
+ `## Instructions\n\n${numberInstructions(buildSimpleIterateInstructions(result, runtime, readyDelaySuffix, runner))}`,
73
72
  ]);
74
73
  case "cancel":
75
74
  return joinSections([
76
75
  [`${heading} — ${result.reason}`, "", baseLine, summaryLine].join("\n"),
77
76
  adaptIterateLog(result.log, runtime),
78
- `## Instructions\n\n${numberInstructions(buildSimpleIterateInstructions(result, runtime, readyDelaySuffix, retryInterval, runner))}`,
77
+ `## Instructions\n\n${numberInstructions(buildSimpleIterateInstructions(result, runtime, readyDelaySuffix, runner))}`,
79
78
  ]);
80
79
  case "escalate":
81
80
  return joinSections([
82
81
  header,
83
82
  result.escalate.humanMessage,
84
- `## Instructions\n\n${numberInstructions(buildSimpleIterateInstructions(result, runtime, readyDelaySuffix, retryInterval, runner))}`,
83
+ `## Instructions\n\n${numberInstructions(buildSimpleIterateInstructions(result, runtime, readyDelaySuffix, runner))}`,
85
84
  ]);
86
85
  case "fix_code":
87
86
  return formatFixCodeResult(header, result, {
88
87
  runtime,
89
88
  readyDelaySuffix,
90
- retryInterval,
91
89
  runner,
92
90
  });
93
91
  }
@@ -1,28 +1,30 @@
1
1
  import { FIX_INSTRUCTION_END_ITERATION, FIX_INSTRUCTION_STOP_AFTER_PUSH, FIX_INSTRUCTION_STOP_BEFORE_NEXT_TICK, } from "../commands/iterate/render.mjs";
2
2
  import { buildPrShepherdCommand } from "./runner.mjs";
3
- const DEFAULT_CODEX_RETRY_INTERVAL = "4m";
4
- const VALID_RETRY_INTERVAL = /^\d+[smhd]$/;
5
3
  const CLAUDE_DYNAMIC_WAKEUP = "Schedule the next dynamic wakeup with `ScheduleWakeup` using `delaySeconds` between 60 and 240 and the same loop prompt (skip if running under a fixed-interval cron loop — the next scheduled fire handles it), then end this iteration.";
6
- export function buildSimpleIterateInstructions(result, runtime, readyDelaySuffix, retryInterval, runner) {
4
+ const CODEX_DYNAMIC_WAIT = "pick a fresh sleep/timeout between 1 and 4 minutes, wait that long";
5
+ function capitalizeFirst(value) {
6
+ return `${value.charAt(0).toUpperCase()}${value.slice(1)}`;
7
+ }
8
+ export function buildSimpleIterateInstructions(result, runtime, readyDelaySuffix, runner) {
7
9
  const rerunCommand = buildCodexIterateCommand(result.pr, readyDelaySuffix, runner);
8
- const retryDelay = buildCodexRetryDelay(retryInterval);
10
+ const retryDelay = buildCodexRetryDelay();
9
11
  switch (result.action) {
10
12
  case "cooldown":
11
13
  return [
12
14
  runtime === "codex"
13
- ? `Continue the active Codex goal — wait about ${retryDelay}, then rerun \`${rerunCommand}\` after CI starts reporting.`
15
+ ? `Continue the active Codex goal — ${retryDelay}, then rerun \`${rerunCommand}\` after CI starts reporting.`
14
16
  : `CI still needs time to start reporting. ${CLAUDE_DYNAMIC_WAKEUP}`,
15
17
  ];
16
18
  case "wait":
17
19
  return [
18
20
  runtime === "codex"
19
- ? `Continue the active Codex goal — wait about ${retryDelay}, then rerun \`${rerunCommand}\` to recheck.`
21
+ ? `Continue the active Codex goal — ${retryDelay}, then rerun \`${rerunCommand}\` to recheck.`
20
22
  : CLAUDE_DYNAMIC_WAKEUP,
21
23
  ];
22
24
  case "mark_ready":
23
25
  return [
24
26
  runtime === "codex"
25
- ? `The CLI already marked the PR ready for review. Continue the active Codex goal until the ready-delay completes — wait about ${retryDelay}, then rerun \`${rerunCommand}\` to recheck.`
27
+ ? `The CLI already marked the PR ready for review. Continue the active Codex goal until the ready-delay completes — ${retryDelay}, then rerun \`${rerunCommand}\` to recheck.`
26
28
  : `The CLI already marked the PR ready for review. ${CLAUDE_DYNAMIC_WAKEUP}`,
27
29
  ];
28
30
  case "cancel":
@@ -41,18 +43,18 @@ export function buildSimpleIterateInstructions(result, runtime, readyDelaySuffix
41
43
  ];
42
44
  }
43
45
  }
44
- export function adaptFixCodeInstructions(instructions, pr, runtime, readyDelaySuffix, retryInterval, runner) {
46
+ export function adaptFixCodeInstructions(instructions, pr, runtime, readyDelaySuffix, runner) {
45
47
  if (runtime !== "codex")
46
48
  return instructions;
47
49
  const rerunCommand = buildCodexIterateCommand(pr, readyDelaySuffix, runner);
48
- const retryDelay = buildCodexRetryDelay(retryInterval);
50
+ const retryDelay = buildCodexRetryDelay();
49
51
  return instructions.map((instruction) => {
50
52
  if (instruction === FIX_INSTRUCTION_STOP_AFTER_PUSH) {
51
- return `Continue the active Codex goal — CI needs time to run on the new push. Wait about ${retryDelay}, then rerun \`${rerunCommand}\` to recheck.`;
53
+ return `Continue the active Codex goal — CI needs time to run on the new push. ${capitalizeFirst(retryDelay)}, then rerun \`${rerunCommand}\` to recheck.`;
52
54
  }
53
55
  if (instruction === FIX_INSTRUCTION_STOP_BEFORE_NEXT_TICK ||
54
56
  instruction === FIX_INSTRUCTION_END_ITERATION) {
55
- return `Continue the active Codex goal — wait about ${retryDelay}, then rerun \`${rerunCommand}\` to recheck.`;
57
+ return `Continue the active Codex goal — ${retryDelay}, then rerun \`${rerunCommand}\` to recheck.`;
56
58
  }
57
59
  return instruction;
58
60
  });
@@ -68,11 +70,8 @@ export function buildCodexIterateCommand(pr, readyDelaySuffix, runner) {
68
70
  runner,
69
71
  }).text;
70
72
  }
71
- export function buildCodexRetryDelay(retryInterval) {
72
- const interval = typeof retryInterval === "string" ? retryInterval.trim() : "";
73
- return VALID_RETRY_INTERVAL.test(interval)
74
- ? `the configured interval (${interval})`
75
- : `the configured interval (default ${DEFAULT_CODEX_RETRY_INTERVAL})`;
73
+ export function buildCodexRetryDelay() {
74
+ return CODEX_DYNAMIC_WAIT;
76
75
  }
77
76
  export function numberInstructions(instructions) {
78
77
  return instructions.map((instruction, i) => `${i + 1}. ${instruction}`).join("\n");
@@ -7,9 +7,8 @@ import { adaptIterateLog, adaptFixCodeInstructions, buildSimpleIterateInstructio
7
7
  export function projectIterateLean(result, opts) {
8
8
  const runtime = opts?.runtime ?? "claude";
9
9
  const readyDelaySuffix = opts?.readyDelaySuffix;
10
- const retryInterval = opts?.retryInterval;
11
10
  const runner = opts?.runner;
12
- const simpleInstructions = (r) => buildSimpleIterateInstructions(r, runtime, readyDelaySuffix, retryInterval, runner);
11
+ const simpleInstructions = (r) => buildSimpleIterateInstructions(r, runtime, readyDelaySuffix, runner);
13
12
  const base = {
14
13
  action: result.action,
15
14
  pr: result.pr,
@@ -102,7 +101,7 @@ export function projectIterateLean(result, opts) {
102
101
  }),
103
102
  resolveCommand: result.fix.resolveCommand,
104
103
  ...(result.fix.instructions.length > 0 && {
105
- instructions: adaptFixCodeInstructions(result.fix.instructions, result.pr, runtime, readyDelaySuffix, retryInterval, runner),
104
+ instructions: adaptFixCodeInstructions(result.fix.instructions, result.pr, runtime, readyDelaySuffix, runner),
106
105
  }),
107
106
  },
108
107
  };
@@ -134,14 +133,13 @@ export function projectIterateLean(result, opts) {
134
133
  export function projectIterateVerbose(result, opts) {
135
134
  const runtime = opts?.runtime ?? "claude";
136
135
  const readyDelaySuffix = opts?.readyDelaySuffix;
137
- const retryInterval = opts?.retryInterval;
138
136
  const runner = opts?.runner;
139
137
  if (result.action === "fix_code") {
140
138
  return {
141
139
  ...result,
142
140
  fix: {
143
141
  ...result.fix,
144
- instructions: adaptFixCodeInstructions(result.fix.instructions, result.pr, runtime, readyDelaySuffix, retryInterval, runner),
142
+ instructions: adaptFixCodeInstructions(result.fix.instructions, result.pr, runtime, readyDelaySuffix, runner),
145
143
  },
146
144
  };
147
145
  }
@@ -151,6 +149,6 @@ export function projectIterateVerbose(result, opts) {
151
149
  return {
152
150
  ...result,
153
151
  ...log,
154
- instructions: buildSimpleIterateInstructions(result, runtime, readyDelaySuffix, retryInterval, runner),
152
+ instructions: buildSimpleIterateInstructions(result, runtime, readyDelaySuffix, runner),
155
153
  };
156
154
  }
@@ -1,5 +1,6 @@
1
1
  import { buildPrShepherdCommand, renderShellCommand } from "../../cli/runner.mjs";
2
2
  import { buildFailingCheckInstructions } from "./check-instructions.mjs";
3
+ import { SHEPHERD_JOURNAL_FIRST_LOOK_GUIDANCE, SHEPHERD_JOURNAL_REFERENCE_GUIDANCE_THREADS_AND_COMMENTS_IN_ITEM_HEADINGS, buildShepherdJournalInstruction, } from "../shepherd-journal.mjs";
3
4
  export const FIX_INSTRUCTION_STOP_AFTER_PUSH = "Stop this iteration — CI needs time to run on the new push before the next tick.";
4
5
  export const FIX_INSTRUCTION_STOP_BEFORE_NEXT_TICK = "Stop this iteration before the next tick.";
5
6
  export const FIX_INSTRUCTION_END_ITERATION = "End this iteration.";
@@ -68,7 +69,7 @@ export function buildFixInstructions(threads, actionableComments, checks, review
68
69
  instructions.push(`Items in \`## First-look items\` are shown so you can acknowledge their current status before acting. If a first-look thread also appears under \`## Review threads to resolve\`, its ID is already included in the \`resolve:\` command; otherwise do not pass first-look-only IDs to mutation flags.`);
69
70
  }
70
71
  if (firstLookSummaries.length > 0) {
71
- instructions.push(`Review the bodies shown under \`## Review summaries (first look — to be minimized)\` — you are seeing these for the first time. Their IDs are already included in the \`resolve:\` command's \`--minimize-comment-ids\`; if any warrants a \`## Shepherd Journal\` entry, record it before running resolve.`);
72
+ instructions.push(SHEPHERD_JOURNAL_FIRST_LOOK_GUIDANCE);
72
73
  }
73
74
  const editedTotal = editedSummaries.length +
74
75
  firstLookThreads.filter((t) => t.edited).length +
@@ -91,7 +92,7 @@ export function buildFixInstructions(threads, actionableComments, checks, review
91
92
  instructions.push(`Do not re-run \`gh run cancel\` on the IDs listed under \`## Cancelled runs\` — the CLI cancelled those runs before your push, and your push has already triggered new runs with different IDs.`);
92
93
  }
93
94
  if (resolveCommand.hasMutations) {
94
- instructions.push(`For any large decisions or rejections you made this iteration, add or update a \`## Shepherd Journal\` section in the PR description (\`gh pr edit ${prNumber} --body …\`) summarizing each decision. For threads and comments, use the markdown link shown in its heading above; for reviews, reference the review ID.`);
95
+ instructions.push(buildShepherdJournalInstruction(prNumber, SHEPHERD_JOURNAL_REFERENCE_GUIDANCE_THREADS_AND_COMMENTS_IN_ITEM_HEADINGS));
95
96
  }
96
97
  if (needsPush) {
97
98
  instructions.push(FIX_INSTRUCTION_STOP_AFTER_PUSH);
@@ -20,7 +20,7 @@ export async function runMonitor(opts) {
20
20
  const loopTag = `#pr-shepherd-loop:pr=${prNumber}:`;
21
21
  const loopArgs = interval;
22
22
  const reusableCommand = buildIterateCommand(prNumber, opts.readyDelaySuffix, config.cli?.runner);
23
- const loopPrompt = buildLoopPrompt(prNumber, loopTag, reusableCommand, loopArgs, opts.runtime ?? "claude", config.cli?.runner);
23
+ const loopPrompt = buildLoopPrompt(prNumber, loopTag, reusableCommand, opts.runtime ?? "claude", config.cli?.runner);
24
24
  return {
25
25
  prNumber,
26
26
  loopTag,
@@ -40,8 +40,10 @@ export function formatMonitorResult(result, opts) {
40
40
  `# PR #${prNumber} [MONITOR]`,
41
41
  "",
42
42
  `Loop tag: \`${loopTag}\``,
43
- `Loop args: \`${loopArgs}\``,
44
- ].join("\n"),
43
+ runtime === "codex" ? null : `Loop args: \`${loopArgs}\``,
44
+ ]
45
+ .filter((line) => line !== null)
46
+ .join("\n"),
45
47
  runtime === "codex" ? `Reusable command: \`${result.reusableCommand}\`` : null,
46
48
  "## Loop prompt",
47
49
  loopPrompt,
@@ -54,10 +56,20 @@ export function formatMonitorResult(result, opts) {
54
56
  }
55
57
  export function formatMonitorJson(result, opts) {
56
58
  const runtime = opts?.runtime ?? "claude";
57
- const { reusableCommand, ...base } = result;
59
+ if (runtime === "codex") {
60
+ return {
61
+ prNumber: result.prNumber,
62
+ loopTag: result.loopTag,
63
+ loopPrompt: result.loopPrompt,
64
+ reusableCommand: result.reusableCommand,
65
+ instructions: buildMonitorInstructions(result, runtime),
66
+ };
67
+ }
58
68
  return {
59
- ...base,
60
- ...(runtime === "codex" && { reusableCommand }),
69
+ prNumber: result.prNumber,
70
+ loopTag: result.loopTag,
71
+ loopArgs: result.loopArgs,
72
+ loopPrompt: result.loopPrompt,
61
73
  instructions: buildMonitorInstructions(result, runtime),
62
74
  };
63
75
  }
@@ -77,14 +89,14 @@ function buildIterateCommand(prNumber, readyDelaySuffix, runner) {
77
89
  const validatedDelay = validateReadyDelaySuffix(readyDelaySuffix);
78
90
  return buildPrShepherdCommand([String(prNumber), ...(validatedDelay ? ["--ready-delay", validatedDelay] : [])], { runner }).text;
79
91
  }
80
- function buildLoopPrompt(prNumber, loopTag, iterateCmd, loopArgs, runtime = "claude", runner) {
92
+ function buildLoopPrompt(prNumber, loopTag, iterateCmd, runtime = "claude", runner) {
81
93
  if (runtime === "codex") {
82
94
  return [
83
95
  loopTag,
84
96
  "",
85
97
  "**IMPORTANT — Codex recurrence rules:**",
86
98
  "- Run the command below once and follow its `## Instructions` exactly.",
87
- `- If the output tells you to continue the active Codex goal, wait about the configured interval (${loopArgs}) and rerun the reusable command from the monitor output.`,
99
+ "- If the output tells you to continue the active Codex goal, pick a fresh sleep/timeout between 1 and 4 minutes, wait that long, and rerun the reusable command from the monitor output.",
88
100
  "- Stop only when Shepherd emits `[CANCEL]` because the ready-delay completed or the PR was merged/closed, or when Shepherd emits `[ESCALATE]` (including `stall-timeout` for repeated unchanged CI failures).",
89
101
  `- Do not call \`/loop\`, \`ScheduleWakeup\`, \`CronCreate\`, or \`${buildPrShepherdCommand(["monitor", String(prNumber)], { runner }).text}\`; Codex recurrence is explicit \`iterate\` command cycles.`,
90
102
  "",
@@ -117,7 +129,7 @@ function buildMonitorInstructions(result, runtime) {
117
129
  if (runtime === "codex") {
118
130
  return [
119
131
  "Run the `## Loop prompt` body once inline now.",
120
- `For an active Codex goal, keep cycling with \`${result.reusableCommand}\` about every configured interval (${result.loopArgs}) until a terminal condition is reached. Codex does not create a \`/loop\` monitor.`,
132
+ `For an active Codex goal, keep cycling with \`${result.reusableCommand}\` by picking a fresh sleep/timeout between 1 and 4 minutes before each rerun until a terminal condition is reached. Codex does not create a \`/loop\` monitor.`,
121
133
  ];
122
134
  }
123
135
  return [
@@ -1,4 +1,5 @@
1
1
  import { buildPrShepherdCommand } from "../cli/runner.mjs";
2
+ import { SHEPHERD_JOURNAL_REFERENCE_GUIDANCE_THREADS_AND_COMMENTS_IN_ITEMS, buildShepherdJournalInstruction, } from "./shepherd-journal.mjs";
2
3
  /**
3
4
  * Build the numbered triage/fix/resolve instruction steps for the agent to follow.
4
5
  * Steps are conditionally emitted based on what the fetch returned (mirrors
@@ -64,7 +65,7 @@ export function buildFetchInstructions(prNumber, result, runner) {
64
65
  runner,
65
66
  }).text} [--resolve-thread-ids <ids>] [--minimize-comment-ids <ids>] [--dismiss-review-ids <ids> --message "<reason>"]`;
66
67
  instructions.push(`Run \`${resolveCommand}\` with only the non-empty flag subsets. Skip the command entirely if all three ID lists are empty.${requireShaHint}${dismissNote}`);
67
- instructions.push(`For any large decisions or rejections you made this iteration, add or update a \`## Shepherd Journal\` section in the PR description (\`gh pr edit ${prNumber} --body …\`) summarizing each decision. For threads and comments, use the markdown link shown in each item's bullet above; for reviews, reference the review ID.`);
68
+ instructions.push(buildShepherdJournalInstruction(prNumber, SHEPHERD_JOURNAL_REFERENCE_GUIDANCE_THREADS_AND_COMMENTS_IN_ITEMS));
68
69
  instructions.push(`Report: echo the CLI's mutation output, then one line per Acknowledged item: \`Acknowledged <id> (@<author>): <reason>\`. If any fetched item was neither resolved nor acknowledged, stop and escalate: "<N> item(s) fetched but not acted on or acknowledged — need human direction before closing".`);
69
70
  return instructions;
70
71
  }
@@ -0,0 +1,13 @@
1
+ export const SHEPHERD_JOURNAL_SECTION = "## Shepherd Journal";
2
+ export const SHEPHERD_JOURNAL_SECTION_PATTERN = /^##\s+Shepherd\s+Journal$/;
3
+ export const SHEPHERD_JOURNAL_APPEND_HINT = "If this section already exists, append your entries under it instead of creating a duplicate heading.";
4
+ export const SHEPHERD_JOURNAL_FIRST_LOOK_GUIDANCE = "Review the bodies shown under `## Review summaries (first look — to be minimized)` — you are seeing these for the first time. Their IDs are already included in the resolve command's `--minimize-comment-ids`; if any warrants a Shepherd Journal note, append it before running resolve.";
5
+ export function buildShepherdJournalInstruction(prNumber, itemReferenceGuidance) {
6
+ return [
7
+ `For any large decisions or rejections you made this iteration, add or update a \`${SHEPHERD_JOURNAL_SECTION}\` section in the PR description (\`gh pr edit ${prNumber} --body …\`) summarizing each decision.`,
8
+ itemReferenceGuidance,
9
+ SHEPHERD_JOURNAL_APPEND_HINT,
10
+ ].join(" ");
11
+ }
12
+ export const SHEPHERD_JOURNAL_REFERENCE_GUIDANCE_THREADS_AND_COMMENTS_IN_ITEM_HEADINGS = "For threads and comments, use the markdown link shown in its heading above; for reviews, reference the review ID.";
13
+ export const SHEPHERD_JOURNAL_REFERENCE_GUIDANCE_THREADS_AND_COMMENTS_IN_ITEMS = "For threads and comments, use the markdown link shown in each item's bullet above; for reviews, reference the review ID.";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pr-shepherd",
3
- "version": "0.15.0",
3
+ "version": "0.15.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",
@@ -29,7 +29,7 @@
29
29
  "@types/node": "^25.6.0",
30
30
  "@vitest/coverage-v8": "^4.1.4",
31
31
  "husky": "^9.1.7",
32
- "oxfmt": "^0.47.0",
32
+ "oxfmt": "^0.48.0",
33
33
  "oxlint": "^1.60.0",
34
34
  "typescript": "^6.0.3",
35
35
  "vitest": "^4.1.4"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pr-shepherd",
3
- "version": "0.15.0",
3
+ "version": "0.15.2",
4
4
  "description": "Autonomous PR CI monitor and review-comment resolver for Codex.",
5
5
  "author": {
6
6
  "name": "Jonathan Ong",
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: pr-shepherd
3
- description: 'Codex-only skill for checking, updating, monitoring, or resolving a GitHub pull request with pr-shepherd. Use for requests like "check this PR", "use pr-shepherd", "iterate PR #123", "resolve this PR''s comments", or "run pr-shepherd until this PR is ready". For open-ended requests, create a Codex goal and run explicit pr-shepherd cycles through the target repo package runner, picking a fresh sleep/timeout between 1 and 4 minutes before each rerun until Shepherd emits `[CANCEL]` for ready-delay completion or merged/closed, or `[ESCALATE]` including repeated unchanged CI failures.'
3
+ description: 'Codex-only skill for iterating a GitHub pull request to completion with pr-shepherd. Use for requests like "use pr-shepherd", "iterate PR #123", or "run pr-shepherd until this PR is ready".'
4
4
  ---
5
5
 
6
6
  # pr-shepherd
@@ -16,10 +16,8 @@ Codex-only workflow for getting actionable PR updates from `pr-shepherd`.
16
16
  `gh pr view --json number --jq .number`
17
17
  - If no PR is found, report that and stop.
18
18
 
19
- 2. Decide whether this is one cycle or an open-ended goal.
20
- - For one-off requests such as "check this PR", "run pr-shepherd once", or "resolve this PR's comments", run one explicit CLI command.
21
- - For requests such as "continue", "until ready", "until this PR is ready", or "keep iterating", create a Codex goal before the first recurring cycle with this objective:
22
- `Run pr-shepherd PR_NUMBER cycles through the target repo package runner, picking a fresh sleep/timeout between 1 and 4 minutes before each rerun, until Shepherd emits [CANCEL] for ready-delay completion or PR #PR_NUMBER is merged/closed, or pr-shepherd escalates, including repeated unchanged CI failures.`
19
+ 2. Use this objective for the whole goal:
20
+ - `Run pr-shepherd PR_NUMBER cycles through the target repo package runner, picking a fresh sleep/timeout between 1 and 4 minutes before each rerun, until Shepherd emits [CANCEL] for ready-delay completion or PR #PR_NUMBER is merged/closed, or pr-shepherd escalates, including repeated unchanged CI failures.`
23
21
 
24
22
  3. Select the package runner from the target repository root.
25
23
  - Prefer `package.json` `packageManager`: `pnpm@...` -> `pnpm exec`, `yarn@...` -> `yarn run`, `npm@...` -> `npx --no-install`.
@@ -32,24 +30,14 @@ Codex-only workflow for getting actionable PR updates from `pr-shepherd`.
32
30
  - In other repositories, run through the selected package runner so Codex does not install packages implicitly. If the package is missing, tell the user to install `pr-shepherd` with the matching dev-dependency command: `pnpm add -D pr-shepherd`, `yarn add -D pr-shepherd`, or `npm install --save-dev pr-shepherd`.
33
31
 
34
32
  5. Run the appropriate command from the repository root.
35
- - For a status check:
36
- `<runner> pr-shepherd check PR_NUMBER`
37
- - For review comment resolution:
38
- `<runner> pr-shepherd resolve PR_NUMBER --fetch`
39
- - For a monitor bootstrap:
40
- `<runner> pr-shepherd monitor PR_NUMBER`
41
- - For the recurring explicit monitor tick:
42
- `<runner> pr-shepherd PR_NUMBER`
43
- - `pr-shepherd iterate PR_NUMBER` remains supported as a legacy alias, but use the default `pr-shepherd PR_NUMBER` form for recurring Codex cycles.
33
+ - `<runner> pr-shepherd PR_NUMBER`
44
34
 
45
35
  6. Print or summarize the important status, then follow the output's `## Instructions` exactly.
46
36
 
47
- 7. Do not call `/loop`, `ScheduleWakeup`, `CronCreate`, or `pr-shepherd monitor` for recurrence. Codex does explicit `pr-shepherd PR_NUMBER` cycles.
37
+ 7. If the output indicates continuation, pick a fresh sleep/timeout between 1 and 4 minutes, wait that long, and run another explicit `<runner> pr-shepherd PR_NUMBER` cycle through the same runner.
48
38
 
49
- 8. For open-ended goal requests, complete the CLI-provided instructions for the current cycle. If the output says to continue the active Codex goal, pick a fresh sleep/timeout between 1 and 4 minutes, wait that long, and run another explicit `pr-shepherd PR_NUMBER` cycle through the same runner.
39
+ 8. Do not stop on `[WAIT]`, `[COOLDOWN]`, `[MARK_READY]`, or post-fix CI wait states. These are nonterminal Codex recurrence states.
50
40
 
51
- 9. Do not stop an open-ended goal only because the output is `[WAIT]`, `[COOLDOWN]`, `[MARK_READY]`, or a post-fix CI wait. These are nonterminal Codex recurrence states.
41
+ 9. Stop only when Shepherd emits `[CANCEL]` for ready-delay completion or PR #PR_NUMBER is merged/closed, or when it emits `[ESCALATE]`, including `stall-timeout` for repeated unchanged CI failures.
52
42
 
53
- 10. Stop only when Shepherd emits `[CANCEL]` for ready-delay completion or merged/closed, or when it emits `[ESCALATE]`, including `stall-timeout` for repeated unchanged CI failures. If a Codex goal is active, mark it complete only when one of those terminal conditions is actually satisfied.
54
-
55
- 11. If the output includes fixes, pushes, rebases, or resolve commands, perform only the instructed scoped actions. Do not resolve, minimize, or dismiss comments until the CLI-provided post-push and `--require-sha` instructions are satisfied.
43
+ 10. If the output includes fixes, pushes, rebases, or resolve commands, perform only the instructed scoped actions. Do not resolve, minimize, or dismiss comments until the CLI-provided post-push and `--require-sha` instructions are satisfied.