pr-shepherd 0.34.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 (187) hide show
  1. package/.claude-plugin/plugin.json +3 -2
  2. package/.grok-plugin/marketplace.json +17 -0
  3. package/README.md +56 -39
  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/duration-flag.d.mts +2 -0
  18. package/bin/cli/duration.d.mts +13 -0
  19. package/bin/cli/fence.d.mts +1 -0
  20. package/bin/cli/fix-formatter-extra.d.mts +3 -0
  21. package/bin/cli/fix-formatter.d.mts +2 -0
  22. package/bin/cli/fix-formatter.mjs +6 -6
  23. package/bin/cli/formatters.d.mts +7 -0
  24. package/bin/cli/handlers.d.mts +4 -0
  25. package/bin/cli/handlers.mjs +11 -10
  26. package/bin/cli/help-command-pages.d.mts +231 -0
  27. package/bin/cli/help-command-pages.mjs +106 -1
  28. package/bin/cli/help-iterate-poll-pages.d.mts +4 -0
  29. package/bin/cli/help-iterate-poll-pages.mjs +4 -2
  30. package/bin/cli/help-log-file-page.d.mts +1 -0
  31. package/bin/cli/help-top-page.d.mts +1 -0
  32. package/bin/cli/help-top-page.mjs +14 -15
  33. package/bin/cli/help.d.mts +236 -0
  34. package/bin/cli/help.mjs +17 -0
  35. package/bin/cli/iterate-emitter.d.mts +8 -0
  36. package/bin/cli/iterate-flags.d.mts +11 -0
  37. package/bin/cli/iterate-formatter.d.mts +19 -0
  38. package/bin/cli/iterate-instructions.d.mts +6 -0
  39. package/bin/cli/iterate-lean.d.mts +12 -0
  40. package/bin/cli/journal-formatter.d.mts +2 -0
  41. package/bin/cli/journal-formatter.mjs +15 -0
  42. package/bin/cli/journal-handler.d.mts +1 -0
  43. package/bin/cli/journal-handler.mjs +7 -20
  44. package/bin/cli/list-formatters.d.mts +76 -0
  45. package/bin/cli/list-formatters.mjs +9 -9
  46. package/bin/cli/mark-files-as-viewed-flags.d.mts +11 -0
  47. package/bin/cli/mark-files-as-viewed-formatter.d.mts +2 -0
  48. package/bin/cli/mutate-formatter.d.mts +2 -0
  49. package/bin/cli/poll-handler.d.mts +1 -0
  50. package/bin/cli/poll-handler.mjs +2 -2
  51. package/bin/cli/resolve-validators.d.mts +3 -0
  52. package/bin/cli/resolve-validators.mjs +3 -3
  53. package/bin/cli/runner.d.mts +7 -0
  54. package/bin/cli/suggestion-renderer.d.mts +3 -0
  55. package/bin/cli/validate-default-args.d.mts +6 -0
  56. package/bin/cli-parser.d.mts +2 -0
  57. package/bin/cli-parser.mjs +72 -16
  58. package/bin/commands/check-annotations.d.mts +5 -0
  59. package/bin/commands/check-status.d.mts +3 -0
  60. package/bin/commands/check-terminal-report.d.mts +5 -0
  61. package/bin/commands/check.d.mts +6 -0
  62. package/bin/commands/check.mjs +2 -1
  63. package/bin/commands/clean.d.mts +21 -0
  64. package/bin/commands/commit-suggestion-instruction.d.mts +8 -0
  65. package/bin/commands/commit-suggestion-instruction.mjs +3 -3
  66. package/bin/commands/commit-suggestion.d.mts +8 -0
  67. package/bin/commands/commit-suggestion.mjs +11 -10
  68. package/bin/commands/iterate/check-instructions.d.mts +16 -0
  69. package/bin/commands/iterate/check-instructions.mjs +3 -3
  70. package/bin/commands/iterate/classify.d.mts +18 -0
  71. package/bin/commands/iterate/classify.mjs +4 -4
  72. package/bin/commands/iterate/escalate.d.mts +31 -0
  73. package/bin/commands/iterate/escalate.mjs +7 -4
  74. package/bin/commands/iterate/fix-code.d.mts +25 -0
  75. package/bin/commands/iterate/helpers.d.mts +13 -0
  76. package/bin/commands/iterate/helpers.mjs +4 -1
  77. package/bin/commands/iterate/index.d.mts +2 -0
  78. package/bin/commands/iterate/index.mjs +5 -2
  79. package/bin/commands/iterate/render.d.mts +5 -0
  80. package/bin/commands/iterate/render.mjs +8 -6
  81. package/bin/commands/iterate/reruns.d.mts +20 -0
  82. package/bin/commands/iterate/stall.d.mts +6 -0
  83. package/bin/commands/journal/index.d.mts +14 -0
  84. package/bin/commands/journal/index.mjs +1 -0
  85. package/bin/commands/journal/transform.d.mts +22 -0
  86. package/bin/commands/log-file.d.mts +5 -0
  87. package/bin/commands/mark-files-as-viewed.d.mts +26 -0
  88. package/bin/commands/mark-files-as-viewed.mjs +1 -0
  89. package/bin/commands/poll.d.mts +10 -0
  90. package/bin/commands/poll.mjs +1 -0
  91. package/bin/commands/ready-delay.d.mts +29 -0
  92. package/bin/commands/ready-mergeability.d.mts +15 -0
  93. package/bin/commands/resolve-mutate.d.mts +4 -0
  94. package/bin/commands/resolve-mutate.mjs +1 -0
  95. package/bin/commands/resolve.d.mts +4 -0
  96. package/bin/commands/shepherd-journal.d.mts +7 -0
  97. package/bin/commands/shepherd-journal.mjs +2 -2
  98. package/bin/comments/authors.d.mts +14 -0
  99. package/bin/comments/marker.d.mts +2 -0
  100. package/bin/comments/minimize-policy.d.mts +4 -0
  101. package/bin/comments/pending-ops.d.mts +15 -0
  102. package/bin/comments/rate-limit.d.mts +18 -0
  103. package/bin/comments/resolve.d.mts +34 -0
  104. package/bin/comments/resolve.mjs +1 -0
  105. package/bin/comments/review-thread-markers.d.mts +8 -0
  106. package/bin/comments/review-visibility.d.mts +28 -0
  107. package/bin/comments/sha-poll.d.mts +2 -0
  108. package/bin/comments/thread-visibility.d.mts +11 -0
  109. package/bin/comments/visible-comments.d.mts +11 -0
  110. package/bin/config/load.d.mts +60 -0
  111. package/bin/config/load.mjs +72 -1
  112. package/bin/config.json +0 -2
  113. package/bin/execution-context.d.mts +9 -0
  114. package/bin/execution-context.mjs +19 -0
  115. package/bin/exit-codes.d.mts +51 -0
  116. package/bin/github/activity.d.mts +3 -0
  117. package/bin/github/activity.mjs +7 -0
  118. package/bin/github/batch-parser-helpers.d.mts +22 -0
  119. package/bin/github/batch-parsers.d.mts +3 -0
  120. package/bin/github/batch-parsers.mjs +6 -0
  121. package/bin/github/batch-raw-types.d.mts +207 -0
  122. package/bin/github/batch-response.d.mts +4 -0
  123. package/bin/github/batch.d.mts +22 -0
  124. package/bin/github/branch-protection.d.mts +3 -0
  125. package/bin/github/check-annotations.d.mts +2 -0
  126. package/bin/github/client.d.mts +46 -0
  127. package/bin/github/client.mjs +7 -2
  128. package/bin/github/errors.d.mts +25 -0
  129. package/bin/github/gql/batch-pr.gql +5 -0
  130. package/bin/github/gql/review-thread-comments.gql +1 -0
  131. package/bin/github/graphql-http.d.mts +19 -0
  132. package/bin/github/graphql-response.d.mts +7 -0
  133. package/bin/github/http-auth.d.mts +4 -0
  134. package/bin/github/http-request.d.mts +7 -0
  135. package/bin/github/http-utils.d.mts +11 -0
  136. package/bin/github/http.d.mts +5 -0
  137. package/bin/github/pagination.d.mts +45 -0
  138. package/bin/github/queries.d.mts +24 -0
  139. package/bin/github/rest-http.d.mts +2 -0
  140. package/bin/github/thread-comments.d.mts +2 -0
  141. package/bin/index.d.mts +10 -0
  142. package/bin/index.mjs +1 -1
  143. package/bin/log/log-file.d.mts +28 -0
  144. package/bin/log/session.d.mts +31 -0
  145. package/bin/log/setup.d.mts +6 -0
  146. package/bin/mcp/index.d.mts +5 -0
  147. package/bin/mcp/index.mjs +8 -0
  148. package/bin/mcp/server.d.mts +8 -0
  149. package/bin/mcp/server.mjs +157 -0
  150. package/bin/mcp-stdio.d.mts +2 -0
  151. package/bin/mcp-stdio.mjs +7 -0
  152. package/bin/merge-status/derive.d.mts +19 -0
  153. package/bin/reporters/agent.d.mts +23 -0
  154. package/bin/reporters/agent.mjs +3 -0
  155. package/bin/state/base.d.mts +1 -0
  156. package/bin/state/bot-cr-seen.d.mts +51 -0
  157. package/bin/state/bot-cr-seen.mjs +1 -1
  158. package/bin/state/fix-attempts.d.mts +27 -0
  159. package/bin/state/iterate-stall.d.mts +27 -0
  160. package/bin/state/seen-comments.d.mts +62 -0
  161. package/bin/suggestions/extract.d.mts +8 -0
  162. package/bin/suggestions/parse.d.mts +48 -0
  163. package/bin/suggestions/patch.d.mts +14 -0
  164. package/bin/threads/transcript.d.mts +14 -0
  165. package/bin/threads/transcript.mjs +4 -0
  166. package/bin/types/activity.d.mts +30 -0
  167. package/bin/types/agent-thread.d.mts +9 -0
  168. package/bin/types/check-annotations.d.mts +14 -0
  169. package/bin/types/check-classification.d.mts +19 -0
  170. package/bin/types/github.d.mts +139 -0
  171. package/bin/types/iterate.d.mts +157 -0
  172. package/bin/types/protected-run.d.mts +6 -0
  173. package/bin/types/report.d.mts +176 -0
  174. package/bin/types/review-thread.d.mts +12 -0
  175. package/bin/types.d.mts +9 -0
  176. package/bin/util/markdown.d.mts +1 -0
  177. package/bin/util/path-segment.d.mts +2 -0
  178. package/bin/util/sleep.d.mts +1 -0
  179. package/bin/util/worktree.d.mts +9 -0
  180. package/bin/util/worktree.mjs +4 -1
  181. package/package.json +51 -37
  182. package/plugins/pr-shepherd/.codex-plugin/plugin.json +3 -2
  183. package/plugins/pr-shepherd/.codex.mcp.json +8 -0
  184. package/plugins/pr-shepherd/.mcp.json +6 -0
  185. package/plugins/pr-shepherd/skills/mark-files-as-viewed/SKILL.md +5 -19
  186. package/plugins/pr-shepherd/skills/pr-shepherd/SKILL.md +6 -15
  187. package/src/classify/types.mts +12 -0
@@ -82,6 +82,7 @@ query BatchPr(
82
82
  id
83
83
  isMinimized
84
84
  url
85
+ authorAssociation
85
86
  author {
86
87
  __typename
87
88
  login
@@ -107,6 +108,7 @@ query BatchPr(
107
108
  id
108
109
  isMinimized
109
110
  url
111
+ authorAssociation
110
112
  author {
111
113
  __typename
112
114
  login
@@ -126,6 +128,7 @@ query BatchPr(
126
128
  }
127
129
  nodes {
128
130
  id
131
+ authorAssociation
129
132
  author {
130
133
  __typename
131
134
  login
@@ -145,6 +148,7 @@ query BatchPr(
145
148
  nodes {
146
149
  id
147
150
  isMinimized
151
+ authorAssociation
148
152
  author {
149
153
  __typename
150
154
  login
@@ -164,6 +168,7 @@ query BatchPr(
164
168
  nodes {
165
169
  id
166
170
  isMinimized
171
+ authorAssociation
167
172
  author {
168
173
  __typename
169
174
  login
@@ -11,6 +11,7 @@ query ReviewThreadComments($threadId: ID!, $commentsCursor: String) {
11
11
  id
12
12
  isMinimized
13
13
  url
14
+ authorAssociation
14
15
  author {
15
16
  __typename
16
17
  login
@@ -0,0 +1,19 @@
1
+ import { type GitHubGraphQlError } from "./errors.mts";
2
+ import { type RateLimitInfo } from "./http-utils.mts";
3
+ interface GraphQlResult<T = unknown> {
4
+ data: T;
5
+ rateLimit?: RateLimitInfo;
6
+ retryAfterSeconds?: number;
7
+ errors?: GitHubGraphQlError[];
8
+ }
9
+ export interface GraphQlRequestOptions {
10
+ /**
11
+ * Mutation batches use partial GraphQL data to preserve per-alias successes.
12
+ * Read paths must keep the strict default so incomplete snapshots never drive
13
+ * PR state transitions.
14
+ */
15
+ allowPartialData?: boolean;
16
+ }
17
+ export declare function graphql<T = unknown>(query: string, vars?: Record<string, unknown>, opts?: GraphQlRequestOptions): Promise<GraphQlResult<T>>;
18
+ export declare function graphqlWithRateLimit<T = unknown>(query: string, vars?: Record<string, unknown>, opts?: GraphQlRequestOptions): Promise<GraphQlResult<T>>;
19
+ export {};
@@ -0,0 +1,7 @@
1
+ import { type GitHubGraphQlError } from "./errors.mts";
2
+ import type { RateLimitInfo } from "./http-utils.mts";
3
+ export declare function parseGraphQlPayload<T>(parsed: unknown, status: number, rateLimit: RateLimitInfo | null, retryAfterSeconds: number | undefined): {
4
+ data: T | null;
5
+ errors?: GitHubGraphQlError[];
6
+ };
7
+ export declare function formatGraphQlErrors(errors: GitHubGraphQlError[] | undefined): string;
@@ -0,0 +1,4 @@
1
+ export declare function _resetTokenCache(): void;
2
+ export declare function hasCachedToken(): boolean;
3
+ export declare function clearTokenCache(): void;
4
+ export declare function makeHeaders(): Promise<Record<string, string>>;
@@ -0,0 +1,7 @@
1
+ type RetryLogFn = (status: number, durationMs: number) => void;
2
+ export declare function requestWithTokenRetry(fn: () => Promise<Response>, t0: number, onIntermediate?: RetryLogFn): Promise<{
3
+ res: Response;
4
+ attempt: number;
5
+ retryT0: number;
6
+ }>;
7
+ export {};
@@ -0,0 +1,11 @@
1
+ export declare function isTransportError(err: unknown): boolean;
2
+ export interface RateLimitInfo {
3
+ remaining: number;
4
+ limit: number;
5
+ resetAt: number;
6
+ }
7
+ export declare function sanitizeBody(body: string): string;
8
+ export declare function redactToken(body: string): string;
9
+ export declare function redactUrl(url: string): string;
10
+ export declare function parseRateLimit(headers: Headers): RateLimitInfo | null;
11
+ export declare function parseRetryAfter(headers: Headers): number | undefined;
@@ -0,0 +1,5 @@
1
+ export { GitHubRequestError } from "./errors.mts";
2
+ export { _resetTokenCache } from "./http-auth.mts";
3
+ export { graphql, graphqlWithRateLimit } from "./graphql-http.mts";
4
+ export { rest, restText } from "./rest-http.mts";
5
+ export type { RateLimitInfo } from "./http-utils.mts";
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Generic GraphQL cursor-based paginator for shepherd.
3
+ *
4
+ * Both paginators accept a `fetchFn` instead of calling `graphql` directly,
5
+ * which makes them testable without any mocking — tests supply a pure function
6
+ * that returns pages of fake data.
7
+ *
8
+ * GitHub's GraphQL connections support two cursor directions:
9
+ * - Forward (`after` + `first`) — used by check contexts.
10
+ * - Backward (`before` + `last`) — used by reviewThreads (default GitHub order).
11
+ */
12
+ interface PageInfo {
13
+ hasNextPage?: boolean;
14
+ hasPreviousPage?: boolean;
15
+ endCursor?: string | null;
16
+ startCursor?: string | null;
17
+ }
18
+ export interface Connection<T> {
19
+ pageInfo: PageInfo;
20
+ nodes: T[];
21
+ }
22
+ /**
23
+ * Paginate forward through a GraphQL connection (`first` / `after` cursors).
24
+ *
25
+ * @param fetchFn Called once per page. Receives the cursor (or null for
26
+ * the very first page) and returns a Connection<T>.
27
+ * @param initialCursor Start from this cursor instead of null. Pass the
28
+ * `endCursor` of an already-fetched page to fetch only
29
+ * the pages *after* it, avoiding a duplicate re-fetch.
30
+ */
31
+ export declare function paginateForward<T>(fetchFn: (cursor: string | null) => Promise<Connection<T>>, initialCursor?: string | null): Promise<T[]>;
32
+ /**
33
+ * Paginate backward through a GraphQL connection (`last` / `before` cursors).
34
+ *
35
+ * Used for `reviewThreads(last: 100, before: $before)`.
36
+ *
37
+ * @param fetchFn Called once per page. Receives the cursor (or null for
38
+ * the very first page). Returns a Connection<T> with
39
+ * `hasPreviousPage` and `startCursor` in pageInfo.
40
+ * @param initialCursor Start from this cursor instead of null. Pass the
41
+ * `startCursor` of an already-fetched page to avoid
42
+ * re-fetching it (fetch only the pages *before* it).
43
+ */
44
+ export declare function paginateBackward<T>(fetchFn: (cursor: string | null) => Promise<Connection<T>>, initialCursor?: string | null): Promise<T[]>;
45
+ export {};
@@ -0,0 +1,24 @@
1
+ /**
2
+ * GraphQL query strings used by pr-shepherd.
3
+ *
4
+ * Static query strings live in src/github/gql/*.gql and are loaded here.
5
+ * Dynamic documents whose content varies per call (e.g. BulkApply in
6
+ * src/comments/resolve.mts) are built at runtime and are exempt — they
7
+ * cannot be expressed as static files.
8
+ */
9
+ /** The primary batch query that fetches CI + comments + merge status in one round-trip. */
10
+ export declare const BATCH_PR_QUERY: string;
11
+ /** Fetches additional comments for a single review thread when its nested connection paginates. */
12
+ export declare const REVIEW_THREAD_COMMENTS_QUERY: string;
13
+ /** Fetch inline annotations for a single CheckRun by node ID. */
14
+ export declare const CHECK_RUN_ANNOTATIONS_QUERY: string;
15
+ /** Returns the current head commit SHA for a PR. Used by waitForSha polling. */
16
+ export declare const GET_PR_HEAD_SHA_QUERY: string;
17
+ /** Look up PR number by branch name (for getCurrentPrNumber). */
18
+ export declare const PR_NUMBER_BY_BRANCH_QUERY: string;
19
+ /** Convert a draft PR to ready for review. */
20
+ export declare const MARK_PR_READY_MUTATION: string;
21
+ /** Fetch the PR body and node ID. */
22
+ export declare const GET_PR_BODY_QUERY: string;
23
+ /** Update the PR body. */
24
+ export declare const UPDATE_PR_BODY_MUTATION: string;
@@ -0,0 +1,2 @@
1
+ export declare function rest<T = unknown>(method: string, path: string, body?: unknown): Promise<T>;
2
+ export declare function restText(path: string): Promise<string>;
@@ -0,0 +1,2 @@
1
+ import type { RawThread } from "./batch-raw-types.mts";
2
+ export declare function hydrateThreadCommentPages(threads: RawThread[]): Promise<RawThread[]>;
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * pr-shepherd — unified GitHub PR status + auto-resolve CLI
4
+ *
5
+ * Usage:
6
+ * pr-shepherd [PR]
7
+ * pr-shepherd apply review [PR]
8
+ * pr-shepherd iterate [PR]
9
+ */
10
+ export {};
package/bin/index.mjs CHANGED
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * Usage:
6
6
  * pr-shepherd [PR]
7
- * pr-shepherd resolve [PR]
7
+ * pr-shepherd apply review [PR]
8
8
  * pr-shepherd iterate [PR]
9
9
  */
10
10
  import { main } from "./cli-parser.mjs";
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Append-only per-worktree markdown log.
3
+ *
4
+ * Log path: $PR_SHEPHERD_STATE_DIR/<owner>-<repo>/worktrees/<basename>-<sha8>.md
5
+ *
6
+ * Always-on by default. Set PR_SHEPHERD_LOG_DISABLED=1 or CI=true to disable.
7
+ * Write failures flip an internal disabled flag so the CLI never crashes because
8
+ * logging failed.
9
+ */
10
+ /** Returns the next monotonically-increasing entry number for the current session. */
11
+ export declare function nextEntry(): number;
12
+ interface RepoKey {
13
+ owner: string;
14
+ repo: string;
15
+ }
16
+ export declare function getLogFilePath(key: RepoKey): string;
17
+ /**
18
+ * Initialize the log for this process. Must be called before appendEntry().
19
+ * Silently disables logging on any error (no git repo, bad repo name, etc.).
20
+ */
21
+ export declare function initLog(repoKey: RepoKey): Promise<string | null>;
22
+ /** Append a pre-formatted markdown chunk to the log. No-op if disabled. */
23
+ export declare function appendEntry(markdown: string): void;
24
+ /** Resolve the log path without initializing (for the log-file subcommand). */
25
+ export declare function resolveLogPath(repoKey: RepoKey): Promise<string>;
26
+ /** Exposed for tests to reset module state. */
27
+ export declare function _resetLogState(): void;
28
+ export {};
@@ -0,0 +1,31 @@
1
+ /** Builds the session header markdown block. */
2
+ export declare function buildSessionHeader(argv: string[]): {
3
+ markdown: string;
4
+ };
5
+ interface HttpRequestEntry {
6
+ n: number;
7
+ kind: "GraphQL" | "REST" | "restText";
8
+ method: string;
9
+ url: string;
10
+ body?: unknown;
11
+ }
12
+ interface HttpResponseEntry {
13
+ n: number;
14
+ kind: "GraphQL" | "REST" | "restText";
15
+ method: string;
16
+ url: string;
17
+ status: number;
18
+ durationMs: number;
19
+ /** Parsed response body. Omit for restText. */
20
+ body?: unknown;
21
+ /** Raw text response body. Omit for restText. */
22
+ textBody?: string;
23
+ contentType?: string;
24
+ contentLength?: number;
25
+ /** Set on 401-retry invocations. */
26
+ attempt?: number;
27
+ }
28
+ export declare function formatRequestEntry(entry: HttpRequestEntry): string;
29
+ export declare function formatResponseEntry(entry: HttpResponseEntry): string;
30
+ export declare function formatOutputEntry(text: string, format: "text" | "json"): string;
31
+ export {};
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Initialize the per-worktree log, write the session header, and install a
3
+ * stdout tee that routes all CLI output to the log. No-op after the first call.
4
+ * Silently skips logging when not in a git repo or on any other error.
5
+ */
6
+ export declare function setupLog(argv: string[]): Promise<void>;
@@ -0,0 +1,5 @@
1
+ import { type CreatePrShepherdMcpServerOptions } from "./server.mts";
2
+ export { createPrShepherdMcpServer } from "./server.mts";
3
+ export type { CreatePrShepherdMcpServerOptions } from "./server.mts";
4
+ /** Starts the local stdio-only MCP transport. */
5
+ export declare function runPrShepherdMcpStdio(options?: CreatePrShepherdMcpServerOptions): Promise<void>;
@@ -0,0 +1,8 @@
1
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
2
+ import { createPrShepherdMcpServer } from "./server.mjs";
3
+ export { createPrShepherdMcpServer } from "./server.mjs";
4
+ /** Starts the local stdio-only MCP transport. */
5
+ export async function runPrShepherdMcpStdio(options = {}) {
6
+ const server = createPrShepherdMcpServer(options);
7
+ await server.connect(new StdioServerTransport());
8
+ }
@@ -0,0 +1,8 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { type CreatePrShepherdOptions, type PrShepherd } from "../api.mts";
3
+ export interface CreatePrShepherdMcpServerOptions extends CreatePrShepherdOptions {
4
+ /** Optional injection point for embedding hosts and focused tests. */
5
+ shepherd?: PrShepherd;
6
+ }
7
+ /** Creates a local-only MCP server with the three public Shepherd operations. */
8
+ export declare function createPrShepherdMcpServer(options?: CreatePrShepherdMcpServerOptions): McpServer;
@@ -0,0 +1,157 @@
1
+ /* eslint-disable max-lines */
2
+ import { readFileSync } from "node:fs";
3
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4
+ import { z } from "zod";
5
+ import { createPrShepherd, PartialApplyError, PrShepherdValidationError, } from "../api.mjs";
6
+ import { formatJournalResult } from "../cli/journal-formatter.mjs";
7
+ import { formatCommitSuggestionResult, formatIterateResult, formatMarkFilesAsViewedResult, formatMutateResult, } from "../cli/formatters.mjs";
8
+ import { errorToExitCode, EXIT } from "../exit-codes.mjs";
9
+ const pr = z.union([z.number().int().positive(), z.string().url()]).optional();
10
+ const ids = z.array(z.string().min(1)).optional();
11
+ const iterateInputSchema = z.object({
12
+ pr,
13
+ readyDelaySeconds: z.number().nonnegative().optional(),
14
+ stallTimeoutSeconds: z.number().nonnegative().optional(),
15
+ noAutoMarkReady: z.boolean().optional(),
16
+ noAutoCancelActionable: z.boolean().optional(),
17
+ neverCancelRuns: z.array(z.string()).optional(),
18
+ });
19
+ const reviewMutationsOperationSchema = z.object({
20
+ type: z.literal("review_mutations"),
21
+ resolveThreadIds: ids,
22
+ replyThreadIds: ids,
23
+ minimizeCommentIds: ids,
24
+ dismissReviewIds: ids,
25
+ message: z.string().optional(),
26
+ requireSha: z.string().optional(),
27
+ });
28
+ const markFilesViewedOperationSchema = z.object({
29
+ type: z.literal("mark_files_viewed"),
30
+ files: z.array(z.string().min(1)).optional(),
31
+ tests: z.boolean().optional(),
32
+ matchPatterns: z.array(z.string().min(1)).optional(),
33
+ });
34
+ const appendJournalOperationSchema = z.object({
35
+ type: z.literal("append_journal"),
36
+ item: z.string(),
37
+ dryRun: z.boolean().optional(),
38
+ });
39
+ const applyInputSchema = z.object({
40
+ pr,
41
+ operations: z
42
+ .array(z.discriminatedUnion("type", [
43
+ reviewMutationsOperationSchema,
44
+ markFilesViewedOperationSchema,
45
+ appendJournalOperationSchema,
46
+ ]))
47
+ .min(1),
48
+ });
49
+ const suggestionPatchInputSchema = z.object({
50
+ pr,
51
+ threadId: z.string().min(1),
52
+ message: z.string().min(1),
53
+ description: z.string().optional(),
54
+ });
55
+ /** Creates a local-only MCP server with the three public Shepherd operations. */
56
+ export function createPrShepherdMcpServer(options = {}) {
57
+ const shepherd = options.shepherd ?? createPrShepherd({ cwd: options.cwd });
58
+ const server = new McpServer({ name: "pr-shepherd", version: readPackageVersion() });
59
+ server.registerTool("iterate", {
60
+ description: "Inspect the current pull request and return the next Shepherd state.",
61
+ inputSchema: iterateInputSchema,
62
+ annotations: {
63
+ readOnlyHint: false,
64
+ destructiveHint: true,
65
+ idempotentHint: false,
66
+ openWorldHint: true,
67
+ },
68
+ }, async (input) => runTool(() => shepherd.iterate(input), formatIterateResult));
69
+ server.registerTool("apply", {
70
+ description: "Apply ordered review, file-view, and journal operations after prevalidation.",
71
+ inputSchema: applyInputSchema,
72
+ annotations: {
73
+ readOnlyHint: false,
74
+ destructiveHint: true,
75
+ idempotentHint: false,
76
+ openWorldHint: true,
77
+ },
78
+ }, async (input) => runTool(() => shepherd.apply(input), formatApplyResult));
79
+ server.registerTool("build_suggestion_patch", {
80
+ description: "Build, but never apply, a patch from an eligible review suggestion.",
81
+ inputSchema: suggestionPatchInputSchema,
82
+ annotations: {
83
+ readOnlyHint: true,
84
+ destructiveHint: false,
85
+ idempotentHint: true,
86
+ openWorldHint: true,
87
+ },
88
+ }, async (input) => runTool(() => shepherd.buildSuggestionPatch(input), formatCommitSuggestionResult));
89
+ return server;
90
+ }
91
+ function readPackageVersion() {
92
+ const packageJson = JSON.parse(readFileSync(new URL("../../package.json", import.meta.url), "utf8"));
93
+ return packageJson.version;
94
+ }
95
+ function toolResult(result, text) {
96
+ return {
97
+ content: [{ type: "text", text }],
98
+ structuredContent: result,
99
+ };
100
+ }
101
+ async function runTool(work, format) {
102
+ try {
103
+ const result = await work();
104
+ return toolResult(result, format(result));
105
+ }
106
+ catch (error) {
107
+ return toolError(error);
108
+ }
109
+ }
110
+ function formatApplyResult(result) {
111
+ return result.operations
112
+ .map((operation, index) => {
113
+ const heading = `## Operation ${index + 1}: ${operation.type}`;
114
+ switch (operation.type) {
115
+ case "review_mutations":
116
+ return `${heading}\n\n${formatMutateResult(operation.result)}`;
117
+ case "mark_files_viewed":
118
+ return `${heading}\n\n${formatMarkFilesAsViewedResult(operation.result)}`;
119
+ case "append_journal":
120
+ return `${heading}\n\n${formatJournalResult(operation.result)}`;
121
+ }
122
+ })
123
+ .join("\n\n");
124
+ }
125
+ function toolError(error) {
126
+ const code = error instanceof PrShepherdValidationError
127
+ ? EXIT.USAGE
128
+ : error instanceof PartialApplyError
129
+ ? errorToExitCode(error.cause)
130
+ : errorToExitCode(error);
131
+ const details = error instanceof PartialApplyError
132
+ ? { failedIndex: error.failedIndex, completed: redactValue(error.completed) }
133
+ : error instanceof PrShepherdValidationError
134
+ ? { validation: true }
135
+ : {};
136
+ const message = redactErrorMessage(error instanceof Error ? error.message : String(error));
137
+ return {
138
+ isError: true,
139
+ content: [{ type: "text", text: `pr-shepherd error (${code}): ${message}` }],
140
+ structuredContent: { code, message, details },
141
+ };
142
+ }
143
+ function redactErrorMessage(message) {
144
+ return message
145
+ .replace(/(?:ghp|github_pat)_[A-Za-z0-9_]+/g, "[redacted]")
146
+ .replace(/(authorization:\s*bearer\s+)[^\s]+/gi, "$1[redacted]");
147
+ }
148
+ function redactValue(value) {
149
+ if (typeof value === "string")
150
+ return redactErrorMessage(value);
151
+ if (Array.isArray(value))
152
+ return value.map(redactValue);
153
+ if (value !== null && typeof value === "object") {
154
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, redactValue(item)]));
155
+ }
156
+ return value;
157
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ import { runPrShepherdMcpStdio } from "./mcp/index.mjs";
3
+ runPrShepherdMcpStdio().catch((error) => {
4
+ const message = error instanceof Error ? error.message : String(error);
5
+ process.stderr.write(`pr-shepherd-mcp error: ${message}\n`);
6
+ process.exitCode = 1;
7
+ });
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Derives a shepherd `MergeStatusResult` from raw PR data.
3
+ *
4
+ * `pr.state` is passed through unchanged; `iterate` handles the cancel action
5
+ * for non-OPEN (merged/closed) PRs — this function does not branch on it.
6
+ *
7
+ * Interpretation order for `status` — first match wins:
8
+ * 1. mergeable == CONFLICTING → CONFLICTS (hard conflict even for drafts)
9
+ * 2. blockingBotReviewInProgress → BLOCKED
10
+ * 3. mergeStateStatus DIRTY → CONFLICTS (GitHub merge conflicts, even for drafts)
11
+ * 4. isDraft → DRAFT
12
+ * 5. mergeStateStatus BEHIND → BEHIND
13
+ * 6. mergeStateStatus BLOCKED / HAS_HOOKS → BLOCKED
14
+ * 7. mergeStateStatus UNSTABLE → UNSTABLE
15
+ * 8. mergeStateStatus UNKNOWN → UNKNOWN
16
+ * 9. mergeStateStatus CLEAN → CLEAN
17
+ */
18
+ import type { BatchPrData, MergeStatusResult } from "../types.mts";
19
+ export declare function deriveMergeStatus(pr: BatchPrData): MergeStatusResult;
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Projections for the agent-facing iterate output.
3
+ *
4
+ * These strip fields that are always-false by the time items reach iterate
5
+ * (isResolved, isOutdated, isMinimized, createdAtUnix) and check metadata the
6
+ * agent/iterate prompt never reads (event, status, category).
7
+ * conclusion is preserved on AgentCheck so the formatter can branch on run-level conclusions.
8
+ * detailsUrl is preserved in AgentCheck as a fallback for external status checks.
9
+ * The original domain types are preserved as internal snapshot types.
10
+ */
11
+ import type { ReviewThread, PrComment, TriagedCheck, ClassifiedCheck, AgentThread, AgentComment, AgentCheck, AgentStalledCheck } from "../types.mts";
12
+ export declare function toAgentThread(t: ReviewThread): AgentThread;
13
+ export declare function toAgentComment(c: PrComment & {
14
+ edited?: boolean;
15
+ }): AgentComment;
16
+ export declare function toAgentCheck(c: TriagedCheck): AgentCheck;
17
+ export declare function toAgentStalledCheck(c: ClassifiedCheck, nowSeconds: number): AgentStalledCheck;
18
+ /**
19
+ * Project failing checks for the agent. Deduplicates only null-runId external
20
+ * checks by name — when runId is present each check may have a distinct job and
21
+ * log tail, so they are all kept.
22
+ */
23
+ export declare function toAgentChecks(checks: TriagedCheck[]): AgentCheck[];
@@ -21,6 +21,7 @@ export function toAgentThread(t) {
21
21
  t.startLine !== t.line && { startLine: t.startLine }),
22
22
  author: t.author,
23
23
  ...(t.authorType !== undefined && { authorType: t.authorType }),
24
+ ...(t.authorAssociation !== undefined && { authorAssociation: t.authorAssociation }),
24
25
  body: t.body,
25
26
  url: t.url,
26
27
  ...(t.edited === true && { edited: true }),
@@ -29,6 +30,7 @@ export function toAgentThread(t) {
29
30
  id: c.id,
30
31
  author: c.author,
31
32
  ...(c.authorType !== undefined && { authorType: c.authorType }),
33
+ ...(c.authorAssociation !== undefined && { authorAssociation: c.authorAssociation }),
32
34
  body: c.body,
33
35
  url: c.url,
34
36
  })),
@@ -41,6 +43,7 @@ export function toAgentComment(c) {
41
43
  id: c.id,
42
44
  author: c.author,
43
45
  ...(c.authorType !== undefined && { authorType: c.authorType }),
46
+ ...(c.authorAssociation !== undefined && { authorAssociation: c.authorAssociation }),
44
47
  body: c.body,
45
48
  url: c.url,
46
49
  ...(c.edited === true && { edited: true }),
@@ -0,0 +1 @@
1
+ export declare function resolveStateBase(): string;
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Persistent first-seen state for bot CHANGES_REQUESTED reviews.
3
+ *
4
+ * Bot CRs are auto-dismissed via `--dismiss-review-ids` in the post-push
5
+ * `apply review:` command. If the agent drops that flag, the bot CR keeps the PR in
6
+ * `CHANGES_REQUESTED` state. This file tracks when each bot CR was first
7
+ * observed so the iterate loop can escalate after `iterate.stallTimeoutMinutes`
8
+ * — independent of the broader fingerprint-based `stall-timeout` mechanism in
9
+ * `iterate-stall.mts` (which only fires when no field of the iterate result
10
+ * changed).
11
+ *
12
+ * State lives in
13
+ * `$TMPDIR/pr-shepherd-state/<owner>-<repo>/<pr>/bot-cr-seen.json`.
14
+ *
15
+ * `bodyHash` lets us reset `firstSeenAt` when the bot re-issues the review
16
+ * with a different body, so a fresh review gets the full timeout window.
17
+ */
18
+ interface BotCrSeenEntry {
19
+ /** Unix timestamp (seconds) when this review was first observed undismissed. */
20
+ firstSeenAt: number;
21
+ /** SHA-256 body-hash prefix; reset firstSeenAt when this changes. */
22
+ bodyHash: string;
23
+ }
24
+ export interface BotCrSeenState {
25
+ reviews: Record<string, BotCrSeenEntry>;
26
+ }
27
+ interface StateKey {
28
+ owner: string;
29
+ repo: string;
30
+ pr: number;
31
+ }
32
+ export declare function readBotCrSeenState(key: StateKey): Promise<BotCrSeenState | null>;
33
+ export declare function writeBotCrSeenState(key: StateKey, state: BotCrSeenState): Promise<void>;
34
+ /**
35
+ * Update tracked entries against the current set of bot CR reviews:
36
+ * - Insert any new IDs with `firstSeenAt = now` and the current body hash.
37
+ * - Reset `firstSeenAt` for entries whose body hash changed (review re-issued).
38
+ * - Drop entries whose IDs are no longer in the input (review was dismissed
39
+ * or superseded by an approval).
40
+ *
41
+ * Pure function: returns the next state and the IDs whose age has reached the
42
+ * `stallTimeoutSeconds` threshold (escalate candidates).
43
+ */
44
+ export declare function updateBotCrSeenState(previous: BotCrSeenState | null, currentBotCrReviews: ReadonlyArray<{
45
+ id: string;
46
+ body: string;
47
+ }>, nowSeconds: number, stallTimeoutSeconds: number): {
48
+ next: BotCrSeenState;
49
+ staleIds: string[];
50
+ };
51
+ export {};
@@ -2,7 +2,7 @@
2
2
  * Persistent first-seen state for bot CHANGES_REQUESTED reviews.
3
3
  *
4
4
  * Bot CRs are auto-dismissed via `--dismiss-review-ids` in the post-push
5
- * `resolve:` command. If the agent drops that flag, the bot CR keeps the PR in
5
+ * `apply review:` command. If the agent drops that flag, the bot CR keeps the PR in
6
6
  * `CHANGES_REQUESTED` state. This file tracks when each bot CR was first
7
7
  * observed so the iterate loop can escalate after `iterate.stallTimeoutMinutes`
8
8
  * — independent of the broader fingerprint-based `stall-timeout` mechanism in
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Persistent attempt counter for the iterate escalation guard.
3
+ *
4
+ * Tracks how many times each review thread has been dispatched to the fix_code
5
+ * handler without being resolved. Counts are reset automatically when the HEAD
6
+ * commit SHA changes (i.e. a new push landed).
7
+ *
8
+ * State lives in `$TMPDIR/pr-shepherd-state/<owner>-<repo>/<pr>/fix-attempts.json`.
9
+ */
10
+ export interface FixAttemptsState {
11
+ /** HEAD SHA at the time the counts were last written. Reset key. */
12
+ headSha: string;
13
+ /** Map from thread ID → number of fix_code dispatches that included this thread. */
14
+ threadAttempts: Record<string, number>;
15
+ /** Map from thread ID → body hash used for the associated attempt count. */
16
+ threadBodyHashes?: Record<string, string>;
17
+ }
18
+ interface StateKey {
19
+ owner: string;
20
+ repo: string;
21
+ pr: number;
22
+ }
23
+ /** Read the current attempt state. Returns null on miss. */
24
+ export declare function readFixAttempts(key: StateKey): Promise<FixAttemptsState | null>;
25
+ /** Write attempt state (fire-and-forget — never throws). */
26
+ export declare function writeFixAttempts(key: StateKey, state: FixAttemptsState): Promise<void>;
27
+ export {};