@sentry/junior-github 0.163.0 → 0.164.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
  });
@@ -2586,8 +2600,200 @@ function createGitHubUpdatePullRequestTool(ctx) {
2586
2600
  });
2587
2601
  }
2588
2602
 
2603
+ // src/tools/resolve-pull-request-review-thread.ts
2604
+ import {
2605
+ definePluginTool as definePluginTool10,
2606
+ PluginToolInputError as PluginToolInputError11,
2607
+ pluginToolOutputSchema as pluginToolOutputSchema10
2608
+ } from "@sentry/junior-plugin-api";
2609
+ import { z as z10 } from "zod";
2610
+
2611
+ // src/webhooks/ownership.ts
2612
+ var GITHUB_NOREPLY_DOMAIN = "users.noreply.github.com";
2613
+ function botLoginFromEmail(value) {
2614
+ const email = value?.trim();
2615
+ if (!email) return void 0;
2616
+ const separator = email.lastIndexOf("@");
2617
+ if (separator <= 0) return void 0;
2618
+ const domain = email.slice(separator + 1).toLowerCase();
2619
+ if (domain !== GITHUB_NOREPLY_DOMAIN) return void 0;
2620
+ const localPart = email.slice(0, separator);
2621
+ const login = localPart.slice(localPart.indexOf("+") + 1).trim();
2622
+ return login.toLowerCase().endsWith("[bot]") ? login : void 0;
2623
+ }
2624
+
2625
+ // src/tools/resolve-pull-request-review-thread.ts
2626
+ var inputSchema8 = z10.object({
2627
+ repo: z10.string().describe(
2628
+ 'Repository in "owner/name" format. Required for repository-scoped credentials (GraphQL has no repo path).'
2629
+ ),
2630
+ threadId: z10.string().trim().min(1).describe(
2631
+ "GitHub pull request review thread node ID (the same `threadId` / `id` variable used by `gh api graphql` resolveReviewThread)."
2632
+ )
2633
+ }).strict();
2634
+ var outputSchema8 = pluginToolOutputSchema10.extend({
2635
+ target: z10.literal("resolvePullRequestReviewThread"),
2636
+ repo: z10.string(),
2637
+ number: z10.number(),
2638
+ threadId: z10.string(),
2639
+ resolved: z10.boolean()
2640
+ });
2641
+ function parseRepo10(value) {
2642
+ const parts = value.split("/").map((part) => part.trim());
2643
+ if (parts.length !== 2 || !parts[0] || !parts[1]) {
2644
+ throw new PluginToolInputError11('repo must use "owner/name" format');
2645
+ }
2646
+ return { owner: parts[0], name: parts[1], ref: `${parts[0]}/${parts[1]}` };
2647
+ }
2648
+ async function readJson7(response) {
2649
+ const text2 = await response.text();
2650
+ if (!text2) return void 0;
2651
+ try {
2652
+ return JSON.parse(text2);
2653
+ } catch {
2654
+ return text2;
2655
+ }
2656
+ }
2657
+ function githubError(payload) {
2658
+ if (payload && typeof payload === "object" && !Array.isArray(payload)) {
2659
+ const message = payload.message;
2660
+ if (typeof message === "string" && message.trim()) return message.trim();
2661
+ }
2662
+ return "GitHub request failed";
2663
+ }
2664
+ function createGitHubResolvePullRequestReviewThreadTool(ctx, botEmail) {
2665
+ return definePluginTool10({
2666
+ annotations: {
2667
+ destructiveHint: true,
2668
+ idempotentHint: true,
2669
+ openWorldHint: true,
2670
+ readOnlyHint: false
2671
+ },
2672
+ 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.",
2673
+ inputSchema: inputSchema8,
2674
+ outputSchema: outputSchema8,
2675
+ async execute(input) {
2676
+ const parsedInput = inputSchema8.safeParse(input);
2677
+ if (!parsedInput.success) {
2678
+ throw new PluginToolInputError11(
2679
+ "Invalid GitHub resolvePullRequestReviewThread input.",
2680
+ { cause: parsedInput.error }
2681
+ );
2682
+ }
2683
+ const repo = parseRepo10(parsedInput.data.repo);
2684
+ const botLogin = botLoginFromEmail(botEmail)?.toLowerCase();
2685
+ if (!botLogin) {
2686
+ throw new Error("GitHub App bot identity is not configured.");
2687
+ }
2688
+ const query = `query ReviewThreadOwnership($threadId: ID!) {
2689
+ node(id: $threadId) {
2690
+ ... on PullRequestReviewThread {
2691
+ id
2692
+ isResolved
2693
+ pullRequest {
2694
+ number
2695
+ repository { nameWithOwner }
2696
+ author { login }
2697
+ }
2698
+ }
2699
+ }
2700
+ }`;
2701
+ const lookupResponse = await ctx.egress.fetch({
2702
+ provider: "github",
2703
+ operation: "github.pull.review-thread.get",
2704
+ request: new Request("https://api.github.com/graphql", {
2705
+ method: "POST",
2706
+ headers: { "Content-Type": "application/json" },
2707
+ body: JSON.stringify({
2708
+ operationName: "ReviewThreadOwnership",
2709
+ query,
2710
+ variables: { threadId: parsedInput.data.threadId }
2711
+ })
2712
+ })
2713
+ });
2714
+ const lookupPayload = await readJson7(lookupResponse);
2715
+ if (!lookupResponse.ok) {
2716
+ throw new Error(
2717
+ `GitHub review thread lookup failed with HTTP ${lookupResponse.status}: ${githubError(lookupPayload)}`
2718
+ );
2719
+ }
2720
+ const thread = z10.object({
2721
+ data: z10.object({
2722
+ node: z10.object({
2723
+ id: z10.string(),
2724
+ isResolved: z10.boolean(),
2725
+ pullRequest: z10.object({
2726
+ author: z10.object({ login: z10.string() }),
2727
+ number: z10.number(),
2728
+ repository: z10.object({ nameWithOwner: z10.string() })
2729
+ })
2730
+ }).nullable()
2731
+ })
2732
+ }).parse(lookupPayload).data.node;
2733
+ if (!thread) throw new Error("GitHub review thread was not found.");
2734
+ const pullRequest = thread.pullRequest;
2735
+ const ownsPullRequest = pullRequest.repository.nameWithOwner.toLowerCase() === repo.ref.toLowerCase() && pullRequest.author.login.toLowerCase() === botLogin;
2736
+ if (!ownsPullRequest) {
2737
+ throw new Error(
2738
+ "Junior can only resolve review threads on pull requests it authored."
2739
+ );
2740
+ }
2741
+ if (thread.isResolved) {
2742
+ return {
2743
+ target: "resolvePullRequestReviewThread",
2744
+ repo: repo.ref,
2745
+ number: pullRequest.number,
2746
+ threadId: thread.id,
2747
+ resolved: true
2748
+ };
2749
+ }
2750
+ const mutation = `mutation ResolveReviewThread($threadId: ID!) {
2751
+ resolveReviewThread(input: {threadId: $threadId}) {
2752
+ thread { id isResolved }
2753
+ }
2754
+ }`;
2755
+ const resolveResponse = await ctx.egress.fetch({
2756
+ provider: "github",
2757
+ operation: `github.pull.review-thread.resolve:${repo.ref.toLowerCase()}`,
2758
+ request: new Request("https://api.github.com/graphql", {
2759
+ method: "POST",
2760
+ headers: { "Content-Type": "application/json" },
2761
+ body: JSON.stringify({
2762
+ operationName: "ResolveReviewThread",
2763
+ query: mutation,
2764
+ variables: { threadId: thread.id }
2765
+ })
2766
+ })
2767
+ });
2768
+ const resolvePayload = await readJson7(resolveResponse);
2769
+ if (!resolveResponse.ok) {
2770
+ throw new Error(
2771
+ `GitHub review thread resolution failed with HTTP ${resolveResponse.status}: ${githubError(resolvePayload)}`
2772
+ );
2773
+ }
2774
+ const resolved = z10.object({
2775
+ data: z10.object({
2776
+ resolveReviewThread: z10.object({
2777
+ thread: z10.object({ id: z10.string(), isResolved: z10.boolean() })
2778
+ })
2779
+ })
2780
+ }).parse(resolvePayload).data.resolveReviewThread.thread;
2781
+ if (resolved.id !== thread.id || !resolved.isResolved) {
2782
+ throw new Error("GitHub did not resolve the requested review thread.");
2783
+ }
2784
+ return {
2785
+ target: "resolvePullRequestReviewThread",
2786
+ repo: repo.ref,
2787
+ number: pullRequest.number,
2788
+ threadId: resolved.id,
2789
+ resolved: true
2790
+ };
2791
+ }
2792
+ });
2793
+ }
2794
+
2589
2795
  // src/tools.ts
2590
- function createGitHubTools(ctx) {
2796
+ function createGitHubTools(ctx, botEmail) {
2591
2797
  return {
2592
2798
  cloneRepository: createGitHubCloneRepositoryTool(ctx),
2593
2799
  createIssue: createGitHubIssueTool(ctx),
@@ -2596,6 +2802,7 @@ function createGitHubTools(ctx) {
2596
2802
  getPullRequest: createGitHubGetPullRequestTool(ctx),
2597
2803
  getRelease: createGitHubGetReleaseTool(ctx),
2598
2804
  getRepository: createGitHubGetRepositoryTool(ctx),
2805
+ resolvePullRequestReviewThread: createGitHubResolvePullRequestReviewThreadTool(ctx, botEmail),
2599
2806
  updateIssue: createGitHubUpdateIssueTool(ctx),
2600
2807
  updatePullRequest: createGitHubUpdatePullRequestTool(ctx)
2601
2808
  };
@@ -2606,7 +2813,7 @@ import { createHmac, timingSafeEqual } from "crypto";
2606
2813
 
2607
2814
  // src/issue-outcomes/store.ts
2608
2815
  import { and, eq, lte, sql as sql2 } from "drizzle-orm";
2609
- import { z as z11 } from "zod";
2816
+ import { z as z12 } from "zod";
2610
2817
 
2611
2818
  // src/db/schema.ts
2612
2819
  import { sql } from "drizzle-orm";
@@ -2618,18 +2825,18 @@ import {
2618
2825
  text,
2619
2826
  timestamp
2620
2827
  } from "drizzle-orm/pg-core";
2621
- import { z as z10 } from "zod";
2622
- var githubPullRequestStateSchema = z10.enum([
2828
+ import { z as z11 } from "zod";
2829
+ var githubPullRequestStateSchema = z11.enum([
2623
2830
  "closed_unmerged",
2624
2831
  "merged",
2625
2832
  "open"
2626
2833
  ]);
2627
- var githubPullRequestCommitCompositionSchema = z10.enum([
2834
+ var githubPullRequestCommitCompositionSchema = z11.enum([
2628
2835
  "junior_only",
2629
2836
  "mixed"
2630
2837
  ]);
2631
- var githubIssueStateSchema = z10.enum(["closed", "open"]);
2632
- var githubIssueStateReasonSchema = z10.enum([
2838
+ var githubIssueStateSchema = z11.enum(["closed", "open"]);
2839
+ var githubIssueStateReasonSchema = z11.enum([
2633
2840
  "completed",
2634
2841
  "duplicate",
2635
2842
  "not_planned",
@@ -2699,21 +2906,21 @@ var juniorGitHubPullRequestIssues = pgTable(
2699
2906
  );
2700
2907
 
2701
2908
  // 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),
2909
+ var githubIssueOutcomeInputSchema = z12.object({
2910
+ candidateOwned: z12.boolean(),
2911
+ closedAt: z12.date().optional(),
2912
+ issueId: z12.string().min(1),
2913
+ number: z12.number().int().positive(),
2914
+ openedAt: z12.date(),
2915
+ repositoryFullName: z12.string().min(1),
2916
+ repositoryId: z12.string().min(1),
2710
2917
  state: githubIssueStateSchema,
2711
2918
  stateReason: githubIssueStateReasonSchema.optional(),
2712
- updatedAt: z11.date()
2919
+ updatedAt: z12.date()
2713
2920
  }).strict();
2714
- var githubIssueConversationsInputSchema = z11.object({
2715
- conversationIds: z11.array(z11.string().min(1)).min(1),
2716
- issueId: z11.string().min(1)
2921
+ var githubIssueConversationsInputSchema = z12.object({
2922
+ conversationIds: z12.array(z12.string().min(1)).min(1),
2923
+ issueId: z12.string().min(1)
2717
2924
  }).strict();
2718
2925
  function projectionValues(input) {
2719
2926
  return {
@@ -2777,32 +2984,32 @@ async function recordGitHubIssueConversations(db, input) {
2777
2984
 
2778
2985
  // src/pull-request-outcomes/store.ts
2779
2986
  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(),
2987
+ import { z as z13 } from "zod";
2988
+ var githubPullRequestOutcomeInputSchema = z13.object({
2989
+ candidateOwned: z13.boolean(),
2990
+ closedAt: z13.date().optional(),
2784
2991
  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),
2992
+ mergedAt: z13.date().optional(),
2993
+ number: z13.number().int().positive(),
2994
+ openedAt: z13.date(),
2995
+ pullRequestId: z13.string().min(1),
2996
+ repositoryFullName: z13.string().min(1),
2997
+ repositoryId: z13.string().min(1),
2791
2998
  state: githubPullRequestStateSchema,
2792
- updatedAt: z12.date()
2999
+ updatedAt: z13.date()
2793
3000
  }).strict();
2794
- var githubPullRequestConversationsInputSchema = z12.object({
2795
- conversationIds: z12.array(z12.string().min(1)).min(1),
2796
- pullRequestId: z12.string().min(1)
3001
+ var githubPullRequestConversationsInputSchema = z13.object({
3002
+ conversationIds: z13.array(z13.string().min(1)).min(1),
3003
+ pullRequestId: z13.string().min(1)
2797
3004
  }).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)
3005
+ var githubPullRequestLinkedIssuesInputSchema = z13.object({
3006
+ linkedIssues: z13.array(
3007
+ z13.object({
3008
+ number: z13.number().int().positive(),
3009
+ repositoryFullName: z13.string().min(1)
2803
3010
  }).strict()
2804
3011
  ).min(1),
2805
- pullRequestId: z12.string().min(1)
3012
+ pullRequestId: z13.string().min(1)
2806
3013
  }).strict();
2807
3014
  function projectionValues2(input) {
2808
3015
  return {
@@ -2967,55 +3174,39 @@ async function recordGitHubPullRequestLinkedIssues(db, input) {
2967
3174
  }
2968
3175
 
2969
3176
  // 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(),
3177
+ import { z as z14 } from "zod";
3178
+ var canonicalIssueOutcomeSchema = z14.object({
3179
+ action: z14.enum(["opened", "closed", "reopened"]),
3180
+ issue: z14.object({
3181
+ body: z14.string().nullable().optional(),
3182
+ closed_at: z14.string().nullable().optional(),
3183
+ created_at: z14.string(),
3184
+ id: z14.number().int().positive(),
3185
+ number: z14.number().int().positive(),
2995
3186
  state_reason: githubIssueStateReasonSchema.nullable().optional(),
2996
- updated_at: z13.string(),
2997
- user: z13.object({ login: z13.string().min(1) }).strict()
3187
+ updated_at: z14.string(),
3188
+ user: z14.object({ login: z14.string().min(1) }).strict()
2998
3189
  }).strict(),
2999
- repository: z13.object({
3000
- full_name: z13.string().min(1),
3001
- id: z13.number().int().positive()
3190
+ repository: z14.object({
3191
+ full_name: z14.string().min(1),
3192
+ id: z14.number().int().positive()
3002
3193
  }).strict()
3003
3194
  }).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(),
3195
+ var issueOutcomeSchema = z14.object({
3196
+ action: z14.enum(["opened", "closed", "reopened"]),
3197
+ issue: z14.object({
3198
+ body: z14.string().nullable().optional(),
3199
+ closed_at: z14.string().nullable().optional(),
3200
+ created_at: z14.string(),
3201
+ id: z14.number().int().positive(),
3202
+ number: z14.number().int().positive(),
3012
3203
  state_reason: githubIssueStateReasonSchema.nullable().optional(),
3013
- updated_at: z13.string(),
3014
- user: z13.object({ login: z13.string().min(1) }).passthrough()
3204
+ updated_at: z14.string(),
3205
+ user: z14.object({ login: z14.string().min(1) }).passthrough()
3015
3206
  }).passthrough(),
3016
- repository: z13.object({
3017
- full_name: z13.string().min(1),
3018
- id: z13.number().int().positive()
3207
+ repository: z14.object({
3208
+ full_name: z14.string().min(1),
3209
+ id: z14.number().int().positive()
3019
3210
  }).passthrough()
3020
3211
  }).passthrough().transform(
3021
3212
  (provider) => canonicalIssueOutcomeSchema.parse({
@@ -3036,7 +3227,7 @@ var issueOutcomeSchema = z13.object({
3036
3227
  }
3037
3228
  })
3038
3229
  );
3039
- var issueLifecycleActionSchema = z13.object({ action: z13.string() }).passthrough();
3230
+ var issueLifecycleActionSchema = z14.object({ action: z14.string() }).passthrough();
3040
3231
  function timestamp2(value) {
3041
3232
  if (!value) return void 0;
3042
3233
  const parsed = new Date(value);
@@ -3082,21 +3273,21 @@ function normalizeGitHubIssueOutcome(args) {
3082
3273
  updatedAt
3083
3274
  };
3084
3275
  }
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()
3276
+ var canonicalIssueConversationSchema = z14.object({
3277
+ issue: z14.object({
3278
+ body: z14.string().nullable().optional(),
3279
+ id: z14.number().int().positive(),
3280
+ user: z14.object({ login: z14.string().min(1) }).strict()
3090
3281
  }).strict(),
3091
- sender: z13.object({ login: z13.string().min(1) }).strict().optional()
3282
+ sender: z14.object({ login: z14.string().min(1) }).strict().optional()
3092
3283
  }).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()
3284
+ var issueConversationSchema = z14.object({
3285
+ issue: z14.object({
3286
+ body: z14.string().nullable().optional(),
3287
+ id: z14.number().int().positive(),
3288
+ user: z14.object({ login: z14.string().min(1) }).passthrough()
3098
3289
  }).passthrough(),
3099
- sender: z13.object({ login: z13.string().min(1) }).passthrough().optional()
3290
+ sender: z14.object({ login: z14.string().min(1) }).passthrough().optional()
3100
3291
  }).passthrough().transform(
3101
3292
  (provider) => canonicalIssueConversationSchema.parse({
3102
3293
  issue: {
@@ -3123,41 +3314,41 @@ function normalizeGitHubIssueConversations(args) {
3123
3314
  }
3124
3315
 
3125
3316
  // 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()
3317
+ import { z as z15 } from "zod";
3318
+ var canonicalPullRequestOutcomeSchema = z15.object({
3319
+ action: z15.enum(["opened", "closed", "reopened"]),
3320
+ pull_request: z15.object({
3321
+ body: z15.string().nullable().optional(),
3322
+ closed_at: z15.string().nullable().optional(),
3323
+ created_at: z15.string(),
3324
+ id: z15.number().int().positive(),
3325
+ merged: z15.boolean(),
3326
+ merged_at: z15.string().nullable().optional(),
3327
+ number: z15.number().int().positive(),
3328
+ updated_at: z15.string(),
3329
+ user: z15.object({ login: z15.string().min(1) }).strict()
3139
3330
  }).strict(),
3140
- repository: z14.object({
3141
- full_name: z14.string().min(1),
3142
- id: z14.number().int().positive()
3331
+ repository: z15.object({
3332
+ full_name: z15.string().min(1),
3333
+ id: z15.number().int().positive()
3143
3334
  }).strict()
3144
3335
  }).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()
3336
+ var pullRequestOutcomeSchema = z15.object({
3337
+ action: z15.enum(["opened", "closed", "reopened"]),
3338
+ pull_request: z15.object({
3339
+ body: z15.string().nullable().optional(),
3340
+ closed_at: z15.string().nullable().optional(),
3341
+ created_at: z15.string(),
3342
+ id: z15.number().int().positive(),
3343
+ merged: z15.boolean(),
3344
+ merged_at: z15.string().nullable().optional(),
3345
+ number: z15.number().int().positive(),
3346
+ updated_at: z15.string(),
3347
+ user: z15.object({ login: z15.string().min(1) }).passthrough()
3157
3348
  }).passthrough(),
3158
- repository: z14.object({
3159
- full_name: z14.string().min(1),
3160
- id: z14.number().int().positive()
3349
+ repository: z15.object({
3350
+ full_name: z15.string().min(1),
3351
+ id: z15.number().int().positive()
3161
3352
  }).passthrough()
3162
3353
  }).passthrough().transform(
3163
3354
  (provider) => canonicalPullRequestOutcomeSchema.parse({
@@ -3179,24 +3370,24 @@ var pullRequestOutcomeSchema = z14.object({
3179
3370
  }
3180
3371
  })
3181
3372
  );
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()
3373
+ var pullRequestLifecycleActionSchema = z15.object({ action: z15.string() }).passthrough();
3374
+ var canonicalPullRequestConversationSchema = z15.object({
3375
+ pull_request: z15.object({
3376
+ body: z15.string().nullable().optional(),
3377
+ id: z15.number().int().positive(),
3378
+ user: z15.object({ login: z15.string().min(1) }).strict()
3188
3379
  }).strict(),
3189
- repository: z14.object({ full_name: z14.string().min(1) }).strict(),
3190
- sender: z14.object({ login: z14.string().min(1) }).strict()
3380
+ repository: z15.object({ full_name: z15.string().min(1) }).strict(),
3381
+ sender: z15.object({ login: z15.string().min(1) }).strict()
3191
3382
  }).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()
3383
+ var pullRequestConversationSchema = z15.object({
3384
+ pull_request: z15.object({
3385
+ body: z15.string().nullable().optional(),
3386
+ id: z15.number().int().positive(),
3387
+ user: z15.object({ login: z15.string().min(1) }).passthrough()
3197
3388
  }).passthrough(),
3198
- repository: z14.object({ full_name: z14.string().min(1) }).passthrough(),
3199
- sender: z14.object({ login: z14.string().min(1) }).passthrough()
3389
+ repository: z15.object({ full_name: z15.string().min(1) }).passthrough(),
3390
+ sender: z15.object({ login: z15.string().min(1) }).passthrough()
3200
3391
  }).passthrough().transform(
3201
3392
  (provider) => canonicalPullRequestConversationSchema.parse({
3202
3393
  pull_request: {
@@ -3436,14 +3627,14 @@ function createGitHubWebhookRoute(args) {
3436
3627
 
3437
3628
  // src/outcomes/profile-report.ts
3438
3629
  import { sql as sql4 } from "drizzle-orm";
3439
- import { z as z15 } from "zod";
3630
+ import { z as z16 } from "zod";
3440
3631
  var DAY_MS = 24 * 60 * 60 * 1e3;
3441
3632
  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()
3633
+ var pullRequestStatsSchema = z16.object({
3634
+ closed: z16.number().int().nonnegative(),
3635
+ created: z16.number().int().nonnegative(),
3636
+ days: z16.number().int().positive(),
3637
+ merged: z16.number().int().nonnegative()
3447
3638
  }).strict().transform((row) => {
3448
3639
  const terminal = row.merged + row.closed;
3449
3640
  return {
@@ -3451,13 +3642,13 @@ var pullRequestStatsSchema = z15.object({
3451
3642
  mergeRate: terminal > 0 ? row.merged / terminal : void 0
3452
3643
  };
3453
3644
  });
3454
- var issueStatsSchema = z15.object({
3455
- created: z15.number().int().nonnegative(),
3456
- days: z15.number().int().positive()
3645
+ var issueStatsSchema = z16.object({
3646
+ created: z16.number().int().nonnegative(),
3647
+ days: z16.number().int().positive()
3457
3648
  }).strict();
3458
- var daySchema = z15.object({
3459
- created: z15.number().int().nonnegative(),
3460
- date: z15.string().date()
3649
+ var daySchema = z16.object({
3650
+ created: z16.number().int().nonnegative(),
3651
+ date: z16.string().date()
3461
3652
  }).strict();
3462
3653
  function queryRows(result) {
3463
3654
  if (typeof result !== "object" || result === null || !("rows" in result) || !Array.isArray(result.rows)) {
@@ -3530,7 +3721,7 @@ async function aggregatePullRequestWindows(args) {
3530
3721
  GROUP BY windows.days
3531
3722
  ORDER BY windows.days
3532
3723
  `);
3533
- return z15.array(pullRequestStatsSchema).parse(queryRows(result));
3724
+ return z16.array(pullRequestStatsSchema).parse(queryRows(result));
3534
3725
  }
3535
3726
  async function aggregateIssueWindows(args) {
3536
3727
  const starts = WINDOWS.map(
@@ -3563,7 +3754,7 @@ async function aggregateIssueWindows(args) {
3563
3754
  GROUP BY windows.days
3564
3755
  ORDER BY windows.days
3565
3756
  `);
3566
- return z15.array(issueStatsSchema).parse(queryRows(result));
3757
+ return z16.array(issueStatsSchema).parse(queryRows(result));
3567
3758
  }
3568
3759
  async function aggregateOpenedDays(args) {
3569
3760
  const end = new Date(args.nowMs);
@@ -3593,7 +3784,7 @@ async function aggregateOpenedDays(args) {
3593
3784
  LEFT JOIN daily ON daily.day = days.day
3594
3785
  ORDER BY days.day
3595
3786
  `);
3596
- return z15.array(daySchema).parse(queryRows(result));
3787
+ return z16.array(daySchema).parse(queryRows(result));
3597
3788
  }
3598
3789
  async function buildGitHubProfileReport(args) {
3599
3790
  const [windows, pullRequestDays, issueWindows, issueDays] = await Promise.all(
@@ -3674,18 +3865,18 @@ async function buildGitHubProfileReport(args) {
3674
3865
 
3675
3866
  // src/outcomes/report.ts
3676
3867
  import { sql as sql6 } from "drizzle-orm";
3677
- import { z as z17 } from "zod";
3868
+ import { z as z18 } from "zod";
3678
3869
 
3679
3870
  // src/outcomes/cost.ts
3680
3871
  import { sql as sql5 } from "drizzle-orm";
3681
- import { z as z16 } from "zod";
3872
+ import { z as z17 } from "zod";
3682
3873
  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()
3874
+ var costWindowSchema = z17.object({
3875
+ days: z17.number().int().positive(),
3876
+ issueCostUsd: z17.number().nonnegative().nullable(),
3877
+ medianIssueCostUsd: z17.number().nonnegative().nullable(),
3878
+ medianPullRequestCostUsd: z17.number().nonnegative().nullable(),
3879
+ pullRequestCostUsd: z17.number().nonnegative().nullable()
3689
3880
  }).strict().transform((row) => ({
3690
3881
  days: row.days,
3691
3882
  issueCostUsd: row.issueCostUsd ?? void 0,
@@ -3693,12 +3884,12 @@ var costWindowSchema = z16.object({
3693
3884
  medianPullRequestCostUsd: row.medianPullRequestCostUsd ?? void 0,
3694
3885
  pullRequestCostUsd: row.pullRequestCostUsd ?? void 0
3695
3886
  }));
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)
3887
+ var repositoryCostSchema = z17.object({
3888
+ issueCostUsd: z17.number().nonnegative().nullable(),
3889
+ medianIssueCostUsd: z17.number().nonnegative().nullable(),
3890
+ medianPullRequestCostUsd: z17.number().nonnegative().nullable(),
3891
+ pullRequestCostUsd: z17.number().nonnegative().nullable(),
3892
+ repository: z17.string().min(1)
3702
3893
  }).strict().transform((row) => ({
3703
3894
  issueCostUsd: row.issueCostUsd ?? void 0,
3704
3895
  medianIssueCostUsd: row.medianIssueCostUsd ?? void 0,
@@ -3903,7 +4094,7 @@ async function aggregateGitHubCostWindows(args) {
3903
4094
  INNER JOIN issue_window ON issue_window.days = pull_request_window.days
3904
4095
  ORDER BY pull_request_window.days
3905
4096
  `);
3906
- return z16.array(costWindowSchema).parse(queryRows2(result));
4097
+ return z17.array(costWindowSchema).parse(queryRows2(result));
3907
4098
  }
3908
4099
  async function aggregateGitHubRepositoryCosts(args) {
3909
4100
  if (!await hasConversationUsageTable(args.db)) {
@@ -4001,7 +4192,7 @@ async function aggregateGitHubRepositoryCosts(args) {
4001
4192
  ON issue_totals.repository = repositories.repository
4002
4193
  ORDER BY "repository" ASC
4003
4194
  `);
4004
- return z16.array(repositoryCostSchema).parse(queryRows2(result));
4195
+ return z17.array(repositoryCostSchema).parse(queryRows2(result));
4005
4196
  }
4006
4197
  function formatCostUsd(value) {
4007
4198
  if (value === void 0) return "\u2014";
@@ -4016,12 +4207,12 @@ function formatCostUsd(value) {
4016
4207
  // src/outcomes/report.ts
4017
4208
  var DAY_MS3 = 24 * 60 * 60 * 1e3;
4018
4209
  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()
4210
+ var pullRequestStatsSchema2 = z18.object({
4211
+ closed: z18.number().int().nonnegative(),
4212
+ created: z18.number().int().nonnegative(),
4213
+ days: z18.number().int().positive(),
4214
+ medianMergeTimeMs: z18.number().nonnegative().nullable(),
4215
+ merged: z18.number().int().nonnegative()
4025
4216
  }).strict().transform((row) => {
4026
4217
  const terminal = row.merged + row.closed;
4027
4218
  return {
@@ -4030,12 +4221,12 @@ var pullRequestStatsSchema2 = z17.object({
4030
4221
  mergeRate: terminal > 0 ? row.merged / terminal : void 0
4031
4222
  };
4032
4223
  });
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)
4224
+ var pullRequestRepositoryStatsSchema = z18.object({
4225
+ closed: z18.number().int().nonnegative(),
4226
+ created: z18.number().int().nonnegative(),
4227
+ juniorOnly: z18.number().int().nonnegative(),
4228
+ merged: z18.number().int().nonnegative(),
4229
+ repository: z18.string().min(1)
4039
4230
  }).strict().transform((row) => {
4040
4231
  const terminal = row.merged + row.closed;
4041
4232
  return {
@@ -4043,33 +4234,33 @@ var pullRequestRepositoryStatsSchema = z17.object({
4043
4234
  mergeRate: terminal > 0 ? row.merged / terminal : void 0
4044
4235
  };
4045
4236
  });
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()
4237
+ var issueStatsSchema2 = z18.object({
4238
+ closedCompleted: z18.number().int().nonnegative(),
4239
+ closedDuplicate: z18.number().int().nonnegative(),
4240
+ closedNotPlanned: z18.number().int().nonnegative(),
4241
+ closedUnknown: z18.number().int().nonnegative(),
4242
+ created: z18.number().int().nonnegative(),
4243
+ days: z18.number().int().positive(),
4244
+ medianCloseTimeMs: z18.number().nonnegative().nullable()
4054
4245
  }).strict().transform((row) => ({
4055
4246
  ...row,
4056
4247
  medianCloseTimeMs: row.medianCloseTimeMs ?? void 0
4057
4248
  }));
4058
- var pullRequestDaySchema = z17.object({
4059
- created: z17.number().int().nonnegative(),
4060
- date: z17.string().date()
4249
+ var pullRequestDaySchema = z18.object({
4250
+ created: z18.number().int().nonnegative(),
4251
+ date: z18.string().date()
4061
4252
  }).strict();
4062
- var issueDaySchema = z17.object({
4063
- created: z17.number().int().nonnegative(),
4064
- date: z17.string().date()
4253
+ var issueDaySchema = z18.object({
4254
+ created: z18.number().int().nonnegative(),
4255
+ date: z18.string().date()
4065
4256
  }).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)
4257
+ var issueRepositoryStatsSchema = z18.object({
4258
+ closedCompleted: z18.number().int().nonnegative(),
4259
+ closedDuplicate: z18.number().int().nonnegative(),
4260
+ closedNotPlanned: z18.number().int().nonnegative(),
4261
+ closedUnknown: z18.number().int().nonnegative(),
4262
+ created: z18.number().int().nonnegative(),
4263
+ repository: z18.string().min(1)
4073
4264
  }).strict();
4074
4265
  function queryRows3(result) {
4075
4266
  if (typeof result !== "object" || result === null || !("rows" in result) || !Array.isArray(result.rows)) {
@@ -4133,7 +4324,7 @@ async function aggregatePullRequestWindows2(args) {
4133
4324
  GROUP BY windows.days
4134
4325
  ORDER BY windows.days
4135
4326
  `);
4136
- return z17.array(pullRequestStatsSchema2).parse(queryRows3(result));
4327
+ return z18.array(pullRequestStatsSchema2).parse(queryRows3(result));
4137
4328
  }
4138
4329
  async function aggregatePullRequestDays(args) {
4139
4330
  const end = new Date(args.nowMs);
@@ -4161,7 +4352,7 @@ async function aggregatePullRequestDays(args) {
4161
4352
  LEFT JOIN daily ON daily.day = days.day
4162
4353
  ORDER BY days.day
4163
4354
  `);
4164
- return z17.array(pullRequestDaySchema).parse(queryRows3(result));
4355
+ return z18.array(pullRequestDaySchema).parse(queryRows3(result));
4165
4356
  }
4166
4357
  async function aggregatePullRequestRepositories(args) {
4167
4358
  const start = new Date(args.nowMs - 30 * DAY_MS3);
@@ -4191,7 +4382,7 @@ async function aggregatePullRequestRepositories(args) {
4191
4382
  ORDER BY "merged" DESC, "created" DESC, "repository" ASC
4192
4383
  LIMIT 25
4193
4384
  `);
4194
- return z17.array(pullRequestRepositoryStatsSchema).parse(queryRows3(result));
4385
+ return z18.array(pullRequestRepositoryStatsSchema).parse(queryRows3(result));
4195
4386
  }
4196
4387
  async function aggregateIssueWindows2(args) {
4197
4388
  const starts = WINDOWS2.map(
@@ -4260,7 +4451,7 @@ async function aggregateIssueWindows2(args) {
4260
4451
  GROUP BY windows.days
4261
4452
  ORDER BY windows.days
4262
4453
  `);
4263
- return z17.array(issueStatsSchema2).parse(queryRows3(result));
4454
+ return z18.array(issueStatsSchema2).parse(queryRows3(result));
4264
4455
  }
4265
4456
  async function aggregateIssueDays(args) {
4266
4457
  const end = new Date(args.nowMs);
@@ -4288,7 +4479,7 @@ async function aggregateIssueDays(args) {
4288
4479
  LEFT JOIN daily ON daily.day = days.day
4289
4480
  ORDER BY days.day
4290
4481
  `);
4291
- return z17.array(issueDaySchema).parse(queryRows3(result));
4482
+ return z18.array(issueDaySchema).parse(queryRows3(result));
4292
4483
  }
4293
4484
  async function aggregateIssueRepositories(args) {
4294
4485
  const start = new Date(args.nowMs - 30 * DAY_MS3);
@@ -4325,7 +4516,7 @@ async function aggregateIssueRepositories(args) {
4325
4516
  ORDER BY "created" DESC, "closedCompleted" DESC, "repository" ASC
4326
4517
  LIMIT 25
4327
4518
  `);
4328
- return z17.array(issueRepositoryStatsSchema).parse(queryRows3(result));
4519
+ return z18.array(issueRepositoryStatsSchema).parse(queryRows3(result));
4329
4520
  }
4330
4521
  function formatPercent2(value) {
4331
4522
  return value === void 0 ? "\u2014" : `${Math.round(value * 100)}%`;
@@ -4489,18 +4680,18 @@ async function buildGitHubOutcomeReport(args) {
4489
4680
  }
4490
4681
 
4491
4682
  // 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()
4683
+ import { z as z19 } from "zod";
4684
+ var canonicalCommitSchema = z19.object({
4685
+ authorEmail: z19.string().nullable(),
4686
+ authorLogin: z19.string().nullable()
4496
4687
  }).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()
4688
+ var providerCommitSchema = z19.object({
4689
+ author: z19.object({ login: z19.string() }).passthrough().nullable(),
4690
+ commit: z19.object({
4691
+ author: z19.object({ email: z19.string() }).passthrough().nullable()
4501
4692
  }).passthrough()
4502
4693
  }).passthrough();
4503
- var commitPageSchema = z18.array(providerCommitSchema).transform(
4694
+ var commitPageSchema = z19.array(providerCommitSchema).transform(
4504
4695
  (commits) => commits.map(
4505
4696
  (commit) => canonicalCommitSchema.parse({
4506
4697
  authorEmail: commit.commit.author?.email ?? null,
@@ -5235,6 +5426,28 @@ function githubApiWriteGrantName(method, upstreamUrl) {
5235
5426
  }
5236
5427
  return void 0;
5237
5428
  }
5429
+ function reviewThreadResolveRepository(operation, method, upstreamUrl, bodyText) {
5430
+ const prefix = "github.pull.review-thread.resolve:";
5431
+ if (method !== "POST" || !isGitHubGraphqlUrl(upstreamUrl) || !operation?.startsWith(prefix)) {
5432
+ return void 0;
5433
+ }
5434
+ const repository = operation.slice(prefix.length);
5435
+ if (!/^[^/]+\/[^/]+$/.test(repository)) return void 0;
5436
+ const parsed = parseGitHubGraphqlRequest(bodyText);
5437
+ if (parsed?.operationName !== "ResolveReviewThread" || !/\bmutation\s+ResolveReviewThread\b/.test(parsed.normalized) || !/\bresolveReviewThread\b/.test(parsed.normalized)) {
5438
+ return void 0;
5439
+ }
5440
+ return repository;
5441
+ }
5442
+ function repositoryLeaseScopeFromRef(repository) {
5443
+ const [owner, name] = repository.split("/");
5444
+ if (!owner || !name) {
5445
+ throw new EgressPolicyDenied2(
5446
+ "GitHub review thread resolution does not identify a target repository."
5447
+ );
5448
+ }
5449
+ return githubRepositoryLeaseScope({ owner, name });
5450
+ }
5238
5451
  function isGitHubGraphqlMutation(method, upstreamUrl, bodyText, field) {
5239
5452
  if (method !== "POST" || !isGitHubGraphqlUrl(upstreamUrl)) return false;
5240
5453
  const parsed = parseGitHubGraphqlRequest(bodyText);
@@ -5326,6 +5539,20 @@ async function githubGrantForEgress(ctx) {
5326
5539
  repositoryLeaseScope(upstreamUrl)
5327
5540
  );
5328
5541
  }
5542
+ const reviewThreadRepository = reviewThreadResolveRepository(
5543
+ ctx.request.operation,
5544
+ method,
5545
+ upstreamUrl,
5546
+ ctx.request.bodyText
5547
+ );
5548
+ if (reviewThreadRepository) {
5549
+ return grantForAccess(
5550
+ "write",
5551
+ "github.installation-write",
5552
+ "installation-write",
5553
+ repositoryLeaseScopeFromRef(reviewThreadRepository)
5554
+ );
5555
+ }
5329
5556
  const graphqlAccess = githubGraphqlAccess(
5330
5557
  method,
5331
5558
  upstreamUrl,
@@ -5544,7 +5771,7 @@ function githubPlugin(options = {}) {
5544
5771
  });
5545
5772
  },
5546
5773
  tools(ctx) {
5547
- return createGitHubTools(ctx);
5774
+ return createGitHubTools(ctx, readEnv(botEmailEnv));
5548
5775
  },
5549
5776
  workspacePrepare: prepareWorkspace,
5550
5777
  async sandboxPrepare(ctx) {
@@ -5563,11 +5790,8 @@ function githubPlugin(options = {}) {
5563
5790
  if (ctx.tool.name !== "bash") {
5564
5791
  return;
5565
5792
  }
5566
- const botName = readEnv(botNameEnv);
5567
- const botEmail = readEnv(botEmailEnv);
5568
- if (!botName || !botEmail) {
5569
- return;
5570
- }
5793
+ const botName = requireEnv(botNameEnv);
5794
+ const botEmail = requireEnv(botEmailEnv);
5571
5795
  ctx.env.set("GIT_AUTHOR_NAME", botName);
5572
5796
  ctx.env.set("GIT_AUTHOR_EMAIL", botEmail);
5573
5797
  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.164.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.164.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. |