@sentry/junior-github 0.200.0 → 0.201.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/{chunk-6OPIFILN.js → chunk-F2PJG6AF.js} +31 -2
- package/dist/index.js +472 -209
- package/dist/resource-events/pull-request.d.ts +2 -0
- package/dist/testing.js +1 -1
- package/dist/tools/update-pull-request-feedback.d.ts +59 -0
- package/dist/webhooks/handler.d.ts +7 -0
- package/package.json +2 -2
- package/skills/github-code/references/api-surface.md +2 -0
|
@@ -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-
|
|
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/
|
|
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/
|
|
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
|
-
|
|
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
|
-
|
|
2031
|
-
"GitHub
|
|
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("
|
|
2048
|
+
target: z10.literal("updatePullRequestFeedback"),
|
|
2036
2049
|
repo: z10.string(),
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
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
|
-
|
|
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: "
|
|
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
|
|
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
|
|
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}: ${
|
|
2295
|
+
`GitHub review thread lookup failed with HTTP ${lookupResponse.status}: ${githubError2(lookupPayload)}`
|
|
2118
2296
|
);
|
|
2119
2297
|
}
|
|
2120
|
-
const thread =
|
|
2121
|
-
data:
|
|
2122
|
-
node:
|
|
2123
|
-
id:
|
|
2124
|
-
isResolved:
|
|
2125
|
-
pullRequest:
|
|
2126
|
-
author:
|
|
2127
|
-
number:
|
|
2128
|
-
repository:
|
|
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
|
|
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
|
|
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
|
|
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}: ${
|
|
2351
|
+
`GitHub review thread resolution failed with HTTP ${resolveResponse.status}: ${githubError2(resolvePayload)}`
|
|
2174
2352
|
);
|
|
2175
2353
|
}
|
|
2176
|
-
const resolved =
|
|
2177
|
-
data:
|
|
2178
|
-
resolveReviewThread:
|
|
2179
|
-
thread:
|
|
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
|
|
2200
|
-
PluginToolInputError as
|
|
2201
|
-
pluginToolOutputSchema as
|
|
2377
|
+
definePluginTool as definePluginTool12,
|
|
2378
|
+
PluginToolInputError as PluginToolInputError13,
|
|
2379
|
+
pluginToolOutputSchema as pluginToolOutputSchema12
|
|
2202
2380
|
} from "@sentry/junior-plugin-api";
|
|
2203
|
-
import { z as
|
|
2204
|
-
var reviewCommentSchema =
|
|
2205
|
-
path:
|
|
2206
|
-
body:
|
|
2207
|
-
line:
|
|
2208
|
-
side:
|
|
2209
|
-
startLine:
|
|
2210
|
-
startSide:
|
|
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
|
|
2216
|
-
repo:
|
|
2217
|
-
number:
|
|
2218
|
-
event:
|
|
2219
|
-
body:
|
|
2220
|
-
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
|
|
2223
|
-
target:
|
|
2224
|
-
repo:
|
|
2225
|
-
number:
|
|
2226
|
-
reviewId:
|
|
2227
|
-
state:
|
|
2228
|
-
url:
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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}: ${
|
|
2433
|
+
const message = `GitHub pull request review failed with HTTP ${status}: ${githubError3(body)}`;
|
|
2256
2434
|
if (status === 404 || status === 422) {
|
|
2257
|
-
throw new
|
|
2435
|
+
throw new PluginToolInputError13(message);
|
|
2258
2436
|
}
|
|
2259
2437
|
throw new Error(message);
|
|
2260
2438
|
}
|
|
2261
2439
|
function createGitHubSubmitPullRequestReviewTool(egress) {
|
|
2262
|
-
return
|
|
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:
|
|
2272
|
-
outputSchema:
|
|
2449
|
+
inputSchema: inputSchema10,
|
|
2450
|
+
outputSchema: outputSchema10,
|
|
2273
2451
|
async execute(review) {
|
|
2274
|
-
const 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
|
|
2481
|
+
const parsed = await readJson9(response);
|
|
2304
2482
|
if (!response.ok) {
|
|
2305
2483
|
throwReviewError(response.status, parsed);
|
|
2306
2484
|
}
|
|
2307
|
-
const providerResult =
|
|
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
|
|
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
|
|
2360
|
-
var githubPullRequestStateSchema =
|
|
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 =
|
|
2547
|
+
var githubPullRequestCommitCompositionSchema = z13.enum([
|
|
2366
2548
|
"junior_only",
|
|
2367
2549
|
"mixed"
|
|
2368
2550
|
]);
|
|
2369
|
-
var githubIssueStateSchema =
|
|
2370
|
-
var githubIssueStateReasonSchema =
|
|
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 =
|
|
2441
|
-
candidateOwned:
|
|
2442
|
-
closedAt:
|
|
2443
|
-
issueId:
|
|
2444
|
-
number:
|
|
2445
|
-
openedAt:
|
|
2446
|
-
repositoryFullName:
|
|
2447
|
-
repositoryId:
|
|
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:
|
|
2632
|
+
updatedAt: z14.date()
|
|
2451
2633
|
}).strict();
|
|
2452
|
-
var githubIssueConversationsInputSchema =
|
|
2453
|
-
conversationIds:
|
|
2454
|
-
issueId:
|
|
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
|
|
2519
|
-
var githubPullRequestOutcomeInputSchema =
|
|
2520
|
-
candidateOwned:
|
|
2521
|
-
closedAt:
|
|
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:
|
|
2524
|
-
number:
|
|
2525
|
-
openedAt:
|
|
2526
|
-
pullRequestId:
|
|
2527
|
-
repositoryFullName:
|
|
2528
|
-
repositoryId:
|
|
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:
|
|
2531
|
-
updatedAt:
|
|
2712
|
+
title: z15.string().min(1).optional(),
|
|
2713
|
+
updatedAt: z15.date()
|
|
2532
2714
|
}).strict();
|
|
2533
|
-
var githubPullRequestConversationsInputSchema =
|
|
2534
|
-
conversationIds:
|
|
2535
|
-
pullRequestId:
|
|
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 =
|
|
2538
|
-
linkedIssues:
|
|
2539
|
-
|
|
2540
|
-
number:
|
|
2541
|
-
repositoryFullName:
|
|
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:
|
|
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
|
|
2710
|
-
var canonicalIssueOutcomeSchema =
|
|
2711
|
-
action:
|
|
2712
|
-
issue:
|
|
2713
|
-
body:
|
|
2714
|
-
closed_at:
|
|
2715
|
-
created_at:
|
|
2716
|
-
id:
|
|
2717
|
-
number:
|
|
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:
|
|
2720
|
-
user:
|
|
2901
|
+
updated_at: z16.string(),
|
|
2902
|
+
user: z16.object({ login: z16.string().min(1) }).strict()
|
|
2721
2903
|
}).strict(),
|
|
2722
|
-
repository:
|
|
2723
|
-
full_name:
|
|
2724
|
-
id:
|
|
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 =
|
|
2728
|
-
action:
|
|
2729
|
-
issue:
|
|
2730
|
-
body:
|
|
2731
|
-
closed_at:
|
|
2732
|
-
created_at:
|
|
2733
|
-
id:
|
|
2734
|
-
number:
|
|
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:
|
|
2737
|
-
user:
|
|
2918
|
+
updated_at: z16.string(),
|
|
2919
|
+
user: z16.object({ login: z16.string().min(1) }).passthrough()
|
|
2738
2920
|
}).passthrough(),
|
|
2739
|
-
repository:
|
|
2740
|
-
full_name:
|
|
2741
|
-
id:
|
|
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 =
|
|
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 =
|
|
2809
|
-
issue:
|
|
2810
|
-
body:
|
|
2811
|
-
id:
|
|
2812
|
-
user:
|
|
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:
|
|
2996
|
+
sender: z16.object({ login: z16.string().min(1) }).strict().optional()
|
|
2815
2997
|
}).strict();
|
|
2816
|
-
var issueConversationSchema =
|
|
2817
|
-
issue:
|
|
2818
|
-
body:
|
|
2819
|
-
id:
|
|
2820
|
-
user:
|
|
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:
|
|
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
|
|
2850
|
-
var canonicalPullRequestOutcomeSchema =
|
|
2851
|
-
action:
|
|
2852
|
-
pull_request:
|
|
2853
|
-
body:
|
|
2854
|
-
closed_at:
|
|
2855
|
-
created_at:
|
|
2856
|
-
id:
|
|
2857
|
-
merged:
|
|
2858
|
-
merged_at:
|
|
2859
|
-
number:
|
|
2860
|
-
title:
|
|
2861
|
-
updated_at:
|
|
2862
|
-
user:
|
|
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:
|
|
2865
|
-
full_name:
|
|
2866
|
-
id:
|
|
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 =
|
|
2870
|
-
action:
|
|
2871
|
-
pull_request:
|
|
2872
|
-
body:
|
|
2873
|
-
closed_at:
|
|
2874
|
-
created_at:
|
|
2875
|
-
id:
|
|
2876
|
-
merged:
|
|
2877
|
-
merged_at:
|
|
2878
|
-
number:
|
|
2879
|
-
title:
|
|
2880
|
-
updated_at:
|
|
2881
|
-
user:
|
|
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:
|
|
2884
|
-
full_name:
|
|
2885
|
-
id:
|
|
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 =
|
|
2909
|
-
var canonicalPullRequestConversationSchema =
|
|
2910
|
-
pull_request:
|
|
2911
|
-
body:
|
|
2912
|
-
id:
|
|
2913
|
-
user:
|
|
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:
|
|
2916
|
-
sender:
|
|
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 =
|
|
2919
|
-
pull_request:
|
|
2920
|
-
body:
|
|
2921
|
-
id:
|
|
2922
|
-
user:
|
|
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:
|
|
2925
|
-
sender:
|
|
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
|
|
3208
|
-
var canonicalCommitSchema =
|
|
3209
|
-
authorEmail:
|
|
3210
|
-
authorLogin:
|
|
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 =
|
|
3213
|
-
author:
|
|
3214
|
-
commit:
|
|
3215
|
-
author:
|
|
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 =
|
|
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
|
-
|
|
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: [
|
|
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
|
@@ -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.
|
|
3
|
+
"version": "0.201.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.
|
|
34
|
+
"@sentry/junior-plugin-api": "0.201.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` |
|