pr-shepherd 0.42.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 (145) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +24 -18
  3. package/bin/api.d.mts +3 -2
  4. package/bin/api.mjs +13 -18
  5. package/bin/checks/classify.d.mts +3 -1
  6. package/bin/checks/classify.mjs +5 -2
  7. package/bin/cli/args.mjs +14 -9
  8. package/bin/cli/default-poll.mjs +1 -0
  9. package/bin/cli/fix-formatter.mjs +20 -8
  10. package/bin/cli/handlers.mjs +1 -0
  11. package/bin/cli/help-command-pages.d.mts +9 -6
  12. package/bin/cli/help-command-pages.mjs +7 -4
  13. package/bin/cli/help-iterate-poll-pages.d.mts +2 -2
  14. package/bin/cli/help-iterate-poll-pages.mjs +9 -4
  15. package/bin/cli/help-top-page.d.mts +1 -1
  16. package/bin/cli/help-top-page.mjs +2 -2
  17. package/bin/cli/help.d.mts +10 -7
  18. package/bin/cli/iterate-flags.d.mts +1 -0
  19. package/bin/cli/iterate-flags.mjs +2 -0
  20. package/bin/cli/iterate-formatter.d.mts +1 -1
  21. package/bin/cli/iterate-formatter.mjs +9 -6
  22. package/bin/cli/iterate-instructions.mjs +15 -0
  23. package/bin/cli/iterate-lean.d.mts +1 -2
  24. package/bin/cli/iterate-lean.mjs +16 -17
  25. package/bin/cli/iterate-merge-formatter.d.mts +3 -0
  26. package/bin/cli/iterate-merge-formatter.mjs +52 -0
  27. package/bin/cli/iterate-verbose.d.mts +6 -0
  28. package/bin/cli/iterate-verbose.mjs +14 -0
  29. package/bin/cli/journal-formatter.mjs +3 -0
  30. package/bin/cli/journal-handler.mjs +11 -7
  31. package/bin/cli/list-formatters.d.mts +3 -1
  32. package/bin/cli/list-formatters.mjs +9 -7
  33. package/bin/cli/mark-files-as-viewed-formatter.mjs +7 -1
  34. package/bin/cli/mutate-formatter.mjs +4 -0
  35. package/bin/cli/poll-handler.mjs +1 -0
  36. package/bin/cli/runner.mjs +4 -3
  37. package/bin/commands/check-terminal-report.mjs +1 -0
  38. package/bin/commands/check.mjs +64 -7
  39. package/bin/commands/commit-suggestion-instruction.d.mts +1 -1
  40. package/bin/commands/commit-suggestion-instruction.mjs +2 -2
  41. package/bin/commands/commit-suggestion.mjs +3 -1
  42. package/bin/commands/iterate/base.d.mts +5 -0
  43. package/bin/commands/iterate/base.mjs +25 -0
  44. package/bin/commands/iterate/check-instructions.d.mts +5 -19
  45. package/bin/commands/iterate/check-instructions.mjs +24 -25
  46. package/bin/commands/iterate/classify.d.mts +2 -2
  47. package/bin/commands/iterate/classify.mjs +43 -25
  48. package/bin/commands/iterate/escalate.d.mts +3 -1
  49. package/bin/commands/iterate/escalate.mjs +29 -2
  50. package/bin/commands/iterate/fix-code.mjs +81 -26
  51. package/bin/commands/iterate/helpers.d.mts +0 -2
  52. package/bin/commands/iterate/helpers.mjs +6 -17
  53. package/bin/commands/iterate/index.mjs +31 -43
  54. package/bin/commands/iterate/mark-ready.d.mts +2 -0
  55. package/bin/commands/iterate/mark-ready.mjs +38 -0
  56. package/bin/commands/iterate/merge-state.d.mts +15 -0
  57. package/bin/commands/iterate/merge-state.mjs +82 -0
  58. package/bin/commands/iterate/merge.d.mts +13 -0
  59. package/bin/commands/iterate/merge.mjs +45 -0
  60. package/bin/commands/iterate/render.d.mts +2 -2
  61. package/bin/commands/iterate/render.mjs +19 -20
  62. package/bin/commands/iterate/stall.mjs +4 -2
  63. package/bin/commands/iterate/thread-mutation-routing.d.mts +9 -0
  64. package/bin/commands/iterate/thread-mutation-routing.mjs +29 -0
  65. package/bin/commands/journal/index.d.mts +5 -0
  66. package/bin/commands/journal/index.mjs +11 -2
  67. package/bin/commands/mark-files-as-viewed.d.mts +2 -1
  68. package/bin/commands/mark-files-as-viewed.mjs +5 -92
  69. package/bin/commands/poll.mjs +3 -1
  70. package/bin/commands/resolve-mutate.mjs +51 -9
  71. package/bin/commands/shepherd-journal.d.mts +1 -1
  72. package/bin/commands/shepherd-journal.mjs +2 -2
  73. package/bin/commands/suggestion-patch-item.d.mts +1 -1
  74. package/bin/commands/suggestion-patch-item.mjs +3 -11
  75. package/bin/commands/suggestion-patches.mjs +3 -3
  76. package/bin/comments/authors.d.mts +6 -0
  77. package/bin/comments/authors.mjs +5 -0
  78. package/bin/comments/marker.d.mts +6 -1
  79. package/bin/comments/marker.mjs +8 -1
  80. package/bin/comments/resolve.d.mts +4 -0
  81. package/bin/comments/thread-visibility.mjs +10 -10
  82. package/bin/comments/visible-comments.mjs +2 -1
  83. package/bin/config/load.d.mts +5 -1
  84. package/bin/config/load.mjs +34 -0
  85. package/bin/config/merge-command-args.d.mts +2 -0
  86. package/bin/config/merge-command-args.mjs +44 -0
  87. package/bin/config.json +3 -0
  88. package/bin/exit-codes.d.mts +2 -0
  89. package/bin/exit-codes.mjs +4 -0
  90. package/bin/github/batch-parse-checks.d.mts +3 -0
  91. package/bin/github/batch-parse-checks.mjs +29 -0
  92. package/bin/github/batch-parsers-rules.d.mts +3 -1
  93. package/bin/github/batch-parsers-rules.mjs +33 -0
  94. package/bin/github/batch-parsers.d.mts +2 -2
  95. package/bin/github/batch-parsers.mjs +40 -27
  96. package/bin/github/batch-raw-rules.d.mts +48 -0
  97. package/bin/github/batch-raw-types.d.mts +12 -0
  98. package/bin/github/batch-raw-types.mjs +0 -1
  99. package/bin/github/batch.mjs +3 -1
  100. package/bin/github/client.d.mts +1 -0
  101. package/bin/github/client.mjs +8 -1
  102. package/bin/github/gql/batch-pr-page.gql +6 -0
  103. package/bin/github/gql/batch-pr.gql +105 -0
  104. package/bin/github/gql/commit-check-contexts.gql +58 -0
  105. package/bin/github/gql/get-pr-body.gql +1 -0
  106. package/bin/github/gql/review-thread-comments.gql +1 -0
  107. package/bin/github/gql/suggestion-threads.gql +1 -0
  108. package/bin/github/merge-queue-checks.d.mts +4 -0
  109. package/bin/github/merge-queue-checks.mjs +48 -0
  110. package/bin/github/queries.d.mts +2 -0
  111. package/bin/github/queries.mjs +2 -0
  112. package/bin/github/suggestion-thread.mjs +1 -0
  113. package/bin/mcp/server.mjs +13 -6
  114. package/bin/pr-reference.d.mts +13 -0
  115. package/bin/pr-reference.mjs +23 -1
  116. package/bin/reporters/agent.mjs +11 -3
  117. package/bin/threads/transcript.d.mts +2 -0
  118. package/bin/threads/transcript.mjs +2 -0
  119. package/bin/types/activity.d.mts +2 -0
  120. package/bin/types/agent-thread.d.mts +1 -0
  121. package/bin/types/escalate.d.mts +35 -0
  122. package/bin/types/escalate.mjs +1 -0
  123. package/bin/types/github.d.mts +27 -0
  124. package/bin/types/github.mjs +0 -3
  125. package/bin/types/iterate.d.mts +23 -39
  126. package/bin/types/merge-action.d.mts +12 -0
  127. package/bin/types/merge-action.mjs +1 -0
  128. package/bin/types/merge-queue.d.mts +13 -0
  129. package/bin/types/merge-queue.mjs +1 -0
  130. package/bin/types/merge-requirements.d.mts +17 -0
  131. package/bin/types/report.d.mts +22 -1
  132. package/bin/types/review-thread.d.mts +1 -0
  133. package/bin/types/suggestion-patch.d.mts +1 -1
  134. package/bin/types.d.mts +3 -0
  135. package/bin/types.mjs +3 -0
  136. package/bin/util/markdown.d.mts +2 -0
  137. package/bin/util/markdown.mjs +7 -0
  138. package/package.json +1 -1
  139. package/plugins/pr-shepherd/.codex-plugin/plugin.json +1 -1
  140. package/plugins/pr-shepherd/.codex.mcp.json +1 -1
  141. package/plugins/pr-shepherd/.mcp.json +1 -1
  142. package/plugins/pr-shepherd/skills/mark-files-as-viewed/SKILL.md +3 -3
  143. package/plugins/pr-shepherd/skills/pr-shepherd/SKILL.md +17 -15
  144. package/bin/commands/iterate/reruns.d.mts +0 -20
  145. package/bin/commands/iterate/reruns.mjs +0 -105
@@ -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,14 +20,50 @@ 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
20
27
  isMergeQueueEnabled
28
+ autoMergeRequest {
29
+ enabledAt
30
+ mergeMethod
31
+ enabledBy {
32
+ login
33
+ }
34
+ }
21
35
  mergeQueueEntry {
22
36
  position
23
37
  state
24
38
  estimatedTimeToMerge
39
+ enqueuedAt
40
+ enqueuer {
41
+ login
42
+ }
43
+ headCommit {
44
+ ...QueueCheckCommit
45
+ }
46
+ }
47
+ mergeQueueAdditions: timelineItems(last: 1, itemTypes: [ADDED_TO_MERGE_QUEUE_EVENT]) {
48
+ nodes {
49
+ ... on AddedToMergeQueueEvent {
50
+ createdAt
51
+ }
52
+ }
53
+ }
54
+ mergeQueueRemovals: timelineItems(last: 1, itemTypes: [REMOVED_FROM_MERGE_QUEUE_EVENT]) {
55
+ nodes {
56
+ ... on RemovedFromMergeQueueEvent {
57
+ reason
58
+ createdAt
59
+ actor {
60
+ login
61
+ }
62
+ beforeCommit {
63
+ ...QueueCheckCommit
64
+ }
65
+ }
66
+ }
25
67
  }
26
68
  stack {
27
69
  number
@@ -111,6 +153,8 @@ query BatchPr($owner: String!, $repo: String!, $pr: Int!) {
111
153
  id
112
154
  isResolved
113
155
  isOutdated
156
+ viewerCanReply
157
+ viewerCanResolve
114
158
  path
115
159
  line
116
160
  startLine
@@ -124,6 +168,7 @@ query BatchPr($owner: String!, $repo: String!, $pr: Int!) {
124
168
  isMinimized
125
169
  url
126
170
  authorAssociation
171
+ viewerDidAuthor
127
172
  author {
128
173
  __typename
129
174
  login
@@ -148,6 +193,7 @@ query BatchPr($owner: String!, $repo: String!, $pr: Int!) {
148
193
  nodes {
149
194
  id
150
195
  isMinimized
196
+ viewerCanMinimize
151
197
  url
152
198
  authorAssociation
153
199
  author {
@@ -185,6 +231,7 @@ query BatchPr($owner: String!, $repo: String!, $pr: Int!) {
185
231
  nodes {
186
232
  id
187
233
  isMinimized
234
+ viewerCanMinimize
188
235
  authorAssociation
189
236
  author {
190
237
  __typename
@@ -205,6 +252,7 @@ query BatchPr($owner: String!, $repo: String!, $pr: Int!) {
205
252
  nodes {
206
253
  id
207
254
  isMinimized
255
+ viewerCanMinimize
208
256
  authorAssociation
209
257
  author {
210
258
  __typename
@@ -289,3 +337,60 @@ query BatchPr($owner: String!, $repo: String!, $pr: Int!) {
289
337
  }
290
338
  }
291
339
  }
340
+
341
+ fragment QueueCheckCommit on Commit {
342
+ oid
343
+ committedDate
344
+ parents(first: 100) {
345
+ nodes {
346
+ oid
347
+ }
348
+ }
349
+ statusCheckRollup {
350
+ contexts(first: 100) {
351
+ pageInfo {
352
+ hasNextPage
353
+ endCursor
354
+ }
355
+ nodes {
356
+ __typename
357
+ ... on CheckRun {
358
+ id
359
+ name
360
+ status
361
+ conclusion
362
+ detailsUrl
363
+ completedAt
364
+ startedAt
365
+ title
366
+ summary
367
+ annotations(first: 1) {
368
+ nodes {
369
+ message
370
+ }
371
+ }
372
+ checkSuite {
373
+ createdAt
374
+ updatedAt
375
+ workflowRun {
376
+ event
377
+ createdAt
378
+ updatedAt
379
+ workflow {
380
+ name
381
+ databaseId
382
+ }
383
+ }
384
+ }
385
+ }
386
+ ... on StatusContext {
387
+ context
388
+ state
389
+ createdAt
390
+ targetUrl
391
+ description
392
+ }
393
+ }
394
+ }
395
+ }
396
+ }
@@ -0,0 +1,58 @@
1
+ query CommitCheckContexts($owner: String!, $repo: String!, $oid: String!, $cursor: String) {
2
+ repository(owner: $owner, name: $repo) {
3
+ object(expression: $oid) {
4
+ __typename
5
+ ... on Commit {
6
+ oid
7
+ statusCheckRollup {
8
+ contexts(first: 100, after: $cursor) {
9
+ pageInfo {
10
+ hasNextPage
11
+ endCursor
12
+ }
13
+ nodes {
14
+ __typename
15
+ ... on CheckRun {
16
+ id
17
+ name
18
+ status
19
+ conclusion
20
+ detailsUrl
21
+ completedAt
22
+ startedAt
23
+ title
24
+ summary
25
+ annotations(first: 1) {
26
+ nodes {
27
+ message
28
+ }
29
+ }
30
+ checkSuite {
31
+ createdAt
32
+ updatedAt
33
+ workflowRun {
34
+ event
35
+ createdAt
36
+ updatedAt
37
+ workflow {
38
+ name
39
+ databaseId
40
+ }
41
+ databaseId
42
+ }
43
+ }
44
+ }
45
+ ... on StatusContext {
46
+ context
47
+ state
48
+ description
49
+ targetUrl
50
+ createdAt
51
+ }
52
+ }
53
+ }
54
+ }
55
+ }
56
+ }
57
+ }
58
+ }
@@ -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
@@ -0,0 +1,4 @@
1
+ import type { RepoInfo } from "./client.mts";
2
+ import type { RawPr } from "./batch-raw-types.mts";
3
+ /** Hydrate all status contexts for the active or most recently removed queue commit. */
4
+ export declare function hydrateMergeQueueChecks(raw: RawPr, repo: RepoInfo): Promise<void>;
@@ -0,0 +1,48 @@
1
+ import { graphql } from "./client.mjs";
2
+ import { requireContextNodes } from "./batch-response.mjs";
3
+ import { COMMIT_CHECK_CONTEXTS_QUERY } from "./queries.mjs";
4
+ async function hydrateCommitContexts(commit, repo) {
5
+ const contexts = commit.statusCheckRollup?.contexts;
6
+ if (!contexts)
7
+ return;
8
+ contexts.nodes = requireContextNodes(contexts.nodes);
9
+ if (contexts.pageInfo.hasNextPage && !contexts.pageInfo.endCursor) {
10
+ throw new Error(`Merge queue check pagination interrupted: GitHub omitted the next cursor for ${commit.oid}. Retry.`);
11
+ }
12
+ let cursor = contexts.pageInfo.hasNextPage ? contexts.pageInfo.endCursor : null;
13
+ while (cursor) {
14
+ // eslint-disable-next-line no-await-in-loop
15
+ const result = await graphql(COMMIT_CHECK_CONTEXTS_QUERY, {
16
+ owner: repo.owner,
17
+ repo: repo.name,
18
+ oid: commit.oid,
19
+ cursor,
20
+ });
21
+ const object = result.data.repository?.object;
22
+ if (object?.__typename !== "Commit" || object.oid !== commit.oid) {
23
+ throw new Error(`Merge queue check pagination interrupted: commit ${commit.oid} disappeared or changed. Retry.`);
24
+ }
25
+ const next = object.statusCheckRollup?.contexts;
26
+ if (!next) {
27
+ throw new Error(`Merge queue check pagination interrupted: statusCheckRollup disappeared for ${commit.oid}. Retry.`);
28
+ }
29
+ contexts.nodes.push(...requireContextNodes(next.nodes));
30
+ contexts.pageInfo = next.pageInfo;
31
+ cursor = next.pageInfo.hasNextPage ? next.pageInfo.endCursor : null;
32
+ if (next.pageInfo.hasNextPage && !cursor) {
33
+ throw new Error(`Merge queue check pagination interrupted: GitHub omitted the next cursor for ${commit.oid}. Retry.`);
34
+ }
35
+ }
36
+ }
37
+ /** Hydrate all status contexts for the active or most recently removed queue commit. */
38
+ export async function hydrateMergeQueueChecks(raw, repo) {
39
+ const active = raw.mergeQueueEntry?.headCommit;
40
+ const removal = raw.mergeQueueRemovals?.nodes[0];
41
+ const addition = raw.mergeQueueAdditions?.nodes[0];
42
+ const removalIsCurrent = Boolean(removal && (!addition || Date.parse(removal.createdAt) >= Date.parse(addition.createdAt)));
43
+ const removed = removalIsCurrent ? removal?.beforeCommit : undefined;
44
+ if (active)
45
+ await hydrateCommitContexts(active, repo);
46
+ if (removed && removed.oid !== active?.oid)
47
+ await hydrateCommitContexts(removed, repo);
48
+ }
@@ -16,6 +16,8 @@ export declare const SUGGESTION_THREADS_QUERY: string;
16
16
  export declare const REVIEW_THREAD_COMMENTS_QUERY: string;
17
17
  /** Fetch inline annotations for a single CheckRun by node ID. */
18
18
  export declare const CHECK_RUN_ANNOTATIONS_QUERY: string;
19
+ /** Fetches additional status contexts for a merge queue synthetic commit. */
20
+ export declare const COMMIT_CHECK_CONTEXTS_QUERY: string;
19
21
  /** Returns the current head commit SHA for a PR. Used by waitForSha polling. */
20
22
  export declare const GET_PR_HEAD_SHA_QUERY: string;
21
23
  /** Look up PR number by branch name (for getCurrentPrNumber). */
@@ -19,6 +19,8 @@ export const SUGGESTION_THREADS_QUERY = gql("suggestion-threads.gql");
19
19
  export const REVIEW_THREAD_COMMENTS_QUERY = gql("review-thread-comments.gql");
20
20
  /** Fetch inline annotations for a single CheckRun by node ID. */
21
21
  export const CHECK_RUN_ANNOTATIONS_QUERY = gql("check-run-annotations.gql");
22
+ /** Fetches additional status contexts for a merge queue synthetic commit. */
23
+ export const COMMIT_CHECK_CONTEXTS_QUERY = gql("commit-check-contexts.gql");
22
24
  /** Returns the current head commit SHA for a PR. Used by waitForSha polling. */
23
25
  export const GET_PR_HEAD_SHA_QUERY = gql("get-pr-head-sha.gql");
24
26
  /** Look up PR number by branch name (for getCurrentPrNumber). */
@@ -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,15 +11,22 @@ 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,
18
18
  readyDelaySeconds: z.number().nonnegative().optional(),
19
19
  stallTimeoutSeconds: z.number().nonnegative().optional(),
20
20
  noAutoMarkReady: z.boolean().optional(),
21
- noAutoCancelActionable: z.boolean().optional(),
22
- neverCancelRuns: z.array(z.string()).optional(),
21
+ merge: z.boolean().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."),
23
30
  });
24
31
  const reviewMutationsOperationSchema = z.object({
25
32
  type: z.literal("review_mutations"),
@@ -33,7 +40,7 @@ const reviewMutationsOperationSchema = z.object({
33
40
  const markFilesViewedOperationSchema = z.object({
34
41
  type: z.literal("mark_files_viewed"),
35
42
  files: z.array(z.string().min(1)).optional(),
36
- tests: z.boolean().optional(),
43
+ tests: z.boolean().optional().describe("Select changed test files; no mutation occurs."),
37
44
  matchPatterns: z.array(z.string().min(1)).optional(),
38
45
  });
39
46
  const appendJournalOperationSchema = z.object({
@@ -72,7 +79,7 @@ export function createPrShepherdMcpServer(options = {}) {
72
79
  const shepherd = options.shepherd ?? createPrShepherd({ cwd: options.cwd });
73
80
  const server = new McpServer({ name: "pr-shepherd", version: readPackageVersion() });
74
81
  server.registerTool("iterate", {
75
- 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.",
76
83
  inputSchema: iterateInputSchema,
77
84
  annotations: {
78
85
  readOnlyHint: false,
@@ -82,7 +89,7 @@ export function createPrShepherdMcpServer(options = {}) {
82
89
  },
83
90
  }, async (input) => runTool(() => shepherd.iterate(requireRepositoryQualifiedPr(input)), formatIterateResult));
84
91
  server.registerTool("apply", {
85
- 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.",
86
93
  inputSchema: applyInputSchema,
87
94
  annotations: {
88
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 }),
@@ -61,6 +66,8 @@ export function toAgentCheck(c) {
61
66
  ...(c.summary !== undefined && { summary: c.summary }),
62
67
  ...(c.logExcerpt !== undefined && { logExcerpt: c.logExcerpt }),
63
68
  ...(c.annotations !== undefined && { annotations: c.annotations }),
69
+ ...(c.scope !== undefined && { scope: c.scope }),
70
+ ...(c.commitOid !== undefined && { commitOid: c.commitOid }),
64
71
  };
65
72
  }
66
73
  export function toAgentStalledCheck(c, nowSeconds) {
@@ -85,13 +92,14 @@ export function toAgentStalledCheck(c, nowSeconds) {
85
92
  * log tail, so they are all kept.
86
93
  */
87
94
  export function toAgentChecks(checks) {
88
- const seenNames = new Set();
95
+ const seenKeys = new Set();
89
96
  const result = [];
90
97
  for (const c of checks) {
91
98
  if (c.runId === null) {
92
- if (seenNames.has(c.name))
99
+ const key = `${c.scope ?? "pr"}:${c.commitOid ?? "head"}:${c.name}`;
100
+ if (seenKeys.has(key))
93
101
  continue;
94
- seenNames.add(c.name);
102
+ seenKeys.add(key);
95
103
  }
96
104
  result.push(toAgentCheck(c));
97
105
  }
@@ -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,
@@ -26,5 +26,7 @@ export interface ActiveCheck {
26
26
  detailsUrl: string | null;
27
27
  workflowName?: string;
28
28
  summary?: string;
29
+ scope?: "merge_group";
30
+ commitOid?: string;
29
31
  }
30
32
  export {};
@@ -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
  }
@@ -0,0 +1,35 @@
1
+ import type { AgentComment, AgentThread } from "./report.mts";
2
+ import type { CheckStatus, Review } from "./github.mts";
3
+ import type { MergeQueueRemovalStatus } from "./merge-requirements.mts";
4
+ export type EscalateTrigger = "fix-thrash" | "base-branch-unknown" | "stall-timeout" | "thread-missing-location" | "authorization-required" | "bot-cr-not-dismissed" | "merge-queue-removed";
5
+ export interface AgentStalledCheck {
6
+ name: string;
7
+ status: CheckStatus;
8
+ source: "check_run" | "status_context" | "startup_failure";
9
+ runId: string | null;
10
+ detailsUrl: string | null;
11
+ createdAtUnix?: number;
12
+ startedAtUnix?: number;
13
+ updatedAtUnix?: number;
14
+ ageSeconds: number;
15
+ summary?: string;
16
+ }
17
+ export interface EscalateDetails {
18
+ triggers: EscalateTrigger[];
19
+ unresolvedThreads: AgentThread[];
20
+ ambiguousComments: AgentComment[];
21
+ changesRequestedReviews: Review[];
22
+ stalledChecks?: AgentStalledCheck[];
23
+ thrashHistory?: Array<{
24
+ threadId: string;
25
+ attempts: number;
26
+ }>;
27
+ suggestion: string;
28
+ humanMessage: string;
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
+ }>;
35
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -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 {
@@ -19,6 +30,10 @@ export interface CheckRun {
19
30
  detailsUrl: string;
20
31
  event: string | null;
21
32
  runId: string | null;
33
+ /** Commit scope that supplied this check. Omitted for ordinary PR-head checks. */
34
+ scope?: "merge_group";
35
+ /** Synthetic merge-group commit OID, when `scope` is `merge_group`. */
36
+ commitOid?: string;
22
37
  createdAtUnix?: number;
23
38
  startedAtUnix?: number;
24
39
  completedAtUnix?: number;
@@ -36,6 +51,8 @@ export interface ReviewThread {
36
51
  isResolved: boolean;
37
52
  isOutdated: boolean;
38
53
  isMinimized: boolean;
54
+ viewerCanReply?: boolean;
55
+ viewerCanResolve?: boolean;
39
56
  path: string | null;
40
57
  line: number | null;
41
58
  /** Start of the comment's line range. Null for single-line comments (use `line` for both). */
@@ -45,6 +62,7 @@ export interface ReviewThread {
45
62
  author: string;
46
63
  authorType: AuthorType;
47
64
  authorAssociation?: CommentAuthorAssociation;
65
+ viewerDidAuthor?: true;
48
66
  body: string;
49
67
  url: string;
50
68
  createdAtUnix?: number;
@@ -66,6 +84,7 @@ export interface SuggestionBlock {
66
84
  export interface PrComment {
67
85
  id: string;
68
86
  isMinimized: boolean;
87
+ viewerCanMinimize?: boolean;
69
88
  author: string;
70
89
  authorType: AuthorType;
71
90
  authorAssociation?: CommentAuthorAssociation;
@@ -78,6 +97,8 @@ export interface Review {
78
97
  author: string;
79
98
  authorType: AuthorType;
80
99
  authorAssociation?: CommentAuthorAssociation;
100
+ /** Present for minimizable COMMENTED/APPROVED reviews; false for non-minimizable review rows. */
101
+ viewerCanMinimize?: boolean;
81
102
  body: string;
82
103
  createdAtUnix?: number;
83
104
  edited?: boolean;
@@ -119,6 +140,7 @@ export interface BatchPrData extends BatchPrMergeFields {
119
140
  headRefName: string;
120
141
  /** `"owner/name"` of the head repository; null when the fork has been deleted. */
121
142
  headRepoWithOwner: string | null;
143
+ viewerAuthorization?: ViewerAuthorization;
122
144
  baseRefName: string;
123
145
  reviewRequests: Array<{
124
146
  login: string;
@@ -133,6 +155,11 @@ export interface BatchPrData extends BatchPrMergeFields {
133
155
  reviewSummaries: Review[];
134
156
  approvedReviews: Review[];
135
157
  checks: CheckRun[];
158
+ mergeQueueChecks?: CheckRun[];
159
+ mergeQueueChecksIncomplete?: true;
160
+ removedMergeQueueChecks?: CheckRun[];
161
+ removedMergeQueueChecksIncomplete?: true;
136
162
  branchProtection: BranchProtection | null;
137
163
  activity?: PrActivitySummary;
138
164
  }
165
+ export {};