@sentry/junior-github 0.235.0 → 0.237.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/README.md CHANGED
@@ -25,6 +25,15 @@ export const plugins = defineJuniorPlugins([
25
25
 
26
26
  Full setup guide: https://junior.sentry.dev/extend/github-plugin/
27
27
 
28
+ The plugin tries to assign new pull requests to the current requester's linked
29
+ GitHub account. Assignment is optional. Missing handles, system actors, identity
30
+ lookup errors, and assignment failures do not stop creation or fail the tool.
31
+ If assignment is not possible, the pull request can remain unassigned.
32
+
33
+ The plugin saves the created pull request before it tries assignment. Retrying
34
+ the same tool call returns the saved pull request without retrying assignment,
35
+ creating another pull request, or replacing existing assignees.
36
+
28
37
  The plugin owns its signed webhook route, deployment, pull request, and release
29
38
  events, and normalized pull request and issue outcome projections.
30
39
  Those projections also feed native code-change records used by the dashboard
@@ -1224,6 +1224,27 @@ async function loadCheckSuiteFacts(args) {
1224
1224
  return facts.failingChecks || facts.pullRequestFactsByNumber ? facts : void 0;
1225
1225
  }
1226
1226
 
1227
+ // src/webhooks/ownership.ts
1228
+ var GITHUB_BOT_NOREPLY_EMAIL = /^(?<userId>[1-9]\d*)\+(?<login>[^@]+\[bot\])@users\.noreply\.github\.com$/i;
1229
+ function parseGitHubBotIdentity(value) {
1230
+ const match = GITHUB_BOT_NOREPLY_EMAIL.exec(value?.trim() ?? "");
1231
+ if (!match?.groups) return void 0;
1232
+ return {
1233
+ login: match.groups.login,
1234
+ userId: Number(match.groups.userId)
1235
+ };
1236
+ }
1237
+ function botLoginFromEmail(value) {
1238
+ return parseGitHubBotIdentity(value)?.login;
1239
+ }
1240
+ function isBotLogin(login, botEmail) {
1241
+ const botLogin = botLoginFromEmail(botEmail)?.toLowerCase();
1242
+ return Boolean(botLogin && login?.trim().toLowerCase() === botLogin);
1243
+ }
1244
+ function botUserIdFromEmail(value) {
1245
+ return parseGitHubBotIdentity(value)?.userId;
1246
+ }
1247
+
1227
1248
  // src/webhooks/events.ts
1228
1249
  function gitHubEventKey2(deliveryId, eventType) {
1229
1250
  return `github:${deliveryId}:${eventType}`;
@@ -1403,14 +1424,15 @@ var issueCommentWebhookSchema = z2.object({
1403
1424
  }),
1404
1425
  repository: repositorySchema2
1405
1426
  });
1406
- function normalizeIssueCommentEvents(deliveryId, body) {
1427
+ function normalizeIssueCommentEvents(deliveryId, body, botEmail) {
1407
1428
  const parsed = issueCommentWebhookSchema.safeParse(body);
1408
1429
  if (!parsed.success || parsed.data.action !== "created") return [];
1430
+ const author = parsed.data.comment.user?.login;
1431
+ if (isBotLogin(author, botEmail)) return [];
1409
1432
  const input = {
1410
1433
  number: parsed.data.issue.number,
1411
1434
  repo: parsed.data.repository.full_name
1412
1435
  };
1413
- const author = parsed.data.comment.user?.login;
1414
1436
  if (parsed.data.issue.pull_request) {
1415
1437
  const eventType = "pull_request.comment.created";
1416
1438
  const resource = gitHubPullRequestResource(input);
@@ -1531,9 +1553,10 @@ var pullRequestReviewCommentWebhookSchema = z2.object({
1531
1553
  }),
1532
1554
  repository: repositorySchema2
1533
1555
  });
1534
- function normalizePullRequestReviewCommentEvent(deliveryId, body) {
1556
+ function normalizePullRequestReviewCommentEvent(deliveryId, body, botEmail) {
1535
1557
  const parsed = pullRequestReviewCommentWebhookSchema.safeParse(body);
1536
1558
  if (!parsed.success || parsed.data.action !== "created") return [];
1559
+ if (isBotLogin(parsed.data.comment.user?.login, botEmail)) return [];
1537
1560
  const eventType = "pull_request.review_comment.created";
1538
1561
  const repo = parsed.data.repository.full_name;
1539
1562
  const resource = gitHubPullRequestResource({
@@ -1585,9 +1608,10 @@ var pullRequestReviewWebhookSchema = z2.object({
1585
1608
  user: z2.object({ login: z2.string().optional() }).optional()
1586
1609
  })
1587
1610
  });
1588
- function normalizePullRequestReviewEvent(deliveryId, body) {
1611
+ function normalizePullRequestReviewEvent(deliveryId, body, botEmail) {
1589
1612
  const parsed = pullRequestReviewWebhookSchema.safeParse(body);
1590
1613
  if (!parsed.success || parsed.data.action !== "submitted") return [];
1614
+ if (isBotLogin(parsed.data.review.user?.login, botEmail)) return [];
1591
1615
  const reviewState = parsed.data.review.state.toUpperCase();
1592
1616
  const eventType = reviewState === "APPROVED" ? "pull_request.review.approved" : reviewState === "CHANGES_REQUESTED" ? "pull_request.review.changes_requested" : reviewState === "COMMENTED" ? "pull_request.review.commented" : void 0;
1593
1617
  if (!eventType) return [];
@@ -1811,11 +1835,23 @@ function normalizeGitHubEvents(args) {
1811
1835
  case "issues":
1812
1836
  return normalizeIssueEvents(args.deliveryId, args.body);
1813
1837
  case "pull_request_review":
1814
- return normalizePullRequestReviewEvent(args.deliveryId, args.body);
1838
+ return normalizePullRequestReviewEvent(
1839
+ args.deliveryId,
1840
+ args.body,
1841
+ args.botEmail
1842
+ );
1815
1843
  case "issue_comment":
1816
- return normalizeIssueCommentEvents(args.deliveryId, args.body);
1844
+ return normalizeIssueCommentEvents(
1845
+ args.deliveryId,
1846
+ args.body,
1847
+ args.botEmail
1848
+ );
1817
1849
  case "pull_request_review_comment":
1818
- return normalizePullRequestReviewCommentEvent(args.deliveryId, args.body);
1850
+ return normalizePullRequestReviewCommentEvent(
1851
+ args.deliveryId,
1852
+ args.body,
1853
+ args.botEmail
1854
+ );
1819
1855
  case "check_suite":
1820
1856
  return normalizeCheckSuiteEvents(
1821
1857
  args.deliveryId,
@@ -1847,6 +1883,9 @@ export {
1847
1883
  GITHUB_RELEASE_SUGGESTED_EVENTS,
1848
1884
  gitHubReleaseSourceSubscribable,
1849
1885
  gitHubRepositorySubscribable,
1886
+ botLoginFromEmail,
1887
+ isBotLogin,
1888
+ botUserIdFromEmail,
1850
1889
  GITHUB_APP_ID_ENV,
1851
1890
  GITHUB_APP_PRIVATE_KEY_ENV,
1852
1891
  GITHUB_INSTALLATION_ID_ENV,
package/dist/index.js CHANGED
@@ -19,6 +19,8 @@ import {
19
19
  HTTP_READ_METHODS,
20
20
  USER_TOKEN_GRANTS,
21
21
  USER_WRITE_REQUIREMENTS,
22
+ botLoginFromEmail,
23
+ botUserIdFromEmail,
22
24
  createPermissionCache,
23
25
  credentialUnavailable,
24
26
  gitHubDeploymentSourceSubscribable,
@@ -29,6 +31,7 @@ import {
29
31
  gitHubRepositorySubscribable,
30
32
  githubRepositoryFromUrl,
31
33
  githubRequest,
34
+ isBotLogin,
32
35
  isRecord,
33
36
  issueInstallationCredential,
34
37
  issueInstallationToken,
@@ -43,7 +46,7 @@ import {
43
46
  readGrantPermissions,
44
47
  requireEnv,
45
48
  resolveUserAccount
46
- } from "./chunk-XP2336UO.js";
49
+ } from "./chunk-UB3DDGLH.js";
47
50
 
48
51
  // src/plugin.ts
49
52
  import {
@@ -1353,6 +1356,44 @@ async function createGitHubPullRequest(ctx, request) {
1353
1356
  url: pullRequest.html_url
1354
1357
  };
1355
1358
  }
1359
+ async function assignPullRequestRequester(ctx, input, result) {
1360
+ if (!ctx.actor || ctx.actor.platform === "system") return;
1361
+ try {
1362
+ const requester = await ctx.users.resolveActor();
1363
+ const assignee = requester?.user?.identities.find((identity) => identity.provider === "github")?.handle?.trim();
1364
+ if (!assignee) return;
1365
+ const repo = parseRepo4(input.repo);
1366
+ const response = await ctx.egress.fetch({
1367
+ provider: "github",
1368
+ operation: "github.pull.assign",
1369
+ request: new Request(
1370
+ `https://api.github.com/repos/${encodeURIComponent(repo.owner)}/${encodeURIComponent(repo.name)}/issues/${result.number}/assignees`,
1371
+ {
1372
+ method: "POST",
1373
+ headers: {
1374
+ Accept: "application/vnd.github+json",
1375
+ "Content-Type": "application/json",
1376
+ "X-GitHub-Api-Version": "2022-11-28"
1377
+ },
1378
+ body: JSON.stringify({ assignees: [assignee] })
1379
+ }
1380
+ )
1381
+ });
1382
+ if (!response.ok) {
1383
+ ctx.log.warn("github.pull_request.assign_after_create.rejected", {
1384
+ number: result.number,
1385
+ repo: input.repo,
1386
+ status: response.status
1387
+ });
1388
+ }
1389
+ } catch (error) {
1390
+ ctx.log.warn("github.pull_request.assign_after_create.failed", {
1391
+ error: error instanceof Error ? error.message : String(error),
1392
+ number: result.number,
1393
+ repo: input.repo
1394
+ });
1395
+ }
1396
+ }
1356
1397
  function gitHubPullRequestToolResult(input, result, canSubscribe, omitSuggestedEvents) {
1357
1398
  const repo = parseRepo4(input.repo);
1358
1399
  const subscribable = canSubscribe ? gitHubPullRequestSubscribable({
@@ -1431,7 +1472,7 @@ function createGitHubPullRequestTool(ctx, subscriptionConfig) {
1431
1472
  openWorldHint: true,
1432
1473
  readOnlyHint: false
1433
1474
  },
1434
- description: "Create a GitHub pull request with a runtime-owned conversation footer. Use this instead of shelling out to gh pr create when creating pull requests.",
1475
+ description: "Create a GitHub pull request with a runtime-owned conversation footer and assign it to the requester's linked GitHub account when available. Use this instead of shelling out to gh pr create when creating pull requests.",
1435
1476
  exposure: "direct",
1436
1477
  inputSchema: createPullRequestToolInputSchema,
1437
1478
  outputSchema: gitHubPullRequestOutputSchema,
@@ -1485,33 +1526,39 @@ function createGitHubPullRequestTool(ctx, subscriptionConfig) {
1485
1526
  pendingState,
1486
1527
  GITHUB_PULL_REQUEST_CREATE_IDEMPOTENCY_TTL_MS
1487
1528
  );
1529
+ let result;
1488
1530
  try {
1489
- const result = await createGitHubPullRequest(ctx, request);
1490
- Object.assign(pendingState, { status: "completed", ...result });
1491
- try {
1492
- await ctx.state.set(
1493
- key,
1494
- pendingState,
1495
- GITHUB_PULL_REQUEST_CREATE_IDEMPOTENCY_TTL_MS
1496
- );
1497
- } catch (error) {
1498
- throw new Error(
1499
- "GitHub pull request was created, but the runtime could not persist the completed pull request state.",
1500
- { cause: error }
1501
- );
1502
- }
1503
- return await gitHubPullRequestStructuredResult(
1504
- ctx,
1505
- parsedInput,
1506
- result,
1507
- subscriptionConfig
1508
- );
1531
+ result = await createGitHubPullRequest(ctx, request);
1509
1532
  } catch (error) {
1510
1533
  if (isEgressAuthRequired2(error) || isDefinitiveGitHubPullRequestCreateRejection(error)) {
1511
1534
  await ctx.state.delete(key);
1512
1535
  }
1513
1536
  throw error;
1514
1537
  }
1538
+ const completedState = {
1539
+ ...pendingState,
1540
+ status: "completed",
1541
+ ...result
1542
+ };
1543
+ try {
1544
+ await ctx.state.set(
1545
+ key,
1546
+ completedState,
1547
+ GITHUB_PULL_REQUEST_CREATE_IDEMPOTENCY_TTL_MS
1548
+ );
1549
+ } catch (error) {
1550
+ throw new Error(
1551
+ "GitHub pull request was created, but the runtime could not persist the completed pull request state.",
1552
+ { cause: error }
1553
+ );
1554
+ }
1555
+ await assignPullRequestRequester(ctx, parsedInput, result);
1556
+ return await gitHubPullRequestStructuredResult(
1557
+ ctx,
1558
+ parsedInput,
1559
+ result,
1560
+ subscriptionConfig
1561
+ );
1515
1562
  }
1516
1563
  );
1517
1564
  }
@@ -2217,25 +2264,6 @@ import {
2217
2264
  pluginToolOutputSchema as pluginToolOutputSchema10
2218
2265
  } from "@sentry/junior-plugin-api";
2219
2266
  import { z as z11 } from "zod";
2220
-
2221
- // src/webhooks/ownership.ts
2222
- var GITHUB_BOT_NOREPLY_EMAIL = /^(?<userId>[1-9]\d*)\+(?<login>[^@]+\[bot\])@users\.noreply\.github\.com$/i;
2223
- function parseGitHubBotIdentity(value) {
2224
- const match = GITHUB_BOT_NOREPLY_EMAIL.exec(value?.trim() ?? "");
2225
- if (!match?.groups) return void 0;
2226
- return {
2227
- login: match.groups.login,
2228
- userId: Number(match.groups.userId)
2229
- };
2230
- }
2231
- function botLoginFromEmail(value) {
2232
- return parseGitHubBotIdentity(value)?.login;
2233
- }
2234
- function botUserIdFromEmail(value) {
2235
- return parseGitHubBotIdentity(value)?.userId;
2236
- }
2237
-
2238
- // src/tools/update-pull-request-feedback.ts
2239
2267
  var STATUS_TO_REACTION_CONTENT = {
2240
2268
  reviewing: "eyes",
2241
2269
  addressed: "+1",
@@ -3184,15 +3212,13 @@ function normalizeGitHubIssueOutcome(args) {
3184
3212
  if (!openedAt || !updatedAt) {
3185
3213
  throw new Error("GitHub issue lifecycle timestamps are invalid");
3186
3214
  }
3187
- const authorLogin = issue.user.login.trim().toLowerCase();
3188
- const botLogin = botLoginFromEmail(args.botEmail)?.toLowerCase();
3189
- if (parsed.action === "opened" && !botLogin) {
3215
+ if (parsed.action === "opened" && !botLoginFromEmail(args.botEmail)) {
3190
3216
  throw new Error(
3191
3217
  "The configured GitHub App bot email must encode a [bot] login in GitHub's noreply format to classify issue ownership"
3192
3218
  );
3193
3219
  }
3194
3220
  const hasOwnershipMarker = Boolean(
3195
- botLogin && authorLogin === botLogin && issue.body?.includes(GITHUB_SESSION_FOOTER_START)
3221
+ isBotLogin(issue.user.login, args.botEmail) && issue.body?.includes(GITHUB_SESSION_FOOTER_START)
3196
3222
  );
3197
3223
  const candidateOwned = hasOwnershipMarker && (parsed.action === "opened" || parsed.action === "closed");
3198
3224
  const closedAt = timestamp2(issue.closed_at);
@@ -3239,12 +3265,12 @@ var issueConversationSchema = z17.object({
3239
3265
  );
3240
3266
  function normalizeGitHubIssueConversations(args) {
3241
3267
  const parsed = issueConversationSchema.safeParse(args.body);
3242
- const botLogin = botLoginFromEmail(args.botEmail)?.toLowerCase();
3243
- if (!parsed.success || !botLogin) return void 0;
3244
- const authorLogin = parsed.data.issue.user.login.trim().toLowerCase();
3245
- if (authorLogin !== botLogin) return void 0;
3246
- const senderLogin = parsed.data.sender?.login.trim().toLowerCase();
3247
- if (senderLogin && senderLogin !== botLogin) return void 0;
3268
+ if (!parsed.success) return void 0;
3269
+ if (!isBotLogin(parsed.data.issue.user.login, args.botEmail)) {
3270
+ return void 0;
3271
+ }
3272
+ const sender = parsed.data.sender;
3273
+ if (sender && !isBotLogin(sender.login, args.botEmail)) return void 0;
3248
3274
  const conversationIds = githubConversationIds(parsed.data.issue.body);
3249
3275
  return conversationIds.length > 0 ? {
3250
3276
  conversationIds,
@@ -3358,15 +3384,13 @@ function normalizeGitHubPullRequestOutcome(args) {
3358
3384
  if (!openedAt || !updatedAt) {
3359
3385
  throw new Error("GitHub pull request lifecycle timestamps are invalid");
3360
3386
  }
3361
- const authorLogin = pullRequest.user.login.trim().toLowerCase();
3362
- const botLogin = botLoginFromEmail(args.botEmail)?.toLowerCase();
3363
- if (parsed.action === "opened" && !botLogin) {
3387
+ if (parsed.action === "opened" && !botLoginFromEmail(args.botEmail)) {
3364
3388
  throw new Error(
3365
3389
  "The configured GitHub App bot email must encode a [bot] login in GitHub's noreply format to classify pull request ownership"
3366
3390
  );
3367
3391
  }
3368
3392
  const hasOwnershipMarker = Boolean(
3369
- botLogin && authorLogin === botLogin && pullRequest.body?.includes(GITHUB_SESSION_FOOTER_START)
3393
+ isBotLogin(pullRequest.user.login, args.botEmail) && pullRequest.body?.includes(GITHUB_SESSION_FOOTER_START)
3370
3394
  );
3371
3395
  const candidateOwned = hasOwnershipMarker && (parsed.action === "opened" || parsed.action === "closed");
3372
3396
  const state = parsed.action !== "closed" ? "open" : pullRequest.merged ? "merged" : "closed_unmerged";
@@ -3391,9 +3415,8 @@ function normalizeGitHubPullRequestOutcome(args) {
3391
3415
  }
3392
3416
  function normalizeGitHubPullRequestConversations(args) {
3393
3417
  const parsed = pullRequestConversationSchema.safeParse(args.body);
3394
- const botLogin = botLoginFromEmail(args.botEmail)?.toLowerCase();
3395
- if (!parsed.success || !botLogin) return void 0;
3396
- if (parsed.data.pull_request.user.login.trim().toLowerCase() !== botLogin || parsed.data.sender.login.trim().toLowerCase() !== botLogin) {
3418
+ if (!parsed.success) return void 0;
3419
+ if (!isBotLogin(parsed.data.pull_request.user.login, args.botEmail) || !isBotLogin(parsed.data.sender.login, args.botEmail)) {
3397
3420
  return void 0;
3398
3421
  }
3399
3422
  const conversationIds = githubConversationIds(parsed.data.pull_request.body);
@@ -3404,9 +3427,8 @@ function normalizeGitHubPullRequestConversations(args) {
3404
3427
  }
3405
3428
  function normalizeGitHubPullRequestLinkedIssues(args) {
3406
3429
  const parsed = pullRequestConversationSchema.safeParse(args.body);
3407
- const botLogin = botLoginFromEmail(args.botEmail)?.toLowerCase();
3408
- if (!parsed.success || !botLogin) return void 0;
3409
- if (parsed.data.pull_request.user.login.trim().toLowerCase() !== botLogin || parsed.data.sender.login.trim().toLowerCase() !== botLogin) {
3430
+ if (!parsed.success) return void 0;
3431
+ if (!isBotLogin(parsed.data.pull_request.user.login, args.botEmail) || !isBotLogin(parsed.data.sender.login, args.botEmail)) {
3410
3432
  return void 0;
3411
3433
  }
3412
3434
  const linkedIssues = githubLinkedIssues(
@@ -3613,6 +3635,7 @@ function createGitHubWebhookRoute(args) {
3613
3635
  }) : void 0;
3614
3636
  const events = normalizeGitHubEvents({
3615
3637
  body,
3638
+ botEmail,
3616
3639
  ...checkSuiteFacts ? { checkSuiteFacts } : void 0,
3617
3640
  deliveryId,
3618
3641
  eventName
@@ -3673,8 +3696,7 @@ var MAX_PAGES = 3;
3673
3696
  var MAX_LISTED_COMMITS = 250;
3674
3697
  async function classifyGitHubPullRequestCommitComposition(args) {
3675
3698
  const botEmail = args.botEmail.trim().toLowerCase();
3676
- const botLogin = botLoginFromEmail(botEmail)?.toLowerCase();
3677
- if (!botEmail || !botLogin) {
3699
+ if (!botEmail || !botLoginFromEmail(botEmail)) {
3678
3700
  throw new Error(
3679
3701
  "The configured GitHub App bot email cannot classify pull request commits"
3680
3702
  );
@@ -3688,9 +3710,8 @@ async function classifyGitHubPullRequestCommitComposition(args) {
3688
3710
  inspectedCommits += commits.length;
3689
3711
  for (const commit of commits) {
3690
3712
  foundCommit = true;
3691
- const authorLogin = commit.authorLogin?.trim().toLowerCase();
3692
3713
  const authorEmail = commit.authorEmail?.trim().toLowerCase();
3693
- if (authorLogin !== botLogin && authorEmail !== botEmail) {
3714
+ if (!isBotLogin(commit.authorLogin, botEmail) && authorEmail !== botEmail) {
3694
3715
  return "mixed";
3695
3716
  }
3696
3717
  }
package/dist/testing.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  normalizeGitHubEvents
3
- } from "./chunk-XP2336UO.js";
3
+ } from "./chunk-UB3DDGLH.js";
4
4
  export {
5
5
  normalizeGitHubEvents
6
6
  };
@@ -2,10 +2,15 @@ import type { EventInput } from "@sentry/junior-plugin-api";
2
2
  import { type GitHubCheckSuiteFacts } from "./check-suite.js";
3
3
  export type { GitHubCheckSuiteFacts, GitHubCheckSuitePullRequestFacts, GitHubFailingCheck, } from "./check-suite.js";
4
4
  export { buildCheckSuiteEvent, buildCheckSuiteUrl, loadCheckSuiteFacts, needsCheckSuitePullRequestFacts, parseCheckSuiteFactsTarget, parseCheckSuitePublishTargets, selectFailingChecks, } from "./check-suite.js";
5
- /** Read the check suite target used to load missing suite facts. */
6
- /** Normalize one verified GitHub delivery into conversation events. */
5
+ /**
6
+ * Normalize one verified GitHub delivery into conversation events.
7
+ *
8
+ * Comments and reviews written by the configured bot are dropped, so Junior
9
+ * does not react to or act on its own feedback.
10
+ */
7
11
  export declare function normalizeGitHubEvents(args: {
8
12
  body: unknown;
13
+ botEmail?: string;
9
14
  checkSuiteFacts?: GitHubCheckSuiteFacts;
10
15
  deliveryId: string;
11
16
  eventName: string;
@@ -1,4 +1,6 @@
1
1
  /** Derive the provider login encoded in a standard GitHub noreply address. */
2
2
  export declare function botLoginFromEmail(value: string | undefined): string | undefined;
3
+ /** Check whether a GitHub login is the bot encoded in the configured bot email. */
4
+ export declare function isBotLogin(login: string | null | undefined, botEmail: string | undefined): boolean;
3
5
  /** Derive the GitHub user id encoded in a standard GitHub noreply address. */
4
6
  export declare function botUserIdFromEmail(value: string | undefined): number | undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sentry/junior-github",
3
- "version": "0.235.0",
3
+ "version": "0.237.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.235.0"
34
+ "@sentry/junior-plugin-api": "0.237.0"
35
35
  },
36
36
  "devDependencies": {
37
37
  "@oxlint/plugins": "1.79.0",