pr-shepherd 0.38.0 → 0.39.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.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +6 -3
- package/bin/cli/help-command-pages.d.mts +1 -1
- package/bin/cli/help-iterate-poll-pages.d.mts +1 -1
- package/bin/cli/help-iterate-poll-pages.mjs +2 -2
- package/bin/cli/help.d.mts +1 -1
- package/bin/cli/iterate-instructions.mjs +2 -2
- package/bin/commands/check-annotations.d.mts +1 -1
- package/bin/commands/check-annotations.mjs +2 -2
- package/bin/commands/check.mjs +13 -1
- package/bin/commands/commit-suggestion-instruction.d.mts +6 -3
- package/bin/commands/commit-suggestion-instruction.mjs +7 -15
- package/bin/commands/iterate/check-instructions.d.mts +31 -1
- package/bin/commands/iterate/check-instructions.mjs +36 -18
- package/bin/commands/iterate/fix-code.mjs +2 -2
- package/bin/commands/iterate/index.mjs +2 -2
- package/bin/commands/iterate/render.mjs +7 -12
- package/bin/commands/iterate/stall.mjs +2 -2
- package/bin/commands/poll.mjs +1 -10
- package/bin/commands/ready-mergeability.mjs +1 -0
- package/bin/commands/shepherd-journal.d.mts +7 -3
- package/bin/commands/shepherd-journal.mjs +8 -7
- package/bin/github/client.d.mts +5 -3
- package/bin/github/client.mjs +11 -4
- package/package.json +1 -1
- package/plugins/pr-shepherd/.codex-plugin/plugin.json +1 -1
- package/plugins/pr-shepherd/.codex.mcp.json +1 -1
- package/plugins/pr-shepherd/.mcp.json +1 -1
- package/plugins/pr-shepherd/skills/pr-shepherd/SKILL.md +44 -0
package/README.md
CHANGED
|
@@ -67,10 +67,13 @@ Conversations Resolved: No [Not Required]
|
|
|
67
67
|
|
|
68
68
|
1. Review each item under `## Review threads` and `## Failing checks` and decide whether it needs a code change.
|
|
69
69
|
2. Apply every warranted review fix in each file referenced above.
|
|
70
|
-
3.
|
|
70
|
+
3. Triage every failure under `## Failing checks`. See "CI failure triage" in the pr-shepherd skill for `gh run view` / `gh run rerun` rules.
|
|
71
71
|
4. If you changed code, commit any remaining changes and push before review mutations. Otherwise, do not commit or push.
|
|
72
|
-
5.
|
|
73
|
-
6. `
|
|
72
|
+
5. Before `apply review:`, remove any `--reply-thread-ids` entry whose latest visible comment is your own Shepherd reply. Do not reply to yourself.
|
|
73
|
+
6. Replace `$HEAD_SHA` with the pushed commit SHA, or `$(git rev-parse HEAD)` if you did not push.
|
|
74
|
+
7. Replace `$DISMISS_MESSAGE` with one sentence describing what changed.
|
|
75
|
+
8. Run the `apply review:` command shown above. See "Review-mutation mechanics" in the pr-shepherd skill for dismiss-ID retention.
|
|
76
|
+
9. `[FIX_CODE]` is non-terminal. After completing these steps, iterate again with the same options to continue.
|
|
74
77
|
```
|
|
75
78
|
|
|
76
79
|
See [docs/actions.md](docs/actions.md) for the complete output contract. Iterate/poll PR outcomes use exit codes `0` and `10`–`14`; command and GitHub failures use `sysexits.h` codes — [docs/exit-codes.md](docs/exit-codes.md).
|
|
@@ -177,7 +177,7 @@ Flags:
|
|
|
177
177
|
PR may be a number or GitHub pull request URL. When omitted, the current branch PR is inferred.
|
|
178
178
|
Exit code: 0 on success; nonzero on failure (sysexits.h — see docs/exit-codes.md).`;
|
|
179
179
|
readonly iterate: "pr-shepherd iterate\n\nRun one iterate tick for a pull request. The no-subcommand form polls; use this subcommand for a single tick.\nThe output contains one action and an action-specific ## Instructions section.\n\nUsage:\n pr-shepherd iterate [PR] [iterate-flags]\n\nIterate flags:\n --ready-delay <duration> Settle window before a clean PR cancels. Bare number = minutes. Example: 15m.\n --stall-timeout <duration> Escalate repeated unchanged failures after this duration. Bare number = minutes. 0 disables.\n --no-auto-mark-ready Do not convert draft PRs to ready for review.\n --no-auto-cancel-actionable Do not cancel in-progress runs before actionable fixes.\n --format text|json Output Markdown text or JSON. Default: text.\n --verbose Include verbose iterate fields.\n --help, -h Print this help and exit before GitHub, git, config, or log I/O.\n\nDurations accept s/m/h suffixes: 30s, 4.5m, 1h. A bare number is minutes; decimals are allowed only with an explicit unit (4.5m).\n\nActions:\n WAIT No immediate action; continue with the next poll.\n MARK_READY Draft PR was marked ready; continue with the next poll.\n FIX_CODE Agent action is required; follow the instructions, then continue polling.\n CANCEL Stop polling: merged/closed or ready-delay elapsed.\n ESCALATE Stop polling until a human provides direction.\n\nExit codes:\n 0 CANCEL (merged or ready-delay elapsed)\n 10 WAIT\n 11 MARK_READY\n 12 FIX_CODE\n 13 ESCALATE\n 14 CANCEL (closed without merging)\n A command/validation/GitHub failure exits with a sysexits.h code instead (see docs/exit-codes.md).";
|
|
180
|
-
readonly poll: "pr-shepherd poll\n\nRun iterate repeatedly for WAIT ticks and during the FIX_CODE debounce window. Print only the\nfinal tick to stdout.\nPoll exits as soon as iterate returns MARK_READY, CANCEL, or ESCALATE, or when timeout\nreturns the last WAIT result. FIX_CODE starts a --debounce settle window (default 1m): poll keeps\niterating at --interval, then runs one more tick after the window and returns that result.\nWith --until-terminal, poll also continues through MARK_READY.\n\nUsage:\n pr-shepherd poll [PR] [poll-flags] [iterate-flags]\n\nPoll flags:\n --interval <duration> Sleep between WAIT ticks. Bare number = seconds. Default: 60s.\n --timeout <duration> Maximum wall-clock wait for WAIT ticks. Bare number = seconds. Default: 4.5m.\n --debounce <duration> Settle window after first FIX_CODE before returning. Bare number = seconds. Default: 60s. 0 disables.\n --quiet-status During WAIT polling, print only changed status snapshots.\n --until-terminal Continue through WAIT/MARK_READY until FIX_CODE/CANCEL/ESCALATE.\n\nForwarded iterate flags:\n --ready-delay <duration> Settle window before a clean PR cancels. Bare number = minutes. Example: 15m.\n --stall-timeout <duration> Escalate repeated unchanged failures after this duration. Bare number = minutes. 0 disables.\n --no-auto-mark-ready Do not convert draft PRs to ready for review.\n --no-auto-cancel-actionable Do not cancel in-progress runs before actionable fixes.\n --format text|json Output Markdown text or JSON. Default: text.\n --verbose Include verbose iterate fields and detailed per-tick lines.\n --help, -h Print this help and exit before GitHub, git, config, or log I/O.\n\nDurations accept s/m/h suffixes: 30s, 4.5m, 1h. A bare number uses each flag's default unit (seconds\nfor --interval/--timeout/--debounce, minutes for --ready-delay/--stall-timeout); decimals are allowed only with\nan explicit unit (4.5m).\nEach WAIT tick writes
|
|
180
|
+
readonly poll: "pr-shepherd poll\n\nRun iterate repeatedly for WAIT ticks and during the FIX_CODE debounce window. Print only the\nfinal tick to stdout.\nPoll exits as soon as iterate returns MARK_READY, CANCEL, or ESCALATE, or when timeout\nreturns the last WAIT result. FIX_CODE starts a --debounce settle window (default 1m): poll keeps\niterating at --interval, then runs one more tick after the window and returns that result.\nWith --until-terminal, poll also continues through MARK_READY.\n\nUsage:\n pr-shepherd poll [PR] [poll-flags] [iterate-flags]\n\nPoll flags:\n --interval <duration> Sleep between WAIT ticks. Bare number = seconds. Default: 60s.\n --timeout <duration> Maximum wall-clock wait for WAIT ticks. Bare number = seconds. Default: 4.5m.\n --debounce <duration> Settle window after first FIX_CODE before returning. Bare number = seconds. Default: 60s. 0 disables.\n --quiet-status During WAIT polling, print only changed status snapshots.\n --until-terminal Continue through WAIT/MARK_READY until FIX_CODE/CANCEL/ESCALATE.\n\nForwarded iterate flags:\n --ready-delay <duration> Settle window before a clean PR cancels. Bare number = minutes. Example: 15m.\n --stall-timeout <duration> Escalate repeated unchanged failures after this duration. Bare number = minutes. 0 disables.\n --no-auto-mark-ready Do not convert draft PRs to ready for review.\n --no-auto-cancel-actionable Do not cancel in-progress runs before actionable fixes.\n --format text|json Output Markdown text or JSON. Default: text.\n --verbose Include verbose iterate fields and detailed per-tick lines.\n --help, -h Print this help and exit before GitHub, git, config, or log I/O.\n\nDurations accept s/m/h suffixes: 30s, 4.5m, 1h. A bare number uses each flag's default unit (seconds\nfor --interval/--timeout/--debounce, minutes for --ready-delay/--stall-timeout); decimals are allowed only with\nan explicit unit (4.5m).\nEach WAIT tick writes an explicit still-running line to stderr by default; --quiet-status prints only changed WAIT snapshots, and --verbose emits detailed per-tick lines.\nFIX_CODE debounce writes a remaining-seconds line to stderr. --timeout does not cut an in-flight debounce short.\nWith --until-terminal, --timeout is ignored for WAIT ticks and polling continues until FIX_CODE, CANCEL, or ESCALATE.\n\nExit codes: same as iterate (the final tick's action/reason decides the code).\n 0 CANCEL (merged or ready-delay elapsed)\n 10 WAIT (including a WAIT returned by --timeout)\n 11 MARK_READY\n 12 FIX_CODE\n 13 ESCALATE\n 14 CANCEL (closed without merging)\n A command/validation/GitHub failure exits with a sysexits.h code instead (see docs/exit-codes.md).";
|
|
181
181
|
readonly clean: `pr-shepherd clean
|
|
182
182
|
|
|
183
183
|
Remove pr-shepherd state files from PR_SHEPHERD_STATE_DIR.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export declare const ITERATE_USAGE = "pr-shepherd iterate\n\nRun one iterate tick for a pull request. The no-subcommand form polls; use this subcommand for a single tick.\nThe output contains one action and an action-specific ## Instructions section.\n\nUsage:\n pr-shepherd iterate [PR] [iterate-flags]\n\nIterate flags:\n --ready-delay <duration> Settle window before a clean PR cancels. Bare number = minutes. Example: 15m.\n --stall-timeout <duration> Escalate repeated unchanged failures after this duration. Bare number = minutes. 0 disables.\n --no-auto-mark-ready Do not convert draft PRs to ready for review.\n --no-auto-cancel-actionable Do not cancel in-progress runs before actionable fixes.\n --format text|json Output Markdown text or JSON. Default: text.\n --verbose Include verbose iterate fields.\n --help, -h Print this help and exit before GitHub, git, config, or log I/O.\n\nDurations accept s/m/h suffixes: 30s, 4.5m, 1h. A bare number is minutes; decimals are allowed only with an explicit unit (4.5m).\n\nActions:\n WAIT No immediate action; continue with the next poll.\n MARK_READY Draft PR was marked ready; continue with the next poll.\n FIX_CODE Agent action is required; follow the instructions, then continue polling.\n CANCEL Stop polling: merged/closed or ready-delay elapsed.\n ESCALATE Stop polling until a human provides direction.\n\nExit codes:\n 0 CANCEL (merged or ready-delay elapsed)\n 10 WAIT\n 11 MARK_READY\n 12 FIX_CODE\n 13 ESCALATE\n 14 CANCEL (closed without merging)\n A command/validation/GitHub failure exits with a sysexits.h code instead (see docs/exit-codes.md).";
|
|
2
|
-
export declare const POLL_USAGE = "pr-shepherd poll\n\nRun iterate repeatedly for WAIT ticks and during the FIX_CODE debounce window. Print only the\nfinal tick to stdout.\nPoll exits as soon as iterate returns MARK_READY, CANCEL, or ESCALATE, or when timeout\nreturns the last WAIT result. FIX_CODE starts a --debounce settle window (default 1m): poll keeps\niterating at --interval, then runs one more tick after the window and returns that result.\nWith --until-terminal, poll also continues through MARK_READY.\n\nUsage:\n pr-shepherd poll [PR] [poll-flags] [iterate-flags]\n\nPoll flags:\n --interval <duration> Sleep between WAIT ticks. Bare number = seconds. Default: 60s.\n --timeout <duration> Maximum wall-clock wait for WAIT ticks. Bare number = seconds. Default: 4.5m.\n --debounce <duration> Settle window after first FIX_CODE before returning. Bare number = seconds. Default: 60s. 0 disables.\n --quiet-status During WAIT polling, print only changed status snapshots.\n --until-terminal Continue through WAIT/MARK_READY until FIX_CODE/CANCEL/ESCALATE.\n\nForwarded iterate flags:\n --ready-delay <duration> Settle window before a clean PR cancels. Bare number = minutes. Example: 15m.\n --stall-timeout <duration> Escalate repeated unchanged failures after this duration. Bare number = minutes. 0 disables.\n --no-auto-mark-ready Do not convert draft PRs to ready for review.\n --no-auto-cancel-actionable Do not cancel in-progress runs before actionable fixes.\n --format text|json Output Markdown text or JSON. Default: text.\n --verbose Include verbose iterate fields and detailed per-tick lines.\n --help, -h Print this help and exit before GitHub, git, config, or log I/O.\n\nDurations accept s/m/h suffixes: 30s, 4.5m, 1h. A bare number uses each flag's default unit (seconds\nfor --interval/--timeout/--debounce, minutes for --ready-delay/--stall-timeout); decimals are allowed only with\nan explicit unit (4.5m).\nEach WAIT tick writes
|
|
2
|
+
export declare const POLL_USAGE = "pr-shepherd poll\n\nRun iterate repeatedly for WAIT ticks and during the FIX_CODE debounce window. Print only the\nfinal tick to stdout.\nPoll exits as soon as iterate returns MARK_READY, CANCEL, or ESCALATE, or when timeout\nreturns the last WAIT result. FIX_CODE starts a --debounce settle window (default 1m): poll keeps\niterating at --interval, then runs one more tick after the window and returns that result.\nWith --until-terminal, poll also continues through MARK_READY.\n\nUsage:\n pr-shepherd poll [PR] [poll-flags] [iterate-flags]\n\nPoll flags:\n --interval <duration> Sleep between WAIT ticks. Bare number = seconds. Default: 60s.\n --timeout <duration> Maximum wall-clock wait for WAIT ticks. Bare number = seconds. Default: 4.5m.\n --debounce <duration> Settle window after first FIX_CODE before returning. Bare number = seconds. Default: 60s. 0 disables.\n --quiet-status During WAIT polling, print only changed status snapshots.\n --until-terminal Continue through WAIT/MARK_READY until FIX_CODE/CANCEL/ESCALATE.\n\nForwarded iterate flags:\n --ready-delay <duration> Settle window before a clean PR cancels. Bare number = minutes. Example: 15m.\n --stall-timeout <duration> Escalate repeated unchanged failures after this duration. Bare number = minutes. 0 disables.\n --no-auto-mark-ready Do not convert draft PRs to ready for review.\n --no-auto-cancel-actionable Do not cancel in-progress runs before actionable fixes.\n --format text|json Output Markdown text or JSON. Default: text.\n --verbose Include verbose iterate fields and detailed per-tick lines.\n --help, -h Print this help and exit before GitHub, git, config, or log I/O.\n\nDurations accept s/m/h suffixes: 30s, 4.5m, 1h. A bare number uses each flag's default unit (seconds\nfor --interval/--timeout/--debounce, minutes for --ready-delay/--stall-timeout); decimals are allowed only with\nan explicit unit (4.5m).\nEach WAIT tick writes an explicit still-running line to stderr by default; --quiet-status prints only changed WAIT snapshots, and --verbose emits detailed per-tick lines.\nFIX_CODE debounce writes a remaining-seconds line to stderr. --timeout does not cut an in-flight debounce short.\nWith --until-terminal, --timeout is ignored for WAIT ticks and polling continues until FIX_CODE, CANCEL, or ESCALATE.\n\nExit codes: same as iterate (the final tick's action/reason decides the code).\n 0 CANCEL (merged or ready-delay elapsed)\n 10 WAIT (including a WAIT returned by --timeout)\n 11 MARK_READY\n 12 FIX_CODE\n 13 ESCALATE\n 14 CANCEL (closed without merging)\n A command/validation/GitHub failure exits with a sysexits.h code instead (see docs/exit-codes.md).";
|
|
3
3
|
/** Public help page for the default PR polling invocation. */
|
|
4
4
|
export declare const DEFAULT_USAGE: string;
|
|
@@ -63,7 +63,7 @@ Forwarded iterate flags:
|
|
|
63
63
|
Durations accept s/m/h suffixes: 30s, 4.5m, 1h. A bare number uses each flag's default unit (seconds
|
|
64
64
|
for --interval/--timeout/--debounce, minutes for --ready-delay/--stall-timeout); decimals are allowed only with
|
|
65
65
|
an explicit unit (4.5m).
|
|
66
|
-
Each WAIT tick writes
|
|
66
|
+
Each WAIT tick writes an explicit still-running line to stderr by default; --quiet-status prints only changed WAIT snapshots, and --verbose emits detailed per-tick lines.
|
|
67
67
|
FIX_CODE debounce writes a remaining-seconds line to stderr. --timeout does not cut an in-flight debounce short.
|
|
68
68
|
With --until-terminal, --timeout is ignored for WAIT ticks and polling continues until FIX_CODE, CANCEL, or ESCALATE.
|
|
69
69
|
|
|
@@ -76,4 +76,4 @@ Exit codes: same as iterate (the final tick's action/reason decides the code).
|
|
|
76
76
|
14 CANCEL (closed without merging)
|
|
77
77
|
A command/validation/GitHub failure exits with a sysexits.h code instead (see docs/exit-codes.md).`;
|
|
78
78
|
/** Public help page for the default PR polling invocation. */
|
|
79
|
-
export const DEFAULT_USAGE = POLL_USAGE.
|
|
79
|
+
export const DEFAULT_USAGE = POLL_USAGE.replace(/^pr-shepherd poll$/m, "pr-shepherd [PR]").replace(/^ {2}pr-shepherd poll \[PR\]/m, " pr-shepherd [PR]");
|
package/bin/cli/help.d.mts
CHANGED
|
@@ -177,7 +177,7 @@ Flags:
|
|
|
177
177
|
PR may be a number or GitHub pull request URL. When omitted, the current branch PR is inferred.
|
|
178
178
|
Exit code: 0 on success; nonzero on failure (sysexits.h — see docs/exit-codes.md).`;
|
|
179
179
|
readonly iterate: "pr-shepherd iterate\n\nRun one iterate tick for a pull request. The no-subcommand form polls; use this subcommand for a single tick.\nThe output contains one action and an action-specific ## Instructions section.\n\nUsage:\n pr-shepherd iterate [PR] [iterate-flags]\n\nIterate flags:\n --ready-delay <duration> Settle window before a clean PR cancels. Bare number = minutes. Example: 15m.\n --stall-timeout <duration> Escalate repeated unchanged failures after this duration. Bare number = minutes. 0 disables.\n --no-auto-mark-ready Do not convert draft PRs to ready for review.\n --no-auto-cancel-actionable Do not cancel in-progress runs before actionable fixes.\n --format text|json Output Markdown text or JSON. Default: text.\n --verbose Include verbose iterate fields.\n --help, -h Print this help and exit before GitHub, git, config, or log I/O.\n\nDurations accept s/m/h suffixes: 30s, 4.5m, 1h. A bare number is minutes; decimals are allowed only with an explicit unit (4.5m).\n\nActions:\n WAIT No immediate action; continue with the next poll.\n MARK_READY Draft PR was marked ready; continue with the next poll.\n FIX_CODE Agent action is required; follow the instructions, then continue polling.\n CANCEL Stop polling: merged/closed or ready-delay elapsed.\n ESCALATE Stop polling until a human provides direction.\n\nExit codes:\n 0 CANCEL (merged or ready-delay elapsed)\n 10 WAIT\n 11 MARK_READY\n 12 FIX_CODE\n 13 ESCALATE\n 14 CANCEL (closed without merging)\n A command/validation/GitHub failure exits with a sysexits.h code instead (see docs/exit-codes.md).";
|
|
180
|
-
readonly poll: "pr-shepherd poll\n\nRun iterate repeatedly for WAIT ticks and during the FIX_CODE debounce window. Print only the\nfinal tick to stdout.\nPoll exits as soon as iterate returns MARK_READY, CANCEL, or ESCALATE, or when timeout\nreturns the last WAIT result. FIX_CODE starts a --debounce settle window (default 1m): poll keeps\niterating at --interval, then runs one more tick after the window and returns that result.\nWith --until-terminal, poll also continues through MARK_READY.\n\nUsage:\n pr-shepherd poll [PR] [poll-flags] [iterate-flags]\n\nPoll flags:\n --interval <duration> Sleep between WAIT ticks. Bare number = seconds. Default: 60s.\n --timeout <duration> Maximum wall-clock wait for WAIT ticks. Bare number = seconds. Default: 4.5m.\n --debounce <duration> Settle window after first FIX_CODE before returning. Bare number = seconds. Default: 60s. 0 disables.\n --quiet-status During WAIT polling, print only changed status snapshots.\n --until-terminal Continue through WAIT/MARK_READY until FIX_CODE/CANCEL/ESCALATE.\n\nForwarded iterate flags:\n --ready-delay <duration> Settle window before a clean PR cancels. Bare number = minutes. Example: 15m.\n --stall-timeout <duration> Escalate repeated unchanged failures after this duration. Bare number = minutes. 0 disables.\n --no-auto-mark-ready Do not convert draft PRs to ready for review.\n --no-auto-cancel-actionable Do not cancel in-progress runs before actionable fixes.\n --format text|json Output Markdown text or JSON. Default: text.\n --verbose Include verbose iterate fields and detailed per-tick lines.\n --help, -h Print this help and exit before GitHub, git, config, or log I/O.\n\nDurations accept s/m/h suffixes: 30s, 4.5m, 1h. A bare number uses each flag's default unit (seconds\nfor --interval/--timeout/--debounce, minutes for --ready-delay/--stall-timeout); decimals are allowed only with\nan explicit unit (4.5m).\nEach WAIT tick writes
|
|
180
|
+
readonly poll: "pr-shepherd poll\n\nRun iterate repeatedly for WAIT ticks and during the FIX_CODE debounce window. Print only the\nfinal tick to stdout.\nPoll exits as soon as iterate returns MARK_READY, CANCEL, or ESCALATE, or when timeout\nreturns the last WAIT result. FIX_CODE starts a --debounce settle window (default 1m): poll keeps\niterating at --interval, then runs one more tick after the window and returns that result.\nWith --until-terminal, poll also continues through MARK_READY.\n\nUsage:\n pr-shepherd poll [PR] [poll-flags] [iterate-flags]\n\nPoll flags:\n --interval <duration> Sleep between WAIT ticks. Bare number = seconds. Default: 60s.\n --timeout <duration> Maximum wall-clock wait for WAIT ticks. Bare number = seconds. Default: 4.5m.\n --debounce <duration> Settle window after first FIX_CODE before returning. Bare number = seconds. Default: 60s. 0 disables.\n --quiet-status During WAIT polling, print only changed status snapshots.\n --until-terminal Continue through WAIT/MARK_READY until FIX_CODE/CANCEL/ESCALATE.\n\nForwarded iterate flags:\n --ready-delay <duration> Settle window before a clean PR cancels. Bare number = minutes. Example: 15m.\n --stall-timeout <duration> Escalate repeated unchanged failures after this duration. Bare number = minutes. 0 disables.\n --no-auto-mark-ready Do not convert draft PRs to ready for review.\n --no-auto-cancel-actionable Do not cancel in-progress runs before actionable fixes.\n --format text|json Output Markdown text or JSON. Default: text.\n --verbose Include verbose iterate fields and detailed per-tick lines.\n --help, -h Print this help and exit before GitHub, git, config, or log I/O.\n\nDurations accept s/m/h suffixes: 30s, 4.5m, 1h. A bare number uses each flag's default unit (seconds\nfor --interval/--timeout/--debounce, minutes for --ready-delay/--stall-timeout); decimals are allowed only with\nan explicit unit (4.5m).\nEach WAIT tick writes an explicit still-running line to stderr by default; --quiet-status prints only changed WAIT snapshots, and --verbose emits detailed per-tick lines.\nFIX_CODE debounce writes a remaining-seconds line to stderr. --timeout does not cut an in-flight debounce short.\nWith --until-terminal, --timeout is ignored for WAIT ticks and polling continues until FIX_CODE, CANCEL, or ESCALATE.\n\nExit codes: same as iterate (the final tick's action/reason decides the code).\n 0 CANCEL (merged or ready-delay elapsed)\n 10 WAIT (including a WAIT returned by --timeout)\n 11 MARK_READY\n 12 FIX_CODE\n 13 ESCALATE\n 14 CANCEL (closed without merging)\n A command/validation/GitHub failure exits with a sysexits.h code instead (see docs/exit-codes.md).";
|
|
181
181
|
readonly clean: `pr-shepherd clean
|
|
182
182
|
|
|
183
183
|
Remove pr-shepherd state files from PR_SHEPHERD_STATE_DIR.
|
|
@@ -2,11 +2,11 @@ export function buildSimpleIterateInstructions(result) {
|
|
|
2
2
|
switch (result.action) {
|
|
3
3
|
case "wait":
|
|
4
4
|
return [
|
|
5
|
-
"
|
|
5
|
+
"Non-terminal — no action needed this tick. Iterate again with the same options to continue.",
|
|
6
6
|
];
|
|
7
7
|
case "mark_ready":
|
|
8
8
|
return [
|
|
9
|
-
"The CLI marked the PR ready for review.
|
|
9
|
+
"The CLI marked the PR ready for review. Iterate again with the same options to continue.",
|
|
10
10
|
];
|
|
11
11
|
case "cancel":
|
|
12
12
|
return ["Stop — the PR loop is complete. No further polling is needed."];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { CheckAnnotation, ClassifiedCheck, ShepherdReport, TriagedCheck } from "../types.mts";
|
|
2
|
-
export declare function
|
|
2
|
+
export declare function checksWithActionableAnnotations(report: ShepherdReport): TriagedCheck[];
|
|
3
3
|
export declare function attachAndMergeCheckAnnotations(buckets: {
|
|
4
4
|
passing: ClassifiedCheck[];
|
|
5
5
|
failing: TriagedCheck[];
|
|
@@ -2,14 +2,14 @@ import { fetchCheckRunAnnotations } from "../github/check-annotations.mjs";
|
|
|
2
2
|
function shouldFetchCheckAnnotations(check) {
|
|
3
3
|
return check.id != null && check.status === "COMPLETED" && check.hasAnnotations === true;
|
|
4
4
|
}
|
|
5
|
-
export function
|
|
5
|
+
export function checksWithActionableAnnotations(report) {
|
|
6
6
|
return [
|
|
7
7
|
...report.checks.failing,
|
|
8
8
|
...report.checks.passing,
|
|
9
9
|
...report.checks.skipped,
|
|
10
10
|
...report.checks.filtered,
|
|
11
11
|
...(report.checks.ignored ?? []),
|
|
12
|
-
].filter((c) => (c.annotations?.length ?? 0) > 0);
|
|
12
|
+
].filter((c) => c.conclusion !== "SUCCESS" && (c.annotations?.length ?? 0) > 0);
|
|
13
13
|
}
|
|
14
14
|
export async function attachAndMergeCheckAnnotations(buckets, seenMap, prNumber) {
|
|
15
15
|
const candidates = [
|
package/bin/commands/check.mjs
CHANGED
|
@@ -7,7 +7,7 @@ import { deriveMergeStatus } from "../merge-status/derive.mjs";
|
|
|
7
7
|
import { loadConfig } from "../config/load.mjs";
|
|
8
8
|
import { classifyVisibleComments } from "../comments/visible-comments.mjs";
|
|
9
9
|
import { computeStatus } from "./check-status.mjs";
|
|
10
|
-
import { attachAndMergeCheckAnnotations } from "./check-annotations.mjs";
|
|
10
|
+
import { annotationMarkerBody, attachAndMergeCheckAnnotations } from "./check-annotations.mjs";
|
|
11
11
|
import { buildTerminalReport } from "./check-terminal-report.mjs";
|
|
12
12
|
import { isBlockedByFilteredCheck, refreshReadyMergeability, refreshUnknownMergeability, } from "./ready-mergeability.mjs";
|
|
13
13
|
import { loadSeenMap, markSeen, classifyItem } from "../state/seen-comments.mjs";
|
|
@@ -85,7 +85,16 @@ export async function runCheck(opts) {
|
|
|
85
85
|
const changesRequestedReviewVisibility = classifyChangesRequestedReviewsForDisplay(batchData.changesRequestedReviews.filter((r) => !partition.suppressedChangesRequestedIds.has(r.id)), seenMap, botUsernames);
|
|
86
86
|
const approvedReviewVisibility = classifyReviewsForDisplay(batchData.approvedReviews, seenMap);
|
|
87
87
|
if (opts.persistSeen !== false) {
|
|
88
|
+
const successfulAnnotations = [
|
|
89
|
+
...merged.passing,
|
|
90
|
+
...merged.skipped,
|
|
91
|
+
...merged.filtered,
|
|
92
|
+
...merged.ignored,
|
|
93
|
+
]
|
|
94
|
+
.filter((check) => check.conclusion === "SUCCESS")
|
|
95
|
+
.flatMap((check) => check.annotations ?? []);
|
|
88
96
|
await Promise.allSettled([
|
|
97
|
+
...successfulAnnotations.map((a) => markSeen(stateKey, a.id, annotationMarkerBody(a))),
|
|
89
98
|
...firstLookComments.map((c) => markSeen(stateKey, c.id, c.body)),
|
|
90
99
|
...threadVisibility.toMarkSeen.map((t) => markSeen(stateKey, t.id, threadTranscriptBody(t))),
|
|
91
100
|
...visibleCommentClassification.toMarkSeen.map((c) => markSeen(stateKey, c.id, c.body)),
|
|
@@ -117,6 +126,9 @@ export async function runCheck(opts) {
|
|
|
117
126
|
batchData = refreshed.batchData;
|
|
118
127
|
mergeStatus = refreshed.mergeStatus;
|
|
119
128
|
status = refreshed.status;
|
|
129
|
+
if (mergeStatus.state === "MERGED" || mergeStatus.state === "CLOSED") {
|
|
130
|
+
return buildTerminalReport(prNumber, repo, batchData, mergeStatus, mergeStatus.state);
|
|
131
|
+
}
|
|
120
132
|
}
|
|
121
133
|
const blockedByFilteredCheck = isBlockedByFilteredCheck(mergeStatus, verdict);
|
|
122
134
|
return {
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Build the `build-suggestion-patch` instruction step for agent consumers.
|
|
3
|
-
* Currently emitted by iterate `fix_code` for suggestion review threads.
|
|
3
|
+
* Currently emitted by iterate `fix_code` for suggestion review threads. The CLI keeps
|
|
4
|
+
* only the trigger and the concrete command; refusal/drift handling is invariant across
|
|
5
|
+
* every invocation, so it lives in the pr-shepherd skill's "Suggestion patches" playbook
|
|
6
|
+
* instead of being re-emitted every tick (see CLAUDE.md "Keep skills and loop prompts
|
|
7
|
+
* minimal").
|
|
4
8
|
* @param sectionName - The markdown section heading where suggestion threads appear,
|
|
5
9
|
* e.g. `"## Review threads"`.
|
|
6
|
-
* @param includeDriftHint - Whether to add the trailing note about drift on failed apply.
|
|
7
10
|
*/
|
|
8
|
-
export declare function buildCommitSuggestionInstruction(prNumber: number, sectionName: string
|
|
11
|
+
export declare function buildCommitSuggestionInstruction(prNumber: number, sectionName: string): string;
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { buildPrShepherdCommand } from "../cli/runner.mjs";
|
|
2
2
|
/**
|
|
3
3
|
* Build the `build-suggestion-patch` instruction step for agent consumers.
|
|
4
|
-
* Currently emitted by iterate `fix_code` for suggestion review threads.
|
|
4
|
+
* Currently emitted by iterate `fix_code` for suggestion review threads. The CLI keeps
|
|
5
|
+
* only the trigger and the concrete command; refusal/drift handling is invariant across
|
|
6
|
+
* every invocation, so it lives in the pr-shepherd skill's "Suggestion patches" playbook
|
|
7
|
+
* instead of being re-emitted every tick (see CLAUDE.md "Keep skills and loop prompts
|
|
8
|
+
* minimal").
|
|
5
9
|
* @param sectionName - The markdown section heading where suggestion threads appear,
|
|
6
10
|
* e.g. `"## Review threads"`.
|
|
7
|
-
* @param includeDriftHint - Whether to add the trailing note about drift on failed apply.
|
|
8
11
|
*/
|
|
9
|
-
export function buildCommitSuggestionInstruction(prNumber, sectionName
|
|
12
|
+
export function buildCommitSuggestionInstruction(prNumber, sectionName) {
|
|
10
13
|
const command = buildPrShepherdCommand([
|
|
11
14
|
"build-suggestion-patch",
|
|
12
15
|
String(prNumber),
|
|
@@ -16,16 +19,5 @@ export function buildCommitSuggestionInstruction(prNumber, sectionName, includeD
|
|
|
16
19
|
"<one-sentence headline>",
|
|
17
20
|
"--format=json",
|
|
18
21
|
]).text;
|
|
19
|
-
|
|
20
|
-
? "If the patch does not apply because the suggestion drifted, use the manual-fix step below. Do not retry the command."
|
|
21
|
-
: "If the patch does not apply, use the manual-edit step below. Do not retry the command.";
|
|
22
|
-
const manualStep = includeDriftHint ? "manual-fix step" : "manual-edit step";
|
|
23
|
-
return [
|
|
24
|
-
`For each thread marked \`[suggestion]\` under \`${sectionName}\`, run \`${command}\` to retrieve its patch and suggested commit.`,
|
|
25
|
-
"The CLI only builds the patch. Apply it, stage the listed file, and follow the returned commit instructions.",
|
|
26
|
-
`If the command refuses because the suggestion is unsafe (an unsafe anchored range or nested/unbalanced suggestion fences), skip patch application and use the ${manualStep} below. Do not retry the command.`,
|
|
27
|
-
"For any other refusal, follow the CLI error's stated recovery action; do not manually edit the suggestion.",
|
|
28
|
-
driftHint,
|
|
29
|
-
"Keep human-authored thread IDs in `apply review:` so Shepherd replies instead of resolving them.",
|
|
30
|
-
];
|
|
22
|
+
return `For each thread marked \`[suggestion]\` under \`${sectionName}\`, run \`${command}\` and apply the returned patch. See "Suggestion patches" in the pr-shepherd skill for refusals and drift.`;
|
|
31
23
|
}
|
|
@@ -11,7 +11,37 @@ 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
|
-
/**
|
|
14
|
+
/**
|
|
15
|
+
* Build the `Run the apply review: command` instruction. Steps stay here (not in the skill)
|
|
16
|
+
* whenever the *unmodified, as-printed* command is unsafe without them:
|
|
17
|
+
*
|
|
18
|
+
* - `$HEAD_SHA`/`$DISMISS_MESSAGE` substitution: without it, the printed command has an
|
|
19
|
+
* empty `--message`/invalid `--require-sha` and `apply review` rejects the mutation.
|
|
20
|
+
* - Self-reply exclusion: a human thread whose latest visible comment is already Shepherd's
|
|
21
|
+
* own prior reply still has its ID in `--reply-thread-ids` by default. Running the
|
|
22
|
+
* printed command as-is replies to Shepherd's own reply, which can re-surface the thread
|
|
23
|
+
* and produce a self-perpetuating reply loop — worse than a rejected mutation, and not
|
|
24
|
+
* something a caller can discover by inspecting the command alone.
|
|
25
|
+
*
|
|
26
|
+
* Contrast with what *does* stay in the skill's "Review-mutation mechanics" playbook —
|
|
27
|
+
* dismiss-ID retention and the first-look/annotation ID-exclusion rules. Those only matter
|
|
28
|
+
* if the caller *edits* the printed command (removes an ID, or adds one back); the printed
|
|
29
|
+
* command run unmodified is already correct for them. The pointer below is load-bearing:
|
|
30
|
+
* without it, nothing in CLI output tells the agent that playbook exists.
|
|
31
|
+
*/
|
|
15
32
|
export declare function buildResolveCommandInstruction(resolveCommand: ResolveCommand): string[];
|
|
33
|
+
/**
|
|
34
|
+
* Build the CI-triage instruction. The per-conclusion rerun policy (GitHub Actions log
|
|
35
|
+
* excerpts, `gh run view`/`gh run rerun` rules for CANCELLED/STARTUP_FAILURE/external
|
|
36
|
+
* failures) is invariant text keyed on the `[conclusion: …]` tags already rendered in
|
|
37
|
+
* `## Failing checks` — it lives in the pr-shepherd skill's "CI failure triage" playbook
|
|
38
|
+
* instead of being re-emitted every tick. This supersedes the "CI budget rules" example in
|
|
39
|
+
* CLAUDE.md's "Keep skills and loop prompts minimal" section (see that section's amendment
|
|
40
|
+
* note). The `(no runId)` case stays here because it flips `buildFixCompletionInstruction`
|
|
41
|
+
* to a human-handoff terminal state — that trigger, unlike the others, is CLI-decided. The
|
|
42
|
+
* CLI sentence does not claim every failure has a log excerpt to read (only GitHub Actions
|
|
43
|
+
* checks with a runId do — CANCELLED, STARTUP_FAILURE, and external checks may not); that
|
|
44
|
+
* per-kind detail is exactly what the skill playbook table disambiguates.
|
|
45
|
+
*/
|
|
16
46
|
export declare function buildFailingCheckInstructions(checks: AgentCheck[]): string[];
|
|
17
47
|
export declare function buildFixCompletionInstruction(checks: AgentCheck[]): string;
|
|
@@ -20,7 +20,24 @@ export function buildBehindBaseHintInstruction(baseBranch, hint, isBehind) {
|
|
|
20
20
|
return [];
|
|
21
21
|
return [`The branch is behind \`origin/${baseBranch}\`. ${trimmedHint} before pushing.`];
|
|
22
22
|
}
|
|
23
|
-
/**
|
|
23
|
+
/**
|
|
24
|
+
* Build the `Run the apply review: command` instruction. Steps stay here (not in the skill)
|
|
25
|
+
* whenever the *unmodified, as-printed* command is unsafe without them:
|
|
26
|
+
*
|
|
27
|
+
* - `$HEAD_SHA`/`$DISMISS_MESSAGE` substitution: without it, the printed command has an
|
|
28
|
+
* empty `--message`/invalid `--require-sha` and `apply review` rejects the mutation.
|
|
29
|
+
* - Self-reply exclusion: a human thread whose latest visible comment is already Shepherd's
|
|
30
|
+
* own prior reply still has its ID in `--reply-thread-ids` by default. Running the
|
|
31
|
+
* printed command as-is replies to Shepherd's own reply, which can re-surface the thread
|
|
32
|
+
* and produce a self-perpetuating reply loop — worse than a rejected mutation, and not
|
|
33
|
+
* something a caller can discover by inspecting the command alone.
|
|
34
|
+
*
|
|
35
|
+
* Contrast with what *does* stay in the skill's "Review-mutation mechanics" playbook —
|
|
36
|
+
* dismiss-ID retention and the first-look/annotation ID-exclusion rules. Those only matter
|
|
37
|
+
* if the caller *edits* the printed command (removes an ID, or adds one back); the printed
|
|
38
|
+
* command run unmodified is already correct for them. The pointer below is load-bearing:
|
|
39
|
+
* without it, nothing in CLI output tells the agent that playbook exists.
|
|
40
|
+
*/
|
|
24
41
|
export function buildResolveCommandInstruction(resolveCommand) {
|
|
25
42
|
if (!resolveCommand.hasMutations)
|
|
26
43
|
return [];
|
|
@@ -34,29 +51,30 @@ export function buildResolveCommandInstruction(resolveCommand) {
|
|
|
34
51
|
if (resolveCommand.requiresDismissMessage) {
|
|
35
52
|
instructions.push("Replace `$DISMISS_MESSAGE` with one sentence describing what changed.");
|
|
36
53
|
}
|
|
37
|
-
instructions.push(
|
|
54
|
+
instructions.push('Run the `apply review:` command shown above. See "Review-mutation mechanics" in the pr-shepherd skill for dismiss-ID retention.');
|
|
38
55
|
return instructions;
|
|
39
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* Build the CI-triage instruction. The per-conclusion rerun policy (GitHub Actions log
|
|
59
|
+
* excerpts, `gh run view`/`gh run rerun` rules for CANCELLED/STARTUP_FAILURE/external
|
|
60
|
+
* failures) is invariant text keyed on the `[conclusion: …]` tags already rendered in
|
|
61
|
+
* `## Failing checks` — it lives in the pr-shepherd skill's "CI failure triage" playbook
|
|
62
|
+
* instead of being re-emitted every tick. This supersedes the "CI budget rules" example in
|
|
63
|
+
* CLAUDE.md's "Keep skills and loop prompts minimal" section (see that section's amendment
|
|
64
|
+
* note). The `(no runId)` case stays here because it flips `buildFixCompletionInstruction`
|
|
65
|
+
* to a human-handoff terminal state — that trigger, unlike the others, is CLI-decided. The
|
|
66
|
+
* CLI sentence does not claim every failure has a log excerpt to read (only GitHub Actions
|
|
67
|
+
* checks with a runId do — CANCELLED, STARTUP_FAILURE, and external checks may not); that
|
|
68
|
+
* per-kind detail is exactly what the skill playbook table disambiguates.
|
|
69
|
+
*/
|
|
40
70
|
export function buildFailingCheckInstructions(checks) {
|
|
41
71
|
if (checks.length === 0)
|
|
42
72
|
return [];
|
|
43
|
-
const hasRunId = checks.some((c) => c.runId && c.conclusion !== "CANCELLED" && c.conclusion !== "STARTUP_FAILURE");
|
|
44
|
-
const hasCancelled = checks.some((c) => c.runId && c.conclusion === "CANCELLED");
|
|
45
|
-
const hasStartupFailure = checks.some((c) => c.runId && c.conclusion === "STARTUP_FAILURE");
|
|
46
|
-
const hasExternal = checks.some((c) => !c.runId && c.detailsUrl);
|
|
47
73
|
const hasBare = checks.some((c) => !c.runId && !c.detailsUrl);
|
|
74
|
+
const hasTriageable = checks.some((c) => c.runId || c.detailsUrl);
|
|
48
75
|
const instructions = [];
|
|
49
|
-
if (
|
|
50
|
-
instructions.push(
|
|
51
|
-
}
|
|
52
|
-
if (hasCancelled) {
|
|
53
|
-
instructions.push("For each `[conclusion: CANCELLED]` failure, run `gh run rerun <runId>` unless this tick will push new commits.", "Do not treat a cancelled failure as resolved. `## Cancelled runs` is a different section.");
|
|
54
|
-
}
|
|
55
|
-
if (hasStartupFailure) {
|
|
56
|
-
instructions.push("For each `[conclusion: STARTUP_FAILURE]` failure, inspect it with `gh run view <runId>` and rerun it with `gh run rerun <runId>` if warranted.");
|
|
57
|
-
}
|
|
58
|
-
if (hasExternal) {
|
|
59
|
-
instructions.push("For each `external` failure, open its URL and inspect it.");
|
|
76
|
+
if (hasTriageable) {
|
|
77
|
+
instructions.push('Triage every failure under `## Failing checks`. See "CI failure triage" in the pr-shepherd skill for `gh run view` / `gh run rerun` rules.');
|
|
60
78
|
}
|
|
61
79
|
if (hasBare) {
|
|
62
80
|
instructions.push("For each `(no runId)` failure, escalate to a human because no log or URL is available.");
|
|
@@ -68,5 +86,5 @@ export function buildFixCompletionInstruction(checks) {
|
|
|
68
86
|
if (requiresHumanHandoff) {
|
|
69
87
|
return "`[FIX_CODE]` requires a human handoff for an uninspectable failing check. Stop polling after escalating, and resume only after human direction.";
|
|
70
88
|
}
|
|
71
|
-
return "`[FIX_CODE]` is non-terminal. After completing these steps,
|
|
89
|
+
return "`[FIX_CODE]` is non-terminal. After completing these steps, iterate again with the same options to continue.";
|
|
72
90
|
}
|
|
@@ -8,7 +8,7 @@ import { buildResolveCommand } from "./classify.mjs";
|
|
|
8
8
|
import { buildFixInstructions } from "./render.mjs";
|
|
9
9
|
import { applyStallGuard } from "./stall.mjs";
|
|
10
10
|
import { tryCancelRun, buildAutoCancelRunIdsWithOptions, buildInProgressRunIds, buildRunProtection, } from "./helpers.mjs";
|
|
11
|
-
import { annotationMarkerBody,
|
|
11
|
+
import { annotationMarkerBody, checksWithActionableAnnotations } from "../check-annotations.mjs";
|
|
12
12
|
import { threadTranscriptBody } from "../../threads/transcript.mjs";
|
|
13
13
|
import { isHumanAuthor, isConfiguredBotAuthor } from "../../comments/authors.mjs";
|
|
14
14
|
import { loadConfig } from "../../config/load.mjs";
|
|
@@ -30,7 +30,7 @@ function nextFixAttempts(stored, headSha, threads) {
|
|
|
30
30
|
export async function handleFixCode(ctx) {
|
|
31
31
|
const { base, report, opts, headSha, stallKey, prNumber, stallTimeoutSeconds, repoOwner, repoName, reviewSummaryIds, firstLookSummaries, editedSummaries, surfacedApprovals, botUsernames, ruleAutoResolveThreadIds, } = ctx;
|
|
32
32
|
const failingChecks = report.checks.failing;
|
|
33
|
-
const annotatedExtra =
|
|
33
|
+
const annotatedExtra = checksWithActionableAnnotations(report).filter((c) => c.category !== "failing");
|
|
34
34
|
const { protectedRunIds, protectedRuns } = buildRunProtection([...failingChecks, ...report.checks.inProgress], opts.neverCancelRuns);
|
|
35
35
|
const stored = await readFixAttempts({ owner: repoOwner, repo: repoName, pr: prNumber });
|
|
36
36
|
const { threadAttempts, threadBodyHashes } = nextFixAttempts(stored, headSha, report.threads.actionable);
|
|
@@ -12,7 +12,7 @@ import { clearStallState } from "../../state/iterate-stall.mjs";
|
|
|
12
12
|
import { handleFixCode } from "./fix-code.mjs";
|
|
13
13
|
import { normalizeBotUsernames } from "../../comments/authors.mjs";
|
|
14
14
|
import { autoMinimizeComments } from "../../comments/resolve.mjs";
|
|
15
|
-
import {
|
|
15
|
+
import { checksWithActionableAnnotations } from "../check-annotations.mjs";
|
|
16
16
|
export async function runIterate(opts) {
|
|
17
17
|
const config = loadConfig();
|
|
18
18
|
const botUsernames = normalizeBotUsernames(config.botUsernames);
|
|
@@ -70,7 +70,7 @@ export async function runIterate(opts) {
|
|
|
70
70
|
report.comments.firstLook.length > 0 ||
|
|
71
71
|
report.changesRequestedReviews.length > 0 ||
|
|
72
72
|
report.checks.failing.length > 0 ||
|
|
73
|
-
|
|
73
|
+
checksWithActionableAnnotations(report).length > 0 ||
|
|
74
74
|
report.mergeStatus.status === "CONFLICTS" ||
|
|
75
75
|
reviewSummaryIds.length > 0 ||
|
|
76
76
|
firstLookSummaries.length > 0 ||
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { renderShellCommand } from "../../cli/runner.mjs";
|
|
2
2
|
import { buildFailingCheckInstructions, buildCrStaleClause, buildBehindBaseHintInstruction, buildResolveCommandInstruction, buildFixCompletionInstruction, } from "./check-instructions.mjs";
|
|
3
|
-
import { SHEPHERD_JOURNAL_FIRST_LOOK_GUIDANCE,
|
|
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";
|
|
6
6
|
/** Render a resolve command as a shell snippet. Appends `--require-sha "$HEAD_SHA"` when set. */
|
|
@@ -42,7 +42,7 @@ isBehind = false) {
|
|
|
42
42
|
}
|
|
43
43
|
const firstLookTotal = firstLookThreads.length + firstLookComments.length;
|
|
44
44
|
if (firstLookTotal > 0) {
|
|
45
|
-
instructions.push("Review every item under `## First-look items` before acting."
|
|
45
|
+
instructions.push("Review every item under `## First-look items` before acting.");
|
|
46
46
|
}
|
|
47
47
|
if (firstLookSummaries.length > 0)
|
|
48
48
|
instructions.push(SHEPHERD_JOURNAL_FIRST_LOOK_GUIDANCE);
|
|
@@ -54,35 +54,30 @@ isBehind = false) {
|
|
|
54
54
|
instructions.push("Read every item marked `[edited since first look]`, including edited summaries and edited first-look bullets, before deciding whether to resolve a matching thread.");
|
|
55
55
|
}
|
|
56
56
|
if (inProgressRunIds.length > 0) {
|
|
57
|
-
instructions.push("If you will push, first cancel every ID under `## In-progress runs` with `gh run cancel <id
|
|
57
|
+
instructions.push("If you will push, first cancel every ID under `## In-progress runs` with `gh run cancel <id>` (ignore errors for runs that already finished). If you will not push, leave them alone.");
|
|
58
58
|
}
|
|
59
59
|
if (cancelledCount > 0) {
|
|
60
60
|
instructions.push("Do not cancel the IDs under `## Cancelled runs` again. The CLI already cancelled them.");
|
|
61
61
|
}
|
|
62
62
|
const hasSuggestions = threads.some((t) => t.suggestion);
|
|
63
63
|
if (hasSuggestions)
|
|
64
|
-
instructions.push(
|
|
64
|
+
instructions.push(buildCommitSuggestionInstruction(prNumber, "## Review threads"));
|
|
65
65
|
if (threads.length > 0 || actionableComments.length > 0) {
|
|
66
66
|
// Actionable comments carry no file/line location (unlike threads), so "referenced above"
|
|
67
67
|
// is only accurate when threads are present.
|
|
68
68
|
const filesRef = threads.length > 0 ? "each file referenced above" : "the relevant files";
|
|
69
69
|
instructions.push(`Apply every warranted review fix in ${filesRef}.`);
|
|
70
|
-
if (hasSuggestions) {
|
|
71
|
-
instructions.push("After source drift prevents a generated suggestion patch from applying, replace the heading's exact `path:startLine-endLine` range with the `Replaces lines …` block verbatim. An empty replacement deletes the range. One blank line replaces it with one blank line.", "When `build-suggestion-patch` refuses because the suggestion is unsafe (an unsafe anchored range or nested/unbalanced suggestion fences), do not apply the replacement block verbatim. Inspect the surrounding source and reviewer intent, then make the intended edit manually.");
|
|
72
|
-
}
|
|
73
70
|
}
|
|
74
71
|
if (resolutionOnlyThreads.length > 0) {
|
|
75
|
-
instructions.push(
|
|
72
|
+
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.');
|
|
76
73
|
}
|
|
77
74
|
instructions.push(...buildFailingCheckInstructions(failingChecks));
|
|
78
75
|
if (hasAnnotations) {
|
|
79
|
-
instructions.push("Inspect every referenced range under `## Check annotations` and apply any warranted change."
|
|
76
|
+
instructions.push("Inspect every referenced range under `## Check annotations` and apply any warranted change.");
|
|
80
77
|
}
|
|
81
78
|
if (changesRequestedReviews.length > 0) {
|
|
82
79
|
const staleClause = buildCrStaleClause(changesRequestedReviews);
|
|
83
80
|
instructions.push(`Read every body under \`## Changes-requested reviews\` and apply any warranted change.${staleClause}`);
|
|
84
|
-
if ((resolveCommand.dismissReviewIds?.length ?? 0) > 0)
|
|
85
|
-
instructions.push("Keep every existing `--dismiss-review-ids` ID in `apply review:`. Each is a bot or non-human review that must be dismissed. Omitting one leaves the PR in `CHANGES_REQUESTED`.");
|
|
86
81
|
}
|
|
87
82
|
instructions.push(...buildBehindBaseHintInstruction(baseBranch, behindBaseHint, isBehind));
|
|
88
83
|
const hasReviewMutations = resolveCommand.hasMutations || resolveOnlyCommand?.hasMutations === true;
|
|
@@ -98,7 +93,7 @@ isBehind = false) {
|
|
|
98
93
|
firstLookTotal > 0 ||
|
|
99
94
|
firstLookSummaries.length > 0 ||
|
|
100
95
|
editedTotal > 0) {
|
|
101
|
-
instructions.push(
|
|
96
|
+
instructions.push(buildShepherdJournalInstruction(prNumber));
|
|
102
97
|
}
|
|
103
98
|
if (resolveOnlyCommand?.hasMutations)
|
|
104
99
|
instructions.push("Run the `resolve-only:` command shown above.");
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readStallState, writeStallState } from "../../state/iterate-stall.mjs";
|
|
2
2
|
import { toAgentThread, toAgentComment, toAgentStalledCheck } from "../../reporters/agent.mjs";
|
|
3
3
|
import { buildEscalateSuggestion, buildEscalateHumanMessage, formatDurationApprox, } from "./escalate.mjs";
|
|
4
|
-
import {
|
|
4
|
+
import { checksWithActionableAnnotations } from "../check-annotations.mjs";
|
|
5
5
|
function computeStallFingerprint(action, headSha, base, report, reviewSummaryIds) {
|
|
6
6
|
const checks = [
|
|
7
7
|
...report.checks.failing.map((f) => `failing:${f.name}:${f.conclusion}`),
|
|
@@ -14,7 +14,7 @@ function computeStallFingerprint(action, headSha, base, report, reviewSummaryIds
|
|
|
14
14
|
const reviews = report.changesRequestedReviews.map((r) => r.id).sort();
|
|
15
15
|
const summaries = [...reviewSummaryIds].sort();
|
|
16
16
|
const ruleAutoResolveSummaries = (report.ruleAutoResolveReviewSummaryIds ?? []).sort((a, b) => a.localeCompare(b));
|
|
17
|
-
const annotations =
|
|
17
|
+
const annotations = checksWithActionableAnnotations(report)
|
|
18
18
|
.flatMap((c) => (c.annotations ?? []).map((a) => a.id))
|
|
19
19
|
.sort((a, b) => a.localeCompare(b));
|
|
20
20
|
return JSON.stringify({
|
package/bin/commands/poll.mjs
CHANGED
|
@@ -6,7 +6,7 @@ function writeTickProgress(tick, elapsedSeconds, sleepSeconds, verbose) {
|
|
|
6
6
|
process.stderr.write(`[poll tick ${tick} / +${elapsedSeconds}s] WAIT — sleeping ${sleepSeconds}s\n`);
|
|
7
7
|
}
|
|
8
8
|
else {
|
|
9
|
-
process.stderr.write(
|
|
9
|
+
process.stderr.write(`[poll tick ${tick} / +${elapsedSeconds}s] WAIT — still running; next tick in ${sleepSeconds}s\n`);
|
|
10
10
|
}
|
|
11
11
|
}
|
|
12
12
|
function waitSignature(result) {
|
|
@@ -73,7 +73,6 @@ export async function runPoll(opts) {
|
|
|
73
73
|
const verbose = opts.verbose === true;
|
|
74
74
|
const quietStatus = quietStatusOpt === true;
|
|
75
75
|
const untilTerminal = untilTerminalOpt === true;
|
|
76
|
-
let dotsPrinted = false;
|
|
77
76
|
let lastWaitSignature = null;
|
|
78
77
|
// When prNumber is omitted, iterateOpts.prNumber starts undefined and each tick would otherwise
|
|
79
78
|
// re-infer the PR from the current branch. That inference query only matches OPEN PRs, so once
|
|
@@ -110,8 +109,6 @@ export async function runPoll(opts) {
|
|
|
110
109
|
verbose,
|
|
111
110
|
lastWaitSignature,
|
|
112
111
|
});
|
|
113
|
-
if (!quietStatus && !verbose)
|
|
114
|
-
dotsPrinted = true;
|
|
115
112
|
await sleep(intervalMs);
|
|
116
113
|
continue;
|
|
117
114
|
}
|
|
@@ -124,10 +121,6 @@ export async function runPoll(opts) {
|
|
|
124
121
|
debounceUntil ??= Date.now() + debounceMs;
|
|
125
122
|
const remainingMs = debounceUntil - Date.now();
|
|
126
123
|
if (remainingMs > 0) {
|
|
127
|
-
if (dotsPrinted) {
|
|
128
|
-
process.stderr.write("\n");
|
|
129
|
-
dotsPrinted = false;
|
|
130
|
-
}
|
|
131
124
|
writeDebounceProgress(tick, Date.now() - start, remainingMs);
|
|
132
125
|
await sleep(Math.min(intervalMs, remainingMs));
|
|
133
126
|
}
|
|
@@ -138,7 +131,5 @@ export async function runPoll(opts) {
|
|
|
138
131
|
}
|
|
139
132
|
break;
|
|
140
133
|
}
|
|
141
|
-
if (dotsPrinted)
|
|
142
|
-
process.stderr.write("\n");
|
|
143
134
|
return lastResult;
|
|
144
135
|
}
|
|
@@ -24,6 +24,7 @@ async function readMergeability(prNumber, repo, batchData) {
|
|
|
24
24
|
const restState = await getMergeableState(prNumber, repo.owner, repo.name);
|
|
25
25
|
return {
|
|
26
26
|
...batchData,
|
|
27
|
+
state: restState.state ?? batchData.state,
|
|
27
28
|
mergeable: restState.mergeable ?? batchData.mergeable,
|
|
28
29
|
mergeStateStatus: restState.mergeStateStatus ?? batchData.mergeStateStatus,
|
|
29
30
|
};
|
|
@@ -5,6 +5,10 @@ export declare const SHEPHERD_JOURNAL_DETAILS_SUMMARY = "<summary>Shepherd Journ
|
|
|
5
5
|
export declare const SHEPHERD_JOURNAL_DETAILS_CLOSE = "</details>";
|
|
6
6
|
export declare const SHEPHERD_JOURNAL_APPEND_HINT = "If Shepherd Journal details already exist, append entries inside them instead of creating another container.";
|
|
7
7
|
export declare const SHEPHERD_JOURNAL_FIRST_LOOK_GUIDANCE = "Review each body under `## Review summaries (first look)`. Eligible non-human IDs are already in `--minimize-comment-ids`. Record any warranted Shepherd Journal note before review mutations.";
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
/**
|
|
9
|
+
* Build the Shepherd Journal instruction step. The reference-citation convention (link
|
|
10
|
+
* threads/comments from their headings, cite reviews by ID) is invariant across every
|
|
11
|
+
* invocation, so it lives in the pr-shepherd skill's "Shepherd Journal" playbook instead
|
|
12
|
+
* of being re-emitted every tick (see CLAUDE.md "Keep skills and loop prompts minimal").
|
|
13
|
+
*/
|
|
14
|
+
export declare function buildShepherdJournalInstruction(prNumber: number): string;
|
|
@@ -5,11 +5,12 @@ export const SHEPHERD_JOURNAL_DETAILS_SUMMARY = "<summary>Shepherd Journal</summ
|
|
|
5
5
|
export const SHEPHERD_JOURNAL_DETAILS_CLOSE = "</details>";
|
|
6
6
|
export const SHEPHERD_JOURNAL_APPEND_HINT = "If Shepherd Journal details already exist, append entries inside them instead of creating another container.";
|
|
7
7
|
export const SHEPHERD_JOURNAL_FIRST_LOOK_GUIDANCE = "Review each body under `## Review summaries (first look)`. Eligible non-human IDs are already in `--minimize-comment-ids`. Record any warranted Shepherd Journal note before review mutations.";
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
/**
|
|
9
|
+
* Build the Shepherd Journal instruction step. The reference-citation convention (link
|
|
10
|
+
* threads/comments from their headings, cite reviews by ID) is invariant across every
|
|
11
|
+
* invocation, so it lives in the pr-shepherd skill's "Shepherd Journal" playbook instead
|
|
12
|
+
* of being re-emitted every tick (see CLAUDE.md "Keep skills and loop prompts minimal").
|
|
13
|
+
*/
|
|
14
|
+
export function buildShepherdJournalInstruction(prNumber) {
|
|
15
|
+
return `For any substantial decision or rejection, append \`- <decision>\` to Shepherd Journal with \`pr-shepherd apply journal ${prNumber} '- <decision>'\`. See "Shepherd Journal" in the pr-shepherd skill for citation conventions.`;
|
|
13
16
|
}
|
|
14
|
-
export const SHEPHERD_JOURNAL_REFERENCE_GUIDANCE_THREADS_AND_COMMENTS_IN_ITEM_HEADINGS = "Link threads and comments from their headings. Cite reviews by ID.";
|
|
15
|
-
export const SHEPHERD_JOURNAL_REFERENCE_GUIDANCE_THREADS_AND_COMMENTS_IN_ITEMS = "Link threads and comments from their item bullets. Cite reviews by ID.";
|
package/bin/github/client.d.mts
CHANGED
|
@@ -34,13 +34,15 @@ export declare function getPullRequestBody(pr: number, owner: string, name: stri
|
|
|
34
34
|
/** Overwrites the PR body. */
|
|
35
35
|
export declare function updatePullRequestBody(pullRequestId: string, body: string): Promise<void>;
|
|
36
36
|
/**
|
|
37
|
-
* Fetches `mergeable
|
|
37
|
+
* Fetches PR state, `mergeable`, and `mergeStateStatus` via the REST API.
|
|
38
38
|
*
|
|
39
|
-
* Used
|
|
40
|
-
*
|
|
39
|
+
* Used when the GraphQL API returns `UNKNOWN` for mergeability or before a
|
|
40
|
+
* READY handoff. The same response carries state so a concurrent merge or
|
|
41
|
+
* close can supersede the earlier GraphQL snapshot without another request.
|
|
41
42
|
*/
|
|
42
43
|
export declare function getMergeableState(pr: number, owner: string, repo: string): Promise<{
|
|
43
44
|
mergeable: MergeableState;
|
|
44
45
|
mergeStateStatus: MergeStateStatus;
|
|
46
|
+
state?: "OPEN" | "CLOSED" | "MERGED";
|
|
45
47
|
}>;
|
|
46
48
|
export declare function getCurrentBranch(): Promise<string>;
|
package/bin/github/client.mjs
CHANGED
|
@@ -83,16 +83,23 @@ export async function updatePullRequestBody(pullRequestId, body) {
|
|
|
83
83
|
await httpGraphql(UPDATE_PR_BODY_MUTATION, { pullRequestId, body });
|
|
84
84
|
}
|
|
85
85
|
/**
|
|
86
|
-
* Fetches `mergeable
|
|
86
|
+
* Fetches PR state, `mergeable`, and `mergeStateStatus` via the REST API.
|
|
87
87
|
*
|
|
88
|
-
* Used
|
|
89
|
-
*
|
|
88
|
+
* Used when the GraphQL API returns `UNKNOWN` for mergeability or before a
|
|
89
|
+
* READY handoff. The same response carries state so a concurrent merge or
|
|
90
|
+
* close can supersede the earlier GraphQL snapshot without another request.
|
|
90
91
|
*/
|
|
91
92
|
export async function getMergeableState(pr, owner, repo) {
|
|
92
93
|
const data = await rest("GET", `/repos/${owner}/${repo}/pulls/${pr}`);
|
|
93
94
|
const mergeable = data.mergeable === true ? "MERGEABLE" : data.mergeable === false ? "CONFLICTING" : "UNKNOWN";
|
|
94
95
|
const mergeStateStatus = data.mergeable_state.toUpperCase();
|
|
95
|
-
|
|
96
|
+
const rawState = data.state?.toUpperCase();
|
|
97
|
+
const state = data.merged_at != null
|
|
98
|
+
? "MERGED"
|
|
99
|
+
: rawState === "OPEN" || rawState === "CLOSED"
|
|
100
|
+
? rawState
|
|
101
|
+
: undefined;
|
|
102
|
+
return { mergeable, mergeStateStatus, ...(state !== undefined && { state }) };
|
|
96
103
|
}
|
|
97
104
|
// ---------------------------------------------------------------------------
|
|
98
105
|
// Internal helpers
|
package/package.json
CHANGED
|
@@ -19,3 +19,47 @@ Thin dispatcher for iterating a PR. Poll with the CLI; use MCP `iterate` only wh
|
|
|
19
19
|
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_patch`; do not run a shell `pr-shepherd apply` command.
|
|
20
20
|
|
|
21
21
|
4. After completing the returned instructions, repeat step 2 unless the action is `[CANCEL]` or `[ESCALATE]`, the instructions require a human handoff, or the human directs you to stop.
|
|
22
|
+
|
|
23
|
+
## Playbooks
|
|
24
|
+
|
|
25
|
+
`## Instructions` steps reference these playbooks by name instead of repeating their
|
|
26
|
+
mechanics every tick. Apply the referenced playbook in full whenever a step points here.
|
|
27
|
+
|
|
28
|
+
### Suggestion patches
|
|
29
|
+
|
|
30
|
+
- The CLI only builds the patch. Apply it, stage the listed file, and follow the returned commit instructions.
|
|
31
|
+
- If the command refuses because the suggestion is unsafe (an unsafe anchored range or nested/unbalanced suggestion fences), skip patch application and edit the file manually. Do not retry the command.
|
|
32
|
+
- For any other refusal, follow the CLI error's stated recovery action; do not manually edit the suggestion.
|
|
33
|
+
- If the patch does not apply for any other reason, edit the file manually instead. Do not retry the command.
|
|
34
|
+
- After source drift prevents a generated suggestion patch from applying, replace the heading's exact `path:startLine-endLine` range with the `Replaces lines …` block verbatim. An empty replacement deletes the range. One blank line replaces it with one blank line.
|
|
35
|
+
- Keep human-authored thread IDs in `apply review:` so Shepherd replies instead of resolving them.
|
|
36
|
+
|
|
37
|
+
### CI failure triage
|
|
38
|
+
|
|
39
|
+
Match each failure's `[conclusion: …]` tag under `## Failing checks` to a rule:
|
|
40
|
+
|
|
41
|
+
More specific rows win over the general "GitHub Actions failure" row — check conclusion first.
|
|
42
|
+
|
|
43
|
+
| Tag / kind | Do |
|
|
44
|
+
| ------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
45
|
+
| GitHub Actions failure (has a run ID, not `CANCELLED`/`STARTUP_FAILURE`) | Read the included log excerpt if one is rendered. If missing or insufficient, run a bounded command such as `gh run view <runId> --log-failed \| tail -n 200` — the unbounded form can dump excessive log output into context. Open the run URL only if that still lacks detail. |
|
|
46
|
+
| Transient infrastructure failure | Rerun with `gh run rerun <runId> --failed`. |
|
|
47
|
+
| Real test or build failure | Apply a code fix — do not rerun. |
|
|
48
|
+
| `[conclusion: CANCELLED]` | No log excerpt is rendered for this conclusion. Run `gh run rerun <runId>` unless this tick will push new commits. Not resolved by a rerun classification — `## Cancelled runs` is a different section. |
|
|
49
|
+
| `[conclusion: STARTUP_FAILURE]` | No log excerpt is rendered for this conclusion. Inspect with `gh run view <runId>`, rerun with `gh run rerun <runId>` if warranted. |
|
|
50
|
+
| `external` (no run ID, has a URL) | Open its URL and inspect it. |
|
|
51
|
+
|
|
52
|
+
### Review-mutation mechanics
|
|
53
|
+
|
|
54
|
+
Applies to every `apply review:` / `resolve-only:` command the CLI prints. Covers only what stays safe if you run the printed command **unmodified** — `$HEAD_SHA`/`$DISMISS_MESSAGE` substitution and the self-reply exclusion rule are separate CLI-printed steps, not covered here, because the printed command is unsafe by default without them.
|
|
55
|
+
|
|
56
|
+
- Never add first-look-only or check-annotation IDs to `--reply-thread-ids`, `--resolve-thread-ids`, `--dismiss-review-ids`, or `--minimize-comment-ids` — those flags are pre-populated by the CLI.
|
|
57
|
+
- Keep every existing `--dismiss-review-ids` ID the CLI already included. Each is a bot or non-human review that must be dismissed; omitting one leaves the PR in `CHANGES_REQUESTED`.
|
|
58
|
+
|
|
59
|
+
### Review-mutation routing
|
|
60
|
+
|
|
61
|
+
For threads under `## Review threads to resolve`: human-authored IDs use `--reply-thread-ids` (Shepherd replies instead of resolving them); bot and non-human IDs use `--resolve-thread-ids`. Use the commands as generated — do not move an ID between flags.
|
|
62
|
+
|
|
63
|
+
### Shepherd Journal
|
|
64
|
+
|
|
65
|
+
Link threads and comments in a journal entry from their headings in the CLI output. Cite reviews by ID.
|