pr-shepherd 0.33.0 → 0.35.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 (198) hide show
  1. package/.claude-plugin/plugin.json +3 -2
  2. package/.grok-plugin/marketplace.json +17 -0
  3. package/README.md +63 -41
  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/classify/apply.d.mts +18 -0
  11. package/bin/classify/apply.mjs +4 -0
  12. package/bin/classify/loader.d.mts +10 -0
  13. package/bin/classify/types.d.mts +35 -0
  14. package/bin/cli/args.d.mts +18 -0
  15. package/bin/cli/clean-formatter.d.mts +2 -0
  16. package/bin/cli/default-poll.d.mts +2 -0
  17. package/bin/cli/default-poll.mjs +2 -1
  18. package/bin/cli/duration-flag.d.mts +2 -0
  19. package/bin/cli/duration-flag.mjs +5 -4
  20. package/bin/cli/duration.d.mts +13 -0
  21. package/bin/cli/{exit-codes.mjs → duration.mjs} +0 -26
  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 +20 -18
  29. package/bin/cli/help-command-pages.d.mts +231 -0
  30. package/bin/cli/help-command-pages.mjs +116 -72
  31. package/bin/cli/help-iterate-poll-pages.d.mts +4 -0
  32. package/bin/cli/help-iterate-poll-pages.mjs +74 -0
  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 +22 -20
  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-emitter.mjs +2 -2
  40. package/bin/cli/iterate-flags.d.mts +11 -0
  41. package/bin/cli/iterate-flags.mjs +1 -1
  42. package/bin/cli/iterate-formatter.d.mts +19 -0
  43. package/bin/cli/iterate-instructions.d.mts +6 -0
  44. package/bin/cli/iterate-lean.d.mts +12 -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 +13 -25
  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 +3 -3
  56. package/bin/cli/resolve-validators.d.mts +3 -0
  57. package/bin/cli/resolve-validators.mjs +6 -5
  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 +77 -20
  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 +6 -0
  67. package/bin/commands/check.mjs +4 -2
  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 +28 -26
  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/helpers.d.mts +13 -0
  81. package/bin/commands/iterate/helpers.mjs +4 -1
  82. package/bin/commands/iterate/index.d.mts +2 -0
  83. package/bin/commands/iterate/index.mjs +10 -5
  84. package/bin/commands/iterate/render.d.mts +5 -0
  85. package/bin/commands/iterate/render.mjs +8 -6
  86. package/bin/commands/iterate/reruns.d.mts +20 -0
  87. package/bin/commands/iterate/stall.d.mts +6 -0
  88. package/bin/commands/journal/index.d.mts +14 -0
  89. package/bin/commands/journal/index.mjs +1 -0
  90. package/bin/commands/journal/transform.d.mts +22 -0
  91. package/bin/commands/log-file.d.mts +5 -0
  92. package/bin/commands/mark-files-as-viewed.d.mts +26 -0
  93. package/bin/commands/mark-files-as-viewed.mjs +8 -5
  94. package/bin/commands/poll.d.mts +10 -0
  95. package/bin/commands/poll.mjs +1 -0
  96. package/bin/commands/ready-delay.d.mts +29 -0
  97. package/bin/commands/ready-mergeability.d.mts +15 -0
  98. package/bin/commands/resolve-mutate.d.mts +4 -0
  99. package/bin/commands/resolve-mutate.mjs +3 -1
  100. package/bin/commands/resolve.d.mts +4 -0
  101. package/bin/commands/shepherd-journal.d.mts +7 -0
  102. package/bin/commands/shepherd-journal.mjs +2 -2
  103. package/bin/comments/authors.d.mts +14 -0
  104. package/bin/comments/marker.d.mts +2 -0
  105. package/bin/comments/minimize-policy.d.mts +4 -0
  106. package/bin/comments/pending-ops.d.mts +15 -0
  107. package/bin/comments/rate-limit.d.mts +18 -0
  108. package/bin/comments/resolve.d.mts +34 -0
  109. package/bin/comments/resolve.mjs +1 -0
  110. package/bin/comments/review-thread-markers.d.mts +8 -0
  111. package/bin/comments/review-visibility.d.mts +28 -0
  112. package/bin/comments/sha-poll.d.mts +2 -0
  113. package/bin/comments/thread-visibility.d.mts +11 -0
  114. package/bin/comments/visible-comments.d.mts +11 -0
  115. package/bin/config/load.d.mts +60 -0
  116. package/bin/config/load.mjs +72 -1
  117. package/bin/config.json +0 -2
  118. package/bin/execution-context.d.mts +9 -0
  119. package/bin/execution-context.mjs +19 -0
  120. package/bin/exit-codes.d.mts +51 -0
  121. package/bin/exit-codes.mjs +74 -0
  122. package/bin/github/activity.d.mts +3 -0
  123. package/bin/github/activity.mjs +7 -0
  124. package/bin/github/batch-parser-helpers.d.mts +22 -0
  125. package/bin/github/batch-parsers.d.mts +3 -0
  126. package/bin/github/batch-parsers.mjs +6 -0
  127. package/bin/github/batch-raw-types.d.mts +207 -0
  128. package/bin/github/batch-response.d.mts +4 -0
  129. package/bin/github/batch-response.mjs +8 -3
  130. package/bin/github/batch.d.mts +22 -0
  131. package/bin/github/branch-protection.d.mts +3 -0
  132. package/bin/github/check-annotations.d.mts +2 -0
  133. package/bin/github/client.d.mts +46 -0
  134. package/bin/github/client.mjs +7 -2
  135. package/bin/github/errors.d.mts +25 -0
  136. package/bin/github/errors.mjs +26 -2
  137. package/bin/github/gql/batch-pr.gql +5 -0
  138. package/bin/github/gql/review-thread-comments.gql +1 -0
  139. package/bin/github/graphql-http.d.mts +19 -0
  140. package/bin/github/graphql-response.d.mts +7 -0
  141. package/bin/github/graphql-response.mjs +5 -0
  142. package/bin/github/http-auth.d.mts +4 -0
  143. package/bin/github/http-auth.mjs +2 -1
  144. package/bin/github/http-request.d.mts +7 -0
  145. package/bin/github/http-utils.d.mts +11 -0
  146. package/bin/github/http.d.mts +5 -0
  147. package/bin/github/pagination.d.mts +45 -0
  148. package/bin/github/queries.d.mts +24 -0
  149. package/bin/github/rest-http.d.mts +2 -0
  150. package/bin/github/rest-http.mjs +19 -5
  151. package/bin/github/thread-comments.d.mts +2 -0
  152. package/bin/index.d.mts +10 -0
  153. package/bin/index.mjs +3 -2
  154. package/bin/log/log-file.d.mts +28 -0
  155. package/bin/log/session.d.mts +31 -0
  156. package/bin/log/setup.d.mts +6 -0
  157. package/bin/mcp/index.d.mts +5 -0
  158. package/bin/mcp/index.mjs +8 -0
  159. package/bin/mcp/server.d.mts +8 -0
  160. package/bin/mcp/server.mjs +157 -0
  161. package/bin/mcp-stdio.d.mts +2 -0
  162. package/bin/mcp-stdio.mjs +7 -0
  163. package/bin/merge-status/derive.d.mts +19 -0
  164. package/bin/reporters/agent.d.mts +23 -0
  165. package/bin/reporters/agent.mjs +3 -0
  166. package/bin/state/base.d.mts +1 -0
  167. package/bin/state/bot-cr-seen.d.mts +51 -0
  168. package/bin/state/bot-cr-seen.mjs +1 -1
  169. package/bin/state/fix-attempts.d.mts +27 -0
  170. package/bin/state/iterate-stall.d.mts +27 -0
  171. package/bin/state/seen-comments.d.mts +62 -0
  172. package/bin/suggestions/extract.d.mts +8 -0
  173. package/bin/suggestions/parse.d.mts +48 -0
  174. package/bin/suggestions/patch.d.mts +14 -0
  175. package/bin/threads/transcript.d.mts +14 -0
  176. package/bin/threads/transcript.mjs +4 -0
  177. package/bin/types/activity.d.mts +30 -0
  178. package/bin/types/agent-thread.d.mts +9 -0
  179. package/bin/types/check-annotations.d.mts +14 -0
  180. package/bin/types/check-classification.d.mts +19 -0
  181. package/bin/types/github.d.mts +139 -0
  182. package/bin/types/iterate.d.mts +157 -0
  183. package/bin/types/protected-run.d.mts +6 -0
  184. package/bin/types/report.d.mts +176 -0
  185. package/bin/types/review-thread.d.mts +12 -0
  186. package/bin/types.d.mts +9 -0
  187. package/bin/util/markdown.d.mts +1 -0
  188. package/bin/util/path-segment.d.mts +2 -0
  189. package/bin/util/sleep.d.mts +1 -0
  190. package/bin/util/worktree.d.mts +9 -0
  191. package/bin/util/worktree.mjs +4 -1
  192. package/package.json +51 -37
  193. package/plugins/pr-shepherd/.codex-plugin/plugin.json +3 -2
  194. package/plugins/pr-shepherd/.codex.mcp.json +8 -0
  195. package/plugins/pr-shepherd/.mcp.json +6 -0
  196. package/plugins/pr-shepherd/skills/mark-files-as-viewed/SKILL.md +5 -19
  197. package/plugins/pr-shepherd/skills/pr-shepherd/SKILL.md +6 -15
  198. package/src/classify/types.mts +12 -0
package/bin/cli/help.mjs CHANGED
@@ -5,6 +5,23 @@ export const USAGE = {
5
5
  top: TOP_USAGE,
6
6
  ...COMMAND_USAGE,
7
7
  };
8
+ /** Resolve help keys for nested public commands before any command I/O. */
9
+ export function helpKeyForArgs(args) {
10
+ if (args[0] === "apply" && args[1] === "review")
11
+ return "apply review";
12
+ if (args[0] === "apply" && args[1] === "files")
13
+ return "apply files";
14
+ if (args[0] === "apply" && args[1] === "journal")
15
+ return "apply journal";
16
+ if (args[0] === "admin" && args[1] === "clean")
17
+ return "admin clean";
18
+ if (args[0] === "admin" && args[1] === "log-file")
19
+ return "admin log-file";
20
+ if (args[0] != null && args[0] in USAGE) {
21
+ return args[0];
22
+ }
23
+ return "top";
24
+ }
8
25
  /** Prints usage for `key` to stdout and returns true if `--help` or `-h` is in args. */
9
26
  export function maybePrintHelp(args, key) {
10
27
  if (!hasFlag(args, "--help") && !hasFlag(args, "-h"))
@@ -0,0 +1,8 @@
1
+ import type { IterateResult } from "../types.mts";
2
+ interface EmitIterateResultOpts {
3
+ format: "text" | "json";
4
+ verbose: boolean;
5
+ readyDelaySuffix?: string;
6
+ }
7
+ export declare function emitIterateResult(result: IterateResult, opts: EmitIterateResultOpts): void;
8
+ export {};
@@ -1,4 +1,4 @@
1
- import { iterateActionToExitCode } from "./exit-codes.mjs";
1
+ import { iterateResultToExitCode } from "../exit-codes.mjs";
2
2
  import { formatIterateResult, projectIterateLean, projectIterateVerbose } from "./formatters.mjs";
3
3
  export function emitIterateResult(result, opts) {
4
4
  const projectionOpts = {
@@ -14,5 +14,5 @@ export function emitIterateResult(result, opts) {
14
14
  const text = formatIterateResult(result, { verbose: opts.verbose, ...projectionOpts });
15
15
  process.stdout.write(`${text}\n`);
16
16
  }
17
- process.exitCode = iterateActionToExitCode(result.action);
17
+ process.exitCode = iterateResultToExitCode(result);
18
18
  }
@@ -0,0 +1,11 @@
1
+ import type { loadConfig } from "../config/load.mts";
2
+ interface IterateFlags {
3
+ readyDelaySuffix: string | undefined | null;
4
+ readyDelaySeconds: number;
5
+ stallTimeoutSuffix: string | undefined | null;
6
+ stallTimeoutSeconds: number;
7
+ noAutoMarkReady: boolean;
8
+ noAutoCancelActionable: boolean;
9
+ }
10
+ export declare function parseIterateFlags(extra: string[], cfg: ReturnType<typeof loadConfig>): IterateFlags;
11
+ export {};
@@ -1,5 +1,5 @@
1
1
  import { getFlag, hasFlag } from "./args.mjs";
2
- import { parseDurationToSeconds } from "./exit-codes.mjs";
2
+ import { parseDurationToSeconds } from "./duration.mjs";
3
3
  import { validateSecondsDurationFlag } from "./duration-flag.mjs";
4
4
  // --ready-delay and --stall-timeout are minute-family flags: a bare number means minutes, and 0 is a
5
5
  // valid value (it disables the ready-delay settle window / stall-timeout escalation, respectively).
@@ -0,0 +1,19 @@
1
+ import type { IterateResult } from "../types.mts";
2
+ /**
3
+ * Format an IterateResult as human-readable Markdown.
4
+ *
5
+ * Load-bearing conventions the iterate skill relies on:
6
+ * 1. The H1 heading on line 1 contains `[<ACTION>]` — the action tag identifies
7
+ * the output for logging and validation. Behavior is driven by `## Instructions`,
8
+ * not by dispatching on the tag.
9
+ * 2. `[FIX_CODE]` wraps the `resolve` command under `## Post-fix push` in
10
+ * backticks — the skill extracts the backticked content for execution.
11
+ * 3. Every action ends with a `## Instructions` section — numbered `1.`, `2.`, … —
12
+ * that tells the agent exactly what to do with this output. The section is
13
+ * unconditional: every action, every variant, always emits at least one step.
14
+ * The skill simply follows those steps; it does not need its own dispatch table.
15
+ */
16
+ export declare function formatIterateResult(result: IterateResult, opts?: {
17
+ verbose?: boolean;
18
+ readyDelaySuffix?: string;
19
+ }): string;
@@ -0,0 +1,6 @@
1
+ import type { IterateResult } from "../types.mts";
2
+ export declare function buildSimpleIterateInstructions(result: Exclude<IterateResult, {
3
+ action: "fix_code";
4
+ }>): string[];
5
+ export declare function adaptIterateLog(log: string): string;
6
+ export declare function numberInstructions(instructions: string[]): string;
@@ -0,0 +1,12 @@
1
+ import type { IterateResult } from "../types.mts";
2
+ interface IterateProjectionOptions {
3
+ readyDelaySuffix?: string;
4
+ }
5
+ /**
6
+ * Project an IterateResult to a lean JSON shape for the default (non-verbose) output.
7
+ * Omits fields that are the trivial default (false, 0, empty) or state-gated fields
8
+ * outside the state where they are meaningful.
9
+ */
10
+ export declare function projectIterateLean(result: IterateResult, opts?: IterateProjectionOptions): unknown;
11
+ export declare function projectIterateVerbose(result: IterateResult, opts?: IterateProjectionOptions): unknown;
12
+ export {};
@@ -0,0 +1,2 @@
1
+ import type { JournalResult } from "../commands/journal/index.mts";
2
+ export declare function formatJournalResult(result: JournalResult): string;
@@ -0,0 +1,15 @@
1
+ export function formatJournalResult(result) {
2
+ if (result.dryRun) {
3
+ const lines = ["Dry run — no body change written."];
4
+ if (result.previewBody !== undefined) {
5
+ lines.push("", result.previewBody);
6
+ }
7
+ return lines.join("\n");
8
+ }
9
+ if (!result.mutated)
10
+ return "No change — entry already present.";
11
+ if (!result.sectionExisted) {
12
+ return `Created ## Shepherd Journal section in PR #${result.prNumber}.`;
13
+ }
14
+ return `Appended to ## Shepherd Journal in PR #${result.prNumber}.`;
15
+ }
@@ -0,0 +1 @@
1
+ export declare function handleJournal(args: string[], command?: "apply journal" | "journal"): Promise<void>;
@@ -1,8 +1,10 @@
1
1
  import { readFile } from "node:fs/promises";
2
+ import { EXIT, errorToExitCode } from "../exit-codes.mjs";
2
3
  import { runJournal } from "../commands/journal/index.mjs";
3
4
  import { getFlag, parsePrNumber } from "./args.mjs";
4
5
  import { USAGE } from "./help.mjs";
5
- export async function handleJournal(args) {
6
+ import { formatJournalResult } from "./journal-formatter.mjs";
7
+ export async function handleJournal(args, command = "apply journal") {
6
8
  for (const a of args) {
7
9
  if (!a.startsWith("--"))
8
10
  continue;
@@ -10,15 +12,15 @@ export async function handleJournal(args) {
10
12
  continue;
11
13
  if (a === "--file" || a.startsWith("--file="))
12
14
  continue;
13
- process.stderr.write(`pr-shepherd: journal: unknown flag: "${a}"\n`);
14
- process.exitCode = 1;
15
+ process.stderr.write(`pr-shepherd: ${command}: unknown flag: "${a}"\n`);
16
+ process.exitCode = EXIT.USAGE;
15
17
  return;
16
18
  }
17
19
  const { prNumber, extra } = parseJournalArgs(args);
18
20
  const filePath = getFlag(args, "--file");
19
21
  if (filePath !== null && extra[0]) {
20
- process.stderr.write(`pr-shepherd: journal: provide the entry as a positional argument or via --file, not both\n`);
21
- process.exitCode = 1;
22
+ process.stderr.write(`pr-shepherd: ${command}: provide the entry as a positional argument or via --file, not both\n`);
23
+ process.exitCode = EXIT.USAGE;
22
24
  return;
23
25
  }
24
26
  let rawItem;
@@ -26,13 +28,13 @@ export async function handleJournal(args) {
26
28
  rawItem = filePath !== null ? await readItemSource(filePath) : extra[0];
27
29
  }
28
30
  catch (e) {
29
- process.stderr.write(`pr-shepherd: journal: ${String(e)}\n`);
30
- process.exitCode = 1;
31
+ process.stderr.write(`pr-shepherd: ${command}: ${String(e)}\n`);
32
+ process.exitCode = EXIT.NOINPUT;
31
33
  return;
32
34
  }
33
35
  if (rawItem === undefined) {
34
- process.stderr.write(`${USAGE.journal}\n`);
35
- process.exitCode = 1;
36
+ process.stderr.write(`${USAGE[command]}\n`);
37
+ process.exitCode = EXIT.USAGE;
36
38
  return;
37
39
  }
38
40
  const dryRun = args.includes("--dry-run");
@@ -48,8 +50,8 @@ export async function handleJournal(args) {
48
50
  }
49
51
  }
50
52
  catch (e) {
51
- process.stderr.write(`pr-shepherd: journal: ${String(e)}\n`);
52
- process.exitCode = 1;
53
+ process.stderr.write(`pr-shepherd: ${command}: ${String(e)}\n`);
54
+ process.exitCode = errorToExitCode(e);
53
55
  }
54
56
  }
55
57
  /** Reads the journal entry from a file, or from stdin when `filePath` is `-`. */
@@ -95,17 +97,3 @@ function parseJournalArgs(args) {
95
97
  }
96
98
  return { prNumber, extra };
97
99
  }
98
- function formatJournalResult(result) {
99
- if (result.dryRun) {
100
- const lines = ["Dry run — no body change written."];
101
- if (result.previewBody !== undefined) {
102
- lines.push("", result.previewBody);
103
- }
104
- return lines.join("\n");
105
- }
106
- if (!result.mutated)
107
- return "No change — entry already present.";
108
- if (!result.sectionExisted)
109
- return `Created ## Shepherd Journal section in PR #${result.prNumber}.`;
110
- return `Appended to ## Shepherd Journal in PR #${result.prNumber}.`;
111
- }
@@ -0,0 +1,76 @@
1
+ import type { AuthorType, CommentAuthorAssociation, SuggestionBlock } from "../types.mts";
2
+ import type { FirstLookThread, FirstLookComment } from "../types/report.mts";
3
+ export declare function renderAuthor(author: string, authorType?: AuthorType, authorAssociation?: CommentAuthorAssociation): string;
4
+ export declare function renderBodyPreview(body: string): string;
5
+ export declare function renderThreadResolutionStatusTag(t: {
6
+ isOutdated?: boolean;
7
+ isMinimized?: boolean;
8
+ }): string;
9
+ interface ThreadBulletInput {
10
+ id: string;
11
+ reviewId?: string;
12
+ url?: string;
13
+ path?: string | null;
14
+ startLine?: number | null;
15
+ line?: number | null;
16
+ author: string;
17
+ authorType?: AuthorType;
18
+ authorAssociation?: CommentAuthorAssociation;
19
+ body: string;
20
+ comments?: Array<{
21
+ id: string;
22
+ author: string;
23
+ authorType?: AuthorType;
24
+ authorAssociation?: CommentAuthorAssociation;
25
+ body: string;
26
+ url: string;
27
+ }>;
28
+ suggestion?: SuggestionBlock;
29
+ edited?: boolean;
30
+ }
31
+ export declare function renderThreadBullet(t: ThreadBulletInput, opts?: {
32
+ statusTag?: string;
33
+ renderSuggestion?: boolean;
34
+ noBody?: boolean;
35
+ suppressEditedMarker?: boolean;
36
+ }): string;
37
+ export declare function renderThreadConversation(t: ThreadBulletInput): string;
38
+ export declare function blockquote(body: string): string;
39
+ export declare function renderCommentBullet(c: {
40
+ id: string;
41
+ url?: string;
42
+ author: string;
43
+ authorType?: AuthorType;
44
+ authorAssociation?: CommentAuthorAssociation;
45
+ body: string;
46
+ }, opts?: {
47
+ statusTag?: string;
48
+ }): string;
49
+ export declare function renderEditedCommentTag(c: {
50
+ edited?: boolean;
51
+ }): string | undefined;
52
+ export declare function renderReviewBullet(r: {
53
+ id: string;
54
+ author: string;
55
+ authorType?: AuthorType;
56
+ authorAssociation?: CommentAuthorAssociation;
57
+ body?: string;
58
+ staleBotCr?: boolean;
59
+ staleReview?: boolean;
60
+ }, opts?: {
61
+ includeBody?: boolean;
62
+ }): string;
63
+ export declare function renderReviewListSection(heading: string, items: {
64
+ id: string;
65
+ author: string;
66
+ authorType?: AuthorType;
67
+ authorAssociation?: CommentAuthorAssociation;
68
+ body?: string;
69
+ }[]): string | null;
70
+ /**
71
+ * Build bullet strings for the `## First-look items` section.
72
+ * Threads that also appear in resolutionOnlyIds have their body suppressed
73
+ * (already shown in `## Review threads to resolve`).
74
+ */
75
+ export declare function buildFirstLookBullets(firstLookThreads: FirstLookThread[], resolutionOnlyIds: Set<string>, firstLookComments: FirstLookComment[]): string[];
76
+ export {};
@@ -1,8 +1,8 @@
1
1
  import { renderLineRange, renderSuggestionBlock } from "./suggestion-renderer.mjs";
2
2
  import { threadComments } from "../threads/transcript.mjs";
3
3
  const BODY_PREVIEW_MAX = 100;
4
- export function renderAuthor(author, authorType) {
5
- return authorType ? `@${author} · ${authorType}` : `@${author}`;
4
+ export function renderAuthor(author, authorType, authorAssociation) {
5
+ return [`@${author}`, authorType, authorAssociation].filter(Boolean).join(" · ");
6
6
  }
7
7
  export function renderBodyPreview(body) {
8
8
  const normalizedBody = body.replace(/\r\n?/g, "\n");
@@ -30,7 +30,7 @@ export function renderThreadBullet(t, opts = {}) {
30
30
  const editedMarker = t.edited && !opts.suppressEditedMarker ? " [edited since first look]" : "";
31
31
  const reviewMarker = t.reviewId ? ` [reviewId=${t.reviewId}]` : "";
32
32
  const statusSuffix = opts.statusTag ? ` ${opts.statusTag}` : "";
33
- const bulletLine = `- \`threadId=${t.id}\`${link} ${loc} (${renderAuthor(t.author, t.authorType)})${reviewMarker}${suggestionMarker}${editedMarker}${statusSuffix}`;
33
+ const bulletLine = `- \`threadId=${t.id}\`${link} ${loc} (${renderAuthor(t.author, t.authorType, t.authorAssociation)})${reviewMarker}${suggestionMarker}${editedMarker}${statusSuffix}`;
34
34
  if (!opts.noBody && (!t.comments || t.comments.length === 0)) {
35
35
  const legacyLine = `${bulletLine}: ${renderBodyPreview(t.body)}`;
36
36
  return t.suggestion && opts.renderSuggestion
@@ -53,9 +53,9 @@ export function renderThreadConversation(t) {
53
53
  .map((c) => {
54
54
  const heading = c.id
55
55
  ? c.url
56
- ? `#### [commentId=${c.id}](${c.url}) (${renderAuthor(c.author, c.authorType)})`
57
- : `#### \`commentId=${c.id}\` (${renderAuthor(c.author, c.authorType)})`
58
- : `#### (${renderAuthor(c.author, c.authorType)})`;
56
+ ? `#### [commentId=${c.id}](${c.url}) (${renderAuthor(c.author, c.authorType, c.authorAssociation)})`
57
+ : `#### \`commentId=${c.id}\` (${renderAuthor(c.author, c.authorType, c.authorAssociation)})`
58
+ : `#### (${renderAuthor(c.author, c.authorType, c.authorAssociation)})`;
59
59
  return `${heading}\n\n${blockquote(c.body)}`;
60
60
  })
61
61
  .join("\n\n");
@@ -73,7 +73,7 @@ function renderThreadCommentBullets(t) {
73
73
  const link = c.url ? ` [↗](${c.url})` : "";
74
74
  const id = c.id ? `\`commentId=${c.id}\`` : "comment";
75
75
  return [
76
- ` - ${id}${link} (${renderAuthor(c.author, c.authorType)})`,
76
+ ` - ${id}${link} (${renderAuthor(c.author, c.authorType, c.authorAssociation)})`,
77
77
  indentBlockquote(c.body, " "),
78
78
  ].join("\n");
79
79
  })
@@ -88,13 +88,13 @@ function indentBlockquote(body, indent) {
88
88
  export function renderCommentBullet(c, opts = {}) {
89
89
  const link = c.url ? ` [↗](${c.url})` : "";
90
90
  const statusSuffix = opts.statusTag ? ` ${opts.statusTag}` : "";
91
- return `- \`commentId=${c.id}\`${link} (${renderAuthor(c.author, c.authorType)})${statusSuffix}: ${renderBodyPreview(c.body)}`;
91
+ return `- \`commentId=${c.id}\`${link} (${renderAuthor(c.author, c.authorType, c.authorAssociation)})${statusSuffix}: ${renderBodyPreview(c.body)}`;
92
92
  }
93
93
  export function renderEditedCommentTag(c) {
94
94
  return c.edited ? "[edited since first look]" : undefined;
95
95
  }
96
96
  export function renderReviewBullet(r, opts = {}) {
97
- const base = `- \`reviewId=${r.id}\` (${renderAuthor(r.author, r.authorType)})`;
97
+ const base = `- \`reviewId=${r.id}\` (${renderAuthor(r.author, r.authorType, r.authorAssociation)})`;
98
98
  const staleTag = r.staleReview
99
99
  ? " [stale — review is on an old commit, all threads resolved]"
100
100
  : "";
@@ -0,0 +1,11 @@
1
+ type ParseMarkFilesAsViewedResult = {
2
+ ok: true;
3
+ files: string[];
4
+ tests: boolean;
5
+ matchPatterns: string[];
6
+ } | {
7
+ ok: false;
8
+ error: string;
9
+ };
10
+ export declare function parseMarkFilesAsViewedArgs(args: string[]): ParseMarkFilesAsViewedResult;
11
+ export {};
@@ -0,0 +1,2 @@
1
+ import type { MarkFilesAsViewedResult } from "../commands/mark-files-as-viewed.mts";
2
+ export declare function formatMarkFilesAsViewedResult(result: MarkFilesAsViewedResult): string;
@@ -0,0 +1,2 @@
1
+ import type { ResolveResult } from "../comments/resolve.mts";
2
+ export declare function formatMutateResult(result: ResolveResult): string;
@@ -0,0 +1 @@
1
+ export declare function handlePoll(args: string[]): Promise<void>;
@@ -1,7 +1,7 @@
1
1
  import { runPoll } from "../commands/poll.mjs";
2
2
  import { loadConfig } from "../config/load.mjs";
3
3
  import { parseCommonArgs, getFlag, hasFlag } from "./args.mjs";
4
- import { parseDurationToSeconds } from "./exit-codes.mjs";
4
+ import { parseDurationToSeconds } from "./duration.mjs";
5
5
  import { validateSecondsDurationFlag } from "./duration-flag.mjs";
6
6
  import { parseIterateFlags } from "./iterate-flags.mjs";
7
7
  import { emitIterateResult } from "./iterate-emitter.mjs";
@@ -14,12 +14,12 @@ export async function handlePoll(args) {
14
14
  if (flags.readyDelaySuffix === null || flags.stallTimeoutSuffix === null)
15
15
  return;
16
16
  const intervalStr = getFlag(extra, "--interval");
17
- const intervalSuffix = validateSecondsDurationFlag("pr-shepherd poll", "--interval", intervalStr, hasFlag(extra, "--interval"));
17
+ const intervalSuffix = validateSecondsDurationFlag("pr-shepherd", "--interval", intervalStr, hasFlag(extra, "--interval"));
18
18
  if (intervalSuffix === null)
19
19
  return;
20
20
  const intervalSeconds = parseDurationToSeconds(intervalSuffix ?? "", DEFAULT_POLL_INTERVAL_SECONDS);
21
21
  const timeoutStr = getFlag(extra, "--timeout");
22
- const timeoutSuffix = validateSecondsDurationFlag("pr-shepherd poll", "--timeout", timeoutStr, hasFlag(extra, "--timeout"));
22
+ const timeoutSuffix = validateSecondsDurationFlag("pr-shepherd", "--timeout", timeoutStr, hasFlag(extra, "--timeout"));
23
23
  if (timeoutSuffix === null)
24
24
  return;
25
25
  const timeoutSeconds = parseDurationToSeconds(timeoutSuffix ?? "", DEFAULT_POLL_TIMEOUT_SECONDS);
@@ -0,0 +1,3 @@
1
+ export declare function rejectPrrcMinimizeIds(ids: string[]): string[];
2
+ export declare function warnPrrcThreadIds(ids: string[]): string[];
3
+ export declare function validateRequireSha(sha: string | undefined): boolean;
@@ -1,15 +1,16 @@
1
+ import { EXIT } from "../exit-codes.mjs";
1
2
  export function rejectPrrcMinimizeIds(ids) {
2
3
  const prrcIds = ids.filter((id) => id.startsWith("PRRC_"));
3
4
  if (prrcIds.length > 0) {
4
- process.stderr.write(`pr-shepherd: resolve: --minimize-comment-ids contains thread comment IDs (PRRC_*): ${prrcIds.join(", ")}. Thread comments cannot be minimized individually — resolve the parent thread using --resolve-thread-ids with the PRRT_* thread ID instead.\n`);
5
- process.exitCode = 1;
5
+ process.stderr.write(`pr-shepherd: apply review: --minimize-comment-ids contains thread comment IDs (PRRC_*): ${prrcIds.join(", ")}. Thread comments cannot be minimized individually — resolve the parent thread using --resolve-thread-ids with the PRRT_* thread ID instead.\n`);
6
+ process.exitCode = EXIT.DATAERR;
6
7
  }
7
8
  return prrcIds;
8
9
  }
9
10
  export function warnPrrcThreadIds(ids) {
10
11
  const prrcIds = ids.filter((id) => id.startsWith("PRRC_"));
11
12
  if (prrcIds.length > 0) {
12
- process.stderr.write(`pr-shepherd: resolve: warning: --resolve-thread-ids contains comment IDs (PRRC_*) instead of thread IDs (PRRT_*): ${prrcIds.join(", ")}. The resolveReviewThread mutation requires PRRT_* thread IDs. Run a GraphQL query for pullRequest.reviewThreads to get the correct IDs.\n`);
13
+ process.stderr.write(`pr-shepherd: apply review: warning: --resolve-thread-ids contains comment IDs (PRRC_*) instead of thread IDs (PRRT_*): ${prrcIds.join(", ")}. The resolveReviewThread mutation requires PRRT_* thread IDs. Run a GraphQL query for pullRequest.reviewThreads to get the correct IDs.\n`);
13
14
  }
14
15
  return prrcIds;
15
16
  }
@@ -18,7 +19,7 @@ export function validateRequireSha(sha) {
18
19
  return true;
19
20
  if (/^[0-9a-f]{40}$/.test(sha))
20
21
  return true;
21
- process.stderr.write(`pr-shepherd: resolve: --require-sha must be a full 40-character lowercase hex SHA, got "${sha}". Short SHAs will never match GitHub's headRefOid. Use $(git rev-parse HEAD) to get the full SHA.\n`);
22
- process.exitCode = 1;
22
+ process.stderr.write(`pr-shepherd: apply review: --require-sha must be a full 40-character lowercase hex SHA, got "${sha}". Short SHAs will never match GitHub's headRefOid. Use $(git rev-parse HEAD) to get the full SHA.\n`);
23
+ process.exitCode = EXIT.DATAERR;
23
24
  return false;
24
25
  }
@@ -0,0 +1,7 @@
1
+ interface PrShepherdCommand {
2
+ argv: string[];
3
+ text: string;
4
+ }
5
+ export declare function buildPrShepherdCommand(args: string[]): PrShepherdCommand;
6
+ export declare function renderShellCommand(argv: string[]): string;
7
+ export {};
@@ -0,0 +1,3 @@
1
+ import type { SuggestionBlock } from "../types.mts";
2
+ export declare function renderLineRange(startLine: number | undefined, endLine: number | null): string;
3
+ export declare function renderSuggestionBlock(s: SuggestionBlock, indent?: string): string;
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Validate args for default-dispatch paths (e.g. `pr-shepherd [PR] [flags]`).
3
+ * Returns false when an unexpected token is found; calls onError with the
4
+ * offending arg so callers can print their own usage message.
5
+ */
6
+ export declare function validateDefaultArgs(args: string[], flagsWithValues: ReadonlySet<string>, booleanFlags: ReadonlySet<string>, onError: (arg: string) => void): boolean;
@@ -0,0 +1,2 @@
1
+ /** CLI argument parsing and subcommand dispatch for pr-shepherd. See --help for usage. */
2
+ export declare function main(argv: string[]): Promise<void>;
@@ -1,10 +1,12 @@
1
+ /* eslint-disable max-lines */
1
2
  /** CLI argument parsing and subcommand dispatch for pr-shepherd. See --help for usage. */
2
3
  import { readFileSync } from "node:fs";
4
+ import { EXIT, errorToExitCode } from "./exit-codes.mjs";
3
5
  import { runResolveMutate } from "./commands/resolve.mjs";
4
6
  import { runLogFile } from "./commands/log-file.mjs";
5
7
  import { parseCommonArgs, getFlag, hasFlag, parseList } from "./cli/args.mjs";
6
8
  import { isDefaultPollInvocation, validateDefaultPollArgs } from "./cli/default-poll.mjs";
7
- import { USAGE, maybePrintHelp } from "./cli/help.mjs";
9
+ import { USAGE, helpKeyForArgs, maybePrintHelp } from "./cli/help.mjs";
8
10
  import { formatMutateResult } from "./cli/formatters.mjs";
9
11
  import { handleClean, handleCommitSuggestion, handleIterate, handleMarkFilesAsViewed, } from "./cli/handlers.mjs";
10
12
  import { handleJournal } from "./cli/journal-handler.mjs";
@@ -30,19 +32,33 @@ export async function main(argv) {
30
32
  await handleLogFile(args.slice(1));
31
33
  return;
32
34
  }
35
+ // The public command groups keep admin diagnostics out of the normal
36
+ // command namespace. Keep this before setupLog because log-file reads the
37
+ // log path and must not initialize/tee the log it is reporting.
38
+ if (subcommand === "admin" && args[1] === "log-file") {
39
+ await handleLogFile(args.slice(2), "admin log-file");
40
+ return;
41
+ }
33
42
  // Short-circuit all --help/-h before any I/O or logging.
34
43
  if (hasFlag(args, "--help") || hasFlag(args, "-h")) {
35
44
  if (isDefaultPollInvocation(subcommand)) {
36
- process.stdout.write(`${USAGE.poll}\n`);
45
+ process.stdout.write(`${USAGE.default}\n`);
37
46
  }
38
47
  else {
39
- const key = subcommand != null && subcommand in USAGE
40
- ? subcommand
41
- : "top";
42
- process.stdout.write(`${USAGE[key]}\n`);
48
+ process.stdout.write(`${USAGE[helpKeyForArgs(args)]}\n`);
43
49
  }
44
50
  return;
45
51
  }
52
+ const legacyReplacement = {
53
+ poll: "pr-shepherd [PR] (the default PR polling command)",
54
+ resolve: "apply review",
55
+ "mark-files-as-viewed": "apply files",
56
+ journal: "apply journal",
57
+ "commit-suggestion": "build-suggestion-patch",
58
+ };
59
+ if (subcommand !== undefined && legacyReplacement[subcommand] !== undefined) {
60
+ warnLegacyAlias(subcommand, legacyReplacement[subcommand]);
61
+ }
46
62
  // Initialize the per-worktree log and install a stdout tee.
47
63
  await setupLog(argv);
48
64
  if (isDefaultPollInvocation(subcommand)) {
@@ -52,14 +68,23 @@ export async function main(argv) {
52
68
  return;
53
69
  }
54
70
  switch (subcommand) {
71
+ case "apply":
72
+ await handleApply(args.slice(1));
73
+ break;
74
+ case "build-suggestion-patch":
75
+ await handleCommitSuggestion(args.slice(1));
76
+ break;
77
+ case "admin":
78
+ await handleAdmin(args.slice(1));
79
+ break;
55
80
  case "resolve":
56
- await handleResolve(args.slice(1));
81
+ await handleResolve(args.slice(1), "resolve");
57
82
  break;
58
83
  case "commit-suggestion":
59
- await handleCommitSuggestion(args.slice(1));
84
+ await handleCommitSuggestion(args.slice(1), "commit-suggestion");
60
85
  break;
61
86
  case "mark-files-as-viewed":
62
- await handleMarkFilesAsViewed(args.slice(1));
87
+ await handleMarkFilesAsViewed(args.slice(1), "mark-files-as-viewed");
63
88
  break;
64
89
  case "iterate":
65
90
  await handleIterate(args.slice(1));
@@ -68,18 +93,50 @@ export async function main(argv) {
68
93
  await handlePoll(args.slice(1));
69
94
  break;
70
95
  case "clean":
71
- await handleClean(args.slice(1));
96
+ await handleClean(args.slice(1), "clean");
72
97
  break;
73
98
  case "journal":
74
- await handleJournal(args.slice(1));
99
+ await handleJournal(args.slice(1), "journal");
75
100
  break;
76
101
  default:
77
102
  process.stderr.write(`Unknown subcommand: ${subcommand ?? "(none)"}\n`);
78
103
  process.stderr.write(`${USAGE.top}\n`);
79
- process.exitCode = 1;
104
+ process.exitCode = EXIT.USAGE;
80
105
  return;
81
106
  }
82
107
  }
108
+ function warnLegacyAlias(alias, replacement) {
109
+ process.stderr.write(`pr-shepherd: ${alias} is deprecated; use ${replacement.startsWith("pr-shepherd") ? replacement : `pr-shepherd ${replacement}`} instead.\n`);
110
+ }
111
+ async function handleApply(args) {
112
+ const action = args[0];
113
+ switch (action) {
114
+ case "review":
115
+ await handleResolve(args.slice(1));
116
+ return;
117
+ case "files":
118
+ await handleMarkFilesAsViewed(args.slice(1));
119
+ return;
120
+ case "journal":
121
+ await handleJournal(args.slice(1));
122
+ return;
123
+ default:
124
+ process.stderr.write(`Unknown apply action: ${action ?? "(none)"}\n`);
125
+ process.stderr.write(`${USAGE.apply}\n`);
126
+ process.exitCode = EXIT.USAGE;
127
+ }
128
+ }
129
+ async function handleAdmin(args) {
130
+ switch (args[0]) {
131
+ case "clean":
132
+ await handleClean(args.slice(1));
133
+ return;
134
+ default:
135
+ process.stderr.write(`Unknown admin command: ${args[0] ?? "(none)"}\n`);
136
+ process.stderr.write(`${USAGE.admin}\n`);
137
+ process.exitCode = EXIT.USAGE;
138
+ }
139
+ }
83
140
  function readVersion() {
84
141
  const pkgUrl = new URL("../package.json", import.meta.url);
85
142
  const pkg = JSON.parse(readFileSync(pkgUrl, "utf8"));
@@ -88,8 +145,8 @@ function readVersion() {
88
145
  // ---------------------------------------------------------------------------
89
146
  // Subcommand handlers
90
147
  // ---------------------------------------------------------------------------
91
- async function handleLogFile(args) {
92
- if (maybePrintHelp(args, "log-file"))
148
+ async function handleLogFile(args, usageKey = "log-file") {
149
+ if (maybePrintHelp(args, usageKey))
93
150
  return;
94
151
  const jsonOut = args.some((a) => a === "--format=json") ||
95
152
  (() => {
@@ -102,10 +159,10 @@ async function handleLogFile(args) {
102
159
  }
103
160
  catch (e) {
104
161
  process.stderr.write(`pr-shepherd: log-file: ${String(e)}\n`);
105
- process.exitCode = 1;
162
+ process.exitCode = errorToExitCode(e);
106
163
  }
107
164
  }
108
- async function handleResolve(args) {
165
+ async function handleResolve(args, command = "apply review") {
109
166
  const { prNumber, global: globalOpts, extra } = parseCommonArgs(args);
110
167
  const resolveThreadIds = parseList(getFlag(extra, "--resolve-thread-ids"));
111
168
  const replyThreadIds = parseList(getFlag(extra, "--reply-thread-ids"));
@@ -119,8 +176,8 @@ async function handleResolve(args) {
119
176
  if (rejectPrrcMinimizeIds(minimizeCommentIds).length > 0)
120
177
  return;
121
178
  if (hasFlag(extra, "--fetch")) {
122
- process.stderr.write("pr-shepherd: resolve: --fetch has been removed; run pr-shepherd iterate or poll to fetch the next action.\n");
123
- process.exitCode = 1;
179
+ process.stderr.write(`pr-shepherd: ${command}: --fetch has been removed; run pr-shepherd iterate to fetch the next action.\n`);
180
+ process.exitCode = EXIT.USAGE;
124
181
  return;
125
182
  }
126
183
  const hasAction = resolveThreadIds.length > 0 ||
@@ -128,8 +185,8 @@ async function handleResolve(args) {
128
185
  minimizeCommentIds.length > 0 ||
129
186
  dismissReviewIds.length > 0;
130
187
  if (!hasAction) {
131
- process.stderr.write("pr-shepherd: resolve: an action flag is required (--reply-thread-ids, --resolve-thread-ids, --minimize-comment-ids, or --dismiss-review-ids).\n");
132
- process.exitCode = 1;
188
+ process.stderr.write(`pr-shepherd: ${command}: an action flag is required (--reply-thread-ids, --resolve-thread-ids, --minimize-comment-ids, or --dismiss-review-ids).\n`);
189
+ process.exitCode = EXIT.USAGE;
133
190
  return;
134
191
  }
135
192
  const result = await runResolveMutate({
@@ -0,0 +1,5 @@
1
+ import type { CheckAnnotation, TriagedCheck } from "../types.mts";
2
+ export declare function attachUnseenCheckAnnotations(checks: TriagedCheck[], seenMap: Map<string, {
3
+ seenAt: number;
4
+ }>, prNumber: number): Promise<TriagedCheck[]>;
5
+ export declare function annotationMarkerBody(a: CheckAnnotation): string;
@@ -0,0 +1,3 @@
1
+ import type { MergeStatusResult, ShepherdStatus } from "../types.mts";
2
+ import type { CiVerdict } from "../checks/classify.mts";
3
+ export declare function computeStatus(verdict: CiVerdict, unresolvedThreads: number, unresolvedComments: number, mergeStatus: MergeStatusResult, changesRequestedReviews: number): ShepherdStatus;