pr-shepherd 0.60.1 → 1.0.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 (197) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +7 -5
  3. package/bin/api.d.mts +35 -24
  4. package/bin/api.mjs +68 -48
  5. package/bin/checks/classify.d.mts +1 -1
  6. package/bin/checks/classify.mjs +4 -4
  7. package/bin/checks/log-excerpt.mjs +1 -1
  8. package/bin/cli/arg-flags.mjs +0 -3
  9. package/bin/cli/clean-handler.d.mts +1 -1
  10. package/bin/cli/clean-handler.mjs +5 -15
  11. package/bin/cli/default-poll.d.mts +2 -0
  12. package/bin/cli/default-poll.mjs +9 -6
  13. package/bin/cli/formatters.d.mts +2 -3
  14. package/bin/cli/formatters.mjs +0 -26
  15. package/bin/cli/handlers.d.mts +3 -3
  16. package/bin/cli/handlers.mjs +45 -35
  17. package/bin/cli/help-command-pages.d.mts +13 -158
  18. package/bin/cli/help-command-pages.mjs +12 -157
  19. package/bin/cli/help-iterate-poll-pages.d.mts +4 -3
  20. package/bin/cli/help-iterate-poll-pages.mjs +76 -36
  21. package/bin/cli/help-log-file-page.d.mts +1 -1
  22. package/bin/cli/help-log-file-page.mjs +3 -5
  23. package/bin/cli/help-top-page.d.mts +1 -1
  24. package/bin/cli/help-top-page.mjs +19 -20
  25. package/bin/cli/help.d.mts +14 -159
  26. package/bin/cli/iterate-checks-formatter.mjs +1 -1
  27. package/bin/cli/iterate-flags.d.mts +2 -4
  28. package/bin/cli/iterate-flags.mjs +4 -20
  29. package/bin/cli/iterate-formatter.d.mts +1 -1
  30. package/bin/cli/iterate-formatter.mjs +6 -6
  31. package/bin/cli/iterate-instructions.d.mts +1 -1
  32. package/bin/cli/iterate-instructions.mjs +5 -6
  33. package/bin/cli/iterate-lean.mjs +32 -40
  34. package/bin/cli/iterate-verbose.mjs +1 -1
  35. package/bin/cli/journal-formatter.mjs +0 -3
  36. package/bin/cli/journal-handler.d.mts +1 -1
  37. package/bin/cli/journal-handler.mjs +2 -1
  38. package/bin/cli/mutate-formatter.mjs +0 -9
  39. package/bin/cli/next-check-format.d.mts +0 -4
  40. package/bin/cli/next-check-format.mjs +0 -4
  41. package/bin/cli/poll-handler.d.mts +3 -0
  42. package/bin/cli/poll-handler.mjs +88 -47
  43. package/bin/cli/poll-summary-emitter.mjs +2 -2
  44. package/bin/cli/poll-summary-formatter.mjs +2 -2
  45. package/bin/cli/poll-targets.d.mts +2 -2
  46. package/bin/cli/poll-targets.mjs +15 -35
  47. package/bin/cli/removed-flags.d.mts +3 -0
  48. package/bin/cli/removed-flags.mjs +24 -0
  49. package/bin/cli/runner.d.mts +1 -1
  50. package/bin/cli/runner.mjs +2 -2
  51. package/bin/cli/stack-overview.d.mts +0 -1
  52. package/bin/cli/stack-overview.mjs +1 -2
  53. package/bin/cli/{fix-formatter-extra.d.mts → triage-formatter-extra.d.mts} +1 -2
  54. package/bin/cli/{fix-formatter-extra.mjs → triage-formatter-extra.mjs} +0 -6
  55. package/bin/cli/triage-formatter.d.mts +4 -0
  56. package/bin/cli/{fix-formatter.mjs → triage-formatter.mjs} +31 -42
  57. package/bin/cli/unknown-flags.d.mts +6 -0
  58. package/bin/cli/unknown-flags.mjs +29 -0
  59. package/bin/cli-parser.mjs +41 -62
  60. package/bin/commands/check-annotations.d.mts +1 -1
  61. package/bin/commands/check-annotations.mjs +1 -1
  62. package/bin/commands/check-review-budget.d.mts +1 -1
  63. package/bin/commands/check-review-budget.mjs +1 -1
  64. package/bin/commands/check-wait-detectors.d.mts +1 -1
  65. package/bin/commands/check-wait-detectors.mjs +1 -1
  66. package/bin/commands/check.d.mts +1 -1
  67. package/bin/commands/check.mjs +7 -7
  68. package/bin/commands/commit-suggestion-instruction.d.mts +2 -2
  69. package/bin/commands/commit-suggestion-instruction.mjs +3 -3
  70. package/bin/commands/iterate/api-usage.mjs +8 -8
  71. package/bin/commands/iterate/check-instructions.d.mts +4 -4
  72. package/bin/commands/iterate/check-instructions.mjs +8 -8
  73. package/bin/commands/iterate/classify.mjs +2 -2
  74. package/bin/commands/iterate/escalate.mjs +6 -6
  75. package/bin/commands/iterate/index.mjs +20 -23
  76. package/bin/commands/iterate/merge-plan.mjs +3 -3
  77. package/bin/commands/iterate/merge-state.mjs +4 -7
  78. package/bin/commands/iterate/native-stack-rebase.mjs +1 -1
  79. package/bin/commands/iterate/render.d.mts +1 -1
  80. package/bin/commands/iterate/render.mjs +2 -2
  81. package/bin/commands/iterate/stall.mjs +11 -9
  82. package/bin/commands/iterate/{fix-loop-instruction.mjs → triage-loop-instruction.mjs} +7 -3
  83. package/bin/commands/iterate/{fix-code.d.mts → triage.d.mts} +2 -2
  84. package/bin/commands/iterate/{fix-code.mjs → triage.mjs} +14 -23
  85. package/bin/commands/iterate/unreported-required.mjs +2 -5
  86. package/bin/commands/journal/index.d.mts +1 -3
  87. package/bin/commands/journal/index.mjs +1 -1
  88. package/bin/commands/mark-files-as-viewed.d.mts +1 -3
  89. package/bin/commands/mark-files-as-viewed.mjs +1 -1
  90. package/bin/commands/next-check.d.mts +2 -2
  91. package/bin/commands/next-check.mjs +4 -2
  92. package/bin/commands/poll-progress.mjs +1 -1
  93. package/bin/commands/poll-summary-explicit-instructions.mjs +1 -1
  94. package/bin/commands/poll-summary-instructions.mjs +7 -7
  95. package/bin/commands/poll-summary.mjs +4 -4
  96. package/bin/commands/poll.d.mts +1 -1
  97. package/bin/commands/poll.mjs +4 -4
  98. package/bin/commands/resolve-mutate.d.mts +1 -1
  99. package/bin/commands/resolve-mutate.mjs +1 -1
  100. package/bin/commands/rest-stack-merge-readiness.mjs +1 -1
  101. package/bin/commands/single-tick.d.mts +5 -0
  102. package/bin/commands/single-tick.mjs +7 -0
  103. package/bin/commands/stack-drain.mjs +1 -1
  104. package/bin/commands/stack-merge-flag.mjs +1 -1
  105. package/bin/commands/stack-stall.d.mts +2 -2
  106. package/bin/commands/stack-stall.mjs +2 -2
  107. package/bin/commands/stack-work.d.mts +2 -2
  108. package/bin/commands/stack-work.mjs +2 -2
  109. package/bin/commands/suggestion-patch-item.d.mts +0 -1
  110. package/bin/commands/suggestion-patch-item.mjs +0 -12
  111. package/bin/commands/suggestion-patches.d.mts +2 -2
  112. package/bin/commands/suggestion-patches.mjs +1 -1
  113. package/bin/commands/tick-instructions.d.mts +9 -0
  114. package/bin/commands/tick-instructions.mjs +23 -0
  115. package/bin/commands/tick.d.mts +15 -0
  116. package/bin/commands/{event-mode.mjs → tick.mjs} +39 -55
  117. package/bin/comments/minimize-policy.mjs +1 -3
  118. package/bin/comments/resolve.d.mts +0 -23
  119. package/bin/comments/resolve.mjs +0 -4
  120. package/bin/comments/review-output-budget.d.mts +1 -1
  121. package/bin/comments/review-output-budget.mjs +1 -1
  122. package/bin/comments/sha-poll.mjs +3 -1
  123. package/bin/config/default-bot-usernames.d.mts +2 -0
  124. package/bin/config/default-bot-usernames.mjs +15 -0
  125. package/bin/config/load.d.mts +4 -138
  126. package/bin/config/load.mjs +88 -443
  127. package/bin/config/merge-command-args.mjs +2 -2
  128. package/bin/config/merge-method.d.mts +1 -2
  129. package/bin/config/merge-method.mjs +1 -8
  130. package/bin/config/renames.d.mts +8 -0
  131. package/bin/config/renames.mjs +87 -0
  132. package/bin/config/resolve.d.mts +31 -0
  133. package/bin/config/resolve.mjs +89 -0
  134. package/bin/config/schema.d.mts +161 -0
  135. package/bin/config/schema.mjs +168 -0
  136. package/bin/exit-codes.d.mts +1 -1
  137. package/bin/exit-codes.mjs +3 -3
  138. package/bin/github/poll-summary-check-blockers.mjs +1 -1
  139. package/bin/github/poll-summary-command.mjs +8 -6
  140. package/bin/github/poll-summary-projector.mjs +2 -2
  141. package/bin/github/poll-summary-review.mjs +1 -1
  142. package/bin/github/poll-summary-route.mjs +9 -9
  143. package/bin/github/poll-summary-unreported.mjs +1 -1
  144. package/bin/github/poll-summary.mjs +1 -1
  145. package/bin/github/queries.d.mts +1 -1
  146. package/bin/github/queries.mjs +1 -1
  147. package/bin/mcp/server.mjs +48 -54
  148. package/bin/merge-status/derive.mjs +1 -1
  149. package/bin/state/durable-state.d.mts +1 -3
  150. package/bin/state/durable-state.mjs +2 -8
  151. package/bin/state/fix-attempts.d.mts +2 -2
  152. package/bin/state/fix-attempts.mjs +1 -1
  153. package/bin/state/merge-request.mjs +1 -1
  154. package/bin/state/pr-fingerprint.mjs +4 -11
  155. package/bin/state/seen-comments.d.mts +4 -5
  156. package/bin/state/seen-comments.mjs +8 -14
  157. package/bin/state/stack-stall.d.mts +1 -1
  158. package/bin/state/stack-stall.mjs +1 -1
  159. package/bin/state/stall-state-store.d.mts +1 -1
  160. package/bin/state/stall-state-store.mjs +1 -1
  161. package/bin/types/escalate.d.mts +2 -2
  162. package/bin/types/iterate.d.mts +12 -26
  163. package/bin/types/merge-queue.d.mts +2 -2
  164. package/bin/types/next-check.d.mts +1 -1
  165. package/bin/types/poll-summary.d.mts +3 -6
  166. package/bin/types/report.d.mts +2 -2
  167. package/bin/types/suggestion-patch.d.mts +1 -8
  168. package/package.json +2 -1
  169. package/plugins/pr-shepherd/.claude-plugin/plugin.json +1 -1
  170. package/plugins/pr-shepherd/.codex-plugin/plugin.json +1 -1
  171. package/plugins/pr-shepherd/.codex.mcp.json +1 -1
  172. package/plugins/pr-shepherd/.mcp.json +1 -1
  173. package/plugins/pr-shepherd/skills/pr-shepherd/SKILL.md +6 -6
  174. package/plugins/pr-shepherd/skills/pr-shepherd/references/ci-failure-triage.md +1 -1
  175. package/plugins/pr-shepherd/skills/pr-shepherd/references/{cloud-event-loop.md → event-loop.md} +5 -5
  176. package/plugins/pr-shepherd/skills/pr-shepherd/references/mcp-fallback.md +3 -3
  177. package/plugins/pr-shepherd/skills/pr-shepherd/references/merge-queue-ejection.md +1 -1
  178. package/plugins/pr-shepherd/skills/pr-shepherd/references/suggestion-patches.md +1 -1
  179. package/plugins/pr-shepherd/skills/pr-shepherd/references/{fix-code-loop.md → triage-loop.md} +3 -3
  180. package/plugins/pr-shepherd/skills/reduce-pr-noise/SKILL.md +1 -1
  181. package/plugins/pr-shepherd/skills/reduce-pr-noise/references/classifiers.md +2 -2
  182. package/plugins/pr-shepherd/skills/reduce-pr-noise/references/settings.md +11 -11
  183. package/pr-shepherd.schema.json +361 -0
  184. package/bin/cli/event-state-scope.d.mts +0 -19
  185. package/bin/cli/event-state-scope.mjs +0 -60
  186. package/bin/cli/fix-formatter.d.mts +0 -4
  187. package/bin/commands/commit-suggestion.d.mts +0 -8
  188. package/bin/commands/commit-suggestion.mjs +0 -26
  189. package/bin/commands/event-instructions.d.mts +0 -11
  190. package/bin/commands/event-instructions.mjs +0 -27
  191. package/bin/commands/event-mode.d.mts +0 -14
  192. package/bin/commands/poll-mode.d.mts +0 -8
  193. package/bin/commands/poll-mode.mjs +0 -14
  194. package/bin/config.json +0 -68
  195. package/bin/types/protected-run.d.mts +0 -6
  196. package/bin/types/protected-run.mjs +0 -1
  197. /package/bin/commands/iterate/{fix-loop-instruction.d.mts → triage-loop-instruction.d.mts} +0 -0
@@ -1,16 +1,14 @@
1
- import { type loadConfig, type InstructionStyle, type PollMode } from "../config/load.mts";
1
+ import type { loadConfig } from "../config/load.mts";
2
+ import { type InstructionStyle } from "../config/resolve.mts";
2
3
  interface IterateFlags {
3
4
  readyDelaySuffix: string | undefined | null;
4
5
  readyDelaySeconds: number;
5
6
  stallTimeoutSuffix: string | undefined | null;
6
7
  stallTimeoutSeconds: number;
7
8
  noAutoMarkReady: boolean;
8
- noAutoCancelActionable: boolean;
9
9
  merge: boolean;
10
10
  /** `null` when `--instructions` was invalid (an error was already printed). */
11
11
  instructions: InstructionStyle | null | undefined;
12
- /** `null` when `--poll-mode` was invalid (an error was already printed). */
13
- pollMode: PollMode | null | undefined;
14
12
  }
15
13
  /** True when any flag failed validation; the usage error is already printed. */
16
14
  export declare function hasInvalidIterateFlags(flags: IterateFlags): boolean;
@@ -1,4 +1,4 @@
1
- import { INSTRUCTION_STYLES, POLL_MODES, isInstructionStyle, isPollMode, } from "../config/load.mjs";
1
+ import { INSTRUCTION_STYLES, isInstructionStyle, } from "../config/resolve.mjs";
2
2
  import { EXIT } from "../exit-codes.mjs";
3
3
  import { getFlag, hasFlag } from "./args.mjs";
4
4
  import { parseDurationToSeconds } from "./duration.mjs";
@@ -10,29 +10,25 @@ const MINUTE_FLAG_OPTS = { defaultUnit: "m", allowZero: true };
10
10
  export function hasInvalidIterateFlags(flags) {
11
11
  return (flags.readyDelaySuffix === null ||
12
12
  flags.stallTimeoutSuffix === null ||
13
- flags.instructions === null ||
14
- flags.pollMode === null);
13
+ flags.instructions === null);
15
14
  }
16
15
  export function parseIterateFlags(extra, cfg) {
17
16
  const readyDelayStr = getFlag(extra, "--ready-delay");
18
17
  const readyDelaySuffix = validateSecondsDurationFlag("pr-shepherd", "--ready-delay", readyDelayStr, hasFlag(extra, "--ready-delay"), MINUTE_FLAG_OPTS);
19
- const readyDelaySeconds = parseDurationToSeconds(readyDelaySuffix ?? "", cfg.watch.readyDelayMinutes * 60, MINUTE_FLAG_OPTS);
18
+ const readyDelaySeconds = parseDurationToSeconds(readyDelaySuffix ?? "", cfg.iterate["ready-delay-minutes"] * 60, MINUTE_FLAG_OPTS);
20
19
  const noAutoMarkReady = hasFlag(extra, "--no-auto-mark-ready");
21
- const noAutoCancelActionable = hasFlag(extra, "--no-auto-cancel-actionable");
22
20
  const merge = hasFlag(extra, "--merge");
23
21
  const stallTimeoutStr = getFlag(extra, "--stall-timeout");
24
22
  const stallTimeoutSuffix = validateSecondsDurationFlag("pr-shepherd", "--stall-timeout", stallTimeoutStr, hasFlag(extra, "--stall-timeout"), MINUTE_FLAG_OPTS);
25
- const stallTimeoutSeconds = parseDurationToSeconds(stallTimeoutSuffix ?? "", cfg.iterate.stallTimeoutMinutes * 60, MINUTE_FLAG_OPTS);
23
+ const stallTimeoutSeconds = parseDurationToSeconds(stallTimeoutSuffix ?? "", cfg.iterate["stall-timeout-minutes"] * 60, MINUTE_FLAG_OPTS);
26
24
  const instructions = parseInstructionsFlag(extra);
27
25
  return {
28
26
  instructions,
29
- pollMode: parsePollModeFlag(extra),
30
27
  readyDelaySuffix,
31
28
  readyDelaySeconds,
32
29
  stallTimeoutSuffix,
33
30
  stallTimeoutSeconds,
34
31
  noAutoMarkReady,
35
- noAutoCancelActionable,
36
32
  merge,
37
33
  };
38
34
  }
@@ -50,15 +46,3 @@ function parseInstructionsFlag(extra) {
50
46
  function hasInstructionsFlag(extra) {
51
47
  return extra.some((arg) => arg === "--instructions" || arg.startsWith("--instructions="));
52
48
  }
53
- /** Validate `--poll-mode`. Returns `undefined` when absent, `null` after printing a usage error. */
54
- function parsePollModeFlag(extra) {
55
- const value = getFlag(extra, "--poll-mode");
56
- if (!extra.some((arg) => arg === "--poll-mode" || arg.startsWith("--poll-mode="))) {
57
- return undefined;
58
- }
59
- if (isPollMode(value))
60
- return value;
61
- process.stderr.write(`pr-shepherd: --poll-mode must be one of ${POLL_MODES.join(", ")}, got ${JSON.stringify(value ?? "")}\n`);
62
- process.exitCode = EXIT.USAGE;
63
- return null;
64
- }
@@ -6,7 +6,7 @@ import type { IterateResult } from "../types.mts";
6
6
  * 1. The H1 heading on line 1 contains `[<ACTION>]` — the action tag identifies
7
7
  * the output for logging and validation. Behavior is driven by `## Instructions`,
8
8
  * not by dispatching on the tag.
9
- * 2. `[FIX_CODE]` prints the `apply review` command inline, in backticks, in its
9
+ * 2. `[TRIAGE]` prints the `apply review` command inline, in backticks, in its
10
10
  * `## Instructions` step; the agent runs that backticked command as printed.
11
11
  * 3. Every action except `cancel` ends with a `## Instructions` section — numbered
12
12
  * `1.`, `2.`, … — that tells the agent exactly what to do with this output, with at
@@ -1,6 +1,6 @@
1
1
  /* eslint-disable max-lines */
2
2
  import { insertEditedCommentsSection } from "./edited-comments-format.mjs";
3
- import { formatFixCodeResult } from "./fix-formatter.mjs";
3
+ import { formatTriageResult } from "./triage-formatter.mjs";
4
4
  import { formatRelevantChecks } from "./iterate-checks-formatter.mjs";
5
5
  import { joinSections } from "../util/markdown.mjs";
6
6
  import { adaptIterateLog, buildSimpleIterateInstructions, numberInstructions, } from "./iterate-instructions.mjs";
@@ -12,7 +12,7 @@ import { formatSuppressedNameLines } from "./iterate-check-names.mjs";
12
12
  import { branchStateSegment } from "./iterate-branch-segment.mjs";
13
13
  import { insertRuleAutoResolveSection } from "../commands/rule-auto-resolve-format.mjs";
14
14
  import { formatTransportEvidence } from "./transport-formatter.mjs";
15
- import { formatNextCheckLines, formatPollModeSegment } from "./next-check-format.mjs";
15
+ import { formatNextCheckLines } from "./next-check-format.mjs";
16
16
  import { formatPrUrl } from "../pr-reference.mjs";
17
17
  /**
18
18
  * Format an IterateResult as human-readable Markdown.
@@ -21,7 +21,7 @@ import { formatPrUrl } from "../pr-reference.mjs";
21
21
  * 1. The H1 heading on line 1 contains `[<ACTION>]` — the action tag identifies
22
22
  * the output for logging and validation. Behavior is driven by `## Instructions`,
23
23
  * not by dispatching on the tag.
24
- * 2. `[FIX_CODE]` prints the `apply review` command inline, in backticks, in its
24
+ * 2. `[TRIAGE]` prints the `apply review` command inline, in backticks, in its
25
25
  * `## Instructions` step; the agent runs that backticked command as printed.
26
26
  * 3. Every action except `cancel` ends with a `## Instructions` section — numbered
27
27
  * `1.`, `2.`, … — that tells the agent exactly what to do with this output, with at
@@ -55,7 +55,7 @@ export function formatIterateResult(result, opts) {
55
55
  ? ` · **merge** \`${result.mergeStateStatus}\``
56
56
  : "";
57
57
  const stateSeg = verbose || result.state !== "OPEN" ? ` · **state** \`${result.state}\`` : "";
58
- const baseLine = `**status** \`${result.status}\`${mergeSeg}${reviewDecisionSeg}${stateSeg}${repoSeg}${baseBranchSeg}${formatPollModeSegment(result)}`;
58
+ const baseLine = `**status** \`${result.status}\`${mergeSeg}${reviewDecisionSeg}${stateSeg}${repoSeg}${baseBranchSeg}`;
59
59
  const branchSeg = branchStateSegment(result);
60
60
  let summaryLine;
61
61
  if (verbose) {
@@ -199,8 +199,8 @@ export function formatIterateResult(result, opts) {
199
199
  result.escalate.humanMessage,
200
200
  `## Instructions\n\n${numberInstructions(buildSimpleIterateInstructions(result))}`,
201
201
  ]));
202
- case "fix_code":
203
- return finish(formatFixCodeResult(joinSections([header, ...telemetrySections]), result, { verbose }));
202
+ case "triage":
203
+ return finish(formatTriageResult(joinSections([header, ...telemetrySections]), result, { verbose }));
204
204
  }
205
205
  }
206
206
  function appendUnreportedLines(lines, result) {
@@ -1,6 +1,6 @@
1
1
  import type { IterateResult } from "../types.mts";
2
2
  export declare function buildSimpleIterateInstructions(result: Exclude<IterateResult, {
3
- action: "fix_code";
3
+ action: "triage";
4
4
  }>): string[];
5
5
  export declare function adaptIterateLog(log: string): string;
6
6
  export declare function numberInstructions(instructions: string[]): string;
@@ -1,14 +1,14 @@
1
1
  import { inlineCode } from "../util/markdown.mjs";
2
2
  import { buildQuotaAwareContinuation } from "../quota-warning.mjs";
3
3
  import { renderShellCommand } from "./runner.mjs";
4
- import { eventWaitSteps } from "../commands/event-instructions.mjs";
4
+ import { tickWaitSteps } from "../commands/tick-instructions.mjs";
5
5
  export function buildSimpleIterateInstructions(result) {
6
6
  switch (result.action) {
7
7
  case "ready": {
8
8
  if (result.nextCheck) {
9
9
  return [
10
10
  `PR #${result.pr} is ready. Ready-delay has ${result.remainingSeconds}s left. Do not invent unrelated work.`,
11
- ...eventWaitSteps(result.nextCheck),
11
+ ...tickWaitSteps(result.nextCheck),
12
12
  ];
13
13
  }
14
14
  const sentence = `PR #${result.pr} is ready. Ready-delay has ${result.remainingSeconds}s left. Rerun this command when the timer elapses. Do not invent unrelated work.`;
@@ -18,7 +18,7 @@ export function buildSimpleIterateInstructions(result) {
18
18
  }
19
19
  case "wait":
20
20
  if (result.nextCheck) {
21
- return ["Non-terminal — no action needed this tick.", ...eventWaitSteps(result.nextCheck)];
21
+ return ["Non-terminal — no action needed this tick.", ...tickWaitSteps(result.nextCheck)];
22
22
  }
23
23
  if (result.quotaWarning) {
24
24
  return [
@@ -29,8 +29,7 @@ export function buildSimpleIterateInstructions(result) {
29
29
  "Non-terminal — no action needed this tick. Iterate immediately with the same options to continue.",
30
30
  ];
31
31
  case "mark_ready":
32
- // Event mode reruns at once too: Shepherd is not polling, so no cadence applies.
33
- if (result.quotaWarning && result.pollMode !== "event") {
32
+ if (result.quotaWarning) {
34
33
  return [
35
34
  buildQuotaAwareContinuation(result.quotaWarning, "The CLI marked the PR ready for review."),
36
35
  ];
@@ -44,7 +43,7 @@ export function buildSimpleIterateInstructions(result) {
44
43
  if (result.merge.mode === "rest" && result.nextCheck)
45
44
  return [
46
45
  `Run the REST merge command exactly as printed: ${command}. \`enqueued\` is not merged.`,
47
- ...eventWaitSteps(result.nextCheck),
46
+ ...tickWaitSteps(result.nextCheck),
48
47
  ];
49
48
  if (result.merge.mode === "rest")
50
49
  return [
@@ -95,7 +95,6 @@ export function projectIterateLean(result, opts) {
95
95
  ...(result.baseBehindBy !== undefined &&
96
96
  result.baseBehindBy > 0 && { baseBehindBy: result.baseBehindBy }),
97
97
  ...(result.quotaWarning && { quotaWarning: result.quotaWarning }),
98
- ...(result.pollMode && { pollMode: result.pollMode }),
99
98
  ...(result.nextCheck && { nextCheck: result.nextCheck }),
100
99
  ...(result.ruleAutoResolve && {
101
100
  ruleAutoResolve: projectRuleAutoResolve(result.ruleAutoResolve),
@@ -140,58 +139,51 @@ export function projectIterateLean(result, opts) {
140
139
  merge: result.merge,
141
140
  instructions: simpleInstructions(result),
142
141
  };
143
- case "fix_code":
142
+ case "triage":
144
143
  return {
145
144
  ...base,
146
- // Passing checks stay summary counts and failures stay in `fix.checks`, as in lean text.
147
- ...(result.cancelled.length > 0 && { cancelled: result.cancelled }),
148
- fix: {
149
- ...(result.fix.threads.length > 0 && { threads: result.fix.threads }),
150
- ...(result.fix.resolutionOnlyThreads.length > 0 && {
151
- resolutionOnlyThreads: result.fix.resolutionOnlyThreads,
145
+ // Passing checks stay summary counts and failures stay in `triage.checks`, as in lean text.
146
+ triage: {
147
+ ...(result.triage.threads.length > 0 && { threads: result.triage.threads }),
148
+ ...(result.triage.resolutionOnlyThreads.length > 0 && {
149
+ resolutionOnlyThreads: result.triage.resolutionOnlyThreads,
152
150
  }),
153
- ...(result.fix.actionableComments.length > 0 && {
154
- actionableComments: result.fix.actionableComments,
151
+ ...(result.triage.actionableComments.length > 0 && {
152
+ actionableComments: result.triage.actionableComments,
155
153
  }),
156
- ...(result.fix.reviewSummaryIds.length > 0 && {
157
- reviewSummaryIds: result.fix.reviewSummaryIds,
154
+ ...(result.triage.reviewSummaryIds.length > 0 && {
155
+ reviewSummaryIds: result.triage.reviewSummaryIds,
158
156
  }),
159
- ...(result.fix.firstLookSummaries.length > 0 && {
160
- firstLookSummaries: result.fix.firstLookSummaries,
157
+ ...(result.triage.firstLookSummaries.length > 0 && {
158
+ firstLookSummaries: result.triage.firstLookSummaries,
161
159
  }),
162
- ...(result.fix.editedSummaries.length > 0 && {
163
- editedSummaries: result.fix.editedSummaries,
160
+ ...(result.triage.editedSummaries.length > 0 && {
161
+ editedSummaries: result.triage.editedSummaries,
164
162
  }),
165
- ...(result.fix.surfacedApprovals.length > 0 && {
166
- surfacedApprovals: result.fix.surfacedApprovals,
163
+ ...(result.triage.surfacedApprovals.length > 0 && {
164
+ surfacedApprovals: result.triage.surfacedApprovals,
167
165
  }),
168
- ...(result.fix.firstLookThreads.length > 0 && {
169
- firstLookThreads: result.fix.firstLookThreads,
166
+ ...(result.triage.firstLookThreads.length > 0 && {
167
+ firstLookThreads: result.triage.firstLookThreads,
170
168
  }),
171
- ...(result.fix.firstLookComments.length > 0 && {
172
- firstLookComments: result.fix.firstLookComments,
169
+ ...(result.triage.firstLookComments.length > 0 && {
170
+ firstLookComments: result.triage.firstLookComments,
173
171
  }),
174
- ...(result.fix.inProgressRunIds.length > 0 && {
175
- inProgressRunIds: result.fix.inProgressRunIds,
172
+ ...(result.triage.requeue && { requeue: result.triage.requeue }),
173
+ ...(result.triage.queueRemovalAcknowledgment && {
174
+ queueRemovalAcknowledgment: result.triage.queueRemovalAcknowledgment,
176
175
  }),
177
- ...(result.fix.protectedRuns.length > 0 && {
178
- protectedRuns: result.fix.protectedRuns,
176
+ ...(result.triage.checks.length > 0 && { checks: result.triage.checks }),
177
+ ...(result.triage.changesRequestedReviews.length > 0 && {
178
+ changesRequestedReviews: result.triage.changesRequestedReviews,
179
179
  }),
180
- ...(result.fix.requeue && { requeue: result.fix.requeue }),
181
- ...(result.fix.queueRemovalAcknowledgment && {
182
- queueRemovalAcknowledgment: result.fix.queueRemovalAcknowledgment,
180
+ resolveCommand: result.triage.resolveCommand,
181
+ ...(result.triage.resolveOnlyCommand && {
182
+ resolveOnlyCommand: result.triage.resolveOnlyCommand,
183
183
  }),
184
- ...(result.fix.checks.length > 0 && { checks: result.fix.checks }),
185
- ...(result.fix.changesRequestedReviews.length > 0 && {
186
- changesRequestedReviews: result.fix.changesRequestedReviews,
187
- }),
188
- resolveCommand: result.fix.resolveCommand,
189
- ...(result.fix.resolveOnlyCommand && {
190
- resolveOnlyCommand: result.fix.resolveOnlyCommand,
191
- }),
192
- ...(result.fix.reviewItems && { reviewItems: result.fix.reviewItems }),
193
- ...(result.fix.instructions.length > 0 && {
194
- instructions: result.fix.instructions,
184
+ ...(result.triage.reviewItems && { reviewItems: result.triage.reviewItems }),
185
+ ...(result.triage.instructions.length > 0 && {
186
+ instructions: result.triage.instructions,
195
187
  }),
196
188
  },
197
189
  };
@@ -7,7 +7,7 @@ export function projectIterateVerbose(result, opts) {
7
7
  const edited = result.editedComments && {
8
8
  editedComments: projectEditedComments(result.editedComments),
9
9
  };
10
- if (result.action === "fix_code")
10
+ if (result.action === "triage")
11
11
  return { ...result, ...readyDelayOverride, ...edited };
12
12
  const log = "log" in result && typeof result.log === "string" ? { log: adaptIterateLog(result.log) } : {};
13
13
  // `cancel` has no instructions, so verbose JSON omits the field as verbose Markdown omits the section.
@@ -1,7 +1,4 @@
1
1
  export function formatJournalResult(result) {
2
- if (result.authorizationSkipped) {
3
- return `Authorization denied or unverifiable — Shepherd Journal in PR #${result.prNumber} was not updated.`;
4
- }
5
2
  if (result.dryRun) {
6
3
  const lines = ["Dry run — no body change written."];
7
4
  if (result.previewBody !== undefined) {
@@ -1 +1 @@
1
- export declare function handleJournal(args: string[], command?: "apply journal" | "journal"): Promise<void>;
1
+ export declare function handleJournal(args: string[]): Promise<void>;
@@ -5,7 +5,8 @@ import { getFlag } from "./args.mjs";
5
5
  import { parseCliPrReference, resolveParsedPrTarget } from "../pr-reference.mjs";
6
6
  import { USAGE } from "./help.mjs";
7
7
  import { formatJournalResult } from "./journal-formatter.mjs";
8
- export async function handleJournal(args, command = "apply journal") {
8
+ export async function handleJournal(args) {
9
+ const command = "apply journal";
9
10
  for (const a of args) {
10
11
  if (!a.startsWith("--"))
11
12
  continue;
@@ -33,15 +33,6 @@ export function formatMutateResult(result) {
33
33
  pushIds(lines, "Resolved threads", result.resolvedThreads);
34
34
  pushIds(lines, "Minimized comments", result.minimizedComments);
35
35
  pushIds(lines, "Dismissed reviews", result.dismissedReviews);
36
- pushIds(lines, "Skipped dismissals", result.skippedDismissals);
37
- pushIds(lines, "Skipped human thread resolves", result.skippedHumanResolves);
38
- pushIds(lines, "Skipped human minimizes", result.skippedHumanMinimizes);
39
- pushIds(lines, "Skipped human review dismissals", result.skippedHumanDismissals);
40
- pushIds(lines, "Skipped non-human/unknown thread replies", result.skippedNonHumanReplies);
41
- pushIds(lines, "Authorization denied/unverifiable for thread replies", result.skippedUnauthorizedReplies);
42
- pushIds(lines, "Authorization denied/unverifiable for thread resolves", result.skippedUnauthorizedResolves);
43
- pushIds(lines, "Authorization denied/unverifiable for minimizes", result.skippedUnauthorizedMinimizes);
44
- pushIds(lines, "Authorization denied/unverifiable for review dismissals", result.skippedUnauthorizedDismissals);
45
36
  const rateLimit = formatRateLimit(result);
46
37
  if (rateLimit)
47
38
  lines.push(rateLimit);
@@ -1,7 +1,3 @@
1
1
  import type { NextCheck } from "../types/next-check.mts";
2
- /** ` · **pollMode** \`event\``, or nothing in poll mode. */
3
- export declare function formatPollModeSegment(result: {
4
- pollMode?: "event";
5
- }): string;
6
2
  /** The header line naming the next wake-up, or nothing when none applies. */
7
3
  export declare function formatNextCheckLines(next: NextCheck | undefined): string[];
@@ -1,7 +1,3 @@
1
- /** ` · **pollMode** \`event\``, or nothing in poll mode. */
2
- export function formatPollModeSegment(result) {
3
- return result.pollMode ? ` · **pollMode** \`${result.pollMode}\`` : "";
4
- }
5
1
  /** The header line naming the next wake-up, or nothing when none applies. */
6
2
  export function formatNextCheckLines(next) {
7
3
  if (!next)
@@ -1 +1,4 @@
1
+ /** The default command: one PR, or several PRs from one repository. */
1
2
  export declare function handlePoll(args: string[]): Promise<void>;
3
+ /** `pr-shepherd stack <PR>`: the native stack containing one PR. */
4
+ export declare function handleStack(args: string[]): Promise<void>;
@@ -1,4 +1,4 @@
1
- import { runAggregatePollForMode, runPollForMode } from "../commands/event-mode.mjs";
1
+ import { runAggregatePollCommand, runPollCommand, runPollSummaryTick } from "../commands/tick.mjs";
2
2
  import { loadConfig } from "../config/load.mjs";
3
3
  import { parseCommonArgs, getFlag, hasFlag } from "./args.mjs";
4
4
  import { parseDurationToSeconds } from "./duration.mjs";
@@ -8,41 +8,88 @@ import { emitIterateResult } from "./iterate-emitter.mjs";
8
8
  import { EXIT } from "../exit-codes.mjs";
9
9
  import { emitPollSummaryResult } from "./poll-summary-emitter.mjs";
10
10
  import { parsePollTargets, resolvePollTargets } from "./poll-targets.mjs";
11
+ import { DEFAULT_POLL_BOOLEAN_FLAGS, DEFAULT_POLL_FLAGS_WITH_VALUES } from "./default-poll.mjs";
12
+ import { validateDefaultArgs } from "./validate-default-args.mjs";
13
+ import { rejectRemovedFlags } from "./removed-flags.mjs";
14
+ import { rejectUnknownFlags } from "./unknown-flags.mjs";
15
+ import { maybePrintHelp, USAGE } from "./help.mjs";
16
+ /** The default command: one PR, or several PRs from one repository. */
11
17
  export async function handlePoll(args) {
12
18
  const parsedTargets = parsePollTargets(args);
13
19
  if (!parsedTargets)
14
20
  return;
15
- const needsResolution = parsedTargets.stack !== undefined || parsedTargets.refs.length > 1;
16
- const resolvedTargets = needsResolution
17
- ? await resolvePollTargets(parsedTargets)
18
- : { prNumbers: [] };
19
- const isAggregate = resolvedTargets.stackPrNumber !== undefined || resolvedTargets.prNumbers.length > 1;
20
- const { prNumber, global: commonGlobalOpts, extra: commonExtra } = parseCommonArgs(args);
21
- const globalOpts = isAggregate ? parsedTargets.global : commonGlobalOpts;
22
- const extra = isAggregate ? parsedTargets.extra : commonExtra;
21
+ const resolvedTargets = parsedTargets.refs.length > 1 ? await resolvePollTargets(parsedTargets) : { prNumbers: [] };
22
+ // Duplicate references collapse to one PR, which takes the single-PR iterator.
23
+ if (resolvedTargets.prNumbers.length > 1) {
24
+ const shared = parseLoopOptions(parsedTargets.extra, parsedTargets.global, true);
25
+ if (!shared)
26
+ return;
27
+ const result = await runAggregatePollCommand({ ...shared.options, ...resolvedTargets });
28
+ emitPollSummaryResult(result, { format: parsedTargets.global.format });
29
+ return;
30
+ }
31
+ const { prNumber, global: globalOpts, extra } = parseCommonArgs(args);
32
+ const shared = parseLoopOptions(extra, globalOpts, false);
33
+ if (!shared)
34
+ return;
35
+ const result = await runPollCommand({ ...shared.options, prNumber });
36
+ emitIterateResult(result, {
37
+ format: globalOpts.format,
38
+ verbose: globalOpts.verbose ?? false,
39
+ readyDelaySuffix: shared.readyDelaySuffix,
40
+ });
41
+ }
42
+ /** `pr-shepherd stack <PR>`: the native stack containing one PR. */
43
+ export async function handleStack(args) {
44
+ if (maybePrintHelp(args, "stack"))
45
+ return;
46
+ if (rejectRemovedFlags(args) || rejectUnknownFlags(args, "stack", STACK_FLAGS))
47
+ return;
48
+ if (!validateDefaultArgs(args, DEFAULT_POLL_FLAGS_WITH_VALUES, STACK_BOOLEAN_FLAGS, usageArg))
49
+ return;
50
+ const parsedTargets = parsePollTargets(args);
51
+ if (!parsedTargets)
52
+ return;
53
+ if (parsedTargets.refs.length !== 1) {
54
+ usage("stack requires exactly one PR reference.");
55
+ return;
56
+ }
57
+ const { prNumbers, targetRepository } = await resolvePollTargets(parsedTargets);
58
+ const shared = parseLoopOptions(parsedTargets.extra, parsedTargets.global, true);
59
+ if (!shared)
60
+ return;
61
+ const target = { stackPrNumber: prNumbers[0], targetRepository };
62
+ const result = hasFlag(parsedTargets.extra, "--once")
63
+ ? await runPollSummaryTick({ ...shared.options, ...target })
64
+ : await runAggregatePollCommand({ ...shared.options, ...target });
65
+ emitPollSummaryResult(result, { format: parsedTargets.global.format });
66
+ }
67
+ const STACK_BOOLEAN_FLAGS = new Set([...DEFAULT_POLL_BOOLEAN_FLAGS, "--once"]);
68
+ const STACK_FLAGS = [...DEFAULT_POLL_FLAGS_WITH_VALUES, ...STACK_BOOLEAN_FLAGS];
69
+ function parseLoopOptions(extra, globalOpts, isAggregate) {
23
70
  const cfg = loadConfig();
24
71
  const flags = parseIterateFlags(extra, cfg);
25
72
  if (hasInvalidIterateFlags(flags))
26
- return;
73
+ return null;
27
74
  const intervalStr = getFlag(extra, "--interval");
28
75
  const intervalSuffix = validateSecondsDurationFlag("pr-shepherd", "--interval", intervalStr, hasFlag(extra, "--interval"));
29
76
  if (intervalSuffix === null)
30
- return;
31
- let intervalSeconds = parseDurationToSeconds(intervalSuffix ?? "", cfg.poll.intervalSeconds);
77
+ return null;
78
+ let intervalSeconds = parseDurationToSeconds(intervalSuffix ?? "", cfg.poll["interval-seconds"]);
32
79
  // `--interval` is the invocation interval. Only an omitted flag is scaled, and only for aggregates.
33
80
  if (isAggregate && intervalSuffix === undefined) {
34
- intervalSeconds *= cfg.poll.stackIntervalFactor;
81
+ intervalSeconds *= cfg.poll["stack-interval-factor"];
35
82
  }
36
83
  const timeoutStr = getFlag(extra, "--timeout");
37
84
  const timeoutSuffix = validateSecondsDurationFlag("pr-shepherd", "--timeout", timeoutStr, hasFlag(extra, "--timeout"));
38
85
  if (timeoutSuffix === null)
39
- return;
40
- const timeoutSeconds = parseDurationToSeconds(timeoutSuffix ?? "", cfg.poll.timeoutSeconds);
86
+ return null;
87
+ const timeoutSeconds = parseDurationToSeconds(timeoutSuffix ?? "", cfg.poll["timeout-seconds"]);
41
88
  const debounceStr = getFlag(extra, "--debounce");
42
89
  const debounceSuffix = validateSecondsDurationFlag("pr-shepherd", "--debounce", debounceStr, hasFlag(extra, "--debounce"), { allowZero: true });
43
90
  if (debounceSuffix === null)
44
- return;
45
- const debounceSeconds = parseDurationToSeconds(debounceSuffix ?? "", cfg.poll.debounceSeconds, {
91
+ return null;
92
+ const debounceSeconds = parseDurationToSeconds(debounceSuffix ?? "", cfg.poll["debounce-seconds"], {
46
93
  allowZero: true,
47
94
  });
48
95
  const quietStatusFlag = hasFlag(extra, "--quiet-status");
@@ -50,36 +97,30 @@ export async function handlePoll(args) {
50
97
  if (quietStatusFlag && noQuietStatusFlag) {
51
98
  process.stderr.write("pr-shepherd: --quiet-status and --no-quiet-status cannot be used together\n");
52
99
  process.exitCode = EXIT.USAGE;
53
- return;
54
- }
55
- const quietStatus = quietStatusFlag || (!noQuietStatusFlag && cfg.poll.quietStatus);
56
- const shared = {
57
- ...globalOpts,
58
- readyDelaySeconds: flags.readyDelaySeconds,
59
- stallTimeoutSeconds: flags.stallTimeoutSeconds,
60
- noAutoMarkReady: flags.noAutoMarkReady,
61
- noAutoCancelActionable: flags.noAutoCancelActionable,
62
- merge: flags.merge,
63
- instructions: flags.instructions ?? undefined,
64
- pollMode: flags.pollMode ?? undefined,
65
- intervalSeconds,
66
- timeoutSeconds,
67
- debounceSeconds,
68
- quietStatus,
69
- untilTerminal: hasFlag(extra, "--until-terminal"),
70
- };
71
- if (isAggregate) {
72
- const result = await runAggregatePollForMode({
73
- ...shared,
74
- ...resolvedTargets,
75
- });
76
- emitPollSummaryResult(result, { format: globalOpts.format });
77
- return;
100
+ return null;
78
101
  }
79
- const result = await runPollForMode({ ...shared, prNumber });
80
- emitIterateResult(result, {
81
- format: globalOpts.format,
82
- verbose: globalOpts.verbose ?? false,
102
+ const quietStatus = quietStatusFlag || (!noQuietStatusFlag && cfg.poll["quiet-status"]);
103
+ return {
83
104
  readyDelaySuffix: flags.readyDelaySuffix ?? undefined,
84
- });
105
+ options: {
106
+ ...globalOpts,
107
+ readyDelaySeconds: flags.readyDelaySeconds,
108
+ stallTimeoutSeconds: flags.stallTimeoutSeconds,
109
+ noAutoMarkReady: flags.noAutoMarkReady,
110
+ merge: flags.merge,
111
+ instructions: flags.instructions ?? undefined,
112
+ intervalSeconds,
113
+ timeoutSeconds,
114
+ debounceSeconds,
115
+ quietStatus,
116
+ untilTerminal: hasFlag(extra, "--until-terminal"),
117
+ },
118
+ };
119
+ }
120
+ function usageArg(arg) {
121
+ usage(`unexpected argument: "${arg}"`);
122
+ }
123
+ function usage(message) {
124
+ process.stderr.write(`pr-shepherd: stack: ${message}\n${USAGE.stack}\n`);
125
+ process.exitCode = EXIT.USAGE;
85
126
  }
@@ -25,8 +25,8 @@ function pollSummaryExitCode(result) {
25
25
  const actions = new Set(result.prs.map((item) => item.action));
26
26
  if (actions.has("escalate"))
27
27
  return EXIT.ESCALATE;
28
- if (actions.has("fix_code"))
29
- return EXIT.FIX_CODE;
28
+ if (actions.has("triage"))
29
+ return EXIT.TRIAGE;
30
30
  if (actions.has("merge"))
31
31
  return EXIT.MERGE;
32
32
  if (actions.has("mark_ready"))
@@ -2,7 +2,7 @@ import { formatApiUsage, formatQuotaWarning } from "./api-usage-formatter.mjs";
2
2
  import { withPollSummaryInstructions } from "../commands/poll-summary-instructions.mjs";
3
3
  import { formatStackOverview, projectStackOverview } from "./stack-overview.mjs";
4
4
  import { formatTransportEvidence } from "./transport-formatter.mjs";
5
- import { formatNextCheckLines, formatPollModeSegment } from "./next-check-format.mjs";
5
+ import { formatNextCheckLines } from "./next-check-format.mjs";
6
6
  export function formatPollSummaryResult(result) {
7
7
  if (result.selection.kind === "stack")
8
8
  return formatStackOverview(projectStackOverview(result));
@@ -10,7 +10,7 @@ export function formatPollSummaryResult(result) {
10
10
  const lines = [
11
11
  `# Poll summary [${result.reason.toUpperCase()}]`,
12
12
  "",
13
- `**repo** \`${result.repo}\` · **selection** ${selection} · **mode** \`${result.mode}\`${formatPollModeSegment(result)}${result.stackMergeable !== undefined ? ` · **stack mergeable** \`${result.stackMergeable}\`` : ""}${result.nextAction ? ` · **next action** \`${result.nextAction}\`` : ""}`,
13
+ `**repo** \`${result.repo}\` · **selection** ${selection} · **mode** \`${result.mode}\`${result.stackMergeable !== undefined ? ` · **stack mergeable** \`${result.stackMergeable}\`` : ""}${result.nextAction ? ` · **next action** \`${result.nextAction}\`` : ""}`,
14
14
  ...formatNextCheckLines(result.nextCheck),
15
15
  "",
16
16
  "## Pull requests",
@@ -3,13 +3,13 @@ import { type ParsedPrReference } from "../pr-reference.mts";
3
3
  import type { GlobalOptions } from "../types.mts";
4
4
  export interface ParsedPollTargets {
5
5
  refs: ParsedPrReference[];
6
- stack: ParsedPrReference | undefined;
7
6
  global: GlobalOptions;
8
7
  extra: string[];
9
8
  }
9
+ /** Splits PR references from flags. Returns null after printing a usage error. */
10
10
  export declare function parsePollTargets(args: string[]): ParsedPollTargets | null;
11
+ /** Resolves PR references that must all name one repository. */
11
12
  export declare function resolvePollTargets(parsed: ParsedPollTargets): Promise<{
12
13
  prNumbers: number[];
13
- stackPrNumber?: number;
14
14
  targetRepository?: RepoInfo;
15
15
  }>;