@sentry/junior-github 0.200.0 → 0.202.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.
@@ -82,6 +82,20 @@ var GITHUB_PULL_REQUEST_EVENTS = [
82
82
  "pull_request.merged",
83
83
  "pull_request.closed_unmerged"
84
84
  ];
85
+ var GITHUB_PULL_REQUEST_EVENT_GUIDANCE = {
86
+ "pull_request.comment.created": 'After acting on actionable feedback, call github_updatePullRequestFeedback with the exact repo, commentId, and commentKind from Verified details, and status "addressed" or "declined".',
87
+ "pull_request.review_comment.created": 'After acting on actionable feedback, call github_updatePullRequestFeedback with the exact repo, commentId, and commentKind from Verified details, and status "addressed" or "declined". Then resolve the thread with github_resolvePullRequestReviewThread.'
88
+ };
89
+ function gitHubPullRequestEventGuidance(guidance) {
90
+ const merged = { ...GITHUB_PULL_REQUEST_EVENT_GUIDANCE };
91
+ for (const eventType of GITHUB_PULL_REQUEST_EVENTS) {
92
+ const extra = guidance?.[eventType]?.trim();
93
+ if (!extra) continue;
94
+ const base = merged[eventType];
95
+ merged[eventType] = base ? `${base} ${extra}` : extra;
96
+ }
97
+ return merged;
98
+ }
85
99
  var GITHUB_PULL_REQUEST_SUGGESTED_EVENTS = [
86
100
  "pull_request.checks.failed",
87
101
  "pull_request.comment.created",
@@ -1374,6 +1388,7 @@ var issueCommentWebhookSchema = z2.object({
1374
1388
  action: z2.string(),
1375
1389
  comment: z2.object({
1376
1390
  body: z2.string(),
1391
+ id: z2.number(),
1377
1392
  user: z2.object({ login: z2.string().optional() }).optional()
1378
1393
  }),
1379
1394
  issue: z2.object({
@@ -1400,7 +1415,13 @@ function normalizeIssueCommentEvents(deliveryId, body) {
1400
1415
  const resource = gitHubPullRequestResource(input);
1401
1416
  const data = pullRequestMatchData([
1402
1417
  pullRequestDraftData(parsed.data.issue.draft),
1403
- pullRequestAuthorData(parsed.data.issue.user)
1418
+ pullRequestAuthorData(parsed.data.issue.user),
1419
+ {
1420
+ repo: input.repo,
1421
+ pullRequest: input.number,
1422
+ commentId: parsed.data.comment.id,
1423
+ commentKind: "conversation"
1424
+ }
1404
1425
  ]);
1405
1426
  return pullRequestTargets2(
1406
1427
  {
@@ -1493,6 +1514,7 @@ var pullRequestReviewCommentWebhookSchema = z2.object({
1493
1514
  action: z2.string(),
1494
1515
  comment: z2.object({
1495
1516
  body: z2.string(),
1517
+ id: z2.number(),
1496
1518
  user: z2.object({ login: z2.string().optional() }).optional()
1497
1519
  }),
1498
1520
  pull_request: z2.object({
@@ -1521,7 +1543,13 @@ function normalizePullRequestReviewCommentEvent(deliveryId, body) {
1521
1543
  const data = pullRequestMatchData([
1522
1544
  pullRequestDraftData(parsed.data.pull_request.draft),
1523
1545
  pullRequestAuthorData(parsed.data.pull_request.user),
1524
- pullRequestHeadBranchData(parsed.data.pull_request.head?.ref)
1546
+ pullRequestHeadBranchData(parsed.data.pull_request.head?.ref),
1547
+ {
1548
+ repo,
1549
+ pullRequest: parsed.data.pull_request.number,
1550
+ commentId: parsed.data.comment.id,
1551
+ commentKind: "review"
1552
+ }
1525
1553
  ]);
1526
1554
  return pullRequestTargets2(
1527
1555
  {
@@ -1810,6 +1838,7 @@ export {
1810
1838
  GITHUB_DEPLOYMENT_SUGGESTED_EVENTS,
1811
1839
  gitHubDeploymentSourceSubscribable,
1812
1840
  GITHUB_PULL_REQUEST_EVENTS,
1841
+ gitHubPullRequestEventGuidance,
1813
1842
  GITHUB_PULL_REQUEST_SUGGESTED_EVENTS,
1814
1843
  GITHUB_PULL_REQUEST_MATCH_FIELDS,
1815
1844
  gitHubPullRequestSubscribable,
package/dist/index.js CHANGED
@@ -23,6 +23,7 @@ import {
23
23
  credentialUnavailable,
24
24
  gitHubDeploymentSourceSubscribable,
25
25
  gitHubIssueSubscribable,
26
+ gitHubPullRequestEventGuidance,
26
27
  gitHubPullRequestSubscribable,
27
28
  gitHubReleaseSourceSubscribable,
28
29
  gitHubRepositorySubscribable,
@@ -42,7 +43,7 @@ import {
42
43
  readGrantPermissions,
43
44
  requireEnv,
44
45
  resolveUserAccount
45
- } from "./chunk-6OPIFILN.js";
46
+ } from "./chunk-F2PJG6AF.js";
46
47
 
47
48
  // src/plugin.ts
48
49
  import {
@@ -1997,7 +1998,7 @@ function createGitHubUpdatePullRequestTool(ctx) {
1997
1998
  });
1998
1999
  }
1999
2000
 
2000
- // src/tools/resolve-pull-request-review-thread.ts
2001
+ // src/tools/update-pull-request-feedback.ts
2001
2002
  import {
2002
2003
  definePluginTool as definePluginTool10,
2003
2004
  PluginToolInputError as PluginToolInputError11,
@@ -2022,21 +2023,33 @@ function botUserIdFromEmail(value) {
2022
2023
  return parseGitHubBotIdentity(value)?.userId;
2023
2024
  }
2024
2025
 
2025
- // src/tools/resolve-pull-request-review-thread.ts
2026
+ // src/tools/update-pull-request-feedback.ts
2027
+ var STATUS_TO_REACTION_CONTENT = {
2028
+ reviewing: "eyes",
2029
+ addressed: "+1",
2030
+ declined: "-1"
2031
+ };
2032
+ var FEEDBACK_REACTION_CONTENTS = new Set(
2033
+ Object.values(STATUS_TO_REACTION_CONTENT)
2034
+ );
2026
2035
  var inputSchema8 = z10.object({
2027
- repo: z10.string().describe(
2028
- 'Repository in "owner/name" format. Required because GraphQL has no repo path.'
2036
+ repo: z10.string().describe('Repository in "owner/name" format.'),
2037
+ commentKind: z10.enum(["conversation", "review"]).describe(
2038
+ 'Comment kind: "conversation" for a pull request conversation comment, "review" for an inline review comment.'
2029
2039
  ),
2030
- threadId: z10.string().trim().min(1).describe(
2031
- "GitHub pull request review thread node ID (the same `threadId` / `id` variable used by `gh api graphql` resolveReviewThread)."
2040
+ commentId: z10.number().int().positive().describe(
2041
+ "GitHub comment id (conversation comment id or inline review comment id)."
2042
+ ),
2043
+ status: z10.enum(["reviewing", "addressed", "declined"]).describe(
2044
+ "Feedback status. Maps to a reaction: reviewing -> eyes, addressed -> +1, declined -> -1."
2032
2045
  )
2033
2046
  }).strict();
2034
2047
  var outputSchema8 = pluginToolOutputSchema10.extend({
2035
- target: z10.literal("resolvePullRequestReviewThread"),
2048
+ target: z10.literal("updatePullRequestFeedback"),
2036
2049
  repo: z10.string(),
2037
- number: z10.number(),
2038
- threadId: z10.string(),
2039
- resolved: z10.boolean()
2050
+ commentId: z10.number(),
2051
+ status: z10.enum(["reviewing", "addressed", "declined"]),
2052
+ reactionId: z10.number()
2040
2053
  });
2041
2054
  function parseRepo10(value) {
2042
2055
  const parts = value.split("/").map((part) => part.trim());
@@ -2045,6 +2058,13 @@ function parseRepo10(value) {
2045
2058
  }
2046
2059
  return { owner: parts[0], name: parts[1], ref: `${parts[0]}/${parts[1]}` };
2047
2060
  }
2061
+ function pullRequestCommentReactionsPath(input) {
2062
+ const segment = input.commentKind === "conversation" ? "issues" : "pulls";
2063
+ return `/repos/${encodeURIComponent(input.owner)}/${encodeURIComponent(input.name)}/${segment}/comments/${input.commentId}/reactions`;
2064
+ }
2065
+ function pullRequestFeedbackReactionContent(status) {
2066
+ return STATUS_TO_REACTION_CONTENT[status];
2067
+ }
2048
2068
  async function readJson7(response) {
2049
2069
  const text2 = await response.text();
2050
2070
  if (!text2) return void 0;
@@ -2061,7 +2081,12 @@ function githubError(payload) {
2061
2081
  }
2062
2082
  return "GitHub request failed";
2063
2083
  }
2064
- function createGitHubResolvePullRequestReviewThreadTool(ctx, botEmail) {
2084
+ var reactionSchema = z10.object({
2085
+ id: z10.number(),
2086
+ content: z10.string(),
2087
+ user: z10.object({ id: z10.number() }).nullable()
2088
+ });
2089
+ function createGitHubUpdatePullRequestFeedbackTool(ctx, botEmail) {
2065
2090
  return definePluginTool10({
2066
2091
  annotations: {
2067
2092
  destructiveHint: true,
@@ -2069,22 +2094,175 @@ function createGitHubResolvePullRequestReviewThreadTool(ctx, botEmail) {
2069
2094
  openWorldHint: true,
2070
2095
  readOnlyHint: false
2071
2096
  },
2072
- 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 the bot authored.",
2097
+ description: "Set Junior's status reaction on GitHub pull request feedback: reviewing (eyes), addressed (+1), or declined (-1). Use the commentId and commentKind from the resource event. Replaces only Junior's prior status reaction on that comment.",
2098
+ exposure: "direct",
2073
2099
  inputSchema: inputSchema8,
2074
2100
  outputSchema: outputSchema8,
2075
2101
  async execute(input) {
2076
2102
  const parsedInput = inputSchema8.safeParse(input);
2077
2103
  if (!parsedInput.success) {
2078
2104
  throw new PluginToolInputError11(
2079
- "Invalid GitHub resolvePullRequestReviewThread input.",
2105
+ "Invalid GitHub updatePullRequestFeedback input.",
2080
2106
  { cause: parsedInput.error }
2081
2107
  );
2082
2108
  }
2109
+ const { commentId, commentKind, status } = parsedInput.data;
2083
2110
  const repo = parseRepo10(parsedInput.data.repo);
2084
2111
  const botUserId = botUserIdFromEmail(botEmail);
2085
2112
  if (botUserId === void 0) {
2086
2113
  throw new Error("GitHub App bot identity is not configured.");
2087
2114
  }
2115
+ const targetContent = pullRequestFeedbackReactionContent(status);
2116
+ const baseUrl = `https://api.github.com${pullRequestCommentReactionsPath({
2117
+ owner: repo.owner,
2118
+ name: repo.name,
2119
+ commentKind,
2120
+ commentId
2121
+ })}`;
2122
+ const listResponse = await ctx.egress.fetch({
2123
+ provider: "github",
2124
+ operation: "github.pull.comment-reaction.list",
2125
+ request: new Request(`${baseUrl}?per_page=100`, {
2126
+ headers: { Accept: "application/vnd.github+json" }
2127
+ })
2128
+ });
2129
+ const listPayload = await readJson7(listResponse);
2130
+ if (!listResponse.ok) {
2131
+ throw new Error(
2132
+ `GitHub reaction lookup failed with HTTP ${listResponse.status}: ${githubError(listPayload)}`
2133
+ );
2134
+ }
2135
+ const botReactions = z10.array(reactionSchema).parse(listPayload).filter(
2136
+ (reaction) => reaction.user?.id === botUserId && FEEDBACK_REACTION_CONTENTS.has(reaction.content)
2137
+ );
2138
+ const existing = botReactions.find(
2139
+ (reaction) => reaction.content === targetContent
2140
+ );
2141
+ for (const stale of botReactions) {
2142
+ if (stale.content === targetContent) continue;
2143
+ const deleteResponse = await ctx.egress.fetch({
2144
+ provider: "github",
2145
+ operation: "github.pull.comment-reaction.delete",
2146
+ request: new Request(`${baseUrl}/${stale.id}`, {
2147
+ method: "DELETE"
2148
+ })
2149
+ });
2150
+ if (!deleteResponse.ok && deleteResponse.status !== 404) {
2151
+ const deletePayload = await readJson7(deleteResponse);
2152
+ throw new Error(
2153
+ `GitHub reaction removal failed with HTTP ${deleteResponse.status}: ${githubError(deletePayload)}`
2154
+ );
2155
+ }
2156
+ }
2157
+ if (existing) {
2158
+ return {
2159
+ target: "updatePullRequestFeedback",
2160
+ repo: repo.ref,
2161
+ commentId,
2162
+ status,
2163
+ reactionId: existing.id
2164
+ };
2165
+ }
2166
+ const createResponse = await ctx.egress.fetch({
2167
+ provider: "github",
2168
+ operation: "github.pull.comment-reaction.create",
2169
+ request: new Request(baseUrl, {
2170
+ method: "POST",
2171
+ headers: {
2172
+ Accept: "application/vnd.github+json",
2173
+ "Content-Type": "application/json"
2174
+ },
2175
+ body: JSON.stringify({ content: targetContent })
2176
+ })
2177
+ });
2178
+ const createPayload = await readJson7(createResponse);
2179
+ if (!createResponse.ok) {
2180
+ throw new Error(
2181
+ `GitHub reaction creation failed with HTTP ${createResponse.status}: ${githubError(createPayload)}`
2182
+ );
2183
+ }
2184
+ const created = reactionSchema.parse(createPayload);
2185
+ return {
2186
+ target: "updatePullRequestFeedback",
2187
+ repo: repo.ref,
2188
+ commentId,
2189
+ status,
2190
+ reactionId: created.id
2191
+ };
2192
+ }
2193
+ });
2194
+ }
2195
+
2196
+ // src/tools/resolve-pull-request-review-thread.ts
2197
+ import {
2198
+ definePluginTool as definePluginTool11,
2199
+ PluginToolInputError as PluginToolInputError12,
2200
+ pluginToolOutputSchema as pluginToolOutputSchema11
2201
+ } from "@sentry/junior-plugin-api";
2202
+ import { z as z11 } from "zod";
2203
+ var inputSchema9 = z11.object({
2204
+ repo: z11.string().describe(
2205
+ 'Repository in "owner/name" format. Required because GraphQL has no repo path.'
2206
+ ),
2207
+ threadId: z11.string().trim().min(1).describe(
2208
+ "GitHub pull request review thread node ID (the same `threadId` / `id` variable used by `gh api graphql` resolveReviewThread)."
2209
+ )
2210
+ }).strict();
2211
+ var outputSchema9 = pluginToolOutputSchema11.extend({
2212
+ target: z11.literal("resolvePullRequestReviewThread"),
2213
+ repo: z11.string(),
2214
+ number: z11.number(),
2215
+ threadId: z11.string(),
2216
+ resolved: z11.boolean()
2217
+ });
2218
+ function parseRepo11(value) {
2219
+ const parts = value.split("/").map((part) => part.trim());
2220
+ if (parts.length !== 2 || !parts[0] || !parts[1]) {
2221
+ throw new PluginToolInputError12('repo must use "owner/name" format');
2222
+ }
2223
+ return { owner: parts[0], name: parts[1], ref: `${parts[0]}/${parts[1]}` };
2224
+ }
2225
+ async function readJson8(response) {
2226
+ const text2 = await response.text();
2227
+ if (!text2) return void 0;
2228
+ try {
2229
+ return JSON.parse(text2);
2230
+ } catch {
2231
+ return text2;
2232
+ }
2233
+ }
2234
+ function githubError2(payload) {
2235
+ if (payload && typeof payload === "object" && !Array.isArray(payload)) {
2236
+ const message = payload.message;
2237
+ if (typeof message === "string" && message.trim()) return message.trim();
2238
+ }
2239
+ return "GitHub request failed";
2240
+ }
2241
+ function createGitHubResolvePullRequestReviewThreadTool(ctx, botEmail) {
2242
+ return definePluginTool11({
2243
+ annotations: {
2244
+ destructiveHint: true,
2245
+ idempotentHint: true,
2246
+ openWorldHint: true,
2247
+ readOnlyHint: false
2248
+ },
2249
+ description: "Resolve a GitHub pull request review thread after acting on its feedback. Only works on pull requests the bot authored.",
2250
+ exposure: "direct",
2251
+ inputSchema: inputSchema9,
2252
+ outputSchema: outputSchema9,
2253
+ async execute(input) {
2254
+ const parsedInput = inputSchema9.safeParse(input);
2255
+ if (!parsedInput.success) {
2256
+ throw new PluginToolInputError12(
2257
+ "Invalid GitHub resolvePullRequestReviewThread input.",
2258
+ { cause: parsedInput.error }
2259
+ );
2260
+ }
2261
+ const repo = parseRepo11(parsedInput.data.repo);
2262
+ const botUserId = botUserIdFromEmail(botEmail);
2263
+ if (botUserId === void 0) {
2264
+ throw new Error("GitHub App bot identity is not configured.");
2265
+ }
2088
2266
  const query = `query ReviewThreadOwnership($threadId: ID!) {
2089
2267
  node(id: $threadId) {
2090
2268
  ... on PullRequestReviewThread {
@@ -2111,32 +2289,32 @@ function createGitHubResolvePullRequestReviewThreadTool(ctx, botEmail) {
2111
2289
  })
2112
2290
  })
2113
2291
  });
2114
- const lookupPayload = await readJson7(lookupResponse);
2292
+ const lookupPayload = await readJson8(lookupResponse);
2115
2293
  if (!lookupResponse.ok) {
2116
2294
  throw new Error(
2117
- `GitHub review thread lookup failed with HTTP ${lookupResponse.status}: ${githubError(lookupPayload)}`
2295
+ `GitHub review thread lookup failed with HTTP ${lookupResponse.status}: ${githubError2(lookupPayload)}`
2118
2296
  );
2119
2297
  }
2120
- const thread = z10.object({
2121
- data: z10.object({
2122
- node: z10.object({
2123
- id: z10.string(),
2124
- isResolved: z10.boolean(),
2125
- pullRequest: z10.object({
2126
- author: z10.object({ databaseId: z10.number().optional() }).nullable(),
2127
- number: z10.number(),
2128
- repository: z10.object({ nameWithOwner: z10.string() })
2298
+ const thread = z11.object({
2299
+ data: z11.object({
2300
+ node: z11.object({
2301
+ id: z11.string(),
2302
+ isResolved: z11.boolean(),
2303
+ pullRequest: z11.object({
2304
+ author: z11.object({ databaseId: z11.number().optional() }).nullable(),
2305
+ number: z11.number(),
2306
+ repository: z11.object({ nameWithOwner: z11.string() })
2129
2307
  })
2130
2308
  }).nullable()
2131
2309
  })
2132
2310
  }).parse(lookupPayload).data.node;
2133
2311
  if (!thread) {
2134
- throw new PluginToolInputError11("GitHub review thread was not found.");
2312
+ throw new PluginToolInputError12("GitHub review thread was not found.");
2135
2313
  }
2136
2314
  const pullRequest = thread.pullRequest;
2137
2315
  const ownsPullRequest = pullRequest.repository.nameWithOwner.toLowerCase() === repo.ref.toLowerCase() && pullRequest.author?.databaseId === botUserId;
2138
2316
  if (!ownsPullRequest) {
2139
- throw new PluginToolInputError11(
2317
+ throw new PluginToolInputError12(
2140
2318
  "This bot can only resolve review threads on pull requests it authored."
2141
2319
  );
2142
2320
  }
@@ -2167,16 +2345,16 @@ function createGitHubResolvePullRequestReviewThreadTool(ctx, botEmail) {
2167
2345
  })
2168
2346
  })
2169
2347
  });
2170
- const resolvePayload = await readJson7(resolveResponse);
2348
+ const resolvePayload = await readJson8(resolveResponse);
2171
2349
  if (!resolveResponse.ok) {
2172
2350
  throw new Error(
2173
- `GitHub review thread resolution failed with HTTP ${resolveResponse.status}: ${githubError(resolvePayload)}`
2351
+ `GitHub review thread resolution failed with HTTP ${resolveResponse.status}: ${githubError2(resolvePayload)}`
2174
2352
  );
2175
2353
  }
2176
- const resolved = z10.object({
2177
- data: z10.object({
2178
- resolveReviewThread: z10.object({
2179
- thread: z10.object({ id: z10.string(), isResolved: z10.boolean() })
2354
+ const resolved = z11.object({
2355
+ data: z11.object({
2356
+ resolveReviewThread: z11.object({
2357
+ thread: z11.object({ id: z11.string(), isResolved: z11.boolean() })
2180
2358
  })
2181
2359
  })
2182
2360
  }).parse(resolvePayload).data.resolveReviewThread.thread;
@@ -2196,45 +2374,45 @@ function createGitHubResolvePullRequestReviewThreadTool(ctx, botEmail) {
2196
2374
 
2197
2375
  // src/tools/submit-pull-request-review.ts
2198
2376
  import {
2199
- definePluginTool as definePluginTool11,
2200
- PluginToolInputError as PluginToolInputError12,
2201
- pluginToolOutputSchema as pluginToolOutputSchema11
2377
+ definePluginTool as definePluginTool12,
2378
+ PluginToolInputError as PluginToolInputError13,
2379
+ pluginToolOutputSchema as pluginToolOutputSchema12
2202
2380
  } from "@sentry/junior-plugin-api";
2203
- import { z as z11 } from "zod";
2204
- var reviewCommentSchema = z11.object({
2205
- path: z11.string().trim().min(1).describe("File path in the pull request."),
2206
- body: z11.string().trim().min(1).describe("Inline review comment."),
2207
- line: z11.number().int().positive().describe("Line in the pull request diff."),
2208
- side: z11.enum(["LEFT", "RIGHT"]).describe("Side of the pull request diff."),
2209
- startLine: z11.number().int().positive().optional().describe("First line of a multi-line comment."),
2210
- startSide: z11.enum(["LEFT", "RIGHT"]).optional().describe("First side of a multi-line comment.")
2381
+ import { z as z12 } from "zod";
2382
+ var reviewCommentSchema = z12.object({
2383
+ path: z12.string().trim().min(1).describe("File path in the pull request."),
2384
+ body: z12.string().trim().min(1).describe("Inline review comment."),
2385
+ line: z12.number().int().positive().describe("Line in the pull request diff."),
2386
+ side: z12.enum(["LEFT", "RIGHT"]).describe("Side of the pull request diff."),
2387
+ startLine: z12.number().int().positive().optional().describe("First line of a multi-line comment."),
2388
+ startSide: z12.enum(["LEFT", "RIGHT"]).optional().describe("First side of a multi-line comment.")
2211
2389
  }).strict().refine(
2212
2390
  ({ startLine, startSide }) => startLine === void 0 === (startSide === void 0),
2213
2391
  { message: "startLine and startSide must be provided together." }
2214
2392
  );
2215
- var inputSchema9 = z11.object({
2216
- repo: z11.string().describe('Repository in "owner/name" format.'),
2217
- number: z11.number().int().positive().describe("Pull request number."),
2218
- event: z11.enum(["COMMENT", "REQUEST_CHANGES"]).describe("Review result. Junior cannot approve pull requests."),
2219
- body: z11.string().trim().min(1).describe("Review summary."),
2220
- comments: z11.array(reviewCommentSchema).optional().describe("Optional inline review comments.")
2393
+ var inputSchema10 = z12.object({
2394
+ repo: z12.string().describe('Repository in "owner/name" format.'),
2395
+ number: z12.number().int().positive().describe("Pull request number."),
2396
+ event: z12.enum(["COMMENT", "REQUEST_CHANGES"]).describe("Review result. Junior cannot approve pull requests."),
2397
+ body: z12.string().trim().min(1).describe("Review summary."),
2398
+ comments: z12.array(reviewCommentSchema).optional().describe("Optional inline review comments.")
2221
2399
  }).strict();
2222
- var outputSchema9 = pluginToolOutputSchema11.extend({
2223
- target: z11.literal("submitPullRequestReview"),
2224
- repo: z11.string(),
2225
- number: z11.number(),
2226
- reviewId: z11.number(),
2227
- state: z11.string(),
2228
- url: z11.string()
2400
+ var outputSchema10 = pluginToolOutputSchema12.extend({
2401
+ target: z12.literal("submitPullRequestReview"),
2402
+ repo: z12.string(),
2403
+ number: z12.number(),
2404
+ reviewId: z12.number(),
2405
+ state: z12.string(),
2406
+ url: z12.string()
2229
2407
  });
2230
- function parseRepo11(value) {
2408
+ function parseRepo12(value) {
2231
2409
  const parts = value.split("/").map((part) => part.trim());
2232
2410
  if (parts.length !== 2 || !parts[0] || !parts[1]) {
2233
- throw new PluginToolInputError12('repo must use "owner/name" format');
2411
+ throw new PluginToolInputError13('repo must use "owner/name" format');
2234
2412
  }
2235
2413
  return { owner: parts[0], name: parts[1], ref: `${parts[0]}/${parts[1]}` };
2236
2414
  }
2237
- async function readJson8(response) {
2415
+ async function readJson9(response) {
2238
2416
  const text2 = await response.text();
2239
2417
  if (!text2) return void 0;
2240
2418
  try {
@@ -2243,7 +2421,7 @@ async function readJson8(response) {
2243
2421
  return text2;
2244
2422
  }
2245
2423
  }
2246
- function githubError2(body) {
2424
+ function githubError3(body) {
2247
2425
  if (body && typeof body === "object" && !Array.isArray(body)) {
2248
2426
  const message = body.message;
2249
2427
  if (typeof message === "string" && message.trim()) return message.trim();
@@ -2252,14 +2430,14 @@ function githubError2(body) {
2252
2430
  return "GitHub request failed";
2253
2431
  }
2254
2432
  function throwReviewError(status, body) {
2255
- const message = `GitHub pull request review failed with HTTP ${status}: ${githubError2(body)}`;
2433
+ const message = `GitHub pull request review failed with HTTP ${status}: ${githubError3(body)}`;
2256
2434
  if (status === 404 || status === 422) {
2257
- throw new PluginToolInputError12(message);
2435
+ throw new PluginToolInputError13(message);
2258
2436
  }
2259
2437
  throw new Error(message);
2260
2438
  }
2261
2439
  function createGitHubSubmitPullRequestReviewTool(egress) {
2262
- return definePluginTool11({
2440
+ return definePluginTool12({
2263
2441
  annotations: {
2264
2442
  destructiveHint: true,
2265
2443
  idempotentHint: false,
@@ -2268,10 +2446,10 @@ function createGitHubSubmitPullRequestReviewTool(egress) {
2268
2446
  },
2269
2447
  description: "Submit a GitHub pull request review as a comment or change request. Use this instead of `gh pr review`, GraphQL, or direct REST calls. Junior cannot approve pull requests.",
2270
2448
  exposure: "direct",
2271
- inputSchema: inputSchema9,
2272
- outputSchema: outputSchema9,
2449
+ inputSchema: inputSchema10,
2450
+ outputSchema: outputSchema10,
2273
2451
  async execute(review) {
2274
- const repo = parseRepo11(review.repo);
2452
+ const repo = parseRepo12(review.repo);
2275
2453
  const comments = review.comments?.map((comment) => ({
2276
2454
  path: comment.path,
2277
2455
  body: comment.body,
@@ -2300,11 +2478,11 @@ function createGitHubSubmitPullRequestReviewTool(egress) {
2300
2478
  }
2301
2479
  )
2302
2480
  });
2303
- const parsed = await readJson8(response);
2481
+ const parsed = await readJson9(response);
2304
2482
  if (!response.ok) {
2305
2483
  throwReviewError(response.status, parsed);
2306
2484
  }
2307
- const providerResult = z11.object({ id: z11.number(), html_url: z11.string(), state: z11.string() }).parse(parsed);
2485
+ const providerResult = z12.object({ id: z12.number(), html_url: z12.string(), state: z12.string() }).parse(parsed);
2308
2486
  return {
2309
2487
  target: "submitPullRequestReview",
2310
2488
  repo: repo.ref,
@@ -2335,7 +2513,11 @@ function createGitHubTools(ctx, botEmail, pullRequestSubscription) {
2335
2513
  ctx.egress
2336
2514
  ),
2337
2515
  updateIssue: createGitHubUpdateIssueTool(ctx),
2338
- updatePullRequest: createGitHubUpdatePullRequestTool(ctx)
2516
+ updatePullRequest: createGitHubUpdatePullRequestTool(ctx),
2517
+ updatePullRequestFeedback: createGitHubUpdatePullRequestFeedbackTool(
2518
+ ctx,
2519
+ botEmail
2520
+ )
2339
2521
  };
2340
2522
  }
2341
2523
 
@@ -2344,7 +2526,7 @@ import { createHmac, timingSafeEqual } from "crypto";
2344
2526
 
2345
2527
  // src/issue-outcomes/store.ts
2346
2528
  import { and, eq, lte, sql as sql2 } from "drizzle-orm";
2347
- import { z as z13 } from "zod";
2529
+ import { z as z14 } from "zod";
2348
2530
 
2349
2531
  // src/db/schema.ts
2350
2532
  import { sql } from "drizzle-orm";
@@ -2356,18 +2538,18 @@ import {
2356
2538
  text,
2357
2539
  timestamp
2358
2540
  } from "drizzle-orm/pg-core";
2359
- import { z as z12 } from "zod";
2360
- var githubPullRequestStateSchema = z12.enum([
2541
+ import { z as z13 } from "zod";
2542
+ var githubPullRequestStateSchema = z13.enum([
2361
2543
  "closed_unmerged",
2362
2544
  "merged",
2363
2545
  "open"
2364
2546
  ]);
2365
- var githubPullRequestCommitCompositionSchema = z12.enum([
2547
+ var githubPullRequestCommitCompositionSchema = z13.enum([
2366
2548
  "junior_only",
2367
2549
  "mixed"
2368
2550
  ]);
2369
- var githubIssueStateSchema = z12.enum(["closed", "open"]);
2370
- var githubIssueStateReasonSchema = z12.enum([
2551
+ var githubIssueStateSchema = z13.enum(["closed", "open"]);
2552
+ var githubIssueStateReasonSchema = z13.enum([
2371
2553
  "completed",
2372
2554
  "duplicate",
2373
2555
  "not_planned",
@@ -2437,21 +2619,21 @@ var juniorGitHubPullRequestIssues = pgTable(
2437
2619
  );
2438
2620
 
2439
2621
  // src/issue-outcomes/store.ts
2440
- var githubIssueOutcomeInputSchema = z13.object({
2441
- candidateOwned: z13.boolean(),
2442
- closedAt: z13.date().optional(),
2443
- issueId: z13.string().min(1),
2444
- number: z13.number().int().positive(),
2445
- openedAt: z13.date(),
2446
- repositoryFullName: z13.string().min(1),
2447
- repositoryId: z13.string().min(1),
2622
+ var githubIssueOutcomeInputSchema = z14.object({
2623
+ candidateOwned: z14.boolean(),
2624
+ closedAt: z14.date().optional(),
2625
+ issueId: z14.string().min(1),
2626
+ number: z14.number().int().positive(),
2627
+ openedAt: z14.date(),
2628
+ repositoryFullName: z14.string().min(1),
2629
+ repositoryId: z14.string().min(1),
2448
2630
  state: githubIssueStateSchema,
2449
2631
  stateReason: githubIssueStateReasonSchema.optional(),
2450
- updatedAt: z13.date()
2632
+ updatedAt: z14.date()
2451
2633
  }).strict();
2452
- var githubIssueConversationsInputSchema = z13.object({
2453
- conversationIds: z13.array(z13.string().min(1)).min(1),
2454
- issueId: z13.string().min(1)
2634
+ var githubIssueConversationsInputSchema = z14.object({
2635
+ conversationIds: z14.array(z14.string().min(1)).min(1),
2636
+ issueId: z14.string().min(1)
2455
2637
  }).strict();
2456
2638
  function projectionValues(input) {
2457
2639
  return {
@@ -2515,33 +2697,33 @@ async function recordGitHubIssueConversations(db, input) {
2515
2697
 
2516
2698
  // src/pull-request-outcomes/store.ts
2517
2699
  import { and as and2, eq as eq2, lte as lte2, ne, sql as sql3 } from "drizzle-orm";
2518
- import { z as z14 } from "zod";
2519
- var githubPullRequestOutcomeInputSchema = z14.object({
2520
- candidateOwned: z14.boolean(),
2521
- closedAt: z14.date().optional(),
2700
+ import { z as z15 } from "zod";
2701
+ var githubPullRequestOutcomeInputSchema = z15.object({
2702
+ candidateOwned: z15.boolean(),
2703
+ closedAt: z15.date().optional(),
2522
2704
  commitComposition: githubPullRequestCommitCompositionSchema.optional(),
2523
- mergedAt: z14.date().optional(),
2524
- number: z14.number().int().positive(),
2525
- openedAt: z14.date(),
2526
- pullRequestId: z14.string().min(1),
2527
- repositoryFullName: z14.string().min(1),
2528
- repositoryId: z14.string().min(1),
2705
+ mergedAt: z15.date().optional(),
2706
+ number: z15.number().int().positive(),
2707
+ openedAt: z15.date(),
2708
+ pullRequestId: z15.string().min(1),
2709
+ repositoryFullName: z15.string().min(1),
2710
+ repositoryId: z15.string().min(1),
2529
2711
  state: githubPullRequestStateSchema,
2530
- title: z14.string().min(1).optional(),
2531
- updatedAt: z14.date()
2712
+ title: z15.string().min(1).optional(),
2713
+ updatedAt: z15.date()
2532
2714
  }).strict();
2533
- var githubPullRequestConversationsInputSchema = z14.object({
2534
- conversationIds: z14.array(z14.string().min(1)).min(1),
2535
- pullRequestId: z14.string().min(1)
2715
+ var githubPullRequestConversationsInputSchema = z15.object({
2716
+ conversationIds: z15.array(z15.string().min(1)).min(1),
2717
+ pullRequestId: z15.string().min(1)
2536
2718
  }).strict();
2537
- var githubPullRequestLinkedIssuesInputSchema = z14.object({
2538
- linkedIssues: z14.array(
2539
- z14.object({
2540
- number: z14.number().int().positive(),
2541
- repositoryFullName: z14.string().min(1)
2719
+ var githubPullRequestLinkedIssuesInputSchema = z15.object({
2720
+ linkedIssues: z15.array(
2721
+ z15.object({
2722
+ number: z15.number().int().positive(),
2723
+ repositoryFullName: z15.string().min(1)
2542
2724
  }).strict()
2543
2725
  ).min(1),
2544
- pullRequestId: z14.string().min(1)
2726
+ pullRequestId: z15.string().min(1)
2545
2727
  }).strict();
2546
2728
  function projectionValues2(input) {
2547
2729
  return {
@@ -2706,39 +2888,39 @@ async function recordGitHubPullRequestLinkedIssues(db, input) {
2706
2888
  }
2707
2889
 
2708
2890
  // src/webhooks/issue-outcome.ts
2709
- import { z as z15 } from "zod";
2710
- var canonicalIssueOutcomeSchema = z15.object({
2711
- action: z15.enum(["opened", "closed", "reopened"]),
2712
- issue: z15.object({
2713
- body: z15.string().nullable().optional(),
2714
- closed_at: z15.string().nullable().optional(),
2715
- created_at: z15.string(),
2716
- id: z15.number().int().positive(),
2717
- number: z15.number().int().positive(),
2891
+ import { z as z16 } from "zod";
2892
+ var canonicalIssueOutcomeSchema = z16.object({
2893
+ action: z16.enum(["opened", "closed", "reopened"]),
2894
+ issue: z16.object({
2895
+ body: z16.string().nullable().optional(),
2896
+ closed_at: z16.string().nullable().optional(),
2897
+ created_at: z16.string(),
2898
+ id: z16.number().int().positive(),
2899
+ number: z16.number().int().positive(),
2718
2900
  state_reason: githubIssueStateReasonSchema.nullable().optional(),
2719
- updated_at: z15.string(),
2720
- user: z15.object({ login: z15.string().min(1) }).strict()
2901
+ updated_at: z16.string(),
2902
+ user: z16.object({ login: z16.string().min(1) }).strict()
2721
2903
  }).strict(),
2722
- repository: z15.object({
2723
- full_name: z15.string().min(1),
2724
- id: z15.number().int().positive()
2904
+ repository: z16.object({
2905
+ full_name: z16.string().min(1),
2906
+ id: z16.number().int().positive()
2725
2907
  }).strict()
2726
2908
  }).strict();
2727
- var issueOutcomeSchema = z15.object({
2728
- action: z15.enum(["opened", "closed", "reopened"]),
2729
- issue: z15.object({
2730
- body: z15.string().nullable().optional(),
2731
- closed_at: z15.string().nullable().optional(),
2732
- created_at: z15.string(),
2733
- id: z15.number().int().positive(),
2734
- number: z15.number().int().positive(),
2909
+ var issueOutcomeSchema = z16.object({
2910
+ action: z16.enum(["opened", "closed", "reopened"]),
2911
+ issue: z16.object({
2912
+ body: z16.string().nullable().optional(),
2913
+ closed_at: z16.string().nullable().optional(),
2914
+ created_at: z16.string(),
2915
+ id: z16.number().int().positive(),
2916
+ number: z16.number().int().positive(),
2735
2917
  state_reason: githubIssueStateReasonSchema.nullable().optional(),
2736
- updated_at: z15.string(),
2737
- user: z15.object({ login: z15.string().min(1) }).passthrough()
2918
+ updated_at: z16.string(),
2919
+ user: z16.object({ login: z16.string().min(1) }).passthrough()
2738
2920
  }).passthrough(),
2739
- repository: z15.object({
2740
- full_name: z15.string().min(1),
2741
- id: z15.number().int().positive()
2921
+ repository: z16.object({
2922
+ full_name: z16.string().min(1),
2923
+ id: z16.number().int().positive()
2742
2924
  }).passthrough()
2743
2925
  }).passthrough().transform(
2744
2926
  (provider) => canonicalIssueOutcomeSchema.parse({
@@ -2759,7 +2941,7 @@ var issueOutcomeSchema = z15.object({
2759
2941
  }
2760
2942
  })
2761
2943
  );
2762
- var issueLifecycleActionSchema = z15.object({ action: z15.string() }).passthrough();
2944
+ var issueLifecycleActionSchema = z16.object({ action: z16.string() }).passthrough();
2763
2945
  function timestamp2(value) {
2764
2946
  if (!value) return void 0;
2765
2947
  const parsed = new Date(value);
@@ -2805,21 +2987,21 @@ function normalizeGitHubIssueOutcome(args) {
2805
2987
  updatedAt
2806
2988
  };
2807
2989
  }
2808
- var canonicalIssueConversationSchema = z15.object({
2809
- issue: z15.object({
2810
- body: z15.string().nullable().optional(),
2811
- id: z15.number().int().positive(),
2812
- user: z15.object({ login: z15.string().min(1) }).strict()
2990
+ var canonicalIssueConversationSchema = z16.object({
2991
+ issue: z16.object({
2992
+ body: z16.string().nullable().optional(),
2993
+ id: z16.number().int().positive(),
2994
+ user: z16.object({ login: z16.string().min(1) }).strict()
2813
2995
  }).strict(),
2814
- sender: z15.object({ login: z15.string().min(1) }).strict().optional()
2996
+ sender: z16.object({ login: z16.string().min(1) }).strict().optional()
2815
2997
  }).strict();
2816
- var issueConversationSchema = z15.object({
2817
- issue: z15.object({
2818
- body: z15.string().nullable().optional(),
2819
- id: z15.number().int().positive(),
2820
- user: z15.object({ login: z15.string().min(1) }).passthrough()
2998
+ var issueConversationSchema = z16.object({
2999
+ issue: z16.object({
3000
+ body: z16.string().nullable().optional(),
3001
+ id: z16.number().int().positive(),
3002
+ user: z16.object({ login: z16.string().min(1) }).passthrough()
2821
3003
  }).passthrough(),
2822
- sender: z15.object({ login: z15.string().min(1) }).passthrough().optional()
3004
+ sender: z16.object({ login: z16.string().min(1) }).passthrough().optional()
2823
3005
  }).passthrough().transform(
2824
3006
  (provider) => canonicalIssueConversationSchema.parse({
2825
3007
  issue: {
@@ -2846,43 +3028,43 @@ function normalizeGitHubIssueConversations(args) {
2846
3028
  }
2847
3029
 
2848
3030
  // src/webhooks/pull-request-outcome.ts
2849
- import { z as z16 } from "zod";
2850
- var canonicalPullRequestOutcomeSchema = z16.object({
2851
- action: z16.enum(["opened", "closed", "reopened"]),
2852
- pull_request: z16.object({
2853
- body: z16.string().nullable().optional(),
2854
- closed_at: z16.string().nullable().optional(),
2855
- created_at: z16.string(),
2856
- id: z16.number().int().positive(),
2857
- merged: z16.boolean(),
2858
- merged_at: z16.string().nullable().optional(),
2859
- number: z16.number().int().positive(),
2860
- title: z16.string().min(1).optional(),
2861
- updated_at: z16.string(),
2862
- user: z16.object({ login: z16.string().min(1) }).strict()
3031
+ import { z as z17 } from "zod";
3032
+ var canonicalPullRequestOutcomeSchema = z17.object({
3033
+ action: z17.enum(["opened", "closed", "reopened"]),
3034
+ pull_request: z17.object({
3035
+ body: z17.string().nullable().optional(),
3036
+ closed_at: z17.string().nullable().optional(),
3037
+ created_at: z17.string(),
3038
+ id: z17.number().int().positive(),
3039
+ merged: z17.boolean(),
3040
+ merged_at: z17.string().nullable().optional(),
3041
+ number: z17.number().int().positive(),
3042
+ title: z17.string().min(1).optional(),
3043
+ updated_at: z17.string(),
3044
+ user: z17.object({ login: z17.string().min(1) }).strict()
2863
3045
  }).strict(),
2864
- repository: z16.object({
2865
- full_name: z16.string().min(1),
2866
- id: z16.number().int().positive()
3046
+ repository: z17.object({
3047
+ full_name: z17.string().min(1),
3048
+ id: z17.number().int().positive()
2867
3049
  }).strict()
2868
3050
  }).strict();
2869
- var pullRequestOutcomeSchema = z16.object({
2870
- action: z16.enum(["opened", "closed", "reopened"]),
2871
- pull_request: z16.object({
2872
- body: z16.string().nullable().optional(),
2873
- closed_at: z16.string().nullable().optional(),
2874
- created_at: z16.string(),
2875
- id: z16.number().int().positive(),
2876
- merged: z16.boolean(),
2877
- merged_at: z16.string().nullable().optional(),
2878
- number: z16.number().int().positive(),
2879
- title: z16.string().min(1).optional(),
2880
- updated_at: z16.string(),
2881
- user: z16.object({ login: z16.string().min(1) }).passthrough()
3051
+ var pullRequestOutcomeSchema = z17.object({
3052
+ action: z17.enum(["opened", "closed", "reopened"]),
3053
+ pull_request: z17.object({
3054
+ body: z17.string().nullable().optional(),
3055
+ closed_at: z17.string().nullable().optional(),
3056
+ created_at: z17.string(),
3057
+ id: z17.number().int().positive(),
3058
+ merged: z17.boolean(),
3059
+ merged_at: z17.string().nullable().optional(),
3060
+ number: z17.number().int().positive(),
3061
+ title: z17.string().min(1).optional(),
3062
+ updated_at: z17.string(),
3063
+ user: z17.object({ login: z17.string().min(1) }).passthrough()
2882
3064
  }).passthrough(),
2883
- repository: z16.object({
2884
- full_name: z16.string().min(1),
2885
- id: z16.number().int().positive()
3065
+ repository: z17.object({
3066
+ full_name: z17.string().min(1),
3067
+ id: z17.number().int().positive()
2886
3068
  }).passthrough()
2887
3069
  }).passthrough().transform(
2888
3070
  (provider) => canonicalPullRequestOutcomeSchema.parse({
@@ -2905,24 +3087,24 @@ var pullRequestOutcomeSchema = z16.object({
2905
3087
  }
2906
3088
  })
2907
3089
  );
2908
- var pullRequestLifecycleActionSchema = z16.object({ action: z16.string() }).passthrough();
2909
- var canonicalPullRequestConversationSchema = z16.object({
2910
- pull_request: z16.object({
2911
- body: z16.string().nullable().optional(),
2912
- id: z16.number().int().positive(),
2913
- user: z16.object({ login: z16.string().min(1) }).strict()
3090
+ var pullRequestLifecycleActionSchema = z17.object({ action: z17.string() }).passthrough();
3091
+ var canonicalPullRequestConversationSchema = z17.object({
3092
+ pull_request: z17.object({
3093
+ body: z17.string().nullable().optional(),
3094
+ id: z17.number().int().positive(),
3095
+ user: z17.object({ login: z17.string().min(1) }).strict()
2914
3096
  }).strict(),
2915
- repository: z16.object({ full_name: z16.string().min(1) }).strict(),
2916
- sender: z16.object({ login: z16.string().min(1) }).strict()
3097
+ repository: z17.object({ full_name: z17.string().min(1) }).strict(),
3098
+ sender: z17.object({ login: z17.string().min(1) }).strict()
2917
3099
  }).strict();
2918
- var pullRequestConversationSchema = z16.object({
2919
- pull_request: z16.object({
2920
- body: z16.string().nullable().optional(),
2921
- id: z16.number().int().positive(),
2922
- user: z16.object({ login: z16.string().min(1) }).passthrough()
3100
+ var pullRequestConversationSchema = z17.object({
3101
+ pull_request: z17.object({
3102
+ body: z17.string().nullable().optional(),
3103
+ id: z17.number().int().positive(),
3104
+ user: z17.object({ login: z17.string().min(1) }).passthrough()
2923
3105
  }).passthrough(),
2924
- repository: z16.object({ full_name: z16.string().min(1) }).passthrough(),
2925
- sender: z16.object({ login: z16.string().min(1) }).passthrough()
3106
+ repository: z17.object({ full_name: z17.string().min(1) }).passthrough(),
3107
+ sender: z17.object({ login: z17.string().min(1) }).passthrough()
2926
3108
  }).passthrough().transform(
2927
3109
  (provider) => canonicalPullRequestConversationSchema.parse({
2928
3110
  pull_request: {
@@ -3042,6 +3224,20 @@ function webhookInstallationId(body) {
3042
3224
  }
3043
3225
  return parseInstallationId(installation.id);
3044
3226
  }
3227
+ function pullRequestFeedbackTarget(events) {
3228
+ const event = events.find(
3229
+ (candidate) => candidate.eventType === "pull_request.comment.created" || candidate.eventType === "pull_request.review_comment.created"
3230
+ );
3231
+ const data = event?.data;
3232
+ if (!data || typeof data.repo !== "string" || typeof data.commentId !== "number" || data.commentKind !== "conversation" && data.commentKind !== "review") {
3233
+ return void 0;
3234
+ }
3235
+ return {
3236
+ commentId: data.commentId,
3237
+ commentKind: data.commentKind,
3238
+ repo: data.repo
3239
+ };
3240
+ }
3045
3241
  function githubCodeChange(outcome, conversationIds) {
3046
3242
  return {
3047
3243
  closedAt: outcome.closedAt,
@@ -3192,6 +3388,21 @@ function createGitHubWebhookRoute(args) {
3192
3388
  deliveryId,
3193
3389
  eventName
3194
3390
  });
3391
+ const feedbackTarget = pullRequestFeedbackTarget(resourceEvents);
3392
+ const hasMatch = args.resourceEvents.hasMatch;
3393
+ const feedbackHasMatch = feedbackTarget && hasMatch ? (await Promise.all(resourceEvents.map((event) => hasMatch(event)))).some(Boolean) : false;
3394
+ if (feedbackTarget && feedbackHasMatch) {
3395
+ try {
3396
+ await args.markFeedbackReviewing(feedbackTarget);
3397
+ } catch (error) {
3398
+ args.log?.error("GitHub pull request feedback reaction failed", {
3399
+ commentId: feedbackTarget.commentId,
3400
+ deliveryId,
3401
+ errorType: error instanceof Error ? error.name : "UnknownError",
3402
+ repository: feedbackTarget.repo
3403
+ });
3404
+ }
3405
+ }
3195
3406
  for (const event of resourceEvents) {
3196
3407
  await args.resourceEvents.publish(event);
3197
3408
  }
@@ -3204,18 +3415,18 @@ function createGitHubWebhookRoute(args) {
3204
3415
  }
3205
3416
 
3206
3417
  // src/pull-request-outcomes/commit-composition.ts
3207
- import { z as z17 } from "zod";
3208
- var canonicalCommitSchema = z17.object({
3209
- authorEmail: z17.string().nullable(),
3210
- authorLogin: z17.string().nullable()
3418
+ import { z as z18 } from "zod";
3419
+ var canonicalCommitSchema = z18.object({
3420
+ authorEmail: z18.string().nullable(),
3421
+ authorLogin: z18.string().nullable()
3211
3422
  }).strict();
3212
- var providerCommitSchema = z17.object({
3213
- author: z17.object({ login: z17.string() }).passthrough().nullable(),
3214
- commit: z17.object({
3215
- author: z17.object({ email: z17.string() }).passthrough().nullable()
3423
+ var providerCommitSchema = z18.object({
3424
+ author: z18.object({ login: z18.string() }).passthrough().nullable(),
3425
+ commit: z18.object({
3426
+ author: z18.object({ email: z18.string() }).passthrough().nullable()
3216
3427
  }).passthrough()
3217
3428
  }).passthrough();
3218
- var commitPageSchema = z17.array(providerCommitSchema).transform(
3429
+ var commitPageSchema = z18.array(providerCommitSchema).transform(
3219
3430
  (commits) => commits.map(
3220
3431
  (commit) => canonicalCommitSchema.parse({
3221
3432
  authorEmail: commit.commit.author?.email ?? null,
@@ -4079,6 +4290,7 @@ function shouldInspectGitHubGraphqlResponse(ctx) {
4079
4290
  const contentType = ctx.response.headers.get("content-type");
4080
4291
  return contentType ? /\bjson\b/i.test(contentType) : false;
4081
4292
  }
4293
+ var PULL_REQUEST_COMMENT_REACTIONS_PATH = /^\/repos\/[^/]+\/[^/]+\/(issues|pulls)\/comments\/[^/]+\/reactions(?:\/[^/]+)?$/;
4082
4294
  var GITHUB_BODY_WRITES = [
4083
4295
  {
4084
4296
  denialMessage: `GitHub issue creation must use the github_createIssue tool so Junior can own idempotency and the conversation footer. ${CREATE_TOOL_ROUTING_GUIDANCE}`,
@@ -4163,6 +4375,9 @@ function githubApiWriteGrantName(method, upstreamUrl) {
4163
4375
  ) && !HTTP_READ_METHODS.has(method)) {
4164
4376
  return "installation-write";
4165
4377
  }
4378
+ if ((method === "POST" || method === "DELETE") && PULL_REQUEST_COMMENT_REACTIONS_PATH.test(pathname)) {
4379
+ return "installation-write";
4380
+ }
4166
4381
  return void 0;
4167
4382
  }
4168
4383
  function reviewThreadResolveRepository(operation, method, upstreamUrl, bodyText) {
@@ -4190,6 +4405,16 @@ function isGitHubGraphqlMutation(method, upstreamUrl, bodyText, field) {
4190
4405
  ).test(parsed.normalized);
4191
4406
  }
4192
4407
  function applyGitHubEgressPolicy(input) {
4408
+ const reactionWrite = isGitHubApiUrl(input.upstreamUrl) && (input.method === "POST" || input.method === "DELETE") && PULL_REQUEST_COMMENT_REACTIONS_PATH.test(
4409
+ input.upstreamUrl.pathname.toLowerCase()
4410
+ );
4411
+ if (reactionWrite) {
4412
+ const expectedOperation = input.method === "POST" ? "github.pull.comment-reaction.create" : "github.pull.comment-reaction.delete";
4413
+ enforceEgressPolicy({
4414
+ allowed: input.operation === expectedOperation,
4415
+ denialMessage: "GitHub pull request feedback reactions must use the github_updatePullRequestFeedback tool."
4416
+ });
4417
+ }
4193
4418
  assertGitHubPullRequestApprovalDenied({
4194
4419
  ...input.bodyText !== void 0 ? { bodyText: input.bodyText } : void 0,
4195
4420
  method: input.method,
@@ -4341,7 +4566,9 @@ function githubPlugin(options = {}) {
4341
4566
  supportedEvents: [...GITHUB_PULL_REQUEST_EVENTS],
4342
4567
  suggestedEvents: [...GITHUB_PULL_REQUEST_SUGGESTED_EVENTS],
4343
4568
  matchFields: GITHUB_PULL_REQUEST_MATCH_FIELDS,
4344
- ...options.pullRequestEvents?.guidance ? { guidance: options.pullRequestEvents.guidance } : void 0
4569
+ guidance: gitHubPullRequestEventGuidance(
4570
+ options.pullRequestEvents?.guidance
4571
+ )
4345
4572
  },
4346
4573
  {
4347
4574
  type: "release_source",
@@ -4350,14 +4577,20 @@ function githubPlugin(options = {}) {
4350
4577
  },
4351
4578
  {
4352
4579
  type: "repository",
4353
- supportedEvents: [...GITHUB_ISSUE_EVENTS, ...GITHUB_PULL_REQUEST_EVENTS],
4580
+ supportedEvents: [
4581
+ ...GITHUB_ISSUE_EVENTS,
4582
+ ...GITHUB_PULL_REQUEST_EVENTS
4583
+ ],
4354
4584
  suggestedEvents: [
4355
4585
  "issue.opened",
4356
4586
  "pull_request.opened",
4357
4587
  ...GITHUB_ISSUE_SUGGESTED_EVENTS,
4358
4588
  ...GITHUB_PULL_REQUEST_SUGGESTED_EVENTS
4359
4589
  ],
4360
- matchFields: GITHUB_PULL_REQUEST_MATCH_FIELDS
4590
+ matchFields: GITHUB_PULL_REQUEST_MATCH_FIELDS,
4591
+ guidance: gitHubPullRequestEventGuidance(
4592
+ options.pullRequestEvents?.guidance
4593
+ )
4361
4594
  }
4362
4595
  ],
4363
4596
  isEnabled: () => Boolean(readEnv("GITHUB_WEBHOOK_SECRET")),
@@ -4479,6 +4712,36 @@ function githubPlugin(options = {}) {
4479
4712
  installationId: () => readEnv(installationIdEnv),
4480
4713
  installationIdEnv,
4481
4714
  log: ctx.log,
4715
+ markFeedbackReviewing: async ({ commentId, commentKind, repo }) => {
4716
+ const [owner, name, ...extra] = repo.split("/");
4717
+ if (!owner || !name || extra.length > 0) {
4718
+ throw new Error(
4719
+ "GitHub pull request feedback received an invalid repository name"
4720
+ );
4721
+ }
4722
+ const token = await issueInstallationToken({
4723
+ appIdEnv,
4724
+ privateKeyEnv,
4725
+ installationIdEnv,
4726
+ repositories: [name]
4727
+ });
4728
+ await githubRequest(
4729
+ "https://api.github.com",
4730
+ pullRequestCommentReactionsPath({
4731
+ owner,
4732
+ name,
4733
+ commentKind,
4734
+ commentId
4735
+ }),
4736
+ {
4737
+ body: {
4738
+ content: pullRequestFeedbackReactionContent("reviewing")
4739
+ },
4740
+ method: "POST",
4741
+ token: token.token
4742
+ }
4743
+ );
4744
+ },
4482
4745
  privateKeyEnv,
4483
4746
  resourceEvents: ctx.resourceEvents,
4484
4747
  webhookSecret: () => readEnv("GITHUB_WEBHOOK_SECRET")
@@ -1,6 +1,8 @@
1
1
  import { type SubscribableResource } from "@sentry/junior-plugin-api";
2
2
  export declare const GITHUB_PULL_REQUEST_EVENTS: readonly ["pull_request.checks.failed", "pull_request.checks.recovered", "pull_request.comment.created", "pull_request.opened", "pull_request.ready_for_review", "pull_request.review.approved", "pull_request.review.changes_requested", "pull_request.review.commented", "pull_request.review_comment.created", "pull_request.merged", "pull_request.closed_unmerged"];
3
3
  export type GitHubPullRequestEvent = (typeof GITHUB_PULL_REQUEST_EVENTS)[number];
4
+ /** Combine provider defaults with app guidance for pull request events. */
5
+ export declare function gitHubPullRequestEventGuidance(guidance?: Partial<Record<GitHubPullRequestEvent, string>>): Partial<Record<GitHubPullRequestEvent, string>>;
4
6
  /** App-configured pull request resource event behavior. */
5
7
  export interface GitHubPullRequestEventOptions {
6
8
  /** App guidance applied within the matching subscription or event task instruction. */
package/dist/testing.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  normalizeGitHubResourceEvents
3
- } from "./chunk-6OPIFILN.js";
3
+ } from "./chunk-F2PJG6AF.js";
4
4
  export {
5
5
  normalizeGitHubResourceEvents
6
6
  };
@@ -0,0 +1,59 @@
1
+ import { type PluginEgress } from "@sentry/junior-plugin-api";
2
+ /**
3
+ * GitHub has reactions but no feedback state. This tool maps each state to a
4
+ * reaction and keeps at most one state reaction that Junior owns. It does not
5
+ * change reactions from other users.
6
+ */
7
+ declare const STATUS_TO_REACTION_CONTENT: {
8
+ readonly reviewing: "eyes";
9
+ readonly addressed: "+1";
10
+ readonly declined: "-1";
11
+ };
12
+ type FeedbackStatus = keyof typeof STATUS_TO_REACTION_CONTENT;
13
+ type PullRequestCommentKind = "conversation" | "review";
14
+ /** Build the GitHub API path for one pull request comment's reactions. */
15
+ export declare function pullRequestCommentReactionsPath(input: {
16
+ owner: string;
17
+ name: string;
18
+ commentKind: PullRequestCommentKind;
19
+ commentId: number;
20
+ }): string;
21
+ /** Return the GitHub reaction for one feedback state. */
22
+ export declare function pullRequestFeedbackReactionContent(status: FeedbackStatus): (typeof STATUS_TO_REACTION_CONTENT)[FeedbackStatus];
23
+ /**
24
+ * Set Junior's feedback reaction on a pull request comment. Replace only
25
+ * state reactions Junior previously added to the same comment.
26
+ */
27
+ export declare function createGitHubUpdatePullRequestFeedbackTool(ctx: {
28
+ egress: PluginEgress;
29
+ }, botEmail: string | undefined): import("@sentry/junior-plugin-api").PluginToolDefinition<{
30
+ repo: string;
31
+ commentKind: "conversation" | "review";
32
+ commentId: number;
33
+ status: "reviewing" | "addressed" | "declined";
34
+ }, {
35
+ [x: string]: unknown;
36
+ target: "updatePullRequestFeedback";
37
+ repo: string;
38
+ commentId: number;
39
+ status: "reviewing" | "addressed" | "declined";
40
+ reactionId: number;
41
+ truncated?: boolean | undefined;
42
+ continuation?: {
43
+ arguments: Record<string, unknown>;
44
+ reason?: string | undefined;
45
+ } | undefined;
46
+ }, {
47
+ [x: string]: unknown;
48
+ target: "updatePullRequestFeedback";
49
+ repo: string;
50
+ commentId: number;
51
+ status: "reviewing" | "addressed" | "declined";
52
+ reactionId: number;
53
+ truncated?: boolean | undefined;
54
+ continuation?: {
55
+ arguments: Record<string, unknown>;
56
+ reason?: string | undefined;
57
+ } | undefined;
58
+ }>;
59
+ export {};
@@ -1,6 +1,11 @@
1
1
  import type { CodeChangePublisher, PluginConversationAnnotations, PluginLogger, PluginRoute, ResourceEventPublisher } from "@sentry/junior-plugin-api";
2
2
  import type { GitHubDb } from "../db/database.js";
3
3
  import type { GitHubPullRequestCommitComposition } from "../db/schema.js";
4
+ interface FeedbackTarget {
5
+ commentId: number;
6
+ commentKind: "conversation" | "review";
7
+ repo: string;
8
+ }
4
9
  /** Create the public, signed GitHub webhook route owned by the plugin. */
5
10
  export declare function createGitHubWebhookRoute(args: {
6
11
  annotations: PluginConversationAnnotations;
@@ -15,7 +20,9 @@ export declare function createGitHubWebhookRoute(args: {
15
20
  installationId(): string | undefined;
16
21
  installationIdEnv: string;
17
22
  log?: Pick<PluginLogger, "error">;
23
+ markFeedbackReviewing(input: FeedbackTarget): Promise<void>;
18
24
  privateKeyEnv: string;
19
25
  resourceEvents: ResourceEventPublisher;
20
26
  webhookSecret(): string | undefined;
21
27
  }): PluginRoute;
28
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sentry/junior-github",
3
- "version": "0.200.0",
3
+ "version": "0.202.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.5.4",
34
- "@sentry/junior-plugin-api": "0.200.0"
34
+ "@sentry/junior-plugin-api": "0.202.0"
35
35
  },
36
36
  "devDependencies": {
37
37
  "@oxlint/plugins": "1.79.0",
@@ -38,6 +38,8 @@ If the user omits `owner/repo`, run `jr-rpc config get github.repo`. Then pass `
38
38
  | Ready for review | `gh api repos/owner/repo/pulls/NUMBER/ready_for_review --method POST` |
39
39
  | Request reviewers | `gh api repos/owner/repo/pulls/NUMBER/requested_reviewers --method POST --input reviewers.json` |
40
40
  | Submit review | `github_submitPullRequestReview` |
41
+ | Set feedback status | `github_updatePullRequestFeedback` |
42
+ | Resolve review thread | `github_resolvePullRequestReviewThread` |
41
43
  | Inline review comment | `gh api repos/owner/repo/pulls/NUMBER/comments --method POST --input comment.json` |
42
44
  | View PR / checks | `gh pr view NUMBER --repo owner/repo` / `gh pr checks NUMBER --repo owner/repo` |
43
45
  | Diff PR | `gh pr diff NUMBER --repo owner/repo` |