pr-shepherd 0.34.0 → 0.36.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 (231) hide show
  1. package/.claude-plugin/plugin.json +3 -2
  2. package/.grok-plugin/marketplace.json +17 -0
  3. package/README.md +80 -43
  4. package/bin/api.d.mts +80 -0
  5. package/bin/api.mjs +236 -0
  6. package/bin/checks/classify.d.mts +41 -0
  7. package/bin/checks/startup-failures.d.mts +2 -0
  8. package/bin/checks/superseded.d.mts +21 -0
  9. package/bin/checks/triage.d.mts +4 -0
  10. package/bin/checks/triage.mjs +11 -3
  11. package/bin/classify/apply.d.mts +18 -0
  12. package/bin/classify/apply.mjs +4 -0
  13. package/bin/classify/loader.d.mts +10 -0
  14. package/bin/classify/types.d.mts +35 -0
  15. package/bin/cli/args.d.mts +18 -0
  16. package/bin/cli/args.mjs +1 -0
  17. package/bin/cli/clean-formatter.d.mts +2 -0
  18. package/bin/cli/default-poll.d.mts +2 -0
  19. package/bin/cli/default-poll.mjs +1 -0
  20. package/bin/cli/duration-flag.d.mts +2 -0
  21. package/bin/cli/duration.d.mts +13 -0
  22. package/bin/cli/fence.d.mts +1 -0
  23. package/bin/cli/fix-formatter-extra.d.mts +3 -0
  24. package/bin/cli/fix-formatter.d.mts +2 -0
  25. package/bin/cli/fix-formatter.mjs +6 -6
  26. package/bin/cli/formatters.d.mts +7 -0
  27. package/bin/cli/handlers.d.mts +4 -0
  28. package/bin/cli/handlers.mjs +11 -10
  29. package/bin/cli/help-command-pages.d.mts +231 -0
  30. package/bin/cli/help-command-pages.mjs +106 -1
  31. package/bin/cli/help-iterate-poll-pages.d.mts +4 -0
  32. package/bin/cli/help-iterate-poll-pages.mjs +14 -7
  33. package/bin/cli/help-log-file-page.d.mts +1 -0
  34. package/bin/cli/help-top-page.d.mts +1 -0
  35. package/bin/cli/help-top-page.mjs +16 -16
  36. package/bin/cli/help.d.mts +236 -0
  37. package/bin/cli/help.mjs +17 -0
  38. package/bin/cli/iterate-emitter.d.mts +8 -0
  39. package/bin/cli/iterate-flags.d.mts +11 -0
  40. package/bin/cli/iterate-formatter.d.mts +19 -0
  41. package/bin/cli/iterate-formatter.mjs +11 -2
  42. package/bin/cli/iterate-instructions.d.mts +6 -0
  43. package/bin/cli/iterate-lean.d.mts +12 -0
  44. package/bin/cli/iterate-lean.mjs +1 -0
  45. package/bin/cli/journal-formatter.d.mts +2 -0
  46. package/bin/cli/journal-formatter.mjs +15 -0
  47. package/bin/cli/journal-handler.d.mts +1 -0
  48. package/bin/cli/journal-handler.mjs +7 -20
  49. package/bin/cli/list-formatters.d.mts +76 -0
  50. package/bin/cli/list-formatters.mjs +9 -9
  51. package/bin/cli/mark-files-as-viewed-flags.d.mts +11 -0
  52. package/bin/cli/mark-files-as-viewed-formatter.d.mts +2 -0
  53. package/bin/cli/mutate-formatter.d.mts +2 -0
  54. package/bin/cli/poll-handler.d.mts +1 -0
  55. package/bin/cli/poll-handler.mjs +9 -2
  56. package/bin/cli/resolve-validators.d.mts +3 -0
  57. package/bin/cli/resolve-validators.mjs +3 -3
  58. package/bin/cli/runner.d.mts +7 -0
  59. package/bin/cli/suggestion-renderer.d.mts +3 -0
  60. package/bin/cli/validate-default-args.d.mts +6 -0
  61. package/bin/cli-parser.d.mts +2 -0
  62. package/bin/cli-parser.mjs +72 -16
  63. package/bin/commands/check-annotations.d.mts +5 -0
  64. package/bin/commands/check-status.d.mts +3 -0
  65. package/bin/commands/check-terminal-report.d.mts +5 -0
  66. package/bin/commands/check.d.mts +7 -0
  67. package/bin/commands/check.mjs +28 -23
  68. package/bin/commands/clean.d.mts +21 -0
  69. package/bin/commands/commit-suggestion-instruction.d.mts +8 -0
  70. package/bin/commands/commit-suggestion-instruction.mjs +3 -3
  71. package/bin/commands/commit-suggestion.d.mts +8 -0
  72. package/bin/commands/commit-suggestion.mjs +20 -13
  73. package/bin/commands/iterate/check-instructions.d.mts +16 -0
  74. package/bin/commands/iterate/check-instructions.mjs +3 -3
  75. package/bin/commands/iterate/classify.d.mts +18 -0
  76. package/bin/commands/iterate/classify.mjs +4 -4
  77. package/bin/commands/iterate/escalate.d.mts +31 -0
  78. package/bin/commands/iterate/escalate.mjs +7 -4
  79. package/bin/commands/iterate/fix-code.d.mts +25 -0
  80. package/bin/commands/iterate/fix-code.mjs +1 -1
  81. package/bin/commands/iterate/helpers.d.mts +14 -0
  82. package/bin/commands/iterate/helpers.mjs +19 -7
  83. package/bin/commands/iterate/index.d.mts +2 -0
  84. package/bin/commands/iterate/index.mjs +8 -12
  85. package/bin/commands/iterate/render.d.mts +5 -0
  86. package/bin/commands/iterate/render.mjs +8 -6
  87. package/bin/commands/iterate/reruns.d.mts +20 -0
  88. package/bin/commands/iterate/stall.d.mts +6 -0
  89. package/bin/commands/journal/index.d.mts +14 -0
  90. package/bin/commands/journal/index.mjs +1 -0
  91. package/bin/commands/journal/transform.d.mts +22 -0
  92. package/bin/commands/log-file.d.mts +5 -0
  93. package/bin/commands/mark-files-as-viewed.d.mts +26 -0
  94. package/bin/commands/mark-files-as-viewed.mjs +1 -0
  95. package/bin/commands/poll.d.mts +12 -0
  96. package/bin/commands/poll.mjs +52 -17
  97. package/bin/commands/ready-delay.d.mts +29 -0
  98. package/bin/commands/ready-delay.mjs +3 -13
  99. package/bin/commands/ready-mergeability.d.mts +15 -0
  100. package/bin/commands/resolve-mutate.d.mts +4 -0
  101. package/bin/commands/resolve-mutate.mjs +1 -0
  102. package/bin/commands/resolve.d.mts +4 -0
  103. package/bin/commands/shepherd-journal.d.mts +7 -0
  104. package/bin/commands/shepherd-journal.mjs +2 -2
  105. package/bin/comments/authors.d.mts +14 -0
  106. package/bin/comments/marker.d.mts +2 -0
  107. package/bin/comments/minimize-policy.d.mts +4 -0
  108. package/bin/comments/pending-ops.d.mts +15 -0
  109. package/bin/comments/rate-limit.d.mts +18 -0
  110. package/bin/comments/resolve.d.mts +34 -0
  111. package/bin/comments/resolve.mjs +1 -0
  112. package/bin/comments/review-thread-markers.d.mts +8 -0
  113. package/bin/comments/review-visibility.d.mts +28 -0
  114. package/bin/comments/sha-poll.d.mts +2 -0
  115. package/bin/comments/thread-visibility.d.mts +11 -0
  116. package/bin/comments/visible-comments.d.mts +11 -0
  117. package/bin/config/load.d.mts +60 -0
  118. package/bin/config/load.mjs +129 -16
  119. package/bin/config.json +0 -2
  120. package/bin/execution-context.d.mts +9 -0
  121. package/bin/execution-context.mjs +19 -0
  122. package/bin/exit-codes.d.mts +51 -0
  123. package/bin/github/activity.d.mts +3 -0
  124. package/bin/github/activity.mjs +7 -0
  125. package/bin/github/batch-page-helpers.d.mts +45 -0
  126. package/bin/github/batch-page-helpers.mjs +63 -0
  127. package/bin/github/batch-page.d.mts +14 -0
  128. package/bin/github/batch-page.mjs +62 -0
  129. package/bin/github/batch-parse-suites.d.mts +4 -0
  130. package/bin/github/batch-parse-suites.mjs +25 -0
  131. package/bin/github/batch-parser-helpers.d.mts +22 -0
  132. package/bin/github/batch-parsers-rules.d.mts +6 -0
  133. package/bin/github/batch-parsers-rules.mjs +122 -0
  134. package/bin/github/batch-parsers.d.mts +3 -0
  135. package/bin/github/batch-parsers.mjs +12 -0
  136. package/bin/github/batch-raw-rules.d.mts +59 -0
  137. package/bin/github/batch-raw-rules.mjs +1 -0
  138. package/bin/github/batch-raw-types.d.mts +216 -0
  139. package/bin/github/batch-response.d.mts +4 -0
  140. package/bin/github/batch.d.mts +24 -0
  141. package/bin/github/batch.mjs +14 -120
  142. package/bin/github/branch-protection.d.mts +3 -0
  143. package/bin/github/check-annotations.d.mts +2 -0
  144. package/bin/github/client.d.mts +46 -0
  145. package/bin/github/client.mjs +7 -2
  146. package/bin/github/errors.d.mts +25 -0
  147. package/bin/github/gql/batch-pr-page.gql +189 -0
  148. package/bin/github/gql/batch-pr.gql +79 -21
  149. package/bin/github/gql/commit-suggestion-thread.gql +40 -0
  150. package/bin/github/gql/review-thread-comments.gql +1 -0
  151. package/bin/github/graphql-http.d.mts +19 -0
  152. package/bin/github/graphql-response.d.mts +7 -0
  153. package/bin/github/http-auth.d.mts +4 -0
  154. package/bin/github/http-request.d.mts +7 -0
  155. package/bin/github/http-utils.d.mts +11 -0
  156. package/bin/github/http.d.mts +6 -0
  157. package/bin/github/http.mjs +2 -1
  158. package/bin/github/pagination.d.mts +46 -0
  159. package/bin/github/pagination.mjs +3 -2
  160. package/bin/github/queries.d.mts +28 -0
  161. package/bin/github/queries.mjs +4 -0
  162. package/bin/github/rest-http.d.mts +7 -0
  163. package/bin/github/rest-http.mjs +25 -86
  164. package/bin/github/rest-text.d.mts +1 -0
  165. package/bin/github/rest-text.mjs +88 -0
  166. package/bin/github/suggestion-thread.d.mts +9 -0
  167. package/bin/github/suggestion-thread.mjs +45 -0
  168. package/bin/github/thread-comments.d.mts +2 -0
  169. package/bin/github/thread-comments.mjs +12 -8
  170. package/bin/index.d.mts +10 -0
  171. package/bin/index.mjs +1 -1
  172. package/bin/log/log-file.d.mts +28 -0
  173. package/bin/log/session.d.mts +31 -0
  174. package/bin/log/setup.d.mts +6 -0
  175. package/bin/mcp/index.d.mts +5 -0
  176. package/bin/mcp/index.mjs +8 -0
  177. package/bin/mcp/server.d.mts +8 -0
  178. package/bin/mcp/server.mjs +157 -0
  179. package/bin/mcp-stdio.d.mts +2 -0
  180. package/bin/mcp-stdio.mjs +7 -0
  181. package/bin/merge-status/derive.d.mts +19 -0
  182. package/bin/merge-status/derive.mjs +2 -0
  183. package/bin/merge-status/requirements-format.d.mts +3 -0
  184. package/bin/merge-status/requirements-format.mjs +88 -0
  185. package/bin/merge-status/requirements.d.mts +2 -0
  186. package/bin/merge-status/requirements.mjs +51 -0
  187. package/bin/reporters/agent.d.mts +23 -0
  188. package/bin/reporters/agent.mjs +3 -0
  189. package/bin/state/base.d.mts +10 -0
  190. package/bin/state/base.mjs +23 -0
  191. package/bin/state/bot-cr-seen.d.mts +51 -0
  192. package/bin/state/bot-cr-seen.mjs +4 -14
  193. package/bin/state/fix-attempts.d.mts +27 -0
  194. package/bin/state/fix-attempts.mjs +3 -13
  195. package/bin/state/iterate-stall.d.mts +27 -0
  196. package/bin/state/iterate-stall.mjs +3 -13
  197. package/bin/state/seen-comments.d.mts +62 -0
  198. package/bin/state/seen-comments.mjs +6 -13
  199. package/bin/suggestions/extract.d.mts +8 -0
  200. package/bin/suggestions/parse.d.mts +48 -0
  201. package/bin/suggestions/patch.d.mts +14 -0
  202. package/bin/threads/transcript.d.mts +14 -0
  203. package/bin/threads/transcript.mjs +4 -0
  204. package/bin/types/activity.d.mts +30 -0
  205. package/bin/types/agent-thread.d.mts +9 -0
  206. package/bin/types/check-annotations.d.mts +14 -0
  207. package/bin/types/check-classification.d.mts +19 -0
  208. package/bin/types/github.d.mts +136 -0
  209. package/bin/types/iterate.d.mts +156 -0
  210. package/bin/types/merge-requirements.d.mts +82 -0
  211. package/bin/types/merge-requirements.mjs +2 -0
  212. package/bin/types/protected-run.d.mts +6 -0
  213. package/bin/types/report.d.mts +176 -0
  214. package/bin/types/review-thread.d.mts +12 -0
  215. package/bin/types.d.mts +10 -0
  216. package/bin/types.mjs +1 -0
  217. package/bin/util/markdown.d.mts +1 -0
  218. package/bin/util/path-segment.d.mts +4 -0
  219. package/bin/util/path-segment.mjs +2 -0
  220. package/bin/util/pool.d.mts +2 -0
  221. package/bin/util/pool.mjs +18 -0
  222. package/bin/util/sleep.d.mts +1 -0
  223. package/bin/util/worktree.d.mts +9 -0
  224. package/bin/util/worktree.mjs +4 -1
  225. package/package.json +51 -37
  226. package/plugins/pr-shepherd/.codex-plugin/plugin.json +3 -2
  227. package/plugins/pr-shepherd/.codex.mcp.json +8 -0
  228. package/plugins/pr-shepherd/.mcp.json +6 -0
  229. package/plugins/pr-shepherd/skills/mark-files-as-viewed/SKILL.md +5 -19
  230. package/plugins/pr-shepherd/skills/pr-shepherd/SKILL.md +6 -15
  231. package/src/classify/types.mts +12 -0
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Build the `build-suggestion-patch` instruction step for agent consumers.
3
+ * Currently emitted by iterate `fix_code` for suggestion review threads.
4
+ * @param sectionName - The markdown section heading where suggestion threads appear,
5
+ * e.g. `"## Review threads"`.
6
+ * @param includeDriftHint - Whether to add the trailing note about drift on failed apply.
7
+ */
8
+ export declare function buildCommitSuggestionInstruction(prNumber: number, sectionName: string, includeDriftHint: boolean): string;
@@ -1,6 +1,6 @@
1
1
  import { buildPrShepherdCommand } from "../cli/runner.mjs";
2
2
  /**
3
- * Build the `commit-suggestion` instruction step for agent consumers.
3
+ * Build the `build-suggestion-patch` instruction step for agent consumers.
4
4
  * Currently emitted by iterate `fix_code` for suggestion review threads.
5
5
  * @param sectionName - The markdown section heading where suggestion threads appear,
6
6
  * e.g. `"## Review threads"`.
@@ -8,7 +8,7 @@ import { buildPrShepherdCommand } from "../cli/runner.mjs";
8
8
  */
9
9
  export function buildCommitSuggestionInstruction(prNumber, sectionName, includeDriftHint) {
10
10
  const command = buildPrShepherdCommand([
11
- "commit-suggestion",
11
+ "build-suggestion-patch",
12
12
  String(prNumber),
13
13
  "--thread-id",
14
14
  "<id>",
@@ -19,5 +19,5 @@ export function buildCommitSuggestionInstruction(prNumber, sectionName, includeD
19
19
  const driftHint = includeDriftHint
20
20
  ? " If the patch fails to apply (drift since the suggestion was written), fall through to the manual fix step."
21
21
  : " If the patch fails to apply, fall through to the manual-edit step.";
22
- return `For each thread marked \`[suggestion]\` under \`${sectionName}\`: run \`${command}\` to retrieve the patch and suggested commit. The CLI does not mutate the working tree — apply the patch yourself (run \`git apply\` with the diff shown, or edit the file directly using the line range), then stage the listed file and run the suggested \`git commit\` from the \`## Instructions\` section. Human-authored thread IDs are replied to by the resolve command below; Shepherd does not auto-resolve them.${driftHint} Do not retry the same command.`;
22
+ return `For each thread marked \`[suggestion]\` under \`${sectionName}\`: run \`${command}\` to retrieve the patch and suggested commit. The CLI does not mutate the working tree — apply the patch yourself (run \`git apply\` with the diff shown, or edit the file directly using the line range), then stage the listed file and run the suggested \`git commit\` from the \`## Instructions\` section. Human-authored thread IDs are replied to by the apply command below; Shepherd does not auto-resolve them.${driftHint} Do not retry the same command.`;
23
23
  }
@@ -0,0 +1,8 @@
1
+ import type { CommitSuggestionResult, GlobalOptions } from "../types.mts";
2
+ export interface CommitSuggestionOptions extends GlobalOptions {
3
+ threadId: string;
4
+ message: string;
5
+ description?: string;
6
+ }
7
+ /** @deprecated Hidden implementation for `commit-suggestion`; use `build-suggestion-patch`. */
8
+ export declare function runCommitSuggestion(opts: CommitSuggestionOptions): Promise<CommitSuggestionResult>;
@@ -1,13 +1,16 @@
1
1
  import { execFile as execFileCb } from "node:child_process";
2
2
  import { readFile } from "node:fs/promises";
3
+ import { relative, resolve } from "node:path";
3
4
  import { promisify } from "node:util";
4
5
  import { getRepoInfo, getCurrentPrNumber, getCurrentBranch } from "../github/client.mjs";
5
- import { fetchPrBatch } from "../github/batch.mjs";
6
+ import { fetchSuggestionThread } from "../github/suggestion-thread.mjs";
6
7
  import { parseSuggestion, isCommittableSuggestion } from "../suggestions/parse.mjs";
7
8
  import { buildUnifiedDiff } from "../suggestions/patch.mjs";
8
9
  import { EXIT, ShepherdError } from "../exit-codes.mjs";
9
10
  import { buildPrShepherdCommand } from "../cli/runner.mjs";
11
+ import { getEffectiveCwd, getExecutionCwd } from "../execution-context.mjs";
10
12
  const execFile = promisify(execFileCb);
13
+ /** @deprecated Hidden implementation for `commit-suggestion`; use `build-suggestion-patch`. */
11
14
  export async function runCommitSuggestion(opts) {
12
15
  if (!opts.threadId) {
13
16
  throw new ShepherdError("--thread-id is required", EXIT.USAGE);
@@ -21,9 +24,11 @@ export async function runCommitSuggestion(opts) {
21
24
  throw new ShepherdError("No open PR found for current branch. Pass a PR number explicitly.", EXIT.UNAVAILABLE);
22
25
  }
23
26
  const currentBranch = await getCurrentBranch();
24
- const { stdout: localHeadOut } = await execFile("git", ["rev-parse", "HEAD"]);
27
+ const { stdout: localHeadOut } = await execFile("git", ["rev-parse", "HEAD"], {
28
+ cwd: getExecutionCwd(),
29
+ });
25
30
  const localHeadSha = localHeadOut.trim();
26
- const { data } = await fetchPrBatch(prNumber, repo);
31
+ const data = await fetchSuggestionThread(prNumber, repo, opts.threadId);
27
32
  if (!data.headRepoWithOwner) {
28
33
  throw new ShepherdError(`PR #${prNumber} head repository is unavailable (fork may have been deleted).`, EXIT.UNAVAILABLE);
29
34
  }
@@ -35,7 +40,7 @@ export async function runCommitSuggestion(opts) {
35
40
  throw new ShepherdError(`Local HEAD ${localHeadSha} does not match PR head ${data.headRefOid}. ` +
36
41
  `Pull/rebase "${data.headRefName}" to the latest PR head and try again.`, EXIT.UNAVAILABLE);
37
42
  }
38
- const thread = data.reviewThreads.find((t) => t.id === opts.threadId);
43
+ const thread = data.thread;
39
44
  if (!thread) {
40
45
  throw new ShepherdError(`Thread ${opts.threadId} not found on PR #${prNumber}.`, EXIT.UNAVAILABLE);
41
46
  }
@@ -53,14 +58,9 @@ export async function runCommitSuggestion(opts) {
53
58
  }
54
59
  // Validate the target file is clean before generating the patch, so the emitted
55
60
  // `git add -- <file>` instruction cannot accidentally stage unrelated local edits.
56
- const { stdout: fileStatus } = await execFile("git", [
57
- "status",
58
- "--porcelain",
59
- "--",
60
- thread.path,
61
- ]);
61
+ const { stdout: fileStatus } = await execFile("git", ["status", "--porcelain", "--", thread.path], { cwd: getExecutionCwd() });
62
62
  if (fileStatus.trim() !== "") {
63
- throw new ShepherdError(`${thread.path} has uncommitted changes. Commit or stash them before running commit-suggestion.`, EXIT.UNAVAILABLE);
63
+ throw new ShepherdError(`${thread.path} has uncommitted changes. Commit or stash them before running build-suggestion-patch.`, EXIT.UNAVAILABLE);
64
64
  }
65
65
  const parsed = parseSuggestion(thread.body);
66
66
  if (!parsed) {
@@ -73,7 +73,13 @@ export async function runCommitSuggestion(opts) {
73
73
  const startLine = thread.startLine ?? thread.line;
74
74
  const endLine = thread.line;
75
75
  const filePath = thread.path;
76
- const originalContent = await readFile(filePath, "utf8");
76
+ const cwd = getEffectiveCwd();
77
+ const resolvedPath = resolve(cwd, filePath);
78
+ const rel = relative(cwd, resolvedPath);
79
+ if (rel.startsWith("..") || rel === "") {
80
+ throw new ShepherdError(`Thread ${opts.threadId} path escapes the working tree.`, EXIT.UNAVAILABLE);
81
+ }
82
+ const originalContent = await readFile(resolvedPath, "utf8");
77
83
  const patch = buildUnifiedDiff({
78
84
  path: filePath,
79
85
  originalContent,
@@ -94,7 +100,8 @@ export async function runCommitSuggestion(opts) {
94
100
  ...commitBodyArg.split("\n\n").map((p) => `-m ${sq(p)}`),
95
101
  ].join(" ");
96
102
  const resolveCommand = buildPrShepherdCommand([
97
- "resolve",
103
+ "apply",
104
+ "review",
98
105
  String(prNumber),
99
106
  "--resolve-thread-ids",
100
107
  opts.threadId,
@@ -0,0 +1,16 @@
1
+ import type { AgentCheck, ResolveCommand, Review } from "../../types.mts";
2
+ /** Build the stale-CR clause appended to the `## Changes-requested reviews` instruction. */
3
+ export declare function buildCrStaleClause(reviews: Review[]): string;
4
+ /**
5
+ * Build the optional behind-base push hint. Empty unless the branch is actually behind its base
6
+ * and the user configured a non-blank `iterate.behindBaseHint` — the CLI never prescribes
7
+ * rebase/merge mechanics itself (see "Keep skills and loop prompts minimal" in CLAUDE.md); this
8
+ * only echoes back the caller's own configured pointer. `hint` is trimmed and type-checked at the
9
+ * point of use (rather than at config load) so a malformed rc file value (non-string, or
10
+ * whitespace-only) degrades to "no hint" instead of rendering garbage into agent-facing text or
11
+ * discarding the rest of the user's config.
12
+ */
13
+ export declare function buildBehindBaseHintInstruction(baseBranch: string, hint: string, isBehind: boolean): string[];
14
+ /** Build the `Run the apply review: command` instruction, including its optional substitution hint. */
15
+ export declare function buildResolveCommandInstruction(resolveCommand: ResolveCommand): string[];
16
+ export declare function buildFailingCheckInstructions(checks: AgentCheck[]): string[];
@@ -23,13 +23,13 @@ export function buildBehindBaseHintInstruction(baseBranch, hint, isBehind) {
23
23
  return [];
24
24
  return [`The branch is behind \`origin/${baseBranch}\` — ${trimmedHint} before pushing.`];
25
25
  }
26
- /** Build the `Run the resolve: command` instruction, including its optional substitution hint. */
26
+ /** Build the `Run the apply review: command` instruction, including its optional substitution hint. */
27
27
  export function buildResolveCommandInstruction(resolveCommand) {
28
28
  if (!resolveCommand.hasMutations)
29
29
  return [];
30
30
  const instructions = [];
31
31
  if ((resolveCommand.replyThreadIds?.length ?? 0) > 0) {
32
- instructions.push(`Before running the \`resolve:\` command, remove any thread from \`--reply-thread-ids\` if the latest visible comment in that thread is your own prior Shepherd reply. Do not reply to your own comments.`);
32
+ instructions.push(`Before running the \`apply review:\` command, remove any thread from \`--reply-thread-ids\` if the latest visible comment in that thread is your own prior Shepherd reply. Do not reply to your own comments.`);
33
33
  }
34
34
  const substituteParts = [];
35
35
  if (resolveCommand.requiresHeadSha) {
@@ -39,7 +39,7 @@ export function buildResolveCommandInstruction(resolveCommand) {
39
39
  substituteParts.push(`\`$DISMISS_MESSAGE\` with a one-sentence reply/description of what you changed`);
40
40
  }
41
41
  const substituteHint = substituteParts.length > 0 ? `, substituting ${substituteParts.join(" and ")}` : "";
42
- instructions.push(`Run the \`resolve:\` command shown above${substituteHint}.`);
42
+ instructions.push(`Run the \`apply review:\` command shown above${substituteHint}.`);
43
43
  return instructions;
44
44
  }
45
45
  export function buildFailingCheckInstructions(checks) {
@@ -0,0 +1,18 @@
1
+ import type { AgentThread, Review, ResolveCommand, AgentCheck, ReviewThread } from "../../types.mts";
2
+ import { type NormalizedBotUsernames } from "../../comments/authors.mts";
3
+ import type { MinimizeCommentsPolicy } from "../../config/load.mts";
4
+ export declare function classifyReviewSummaries(summaries: {
5
+ firstLook: Review[];
6
+ seen: Review[];
7
+ edited: Review[];
8
+ }, approvals: Review[], minimizeApprovals: boolean, minimizeComments?: MinimizeCommentsPolicy | undefined, botUsernames?: NormalizedBotUsernames, unresolvedThreads?: ReviewThread[], ruleAutoResolveIds?: string[]): {
9
+ minimizeIds: string[];
10
+ selfMinimizeIds: string[];
11
+ firstLookSummaries: Review[];
12
+ editedSummaries: Review[];
13
+ surfacedApprovals: Review[];
14
+ };
15
+ export declare function buildResolveCommand(threads: AgentThread[], resolutionOnlyThreads: ReviewThread[], allCommentIds: string[], reviews: Review[], checks: AgentCheck[], prNumber: number, botUsernames?: NormalizedBotUsernames, ruleAutoResolveThreadIds?: string[]): {
16
+ resolveCommand: ResolveCommand;
17
+ resolveOnlyCommand?: ResolveCommand;
18
+ };
@@ -17,7 +17,7 @@ export function classifyReviewSummaries(summaries, approvals, minimizeApprovals,
17
17
  const eligible = (r) => shouldMinimizeAuthor(r.authorType, minimizeComments, r.author, botUsernames) &&
18
18
  !blockedReviewIds.has(r.id);
19
19
  // First-look summaries still need one tick to surface their body to the agent,
20
- // so their minimize IDs ride in the agent-facing resolve command. Seen summaries
20
+ // so their minimize IDs ride in the agent-facing apply command. Seen summaries
21
21
  // (already surfaced in a prior tick) have no new content to show — the CLI
22
22
  // self-minimizes them in-process (selfMinimizeIds) instead of routing a
23
23
  // cosmetic-only mutation through fix_code (issue #313). Edited summaries are
@@ -80,7 +80,7 @@ export function buildResolveCommand(threads, resolutionOnlyThreads, allCommentId
80
80
  if (hasMessageMutations && hasResolveOrMinimize) {
81
81
  // Split: message-bearing mutations (replies + dismissals) ride in resolveArgv;
82
82
  // resolve/minimize mutations go in resolveOnlyArgv so they can run without SHA or message.
83
- const resolveArgv = buildPrShepherdCommand(["resolve", String(prNumber)]).argv;
83
+ const resolveArgv = buildPrShepherdCommand(["apply", "review", String(prNumber)]).argv;
84
84
  if (replyThreadIds.length > 0) {
85
85
  resolveArgv.push("--reply-thread-ids", replyThreadIds.join(","));
86
86
  }
@@ -99,7 +99,7 @@ export function buildResolveCommand(threads, resolutionOnlyThreads, allCommentId
99
99
  ...(hasDismiss ? { dismissReviewIds } : undefined),
100
100
  hasMutations: true,
101
101
  };
102
- const resolveOnlyArgv = buildPrShepherdCommand(["resolve", String(prNumber)]).argv;
102
+ const resolveOnlyArgv = buildPrShepherdCommand(["apply", "review", String(prNumber)]).argv;
103
103
  if (resolveThreadIds.length > 0) {
104
104
  resolveOnlyArgv.push("--resolve-thread-ids", resolveThreadIds.join(","));
105
105
  }
@@ -116,7 +116,7 @@ export function buildResolveCommand(threads, resolutionOnlyThreads, allCommentId
116
116
  return { resolveCommand, resolveOnlyCommand };
117
117
  }
118
118
  // Single command: all mutations combined (or only one category present).
119
- const argv = buildPrShepherdCommand(["resolve", String(prNumber)]).argv;
119
+ const argv = buildPrShepherdCommand(["apply", "review", String(prNumber)]).argv;
120
120
  if (replyThreadIds.length > 0) {
121
121
  argv.push("--reply-thread-ids", replyThreadIds.join(","));
122
122
  argv.push("--message", "$DISMISS_MESSAGE");
@@ -0,0 +1,31 @@
1
+ import type { EscalateDetails, EscalateTrigger, ReviewThread } from "../../types.mts";
2
+ interface EscalateCheck {
3
+ triggers: EscalateTrigger[];
4
+ thrashHistory?: EscalateDetails["thrashHistory"];
5
+ }
6
+ export declare function checkEscalateTriggers(actionableThreads: ReviewThread[], threadAttempts: Record<string, number>): EscalateCheck;
7
+ interface BaseBranchLookup {
8
+ branch: string;
9
+ /** True when we could not confirm the branch name from GitHub. Callers must
10
+ * escalate rather than emitting a rebase against a potentially-wrong base. */
11
+ isFallback: boolean;
12
+ /** Populated when `isFallback`; one-line reason shown in escalate output. */
13
+ failureReason?: string;
14
+ }
15
+ /**
16
+ * Validate the base branch name from the GraphQL batch (`report.baseBranch`)
17
+ * and fall back safely if it's missing/unsafe. The branch is interpolated into
18
+ * shell commands by `buildFixInstructions`, so we reject anything outside
19
+ * `[A-Za-z0-9._/-]` to prevent shell injection.
20
+ */
21
+ /**
22
+ * Render a seconds count as an approximate human duration: "8 seconds" below one minute,
23
+ * otherwise whole minutes ("166 minutes", "60 minutes") — matching the precision the
24
+ * generic stall timer has always reported, just adding a seconds tier for sub-minute ages
25
+ * instead of always flooring to "0 minutes".
26
+ */
27
+ export declare function formatDurationApprox(seconds: number): string;
28
+ export declare function validateBaseBranch(raw: string): BaseBranchLookup;
29
+ export declare function buildEscalateHumanMessage(escalate: Omit<EscalateDetails, "humanMessage">, pr: number): string;
30
+ export declare function buildEscalateSuggestion(triggers: EscalateTrigger[], detail?: string): string;
31
+ export {};
@@ -1,4 +1,7 @@
1
1
  import { loadConfig } from "../../config/load.mjs";
2
+ function renderEscalateAuthor(item) {
3
+ return [`@${item.author}`, item.authorType, item.authorAssociation].filter(Boolean).join(" · ");
4
+ }
2
5
  export function checkEscalateTriggers(actionableThreads, threadAttempts) {
3
6
  const triggers = [];
4
7
  const maxAttempts = loadConfig().iterate.fixAttemptsPerThread;
@@ -85,21 +88,21 @@ export function buildEscalateHumanMessage(escalate, pr) {
85
88
  lines.push("");
86
89
  for (const t of escalate.unresolvedThreads) {
87
90
  const loc = t.path ? `\`${t.path}:${t.line ?? "?"}\`` : "(no location)";
88
- lines.push(`- thread \`${t.id}\` — ${loc} (@${t.author}):`);
91
+ lines.push(`- thread \`${t.id}\` — ${loc} (${renderEscalateAuthor(t)}):`);
89
92
  lines.push("");
90
93
  for (const bodyLine of t.body.split("\n"))
91
94
  lines.push(` > ${bodyLine}`);
92
95
  lines.push("");
93
96
  }
94
97
  for (const r of escalate.changesRequestedReviews) {
95
- lines.push(`- review \`${r.id}\` (@${r.author}):`);
98
+ lines.push(`- review \`${r.id}\` (${renderEscalateAuthor(r)}):`);
96
99
  lines.push("");
97
100
  for (const bodyLine of r.body.split("\n"))
98
101
  lines.push(` > ${bodyLine}`);
99
102
  lines.push("");
100
103
  }
101
104
  for (const c of escalate.ambiguousComments) {
102
- lines.push(`- comment \`${c.id}\` (@${c.author}):`);
105
+ lines.push(`- comment \`${c.id}\` (${renderEscalateAuthor(c)}):`);
103
106
  lines.push("");
104
107
  for (const bodyLine of c.body.split("\n"))
105
108
  lines.push(` > ${bodyLine}`);
@@ -137,7 +140,7 @@ export function buildEscalateSuggestion(triggers, detail) {
137
140
  }
138
141
  if (triggers.includes("bot-cr-not-dismissed")) {
139
142
  const ids = detail ? ` (review IDs: ${detail})` : "";
140
- return `Bot CHANGES_REQUESTED review(s) remained undismissed past the stall window${ids}. The agent likely dropped \`--dismiss-review-ids\` from a prior resolve command. Dismiss the review(s) manually (or re-run resolve with the IDs) to unblock the PR.`;
143
+ return `Bot CHANGES_REQUESTED review(s) remained undismissed past the stall window${ids}. The agent likely dropped \`--dismiss-review-ids\` from a prior apply command. Dismiss the review(s) manually (or re-run \`pr-shepherd apply review\` with the IDs) to unblock the PR.`;
141
144
  }
142
145
  return "Ambiguous state — automated handling cannot proceed safely. Inspect the PR and act manually.";
143
146
  }
@@ -0,0 +1,25 @@
1
+ import type { IterateCommandOptions, IterateResult, IterateResultBase, Review, ShepherdReport } from "../../types.mts";
2
+ import type { NormalizedBotUsernames } from "../../comments/authors.mts";
3
+ interface HandleFixCodeContext {
4
+ base: IterateResultBase;
5
+ report: ShepherdReport;
6
+ opts: IterateCommandOptions;
7
+ headSha: string;
8
+ stallKey: {
9
+ owner: string;
10
+ repo: string;
11
+ pr: number;
12
+ };
13
+ prNumber: number;
14
+ stallTimeoutSeconds: number;
15
+ repoOwner: string;
16
+ repoName: string;
17
+ reviewSummaryIds: string[];
18
+ firstLookSummaries: Review[];
19
+ editedSummaries: Review[];
20
+ surfacedApprovals: Review[];
21
+ botUsernames: NormalizedBotUsernames;
22
+ ruleAutoResolveThreadIds?: string[];
23
+ }
24
+ export declare function handleFixCode(ctx: HandleFixCodeContext): Promise<IterateResult>;
25
+ export {};
@@ -166,7 +166,7 @@ export async function handleFixCode(ctx) {
166
166
  cancelled,
167
167
  };
168
168
  const result = await applyStallGuard(stallKey, stallTimeoutSeconds, headSha, base, prNumber, prospectiveResult, report, reviewSummaryIds);
169
- if (result.action === "fix_code") {
169
+ if (result.action === "fix_code" && opts.persistSeen !== false) {
170
170
  await Promise.allSettled(result.fix.checks.flatMap((ch) => (ch.annotations ?? []).map((a) => markSeen(stallKey, a.id, annotationMarkerBody(a)))));
171
171
  }
172
172
  return result;
@@ -0,0 +1,14 @@
1
+ import type { ActiveCheck, IterateResult, IterateResultBase, IterateResultSummary, RelevantCheck, ShepherdReport } from "../../types.mts";
2
+ export { buildAutoCancelRunIdsWithOptions, buildInProgressRunIds, buildRunProtection, } from "./reruns.mts";
3
+ export declare function buildSummary(report: ShepherdReport): IterateResultSummary;
4
+ /** Non-blocking check-name lists (ignored/superseded), omitted from the result when empty. */
5
+ export declare function buildSuppressedCheckFields(report: ShepherdReport): Pick<IterateResultBase, "ignoredNames" | "supersededNames">;
6
+ /** Build the `cancel` result for a merged/closed PR — caller has already updated ready-delay/stall state. */
7
+ export declare function buildTerminalCancelResult(report: ShepherdReport): IterateResult;
8
+ /** Build completed, non-skipped checks relevant to PR readiness. */
9
+ export declare function buildRelevantChecks(report: ShepherdReport): RelevantCheck[];
10
+ export declare function buildActiveChecks(report: ShepherdReport): ActiveCheck[];
11
+ export declare function tryCancelRun(runId: string, owner: string, repo: string): Promise<string | null>;
12
+ export declare function getCurrentHeadSha(): Promise<string | null>;
13
+ export declare function buildWaitLog(base: IterateResultBase): string;
14
+ export declare function blockedCancelNote(base: IterateResultBase): string;
@@ -1,6 +1,8 @@
1
1
  import { execFile as execFileCb } from "node:child_process";
2
2
  import { promisify } from "node:util";
3
3
  import { rest } from "../../github/http.mjs";
4
+ import { getExecutionCwd } from "../../execution-context.mjs";
5
+ import { blockedReasonFromRequirements } from "../../merge-status/requirements-format.mjs";
4
6
  const execFile = promisify(execFileCb);
5
7
  export { buildAutoCancelRunIdsWithOptions, buildInProgressRunIds, buildRunProtection, } from "./reruns.mjs";
6
8
  export function buildSummary(report) {
@@ -39,6 +41,7 @@ export function buildTerminalCancelResult(report) {
39
41
  summary: buildSummary(report),
40
42
  baseBranch: report.baseBranch,
41
43
  branchProtection: report.branchProtection,
44
+ mergeRequirements: report.mergeStatus.mergeRequirements,
42
45
  checks: buildRelevantChecks(report),
43
46
  inProgressChecks: buildActiveChecks(report),
44
47
  ...buildSuppressedCheckFields(report),
@@ -114,7 +117,9 @@ export async function tryCancelRun(runId, owner, repo) {
114
117
  }
115
118
  export async function getCurrentHeadSha() {
116
119
  try {
117
- const { stdout } = await execFile("git", ["rev-parse", "HEAD"]);
120
+ const { stdout } = await execFile("git", ["rev-parse", "HEAD"], {
121
+ cwd: getExecutionCwd(),
122
+ });
118
123
  return stdout.trim();
119
124
  }
120
125
  catch {
@@ -131,14 +136,15 @@ export function buildWaitLog(base) {
131
136
  .join(", ")}`);
132
137
  }
133
138
  switch (base.mergeStatus) {
134
- case "BLOCKED":
135
- if (base.reviewDecision === "REVIEW_REQUIRED")
136
- parts.push("awaiting human review");
137
- else if (base.reviewDecision === "APPROVED")
138
- parts.push("awaiting additional approvals");
139
- else
139
+ case "BLOCKED": {
140
+ const fromRules = blockedReasonFromRequirements(base.mergeRequirements);
141
+ if (fromRules)
142
+ parts.push(fromRules);
143
+ else if (!base.blockingBotReviewInProgress) {
140
144
  parts.push("awaiting human review or branch protection");
145
+ }
141
146
  break;
147
+ }
142
148
  case "BEHIND":
143
149
  parts.push("branch is behind base");
144
150
  break;
@@ -154,3 +160,9 @@ export function buildWaitLog(base) {
154
160
  }
155
161
  return parts.join(" — ");
156
162
  }
163
+ export function blockedCancelNote(base) {
164
+ if (base.mergeStatus !== "BLOCKED")
165
+ return "has been ready for review";
166
+ const fromRules = blockedReasonFromRequirements(base.mergeRequirements);
167
+ return fromRules ? `is ${fromRules}` : "is awaiting human review or branch protection resolution";
168
+ }
@@ -0,0 +1,2 @@
1
+ import type { IterateCommandOptions, IterateResult } from "../../types.mts";
2
+ export declare function runIterate(opts: IterateCommandOptions): Promise<IterateResult>;
@@ -5,7 +5,7 @@ import { graphql } from "../../github/http.mjs";
5
5
  import { MARK_PR_READY_MUTATION } from "../../github/queries.mjs";
6
6
  import { loadConfig } from "../../config/load.mjs";
7
7
  import { EXIT, ShepherdError } from "../../exit-codes.mjs";
8
- import { getCurrentHeadSha, buildSummary, buildRelevantChecks, buildActiveChecks, buildWaitLog, buildSuppressedCheckFields, buildTerminalCancelResult, } from "./helpers.mjs";
8
+ import { getCurrentHeadSha, buildSummary, buildRelevantChecks, buildActiveChecks, buildWaitLog, buildSuppressedCheckFields, buildTerminalCancelResult, blockedCancelNote, } from "./helpers.mjs";
9
9
  import { classifyReviewSummaries } from "./classify.mjs";
10
10
  import { applyStallGuard } from "./stall.mjs";
11
11
  import { clearStallState } from "../../state/iterate-stall.mjs";
@@ -25,7 +25,10 @@ export async function runIterate(opts) {
25
25
  const report = await runCheck({
26
26
  ...opts,
27
27
  prNumber,
28
- autoResolve: config.actions.autoResolveOutdated,
28
+ // Outdated Shepherd-visible threads are always resolved by iterate. The
29
+ // old actions.autoResolveOutdated switch is retained only as an ignored
30
+ // compatibility key in the config loader.
31
+ autoResolve: true,
29
32
  autoMinimizeSuppressed: config.actions.autoMinimizeSuppressed,
30
33
  });
31
34
  const [repoOwner, repoName] = report.repo.split("/");
@@ -48,7 +51,7 @@ export async function runIterate(opts) {
48
51
  // GitHub can still return a null/error/rate-limit result per ID without
49
52
  // throwing (autoMinimizeComments reports this via `errors`, not a rejection);
50
53
  // any ID it did not confirm minimized falls back into the agent-facing set so
51
- // the resolve command remains a working fallback instead of silently dropping it.
54
+ // the apply command remains a working fallback instead of silently dropping it.
52
55
  let reviewSummaryIds = minimizeIds;
53
56
  if (selfMinimizeIds.length > 0) {
54
57
  const { minimized } = await autoMinimizeComments(selfMinimizeIds);
@@ -87,6 +90,7 @@ export async function runIterate(opts) {
87
90
  summary: buildSummary(report),
88
91
  baseBranch: report.baseBranch,
89
92
  branchProtection: report.branchProtection,
93
+ mergeRequirements: report.mergeStatus.mergeRequirements,
90
94
  checks: buildRelevantChecks(report),
91
95
  inProgressChecks: buildActiveChecks(report),
92
96
  ...buildSuppressedCheckFields(report),
@@ -94,15 +98,7 @@ export async function runIterate(opts) {
94
98
  };
95
99
  if (readyState.shouldCancel) {
96
100
  await clearStallState(stallKey);
97
- let cancelNote;
98
- if (base.mergeStatus !== "BLOCKED")
99
- cancelNote = "has been ready for review";
100
- else if (base.reviewDecision === "REVIEW_REQUIRED")
101
- cancelNote = "is awaiting human review";
102
- else if (base.reviewDecision === "APPROVED")
103
- cancelNote = "is awaiting additional approvals";
104
- else
105
- cancelNote = "is awaiting human review or branch protection resolution";
101
+ const cancelNote = blockedCancelNote(base);
106
102
  return {
107
103
  ...base,
108
104
  action: "cancel",
@@ -0,0 +1,5 @@
1
+ import type { AgentThread, AgentComment, AgentCheck, Review, ResolveCommand, FirstLookThread, FirstLookComment, ReviewThread } from "../../types.mts";
2
+ /** Render a resolve command as a shell snippet. Appends `--require-sha "$HEAD_SHA"` when set. */
3
+ export declare function renderResolveCommand(rc: ResolveCommand): string;
4
+ export declare function buildFixInstructions(threads: AgentThread[], actionableComments: AgentComment[], checks: AgentCheck[], changesRequestedReviews: Review[], baseBranch: string, resolveCommand: ResolveCommand, hasConflicts: boolean, prNumber: number, cancelledCount: number, firstLookThreads?: FirstLookThread[], firstLookComments?: FirstLookComment[], firstLookSummaries?: Review[], editedSummaries?: Review[], inProgressRunIds?: string[], resolutionOnlyThreads?: ReviewThread[], resolveOnlyCommand?: ResolveCommand, behindBaseHint?: string, // iterate.behindBaseHint — see buildBehindBaseHintInstruction
5
+ isBehind?: boolean): string[];
@@ -32,14 +32,16 @@ isBehind = false) {
32
32
  if (changesRequestedReviews.length > 0)
33
33
  actionableSections.push("`## Changes-requested reviews`");
34
34
  const sectionRef = actionableSections.length > 0 ? `under ${actionableSections.join(", ")}` : "above";
35
- const resolveClause = resolveCommand.hasMutations ? ", then run the `resolve:` command" : "";
35
+ const resolveClause = resolveCommand.hasMutations
36
+ ? ", then run the `apply review:` command"
37
+ : "";
36
38
  if (hasConflicts) {
37
39
  // Conflicts make push mandatory regardless of whether code edits are needed.
38
40
  instructions.push(`The branch has merge conflicts that must be resolved before merging (see \`**branch**\` above). Apply any code edits for items ${sectionRef}, then commit and push${resolveClause}.`);
39
41
  }
40
42
  else {
41
43
  const skipClause = resolveCommand.hasMutations
42
- ? "skip the commit/push and run the `resolve:` command"
44
+ ? "skip the commit/push and run the `apply review:` command"
43
45
  : "no push is needed";
44
46
  instructions.push(`Decide for each item ${sectionRef} whether a code change is warranted. **If any code changes are needed:** apply edits, commit, push${resolveClause}. **If no code changes are needed:** ${skipClause}.`);
45
47
  }
@@ -59,12 +61,12 @@ isBehind = false) {
59
61
  // is only accurate when threads are present.
60
62
  const filesRef = threads.length > 0 ? "each file referenced above" : "the relevant files";
61
63
  const suggestionFallback = hasSuggestions
62
- ? ` When applying a \`[suggestion]\` thread manually (e.g. after a failed \`commit-suggestion\` run), replace the exact line range shown in the heading (\`path:startLine-endLine\`) with the replacement shown in its \`Replaces lines …\` block verbatim — an empty replacement deletes those lines, a single blank line replaces the range with one blank line.`
64
+ ? ` When applying a \`[suggestion]\` thread manually (e.g. after a failed \`build-suggestion-patch\` run), replace the exact line range shown in the heading (\`path:startLine-endLine\`) with the replacement shown in its \`Replaces lines …\` block verbatim — an empty replacement deletes those lines, a single blank line replaces the range with one blank line.`
63
65
  : "";
64
66
  instructions.push(`Apply code fixes: read and edit ${filesRef}.${suggestionFallback}`);
65
67
  }
66
68
  if (resolutionOnlyThreads.length > 0) {
67
- instructions.push(`Review the threads under \`## Review threads to resolve\`. Human-authored threads are replied to by the \`resolve:\` command shown below; Shepherd does not resolve them. Bot/non-human threads are included in \`--resolve-thread-ids\`.`);
69
+ instructions.push(`Review the threads under \`## Review threads to resolve\`. Human-authored threads are replied to by the \`apply review:\` command shown below; Shepherd does not resolve them. Bot/non-human threads are included in \`--resolve-thread-ids\`.`);
68
70
  }
69
71
  instructions.push(...buildFailingCheckInstructions(checks));
70
72
  if (checks.some((c) => (c.annotations?.length ?? 0) > 0)) {
@@ -74,7 +76,7 @@ isBehind = false) {
74
76
  const staleClause = buildCrStaleClause(changesRequestedReviews);
75
77
  instructions.push(`For each bullet under \`## Changes-requested reviews\` above: read the review body and apply the requested changes.${staleClause}`);
76
78
  if ((resolveCommand.dismissReviewIds?.length ?? 0) > 0)
77
- instructions.push(`Pass every ID listed in \`--dismiss-review-ids\` to the \`resolve:\` command verbatim — these are bot/non-human CR reviews that the agent (not the author) must dismiss. Dropping an ID leaves the PR in \`CHANGES_REQUESTED\` state; the next tick re-surfaces it as \`[pending dismissal]\` and an unattended bot CR escalates after \`iterate.stallTimeoutMinutes\`.`);
79
+ instructions.push(`Pass every ID listed in \`--dismiss-review-ids\` to the \`apply review:\` command verbatim — these are bot/non-human CR reviews that the agent (not the author) must dismiss. Dropping an ID leaves the PR in \`CHANGES_REQUESTED\` state; the next tick re-surfaces it as \`[pending dismissal]\` and an unattended bot CR escalates after \`iterate.stallTimeoutMinutes\`.`);
78
80
  }
79
81
  if (resolveOnlyCommand?.hasMutations)
80
82
  instructions.push(`Run the \`resolve-only:\` command shown above — no substitutions needed.`);
@@ -84,7 +86,7 @@ isBehind = false) {
84
86
  }
85
87
  const firstLookTotal = firstLookThreads.length + firstLookComments.length;
86
88
  if (firstLookTotal > 0) {
87
- instructions.push(`Items in \`## First-look items\` are shown so you can acknowledge their current status before acting. If a first-look thread also appears under \`## Review threads to resolve\`, its ID is already included in the \`resolve:\` command; otherwise do not pass first-look-only IDs to mutation flags.`);
89
+ instructions.push(`Items in \`## First-look items\` are shown so you can acknowledge their current status before acting. If a first-look thread also appears under \`## Review threads to resolve\`, its ID is already included in the \`apply review:\` command; otherwise do not pass first-look-only IDs to mutation flags.`);
88
90
  }
89
91
  if (firstLookSummaries.length > 0)
90
92
  instructions.push(SHEPHERD_JOURNAL_FIRST_LOOK_GUIDANCE);
@@ -0,0 +1,20 @@
1
+ import type { ShepherdReport } from "../../types.mts";
2
+ import type { ProtectedRun } from "../../types/protected-run.mts";
3
+ type FailingCheck = ShepherdReport["checks"]["failing"][number];
4
+ type InProgressCheck = ShepherdReport["checks"]["inProgress"][number];
5
+ type CancellableCheck = FailingCheck | InProgressCheck;
6
+ interface RunProtection {
7
+ protectedRunIds: Set<string>;
8
+ protectedRuns: ProtectedRun[];
9
+ }
10
+ interface BuildRunIdOptions {
11
+ protectedRunIds?: Set<string>;
12
+ }
13
+ export declare function buildAutoCancelRunIdsWithOptions(report: ShepherdReport, opts?: BuildRunIdOptions): string[];
14
+ interface BuildInProgressRunIdOptions {
15
+ suppressProtectedFreshReruns?: boolean;
16
+ protectedRunIds?: Set<string>;
17
+ }
18
+ export declare function buildInProgressRunIds(report: ShepherdReport, cancelledSet: Set<string>, opts?: BuildInProgressRunIdOptions): string[];
19
+ export declare function buildRunProtection(checks: CancellableCheck[], patterns?: string[]): RunProtection;
20
+ export {};
@@ -0,0 +1,6 @@
1
+ import type { IterateResult, IterateResultBase, ShepherdReport } from "../../types.mts";
2
+ export declare function applyStallGuard(stallKey: {
3
+ owner: string;
4
+ repo: string;
5
+ pr: number;
6
+ }, stallTimeoutSeconds: number, headSha: string, base: IterateResultBase, prNumber: number, prospectiveResult: IterateResult, report: ShepherdReport, reviewSummaryIds: string[]): Promise<IterateResult>;
@@ -0,0 +1,14 @@
1
+ export interface RunJournalOptions {
2
+ prNumber: number | undefined;
3
+ rawItem: string;
4
+ dryRun: boolean;
5
+ }
6
+ export interface JournalResult {
7
+ prNumber: number;
8
+ mutated: boolean;
9
+ sectionExisted: boolean;
10
+ dryRun: boolean;
11
+ previewBody?: string;
12
+ }
13
+ /** @deprecated Hidden implementation for standalone `journal`; use `apply journal`. */
14
+ export declare function runJournal(opts: RunJournalOptions): Promise<JournalResult>;
@@ -1,5 +1,6 @@
1
1
  import { getRepoInfo, getPullRequestBody, updatePullRequestBody, getCurrentPrNumber, } from "../../github/client.mjs";
2
2
  import { validateJournalItem, appendJournalItem } from "./transform.mjs";
3
+ /** @deprecated Hidden implementation for standalone `journal`; use `apply journal`. */
3
4
  export async function runJournal(opts) {
4
5
  const validation = validateJournalItem(opts.rawItem);
5
6
  if (!validation.ok) {
@@ -0,0 +1,22 @@
1
+ export interface AppendResult {
2
+ body: string;
3
+ mutated: boolean;
4
+ sectionExisted: boolean;
5
+ }
6
+ type ValidationOk = {
7
+ ok: true;
8
+ item: string;
9
+ };
10
+ type ValidationError = {
11
+ ok: false;
12
+ error: string;
13
+ };
14
+ export type ValidationResult = ValidationOk | ValidationError;
15
+ /** Validates that the input is a properly formed markdown list item. */
16
+ export declare function validateJournalItem(input: string): ValidationResult;
17
+ /**
18
+ * Appends a validated list item to the ## Shepherd Journal section of a PR body.
19
+ * Creates the section at the end if absent. Skips if the exact item is already present (idempotent).
20
+ */
21
+ export declare function appendJournalItem(body: string, item: string): AppendResult;
22
+ export {};
@@ -0,0 +1,5 @@
1
+ interface LogFileResult {
2
+ path: string;
3
+ }
4
+ export declare function runLogFile(): Promise<LogFileResult>;
5
+ export {};