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.
Files changed (33) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +7 -7
  3. package/bin/checks/triage.test-support.mjs +1 -2
  4. package/bin/cli/default-poll.mjs +32 -0
  5. package/bin/cli/help-command-pages.mjs +3 -4
  6. package/bin/cli/help-top-page.mjs +5 -5
  7. package/bin/cli/iterate-lean.test-support.mjs +0 -2
  8. package/bin/cli/validate-default-args.mjs +32 -0
  9. package/bin/cli-parser.clean.test-support.mjs +0 -1
  10. package/bin/cli-parser.commit-suggestion.test-support.mjs +1 -2
  11. package/bin/cli-parser.iterate-fix.test-support.mjs +1 -2
  12. package/bin/cli-parser.iterate.test-support.mjs +1 -2
  13. package/bin/cli-parser.mjs +7 -7
  14. package/bin/cli-parser.test-support.mjs +1 -2
  15. package/bin/commands/check.mjs +2 -1
  16. package/bin/commands/check.test-support.mjs +1 -2
  17. package/bin/commands/clean.test-support.mjs +0 -1
  18. package/bin/commands/commit-suggestion.apply.test-support.mjs +1 -2
  19. package/bin/commands/commit-suggestion.test-support.mjs +1 -2
  20. package/bin/commands/iterate-stall.test-support.mjs +0 -1
  21. package/bin/commands/iterate.fix-code-in-progress.test-support.mjs +2 -2
  22. package/bin/commands/poll.mjs +12 -3
  23. package/bin/commands/resolve.test-support.mjs +2 -2
  24. package/bin/commands/shepherd-journal.test-support.mjs +0 -2
  25. package/bin/comments/resolve.test-support.mjs +1 -2
  26. package/bin/github/batch-parsers.test-support.mjs +1 -2
  27. package/bin/github/batch.test-support.mjs +1 -2
  28. package/bin/github/http.test-support.mjs +1 -2
  29. package/bin/suggestions/patch.test-support.mjs +0 -2
  30. package/package.json +1 -1
  31. package/plugins/pr-shepherd/.codex-plugin/plugin.json +1 -1
  32. package/plugins/pr-shepherd/skills/pr-shepherd/SKILL.md +8 -8
  33. package/bin/cli/default-iterate.mjs +0 -50
@@ -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.20.0",
4
+ "version": "0.21.0",
5
5
  "author": {
6
6
  "name": "Jonathan Ong",
7
7
  "email": "jonathanrichardong@gmail.com"
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 poll <PR>`, which provides actionable feedback directly to the agent:
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 poll 123
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 poll 123` again to continue until Shepherd returns `[CANCEL]` or `[ESCALATE]`.
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 poll` until Shepherd emits `[CANCEL]` for ready-delay completion or merged/closed, or `[ESCALATE]` (including `stall-timeout` for repeated unchanged CI failures).
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 poll` again until `[CANCEL]` or `[ESCALATE]`.
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 poll 42
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
- // @ts-nocheck
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. This is an alias for 'pr-shepherd [PR]'.
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 always print poll progress.
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 progress is written to stderr when stderr is a TTY or --verbose is set.
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] Run one iterate tick. This is the default command.
18
- iterate Alias for the default one-tick iterate command.
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 progress.
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,4 +1,3 @@
1
- // @ts-nocheck
2
1
  import { vi, beforeEach, afterEach } from "vitest";
3
2
  vi.mock("./commands/clean.mts", () => ({
4
3
  runClean: vi.fn(),
@@ -1,5 +1,4 @@
1
- // @ts-nocheck
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
- // @ts-nocheck
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
- // @ts-nocheck
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(),
@@ -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 { isDefaultIterateInvocation, validateDefaultIterateArgs } from "./cli/default-iterate.mjs";
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 (isDefaultIterateInvocation(subcommand)) {
53
- process.stdout.write(`${USAGE.iterate}\n`);
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 (isDefaultIterateInvocation(subcommand)) {
66
- if (!validateDefaultIterateArgs(args))
65
+ if (isDefaultPollInvocation(subcommand)) {
66
+ if (!validateDefaultPollArgs(args))
67
67
  return;
68
- await handleIterate(args);
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 { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
+ import { vi, beforeEach, afterEach } from "vitest";
4
3
  vi.mock("./commands/resolve.mts", () => ({
5
4
  runResolveFetch: vi.fn(),
6
5
  runResolveMutate: vi.fn(),
@@ -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
- autoResolved = outdated.filter((t) => resolvedIds.includes(t.id));
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
- // @ts-nocheck
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,4 +1,3 @@
1
- // @ts-nocheck
2
1
  import { vi, beforeEach, afterEach } from "vitest";
3
2
  import { join } from "node:path";
4
3
  import { mkdtemp, realpath, rm, mkdir, writeFile, stat } from "node:fs/promises";
@@ -1,5 +1,4 @@
1
- // @ts-nocheck
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
- // @ts-nocheck
2
- import { describe, it, expect, vi, beforeEach } from "vitest";
1
+ import { vi, beforeEach } from "vitest";
3
2
  // ---------------------------------------------------------------------------
4
3
  // Hoisted mocks
5
4
  // ---------------------------------------------------------------------------
@@ -1,4 +1,3 @@
1
- // @ts-nocheck
2
1
  import { vi } from "vitest";
3
2
  import { NOW, makeOpts } from "./iterate-test-support.mjs";
4
3
  import { readStallState, writeStallState } from "../state/iterate-stall.mjs";
@@ -1,5 +1,4 @@
1
- // @ts-nocheck
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",
@@ -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 (process.stderr.isTTY || verbose) {
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
- return lastResult;
27
+ break;
24
28
  const elapsedMs = Date.now() - start;
25
29
  const remainingMs = timeoutMs - elapsedMs;
26
30
  if (remainingMs <= 0)
27
- return lastResult;
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
- // @ts-nocheck
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
- // @ts-nocheck
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
- // @ts-nocheck
2
- import { describe, it, expect, vi, beforeEach } from "vitest";
1
+ import { vi, beforeEach } from "vitest";
3
2
  vi.mock("./client.mts", () => ({
4
3
  graphql: vi.fn(),
5
4
  graphqlWithRateLimit: vi.fn(),
@@ -1,5 +1,4 @@
1
- // @ts-nocheck
2
- import { describe, it, expect, vi, beforeEach } from "vitest";
1
+ import { vi, beforeEach } from "vitest";
3
2
  vi.mock("./client.mts", () => ({
4
3
  graphql: vi.fn(),
5
4
  graphqlWithRateLimit: vi.fn(),
@@ -1,5 +1,4 @@
1
- // @ts-nocheck
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
  // ---------------------------------------------------------------------------
@@ -1,4 +1,2 @@
1
- // @ts-nocheck
2
- import { describe, it, expect } from "vitest";
3
1
  import { buildUnifiedDiff } from "./patch.mjs";
4
2
  export { buildUnifiedDiff };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pr-shepherd",
3
- "version": "0.20.0",
3
+ "version": "0.21.0",
4
4
  "description": "Autonomous PR CI monitor and review-comment resolver for agentic coding tools",
5
5
  "license": "MIT",
6
6
  "author": "Jonathan Ong",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pr-shepherd",
3
- "version": "0.20.0",
3
+ "version": "0.21.0",
4
4
  "description": "Autonomous PR CI monitor and review-comment resolver for Codex.",
5
5
  "author": {
6
6
  "name": "Jonathan Ong",
@@ -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 poll`:**
23
+ 2. **Run `pr-shepherd`:**
24
24
 
25
25
  ```bash
26
- pr-shepherd poll <N>
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 poll --help`.
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. When those instructions tell you to stop and recheck with `pr-shepherd <N>` after a delay, use `pr-shepherd poll <N>` as the next invocation instead; do not also run the one-shot command.
31
+ Print the full output. Follow the `## Instructions` section exactly for the current action.
32
32
 
33
- 3. **Persistence:** Continuously call `pr-shepherd poll <N>` 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 poll <N>` again.
35
- - `[MARK_READY]`: call `pr-shepherd poll <N>` again.
36
- - `[FIX_CODE]`: follow the output's `## Instructions`, then call `pr-shepherd poll <N>` again.
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
- }