pr-shepherd 0.20.0 → 0.21.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 +7 -7
- package/bin/checks/triage.test-support.mjs +1 -2
- package/bin/cli/default-poll.mjs +32 -0
- package/bin/cli/help-command-pages.mjs +3 -4
- package/bin/cli/help-top-page.mjs +5 -5
- package/bin/cli/iterate-lean.test-support.mjs +0 -2
- package/bin/cli/validate-default-args.mjs +32 -0
- package/bin/cli-parser.clean.test-support.mjs +0 -1
- package/bin/cli-parser.commit-suggestion.test-support.mjs +1 -2
- package/bin/cli-parser.iterate-fix.test-support.mjs +1 -2
- package/bin/cli-parser.iterate.test-support.mjs +1 -2
- package/bin/cli-parser.mjs +7 -7
- package/bin/cli-parser.test-support.mjs +1 -2
- package/bin/commands/check.mjs +2 -1
- package/bin/commands/check.test-support.mjs +1 -2
- package/bin/commands/clean.test-support.mjs +0 -1
- package/bin/commands/commit-suggestion.apply.test-support.mjs +1 -2
- package/bin/commands/commit-suggestion.test-support.mjs +1 -2
- package/bin/commands/iterate-stall.test-support.mjs +0 -1
- package/bin/commands/iterate.fix-code-in-progress.test-support.mjs +2 -2
- package/bin/commands/poll.mjs +12 -3
- package/bin/commands/resolve.test-support.mjs +2 -2
- package/bin/commands/shepherd-journal.test-support.mjs +0 -2
- package/bin/comments/resolve.test-support.mjs +1 -2
- package/bin/github/batch-parsers.test-support.mjs +1 -2
- package/bin/github/batch.test-support.mjs +1 -2
- package/bin/github/http.test-support.mjs +1 -2
- package/bin/suggestions/patch.test-support.mjs +0 -2
- package/package.json +1 -1
- package/plugins/pr-shepherd/.codex-plugin/plugin.json +1 -1
- package/plugins/pr-shepherd/skills/pr-shepherd/SKILL.md +8 -8
- package/bin/cli/default-iterate.mjs +0 -50
package/README.md
CHANGED
|
@@ -22,10 +22,10 @@ Example Workflow:
|
|
|
22
22
|
|
|
23
23
|
The CLI emits unified recheck instructions. Generated commands call `pr-shepherd` directly.
|
|
24
24
|
|
|
25
|
-
At a high level, the skill invokes `pr-shepherd
|
|
25
|
+
At a high level, the skill invokes `pr-shepherd <PR>` (which polls by default), providing actionable feedback directly to the agent:
|
|
26
26
|
|
|
27
27
|
```text
|
|
28
|
-
> pr-shepherd
|
|
28
|
+
> pr-shepherd 123
|
|
29
29
|
|
|
30
30
|
# PR #123 [FIX_CODE]
|
|
31
31
|
|
|
@@ -61,7 +61,7 @@ _(schematic — actual steps depend on PR state)_
|
|
|
61
61
|
4. Rebase and push: `git fetch origin && git rebase origin/main && git push --force-with-lease` — capture `HEAD_SHA=$(git rev-parse HEAD)`.
|
|
62
62
|
5. Run the `resolve:` command above, substituting `"$HEAD_SHA"`.
|
|
63
63
|
6. Add or update a `## Shepherd Journal` section in the PR description for any large decisions made, appending under the existing heading if it already exists.
|
|
64
|
-
7. CI needs time to run on the new push. Run `pr-shepherd
|
|
64
|
+
7. CI needs time to run on the new push. Run `pr-shepherd 123` again to continue until Shepherd returns `[CANCEL]` or `[ESCALATE]`.
|
|
65
65
|
```
|
|
66
66
|
|
|
67
67
|
On every iteration, a command is returned to instruct the agent exactly what to do. No guessing, no thinking, as few agentic turns as possible:
|
|
@@ -97,7 +97,7 @@ Some other workflow improvements:
|
|
|
97
97
|
|
|
98
98
|
Recommendations:
|
|
99
99
|
|
|
100
|
-
- Run `pr-shepherd` on all your PRs before you go to sleep so that you wake up to reviewable PRs. Keep an active goal cycling `pr-shepherd
|
|
100
|
+
- Run `pr-shepherd` on all your PRs before you go to sleep so that you wake up to reviewable PRs. Keep an active goal cycling `pr-shepherd <PR>` until Shepherd emits `[CANCEL]` for ready-delay completion or merged/closed, or `[ESCALATE]` (including `stall-timeout` for repeated unchanged CI failures).
|
|
101
101
|
- Instruct your agents to write comments in a single review (comment, changes requested, or approved). This allows the review's comments/threads to be minimized or resolved together, keeping your pull request history clean. If you write inline comments outside of a review, each comment would still show up in the pull request history and take up space.
|
|
102
102
|
- Avoid sticky comments as they will continue to be hidden. Instead, just make a new comment, especially on reviews. If you really want sticky comments, instruct your agent to unhide/unminimize them when updating them.
|
|
103
103
|
- Avoid having automation edit comments, reviews, or threads in place because updated items get minimized. Instead, always make a new review, comment, thread, etc.
|
|
@@ -114,7 +114,7 @@ Recommendations:
|
|
|
114
114
|
|
|
115
115
|
### Iterate a PR to completion
|
|
116
116
|
|
|
117
|
-
Poll dispatcher — checks CI and review comments, fixes issues, and marks the PR ready for review when clean. Each non-terminal tick emits an `## Instructions` section; the skill follows it, then invokes `pr-shepherd
|
|
117
|
+
Poll dispatcher — checks CI and review comments, fixes issues, and marks the PR ready for review when clean. Each non-terminal tick emits an `## Instructions` section; the skill follows it, then invokes `pr-shepherd <PR>` again until `[CANCEL]` or `[ESCALATE]`.
|
|
118
118
|
|
|
119
119
|
Claude Code (via `pr-shepherd` skill):
|
|
120
120
|
|
|
@@ -128,9 +128,9 @@ Codex or direct CLI:
|
|
|
128
128
|
|
|
129
129
|
```sh
|
|
130
130
|
pr-shepherd 42
|
|
131
|
+
pr-shepherd 42 --interval 45s --timeout 4m
|
|
131
132
|
pr-shepherd 42 --ready-delay 15m
|
|
132
|
-
pr-shepherd
|
|
133
|
-
pr-shepherd iterate 42 # legacy-compatible spelling
|
|
133
|
+
pr-shepherd iterate 42 # single tick
|
|
134
134
|
```
|
|
135
135
|
|
|
136
136
|
## Iterate decision loop
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
1
|
+
import { vi, beforeEach } from "vitest";
|
|
3
2
|
// ---------------------------------------------------------------------------
|
|
4
3
|
// Stub fetch globally so http.mts uses our mock.
|
|
5
4
|
// ---------------------------------------------------------------------------
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { parsePrNumber } from "./args.mjs";
|
|
2
|
+
import { validateDefaultArgs } from "./validate-default-args.mjs";
|
|
3
|
+
import { USAGE } from "./help.mjs";
|
|
4
|
+
const DEFAULT_POLL_FLAGS_WITH_VALUES = new Set([
|
|
5
|
+
"--format",
|
|
6
|
+
"--ready-delay",
|
|
7
|
+
"--stall-timeout",
|
|
8
|
+
"--interval",
|
|
9
|
+
"--timeout",
|
|
10
|
+
]);
|
|
11
|
+
const DEFAULT_POLL_BOOLEAN_FLAGS = new Set([
|
|
12
|
+
"--verbose",
|
|
13
|
+
"--no-auto-mark-ready",
|
|
14
|
+
"--no-auto-cancel-actionable",
|
|
15
|
+
]);
|
|
16
|
+
export function isDefaultPollInvocation(subcommand) {
|
|
17
|
+
if (subcommand === "--help" || subcommand === "-h")
|
|
18
|
+
return false;
|
|
19
|
+
return (subcommand === undefined || parsePrNumber(subcommand) !== null || isDefaultPollFlag(subcommand));
|
|
20
|
+
}
|
|
21
|
+
export function validateDefaultPollArgs(args) {
|
|
22
|
+
return validateDefaultArgs(args, DEFAULT_POLL_FLAGS_WITH_VALUES, DEFAULT_POLL_BOOLEAN_FLAGS, writeDefaultUsageError);
|
|
23
|
+
}
|
|
24
|
+
function isDefaultPollFlag(arg) {
|
|
25
|
+
const name = arg.split("=", 1)[0];
|
|
26
|
+
return DEFAULT_POLL_FLAGS_WITH_VALUES.has(name) || DEFAULT_POLL_BOOLEAN_FLAGS.has(arg);
|
|
27
|
+
}
|
|
28
|
+
function writeDefaultUsageError(arg) {
|
|
29
|
+
process.stderr.write(`Unknown subcommand: ${arg}\n`);
|
|
30
|
+
process.stderr.write(`${USAGE.top}\n`);
|
|
31
|
+
process.exitCode = 1;
|
|
32
|
+
}
|
|
@@ -51,11 +51,10 @@ Exit codes:
|
|
|
51
51
|
1 validation, lookup, precondition, or suggestion parsing failure`,
|
|
52
52
|
iterate: `pr-shepherd iterate
|
|
53
53
|
|
|
54
|
-
Run one iterate tick for a pull request.
|
|
54
|
+
Run one iterate tick for a pull request. The no-subcommand form polls; use this subcommand for a single tick.
|
|
55
55
|
The output contains one action and an action-specific ## Instructions section.
|
|
56
56
|
|
|
57
57
|
Usage:
|
|
58
|
-
pr-shepherd [PR] [iterate-flags]
|
|
59
58
|
pr-shepherd iterate [PR] [iterate-flags]
|
|
60
59
|
|
|
61
60
|
Iterate flags:
|
|
@@ -98,11 +97,11 @@ Forwarded iterate flags:
|
|
|
98
97
|
--no-auto-mark-ready Do not convert draft PRs to ready for review.
|
|
99
98
|
--no-auto-cancel-actionable Do not cancel in-progress runs before actionable fixes.
|
|
100
99
|
--format text|json Output Markdown text or JSON. Default: text.
|
|
101
|
-
--verbose Include verbose iterate fields and
|
|
100
|
+
--verbose Include verbose iterate fields and detailed per-tick lines.
|
|
102
101
|
--help, -h Print this help and exit before GitHub, git, config, or log I/O.
|
|
103
102
|
|
|
104
103
|
Durations accept seconds, minutes, or hours: 30s, 2m, 1h, or bare seconds.
|
|
105
|
-
WAIT
|
|
104
|
+
Each WAIT tick writes a single dot to stderr; --verbose emits the detailed per-tick line.
|
|
106
105
|
|
|
107
106
|
Exit codes:
|
|
108
107
|
0 WAIT timeout or MARK_READY
|
|
@@ -5,8 +5,8 @@ Autonomous PR CI monitor and review-comment resolver for agentic coding tools.
|
|
|
5
5
|
Usage:
|
|
6
6
|
pr-shepherd --version | -v
|
|
7
7
|
pr-shepherd --help | -h
|
|
8
|
-
pr-shepherd [PR] [flags]
|
|
9
|
-
pr-shepherd iterate [PR] [flags]
|
|
8
|
+
pr-shepherd [PR] [poll-flags] [iterate-flags]
|
|
9
|
+
pr-shepherd iterate [PR] [iterate-flags]
|
|
10
10
|
pr-shepherd poll [PR] [poll-flags] [iterate-flags]
|
|
11
11
|
pr-shepherd resolve [PR] [resolve-flags]
|
|
12
12
|
pr-shepherd commit-suggestion [PR] --thread-id ID --message MSG [flags]
|
|
@@ -14,8 +14,8 @@ Usage:
|
|
|
14
14
|
pr-shepherd log-file [--format text|json]
|
|
15
15
|
|
|
16
16
|
Commands:
|
|
17
|
-
[PR]
|
|
18
|
-
iterate
|
|
17
|
+
[PR] Poll until non-WAIT or timeout. This is the default command.
|
|
18
|
+
iterate Run one iterate tick (single-tick alias).
|
|
19
19
|
poll Re-run iterate while the action is WAIT, then print the final tick.
|
|
20
20
|
resolve Fetch actionable review items, or resolve/minimize/dismiss IDs.
|
|
21
21
|
commit-suggestion Convert one GitHub suggestion thread into a patch and commit instructions.
|
|
@@ -28,7 +28,7 @@ PR argument:
|
|
|
28
28
|
|
|
29
29
|
Common flags:
|
|
30
30
|
--format text|json Output Markdown text or JSON. Default: text.
|
|
31
|
-
--verbose Include verbose iterate fields and poll
|
|
31
|
+
--verbose Include verbose iterate fields and detailed poll-tick lines.
|
|
32
32
|
--help, -h Print help and exit before any GitHub, git, config, or log I/O.
|
|
33
33
|
|
|
34
34
|
Iterate flags:
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
|
-
import { describe, it, expect } from "vitest";
|
|
3
1
|
import { projectIterateLean, projectIterateVerbose } from "./iterate-lean.mjs";
|
|
4
2
|
import { makeIterateResult } from "../cli-parser.iterate-fixtures.mjs";
|
|
5
3
|
export { makeIterateResult, projectIterateLean, projectIterateVerbose };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { parsePrNumber } from "./args.mjs";
|
|
2
|
+
/**
|
|
3
|
+
* Validate args for default-dispatch paths (e.g. `pr-shepherd [PR] [flags]`).
|
|
4
|
+
* Returns false when an unexpected token is found; calls onError with the
|
|
5
|
+
* offending arg so callers can print their own usage message.
|
|
6
|
+
*/
|
|
7
|
+
export function validateDefaultArgs(args, flagsWithValues, booleanFlags, onError) {
|
|
8
|
+
let sawPr = false;
|
|
9
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
10
|
+
const arg = args[i];
|
|
11
|
+
if (flagsWithValues.has(arg)) {
|
|
12
|
+
if (i + 1 >= args.length || args[i + 1].startsWith("--")) {
|
|
13
|
+
onError(arg);
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
i += 1;
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
const inlineFlag = arg.split("=", 1)[0];
|
|
20
|
+
if (flagsWithValues.has(inlineFlag))
|
|
21
|
+
continue;
|
|
22
|
+
if (booleanFlags.has(arg))
|
|
23
|
+
continue;
|
|
24
|
+
if (parsePrNumber(arg) !== null && !sawPr) {
|
|
25
|
+
sawPr = true;
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
onError(arg);
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
1
|
+
import { vi, beforeEach, afterEach } from "vitest";
|
|
3
2
|
vi.mock("./commands/check.mts", () => ({ runCheck: vi.fn() }));
|
|
4
3
|
vi.mock("./commands/resolve.mts", () => ({
|
|
5
4
|
runResolveFetch: vi.fn(),
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
1
|
+
import { vi, beforeEach, afterEach } from "vitest";
|
|
3
2
|
vi.mock("./commands/check.mts", () => ({ runCheck: vi.fn() }));
|
|
4
3
|
vi.mock("./commands/resolve.mts", () => ({
|
|
5
4
|
runResolveFetch: vi.fn(),
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
1
|
+
import { vi, beforeEach, afterEach } from "vitest";
|
|
3
2
|
vi.mock("./commands/check.mts", () => ({ runCheck: vi.fn() }));
|
|
4
3
|
vi.mock("./commands/resolve.mts", () => ({
|
|
5
4
|
runResolveFetch: vi.fn(),
|
package/bin/cli-parser.mjs
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Usage:
|
|
5
5
|
* pr-shepherd --version
|
|
6
|
-
* pr-shepherd [PR] [--format text|json] [--ready-delay Nm]
|
|
6
|
+
* pr-shepherd [PR] [--interval 45s] [--timeout 4m] [--format text|json] [--ready-delay Nm]
|
|
7
7
|
* [--stall-timeout <duration>] [--no-auto-mark-ready]
|
|
8
8
|
* [--no-auto-cancel-actionable]
|
|
9
9
|
* pr-shepherd resolve [PR] [--fetch] [--resolve-thread-ids A,B] [--minimize-comment-ids X,Y]
|
|
@@ -22,7 +22,7 @@ import { readFileSync } from "node:fs";
|
|
|
22
22
|
import { runResolveFetch, runResolveMutate } from "./commands/resolve.mjs";
|
|
23
23
|
import { runLogFile } from "./commands/log-file.mjs";
|
|
24
24
|
import { parseCommonArgs, getFlag, hasFlag, parseList } from "./cli/args.mjs";
|
|
25
|
-
import {
|
|
25
|
+
import { isDefaultPollInvocation, validateDefaultPollArgs } from "./cli/default-poll.mjs";
|
|
26
26
|
import { USAGE, maybePrintHelp } from "./cli/help.mjs";
|
|
27
27
|
import { formatFetchResult, formatMutateResult } from "./cli/formatters.mjs";
|
|
28
28
|
import { handleClean, handleCommitSuggestion, handleIterate } from "./cli/handlers.mjs";
|
|
@@ -49,8 +49,8 @@ export async function main(argv) {
|
|
|
49
49
|
}
|
|
50
50
|
// Short-circuit all --help/-h before any I/O or logging.
|
|
51
51
|
if (hasFlag(args, "--help") || hasFlag(args, "-h")) {
|
|
52
|
-
if (
|
|
53
|
-
process.stdout.write(`${USAGE.
|
|
52
|
+
if (isDefaultPollInvocation(subcommand)) {
|
|
53
|
+
process.stdout.write(`${USAGE.poll}\n`);
|
|
54
54
|
}
|
|
55
55
|
else {
|
|
56
56
|
const key = subcommand != null && subcommand in USAGE
|
|
@@ -62,10 +62,10 @@ export async function main(argv) {
|
|
|
62
62
|
}
|
|
63
63
|
// Initialize the per-worktree log and install a stdout tee.
|
|
64
64
|
await setupLog(argv);
|
|
65
|
-
if (
|
|
66
|
-
if (!
|
|
65
|
+
if (isDefaultPollInvocation(subcommand)) {
|
|
66
|
+
if (!validateDefaultPollArgs(args))
|
|
67
67
|
return;
|
|
68
|
-
await
|
|
68
|
+
await handlePoll(args);
|
|
69
69
|
return;
|
|
70
70
|
}
|
|
71
71
|
switch (subcommand) {
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
1
|
import { readFileSync } from "node:fs";
|
|
3
|
-
import {
|
|
2
|
+
import { vi, beforeEach, afterEach } from "vitest";
|
|
4
3
|
vi.mock("./commands/resolve.mts", () => ({
|
|
5
4
|
runResolveFetch: vi.fn(),
|
|
6
5
|
runResolveMutate: vi.fn(),
|
package/bin/commands/check.mjs
CHANGED
|
@@ -46,7 +46,8 @@ export async function runCheck(opts) {
|
|
|
46
46
|
let autoResolveErrors = [];
|
|
47
47
|
if (opts.autoResolve && outdated.length > 0) {
|
|
48
48
|
const { resolved: resolvedIds, errors } = await autoResolveOutdated(outdated.map((t) => t.id));
|
|
49
|
-
|
|
49
|
+
const resolvedIdsSet = new Set(resolvedIds); // ⚡ Bolt optimization: O(1) lookup
|
|
50
|
+
autoResolved = outdated.filter((t) => resolvedIdsSet.has(t.id));
|
|
50
51
|
autoResolveErrors = errors;
|
|
51
52
|
}
|
|
52
53
|
const activeThreads = unresolvedThreads.filter((t) => !t.isOutdated && !t.isMinimized);
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
1
|
+
import { vi, beforeEach } from "vitest";
|
|
3
2
|
vi.mock("../github/batch.mts", () => ({ fetchPrBatch: vi.fn() }));
|
|
4
3
|
vi.mock("../github/client.mts", () => ({
|
|
5
4
|
getRepoInfo: vi.fn().mockResolvedValue({ owner: "owner", name: "repo" }),
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
1
|
+
import { vi } from "vitest";
|
|
3
2
|
// ---------------------------------------------------------------------------
|
|
4
3
|
// Hoisted mocks
|
|
5
4
|
// ---------------------------------------------------------------------------
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
1
|
+
import { vi, beforeEach } from "vitest";
|
|
3
2
|
// ---------------------------------------------------------------------------
|
|
4
3
|
// Hoisted mocks
|
|
5
4
|
// ---------------------------------------------------------------------------
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
1
|
+
import { vi, beforeEach, afterEach } from "vitest";
|
|
3
2
|
const mockFetch = vi.fn();
|
|
4
3
|
vi.stubGlobal("fetch", mockFetch);
|
|
5
4
|
const { mockExecFile } = vi.hoisted(() => ({ mockExecFile: vi.fn() }));
|
|
@@ -44,6 +43,7 @@ function makeReport(overrides = {}) {
|
|
|
44
43
|
repo: "owner/repo",
|
|
45
44
|
status: "READY",
|
|
46
45
|
baseBranch: "main",
|
|
46
|
+
branchProtection: null,
|
|
47
47
|
mergeStatus: {
|
|
48
48
|
status: "CLEAN",
|
|
49
49
|
state: "OPEN",
|
package/bin/commands/poll.mjs
CHANGED
|
@@ -3,9 +3,12 @@ function sleep(ms) {
|
|
|
3
3
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
4
4
|
}
|
|
5
5
|
function writeTickProgress(tick, elapsedSeconds, sleepSeconds, verbose) {
|
|
6
|
-
if (
|
|
6
|
+
if (verbose) {
|
|
7
7
|
process.stderr.write(`[poll tick ${tick} / +${elapsedSeconds}s] WAIT — sleeping ${sleepSeconds}s\n`);
|
|
8
8
|
}
|
|
9
|
+
else {
|
|
10
|
+
process.stderr.write(".");
|
|
11
|
+
}
|
|
9
12
|
}
|
|
10
13
|
const MAX_TIMER_MS = 2 ** 31 - 1;
|
|
11
14
|
export async function runPoll(opts) {
|
|
@@ -16,17 +19,23 @@ export async function runPoll(opts) {
|
|
|
16
19
|
let tick = 0;
|
|
17
20
|
let lastResult;
|
|
18
21
|
const verbose = opts.verbose === true;
|
|
22
|
+
let dotsPrinted = false;
|
|
19
23
|
while (true) {
|
|
20
24
|
tick += 1;
|
|
21
25
|
lastResult = await runIterate(iterateOpts);
|
|
22
26
|
if (lastResult.action !== "wait")
|
|
23
|
-
|
|
27
|
+
break;
|
|
24
28
|
const elapsedMs = Date.now() - start;
|
|
25
29
|
const remainingMs = timeoutMs - elapsedMs;
|
|
26
30
|
if (remainingMs <= 0)
|
|
27
|
-
|
|
31
|
+
break;
|
|
28
32
|
const nextSleepMs = Math.min(intervalMs, remainingMs);
|
|
29
33
|
writeTickProgress(tick, Math.round(elapsedMs / 1000), Math.round(nextSleepMs / 1000), verbose);
|
|
34
|
+
if (!verbose)
|
|
35
|
+
dotsPrinted = true;
|
|
30
36
|
await sleep(nextSleepMs);
|
|
31
37
|
}
|
|
38
|
+
if (dotsPrinted)
|
|
39
|
+
process.stderr.write("\n");
|
|
40
|
+
return lastResult;
|
|
32
41
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
1
|
+
import { vi, beforeEach } from "vitest";
|
|
3
2
|
vi.mock("../github/client.mts", () => ({
|
|
4
3
|
getRepoInfo: vi.fn().mockResolvedValue({ owner: "owner", name: "repo" }),
|
|
5
4
|
getCurrentPrNumber: vi.fn().mockResolvedValue(42),
|
|
@@ -67,6 +66,7 @@ function makeBatchData(overrides = {}) {
|
|
|
67
66
|
reviewSummaries: [],
|
|
68
67
|
approvedReviews: [],
|
|
69
68
|
checks: [],
|
|
69
|
+
branchProtection: null,
|
|
70
70
|
...overrides,
|
|
71
71
|
};
|
|
72
72
|
}
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
|
-
import { describe, expect, it } from "vitest";
|
|
3
1
|
import { buildShepherdJournalInstruction, SHEPHERD_JOURNAL_APPEND_HINT, SHEPHERD_JOURNAL_FIRST_LOOK_GUIDANCE, SHEPHERD_JOURNAL_REFERENCE_GUIDANCE_THREADS_AND_COMMENTS_IN_ITEM_HEADINGS, SHEPHERD_JOURNAL_REFERENCE_GUIDANCE_THREADS_AND_COMMENTS_IN_ITEMS, SHEPHERD_JOURNAL_SECTION, SHEPHERD_JOURNAL_SECTION_PATTERN, } from "./shepherd-journal.mjs";
|
|
4
2
|
import { buildFixInstructions } from "./iterate/render.mjs";
|
|
5
3
|
import { buildFetchInstructions } from "./resolve-instructions.mjs";
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
1
|
+
import { vi, beforeEach } from "vitest";
|
|
3
2
|
// ---------------------------------------------------------------------------
|
|
4
3
|
// Mock github/client.mts before any imports.
|
|
5
4
|
// ---------------------------------------------------------------------------
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
1
|
+
import { vi, beforeEach } from "vitest";
|
|
3
2
|
// ---------------------------------------------------------------------------
|
|
4
3
|
// Stub fetch and child_process globally before any imports.
|
|
5
4
|
// ---------------------------------------------------------------------------
|
package/package.json
CHANGED
|
@@ -20,20 +20,20 @@ Poll dispatcher for iterating a PR to completion.
|
|
|
20
20
|
- Otherwise, infer: `gh pr view --json number --jq .number`
|
|
21
21
|
- If no PR found, report an error and stop.
|
|
22
22
|
|
|
23
|
-
2. **Run `pr-shepherd
|
|
23
|
+
2. **Run `pr-shepherd`:**
|
|
24
24
|
|
|
25
25
|
```bash
|
|
26
|
-
pr-shepherd
|
|
26
|
+
pr-shepherd <N> --interval 45s --timeout 4m
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
-
Do not pass `$ARGUMENTS` through as extra flags. If you need to inspect supported options, run `pr-shepherd
|
|
29
|
+
Do not pass `$ARGUMENTS` through as extra flags. If you need to inspect supported options, run `pr-shepherd --help`.
|
|
30
30
|
|
|
31
|
-
Print the full output. Follow the `## Instructions` section exactly for the current action.
|
|
31
|
+
Print the full output. Follow the `## Instructions` section exactly for the current action.
|
|
32
32
|
|
|
33
|
-
3. **Persistence:** Continuously call `pr-shepherd
|
|
34
|
-
- `[WAIT]`: call `pr-shepherd
|
|
35
|
-
- `[MARK_READY]`: call `pr-shepherd
|
|
36
|
-
- `[FIX_CODE]`: follow the output's `## Instructions`, then call `pr-shepherd
|
|
33
|
+
3. **Persistence:** Continuously call `pr-shepherd <N> --interval 45s --timeout 4m` until the CLI returns `[CANCEL]` or `[ESCALATE]`, unless the human directs you to stop. Every other action is non-terminal:
|
|
34
|
+
- `[WAIT]`: call `pr-shepherd <N> --interval 45s --timeout 4m` again.
|
|
35
|
+
- `[MARK_READY]`: call `pr-shepherd <N> --interval 45s --timeout 4m` again.
|
|
36
|
+
- `[FIX_CODE]`: follow the output's `## Instructions`, then call `pr-shepherd <N> --interval 45s --timeout 4m` again.
|
|
37
37
|
|
|
38
38
|
Treat a nonzero poll exit code as PR state only when the output contains a matching `# PR #N [ACTION]` heading. Exit code `1` can also mean a command or validation failure; if there is no `[FIX_CODE]` heading, surface the error and stop instead of looping.
|
|
39
39
|
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import { parsePrNumber } from "./args.mjs";
|
|
2
|
-
import { USAGE } from "./help.mjs";
|
|
3
|
-
const DEFAULT_ITERATE_FLAGS_WITH_VALUES = new Set(["--format", "--ready-delay", "--stall-timeout"]);
|
|
4
|
-
const DEFAULT_ITERATE_BOOLEAN_FLAGS = new Set([
|
|
5
|
-
"--verbose",
|
|
6
|
-
"--no-auto-mark-ready",
|
|
7
|
-
"--no-auto-cancel-actionable",
|
|
8
|
-
]);
|
|
9
|
-
export function isDefaultIterateInvocation(subcommand) {
|
|
10
|
-
if (subcommand === "--help" || subcommand === "-h")
|
|
11
|
-
return false;
|
|
12
|
-
return (subcommand === undefined ||
|
|
13
|
-
parsePrNumber(subcommand) !== null ||
|
|
14
|
-
isDefaultIterateFlag(subcommand));
|
|
15
|
-
}
|
|
16
|
-
export function validateDefaultIterateArgs(args) {
|
|
17
|
-
let sawPr = false;
|
|
18
|
-
for (let i = 0; i < args.length; i += 1) {
|
|
19
|
-
const arg = args[i];
|
|
20
|
-
if (DEFAULT_ITERATE_FLAGS_WITH_VALUES.has(arg)) {
|
|
21
|
-
if (i + 1 >= args.length || args[i + 1].startsWith("--")) {
|
|
22
|
-
writeDefaultUsageError(arg);
|
|
23
|
-
return false;
|
|
24
|
-
}
|
|
25
|
-
i += 1;
|
|
26
|
-
continue;
|
|
27
|
-
}
|
|
28
|
-
const inlineFlag = arg.split("=", 1)[0];
|
|
29
|
-
if (DEFAULT_ITERATE_FLAGS_WITH_VALUES.has(inlineFlag))
|
|
30
|
-
continue;
|
|
31
|
-
if (DEFAULT_ITERATE_BOOLEAN_FLAGS.has(arg))
|
|
32
|
-
continue;
|
|
33
|
-
if (parsePrNumber(arg) !== null && !sawPr) {
|
|
34
|
-
sawPr = true;
|
|
35
|
-
continue;
|
|
36
|
-
}
|
|
37
|
-
writeDefaultUsageError(arg);
|
|
38
|
-
return false;
|
|
39
|
-
}
|
|
40
|
-
return true;
|
|
41
|
-
}
|
|
42
|
-
function isDefaultIterateFlag(arg) {
|
|
43
|
-
const name = arg.split("=", 1)[0];
|
|
44
|
-
return DEFAULT_ITERATE_FLAGS_WITH_VALUES.has(name) || DEFAULT_ITERATE_BOOLEAN_FLAGS.has(arg);
|
|
45
|
-
}
|
|
46
|
-
function writeDefaultUsageError(arg) {
|
|
47
|
-
process.stderr.write(`Unknown subcommand: ${arg}\n`);
|
|
48
|
-
process.stderr.write(`${USAGE.top}\n`);
|
|
49
|
-
process.exitCode = 1;
|
|
50
|
-
}
|