mobbdev 1.1.7 → 1.1.8

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.
@@ -51,26 +51,18 @@ declare const PromptItemZ: z.ZodObject<{
51
51
  name: string;
52
52
  parameters: string;
53
53
  result: string;
54
- accepted?: boolean | undefined;
55
54
  rawArguments?: string | undefined;
55
+ accepted?: boolean | undefined;
56
56
  }, {
57
57
  name: string;
58
58
  parameters: string;
59
59
  result: string;
60
- accepted?: boolean | undefined;
61
60
  rawArguments?: string | undefined;
61
+ accepted?: boolean | undefined;
62
62
  }>>;
63
63
  }, "strip", z.ZodTypeAny, {
64
64
  type: "USER_PROMPT" | "AI_RESPONSE" | "TOOL_EXECUTION" | "AI_THINKING";
65
- tool?: {
66
- name: string;
67
- parameters: string;
68
- result: string;
69
- accepted?: boolean | undefined;
70
- rawArguments?: string | undefined;
71
- } | undefined;
72
65
  date?: Date | undefined;
73
- text?: string | undefined;
74
66
  attachedFiles?: {
75
67
  relativePath: string;
76
68
  startLine?: number | undefined;
@@ -79,17 +71,17 @@ declare const PromptItemZ: z.ZodObject<{
79
71
  inputCount: number;
80
72
  outputCount: number;
81
73
  } | undefined;
82
- }, {
83
- type: "USER_PROMPT" | "AI_RESPONSE" | "TOOL_EXECUTION" | "AI_THINKING";
74
+ text?: string | undefined;
84
75
  tool?: {
85
76
  name: string;
86
77
  parameters: string;
87
78
  result: string;
88
- accepted?: boolean | undefined;
89
79
  rawArguments?: string | undefined;
80
+ accepted?: boolean | undefined;
90
81
  } | undefined;
82
+ }, {
83
+ type: "USER_PROMPT" | "AI_RESPONSE" | "TOOL_EXECUTION" | "AI_THINKING";
91
84
  date?: Date | undefined;
92
- text?: string | undefined;
93
85
  attachedFiles?: {
94
86
  relativePath: string;
95
87
  startLine?: number | undefined;
@@ -98,6 +90,14 @@ declare const PromptItemZ: z.ZodObject<{
98
90
  inputCount: number;
99
91
  outputCount: number;
100
92
  } | undefined;
93
+ text?: string | undefined;
94
+ tool?: {
95
+ name: string;
96
+ parameters: string;
97
+ result: string;
98
+ rawArguments?: string | undefined;
99
+ accepted?: boolean | undefined;
100
+ } | undefined;
101
101
  }>;
102
102
  type PromptItem = z.infer<typeof PromptItemZ>;
103
103
  declare const PromptItemArrayZ: z.ZodArray<z.ZodObject<{
@@ -134,26 +134,18 @@ declare const PromptItemArrayZ: z.ZodArray<z.ZodObject<{
134
134
  name: string;
135
135
  parameters: string;
136
136
  result: string;
137
- accepted?: boolean | undefined;
138
137
  rawArguments?: string | undefined;
138
+ accepted?: boolean | undefined;
139
139
  }, {
140
140
  name: string;
141
141
  parameters: string;
142
142
  result: string;
143
- accepted?: boolean | undefined;
144
143
  rawArguments?: string | undefined;
144
+ accepted?: boolean | undefined;
145
145
  }>>;
146
146
  }, "strip", z.ZodTypeAny, {
147
147
  type: "USER_PROMPT" | "AI_RESPONSE" | "TOOL_EXECUTION" | "AI_THINKING";
148
- tool?: {
149
- name: string;
150
- parameters: string;
151
- result: string;
152
- accepted?: boolean | undefined;
153
- rawArguments?: string | undefined;
154
- } | undefined;
155
148
  date?: Date | undefined;
156
- text?: string | undefined;
157
149
  attachedFiles?: {
158
150
  relativePath: string;
159
151
  startLine?: number | undefined;
@@ -162,17 +154,17 @@ declare const PromptItemArrayZ: z.ZodArray<z.ZodObject<{
162
154
  inputCount: number;
163
155
  outputCount: number;
164
156
  } | undefined;
165
- }, {
166
- type: "USER_PROMPT" | "AI_RESPONSE" | "TOOL_EXECUTION" | "AI_THINKING";
157
+ text?: string | undefined;
167
158
  tool?: {
168
159
  name: string;
169
160
  parameters: string;
170
161
  result: string;
171
- accepted?: boolean | undefined;
172
162
  rawArguments?: string | undefined;
163
+ accepted?: boolean | undefined;
173
164
  } | undefined;
165
+ }, {
166
+ type: "USER_PROMPT" | "AI_RESPONSE" | "TOOL_EXECUTION" | "AI_THINKING";
174
167
  date?: Date | undefined;
175
- text?: string | undefined;
176
168
  attachedFiles?: {
177
169
  relativePath: string;
178
170
  startLine?: number | undefined;
@@ -181,6 +173,14 @@ declare const PromptItemArrayZ: z.ZodArray<z.ZodObject<{
181
173
  inputCount: number;
182
174
  outputCount: number;
183
175
  } | undefined;
176
+ text?: string | undefined;
177
+ tool?: {
178
+ name: string;
179
+ parameters: string;
180
+ result: string;
181
+ rawArguments?: string | undefined;
182
+ accepted?: boolean | undefined;
183
+ } | undefined;
184
184
  }>, "many">;
185
185
  type PromptItemArray = z.infer<typeof PromptItemArrayZ>;
186
186
  type UploadAiBlameOptions = {
@@ -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,125 @@ 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
+ author {
8269
+ user {
8270
+ name
8271
+ login
8272
+ email
8273
+ }
8274
+ }
8275
+ }
8276
+ }
8277
+ }
8278
+ }
8279
+ }`,
8280
+ extractResult: (data) => {
8281
+ const fileData = data;
8282
+ if (fileData.blame?.ranges) {
8283
+ return fileData.blame.ranges.map((range) => ({
8284
+ startingLine: range.startingLine,
8285
+ endingLine: range.endingLine,
8286
+ commitSha: range.commit.oid,
8287
+ email: range.commit.author.user?.email || "",
8288
+ name: range.commit.author.user?.name || "",
8289
+ login: range.commit.author.user?.login || ""
8290
+ }));
8291
+ }
8292
+ return void 0;
8293
+ }
8294
+ });
8295
+ },
8296
+ /**
8297
+ * Batch fetch commit timestamps for multiple commits via GraphQL.
8298
+ * Uses GITHUB_GRAPHQL_FRAGMENTS.COMMIT_TIMESTAMP for the field selection.
8299
+ */
8300
+ async getCommitsBatch(params2) {
8301
+ return executeBatchGraphQL(octokit, params2.owner, params2.repo, {
8302
+ items: params2.commitShas,
8303
+ aliasPrefix: "commit",
8304
+ buildFragment: (sha, index) => `
8305
+ commit${index}: object(oid: "${sha}") {
8306
+ ... on Commit {
8307
+ ${GITHUB_GRAPHQL_FRAGMENTS.COMMIT_TIMESTAMP}
8308
+ }
8309
+ }`,
8310
+ extractResult: (data) => {
8311
+ const commitData = data;
8312
+ if (commitData.oid && commitData.committedDate) {
8313
+ return {
8314
+ sha: commitData.oid,
8315
+ timestamp: new Date(commitData.committedDate)
8316
+ };
8317
+ }
8318
+ return void 0;
8319
+ }
8320
+ });
8117
8321
  }
8118
8322
  };
8119
8323
  }
@@ -8382,7 +8586,7 @@ var GithubSCMLib = class extends SCMLib {
8382
8586
  comment_id: commentId
8383
8587
  });
8384
8588
  }
8385
- async getCommitDiff(commitSha) {
8589
+ async getCommitDiff(commitSha, options) {
8386
8590
  this._validateAccessTokenAndUrl();
8387
8591
  const { owner, repo } = parseGithubOwnerAndRepo(this.url);
8388
8592
  const { commit, diff } = await this.githubSdk.getCommitWithDiff({
@@ -8393,43 +8597,49 @@ var GithubSCMLib = class extends SCMLib {
8393
8597
  const commitTimestamp = commit.commit.committer?.date ? new Date(commit.commit.committer.date) : new Date(commit.commit.author?.date || Date.now());
8394
8598
  let parentCommits;
8395
8599
  if (commit.parents && commit.parents.length > 0) {
8600
+ if (options?.parentCommitTimestamps) {
8601
+ parentCommits = commit.parents.map((p) => options.parentCommitTimestamps.get(p.sha)).filter((p) => p !== void 0);
8602
+ } else {
8603
+ try {
8604
+ parentCommits = await Promise.all(
8605
+ commit.parents.map(async (parent) => {
8606
+ const parentCommit = await this.githubSdk.getCommit({
8607
+ owner,
8608
+ repo,
8609
+ commitSha: parent.sha
8610
+ });
8611
+ const parentTimestamp = parentCommit.data.committer?.date ? new Date(parentCommit.data.committer.date) : new Date(Date.now());
8612
+ return {
8613
+ sha: parent.sha,
8614
+ timestamp: parentTimestamp
8615
+ };
8616
+ })
8617
+ );
8618
+ } catch (error) {
8619
+ console.error("Failed to fetch parent commit timestamps", {
8620
+ error,
8621
+ commitSha,
8622
+ owner,
8623
+ repo
8624
+ });
8625
+ parentCommits = void 0;
8626
+ }
8627
+ }
8628
+ }
8629
+ let repositoryCreatedAt = options?.repositoryCreatedAt;
8630
+ if (repositoryCreatedAt === void 0) {
8396
8631
  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
- );
8632
+ const repoData = await this.githubSdk.getRepository({ owner, repo });
8633
+ repositoryCreatedAt = repoData.data.created_at ? new Date(repoData.data.created_at) : void 0;
8411
8634
  } catch (error) {
8412
- console.error("Failed to fetch parent commit timestamps", {
8635
+ console.error("Failed to fetch repository creation date", {
8413
8636
  error,
8414
- commitSha,
8415
8637
  owner,
8416
8638
  repo
8417
8639
  });
8418
- parentCommits = void 0;
8640
+ repositoryCreatedAt = void 0;
8419
8641
  }
8420
8642
  }
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
8643
  return {
8434
8644
  diff,
8435
8645
  commitTimestamp,
@@ -8445,15 +8655,37 @@ var GithubSCMLib = class extends SCMLib {
8445
8655
  this._validateAccessTokenAndUrl();
8446
8656
  const { owner, repo } = parseGithubOwnerAndRepo(this.url);
8447
8657
  const prNumber = Number(submitRequestId);
8448
- const [prRes, commitsRes, filesRes] = await Promise.all([
8658
+ const [prRes, commitsRes, filesRes, repoData] = await Promise.all([
8449
8659
  this.githubSdk.getPr({ owner, repo, pull_number: prNumber }),
8450
8660
  this.githubSdk.getPrCommits({ owner, repo, pull_number: prNumber }),
8451
- this.githubSdk.listPRFiles({ owner, repo, pull_number: prNumber })
8661
+ this.githubSdk.listPRFiles({ owner, repo, pull_number: prNumber }),
8662
+ this.githubSdk.getRepository({ owner, repo })
8452
8663
  ]);
8453
8664
  const pr = prRes.data;
8454
- const prDiff = await this.getPrDiff({ pull_number: prNumber });
8665
+ const repositoryCreatedAt = repoData.data.created_at ? new Date(repoData.data.created_at) : void 0;
8666
+ const allParentShas = /* @__PURE__ */ new Set();
8667
+ for (const commit of commitsRes.data) {
8668
+ if (commit.parents) {
8669
+ for (const parent of commit.parents) {
8670
+ allParentShas.add(parent.sha);
8671
+ }
8672
+ }
8673
+ }
8674
+ const [parentCommitTimestamps, prDiff] = await Promise.all([
8675
+ this.githubSdk.getCommitsBatch({
8676
+ owner,
8677
+ repo,
8678
+ commitShas: Array.from(allParentShas)
8679
+ }),
8680
+ this.getPrDiff({ pull_number: prNumber })
8681
+ ]);
8455
8682
  const commits = await Promise.all(
8456
- commitsRes.data.map((commit) => this.getCommitDiff(commit.sha))
8683
+ commitsRes.data.map(
8684
+ (commit) => this.getCommitDiff(commit.sha, {
8685
+ repositoryCreatedAt,
8686
+ parentCommitTimestamps
8687
+ })
8688
+ )
8457
8689
  );
8458
8690
  const diffLines = await this._attributeLinesViaBlame(
8459
8691
  pr.head.ref,
@@ -8480,104 +8712,83 @@ var GithubSCMLib = class extends SCMLib {
8480
8712
  this._validateAccessToken();
8481
8713
  const { owner, repo } = parseGithubOwnerAndRepo(repoUrl);
8482
8714
  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
- );
8715
+ const prNumbers = pullsRes.data.map((pr) => pr.number);
8716
+ const [additionsDeletionsMap, commentsMap] = await Promise.all([
8717
+ this.githubSdk.getPrAdditionsDeletionsBatch({ owner, repo, prNumbers }),
8718
+ this.githubSdk.getPrCommentsBatch({ owner, repo, prNumbers })
8719
+ ]);
8720
+ const submitRequests = pullsRes.data.map((pr) => {
8721
+ let status = "open";
8722
+ if (pr.state === "closed") {
8723
+ status = pr.merged_at ? "merged" : "closed";
8724
+ } else if (pr.draft) {
8725
+ status = "draft";
8726
+ }
8727
+ const changedLinesData = additionsDeletionsMap.get(pr.number);
8728
+ const changedLines = changedLinesData ? {
8729
+ added: changedLinesData.additions,
8730
+ removed: changedLinesData.deletions
8731
+ } : { added: 0, removed: 0 };
8732
+ const comments = commentsMap.get(pr.number) || [];
8733
+ const tickets = this._extractLinearTicketsFromComments(comments);
8734
+ return {
8735
+ submitRequestId: String(pr.number),
8736
+ submitRequestNumber: pr.number,
8737
+ title: pr.title,
8738
+ status,
8739
+ sourceBranch: pr.head.ref,
8740
+ targetBranch: pr.base.ref,
8741
+ authorName: pr.user?.name || pr.user?.login,
8742
+ authorEmail: pr.user?.email || void 0,
8743
+ createdAt: new Date(pr.created_at),
8744
+ updatedAt: new Date(pr.updated_at),
8745
+ description: pr.body || void 0,
8746
+ tickets,
8747
+ changedLines
8748
+ };
8749
+ });
8512
8750
  return submitRequests;
8513
8751
  }
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 });
8752
+ /**
8753
+ * Parse a Linear ticket from URL and name
8754
+ * Returns null if invalid or missing data
8755
+ */
8756
+ _parseLinearTicket(url, name) {
8757
+ if (!name || !url) return null;
8758
+ const urlParts = url.split("/");
8759
+ const titleSlug = urlParts[urlParts.length - 1] || "";
8760
+ const title = titleSlug.replace(/-/g, " ");
8761
+ return { name, title, url };
8762
+ }
8763
+ /**
8764
+ * Extract Linear ticket links from pre-fetched comments (pure function, no API calls)
8765
+ */
8766
+ _extractLinearTicketsFromComments(comments) {
8767
+ const tickets = [];
8768
+ const seen = /* @__PURE__ */ new Set();
8769
+ for (const comment of comments) {
8770
+ if (comment.author?.login === "linear[bot]" || comment.author?.type === "Bot") {
8771
+ const body = comment.body || "";
8772
+ const htmlPattern = /<a href="(https:\/\/linear\.app\/[^"]+)">([A-Z]+-\d+)<\/a>/g;
8773
+ let match;
8774
+ while ((match = htmlPattern.exec(body)) !== null) {
8775
+ const ticket = this._parseLinearTicket(match[1], match[2]);
8776
+ if (ticket && !seen.has(`${ticket.name}|${ticket.url}`)) {
8777
+ seen.add(`${ticket.name}|${ticket.url}`);
8778
+ tickets.push(ticket);
8551
8779
  }
8552
8780
  }
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++;
8781
+ const markdownPattern = /\[([A-Z]+-\d+)\]\((https:\/\/linear\.app\/[^)]+)\)/g;
8782
+ while ((match = markdownPattern.exec(body)) !== null) {
8783
+ const ticket = this._parseLinearTicket(match[2], match[1]);
8784
+ if (ticket && !seen.has(`${ticket.name}|${ticket.url}`)) {
8785
+ seen.add(`${ticket.name}|${ticket.url}`);
8786
+ tickets.push(ticket);
8787
+ }
8575
8788
  }
8576
8789
  }
8577
- return { added, removed };
8578
- } catch (error) {
8579
- return { added: 0, removed: 0 };
8580
8790
  }
8791
+ return tickets;
8581
8792
  }
8582
8793
  /**
8583
8794
  * Optimized helper to parse added line numbers from a unified diff patch
@@ -8605,48 +8816,59 @@ var GithubSCMLib = class extends SCMLib {
8605
8816
  return addedLines;
8606
8817
  }
8607
8818
  /**
8608
- * Attribute lines in a single file to their commits using blame
8819
+ * Process blame data for a single file to attribute lines to commits
8820
+ * Uses pre-fetched blame data instead of making API calls
8609
8821
  */
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
- }
8822
+ _processFileBlameSafe(file, blameData, prCommitShas) {
8823
+ const addedLines = this._parseAddedLinesFromPatch(file.patch);
8824
+ const addedLinesSet = new Set(addedLines);
8825
+ const fileAttributions = [];
8826
+ for (const blameRange of blameData) {
8827
+ if (!prCommitShas.has(blameRange.commitSha)) {
8828
+ continue;
8829
+ }
8830
+ for (let lineNum = blameRange.startingLine; lineNum <= blameRange.endingLine; lineNum++) {
8831
+ if (addedLinesSet.has(lineNum)) {
8832
+ fileAttributions.push({
8833
+ file: file.filename,
8834
+ line: lineNum,
8835
+ commitSha: blameRange.commitSha
8836
+ });
8628
8837
  }
8629
8838
  }
8630
- return fileAttributions;
8631
- } catch (error) {
8632
- return [];
8633
8839
  }
8840
+ return fileAttributions;
8634
8841
  }
8635
8842
  /**
8636
8843
  * Optimized helper to attribute PR lines to commits using blame API
8637
- * Parallel blame queries for minimal API call time
8844
+ * Batch blame queries for minimal API call time (1 call instead of M calls)
8638
8845
  */
8639
8846
  async _attributeLinesViaBlame(headRef, changedFiles, prCommits) {
8640
8847
  const prCommitShas = new Set(prCommits.map((c) => c.commitSha));
8641
8848
  const filesWithAdditions = changedFiles.filter(
8642
8849
  (file) => file.patch?.includes("\n+")
8643
8850
  );
8644
- const attributions = await Promise.all(
8645
- filesWithAdditions.map(
8646
- (file) => this._attributeFileLines(file, headRef, prCommitShas)
8647
- )
8648
- );
8649
- return attributions.flat();
8851
+ if (filesWithAdditions.length === 0) {
8852
+ return [];
8853
+ }
8854
+ const { owner, repo } = parseGithubOwnerAndRepo(this.url);
8855
+ const blameMap = await this.githubSdk.getBlameBatch({
8856
+ owner,
8857
+ repo,
8858
+ ref: headRef,
8859
+ filePaths: filesWithAdditions.map((f) => f.filename)
8860
+ });
8861
+ const allAttributions = [];
8862
+ for (const file of filesWithAdditions) {
8863
+ const blameData = blameMap.get(file.filename) || [];
8864
+ const fileAttributions = this._processFileBlameSafe(
8865
+ file,
8866
+ blameData,
8867
+ prCommitShas
8868
+ );
8869
+ allAttributions.push(...fileAttributions);
8870
+ }
8871
+ return allAttributions;
8650
8872
  }
8651
8873
  };
8652
8874
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mobbdev",
3
- "version": "1.1.7",
3
+ "version": "1.1.8",
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",