pr-shepherd 0.43.0 → 0.44.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 (96) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +18 -17
  3. package/bin/api.d.mts +3 -2
  4. package/bin/api.mjs +13 -18
  5. package/bin/cli/args.mjs +13 -9
  6. package/bin/cli/fix-formatter.mjs +8 -6
  7. package/bin/cli/help-command-pages.d.mts +9 -6
  8. package/bin/cli/help-command-pages.mjs +7 -4
  9. package/bin/cli/help-iterate-poll-pages.d.mts +2 -2
  10. package/bin/cli/help-iterate-poll-pages.mjs +2 -2
  11. package/bin/cli/help-top-page.d.mts +1 -1
  12. package/bin/cli/help-top-page.mjs +2 -2
  13. package/bin/cli/help.d.mts +10 -7
  14. package/bin/cli/iterate-formatter.d.mts +1 -1
  15. package/bin/cli/iterate-formatter.mjs +5 -5
  16. package/bin/cli/iterate-lean.mjs +4 -0
  17. package/bin/cli/journal-formatter.mjs +3 -0
  18. package/bin/cli/journal-handler.mjs +11 -7
  19. package/bin/cli/list-formatters.d.mts +3 -1
  20. package/bin/cli/list-formatters.mjs +9 -7
  21. package/bin/cli/mark-files-as-viewed-formatter.mjs +7 -1
  22. package/bin/cli/mutate-formatter.mjs +4 -0
  23. package/bin/commands/check.mjs +22 -6
  24. package/bin/commands/commit-suggestion-instruction.d.mts +1 -1
  25. package/bin/commands/commit-suggestion-instruction.mjs +2 -2
  26. package/bin/commands/commit-suggestion.mjs +3 -1
  27. package/bin/commands/iterate/check-instructions.d.mts +5 -19
  28. package/bin/commands/iterate/check-instructions.mjs +24 -25
  29. package/bin/commands/iterate/classify.d.mts +2 -2
  30. package/bin/commands/iterate/classify.mjs +43 -25
  31. package/bin/commands/iterate/escalate.d.mts +1 -1
  32. package/bin/commands/iterate/escalate.mjs +11 -0
  33. package/bin/commands/iterate/fix-code.mjs +81 -42
  34. package/bin/commands/iterate/helpers.d.mts +0 -2
  35. package/bin/commands/iterate/helpers.mjs +0 -15
  36. package/bin/commands/iterate/index.mjs +6 -12
  37. package/bin/commands/iterate/mark-ready.d.mts +2 -0
  38. package/bin/commands/iterate/mark-ready.mjs +38 -0
  39. package/bin/commands/iterate/merge-state.mjs +38 -9
  40. package/bin/commands/iterate/merge.mjs +0 -1
  41. package/bin/commands/iterate/render.d.mts +2 -2
  42. package/bin/commands/iterate/render.mjs +19 -20
  43. package/bin/commands/iterate/stall.mjs +4 -2
  44. package/bin/commands/iterate/thread-mutation-routing.d.mts +9 -0
  45. package/bin/commands/iterate/thread-mutation-routing.mjs +29 -0
  46. package/bin/commands/journal/index.d.mts +5 -0
  47. package/bin/commands/journal/index.mjs +11 -2
  48. package/bin/commands/mark-files-as-viewed.d.mts +2 -1
  49. package/bin/commands/mark-files-as-viewed.mjs +5 -92
  50. package/bin/commands/resolve-mutate.mjs +51 -9
  51. package/bin/commands/shepherd-journal.d.mts +1 -1
  52. package/bin/commands/shepherd-journal.mjs +2 -2
  53. package/bin/commands/suggestion-patch-item.d.mts +1 -1
  54. package/bin/commands/suggestion-patch-item.mjs +3 -11
  55. package/bin/commands/suggestion-patches.mjs +3 -3
  56. package/bin/comments/authors.d.mts +6 -0
  57. package/bin/comments/authors.mjs +5 -0
  58. package/bin/comments/marker.d.mts +6 -1
  59. package/bin/comments/marker.mjs +8 -1
  60. package/bin/comments/resolve.d.mts +4 -0
  61. package/bin/comments/thread-visibility.mjs +10 -10
  62. package/bin/comments/visible-comments.mjs +2 -1
  63. package/bin/config/load.d.mts +1 -1
  64. package/bin/github/batch-parsers.d.mts +2 -2
  65. package/bin/github/batch-parsers.mjs +17 -1
  66. package/bin/github/batch-raw-types.d.mts +12 -0
  67. package/bin/github/batch.mjs +1 -1
  68. package/bin/github/client.d.mts +1 -0
  69. package/bin/github/client.mjs +8 -1
  70. package/bin/github/gql/batch-pr-page.gql +6 -0
  71. package/bin/github/gql/batch-pr.gql +13 -0
  72. package/bin/github/gql/get-pr-body.gql +1 -0
  73. package/bin/github/gql/review-thread-comments.gql +1 -0
  74. package/bin/github/gql/suggestion-threads.gql +1 -0
  75. package/bin/github/suggestion-thread.mjs +1 -0
  76. package/bin/mcp/server.mjs +12 -6
  77. package/bin/pr-reference.d.mts +13 -0
  78. package/bin/pr-reference.mjs +23 -1
  79. package/bin/reporters/agent.mjs +5 -0
  80. package/bin/threads/transcript.d.mts +2 -0
  81. package/bin/threads/transcript.mjs +2 -0
  82. package/bin/types/agent-thread.d.mts +1 -0
  83. package/bin/types/escalate.d.mts +6 -1
  84. package/bin/types/github.d.mts +19 -0
  85. package/bin/types/iterate.d.mts +9 -8
  86. package/bin/types/report.d.mts +14 -1
  87. package/bin/types/review-thread.d.mts +1 -0
  88. package/bin/types/suggestion-patch.d.mts +1 -1
  89. package/package.json +1 -1
  90. package/plugins/pr-shepherd/.codex-plugin/plugin.json +1 -1
  91. package/plugins/pr-shepherd/.codex.mcp.json +1 -1
  92. package/plugins/pr-shepherd/.mcp.json +1 -1
  93. package/plugins/pr-shepherd/skills/mark-files-as-viewed/SKILL.md +3 -3
  94. package/plugins/pr-shepherd/skills/pr-shepherd/SKILL.md +15 -13
  95. package/bin/commands/iterate/reruns.d.mts +0 -20
  96. package/bin/commands/iterate/reruns.mjs +0 -105
@@ -1,3 +1,3 @@
1
1
  import type { BatchPrData } from "../types.mts";
2
- import type { RawPr, RawThread, RawComment, RawReview, RawReviewSummary, RawContextNode } from "./batch-raw-types.mts";
3
- export declare function parseRawPr(raw: RawPr, rawThreadPages: RawThread[], rawCommentNodes: RawComment[], rawReviewNodes: RawReview[], rawReviewSummaryNodes: RawReviewSummary[], rawApprovedReviewNodes: RawReviewSummary[], rawCheckNodes: RawContextNode[]): BatchPrData;
2
+ import type { RawBatchResponse, RawPr, RawThread, RawComment, RawReview, RawReviewSummary, RawContextNode } from "./batch-raw-types.mts";
3
+ export declare function parseRawPr(raw: RawPr, rawThreadPages: RawThread[], rawCommentNodes: RawComment[], rawReviewNodes: RawReview[], rawReviewSummaryNodes: RawReviewSummary[], rawApprovedReviewNodes: RawReviewSummary[], rawCheckNodes: RawContextNode[], repository?: Pick<NonNullable<RawBatchResponse["repository"]>, "viewerPermission" | "viewerCanAdminister">): BatchPrData;
@@ -10,6 +10,7 @@ function parseReviewNode(r) {
10
10
  author: r.author?.login ?? "unknown",
11
11
  authorType: mapAuthorType(r.author?.__typename, r.author?.login),
12
12
  ...(r.authorAssociation !== undefined && { authorAssociation: r.authorAssociation }),
13
+ viewerCanMinimize: "viewerCanMinimize" in r && r.viewerCanMinimize === true,
13
14
  body: r.body,
14
15
  createdAtUnix: r.createdAt ? parseCreatedAt(r.createdAt) : 0,
15
16
  };
@@ -18,7 +19,7 @@ function parseReviewNode(r) {
18
19
  }
19
20
  return base;
20
21
  }
21
- export function parseRawPr(raw, rawThreadPages, rawCommentNodes, rawReviewNodes, rawReviewSummaryNodes, rawApprovedReviewNodes, rawCheckNodes) {
22
+ export function parseRawPr(raw, rawThreadPages, rawCommentNodes, rawReviewNodes, rawReviewSummaryNodes, rawApprovedReviewNodes, rawCheckNodes, repository) {
22
23
  const reviewRequests = (raw.reviewRequests?.nodes ?? []).flatMap((n) => {
23
24
  const login = n.requestedReviewer?.login ?? n.requestedReviewer?.name;
24
25
  return login ? [{ login }] : [];
@@ -37,6 +38,7 @@ export function parseRawPr(raw, rawThreadPages, rawCommentNodes, rawReviewNodes,
37
38
  author: c.author?.login ?? "unknown",
38
39
  authorType: mapAuthorType(c.author?.__typename, c.author?.login),
39
40
  ...(c.authorAssociation !== undefined && { authorAssociation: c.authorAssociation }),
41
+ ...(c.viewerDidAuthor === true && { viewerDidAuthor: true }),
40
42
  body: c.body,
41
43
  url: c.url,
42
44
  createdAtUnix: c.createdAt ? parseCreatedAt(c.createdAt) : 0,
@@ -46,6 +48,8 @@ export function parseRawPr(raw, rawThreadPages, rawCommentNodes, rawReviewNodes,
46
48
  isResolved: t.isResolved,
47
49
  isOutdated: t.isOutdated,
48
50
  isMinimized: comment?.isMinimized ?? false,
51
+ viewerCanReply: t.viewerCanReply === true,
52
+ viewerCanResolve: t.viewerCanResolve === true,
49
53
  path: t.path ?? comment?.path ?? null,
50
54
  line: t.line ?? comment?.line ?? null,
51
55
  startLine: t.startLine ?? comment?.startLine ?? null,
@@ -55,6 +59,7 @@ export function parseRawPr(raw, rawThreadPages, rawCommentNodes, rawReviewNodes,
55
59
  ...(comment?.authorAssociation !== undefined && {
56
60
  authorAssociation: comment.authorAssociation,
57
61
  }),
62
+ ...(comment?.viewerDidAuthor === true && { viewerDidAuthor: true }),
58
63
  body: comment?.body ?? "",
59
64
  url: comment?.url ?? "",
60
65
  createdAtUnix: comment?.createdAt ? parseCreatedAt(comment.createdAt) : 0,
@@ -64,6 +69,7 @@ export function parseRawPr(raw, rawThreadPages, rawCommentNodes, rawReviewNodes,
64
69
  const comments = rawCommentNodes.map((c) => ({
65
70
  id: c.id,
66
71
  isMinimized: c.isMinimized,
72
+ viewerCanMinimize: c.viewerCanMinimize === true,
67
73
  author: c.author?.login ?? "unknown",
68
74
  authorType: mapAuthorType(c.author?.__typename, c.author?.login),
69
75
  ...(c.authorAssociation !== undefined && { authorAssociation: c.authorAssociation }),
@@ -108,6 +114,16 @@ export function parseRawPr(raw, rawThreadPages, rawCommentNodes, rawReviewNodes,
108
114
  headRefOid: raw.headRefOid,
109
115
  headRefName: raw.headRefName,
110
116
  headRepoWithOwner: raw.headRepository?.nameWithOwner ?? null,
117
+ viewerAuthorization: {
118
+ repositoryPermission: repository?.viewerPermission ?? null,
119
+ viewerCanAdminister: repository?.viewerCanAdminister === true,
120
+ viewerDidAuthor: raw.viewerDidAuthor === true,
121
+ viewerCanUpdate: raw.viewerCanUpdate === true,
122
+ viewerCanEnableAutoMerge: raw.viewerCanEnableAutoMerge === true,
123
+ viewerCanEditFiles: raw.viewerCanEditFiles === true,
124
+ headRepositoryPermission: raw.headRepository?.viewerPermission ??
125
+ null,
126
+ },
111
127
  baseRefName: raw.baseRefName,
112
128
  reviewRequests,
113
129
  latestReviews,
@@ -2,6 +2,8 @@ import type { CommentAuthorAssociation } from "../types/github.mts";
2
2
  import type { RawPrMergeFields } from "./batch-raw-rules.mts";
3
3
  export interface RawBatchResponse {
4
4
  repository: {
5
+ viewerPermission: string | null;
6
+ viewerCanAdminister: boolean;
5
7
  pullRequest: RawPr | null;
6
8
  } | null;
7
9
  }
@@ -10,6 +12,10 @@ export interface RawPr extends RawPrMergeFields {
10
12
  number: number;
11
13
  state: string;
12
14
  isDraft: boolean;
15
+ viewerDidAuthor: boolean;
16
+ viewerCanUpdate: boolean;
17
+ viewerCanEnableAutoMerge: boolean;
18
+ viewerCanEditFiles: boolean;
13
19
  mergeable: string;
14
20
  mergeStateStatus: string;
15
21
  reviewDecision: string | null;
@@ -17,6 +23,7 @@ export interface RawPr extends RawPrMergeFields {
17
23
  headRefName: string;
18
24
  headRepository: {
19
25
  nameWithOwner: string;
26
+ viewerPermission: string | null;
20
27
  } | null;
21
28
  baseRefName: string;
22
29
  baseRef: import("./batch-raw-rules.mts").RawBaseRef | null;
@@ -117,6 +124,7 @@ export interface RawThreadComment {
117
124
  isMinimized: boolean;
118
125
  url: string;
119
126
  authorAssociation?: CommentAuthorAssociation;
127
+ viewerDidAuthor?: boolean;
120
128
  author: RawAuthor | null;
121
129
  pullRequestReview?: {
122
130
  id: string;
@@ -131,6 +139,8 @@ export interface RawThread {
131
139
  id: string;
132
140
  isResolved: boolean;
133
141
  isOutdated: boolean;
142
+ viewerCanReply: boolean;
143
+ viewerCanResolve: boolean;
134
144
  path?: string | null;
135
145
  line?: number | null;
136
146
  startLine?: number | null;
@@ -157,6 +167,7 @@ export interface RawReviewThreadCommentsResponse {
157
167
  export interface RawComment {
158
168
  id: string;
159
169
  isMinimized: boolean;
170
+ viewerCanMinimize: boolean;
160
171
  url: string;
161
172
  authorAssociation?: CommentAuthorAssociation;
162
173
  author: RawAuthor | null;
@@ -176,6 +187,7 @@ export interface RawReview {
176
187
  export interface RawReviewSummary {
177
188
  id: string;
178
189
  isMinimized: boolean;
190
+ viewerCanMinimize: boolean;
179
191
  authorAssociation?: CommentAuthorAssociation;
180
192
  author: RawAuthor | null;
181
193
  body: string;
@@ -20,7 +20,7 @@ export async function fetchPrBatch(pr, repo, opts = {}) {
20
20
  await hydrateMergeQueueChecks(raw, repo);
21
21
  const paged = await paginateBatchConnections(pr, repo, raw, opts, result.rateLimit);
22
22
  const rawThreadPages = await hydrateThreadCommentPages(paged.threads);
23
- const data = parseRawPr(raw, rawThreadPages, paged.comments, paged.changesRequested, paged.reviewSummaries, paged.approvedReviews, paged.checks);
23
+ const data = parseRawPr(raw, rawThreadPages, paged.comments, paged.changesRequested, paged.reviewSummaries, paged.approvedReviews, paged.checks, result.data.repository);
24
24
  data.checks = mergeStartupFailureChecks(data.checks, parseSuiteStartupFailures(raw));
25
25
  return {
26
26
  data,
@@ -30,6 +30,7 @@ export declare function getPrHeadSha(pr: number, owner: string, name: string): P
30
30
  export declare function getPullRequestBody(pr: number, owner: string, name: string): Promise<{
31
31
  nodeId: string;
32
32
  body: string;
33
+ viewerCanUpdate?: boolean;
33
34
  }>;
34
35
  /** Overwrites the PR body. */
35
36
  export declare function updatePullRequestBody(pullRequestId: string, body: string): Promise<void>;
@@ -1,3 +1,4 @@
1
+ /* eslint-disable max-lines */
1
2
  /**
2
3
  * High-level GitHub client — wraps http.mts for application-level concerns.
3
4
  *
@@ -76,7 +77,13 @@ export async function getPullRequestBody(pr, owner, name) {
76
77
  : "PR not found or access denied";
77
78
  throw new Error(`Could not fetch body for ${owner}/${name} PR #${pr}: ${detail}`);
78
79
  }
79
- return { nodeId: pullRequest.id, body: pullRequest.body ?? "" };
80
+ return {
81
+ nodeId: pullRequest.id,
82
+ body: pullRequest.body ?? "",
83
+ ...(pullRequest.viewerCanUpdate !== undefined && {
84
+ viewerCanUpdate: pullRequest.viewerCanUpdate,
85
+ }),
86
+ };
80
87
  }
81
88
  /** Overwrites the PR body. */
82
89
  export async function updatePullRequestBody(pullRequestId, body) {
@@ -29,6 +29,8 @@ query BatchPrPage(
29
29
  id
30
30
  isResolved
31
31
  isOutdated
32
+ viewerCanReply
33
+ viewerCanResolve
32
34
  path
33
35
  line
34
36
  startLine
@@ -42,6 +44,7 @@ query BatchPrPage(
42
44
  isMinimized
43
45
  url
44
46
  authorAssociation
47
+ viewerDidAuthor
45
48
  author {
46
49
  __typename
47
50
  login
@@ -66,6 +69,7 @@ query BatchPrPage(
66
69
  nodes {
67
70
  id
68
71
  isMinimized
72
+ viewerCanMinimize
69
73
  url
70
74
  authorAssociation
71
75
  author {
@@ -108,6 +112,7 @@ query BatchPrPage(
108
112
  nodes {
109
113
  id
110
114
  isMinimized
115
+ viewerCanMinimize
111
116
  authorAssociation
112
117
  author {
113
118
  __typename
@@ -126,6 +131,7 @@ query BatchPrPage(
126
131
  nodes {
127
132
  id
128
133
  isMinimized
134
+ viewerCanMinimize
129
135
  authorAssociation
130
136
  author {
131
137
  __typename
@@ -2,11 +2,17 @@
2
2
  # re-select every connection (GraphQL points scale with possible nodes).
3
3
  query BatchPr($owner: String!, $repo: String!, $pr: Int!) {
4
4
  repository(owner: $owner, name: $repo) {
5
+ viewerPermission
6
+ viewerCanAdminister
5
7
  pullRequest(number: $pr) {
6
8
  id
7
9
  number
8
10
  state
9
11
  isDraft
12
+ viewerDidAuthor
13
+ viewerCanUpdate
14
+ viewerCanEnableAutoMerge
15
+ viewerCanEditFiles
10
16
  mergeable
11
17
  mergeStateStatus
12
18
  reviewDecision
@@ -14,6 +20,7 @@ query BatchPr($owner: String!, $repo: String!, $pr: Int!) {
14
20
  headRefName
15
21
  headRepository {
16
22
  nameWithOwner
23
+ viewerPermission
17
24
  }
18
25
  baseRefName
19
26
  isInMergeQueue
@@ -146,6 +153,8 @@ query BatchPr($owner: String!, $repo: String!, $pr: Int!) {
146
153
  id
147
154
  isResolved
148
155
  isOutdated
156
+ viewerCanReply
157
+ viewerCanResolve
149
158
  path
150
159
  line
151
160
  startLine
@@ -159,6 +168,7 @@ query BatchPr($owner: String!, $repo: String!, $pr: Int!) {
159
168
  isMinimized
160
169
  url
161
170
  authorAssociation
171
+ viewerDidAuthor
162
172
  author {
163
173
  __typename
164
174
  login
@@ -183,6 +193,7 @@ query BatchPr($owner: String!, $repo: String!, $pr: Int!) {
183
193
  nodes {
184
194
  id
185
195
  isMinimized
196
+ viewerCanMinimize
186
197
  url
187
198
  authorAssociation
188
199
  author {
@@ -220,6 +231,7 @@ query BatchPr($owner: String!, $repo: String!, $pr: Int!) {
220
231
  nodes {
221
232
  id
222
233
  isMinimized
234
+ viewerCanMinimize
223
235
  authorAssociation
224
236
  author {
225
237
  __typename
@@ -240,6 +252,7 @@ query BatchPr($owner: String!, $repo: String!, $pr: Int!) {
240
252
  nodes {
241
253
  id
242
254
  isMinimized
255
+ viewerCanMinimize
243
256
  authorAssociation
244
257
  author {
245
258
  __typename
@@ -3,6 +3,7 @@ query GetPrBody($owner: String!, $repo: String!, $pr: Int!) {
3
3
  pullRequest(number: $pr) {
4
4
  id
5
5
  body
6
+ viewerCanUpdate
6
7
  }
7
8
  }
8
9
  }
@@ -12,6 +12,7 @@ query ReviewThreadComments($threadId: ID!, $commentsCursor: String) {
12
12
  isMinimized
13
13
  url
14
14
  authorAssociation
15
+ viewerDidAuthor
15
16
  author {
16
17
  __typename
17
18
  login
@@ -24,6 +24,7 @@ query SuggestionThreads($owner: String!, $repo: String!, $pr: Int!, $threadIds:
24
24
  isMinimized
25
25
  url
26
26
  authorAssociation
27
+ viewerDidAuthor
27
28
  author {
28
29
  __typename
29
30
  login
@@ -41,6 +41,7 @@ function parseThread(raw, pr, threadId) {
41
41
  ...(comment?.authorAssociation !== undefined && {
42
42
  authorAssociation: comment.authorAssociation,
43
43
  }),
44
+ ...(comment?.viewerDidAuthor === true && { viewerDidAuthor: true }),
44
45
  body: comment?.body ?? "",
45
46
  url: comment?.url ?? "",
46
47
  createdAtUnix: comment?.createdAt ? parseCreatedAt(comment.createdAt) : 0,
@@ -11,7 +11,7 @@ const QUALIFIED_PR_ERROR = "pr must be a GitHub pull-request URL or an owner/rep
11
11
  const pr = z
12
12
  .string()
13
13
  .refine(isRepositoryQualifiedPrReference, { message: QUALIFIED_PR_ERROR })
14
- .describe("GitHub pull-request URL or owner/repo#number");
14
+ .describe("GitHub pull-request URL or owner/repo#number; the explicit repository may differ from the server working directory");
15
15
  const ids = z.array(z.string().min(1)).optional();
16
16
  const iterateInputSchema = z.object({
17
17
  pr,
@@ -19,8 +19,14 @@ const iterateInputSchema = z.object({
19
19
  stallTimeoutSeconds: z.number().nonnegative().optional(),
20
20
  noAutoMarkReady: z.boolean().optional(),
21
21
  merge: z.boolean().optional(),
22
- noAutoCancelActionable: z.boolean().optional(),
23
- neverCancelRuns: z.array(z.string()).optional(),
22
+ noAutoCancelActionable: z
23
+ .boolean()
24
+ .optional()
25
+ .describe("Deprecated no-op; Shepherd never cancels workflow runs."),
26
+ neverCancelRuns: z
27
+ .array(z.string())
28
+ .optional()
29
+ .describe("Deprecated per-call no-op retained for compatibility."),
24
30
  });
25
31
  const reviewMutationsOperationSchema = z.object({
26
32
  type: z.literal("review_mutations"),
@@ -34,7 +40,7 @@ const reviewMutationsOperationSchema = z.object({
34
40
  const markFilesViewedOperationSchema = z.object({
35
41
  type: z.literal("mark_files_viewed"),
36
42
  files: z.array(z.string().min(1)).optional(),
37
- tests: z.boolean().optional(),
43
+ tests: z.boolean().optional().describe("Select changed test files; no mutation occurs."),
38
44
  matchPatterns: z.array(z.string().min(1)).optional(),
39
45
  });
40
46
  const appendJournalOperationSchema = z.object({
@@ -73,7 +79,7 @@ export function createPrShepherdMcpServer(options = {}) {
73
79
  const shepherd = options.shepherd ?? createPrShepherd({ cwd: options.cwd });
74
80
  const server = new McpServer({ name: "pr-shepherd", version: readPackageVersion() });
75
81
  server.registerTool("iterate", {
76
- description: "Inspect the current pull request and return the next Shepherd state.",
82
+ description: "Inspect the specified pull request and return the next Shepherd state.",
77
83
  inputSchema: iterateInputSchema,
78
84
  annotations: {
79
85
  readOnlyHint: false,
@@ -83,7 +89,7 @@ export function createPrShepherdMcpServer(options = {}) {
83
89
  },
84
90
  }, async (input) => runTool(() => shepherd.iterate(requireRepositoryQualifiedPr(input)), formatIterateResult));
85
91
  server.registerTool("apply", {
86
- description: "Apply ordered review, file-view, and journal operations after prevalidation.",
92
+ description: "Apply ordered authorized review and journal operations, or run selection-only file-view diagnostics, after prevalidation.",
87
93
  inputSchema: applyInputSchema,
88
94
  annotations: {
89
95
  readOnlyHint: false,
@@ -2,6 +2,19 @@ export interface ParsedPrReference {
2
2
  number?: number;
3
3
  repository?: string;
4
4
  }
5
+ export interface ResolvedPrTarget {
6
+ prNumber?: number;
7
+ targetRepository?: {
8
+ owner: string;
9
+ name: string;
10
+ };
11
+ }
5
12
  /** Parses API PR references without performing repository or GitHub I/O. */
6
13
  export declare function parsePrReference(pr: number | string | undefined): ParsedPrReference | null;
14
+ /** Parses a positional CLI PR reference, including a bare numeric string. */
15
+ export declare function parseCliPrReference(value: string): ParsedPrReference | null;
16
+ /** Converts a validated parsed reference into the command-layer target shape. */
17
+ export declare function resolveParsedPrTarget(parsed: ParsedPrReference): ResolvedPrTarget;
18
+ /** Canonical collision-safe PR reference for generated commands and handoffs. */
19
+ export declare function formatPrUrl(repository: string, prNumber: number): string;
7
20
  export declare function isRepositoryQualifiedPrReference(pr: unknown): pr is string;
@@ -18,7 +18,7 @@ export function parsePrReference(pr) {
18
18
  const parts = url.pathname.split("/").filter(Boolean);
19
19
  if ((url.protocol === "https:" || url.protocol === "http:") &&
20
20
  (url.hostname === "github.com" || url.hostname === "www.github.com") &&
21
- parts.length === 4 &&
21
+ parts.length >= 4 &&
22
22
  parts[2] === "pull" &&
23
23
  /^[1-9][0-9]*$/.test(parts[3])) {
24
24
  return { number: Number(parts[3]), repository: `${parts[0]}/${parts[1]}` };
@@ -29,6 +29,28 @@ export function parsePrReference(pr) {
29
29
  }
30
30
  return null;
31
31
  }
32
+ /** Parses a positional CLI PR reference, including a bare numeric string. */
33
+ export function parseCliPrReference(value) {
34
+ if (/^[0-9]+$/.test(value))
35
+ return { number: Number(value) };
36
+ return parsePrReference(value);
37
+ }
38
+ /** Converts a validated parsed reference into the command-layer target shape. */
39
+ export function resolveParsedPrTarget(parsed) {
40
+ if (parsed.repository === undefined)
41
+ return { prNumber: parsed.number };
42
+ const [owner, name] = parsed.repository.split("/");
43
+ if (!owner || !name)
44
+ throw new Error(`Invalid repository reference: ${parsed.repository}`);
45
+ return {
46
+ prNumber: parsed.number,
47
+ targetRepository: { owner, name },
48
+ };
49
+ }
50
+ /** Canonical collision-safe PR reference for generated commands and handoffs. */
51
+ export function formatPrUrl(repository, prNumber) {
52
+ return `https://github.com/${repository}/pull/${prNumber}`;
53
+ }
32
54
  export function isRepositoryQualifiedPrReference(pr) {
33
55
  if (typeof pr !== "string")
34
56
  return false;
@@ -13,6 +13,8 @@ export function toAgentThread(t) {
13
13
  const suggestion = extractSuggestion(t) ?? undefined;
14
14
  return {
15
15
  id: t.id,
16
+ ...(t.viewerCanReply === false && { viewerCanReply: false }),
17
+ ...(t.viewerCanResolve === false && { viewerCanResolve: false }),
16
18
  ...(t.reviewId !== undefined && { reviewId: t.reviewId }),
17
19
  path: t.path,
18
20
  line: t.line,
@@ -22,6 +24,7 @@ export function toAgentThread(t) {
22
24
  author: t.author,
23
25
  ...(t.authorType !== undefined && { authorType: t.authorType }),
24
26
  ...(t.authorAssociation !== undefined && { authorAssociation: t.authorAssociation }),
27
+ ...(t.viewerDidAuthor === true && { viewerDidAuthor: true }),
25
28
  body: t.body,
26
29
  url: t.url,
27
30
  ...(t.edited === true && { edited: true }),
@@ -31,6 +34,7 @@ export function toAgentThread(t) {
31
34
  author: c.author,
32
35
  ...(c.authorType !== undefined && { authorType: c.authorType }),
33
36
  ...(c.authorAssociation !== undefined && { authorAssociation: c.authorAssociation }),
37
+ ...(c.viewerDidAuthor === true && { viewerDidAuthor: true }),
34
38
  body: c.body,
35
39
  url: c.url,
36
40
  })),
@@ -41,6 +45,7 @@ export function toAgentThread(t) {
41
45
  export function toAgentComment(c) {
42
46
  return {
43
47
  id: c.id,
48
+ ...(c.viewerCanMinimize === false && { viewerCanMinimize: false }),
44
49
  author: c.author,
45
50
  ...(c.authorType !== undefined && { authorType: c.authorType }),
46
51
  ...(c.authorAssociation !== undefined && { authorAssociation: c.authorAssociation }),
@@ -4,11 +4,13 @@ export declare function threadComments(thread: {
4
4
  author: string;
5
5
  authorType?: ReviewThreadComment["authorType"];
6
6
  authorAssociation?: ReviewThreadComment["authorAssociation"];
7
+ viewerDidAuthor?: true;
7
8
  url?: string;
8
9
  createdAtUnix?: number;
9
10
  comments?: Array<Pick<ReviewThreadComment, "id" | "author" | "body" | "url"> & {
10
11
  authorType?: ReviewThreadComment["authorType"];
11
12
  authorAssociation?: ReviewThreadComment["authorAssociation"];
13
+ viewerDidAuthor?: true;
12
14
  } & Partial<Pick<ReviewThreadComment, "isMinimized" | "createdAtUnix">>>;
13
15
  }): ReviewThreadComment[];
14
16
  export declare function threadTranscriptBody(thread: ReviewThread, appendedBodies?: string[]): string;
@@ -6,6 +6,7 @@ export function threadComments(thread) {
6
6
  author: c.author,
7
7
  authorType: c.authorType ?? "Unknown",
8
8
  ...(c.authorAssociation !== undefined && { authorAssociation: c.authorAssociation }),
9
+ ...(c.viewerDidAuthor === true && { viewerDidAuthor: true }),
9
10
  body: c.body,
10
11
  url: c.url,
11
12
  createdAtUnix: c.createdAtUnix ?? 0,
@@ -20,6 +21,7 @@ export function threadComments(thread) {
20
21
  ...(thread.authorAssociation !== undefined && {
21
22
  authorAssociation: thread.authorAssociation,
22
23
  }),
24
+ ...(thread.viewerDidAuthor === true && { viewerDidAuthor: true }),
23
25
  body: thread.body,
24
26
  url: thread.url ?? "",
25
27
  createdAtUnix: thread.createdAtUnix ?? 0,
@@ -4,6 +4,7 @@ export interface AgentThreadComment {
4
4
  author: string;
5
5
  authorType?: AuthorType;
6
6
  authorAssociation?: CommentAuthorAssociation;
7
+ viewerDidAuthor?: true;
7
8
  body: string;
8
9
  url: string;
9
10
  }
@@ -1,7 +1,7 @@
1
1
  import type { AgentComment, AgentThread } from "./report.mts";
2
2
  import type { CheckStatus, Review } from "./github.mts";
3
3
  import type { MergeQueueRemovalStatus } from "./merge-requirements.mts";
4
- export type EscalateTrigger = "fix-thrash" | "base-branch-unknown" | "stall-timeout" | "thread-missing-location" | "bot-cr-not-dismissed" | "merge-queue-removed";
4
+ export type EscalateTrigger = "fix-thrash" | "base-branch-unknown" | "stall-timeout" | "thread-missing-location" | "authorization-required" | "bot-cr-not-dismissed" | "merge-queue-removed";
5
5
  export interface AgentStalledCheck {
6
6
  name: string;
7
7
  status: CheckStatus;
@@ -27,4 +27,9 @@ export interface EscalateDetails {
27
27
  suggestion: string;
28
28
  humanMessage: string;
29
29
  mergeQueueRemoval?: MergeQueueRemovalStatus;
30
+ authorization?: Array<{
31
+ action: "reply-thread" | "resolve-thread" | "dismiss-review" | "mark-ready" | "merge-or-enqueue";
32
+ targetIds: string[];
33
+ reason: "denied-or-unverifiable";
34
+ }>;
30
35
  }
@@ -7,6 +7,17 @@ export type MergeableState = "CONFLICTING" | "MERGEABLE" | "UNKNOWN";
7
7
  export type MergeStateStatus = "BEHIND" | "BLOCKED" | "CLEAN" | "DIRTY" | "DRAFT" | "HAS_HOOKS" | "UNKNOWN" | "UNSTABLE";
8
8
  export type ReviewDecision = "APPROVED" | "CHANGES_REQUESTED" | "REVIEW_REQUIRED" | null;
9
9
  export type AuthorType = "User" | "Bot" | "Unknown";
10
+ type RepositoryPermission = "NONE" | "READ" | "TRIAGE" | "WRITE" | "MAINTAIN" | "ADMIN";
11
+ /** Raw GitHub viewer fields used to gate remote actions. */
12
+ export interface ViewerAuthorization {
13
+ repositoryPermission: RepositoryPermission | null;
14
+ viewerCanAdminister: boolean;
15
+ viewerDidAuthor: boolean;
16
+ viewerCanUpdate: boolean;
17
+ viewerCanEnableAutoMerge: boolean;
18
+ viewerCanEditFiles: boolean;
19
+ headRepositoryPermission: RepositoryPermission | null;
20
+ }
10
21
  /** Raw relationship between a comment author and the repository, as reported by GitHub. */
11
22
  export type CommentAuthorAssociation = "COLLABORATOR" | "CONTRIBUTOR" | "FIRST_TIMER" | "FIRST_TIME_CONTRIBUTOR" | "MANNEQUIN" | "MEMBER" | "NONE" | "OWNER";
12
23
  export interface CheckRun {
@@ -40,6 +51,8 @@ export interface ReviewThread {
40
51
  isResolved: boolean;
41
52
  isOutdated: boolean;
42
53
  isMinimized: boolean;
54
+ viewerCanReply?: boolean;
55
+ viewerCanResolve?: boolean;
43
56
  path: string | null;
44
57
  line: number | null;
45
58
  /** Start of the comment's line range. Null for single-line comments (use `line` for both). */
@@ -49,6 +62,7 @@ export interface ReviewThread {
49
62
  author: string;
50
63
  authorType: AuthorType;
51
64
  authorAssociation?: CommentAuthorAssociation;
65
+ viewerDidAuthor?: true;
52
66
  body: string;
53
67
  url: string;
54
68
  createdAtUnix?: number;
@@ -70,6 +84,7 @@ export interface SuggestionBlock {
70
84
  export interface PrComment {
71
85
  id: string;
72
86
  isMinimized: boolean;
87
+ viewerCanMinimize?: boolean;
73
88
  author: string;
74
89
  authorType: AuthorType;
75
90
  authorAssociation?: CommentAuthorAssociation;
@@ -82,6 +97,8 @@ export interface Review {
82
97
  author: string;
83
98
  authorType: AuthorType;
84
99
  authorAssociation?: CommentAuthorAssociation;
100
+ /** Present for minimizable COMMENTED/APPROVED reviews; false for non-minimizable review rows. */
101
+ viewerCanMinimize?: boolean;
85
102
  body: string;
86
103
  createdAtUnix?: number;
87
104
  edited?: boolean;
@@ -123,6 +140,7 @@ export interface BatchPrData extends BatchPrMergeFields {
123
140
  headRefName: string;
124
141
  /** `"owner/name"` of the head repository; null when the fork has been deleted. */
125
142
  headRepoWithOwner: string | null;
143
+ viewerAuthorization?: ViewerAuthorization;
126
144
  baseRefName: string;
127
145
  reviewRequests: Array<{
128
146
  login: string;
@@ -144,3 +162,4 @@ export interface BatchPrData extends BatchPrMergeFields {
144
162
  branchProtection: BranchProtection | null;
145
163
  activity?: PrActivitySummary;
146
164
  }
165
+ export {};
@@ -59,9 +59,9 @@ export interface ResolveCommand {
59
59
  requiresHeadSha: boolean;
60
60
  /** Whether the model must substitute $DISMISS_MESSAGE with a specific description of the fix. */
61
61
  requiresDismissMessage: boolean;
62
- /** Thread IDs that should receive a reply instead of a resolve mutation. */
62
+ /** Thread IDs that should receive a reply. Viewer-authored human IDs may also appear in resolveThreadIds. */
63
63
  replyThreadIds?: string[];
64
- /** Thread IDs that should be resolved on GitHub. Human-authored IDs must not appear here. */
64
+ /** Thread IDs that should be resolved on GitHub. Human IDs are allowed only for authenticated-viewer reply+resolve pairs or marker-ended retries. */
65
65
  resolveThreadIds?: string[];
66
66
  /** Bot/non-human CHANGES_REQUESTED review IDs to dismiss. Human-authored IDs must not appear here. */
67
67
  dismissReviewIds?: string[];
@@ -69,8 +69,8 @@ export interface ResolveCommand {
69
69
  hasMutations: boolean;
70
70
  }
71
71
  /**
72
- * Default fix_code variant: agent applies edits locally, commits, pushes,
73
- * then runs the pre-built apply command. Emitted under `## Post-fix push`.
72
+ * Default fix_code variant: agent applies edits locally, commits when needed,
73
+ * then runs the pre-built apply command. Emitted under `## Post-fix actions`.
74
74
  */
75
75
  interface FixRebaseAndPush {
76
76
  threads: AgentThread[];
@@ -90,13 +90,13 @@ interface FixRebaseAndPush {
90
90
  changesRequestedReviews: Review[];
91
91
  /** Pre-built apply command. Run after committing and pushing. */
92
92
  resolveCommand: ResolveCommand;
93
- /** When present, run this command first (no SHA substitution needed) to resolve bot threads and minimize comments, independent of any push. */
93
+ /** When present, run this command first (no SHA substitution needed) for standalone thread resolves and comment minimization, independent of any push. */
94
94
  resolveOnlyCommand?: ResolveCommand;
95
95
  /** Ordered steps for the model to follow. */
96
96
  instructions: string[];
97
- /** Run IDs of in-progress GitHub Actions checks. The agent should cancel these before pushing new commits; if it decides not to push (e.g. resolve-only), it may skip cancellation. Empty when all in-progress runs are external status checks or already cancelled. */
97
+ /** Reserved compatibility field. Always empty because Shepherd cannot verify workflow-cancellation authorization. */
98
98
  inProgressRunIds: string[];
99
- /** Workflow runs deliberately excluded from cancellation by actions.neverCancelRuns. */
99
+ /** Reserved compatibility field. Always empty because Shepherd never recommends workflow cancellation. */
100
100
  protectedRuns: ProtectedRun[];
101
101
  /** Requeue command emitted after merge-group remediation. */
102
102
  requeue?: MergeCommandPlan;
@@ -127,10 +127,11 @@ export type IterateResult = IterateResultWait | IterateResultCancel | IterateRes
127
127
  export interface IterateCommandOptions extends GlobalOptions {
128
128
  readyDelaySeconds?: number;
129
129
  noAutoMarkReady?: boolean;
130
+ /** Legacy no-op retained for API compatibility; workflow runs are never cancelled. */
130
131
  noAutoCancelActionable?: boolean;
131
132
  /** Override stall timeout seconds. Defaults to config.iterate.stallTimeoutMinutes * 60. */
132
133
  stallTimeoutSeconds?: number;
133
- /** Case-insensitive workflow/check glob patterns Shepherd must not cancel. */
134
+ /** Legacy per-invocation no-op retained for API compatibility. */
134
135
  neverCancelRuns?: string[];
135
136
  persistSeen?: boolean;
136
137
  /** Shepherd through readiness and emit the exact merge/queue command when ready. */