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.
- package/dist/args/commands/upload_ai_blame.mjs +1 -1
- package/dist/index.mjs +371 -155
- package/package.json +1 -1
|
@@ -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
|
-
|
|
8398
|
-
|
|
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
|
|
8629
|
+
console.error("Failed to fetch repository creation date", {
|
|
8413
8630
|
error,
|
|
8414
|
-
commitSha,
|
|
8415
8631
|
owner,
|
|
8416
8632
|
repo
|
|
8417
8633
|
});
|
|
8418
|
-
|
|
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
|
|
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(
|
|
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
|
|
8484
|
-
|
|
8485
|
-
|
|
8486
|
-
|
|
8487
|
-
|
|
8488
|
-
|
|
8489
|
-
|
|
8490
|
-
|
|
8491
|
-
|
|
8492
|
-
|
|
8493
|
-
|
|
8494
|
-
|
|
8495
|
-
|
|
8496
|
-
|
|
8497
|
-
|
|
8498
|
-
|
|
8499
|
-
|
|
8500
|
-
|
|
8501
|
-
|
|
8502
|
-
|
|
8503
|
-
|
|
8504
|
-
|
|
8505
|
-
|
|
8506
|
-
|
|
8507
|
-
|
|
8508
|
-
|
|
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
|
-
|
|
8515
|
-
|
|
8516
|
-
|
|
8517
|
-
|
|
8518
|
-
|
|
8519
|
-
|
|
8520
|
-
|
|
8521
|
-
|
|
8522
|
-
|
|
8523
|
-
|
|
8524
|
-
|
|
8525
|
-
|
|
8526
|
-
|
|
8527
|
-
|
|
8528
|
-
|
|
8529
|
-
|
|
8530
|
-
|
|
8531
|
-
|
|
8532
|
-
|
|
8533
|
-
|
|
8534
|
-
|
|
8535
|
-
|
|
8536
|
-
|
|
8537
|
-
const
|
|
8538
|
-
|
|
8539
|
-
|
|
8540
|
-
|
|
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
|
-
|
|
8555
|
-
|
|
8556
|
-
|
|
8557
|
-
|
|
8558
|
-
|
|
8559
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
8611
|
-
|
|
8612
|
-
|
|
8613
|
-
|
|
8614
|
-
|
|
8615
|
-
|
|
8616
|
-
|
|
8617
|
-
|
|
8618
|
-
|
|
8619
|
-
|
|
8620
|
-
|
|
8621
|
-
|
|
8622
|
-
|
|
8623
|
-
|
|
8624
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
8645
|
-
|
|
8646
|
-
|
|
8647
|
-
|
|
8648
|
-
|
|
8649
|
-
|
|
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
|
|