mobbdev 1.1.7 → 1.1.9

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.
@@ -736,7 +736,7 @@ var GetVulByNodesMetadataDocument = `
736
736
  where: {id: {_eq: $vulnerabilityReportId}}
737
737
  ) {
738
738
  vulnerabilityReportIssues(
739
- where: {fixId: {_is_null: true}, category: {_in: [Irrelevant, FalsePositive, Filtered]}}
739
+ where: {fixId: {_is_null: true}, category: {_in: [Irrelevant, FalsePositive, Filtered]}, _not: {vulnerabilityReportIssueTags: {vulnerability_report_issue_tag_value: {_eq: SUPPRESSED}}}}
740
740
  ) {
741
741
  id
742
742
  safeIssueType
package/dist/index.mjs CHANGED
@@ -1996,7 +1996,7 @@ var GetVulByNodesMetadataDocument = `
1996
1996
  where: {id: {_eq: $vulnerabilityReportId}}
1997
1997
  ) {
1998
1998
  vulnerabilityReportIssues(
1999
- where: {fixId: {_is_null: true}, category: {_in: [Irrelevant, FalsePositive, Filtered]}}
1999
+ where: {fixId: {_is_null: true}, category: {_in: [Irrelevant, FalsePositive, Filtered]}, _not: {vulnerabilityReportIssueTags: {vulnerability_report_issue_tag_value: {_eq: SUPPRESSED}}}}
2000
2000
  ) {
2001
2001
  id
2002
2002
  safeIssueType
@@ -7512,6 +7512,61 @@ var GET_BLAME_DOCUMENT = `
7512
7512
  }
7513
7513
  }
7514
7514
  `;
7515
+ var GITHUB_GRAPHQL_FRAGMENTS = {
7516
+ /**
7517
+ * Fragment for fetching PR additions/deletions.
7518
+ * Use with pullRequest(number: $n) alias.
7519
+ */
7520
+ PR_CHANGES: `
7521
+ additions
7522
+ deletions
7523
+ `,
7524
+ /**
7525
+ * Fragment for fetching PR comments.
7526
+ * Returns first 100 comments with author info.
7527
+ */
7528
+ PR_COMMENTS: `
7529
+ comments(first: 100) {
7530
+ nodes {
7531
+ author {
7532
+ login
7533
+ __typename
7534
+ }
7535
+ body
7536
+ }
7537
+ }
7538
+ `,
7539
+ /**
7540
+ * Fragment for fetching blame data.
7541
+ * Use with object(expression: $ref) on Commit type.
7542
+ */
7543
+ BLAME_RANGES: `
7544
+ blame(path: "$path") {
7545
+ ranges {
7546
+ startingLine
7547
+ endingLine
7548
+ commit {
7549
+ oid
7550
+ author {
7551
+ user {
7552
+ name
7553
+ login
7554
+ email
7555
+ }
7556
+ }
7557
+ }
7558
+ }
7559
+ }
7560
+ `,
7561
+ /**
7562
+ * Fragment for fetching commit timestamp.
7563
+ * Use with object(oid: $sha) on Commit type.
7564
+ */
7565
+ COMMIT_TIMESTAMP: `
7566
+ oid
7567
+ committedDate
7568
+ `
7569
+ };
7515
7570
 
7516
7571
  // src/features/analysis/scm/github/utils/encrypt_secret.ts
7517
7572
  import sodium from "libsodium-wrappers";
@@ -7649,6 +7704,36 @@ async function githubValidateParams(url, accessToken) {
7649
7704
 
7650
7705
  // src/features/analysis/scm/github/github.ts
7651
7706
  var MAX_GH_PR_BODY_LENGTH = 65536;
7707
+ async function executeBatchGraphQL(octokit, owner, repo, config2) {
7708
+ const { items, aliasPrefix, buildFragment, extractResult } = config2;
7709
+ if (items.length === 0) {
7710
+ return /* @__PURE__ */ new Map();
7711
+ }
7712
+ const fragments = items.map((item, index) => buildFragment(item, index)).join("\n");
7713
+ const query = `
7714
+ query Batch${aliasPrefix}($owner: String!, $repo: String!) {
7715
+ repository(owner: $owner, name: $repo) {
7716
+ ${fragments}
7717
+ }
7718
+ }
7719
+ `;
7720
+ const response = await octokit.graphql(query, { owner, repo });
7721
+ const result = /* @__PURE__ */ new Map();
7722
+ items.forEach((item, index) => {
7723
+ const data = response.repository[`${aliasPrefix}${index}`];
7724
+ if (data) {
7725
+ const extracted = extractResult(
7726
+ data,
7727
+ item,
7728
+ index
7729
+ );
7730
+ if (extracted !== void 0) {
7731
+ result.set(item, extracted);
7732
+ }
7733
+ }
7734
+ });
7735
+ return result;
7736
+ }
7652
7737
  function getGithubSdk(params = {}) {
7653
7738
  const octokit = getOctoKit(params);
7654
7739
  return {
@@ -8114,6 +8199,119 @@ function getGithubSdk(params = {}) {
8114
8199
  pull_number: params2.pull_number
8115
8200
  });
8116
8201
  return { data };
8202
+ },
8203
+ /**
8204
+ * Batch fetch additions/deletions for multiple PRs via GraphQL.
8205
+ * Uses GITHUB_GRAPHQL_FRAGMENTS.PR_CHANGES for the field selection.
8206
+ */
8207
+ async getPrAdditionsDeletionsBatch(params2) {
8208
+ return executeBatchGraphQL(octokit, params2.owner, params2.repo, {
8209
+ items: params2.prNumbers,
8210
+ aliasPrefix: "pr",
8211
+ buildFragment: (prNumber, index) => `
8212
+ pr${index}: pullRequest(number: ${prNumber}) {
8213
+ ${GITHUB_GRAPHQL_FRAGMENTS.PR_CHANGES}
8214
+ }`,
8215
+ extractResult: (data) => {
8216
+ const prData = data;
8217
+ if (prData.additions !== void 0 && prData.deletions !== void 0) {
8218
+ return {
8219
+ additions: prData.additions,
8220
+ deletions: prData.deletions
8221
+ };
8222
+ }
8223
+ return void 0;
8224
+ }
8225
+ });
8226
+ },
8227
+ /**
8228
+ * Batch fetch comments for multiple PRs via GraphQL.
8229
+ * Uses GITHUB_GRAPHQL_FRAGMENTS.PR_COMMENTS for the field selection.
8230
+ */
8231
+ async getPrCommentsBatch(params2) {
8232
+ return executeBatchGraphQL(octokit, params2.owner, params2.repo, {
8233
+ items: params2.prNumbers,
8234
+ aliasPrefix: "pr",
8235
+ buildFragment: (prNumber, index) => `
8236
+ pr${index}: pullRequest(number: ${prNumber}) {
8237
+ ${GITHUB_GRAPHQL_FRAGMENTS.PR_COMMENTS}
8238
+ }`,
8239
+ extractResult: (data) => {
8240
+ const prData = data;
8241
+ if (prData.comments?.nodes) {
8242
+ return prData.comments.nodes.map((node) => ({
8243
+ author: node.author ? { login: node.author.login, type: node.author.__typename } : null,
8244
+ body: node.body
8245
+ }));
8246
+ }
8247
+ return void 0;
8248
+ }
8249
+ });
8250
+ },
8251
+ /**
8252
+ * Batch fetch blame data for multiple files via GraphQL.
8253
+ * Field selection matches GITHUB_GRAPHQL_FRAGMENTS.BLAME_RANGES pattern.
8254
+ */
8255
+ async getBlameBatch(params2) {
8256
+ return executeBatchGraphQL(octokit, params2.owner, params2.repo, {
8257
+ items: params2.filePaths,
8258
+ aliasPrefix: "file",
8259
+ buildFragment: (path22, index) => `
8260
+ file${index}: object(expression: "${params2.ref}") {
8261
+ ... on Commit {
8262
+ blame(path: "${path22}") {
8263
+ ranges {
8264
+ startingLine
8265
+ endingLine
8266
+ commit {
8267
+ oid
8268
+ }
8269
+ }
8270
+ }
8271
+ }
8272
+ }`,
8273
+ extractResult: (data) => {
8274
+ const fileData = data;
8275
+ if (fileData.blame?.ranges) {
8276
+ return fileData.blame.ranges.map((range) => ({
8277
+ startingLine: range.startingLine,
8278
+ endingLine: range.endingLine,
8279
+ commitSha: range.commit.oid,
8280
+ // This is an urgent fix. We need to later remove these fields from the return type and propagate the change.
8281
+ email: "",
8282
+ name: "",
8283
+ login: ""
8284
+ }));
8285
+ }
8286
+ return void 0;
8287
+ }
8288
+ });
8289
+ },
8290
+ /**
8291
+ * Batch fetch commit timestamps for multiple commits via GraphQL.
8292
+ * Uses GITHUB_GRAPHQL_FRAGMENTS.COMMIT_TIMESTAMP for the field selection.
8293
+ */
8294
+ async getCommitsBatch(params2) {
8295
+ return executeBatchGraphQL(octokit, params2.owner, params2.repo, {
8296
+ items: params2.commitShas,
8297
+ aliasPrefix: "commit",
8298
+ buildFragment: (sha, index) => `
8299
+ commit${index}: object(oid: "${sha}") {
8300
+ ... on Commit {
8301
+ ${GITHUB_GRAPHQL_FRAGMENTS.COMMIT_TIMESTAMP}
8302
+ }
8303
+ }`,
8304
+ extractResult: (data) => {
8305
+ const commitData = data;
8306
+ if (commitData.oid && commitData.committedDate) {
8307
+ return {
8308
+ sha: commitData.oid,
8309
+ timestamp: new Date(commitData.committedDate)
8310
+ };
8311
+ }
8312
+ return void 0;
8313
+ }
8314
+ });
8117
8315
  }
8118
8316
  };
8119
8317
  }
@@ -8382,7 +8580,7 @@ var GithubSCMLib = class extends SCMLib {
8382
8580
  comment_id: commentId
8383
8581
  });
8384
8582
  }
8385
- async getCommitDiff(commitSha) {
8583
+ async getCommitDiff(commitSha, options) {
8386
8584
  this._validateAccessTokenAndUrl();
8387
8585
  const { owner, repo } = parseGithubOwnerAndRepo(this.url);
8388
8586
  const { commit, diff } = await this.githubSdk.getCommitWithDiff({
@@ -8393,43 +8591,49 @@ var GithubSCMLib = class extends SCMLib {
8393
8591
  const commitTimestamp = commit.commit.committer?.date ? new Date(commit.commit.committer.date) : new Date(commit.commit.author?.date || Date.now());
8394
8592
  let parentCommits;
8395
8593
  if (commit.parents && commit.parents.length > 0) {
8594
+ if (options?.parentCommitTimestamps) {
8595
+ parentCommits = commit.parents.map((p) => options.parentCommitTimestamps.get(p.sha)).filter((p) => p !== void 0);
8596
+ } else {
8597
+ try {
8598
+ parentCommits = await Promise.all(
8599
+ commit.parents.map(async (parent) => {
8600
+ const parentCommit = await this.githubSdk.getCommit({
8601
+ owner,
8602
+ repo,
8603
+ commitSha: parent.sha
8604
+ });
8605
+ const parentTimestamp = parentCommit.data.committer?.date ? new Date(parentCommit.data.committer.date) : new Date(Date.now());
8606
+ return {
8607
+ sha: parent.sha,
8608
+ timestamp: parentTimestamp
8609
+ };
8610
+ })
8611
+ );
8612
+ } catch (error) {
8613
+ console.error("Failed to fetch parent commit timestamps", {
8614
+ error,
8615
+ commitSha,
8616
+ owner,
8617
+ repo
8618
+ });
8619
+ parentCommits = void 0;
8620
+ }
8621
+ }
8622
+ }
8623
+ let repositoryCreatedAt = options?.repositoryCreatedAt;
8624
+ if (repositoryCreatedAt === void 0) {
8396
8625
  try {
8397
- parentCommits = await Promise.all(
8398
- commit.parents.map(async (parent) => {
8399
- const parentCommit = await this.githubSdk.getCommit({
8400
- owner,
8401
- repo,
8402
- commitSha: parent.sha
8403
- });
8404
- const parentTimestamp = parentCommit.data.committer?.date ? new Date(parentCommit.data.committer.date) : new Date(Date.now());
8405
- return {
8406
- sha: parent.sha,
8407
- timestamp: parentTimestamp
8408
- };
8409
- })
8410
- );
8626
+ const repoData = await this.githubSdk.getRepository({ owner, repo });
8627
+ repositoryCreatedAt = repoData.data.created_at ? new Date(repoData.data.created_at) : void 0;
8411
8628
  } catch (error) {
8412
- console.error("Failed to fetch parent commit timestamps", {
8629
+ console.error("Failed to fetch repository creation date", {
8413
8630
  error,
8414
- commitSha,
8415
8631
  owner,
8416
8632
  repo
8417
8633
  });
8418
- parentCommits = void 0;
8634
+ repositoryCreatedAt = void 0;
8419
8635
  }
8420
8636
  }
8421
- let repositoryCreatedAt;
8422
- try {
8423
- const repoData = await this.githubSdk.getRepository({ owner, repo });
8424
- repositoryCreatedAt = repoData.data.created_at ? new Date(repoData.data.created_at) : void 0;
8425
- } catch (error) {
8426
- console.error("Failed to fetch repository creation date", {
8427
- error,
8428
- owner,
8429
- repo
8430
- });
8431
- repositoryCreatedAt = void 0;
8432
- }
8433
8637
  return {
8434
8638
  diff,
8435
8639
  commitTimestamp,
@@ -8445,15 +8649,37 @@ var GithubSCMLib = class extends SCMLib {
8445
8649
  this._validateAccessTokenAndUrl();
8446
8650
  const { owner, repo } = parseGithubOwnerAndRepo(this.url);
8447
8651
  const prNumber = Number(submitRequestId);
8448
- const [prRes, commitsRes, filesRes] = await Promise.all([
8652
+ const [prRes, commitsRes, filesRes, repoData] = await Promise.all([
8449
8653
  this.githubSdk.getPr({ owner, repo, pull_number: prNumber }),
8450
8654
  this.githubSdk.getPrCommits({ owner, repo, pull_number: prNumber }),
8451
- this.githubSdk.listPRFiles({ owner, repo, pull_number: prNumber })
8655
+ this.githubSdk.listPRFiles({ owner, repo, pull_number: prNumber }),
8656
+ this.githubSdk.getRepository({ owner, repo })
8452
8657
  ]);
8453
8658
  const pr = prRes.data;
8454
- const prDiff = await this.getPrDiff({ pull_number: prNumber });
8659
+ const repositoryCreatedAt = repoData.data.created_at ? new Date(repoData.data.created_at) : void 0;
8660
+ const allParentShas = /* @__PURE__ */ new Set();
8661
+ for (const commit of commitsRes.data) {
8662
+ if (commit.parents) {
8663
+ for (const parent of commit.parents) {
8664
+ allParentShas.add(parent.sha);
8665
+ }
8666
+ }
8667
+ }
8668
+ const [parentCommitTimestamps, prDiff] = await Promise.all([
8669
+ this.githubSdk.getCommitsBatch({
8670
+ owner,
8671
+ repo,
8672
+ commitShas: Array.from(allParentShas)
8673
+ }),
8674
+ this.getPrDiff({ pull_number: prNumber })
8675
+ ]);
8455
8676
  const commits = await Promise.all(
8456
- commitsRes.data.map((commit) => this.getCommitDiff(commit.sha))
8677
+ commitsRes.data.map(
8678
+ (commit) => this.getCommitDiff(commit.sha, {
8679
+ repositoryCreatedAt,
8680
+ parentCommitTimestamps
8681
+ })
8682
+ )
8457
8683
  );
8458
8684
  const diffLines = await this._attributeLinesViaBlame(
8459
8685
  pr.head.ref,
@@ -8480,104 +8706,83 @@ var GithubSCMLib = class extends SCMLib {
8480
8706
  this._validateAccessToken();
8481
8707
  const { owner, repo } = parseGithubOwnerAndRepo(repoUrl);
8482
8708
  const pullsRes = await this.githubSdk.getRepoPullRequests({ owner, repo });
8483
- const submitRequests = await Promise.all(
8484
- pullsRes.data.map(async (pr) => {
8485
- let status = "open";
8486
- if (pr.state === "closed") {
8487
- status = pr.merged_at ? "merged" : "closed";
8488
- } else if (pr.draft) {
8489
- status = "draft";
8490
- }
8491
- const [tickets, changedLines] = await Promise.all([
8492
- this._extractLinearTicketsFromPR(owner, repo, pr.number),
8493
- this._calculateChangedLinesFromPR(owner, repo, pr.number)
8494
- ]);
8495
- return {
8496
- submitRequestId: String(pr.number),
8497
- submitRequestNumber: pr.number,
8498
- title: pr.title,
8499
- status,
8500
- sourceBranch: pr.head.ref,
8501
- targetBranch: pr.base.ref,
8502
- authorName: pr.user?.name || pr.user?.login,
8503
- authorEmail: pr.user?.email || void 0,
8504
- createdAt: new Date(pr.created_at),
8505
- updatedAt: new Date(pr.updated_at),
8506
- description: pr.body || void 0,
8507
- tickets,
8508
- changedLines
8509
- };
8510
- })
8511
- );
8709
+ const prNumbers = pullsRes.data.map((pr) => pr.number);
8710
+ const [additionsDeletionsMap, commentsMap] = await Promise.all([
8711
+ this.githubSdk.getPrAdditionsDeletionsBatch({ owner, repo, prNumbers }),
8712
+ this.githubSdk.getPrCommentsBatch({ owner, repo, prNumbers })
8713
+ ]);
8714
+ const submitRequests = pullsRes.data.map((pr) => {
8715
+ let status = "open";
8716
+ if (pr.state === "closed") {
8717
+ status = pr.merged_at ? "merged" : "closed";
8718
+ } else if (pr.draft) {
8719
+ status = "draft";
8720
+ }
8721
+ const changedLinesData = additionsDeletionsMap.get(pr.number);
8722
+ const changedLines = changedLinesData ? {
8723
+ added: changedLinesData.additions,
8724
+ removed: changedLinesData.deletions
8725
+ } : { added: 0, removed: 0 };
8726
+ const comments = commentsMap.get(pr.number) || [];
8727
+ const tickets = this._extractLinearTicketsFromComments(comments);
8728
+ return {
8729
+ submitRequestId: String(pr.number),
8730
+ submitRequestNumber: pr.number,
8731
+ title: pr.title,
8732
+ status,
8733
+ sourceBranch: pr.head.ref,
8734
+ targetBranch: pr.base.ref,
8735
+ authorName: pr.user?.name || pr.user?.login,
8736
+ authorEmail: pr.user?.email || void 0,
8737
+ createdAt: new Date(pr.created_at),
8738
+ updatedAt: new Date(pr.updated_at),
8739
+ description: pr.body || void 0,
8740
+ tickets,
8741
+ changedLines
8742
+ };
8743
+ });
8512
8744
  return submitRequests;
8513
8745
  }
8514
- async _extractLinearTicketsFromPR(owner, repo, prNumber) {
8515
- try {
8516
- const commentsRes = await this.githubSdk.getGeneralPrComments({
8517
- owner,
8518
- repo,
8519
- issue_number: prNumber
8520
- });
8521
- const tickets = [];
8522
- for (const comment of commentsRes.data) {
8523
- if (comment.user?.login === "linear[bot]" || comment.user?.type === "Bot") {
8524
- const htmlLinkPattern = /<a href="(https:\/\/linear\.app\/[^"]+)">([A-Z]+-\d+)<\/a>/g;
8525
- let match;
8526
- while ((match = htmlLinkPattern.exec(comment.body || "")) !== null) {
8527
- const url = match[1];
8528
- const name = match[2];
8529
- if (!name || !url) {
8530
- continue;
8531
- }
8532
- const urlParts = url.split("/");
8533
- const titleSlug = urlParts[urlParts.length - 1] || "";
8534
- const title = titleSlug.replace(/-/g, " ");
8535
- tickets.push({ name, title, url });
8536
- }
8537
- const markdownLinkPattern = /\[([A-Z]+-\d+)\]\((https:\/\/linear\.app\/[^)]+)\)/g;
8538
- while ((match = markdownLinkPattern.exec(comment.body || "")) !== null) {
8539
- const name = match[1];
8540
- const url = match[2];
8541
- if (tickets.some((t) => t.name === name && t.url === url)) {
8542
- continue;
8543
- }
8544
- if (!name || !url) {
8545
- continue;
8546
- }
8547
- const urlParts = url.split("/");
8548
- const titleSlug = urlParts[urlParts.length - 1] || "";
8549
- const title = titleSlug.replace(/-/g, " ");
8550
- tickets.push({ name, title, url });
8746
+ /**
8747
+ * Parse a Linear ticket from URL and name
8748
+ * Returns null if invalid or missing data
8749
+ */
8750
+ _parseLinearTicket(url, name) {
8751
+ if (!name || !url) return null;
8752
+ const urlParts = url.split("/");
8753
+ const titleSlug = urlParts[urlParts.length - 1] || "";
8754
+ const title = titleSlug.replace(/-/g, " ");
8755
+ return { name, title, url };
8756
+ }
8757
+ /**
8758
+ * Extract Linear ticket links from pre-fetched comments (pure function, no API calls)
8759
+ */
8760
+ _extractLinearTicketsFromComments(comments) {
8761
+ const tickets = [];
8762
+ const seen = /* @__PURE__ */ new Set();
8763
+ for (const comment of comments) {
8764
+ if (comment.author?.login === "linear[bot]" || comment.author?.type === "Bot") {
8765
+ const body = comment.body || "";
8766
+ const htmlPattern = /<a href="(https:\/\/linear\.app\/[^"]+)">([A-Z]+-\d+)<\/a>/g;
8767
+ let match;
8768
+ while ((match = htmlPattern.exec(body)) !== null) {
8769
+ const ticket = this._parseLinearTicket(match[1], match[2]);
8770
+ if (ticket && !seen.has(`${ticket.name}|${ticket.url}`)) {
8771
+ seen.add(`${ticket.name}|${ticket.url}`);
8772
+ tickets.push(ticket);
8551
8773
  }
8552
8774
  }
8553
- }
8554
- return tickets;
8555
- } catch (error) {
8556
- return [];
8557
- }
8558
- }
8559
- async _calculateChangedLinesFromPR(owner, repo, prNumber) {
8560
- try {
8561
- const diffRes = await this.githubSdk.getPrDiff({
8562
- owner,
8563
- repo,
8564
- pull_number: prNumber
8565
- });
8566
- const diff = z21.string().parse(diffRes.data);
8567
- let added = 0;
8568
- let removed = 0;
8569
- const lines = diff.split("\n");
8570
- for (const line of lines) {
8571
- if (line.startsWith("+") && !line.startsWith("+++")) {
8572
- added++;
8573
- } else if (line.startsWith("-") && !line.startsWith("---")) {
8574
- removed++;
8775
+ const markdownPattern = /\[([A-Z]+-\d+)\]\((https:\/\/linear\.app\/[^)]+)\)/g;
8776
+ while ((match = markdownPattern.exec(body)) !== null) {
8777
+ const ticket = this._parseLinearTicket(match[2], match[1]);
8778
+ if (ticket && !seen.has(`${ticket.name}|${ticket.url}`)) {
8779
+ seen.add(`${ticket.name}|${ticket.url}`);
8780
+ tickets.push(ticket);
8781
+ }
8575
8782
  }
8576
8783
  }
8577
- return { added, removed };
8578
- } catch (error) {
8579
- return { added: 0, removed: 0 };
8580
8784
  }
8785
+ return tickets;
8581
8786
  }
8582
8787
  /**
8583
8788
  * Optimized helper to parse added line numbers from a unified diff patch
@@ -8605,48 +8810,59 @@ var GithubSCMLib = class extends SCMLib {
8605
8810
  return addedLines;
8606
8811
  }
8607
8812
  /**
8608
- * Attribute lines in a single file to their commits using blame
8813
+ * Process blame data for a single file to attribute lines to commits
8814
+ * Uses pre-fetched blame data instead of making API calls
8609
8815
  */
8610
- async _attributeFileLines(file, headRef, prCommitShas) {
8611
- try {
8612
- const blame = await this.getRepoBlameRanges(headRef, file.filename);
8613
- const addedLines = this._parseAddedLinesFromPatch(file.patch);
8614
- const addedLinesSet = new Set(addedLines);
8615
- const fileAttributions = [];
8616
- for (const blameRange of blame) {
8617
- if (!prCommitShas.has(blameRange.commitSha)) {
8618
- continue;
8619
- }
8620
- for (let lineNum = blameRange.startingLine; lineNum <= blameRange.endingLine; lineNum++) {
8621
- if (addedLinesSet.has(lineNum)) {
8622
- fileAttributions.push({
8623
- file: file.filename,
8624
- line: lineNum,
8625
- commitSha: blameRange.commitSha
8626
- });
8627
- }
8816
+ _processFileBlameSafe(file, blameData, prCommitShas) {
8817
+ const addedLines = this._parseAddedLinesFromPatch(file.patch);
8818
+ const addedLinesSet = new Set(addedLines);
8819
+ const fileAttributions = [];
8820
+ for (const blameRange of blameData) {
8821
+ if (!prCommitShas.has(blameRange.commitSha)) {
8822
+ continue;
8823
+ }
8824
+ for (let lineNum = blameRange.startingLine; lineNum <= blameRange.endingLine; lineNum++) {
8825
+ if (addedLinesSet.has(lineNum)) {
8826
+ fileAttributions.push({
8827
+ file: file.filename,
8828
+ line: lineNum,
8829
+ commitSha: blameRange.commitSha
8830
+ });
8628
8831
  }
8629
8832
  }
8630
- return fileAttributions;
8631
- } catch (error) {
8632
- return [];
8633
8833
  }
8834
+ return fileAttributions;
8634
8835
  }
8635
8836
  /**
8636
8837
  * Optimized helper to attribute PR lines to commits using blame API
8637
- * Parallel blame queries for minimal API call time
8838
+ * Batch blame queries for minimal API call time (1 call instead of M calls)
8638
8839
  */
8639
8840
  async _attributeLinesViaBlame(headRef, changedFiles, prCommits) {
8640
8841
  const prCommitShas = new Set(prCommits.map((c) => c.commitSha));
8641
8842
  const filesWithAdditions = changedFiles.filter(
8642
8843
  (file) => file.patch?.includes("\n+")
8643
8844
  );
8644
- const attributions = await Promise.all(
8645
- filesWithAdditions.map(
8646
- (file) => this._attributeFileLines(file, headRef, prCommitShas)
8647
- )
8648
- );
8649
- return attributions.flat();
8845
+ if (filesWithAdditions.length === 0) {
8846
+ return [];
8847
+ }
8848
+ const { owner, repo } = parseGithubOwnerAndRepo(this.url);
8849
+ const blameMap = await this.githubSdk.getBlameBatch({
8850
+ owner,
8851
+ repo,
8852
+ ref: headRef,
8853
+ filePaths: filesWithAdditions.map((f) => f.filename)
8854
+ });
8855
+ const allAttributions = [];
8856
+ for (const file of filesWithAdditions) {
8857
+ const blameData = blameMap.get(file.filename) || [];
8858
+ const fileAttributions = this._processFileBlameSafe(
8859
+ file,
8860
+ blameData,
8861
+ prCommitShas
8862
+ );
8863
+ allAttributions.push(...fileAttributions);
8864
+ }
8865
+ return allAttributions;
8650
8866
  }
8651
8867
  };
8652
8868
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mobbdev",
3
- "version": "1.1.7",
3
+ "version": "1.1.9",
4
4
  "description": "Automated secure code remediation tool",
5
5
  "repository": "git+https://github.com/mobb-dev/bugsy.git",
6
6
  "main": "dist/index.mjs",