@sentry/junior-github 0.163.0 → 0.165.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.
package/dist/index.js CHANGED
@@ -755,7 +755,8 @@ var inputSchema = z.object({
755
755
  }).strict();
756
756
  var cloneSchema = z.object({
757
757
  path: z.string(),
758
- repo: z.string()
758
+ repo: z.string(),
759
+ workspaces: z.array(z.string())
759
760
  });
760
761
  var outputSchema = pluginToolOutputSchema.extend({
761
762
  target: z.literal("cloneRepository"),
@@ -873,7 +874,20 @@ function createGitHubCloneRepositoryTool(ctx) {
873
874
  `GitHub repository clone failed: ${clone.stderr.trim() || `exit ${clone.exitCode}`}`
874
875
  );
875
876
  }
876
- const data = { repo: `${repo.owner}/${repo.name}`, path };
877
+ const repoId = `${repo.owner}/${repo.name}`;
878
+ let workspaces = [];
879
+ try {
880
+ workspaces = await ctx.workspaces.findByRepository({
881
+ provider: "github",
882
+ repo: repoId
883
+ });
884
+ } catch (error) {
885
+ ctx.log.error("github.clone.workspaces_lookup.failed", {
886
+ repo: repoId,
887
+ error: error instanceof Error ? error.message : String(error)
888
+ });
889
+ }
890
+ const data = { repo: repoId, path, workspaces };
877
891
  return { target: "cloneRepository", ...data };
878
892
  }
879
893
  });
@@ -2008,10 +2022,13 @@ function createGitHubGetPullRequestTool(ctx) {
2008
2022
  )
2009
2023
  });
2010
2024
  const parsed = await readJson2(response);
2011
- if (!response.ok)
2012
- throw new Error(
2013
- `GitHub pull request lookup failed with HTTP ${response.status}`
2014
- );
2025
+ if (!response.ok) {
2026
+ const message = `GitHub pull request lookup failed with HTTP ${response.status}`;
2027
+ if (response.status === 404) {
2028
+ throw new PluginToolInputError6(message);
2029
+ }
2030
+ throw new Error(message);
2031
+ }
2015
2032
  const providerResult = z5.object({
2016
2033
  base: z5.object({ ref: z5.string() }),
2017
2034
  draft: z5.boolean(),
@@ -2586,8 +2603,202 @@ function createGitHubUpdatePullRequestTool(ctx) {
2586
2603
  });
2587
2604
  }
2588
2605
 
2606
+ // src/tools/resolve-pull-request-review-thread.ts
2607
+ import {
2608
+ definePluginTool as definePluginTool10,
2609
+ PluginToolInputError as PluginToolInputError11,
2610
+ pluginToolOutputSchema as pluginToolOutputSchema10
2611
+ } from "@sentry/junior-plugin-api";
2612
+ import { z as z10 } from "zod";
2613
+
2614
+ // src/webhooks/ownership.ts
2615
+ var GITHUB_NOREPLY_DOMAIN = "users.noreply.github.com";
2616
+ function botLoginFromEmail(value) {
2617
+ const email = value?.trim();
2618
+ if (!email) return void 0;
2619
+ const separator = email.lastIndexOf("@");
2620
+ if (separator <= 0) return void 0;
2621
+ const domain = email.slice(separator + 1).toLowerCase();
2622
+ if (domain !== GITHUB_NOREPLY_DOMAIN) return void 0;
2623
+ const localPart = email.slice(0, separator);
2624
+ const login = localPart.slice(localPart.indexOf("+") + 1).trim();
2625
+ return login.toLowerCase().endsWith("[bot]") ? login : void 0;
2626
+ }
2627
+
2628
+ // src/tools/resolve-pull-request-review-thread.ts
2629
+ var inputSchema8 = z10.object({
2630
+ repo: z10.string().describe(
2631
+ 'Repository in "owner/name" format. Required for repository-scoped credentials (GraphQL has no repo path).'
2632
+ ),
2633
+ threadId: z10.string().trim().min(1).describe(
2634
+ "GitHub pull request review thread node ID (the same `threadId` / `id` variable used by `gh api graphql` resolveReviewThread)."
2635
+ )
2636
+ }).strict();
2637
+ var outputSchema8 = pluginToolOutputSchema10.extend({
2638
+ target: z10.literal("resolvePullRequestReviewThread"),
2639
+ repo: z10.string(),
2640
+ number: z10.number(),
2641
+ threadId: z10.string(),
2642
+ resolved: z10.boolean()
2643
+ });
2644
+ function parseRepo10(value) {
2645
+ const parts = value.split("/").map((part) => part.trim());
2646
+ if (parts.length !== 2 || !parts[0] || !parts[1]) {
2647
+ throw new PluginToolInputError11('repo must use "owner/name" format');
2648
+ }
2649
+ return { owner: parts[0], name: parts[1], ref: `${parts[0]}/${parts[1]}` };
2650
+ }
2651
+ async function readJson7(response) {
2652
+ const text2 = await response.text();
2653
+ if (!text2) return void 0;
2654
+ try {
2655
+ return JSON.parse(text2);
2656
+ } catch {
2657
+ return text2;
2658
+ }
2659
+ }
2660
+ function githubError(payload) {
2661
+ if (payload && typeof payload === "object" && !Array.isArray(payload)) {
2662
+ const message = payload.message;
2663
+ if (typeof message === "string" && message.trim()) return message.trim();
2664
+ }
2665
+ return "GitHub request failed";
2666
+ }
2667
+ function createGitHubResolvePullRequestReviewThreadTool(ctx, botEmail) {
2668
+ return definePluginTool10({
2669
+ annotations: {
2670
+ destructiveHint: true,
2671
+ idempotentHint: true,
2672
+ openWorldHint: true,
2673
+ readOnlyHint: false
2674
+ },
2675
+ description: "Resolve a GitHub pull request review thread. Use this instead of shelling out to `gh api graphql` for resolveReviewThread (GraphQL-only; no REST or `gh pr` equivalent). Only works on pull requests Junior authored.",
2676
+ inputSchema: inputSchema8,
2677
+ outputSchema: outputSchema8,
2678
+ async execute(input) {
2679
+ const parsedInput = inputSchema8.safeParse(input);
2680
+ if (!parsedInput.success) {
2681
+ throw new PluginToolInputError11(
2682
+ "Invalid GitHub resolvePullRequestReviewThread input.",
2683
+ { cause: parsedInput.error }
2684
+ );
2685
+ }
2686
+ const repo = parseRepo10(parsedInput.data.repo);
2687
+ const botLogin = botLoginFromEmail(botEmail)?.toLowerCase();
2688
+ if (!botLogin) {
2689
+ throw new Error("GitHub App bot identity is not configured.");
2690
+ }
2691
+ const query = `query ReviewThreadOwnership($threadId: ID!) {
2692
+ node(id: $threadId) {
2693
+ ... on PullRequestReviewThread {
2694
+ id
2695
+ isResolved
2696
+ pullRequest {
2697
+ number
2698
+ repository { nameWithOwner }
2699
+ author { login }
2700
+ }
2701
+ }
2702
+ }
2703
+ }`;
2704
+ const lookupResponse = await ctx.egress.fetch({
2705
+ provider: "github",
2706
+ operation: "github.pull.review-thread.get",
2707
+ request: new Request("https://api.github.com/graphql", {
2708
+ method: "POST",
2709
+ headers: { "Content-Type": "application/json" },
2710
+ body: JSON.stringify({
2711
+ operationName: "ReviewThreadOwnership",
2712
+ query,
2713
+ variables: { threadId: parsedInput.data.threadId }
2714
+ })
2715
+ })
2716
+ });
2717
+ const lookupPayload = await readJson7(lookupResponse);
2718
+ if (!lookupResponse.ok) {
2719
+ throw new Error(
2720
+ `GitHub review thread lookup failed with HTTP ${lookupResponse.status}: ${githubError(lookupPayload)}`
2721
+ );
2722
+ }
2723
+ const thread = z10.object({
2724
+ data: z10.object({
2725
+ node: z10.object({
2726
+ id: z10.string(),
2727
+ isResolved: z10.boolean(),
2728
+ pullRequest: z10.object({
2729
+ author: z10.object({ login: z10.string() }),
2730
+ number: z10.number(),
2731
+ repository: z10.object({ nameWithOwner: z10.string() })
2732
+ })
2733
+ }).nullable()
2734
+ })
2735
+ }).parse(lookupPayload).data.node;
2736
+ if (!thread) {
2737
+ throw new PluginToolInputError11("GitHub review thread was not found.");
2738
+ }
2739
+ const pullRequest = thread.pullRequest;
2740
+ const ownsPullRequest = pullRequest.repository.nameWithOwner.toLowerCase() === repo.ref.toLowerCase() && pullRequest.author.login.toLowerCase() === botLogin;
2741
+ if (!ownsPullRequest) {
2742
+ throw new PluginToolInputError11(
2743
+ "Junior can only resolve review threads on pull requests it authored."
2744
+ );
2745
+ }
2746
+ if (thread.isResolved) {
2747
+ return {
2748
+ target: "resolvePullRequestReviewThread",
2749
+ repo: repo.ref,
2750
+ number: pullRequest.number,
2751
+ threadId: thread.id,
2752
+ resolved: true
2753
+ };
2754
+ }
2755
+ const mutation = `mutation ResolveReviewThread($threadId: ID!) {
2756
+ resolveReviewThread(input: {threadId: $threadId}) {
2757
+ thread { id isResolved }
2758
+ }
2759
+ }`;
2760
+ const resolveResponse = await ctx.egress.fetch({
2761
+ provider: "github",
2762
+ operation: `github.pull.review-thread.resolve:${repo.ref.toLowerCase()}`,
2763
+ request: new Request("https://api.github.com/graphql", {
2764
+ method: "POST",
2765
+ headers: { "Content-Type": "application/json" },
2766
+ body: JSON.stringify({
2767
+ operationName: "ResolveReviewThread",
2768
+ query: mutation,
2769
+ variables: { threadId: thread.id }
2770
+ })
2771
+ })
2772
+ });
2773
+ const resolvePayload = await readJson7(resolveResponse);
2774
+ if (!resolveResponse.ok) {
2775
+ throw new Error(
2776
+ `GitHub review thread resolution failed with HTTP ${resolveResponse.status}: ${githubError(resolvePayload)}`
2777
+ );
2778
+ }
2779
+ const resolved = z10.object({
2780
+ data: z10.object({
2781
+ resolveReviewThread: z10.object({
2782
+ thread: z10.object({ id: z10.string(), isResolved: z10.boolean() })
2783
+ })
2784
+ })
2785
+ }).parse(resolvePayload).data.resolveReviewThread.thread;
2786
+ if (resolved.id !== thread.id || !resolved.isResolved) {
2787
+ throw new Error("GitHub did not resolve the requested review thread.");
2788
+ }
2789
+ return {
2790
+ target: "resolvePullRequestReviewThread",
2791
+ repo: repo.ref,
2792
+ number: pullRequest.number,
2793
+ threadId: resolved.id,
2794
+ resolved: true
2795
+ };
2796
+ }
2797
+ });
2798
+ }
2799
+
2589
2800
  // src/tools.ts
2590
- function createGitHubTools(ctx) {
2801
+ function createGitHubTools(ctx, botEmail) {
2591
2802
  return {
2592
2803
  cloneRepository: createGitHubCloneRepositoryTool(ctx),
2593
2804
  createIssue: createGitHubIssueTool(ctx),
@@ -2596,6 +2807,7 @@ function createGitHubTools(ctx) {
2596
2807
  getPullRequest: createGitHubGetPullRequestTool(ctx),
2597
2808
  getRelease: createGitHubGetReleaseTool(ctx),
2598
2809
  getRepository: createGitHubGetRepositoryTool(ctx),
2810
+ resolvePullRequestReviewThread: createGitHubResolvePullRequestReviewThreadTool(ctx, botEmail),
2599
2811
  updateIssue: createGitHubUpdateIssueTool(ctx),
2600
2812
  updatePullRequest: createGitHubUpdatePullRequestTool(ctx)
2601
2813
  };
@@ -2606,7 +2818,7 @@ import { createHmac, timingSafeEqual } from "crypto";
2606
2818
 
2607
2819
  // src/issue-outcomes/store.ts
2608
2820
  import { and, eq, lte, sql as sql2 } from "drizzle-orm";
2609
- import { z as z11 } from "zod";
2821
+ import { z as z12 } from "zod";
2610
2822
 
2611
2823
  // src/db/schema.ts
2612
2824
  import { sql } from "drizzle-orm";
@@ -2618,18 +2830,18 @@ import {
2618
2830
  text,
2619
2831
  timestamp
2620
2832
  } from "drizzle-orm/pg-core";
2621
- import { z as z10 } from "zod";
2622
- var githubPullRequestStateSchema = z10.enum([
2833
+ import { z as z11 } from "zod";
2834
+ var githubPullRequestStateSchema = z11.enum([
2623
2835
  "closed_unmerged",
2624
2836
  "merged",
2625
2837
  "open"
2626
2838
  ]);
2627
- var githubPullRequestCommitCompositionSchema = z10.enum([
2839
+ var githubPullRequestCommitCompositionSchema = z11.enum([
2628
2840
  "junior_only",
2629
2841
  "mixed"
2630
2842
  ]);
2631
- var githubIssueStateSchema = z10.enum(["closed", "open"]);
2632
- var githubIssueStateReasonSchema = z10.enum([
2843
+ var githubIssueStateSchema = z11.enum(["closed", "open"]);
2844
+ var githubIssueStateReasonSchema = z11.enum([
2633
2845
  "completed",
2634
2846
  "duplicate",
2635
2847
  "not_planned",
@@ -2699,21 +2911,21 @@ var juniorGitHubPullRequestIssues = pgTable(
2699
2911
  );
2700
2912
 
2701
2913
  // src/issue-outcomes/store.ts
2702
- var githubIssueOutcomeInputSchema = z11.object({
2703
- candidateOwned: z11.boolean(),
2704
- closedAt: z11.date().optional(),
2705
- issueId: z11.string().min(1),
2706
- number: z11.number().int().positive(),
2707
- openedAt: z11.date(),
2708
- repositoryFullName: z11.string().min(1),
2709
- repositoryId: z11.string().min(1),
2914
+ var githubIssueOutcomeInputSchema = z12.object({
2915
+ candidateOwned: z12.boolean(),
2916
+ closedAt: z12.date().optional(),
2917
+ issueId: z12.string().min(1),
2918
+ number: z12.number().int().positive(),
2919
+ openedAt: z12.date(),
2920
+ repositoryFullName: z12.string().min(1),
2921
+ repositoryId: z12.string().min(1),
2710
2922
  state: githubIssueStateSchema,
2711
2923
  stateReason: githubIssueStateReasonSchema.optional(),
2712
- updatedAt: z11.date()
2924
+ updatedAt: z12.date()
2713
2925
  }).strict();
2714
- var githubIssueConversationsInputSchema = z11.object({
2715
- conversationIds: z11.array(z11.string().min(1)).min(1),
2716
- issueId: z11.string().min(1)
2926
+ var githubIssueConversationsInputSchema = z12.object({
2927
+ conversationIds: z12.array(z12.string().min(1)).min(1),
2928
+ issueId: z12.string().min(1)
2717
2929
  }).strict();
2718
2930
  function projectionValues(input) {
2719
2931
  return {
@@ -2777,32 +2989,32 @@ async function recordGitHubIssueConversations(db, input) {
2777
2989
 
2778
2990
  // src/pull-request-outcomes/store.ts
2779
2991
  import { and as and2, eq as eq2, lte as lte2, ne, sql as sql3 } from "drizzle-orm";
2780
- import { z as z12 } from "zod";
2781
- var githubPullRequestOutcomeInputSchema = z12.object({
2782
- candidateOwned: z12.boolean(),
2783
- closedAt: z12.date().optional(),
2992
+ import { z as z13 } from "zod";
2993
+ var githubPullRequestOutcomeInputSchema = z13.object({
2994
+ candidateOwned: z13.boolean(),
2995
+ closedAt: z13.date().optional(),
2784
2996
  commitComposition: githubPullRequestCommitCompositionSchema.optional(),
2785
- mergedAt: z12.date().optional(),
2786
- number: z12.number().int().positive(),
2787
- openedAt: z12.date(),
2788
- pullRequestId: z12.string().min(1),
2789
- repositoryFullName: z12.string().min(1),
2790
- repositoryId: z12.string().min(1),
2997
+ mergedAt: z13.date().optional(),
2998
+ number: z13.number().int().positive(),
2999
+ openedAt: z13.date(),
3000
+ pullRequestId: z13.string().min(1),
3001
+ repositoryFullName: z13.string().min(1),
3002
+ repositoryId: z13.string().min(1),
2791
3003
  state: githubPullRequestStateSchema,
2792
- updatedAt: z12.date()
3004
+ updatedAt: z13.date()
2793
3005
  }).strict();
2794
- var githubPullRequestConversationsInputSchema = z12.object({
2795
- conversationIds: z12.array(z12.string().min(1)).min(1),
2796
- pullRequestId: z12.string().min(1)
3006
+ var githubPullRequestConversationsInputSchema = z13.object({
3007
+ conversationIds: z13.array(z13.string().min(1)).min(1),
3008
+ pullRequestId: z13.string().min(1)
2797
3009
  }).strict();
2798
- var githubPullRequestLinkedIssuesInputSchema = z12.object({
2799
- linkedIssues: z12.array(
2800
- z12.object({
2801
- number: z12.number().int().positive(),
2802
- repositoryFullName: z12.string().min(1)
3010
+ var githubPullRequestLinkedIssuesInputSchema = z13.object({
3011
+ linkedIssues: z13.array(
3012
+ z13.object({
3013
+ number: z13.number().int().positive(),
3014
+ repositoryFullName: z13.string().min(1)
2803
3015
  }).strict()
2804
3016
  ).min(1),
2805
- pullRequestId: z12.string().min(1)
3017
+ pullRequestId: z13.string().min(1)
2806
3018
  }).strict();
2807
3019
  function projectionValues2(input) {
2808
3020
  return {
@@ -2967,55 +3179,39 @@ async function recordGitHubPullRequestLinkedIssues(db, input) {
2967
3179
  }
2968
3180
 
2969
3181
  // src/webhooks/issue-outcome.ts
2970
- import { z as z13 } from "zod";
2971
-
2972
- // src/webhooks/ownership.ts
2973
- var GITHUB_NOREPLY_DOMAIN = "users.noreply.github.com";
2974
- function botLoginFromEmail(value) {
2975
- const email = value?.trim();
2976
- if (!email) return void 0;
2977
- const separator = email.lastIndexOf("@");
2978
- if (separator <= 0) return void 0;
2979
- const domain = email.slice(separator + 1).toLowerCase();
2980
- if (domain !== GITHUB_NOREPLY_DOMAIN) return void 0;
2981
- const localPart = email.slice(0, separator);
2982
- const login = localPart.slice(localPart.indexOf("+") + 1).trim();
2983
- return login.toLowerCase().endsWith("[bot]") ? login : void 0;
2984
- }
2985
-
2986
- // src/webhooks/issue-outcome.ts
2987
- var canonicalIssueOutcomeSchema = z13.object({
2988
- action: z13.enum(["opened", "closed", "reopened"]),
2989
- issue: z13.object({
2990
- body: z13.string().nullable().optional(),
2991
- closed_at: z13.string().nullable().optional(),
2992
- created_at: z13.string(),
2993
- id: z13.number().int().positive(),
2994
- number: z13.number().int().positive(),
3182
+ import { z as z14 } from "zod";
3183
+ var canonicalIssueOutcomeSchema = z14.object({
3184
+ action: z14.enum(["opened", "closed", "reopened"]),
3185
+ issue: z14.object({
3186
+ body: z14.string().nullable().optional(),
3187
+ closed_at: z14.string().nullable().optional(),
3188
+ created_at: z14.string(),
3189
+ id: z14.number().int().positive(),
3190
+ number: z14.number().int().positive(),
2995
3191
  state_reason: githubIssueStateReasonSchema.nullable().optional(),
2996
- updated_at: z13.string(),
2997
- user: z13.object({ login: z13.string().min(1) }).strict()
3192
+ updated_at: z14.string(),
3193
+ user: z14.object({ login: z14.string().min(1) }).strict()
2998
3194
  }).strict(),
2999
- repository: z13.object({
3000
- full_name: z13.string().min(1),
3001
- id: z13.number().int().positive()
3195
+ repository: z14.object({
3196
+ full_name: z14.string().min(1),
3197
+ id: z14.number().int().positive()
3002
3198
  }).strict()
3003
3199
  }).strict();
3004
- var issueOutcomeSchema = z13.object({
3005
- action: z13.enum(["opened", "closed", "reopened"]),
3006
- issue: z13.object({
3007
- body: z13.string().nullable().optional(),
3008
- closed_at: z13.string().nullable().optional(),
3009
- created_at: z13.string(),
3010
- id: z13.number().int().positive(),
3011
- number: z13.number().int().positive(),
3200
+ var issueOutcomeSchema = z14.object({
3201
+ action: z14.enum(["opened", "closed", "reopened"]),
3202
+ issue: z14.object({
3203
+ body: z14.string().nullable().optional(),
3204
+ closed_at: z14.string().nullable().optional(),
3205
+ created_at: z14.string(),
3206
+ id: z14.number().int().positive(),
3207
+ number: z14.number().int().positive(),
3012
3208
  state_reason: githubIssueStateReasonSchema.nullable().optional(),
3013
- updated_at: z13.string(),
3014
- user: z13.object({ login: z13.string().min(1) }).passthrough()
3209
+ updated_at: z14.string(),
3210
+ user: z14.object({ login: z14.string().min(1) }).passthrough()
3015
3211
  }).passthrough(),
3016
- repository: z13.object({
3017
- full_name: z13.string().min(1),
3018
- id: z13.number().int().positive()
3212
+ repository: z14.object({
3213
+ full_name: z14.string().min(1),
3214
+ id: z14.number().int().positive()
3019
3215
  }).passthrough()
3020
3216
  }).passthrough().transform(
3021
3217
  (provider) => canonicalIssueOutcomeSchema.parse({
@@ -3036,7 +3232,7 @@ var issueOutcomeSchema = z13.object({
3036
3232
  }
3037
3233
  })
3038
3234
  );
3039
- var issueLifecycleActionSchema = z13.object({ action: z13.string() }).passthrough();
3235
+ var issueLifecycleActionSchema = z14.object({ action: z14.string() }).passthrough();
3040
3236
  function timestamp2(value) {
3041
3237
  if (!value) return void 0;
3042
3238
  const parsed = new Date(value);
@@ -3082,21 +3278,21 @@ function normalizeGitHubIssueOutcome(args) {
3082
3278
  updatedAt
3083
3279
  };
3084
3280
  }
3085
- var canonicalIssueConversationSchema = z13.object({
3086
- issue: z13.object({
3087
- body: z13.string().nullable().optional(),
3088
- id: z13.number().int().positive(),
3089
- user: z13.object({ login: z13.string().min(1) }).strict()
3281
+ var canonicalIssueConversationSchema = z14.object({
3282
+ issue: z14.object({
3283
+ body: z14.string().nullable().optional(),
3284
+ id: z14.number().int().positive(),
3285
+ user: z14.object({ login: z14.string().min(1) }).strict()
3090
3286
  }).strict(),
3091
- sender: z13.object({ login: z13.string().min(1) }).strict().optional()
3287
+ sender: z14.object({ login: z14.string().min(1) }).strict().optional()
3092
3288
  }).strict();
3093
- var issueConversationSchema = z13.object({
3094
- issue: z13.object({
3095
- body: z13.string().nullable().optional(),
3096
- id: z13.number().int().positive(),
3097
- user: z13.object({ login: z13.string().min(1) }).passthrough()
3289
+ var issueConversationSchema = z14.object({
3290
+ issue: z14.object({
3291
+ body: z14.string().nullable().optional(),
3292
+ id: z14.number().int().positive(),
3293
+ user: z14.object({ login: z14.string().min(1) }).passthrough()
3098
3294
  }).passthrough(),
3099
- sender: z13.object({ login: z13.string().min(1) }).passthrough().optional()
3295
+ sender: z14.object({ login: z14.string().min(1) }).passthrough().optional()
3100
3296
  }).passthrough().transform(
3101
3297
  (provider) => canonicalIssueConversationSchema.parse({
3102
3298
  issue: {
@@ -3123,41 +3319,41 @@ function normalizeGitHubIssueConversations(args) {
3123
3319
  }
3124
3320
 
3125
3321
  // src/webhooks/pull-request-outcome.ts
3126
- import { z as z14 } from "zod";
3127
- var canonicalPullRequestOutcomeSchema = z14.object({
3128
- action: z14.enum(["opened", "closed", "reopened"]),
3129
- pull_request: z14.object({
3130
- body: z14.string().nullable().optional(),
3131
- closed_at: z14.string().nullable().optional(),
3132
- created_at: z14.string(),
3133
- id: z14.number().int().positive(),
3134
- merged: z14.boolean(),
3135
- merged_at: z14.string().nullable().optional(),
3136
- number: z14.number().int().positive(),
3137
- updated_at: z14.string(),
3138
- user: z14.object({ login: z14.string().min(1) }).strict()
3322
+ import { z as z15 } from "zod";
3323
+ var canonicalPullRequestOutcomeSchema = z15.object({
3324
+ action: z15.enum(["opened", "closed", "reopened"]),
3325
+ pull_request: z15.object({
3326
+ body: z15.string().nullable().optional(),
3327
+ closed_at: z15.string().nullable().optional(),
3328
+ created_at: z15.string(),
3329
+ id: z15.number().int().positive(),
3330
+ merged: z15.boolean(),
3331
+ merged_at: z15.string().nullable().optional(),
3332
+ number: z15.number().int().positive(),
3333
+ updated_at: z15.string(),
3334
+ user: z15.object({ login: z15.string().min(1) }).strict()
3139
3335
  }).strict(),
3140
- repository: z14.object({
3141
- full_name: z14.string().min(1),
3142
- id: z14.number().int().positive()
3336
+ repository: z15.object({
3337
+ full_name: z15.string().min(1),
3338
+ id: z15.number().int().positive()
3143
3339
  }).strict()
3144
3340
  }).strict();
3145
- var pullRequestOutcomeSchema = z14.object({
3146
- action: z14.enum(["opened", "closed", "reopened"]),
3147
- pull_request: z14.object({
3148
- body: z14.string().nullable().optional(),
3149
- closed_at: z14.string().nullable().optional(),
3150
- created_at: z14.string(),
3151
- id: z14.number().int().positive(),
3152
- merged: z14.boolean(),
3153
- merged_at: z14.string().nullable().optional(),
3154
- number: z14.number().int().positive(),
3155
- updated_at: z14.string(),
3156
- user: z14.object({ login: z14.string().min(1) }).passthrough()
3341
+ var pullRequestOutcomeSchema = z15.object({
3342
+ action: z15.enum(["opened", "closed", "reopened"]),
3343
+ pull_request: z15.object({
3344
+ body: z15.string().nullable().optional(),
3345
+ closed_at: z15.string().nullable().optional(),
3346
+ created_at: z15.string(),
3347
+ id: z15.number().int().positive(),
3348
+ merged: z15.boolean(),
3349
+ merged_at: z15.string().nullable().optional(),
3350
+ number: z15.number().int().positive(),
3351
+ updated_at: z15.string(),
3352
+ user: z15.object({ login: z15.string().min(1) }).passthrough()
3157
3353
  }).passthrough(),
3158
- repository: z14.object({
3159
- full_name: z14.string().min(1),
3160
- id: z14.number().int().positive()
3354
+ repository: z15.object({
3355
+ full_name: z15.string().min(1),
3356
+ id: z15.number().int().positive()
3161
3357
  }).passthrough()
3162
3358
  }).passthrough().transform(
3163
3359
  (provider) => canonicalPullRequestOutcomeSchema.parse({
@@ -3179,24 +3375,24 @@ var pullRequestOutcomeSchema = z14.object({
3179
3375
  }
3180
3376
  })
3181
3377
  );
3182
- var pullRequestLifecycleActionSchema = z14.object({ action: z14.string() }).passthrough();
3183
- var canonicalPullRequestConversationSchema = z14.object({
3184
- pull_request: z14.object({
3185
- body: z14.string().nullable().optional(),
3186
- id: z14.number().int().positive(),
3187
- user: z14.object({ login: z14.string().min(1) }).strict()
3378
+ var pullRequestLifecycleActionSchema = z15.object({ action: z15.string() }).passthrough();
3379
+ var canonicalPullRequestConversationSchema = z15.object({
3380
+ pull_request: z15.object({
3381
+ body: z15.string().nullable().optional(),
3382
+ id: z15.number().int().positive(),
3383
+ user: z15.object({ login: z15.string().min(1) }).strict()
3188
3384
  }).strict(),
3189
- repository: z14.object({ full_name: z14.string().min(1) }).strict(),
3190
- sender: z14.object({ login: z14.string().min(1) }).strict()
3385
+ repository: z15.object({ full_name: z15.string().min(1) }).strict(),
3386
+ sender: z15.object({ login: z15.string().min(1) }).strict()
3191
3387
  }).strict();
3192
- var pullRequestConversationSchema = z14.object({
3193
- pull_request: z14.object({
3194
- body: z14.string().nullable().optional(),
3195
- id: z14.number().int().positive(),
3196
- user: z14.object({ login: z14.string().min(1) }).passthrough()
3388
+ var pullRequestConversationSchema = z15.object({
3389
+ pull_request: z15.object({
3390
+ body: z15.string().nullable().optional(),
3391
+ id: z15.number().int().positive(),
3392
+ user: z15.object({ login: z15.string().min(1) }).passthrough()
3197
3393
  }).passthrough(),
3198
- repository: z14.object({ full_name: z14.string().min(1) }).passthrough(),
3199
- sender: z14.object({ login: z14.string().min(1) }).passthrough()
3394
+ repository: z15.object({ full_name: z15.string().min(1) }).passthrough(),
3395
+ sender: z15.object({ login: z15.string().min(1) }).passthrough()
3200
3396
  }).passthrough().transform(
3201
3397
  (provider) => canonicalPullRequestConversationSchema.parse({
3202
3398
  pull_request: {
@@ -3436,14 +3632,14 @@ function createGitHubWebhookRoute(args) {
3436
3632
 
3437
3633
  // src/outcomes/profile-report.ts
3438
3634
  import { sql as sql4 } from "drizzle-orm";
3439
- import { z as z15 } from "zod";
3635
+ import { z as z16 } from "zod";
3440
3636
  var DAY_MS = 24 * 60 * 60 * 1e3;
3441
3637
  var WINDOWS = [7, 30, 90];
3442
- var pullRequestStatsSchema = z15.object({
3443
- closed: z15.number().int().nonnegative(),
3444
- created: z15.number().int().nonnegative(),
3445
- days: z15.number().int().positive(),
3446
- merged: z15.number().int().nonnegative()
3638
+ var pullRequestStatsSchema = z16.object({
3639
+ closed: z16.number().int().nonnegative(),
3640
+ created: z16.number().int().nonnegative(),
3641
+ days: z16.number().int().positive(),
3642
+ merged: z16.number().int().nonnegative()
3447
3643
  }).strict().transform((row) => {
3448
3644
  const terminal = row.merged + row.closed;
3449
3645
  return {
@@ -3451,13 +3647,13 @@ var pullRequestStatsSchema = z15.object({
3451
3647
  mergeRate: terminal > 0 ? row.merged / terminal : void 0
3452
3648
  };
3453
3649
  });
3454
- var issueStatsSchema = z15.object({
3455
- created: z15.number().int().nonnegative(),
3456
- days: z15.number().int().positive()
3650
+ var issueStatsSchema = z16.object({
3651
+ created: z16.number().int().nonnegative(),
3652
+ days: z16.number().int().positive()
3457
3653
  }).strict();
3458
- var daySchema = z15.object({
3459
- created: z15.number().int().nonnegative(),
3460
- date: z15.string().date()
3654
+ var daySchema = z16.object({
3655
+ created: z16.number().int().nonnegative(),
3656
+ date: z16.string().date()
3461
3657
  }).strict();
3462
3658
  function queryRows(result) {
3463
3659
  if (typeof result !== "object" || result === null || !("rows" in result) || !Array.isArray(result.rows)) {
@@ -3530,7 +3726,7 @@ async function aggregatePullRequestWindows(args) {
3530
3726
  GROUP BY windows.days
3531
3727
  ORDER BY windows.days
3532
3728
  `);
3533
- return z15.array(pullRequestStatsSchema).parse(queryRows(result));
3729
+ return z16.array(pullRequestStatsSchema).parse(queryRows(result));
3534
3730
  }
3535
3731
  async function aggregateIssueWindows(args) {
3536
3732
  const starts = WINDOWS.map(
@@ -3563,7 +3759,7 @@ async function aggregateIssueWindows(args) {
3563
3759
  GROUP BY windows.days
3564
3760
  ORDER BY windows.days
3565
3761
  `);
3566
- return z15.array(issueStatsSchema).parse(queryRows(result));
3762
+ return z16.array(issueStatsSchema).parse(queryRows(result));
3567
3763
  }
3568
3764
  async function aggregateOpenedDays(args) {
3569
3765
  const end = new Date(args.nowMs);
@@ -3593,7 +3789,7 @@ async function aggregateOpenedDays(args) {
3593
3789
  LEFT JOIN daily ON daily.day = days.day
3594
3790
  ORDER BY days.day
3595
3791
  `);
3596
- return z15.array(daySchema).parse(queryRows(result));
3792
+ return z16.array(daySchema).parse(queryRows(result));
3597
3793
  }
3598
3794
  async function buildGitHubProfileReport(args) {
3599
3795
  const [windows, pullRequestDays, issueWindows, issueDays] = await Promise.all(
@@ -3674,18 +3870,18 @@ async function buildGitHubProfileReport(args) {
3674
3870
 
3675
3871
  // src/outcomes/report.ts
3676
3872
  import { sql as sql6 } from "drizzle-orm";
3677
- import { z as z17 } from "zod";
3873
+ import { z as z18 } from "zod";
3678
3874
 
3679
3875
  // src/outcomes/cost.ts
3680
3876
  import { sql as sql5 } from "drizzle-orm";
3681
- import { z as z16 } from "zod";
3877
+ import { z as z17 } from "zod";
3682
3878
  var DAY_MS2 = 24 * 60 * 60 * 1e3;
3683
- var costWindowSchema = z16.object({
3684
- days: z16.number().int().positive(),
3685
- issueCostUsd: z16.number().nonnegative().nullable(),
3686
- medianIssueCostUsd: z16.number().nonnegative().nullable(),
3687
- medianPullRequestCostUsd: z16.number().nonnegative().nullable(),
3688
- pullRequestCostUsd: z16.number().nonnegative().nullable()
3879
+ var costWindowSchema = z17.object({
3880
+ days: z17.number().int().positive(),
3881
+ issueCostUsd: z17.number().nonnegative().nullable(),
3882
+ medianIssueCostUsd: z17.number().nonnegative().nullable(),
3883
+ medianPullRequestCostUsd: z17.number().nonnegative().nullable(),
3884
+ pullRequestCostUsd: z17.number().nonnegative().nullable()
3689
3885
  }).strict().transform((row) => ({
3690
3886
  days: row.days,
3691
3887
  issueCostUsd: row.issueCostUsd ?? void 0,
@@ -3693,12 +3889,12 @@ var costWindowSchema = z16.object({
3693
3889
  medianPullRequestCostUsd: row.medianPullRequestCostUsd ?? void 0,
3694
3890
  pullRequestCostUsd: row.pullRequestCostUsd ?? void 0
3695
3891
  }));
3696
- var repositoryCostSchema = z16.object({
3697
- issueCostUsd: z16.number().nonnegative().nullable(),
3698
- medianIssueCostUsd: z16.number().nonnegative().nullable(),
3699
- medianPullRequestCostUsd: z16.number().nonnegative().nullable(),
3700
- pullRequestCostUsd: z16.number().nonnegative().nullable(),
3701
- repository: z16.string().min(1)
3892
+ var repositoryCostSchema = z17.object({
3893
+ issueCostUsd: z17.number().nonnegative().nullable(),
3894
+ medianIssueCostUsd: z17.number().nonnegative().nullable(),
3895
+ medianPullRequestCostUsd: z17.number().nonnegative().nullable(),
3896
+ pullRequestCostUsd: z17.number().nonnegative().nullable(),
3897
+ repository: z17.string().min(1)
3702
3898
  }).strict().transform((row) => ({
3703
3899
  issueCostUsd: row.issueCostUsd ?? void 0,
3704
3900
  medianIssueCostUsd: row.medianIssueCostUsd ?? void 0,
@@ -3903,7 +4099,7 @@ async function aggregateGitHubCostWindows(args) {
3903
4099
  INNER JOIN issue_window ON issue_window.days = pull_request_window.days
3904
4100
  ORDER BY pull_request_window.days
3905
4101
  `);
3906
- return z16.array(costWindowSchema).parse(queryRows2(result));
4102
+ return z17.array(costWindowSchema).parse(queryRows2(result));
3907
4103
  }
3908
4104
  async function aggregateGitHubRepositoryCosts(args) {
3909
4105
  if (!await hasConversationUsageTable(args.db)) {
@@ -4001,7 +4197,7 @@ async function aggregateGitHubRepositoryCosts(args) {
4001
4197
  ON issue_totals.repository = repositories.repository
4002
4198
  ORDER BY "repository" ASC
4003
4199
  `);
4004
- return z16.array(repositoryCostSchema).parse(queryRows2(result));
4200
+ return z17.array(repositoryCostSchema).parse(queryRows2(result));
4005
4201
  }
4006
4202
  function formatCostUsd(value) {
4007
4203
  if (value === void 0) return "\u2014";
@@ -4016,12 +4212,12 @@ function formatCostUsd(value) {
4016
4212
  // src/outcomes/report.ts
4017
4213
  var DAY_MS3 = 24 * 60 * 60 * 1e3;
4018
4214
  var WINDOWS2 = [7, 30, 90];
4019
- var pullRequestStatsSchema2 = z17.object({
4020
- closed: z17.number().int().nonnegative(),
4021
- created: z17.number().int().nonnegative(),
4022
- days: z17.number().int().positive(),
4023
- medianMergeTimeMs: z17.number().nonnegative().nullable(),
4024
- merged: z17.number().int().nonnegative()
4215
+ var pullRequestStatsSchema2 = z18.object({
4216
+ closed: z18.number().int().nonnegative(),
4217
+ created: z18.number().int().nonnegative(),
4218
+ days: z18.number().int().positive(),
4219
+ medianMergeTimeMs: z18.number().nonnegative().nullable(),
4220
+ merged: z18.number().int().nonnegative()
4025
4221
  }).strict().transform((row) => {
4026
4222
  const terminal = row.merged + row.closed;
4027
4223
  return {
@@ -4030,12 +4226,12 @@ var pullRequestStatsSchema2 = z17.object({
4030
4226
  mergeRate: terminal > 0 ? row.merged / terminal : void 0
4031
4227
  };
4032
4228
  });
4033
- var pullRequestRepositoryStatsSchema = z17.object({
4034
- closed: z17.number().int().nonnegative(),
4035
- created: z17.number().int().nonnegative(),
4036
- juniorOnly: z17.number().int().nonnegative(),
4037
- merged: z17.number().int().nonnegative(),
4038
- repository: z17.string().min(1)
4229
+ var pullRequestRepositoryStatsSchema = z18.object({
4230
+ closed: z18.number().int().nonnegative(),
4231
+ created: z18.number().int().nonnegative(),
4232
+ juniorOnly: z18.number().int().nonnegative(),
4233
+ merged: z18.number().int().nonnegative(),
4234
+ repository: z18.string().min(1)
4039
4235
  }).strict().transform((row) => {
4040
4236
  const terminal = row.merged + row.closed;
4041
4237
  return {
@@ -4043,33 +4239,33 @@ var pullRequestRepositoryStatsSchema = z17.object({
4043
4239
  mergeRate: terminal > 0 ? row.merged / terminal : void 0
4044
4240
  };
4045
4241
  });
4046
- var issueStatsSchema2 = z17.object({
4047
- closedCompleted: z17.number().int().nonnegative(),
4048
- closedDuplicate: z17.number().int().nonnegative(),
4049
- closedNotPlanned: z17.number().int().nonnegative(),
4050
- closedUnknown: z17.number().int().nonnegative(),
4051
- created: z17.number().int().nonnegative(),
4052
- days: z17.number().int().positive(),
4053
- medianCloseTimeMs: z17.number().nonnegative().nullable()
4242
+ var issueStatsSchema2 = z18.object({
4243
+ closedCompleted: z18.number().int().nonnegative(),
4244
+ closedDuplicate: z18.number().int().nonnegative(),
4245
+ closedNotPlanned: z18.number().int().nonnegative(),
4246
+ closedUnknown: z18.number().int().nonnegative(),
4247
+ created: z18.number().int().nonnegative(),
4248
+ days: z18.number().int().positive(),
4249
+ medianCloseTimeMs: z18.number().nonnegative().nullable()
4054
4250
  }).strict().transform((row) => ({
4055
4251
  ...row,
4056
4252
  medianCloseTimeMs: row.medianCloseTimeMs ?? void 0
4057
4253
  }));
4058
- var pullRequestDaySchema = z17.object({
4059
- created: z17.number().int().nonnegative(),
4060
- date: z17.string().date()
4254
+ var pullRequestDaySchema = z18.object({
4255
+ created: z18.number().int().nonnegative(),
4256
+ date: z18.string().date()
4061
4257
  }).strict();
4062
- var issueDaySchema = z17.object({
4063
- created: z17.number().int().nonnegative(),
4064
- date: z17.string().date()
4258
+ var issueDaySchema = z18.object({
4259
+ created: z18.number().int().nonnegative(),
4260
+ date: z18.string().date()
4065
4261
  }).strict();
4066
- var issueRepositoryStatsSchema = z17.object({
4067
- closedCompleted: z17.number().int().nonnegative(),
4068
- closedDuplicate: z17.number().int().nonnegative(),
4069
- closedNotPlanned: z17.number().int().nonnegative(),
4070
- closedUnknown: z17.number().int().nonnegative(),
4071
- created: z17.number().int().nonnegative(),
4072
- repository: z17.string().min(1)
4262
+ var issueRepositoryStatsSchema = z18.object({
4263
+ closedCompleted: z18.number().int().nonnegative(),
4264
+ closedDuplicate: z18.number().int().nonnegative(),
4265
+ closedNotPlanned: z18.number().int().nonnegative(),
4266
+ closedUnknown: z18.number().int().nonnegative(),
4267
+ created: z18.number().int().nonnegative(),
4268
+ repository: z18.string().min(1)
4073
4269
  }).strict();
4074
4270
  function queryRows3(result) {
4075
4271
  if (typeof result !== "object" || result === null || !("rows" in result) || !Array.isArray(result.rows)) {
@@ -4133,7 +4329,7 @@ async function aggregatePullRequestWindows2(args) {
4133
4329
  GROUP BY windows.days
4134
4330
  ORDER BY windows.days
4135
4331
  `);
4136
- return z17.array(pullRequestStatsSchema2).parse(queryRows3(result));
4332
+ return z18.array(pullRequestStatsSchema2).parse(queryRows3(result));
4137
4333
  }
4138
4334
  async function aggregatePullRequestDays(args) {
4139
4335
  const end = new Date(args.nowMs);
@@ -4161,7 +4357,7 @@ async function aggregatePullRequestDays(args) {
4161
4357
  LEFT JOIN daily ON daily.day = days.day
4162
4358
  ORDER BY days.day
4163
4359
  `);
4164
- return z17.array(pullRequestDaySchema).parse(queryRows3(result));
4360
+ return z18.array(pullRequestDaySchema).parse(queryRows3(result));
4165
4361
  }
4166
4362
  async function aggregatePullRequestRepositories(args) {
4167
4363
  const start = new Date(args.nowMs - 30 * DAY_MS3);
@@ -4191,7 +4387,7 @@ async function aggregatePullRequestRepositories(args) {
4191
4387
  ORDER BY "merged" DESC, "created" DESC, "repository" ASC
4192
4388
  LIMIT 25
4193
4389
  `);
4194
- return z17.array(pullRequestRepositoryStatsSchema).parse(queryRows3(result));
4390
+ return z18.array(pullRequestRepositoryStatsSchema).parse(queryRows3(result));
4195
4391
  }
4196
4392
  async function aggregateIssueWindows2(args) {
4197
4393
  const starts = WINDOWS2.map(
@@ -4260,7 +4456,7 @@ async function aggregateIssueWindows2(args) {
4260
4456
  GROUP BY windows.days
4261
4457
  ORDER BY windows.days
4262
4458
  `);
4263
- return z17.array(issueStatsSchema2).parse(queryRows3(result));
4459
+ return z18.array(issueStatsSchema2).parse(queryRows3(result));
4264
4460
  }
4265
4461
  async function aggregateIssueDays(args) {
4266
4462
  const end = new Date(args.nowMs);
@@ -4288,7 +4484,7 @@ async function aggregateIssueDays(args) {
4288
4484
  LEFT JOIN daily ON daily.day = days.day
4289
4485
  ORDER BY days.day
4290
4486
  `);
4291
- return z17.array(issueDaySchema).parse(queryRows3(result));
4487
+ return z18.array(issueDaySchema).parse(queryRows3(result));
4292
4488
  }
4293
4489
  async function aggregateIssueRepositories(args) {
4294
4490
  const start = new Date(args.nowMs - 30 * DAY_MS3);
@@ -4325,7 +4521,7 @@ async function aggregateIssueRepositories(args) {
4325
4521
  ORDER BY "created" DESC, "closedCompleted" DESC, "repository" ASC
4326
4522
  LIMIT 25
4327
4523
  `);
4328
- return z17.array(issueRepositoryStatsSchema).parse(queryRows3(result));
4524
+ return z18.array(issueRepositoryStatsSchema).parse(queryRows3(result));
4329
4525
  }
4330
4526
  function formatPercent2(value) {
4331
4527
  return value === void 0 ? "\u2014" : `${Math.round(value * 100)}%`;
@@ -4489,18 +4685,18 @@ async function buildGitHubOutcomeReport(args) {
4489
4685
  }
4490
4686
 
4491
4687
  // src/pull-request-outcomes/commit-composition.ts
4492
- import { z as z18 } from "zod";
4493
- var canonicalCommitSchema = z18.object({
4494
- authorEmail: z18.string().nullable(),
4495
- authorLogin: z18.string().nullable()
4688
+ import { z as z19 } from "zod";
4689
+ var canonicalCommitSchema = z19.object({
4690
+ authorEmail: z19.string().nullable(),
4691
+ authorLogin: z19.string().nullable()
4496
4692
  }).strict();
4497
- var providerCommitSchema = z18.object({
4498
- author: z18.object({ login: z18.string() }).passthrough().nullable(),
4499
- commit: z18.object({
4500
- author: z18.object({ email: z18.string() }).passthrough().nullable()
4693
+ var providerCommitSchema = z19.object({
4694
+ author: z19.object({ login: z19.string() }).passthrough().nullable(),
4695
+ commit: z19.object({
4696
+ author: z19.object({ email: z19.string() }).passthrough().nullable()
4501
4697
  }).passthrough()
4502
4698
  }).passthrough();
4503
- var commitPageSchema = z18.array(providerCommitSchema).transform(
4699
+ var commitPageSchema = z19.array(providerCommitSchema).transform(
4504
4700
  (commits) => commits.map(
4505
4701
  (commit) => canonicalCommitSchema.parse({
4506
4702
  authorEmail: commit.commit.author?.email ?? null,
@@ -5235,6 +5431,28 @@ function githubApiWriteGrantName(method, upstreamUrl) {
5235
5431
  }
5236
5432
  return void 0;
5237
5433
  }
5434
+ function reviewThreadResolveRepository(operation, method, upstreamUrl, bodyText) {
5435
+ const prefix = "github.pull.review-thread.resolve:";
5436
+ if (method !== "POST" || !isGitHubGraphqlUrl(upstreamUrl) || !operation?.startsWith(prefix)) {
5437
+ return void 0;
5438
+ }
5439
+ const repository = operation.slice(prefix.length);
5440
+ if (!/^[^/]+\/[^/]+$/.test(repository)) return void 0;
5441
+ const parsed = parseGitHubGraphqlRequest(bodyText);
5442
+ if (parsed?.operationName !== "ResolveReviewThread" || !/\bmutation\s+ResolveReviewThread\b/.test(parsed.normalized) || !/\bresolveReviewThread\b/.test(parsed.normalized)) {
5443
+ return void 0;
5444
+ }
5445
+ return repository;
5446
+ }
5447
+ function repositoryLeaseScopeFromRef(repository) {
5448
+ const [owner, name] = repository.split("/");
5449
+ if (!owner || !name) {
5450
+ throw new EgressPolicyDenied2(
5451
+ "GitHub review thread resolution does not identify a target repository."
5452
+ );
5453
+ }
5454
+ return githubRepositoryLeaseScope({ owner, name });
5455
+ }
5238
5456
  function isGitHubGraphqlMutation(method, upstreamUrl, bodyText, field) {
5239
5457
  if (method !== "POST" || !isGitHubGraphqlUrl(upstreamUrl)) return false;
5240
5458
  const parsed = parseGitHubGraphqlRequest(bodyText);
@@ -5326,6 +5544,20 @@ async function githubGrantForEgress(ctx) {
5326
5544
  repositoryLeaseScope(upstreamUrl)
5327
5545
  );
5328
5546
  }
5547
+ const reviewThreadRepository = reviewThreadResolveRepository(
5548
+ ctx.request.operation,
5549
+ method,
5550
+ upstreamUrl,
5551
+ ctx.request.bodyText
5552
+ );
5553
+ if (reviewThreadRepository) {
5554
+ return grantForAccess(
5555
+ "write",
5556
+ "github.installation-write",
5557
+ "installation-write",
5558
+ repositoryLeaseScopeFromRef(reviewThreadRepository)
5559
+ );
5560
+ }
5329
5561
  const graphqlAccess = githubGraphqlAccess(
5330
5562
  method,
5331
5563
  upstreamUrl,
@@ -5544,7 +5776,7 @@ function githubPlugin(options = {}) {
5544
5776
  });
5545
5777
  },
5546
5778
  tools(ctx) {
5547
- return createGitHubTools(ctx);
5779
+ return createGitHubTools(ctx, readEnv(botEmailEnv));
5548
5780
  },
5549
5781
  workspacePrepare: prepareWorkspace,
5550
5782
  async sandboxPrepare(ctx) {
@@ -5563,11 +5795,8 @@ function githubPlugin(options = {}) {
5563
5795
  if (ctx.tool.name !== "bash") {
5564
5796
  return;
5565
5797
  }
5566
- const botName = readEnv(botNameEnv);
5567
- const botEmail = readEnv(botEmailEnv);
5568
- if (!botName || !botEmail) {
5569
- return;
5570
- }
5798
+ const botName = requireEnv(botNameEnv);
5799
+ const botEmail = requireEnv(botEmailEnv);
5571
5800
  ctx.env.set("GIT_AUTHOR_NAME", botName);
5572
5801
  ctx.env.set("GIT_AUTHOR_EMAIL", botEmail);
5573
5802
  ctx.env.set("JUNIOR_GIT_AUTHOR_NAME", botName);
@@ -7,6 +7,7 @@ export declare function createGitHubCloneRepositoryTool(ctx: ToolRegistrationHoo
7
7
  [x: string]: unknown;
8
8
  path: string;
9
9
  repo: string;
10
+ workspaces: string[];
10
11
  target: "cloneRepository";
11
12
  truncated?: boolean | undefined;
12
13
  continuation?: {
@@ -17,6 +18,7 @@ export declare function createGitHubCloneRepositoryTool(ctx: ToolRegistrationHoo
17
18
  [x: string]: unknown;
18
19
  path: string;
19
20
  repo: string;
21
+ workspaces: string[];
20
22
  target: "cloneRepository";
21
23
  truncated?: boolean | undefined;
22
24
  continuation?: {
@@ -0,0 +1,30 @@
1
+ import { type ToolRegistrationHookContext } from "@sentry/junior-plugin-api";
2
+ /** Resolve one review thread after GitHub proves it belongs to a Junior-authored PR. */
3
+ export declare function createGitHubResolvePullRequestReviewThreadTool(ctx: ToolRegistrationHookContext, botEmail: string | undefined): import("@sentry/junior-plugin-api").PluginToolDefinition<{
4
+ repo: string;
5
+ threadId: string;
6
+ }, {
7
+ [x: string]: unknown;
8
+ target: "resolvePullRequestReviewThread";
9
+ repo: string;
10
+ number: number;
11
+ threadId: string;
12
+ resolved: boolean;
13
+ truncated?: boolean | undefined;
14
+ continuation?: {
15
+ arguments: Record<string, unknown>;
16
+ reason?: string | undefined;
17
+ } | undefined;
18
+ }, {
19
+ [x: string]: unknown;
20
+ target: "resolvePullRequestReviewThread";
21
+ repo: string;
22
+ number: number;
23
+ threadId: string;
24
+ resolved: boolean;
25
+ truncated?: boolean | undefined;
26
+ continuation?: {
27
+ arguments: Record<string, unknown>;
28
+ reason?: string | undefined;
29
+ } | undefined;
30
+ }>;
package/dist/tools.d.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  import type { PluginToolDefinition, ToolRegistrationHookContext } from "@sentry/junior-plugin-api";
2
2
  /** Build the GitHub plugin's runtime tools from their per-tool modules. */
3
- export declare function createGitHubTools(ctx: ToolRegistrationHookContext): Record<string, PluginToolDefinition>;
3
+ export declare function createGitHubTools(ctx: ToolRegistrationHookContext, botEmail?: string): Record<string, PluginToolDefinition>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sentry/junior-github",
3
- "version": "0.163.0",
3
+ "version": "0.165.0",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -31,7 +31,7 @@
31
31
  "@sinclair/typebox": "^0.34.49",
32
32
  "drizzle-orm": "^0.45.2",
33
33
  "zod": "^4.4.3",
34
- "@sentry/junior-plugin-api": "0.163.0"
34
+ "@sentry/junior-plugin-api": "0.165.0"
35
35
  },
36
36
  "devDependencies": {
37
37
  "@types/node": "^25.9.1",
@@ -5,7 +5,7 @@ description: Work with GitHub repositories, source code, branches, commits, pull
5
5
 
6
6
  # GitHub Code Operations
7
7
 
8
- Use `git` and `gh` for repository work. Use `github_createPullRequest`, not `gh pr create`, for new PRs. Use `github_updatePullRequest`, not raw `gh api`/`gh pr edit`, when changing PR title, body, base, or open/closed state.
8
+ Use `git` and `gh` for repository work. Use `github_createPullRequest`, not `gh pr create`, for new PRs. Use `github_updatePullRequest`, not raw `gh api`/`gh pr edit`, when changing PR title, body, base, or open/closed state. Use `github_resolvePullRequestReviewThread`, not raw `gh api graphql` `resolveReviewThread`, when resolving review threads on Junior-authored PRs.
9
9
 
10
10
  ## References
11
11
 
@@ -1,6 +1,6 @@
1
1
  # GitHub API Surface — code & pull requests
2
2
 
3
- PR creation uses Junior's `github_createPullRequest` tool. PR title, body, base, and open/closed state updates use `github_updatePullRequest` so Junior keeps requester attribution and the conversation footer. Other supported mutations use allowlisted REST endpoints through `gh api`; generic GraphQL-backed `gh pr` mutations are not supported.
3
+ PR creation uses Junior's `github_createPullRequest` tool. PR title, body, base, and open/closed state updates use `github_updatePullRequest` so Junior keeps requester attribution and the conversation footer. Review-thread resolve uses `github_resolvePullRequestReviewThread` because GitHub exposes only the GraphQL `resolveReviewThread` mutation (no REST endpoint and no first-class `gh pr` subcommand). Other supported mutations use allowlisted REST endpoints through `gh api`; generic GraphQL-backed `gh pr` mutations are not supported.
4
4
 
5
5
  ## Repo scoping
6
6
 
@@ -50,6 +50,7 @@ Treat explicit repo flags as command-targeting safety rails, not as a credential
50
50
  | Submit pull request review | `gh api repos/owner/repo/pulls/NUMBER/reviews --method POST --input review.json` |
51
51
  | Post inline review comment | `gh api repos/owner/repo/pulls/NUMBER/comments --method POST --input comment.json` |
52
52
  | Reply to inline review comment | `gh api repos/owner/repo/pulls/NUMBER/comments/COMMENT_ID/replies --method POST --input reply.json` |
53
+ | Resolve review thread | `github_resolvePullRequestReviewThread({ repo: "owner/repo", threadId: "PRRT_..." })` (GraphQL `resolveReviewThread` substitute; Junior-authored PRs only) |
53
54
  | View pull request | `gh pr view NUMBER --repo owner/repo [--json ...]` |
54
55
  | List pull requests | `gh pr list --repo owner/repo [--state open \| closed \| merged]` |
55
56
  | Diff pull request | `gh pr diff NUMBER --repo owner/repo` |
@@ -78,6 +79,7 @@ jr-rpc config set github.repo owner/repo
78
79
  - Use `github_updatePullRequest` for title, body, base, or open/closed state changes. Do not raw-`PATCH` `/repos/.../pulls/NUMBER`; that path is denied so Junior can keep the conversation footer.
79
80
  - Merge, fork creation, REST contents/Git database writes, and repository administration are outside the current write allowlist.
80
81
  - Pull request reviews and inline review comments use the same repository-scoped `installation-write` credential as other bot-owned PR writes, so they post as Junior even on headless turns. Merge remains denied.
82
+ - Resolve review threads with `github_resolvePullRequestReviewThread`. That tool is the Junior equivalent of `gh api graphql` `resolveReviewThread`; raw GraphQL mutations stay denied, and the tool only succeeds on Junior-authored PRs.
81
83
  - If the explicit `git push` fails with 401/403 or another access/permission error, verify the repo context and retry once. If it still fails, load troubleshooting guidance and report the exact command failure.
82
84
  - PR comments, labels, and assignees use GitHub's issue endpoints; use the `github-issues` REST guidance for those operations. All allowlisted bot writes share the same repository-scoped `installation-write` credential.
83
85
  - To embed a local image in a GitHub issue, pull request, review, or comment, call `publishImage` first. That tool returns a durable public URL. The published image is public to anyone on the internet who has the URL. Embed the URL with normal GitHub Markdown. Do not use private Slack file links or conversation attachment URLs.
@@ -19,6 +19,7 @@ Use this table to recover quickly while keeping operations deterministic.
19
19
  | `github_createPullRequest` returns 422 for `head` | The head branch was not pushed or the explicit head ref is wrong. | Push the branch, then retry with explicit `repo`, `head`, and `base` values. |
20
20
  | `github_createPullRequest` fails with 422 validation on `base` | The `base` branch does not exist in the target repo. | Resolve the default branch with `gh repo view owner/repo --json defaultBranchRef --jq .defaultBranchRef.name`, then retry with that value as `base`. |
21
21
  | `403` names `github_updatePullRequest` | Raw PR title/body/base/state PATCH was blocked so Junior can own the footer. | Retry with `github_updatePullRequest`; do not use `gh api .../pulls/NUMBER --method PATCH` or `gh pr edit`. |
22
+ | `GraphQL mutations are not enabled` / resolve thread denied | Raw `gh api graphql` `resolveReviewThread` is blocked. | Retry with `github_resolvePullRequestReviewThread({ repo, threadId })`. Only Junior-authored PRs are allowed. |
22
23
  | `github_updatePullRequest` returns upstream 401/403 | The App installation or target repository does not permit the operation. | Use the structured upstream denial to verify installation scope and accepted permissions; do not request user OAuth for this bot-owned operation. |
23
24
  | `git blame`, long log history, or old commits are missing after clone | Repo was cloned shallow by design. | Fetch the required ref and deepen it incrementally; use `--unshallow` only when bounded deepening is insufficient. |
24
25
  | Rebase, merge-base, or `origin/BASE...HEAD` comparison fails or gives odd ancestry | Required ancestry or the remote-tracking base ref is absent from the shallow clone. | Fetch a bounded depth into `BASE:refs/remotes/origin/BASE`, deepen that base ref until the merge base exists, and compare or rebase against `origin/BASE`. Never force-push around incomplete history. |