mobbdev 1.4.36 → 1.4.41
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 +0 -13
- package/dist/index.mjs +375 -65
- package/package.json +1 -1
|
@@ -342,10 +342,6 @@ var init_client_generates = __esm({
|
|
|
342
342
|
inputType
|
|
343
343
|
options
|
|
344
344
|
index
|
|
345
|
-
extraContext {
|
|
346
|
-
key
|
|
347
|
-
value
|
|
348
|
-
}
|
|
349
345
|
content
|
|
350
346
|
description
|
|
351
347
|
guidance
|
|
@@ -583,10 +579,6 @@ var init_client_generates = __esm({
|
|
|
583
579
|
patchOriginalEncodingBase64
|
|
584
580
|
questions {
|
|
585
581
|
defaultValue
|
|
586
|
-
extraContext {
|
|
587
|
-
key
|
|
588
|
-
value
|
|
589
|
-
}
|
|
590
582
|
index
|
|
591
583
|
inputType
|
|
592
584
|
key
|
|
@@ -1090,10 +1082,6 @@ var init_client_generates = __esm({
|
|
|
1090
1082
|
inputType
|
|
1091
1083
|
options
|
|
1092
1084
|
index
|
|
1093
|
-
extraContext {
|
|
1094
|
-
key
|
|
1095
|
-
value
|
|
1096
|
-
}
|
|
1097
1085
|
content
|
|
1098
1086
|
description
|
|
1099
1087
|
guidance
|
|
@@ -1352,7 +1340,6 @@ var init_fix = __esm({
|
|
|
1352
1340
|
index: z2.number(),
|
|
1353
1341
|
defaultValue: z2.string(),
|
|
1354
1342
|
value: z2.string().nullable(),
|
|
1355
|
-
extraContext: z2.array(ExtraContextInternalZ),
|
|
1356
1343
|
inputType: z2.nativeEnum(FixQuestionInputType),
|
|
1357
1344
|
options: z2.array(z2.string()),
|
|
1358
1345
|
// E-2015: analyzer-served question text. default('') so a query that
|
package/dist/index.mjs
CHANGED
|
@@ -342,10 +342,6 @@ var init_client_generates = __esm({
|
|
|
342
342
|
inputType
|
|
343
343
|
options
|
|
344
344
|
index
|
|
345
|
-
extraContext {
|
|
346
|
-
key
|
|
347
|
-
value
|
|
348
|
-
}
|
|
349
345
|
content
|
|
350
346
|
description
|
|
351
347
|
guidance
|
|
@@ -583,10 +579,6 @@ var init_client_generates = __esm({
|
|
|
583
579
|
patchOriginalEncodingBase64
|
|
584
580
|
questions {
|
|
585
581
|
defaultValue
|
|
586
|
-
extraContext {
|
|
587
|
-
key
|
|
588
|
-
value
|
|
589
|
-
}
|
|
590
582
|
index
|
|
591
583
|
inputType
|
|
592
584
|
key
|
|
@@ -1090,10 +1082,6 @@ var init_client_generates = __esm({
|
|
|
1090
1082
|
inputType
|
|
1091
1083
|
options
|
|
1092
1084
|
index
|
|
1093
|
-
extraContext {
|
|
1094
|
-
key
|
|
1095
|
-
value
|
|
1096
|
-
}
|
|
1097
1085
|
content
|
|
1098
1086
|
description
|
|
1099
1087
|
guidance
|
|
@@ -1454,7 +1442,6 @@ var init_fix = __esm({
|
|
|
1454
1442
|
index: z3.number(),
|
|
1455
1443
|
defaultValue: z3.string(),
|
|
1456
1444
|
value: z3.string().nullable(),
|
|
1457
|
-
extraContext: z3.array(ExtraContextInternalZ),
|
|
1458
1445
|
inputType: z3.nativeEnum(FixQuestionInputType),
|
|
1459
1446
|
options: z3.array(z3.string()),
|
|
1460
1447
|
// E-2015: analyzer-served question text. default('') so a query that
|
|
@@ -4744,22 +4731,21 @@ function getScmConfig({
|
|
|
4744
4731
|
url,
|
|
4745
4732
|
scmConfigs,
|
|
4746
4733
|
brokerHosts,
|
|
4747
|
-
includeOrgTokens = true
|
|
4734
|
+
includeOrgTokens = true,
|
|
4735
|
+
allowRelaxedProtocolMatch = false
|
|
4748
4736
|
}) {
|
|
4749
4737
|
const urlObject = new URL(url);
|
|
4750
|
-
const
|
|
4738
|
+
const matchesHostname = (configUrl) => urlObject.hostname.toLowerCase() === configUrl.hostname.toLowerCase() || urlObject.hostname.toLowerCase().endsWith(".visualstudio.com") && configUrl.hostname.toLowerCase() === "dev.azure.com";
|
|
4739
|
+
const exactScmConfigs = scmConfigs.filter((scm) => {
|
|
4751
4740
|
const configUrl = new URL(scm.scmUrl);
|
|
4752
|
-
return (
|
|
4753
|
-
//if we the user does an ADO oauth flow then the token is saved for dev.azure.com but
|
|
4754
|
-
//sometimes the user uses the url dev.azure.com and sometimes the url visualstudio.com
|
|
4755
|
-
//so we need to check both
|
|
4756
|
-
(urlObject.hostname.toLowerCase() === configUrl.hostname.toLowerCase() || urlObject.hostname.toLowerCase().endsWith(".visualstudio.com") && configUrl.hostname.toLowerCase() === "dev.azure.com") && urlObject.protocol === configUrl.protocol && urlObject.port === configUrl.port
|
|
4757
|
-
);
|
|
4741
|
+
return matchesHostname(configUrl) && urlObject.protocol === configUrl.protocol && urlObject.port === configUrl.port;
|
|
4758
4742
|
});
|
|
4759
4743
|
const filteredBrokerHosts = brokerHosts.filter((broker) => {
|
|
4760
4744
|
const urlObject2 = new URL(url);
|
|
4761
4745
|
return urlObject2.hostname.toLowerCase() === broker.realDomain.toLowerCase();
|
|
4762
4746
|
});
|
|
4747
|
+
const brokerBacked = filteredBrokerHosts.length > 0;
|
|
4748
|
+
const filteredScmConfigs = exactScmConfigs.length > 0 ? exactScmConfigs : allowRelaxedProtocolMatch && brokerBacked ? scmConfigs.filter((scm) => matchesHostname(new URL(scm.scmUrl))) : [];
|
|
4763
4749
|
const virtualDomain = filteredBrokerHosts[0]?.virtualDomain;
|
|
4764
4750
|
const virtualUrl = virtualDomain ? `https://${virtualDomain}${urlObject.pathname}${urlObject.search}` : void 0;
|
|
4765
4751
|
const matched = filteredScmConfigs.find((scm) => {
|
|
@@ -5373,6 +5359,59 @@ async function getAdoSdk(params) {
|
|
|
5373
5359
|
);
|
|
5374
5360
|
return commits;
|
|
5375
5361
|
},
|
|
5362
|
+
// Distinct commit authors since `since`, paged with $skip (the plain
|
|
5363
|
+
// getAdoRecentCommits above fetches only the first 100 — no pagination — so
|
|
5364
|
+
// it under-reports 90-day contributors on any active repo). Accumulates one
|
|
5365
|
+
// row per author as pages arrive → memory bounded by author count, uncapped
|
|
5366
|
+
// walk (the per-repo sync timeout bounds pathological repos). ADO commits
|
|
5367
|
+
// carry no stable author account id here, so authors are keyed by email.
|
|
5368
|
+
async getAdoRecentCommitAuthors({
|
|
5369
|
+
repoUrl,
|
|
5370
|
+
since
|
|
5371
|
+
}) {
|
|
5372
|
+
const { repo, projectName } = parseAdoOwnerAndRepo(repoUrl);
|
|
5373
|
+
const git = await api2.getGitApi();
|
|
5374
|
+
const repository = await git.getRepository(
|
|
5375
|
+
decodeURI(repo),
|
|
5376
|
+
projectName ? decodeURI(projectName) : void 0
|
|
5377
|
+
);
|
|
5378
|
+
const defaultBranch = repository.defaultBranch?.replace("refs/heads/", "");
|
|
5379
|
+
const byEmail = /* @__PURE__ */ new Map();
|
|
5380
|
+
const PAGE = 100;
|
|
5381
|
+
let skip = 0;
|
|
5382
|
+
while (true) {
|
|
5383
|
+
const page = await git.getCommits(
|
|
5384
|
+
repo,
|
|
5385
|
+
{
|
|
5386
|
+
fromDate: since,
|
|
5387
|
+
itemVersion: defaultBranch ? { version: defaultBranch } : void 0,
|
|
5388
|
+
$top: PAGE,
|
|
5389
|
+
$skip: skip
|
|
5390
|
+
},
|
|
5391
|
+
projectName
|
|
5392
|
+
);
|
|
5393
|
+
if (!page || page.length === 0) break;
|
|
5394
|
+
for (const c of page) {
|
|
5395
|
+
const email = c.author?.email;
|
|
5396
|
+
if (!email) continue;
|
|
5397
|
+
const date = c.author?.date ? new Date(c.author.date).toISOString() : since;
|
|
5398
|
+
const key = email.toLowerCase();
|
|
5399
|
+
const prev = byEmail.get(key);
|
|
5400
|
+
if (!prev || new Date(date).getTime() > new Date(prev.date).getTime()) {
|
|
5401
|
+
byEmail.set(key, {
|
|
5402
|
+
email: key,
|
|
5403
|
+
name: c.author?.name ?? null,
|
|
5404
|
+
date,
|
|
5405
|
+
externalId: null,
|
|
5406
|
+
isBot: false
|
|
5407
|
+
});
|
|
5408
|
+
}
|
|
5409
|
+
}
|
|
5410
|
+
if (page.length < PAGE) break;
|
|
5411
|
+
skip += PAGE;
|
|
5412
|
+
}
|
|
5413
|
+
return [...byEmail.values()];
|
|
5414
|
+
},
|
|
5376
5415
|
async getAdoPrCommits({
|
|
5377
5416
|
repoUrl,
|
|
5378
5417
|
prNumber
|
|
@@ -6130,6 +6169,11 @@ var AdoSCMLib = class extends SCMLib {
|
|
|
6130
6169
|
}))
|
|
6131
6170
|
};
|
|
6132
6171
|
}
|
|
6172
|
+
async getRecentCommitAuthors(since) {
|
|
6173
|
+
this._validateAccessTokenAndUrl();
|
|
6174
|
+
const adoSdk = await this.getAdoSdk();
|
|
6175
|
+
return adoSdk.getAdoRecentCommitAuthors({ repoUrl: this.url, since });
|
|
6176
|
+
}
|
|
6133
6177
|
async getRateLimitStatus() {
|
|
6134
6178
|
return {
|
|
6135
6179
|
remaining: 1e4,
|
|
@@ -6168,6 +6212,11 @@ var BitbucketAuthResultZ = z15.object({
|
|
|
6168
6212
|
|
|
6169
6213
|
// src/features/analysis/scm/bitbucket/bitbucket.ts
|
|
6170
6214
|
var debug3 = Debug2("scm:bitbucket");
|
|
6215
|
+
function isBitbucketPermissionDenied(e) {
|
|
6216
|
+
const status = e?.status;
|
|
6217
|
+
const msg = (e instanceof Error ? e.message : String(e)).toLowerCase();
|
|
6218
|
+
return status === 403 || /forbidden|insufficient/.test(msg);
|
|
6219
|
+
}
|
|
6171
6220
|
var BITBUCKET_HOSTNAME = "bitbucket.org";
|
|
6172
6221
|
var TokenExpiredErrorZ = z16.object({
|
|
6173
6222
|
status: z16.number(),
|
|
@@ -6428,21 +6477,28 @@ function getBitbucketSdk(params) {
|
|
|
6428
6477
|
return res.data;
|
|
6429
6478
|
},
|
|
6430
6479
|
async getWorkspaceMembers(params2) {
|
|
6431
|
-
|
|
6432
|
-
|
|
6433
|
-
|
|
6434
|
-
|
|
6435
|
-
|
|
6436
|
-
|
|
6437
|
-
|
|
6438
|
-
|
|
6439
|
-
|
|
6440
|
-
|
|
6441
|
-
|
|
6442
|
-
|
|
6443
|
-
|
|
6480
|
+
try {
|
|
6481
|
+
const allMembers = [];
|
|
6482
|
+
let hasMore = true;
|
|
6483
|
+
let page = 1;
|
|
6484
|
+
while (hasMore) {
|
|
6485
|
+
const res = await bitbucketClient.workspaces.getMembersForWorkspace({
|
|
6486
|
+
workspace: params2.workspace,
|
|
6487
|
+
page: String(page),
|
|
6488
|
+
pagelen: 100
|
|
6489
|
+
});
|
|
6490
|
+
const values = res.data.values ?? [];
|
|
6491
|
+
allMembers.push(...values);
|
|
6492
|
+
hasMore = Boolean(res.data.next) && values.length > 0;
|
|
6493
|
+
page++;
|
|
6494
|
+
}
|
|
6495
|
+
return allMembers;
|
|
6496
|
+
} catch (e) {
|
|
6497
|
+
if (isBitbucketPermissionDenied(e)) {
|
|
6498
|
+
return [];
|
|
6499
|
+
}
|
|
6500
|
+
throw e;
|
|
6444
6501
|
}
|
|
6445
|
-
return allMembers;
|
|
6446
6502
|
},
|
|
6447
6503
|
async getCurrentUserWithEmail() {
|
|
6448
6504
|
try {
|
|
@@ -6489,15 +6545,74 @@ function getBitbucketSdk(params) {
|
|
|
6489
6545
|
}
|
|
6490
6546
|
return commits;
|
|
6491
6547
|
},
|
|
6548
|
+
// Distinct authors of commits in the window (since), streamed and
|
|
6549
|
+
// window-bounded by date rather than a fixed page cap, so the 90-day count
|
|
6550
|
+
// is exact even on a busy repo. Accumulates one row per author (latest date)
|
|
6551
|
+
// as pages arrive → memory bounded by author count. Errors propagate for
|
|
6552
|
+
// classification (the repo is the read-commits population source).
|
|
6553
|
+
async streamRecentCommitAuthors(params2) {
|
|
6554
|
+
const sinceMs = new Date(params2.since).getTime();
|
|
6555
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
6556
|
+
let page = 1;
|
|
6557
|
+
const HARD_PAGE_CEILING = 1e4;
|
|
6558
|
+
while (page <= HARD_PAGE_CEILING) {
|
|
6559
|
+
const res = await bitbucketClient.repositories.listCommits({
|
|
6560
|
+
repo_slug: params2.repo_slug,
|
|
6561
|
+
workspace: params2.workspace,
|
|
6562
|
+
page: String(page),
|
|
6563
|
+
pagelen: 100
|
|
6564
|
+
});
|
|
6565
|
+
const data = res.data;
|
|
6566
|
+
const commits = data?.values ?? [];
|
|
6567
|
+
if (commits.length === 0) break;
|
|
6568
|
+
let sawInWindow = false;
|
|
6569
|
+
for (const commit of commits) {
|
|
6570
|
+
const record = commit;
|
|
6571
|
+
const dateStr = record["date"];
|
|
6572
|
+
if (dateStr && new Date(dateStr).getTime() < sinceMs) {
|
|
6573
|
+
continue;
|
|
6574
|
+
}
|
|
6575
|
+
sawInWindow = true;
|
|
6576
|
+
const author = record["author"];
|
|
6577
|
+
if (!author?.raw) continue;
|
|
6578
|
+
const match = author.raw.match(/^(.+?)\s*<([^>]+)>/);
|
|
6579
|
+
if (!match) continue;
|
|
6580
|
+
const [, name, email] = match;
|
|
6581
|
+
if (!email) continue;
|
|
6582
|
+
const externalId = author.user?.account_id ?? null;
|
|
6583
|
+
const date = dateStr ?? params2.since;
|
|
6584
|
+
const key = externalId ?? email.toLowerCase();
|
|
6585
|
+
const prev = byKey.get(key);
|
|
6586
|
+
if (!prev || new Date(date).getTime() > new Date(prev.date).getTime()) {
|
|
6587
|
+
byKey.set(key, {
|
|
6588
|
+
email: email.toLowerCase(),
|
|
6589
|
+
name: name.trim() || null,
|
|
6590
|
+
date,
|
|
6591
|
+
externalId,
|
|
6592
|
+
isBot: false
|
|
6593
|
+
});
|
|
6594
|
+
}
|
|
6595
|
+
}
|
|
6596
|
+
if (!sawInWindow) break;
|
|
6597
|
+
if (!data?.next) break;
|
|
6598
|
+
page++;
|
|
6599
|
+
}
|
|
6600
|
+
return [...byKey.values()];
|
|
6601
|
+
},
|
|
6492
6602
|
async getRepoCommitAuthors(params2) {
|
|
6493
|
-
|
|
6603
|
+
const MAX_PAGES = 100;
|
|
6604
|
+
const authorMap = /* @__PURE__ */ new Map();
|
|
6605
|
+
let page = 1;
|
|
6606
|
+
while (page <= MAX_PAGES) {
|
|
6494
6607
|
const res = await bitbucketClient.repositories.listCommits({
|
|
6495
6608
|
repo_slug: params2.repo_slug,
|
|
6496
6609
|
workspace: params2.workspace,
|
|
6610
|
+
page: String(page),
|
|
6497
6611
|
pagelen: 100
|
|
6498
6612
|
});
|
|
6499
|
-
const
|
|
6500
|
-
const
|
|
6613
|
+
const data = res.data;
|
|
6614
|
+
const commits = data?.values ?? [];
|
|
6615
|
+
if (commits.length === 0) break;
|
|
6501
6616
|
for (const commit of commits) {
|
|
6502
6617
|
const raw = commit?.["author"];
|
|
6503
6618
|
if (!raw?.raw) continue;
|
|
@@ -6515,10 +6630,10 @@ function getBitbucketSdk(params) {
|
|
|
6515
6630
|
});
|
|
6516
6631
|
}
|
|
6517
6632
|
}
|
|
6518
|
-
|
|
6519
|
-
|
|
6520
|
-
return [];
|
|
6633
|
+
if (!data?.next) break;
|
|
6634
|
+
page++;
|
|
6521
6635
|
}
|
|
6636
|
+
return Array.from(authorMap.values());
|
|
6522
6637
|
}
|
|
6523
6638
|
};
|
|
6524
6639
|
}
|
|
@@ -6865,17 +6980,33 @@ var BitbucketSCMLib = class extends SCMLib {
|
|
|
6865
6980
|
})
|
|
6866
6981
|
};
|
|
6867
6982
|
}
|
|
6983
|
+
async getRecentCommitAuthors(since) {
|
|
6984
|
+
this._validateAccessTokenAndUrl();
|
|
6985
|
+
const { workspace, repo_slug } = parseBitbucketOrganizationAndRepo(this.url);
|
|
6986
|
+
const bitbucketSdk = createBitbucketSdk(this.accessToken);
|
|
6987
|
+
return bitbucketSdk.streamRecentCommitAuthors({
|
|
6988
|
+
workspace,
|
|
6989
|
+
repo_slug,
|
|
6990
|
+
since
|
|
6991
|
+
});
|
|
6992
|
+
}
|
|
6868
6993
|
async getRateLimitStatus() {
|
|
6869
6994
|
return null;
|
|
6870
6995
|
}
|
|
6871
6996
|
async getRepositoryContributors() {
|
|
6872
6997
|
this._validateAccessTokenAndUrl();
|
|
6873
6998
|
const { workspace, repo_slug } = parseBitbucketOrganizationAndRepo(this.url);
|
|
6874
|
-
const [
|
|
6999
|
+
const [membersRes, commitAuthorsRes, currentUserRes] = await Promise.allSettled([
|
|
6875
7000
|
this.bitbucketSdk.getWorkspaceMembers({ workspace }),
|
|
6876
7001
|
this.bitbucketSdk.getRepoCommitAuthors({ workspace, repo_slug }),
|
|
6877
7002
|
this.bitbucketSdk.getCurrentUserWithEmail()
|
|
6878
7003
|
]);
|
|
7004
|
+
if (membersRes.status === "rejected" && commitAuthorsRes.status === "rejected") {
|
|
7005
|
+
throw membersRes.reason;
|
|
7006
|
+
}
|
|
7007
|
+
const members = membersRes.status === "fulfilled" ? membersRes.value : [];
|
|
7008
|
+
const commitAuthors = commitAuthorsRes.status === "fulfilled" ? commitAuthorsRes.value : [];
|
|
7009
|
+
const currentUser = currentUserRes.status === "fulfilled" ? currentUserRes.value : { accountId: null, email: null };
|
|
6879
7010
|
const emailByAccountId = /* @__PURE__ */ new Map();
|
|
6880
7011
|
const emailByName = /* @__PURE__ */ new Map();
|
|
6881
7012
|
for (const author of commitAuthors) {
|
|
@@ -6933,6 +7064,20 @@ var BitbucketSCMLib = class extends SCMLib {
|
|
|
6933
7064
|
accessLevel: null
|
|
6934
7065
|
};
|
|
6935
7066
|
});
|
|
7067
|
+
if (result.length === 0 && commitAuthors.length > 0) {
|
|
7068
|
+
return commitAuthors.filter((a) => a.accountId || a.email || a.name).map((a) => ({
|
|
7069
|
+
// Real Bitbucket account id keeps the member identity; otherwise use a
|
|
7070
|
+
// synthetic commit key so email/display-name-only rows can't collide
|
|
7071
|
+
// with numeric member rows (or, on Bitbucket, merge two distinct
|
|
7072
|
+
// people who happen to share a display name). Lowercased to match the
|
|
7073
|
+
// 90-day stamp path (commit:<lowercased email>).
|
|
7074
|
+
externalId: a.accountId ?? `commit:${(a.email || a.name).toLowerCase()}`,
|
|
7075
|
+
username: null,
|
|
7076
|
+
displayName: a.name ?? null,
|
|
7077
|
+
email: a.email ?? null,
|
|
7078
|
+
accessLevel: null
|
|
7079
|
+
}));
|
|
7080
|
+
}
|
|
6936
7081
|
const withEmail = result.filter((c) => c.email);
|
|
6937
7082
|
contextLogger.info("[Bitbucket] Contributor enrichment summary", {
|
|
6938
7083
|
total: result.length,
|
|
@@ -7721,6 +7866,42 @@ function getGithubSdk(params = {}) {
|
|
|
7721
7866
|
});
|
|
7722
7867
|
return { data: commits };
|
|
7723
7868
|
},
|
|
7869
|
+
// Distinct commit authors in the window, streamed. Uses paginate.iterator so
|
|
7870
|
+
// only ONE page is in memory at a time and we accumulate one row per author
|
|
7871
|
+
// (keeping their latest commit date) — memory is bounded by author count, not
|
|
7872
|
+
// commit volume, and the walk is uncapped so the 90-day count is exact even
|
|
7873
|
+
// for a repo with hundreds of thousands of commits.
|
|
7874
|
+
async streamRecentCommitAuthors(params2) {
|
|
7875
|
+
const byAuthor = /* @__PURE__ */ new Map();
|
|
7876
|
+
for await (const { data } of octokit.paginate.iterator(
|
|
7877
|
+
octokit.rest.repos.listCommits,
|
|
7878
|
+
{
|
|
7879
|
+
owner: params2.owner,
|
|
7880
|
+
repo: params2.repo,
|
|
7881
|
+
since: params2.since,
|
|
7882
|
+
per_page: 100
|
|
7883
|
+
}
|
|
7884
|
+
)) {
|
|
7885
|
+
for (const c of data) {
|
|
7886
|
+
const email = c.commit?.author?.email;
|
|
7887
|
+
if (!email) continue;
|
|
7888
|
+
const externalId = c.author?.id != null ? String(c.author.id) : null;
|
|
7889
|
+
const date = c.commit?.committer?.date ?? c.commit?.author?.date ?? params2.since;
|
|
7890
|
+
const key = externalId ?? email.toLowerCase();
|
|
7891
|
+
const prev = byAuthor.get(key);
|
|
7892
|
+
if (!prev || new Date(date).getTime() > new Date(prev.date).getTime()) {
|
|
7893
|
+
byAuthor.set(key, {
|
|
7894
|
+
email: email.toLowerCase(),
|
|
7895
|
+
name: c.commit?.author?.name ?? null,
|
|
7896
|
+
date,
|
|
7897
|
+
externalId,
|
|
7898
|
+
isBot: c.author?.type === "Bot"
|
|
7899
|
+
});
|
|
7900
|
+
}
|
|
7901
|
+
}
|
|
7902
|
+
}
|
|
7903
|
+
return [...byAuthor.values()];
|
|
7904
|
+
},
|
|
7724
7905
|
async getRateLimitStatus() {
|
|
7725
7906
|
const response = await octokit.rest.rateLimit.get();
|
|
7726
7907
|
return {
|
|
@@ -7886,15 +8067,46 @@ function getGithubSdk(params = {}) {
|
|
|
7886
8067
|
};
|
|
7887
8068
|
},
|
|
7888
8069
|
async listRepositoryCollaborators(params2) {
|
|
7889
|
-
|
|
7890
|
-
octokit.
|
|
8070
|
+
try {
|
|
8071
|
+
const collaborators = await octokit.paginate(
|
|
8072
|
+
octokit.rest.repos.listCollaborators,
|
|
8073
|
+
{
|
|
8074
|
+
owner: params2.owner,
|
|
8075
|
+
repo: params2.repo,
|
|
8076
|
+
per_page: 100
|
|
8077
|
+
}
|
|
8078
|
+
);
|
|
8079
|
+
return collaborators;
|
|
8080
|
+
} catch (error) {
|
|
8081
|
+
const status = error?.status;
|
|
8082
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
8083
|
+
if (/rate limit|secondary rate|abuse detection/i.test(message)) {
|
|
8084
|
+
throw error;
|
|
8085
|
+
}
|
|
8086
|
+
if (status === 403 || status === 404 || /push access|forbidden/i.test(message)) {
|
|
8087
|
+
return [];
|
|
8088
|
+
}
|
|
8089
|
+
throw error;
|
|
8090
|
+
}
|
|
8091
|
+
},
|
|
8092
|
+
// Read-only contributor list (commit authors, aggregated). Unlike
|
|
8093
|
+
// listCollaborators this needs only read access, so it works for repos where
|
|
8094
|
+
// we can read code but aren't a push-level collaborator.
|
|
8095
|
+
async listRepositoryContributors(params2) {
|
|
8096
|
+
const contributors = await octokit.paginate(
|
|
8097
|
+
octokit.rest.repos.listContributors,
|
|
7891
8098
|
{
|
|
7892
8099
|
owner: params2.owner,
|
|
7893
8100
|
repo: params2.repo,
|
|
7894
8101
|
per_page: 100
|
|
7895
|
-
}
|
|
8102
|
+
},
|
|
8103
|
+
(response) => response.data.map((c) => ({
|
|
8104
|
+
login: c.login,
|
|
8105
|
+
id: c.id,
|
|
8106
|
+
type: c.type
|
|
8107
|
+
}))
|
|
7896
8108
|
);
|
|
7897
|
-
return
|
|
8109
|
+
return contributors;
|
|
7898
8110
|
},
|
|
7899
8111
|
async listOrgMembers(params2) {
|
|
7900
8112
|
const members = await octokit.paginate(octokit.rest.orgs.listMembers, {
|
|
@@ -8154,6 +8366,11 @@ var GithubSCMLib = class extends SCMLib {
|
|
|
8154
8366
|
}))
|
|
8155
8367
|
};
|
|
8156
8368
|
}
|
|
8369
|
+
async getRecentCommitAuthors(since) {
|
|
8370
|
+
this._validateAccessTokenAndUrl();
|
|
8371
|
+
const { owner, repo } = parseGithubOwnerAndRepo(this.url);
|
|
8372
|
+
return this.githubSdk.streamRecentCommitAuthors({ owner, repo, since });
|
|
8373
|
+
}
|
|
8157
8374
|
async getRateLimitStatus() {
|
|
8158
8375
|
const result = await this.githubSdk.getRateLimitStatus();
|
|
8159
8376
|
return {
|
|
@@ -8165,11 +8382,25 @@ var GithubSCMLib = class extends SCMLib {
|
|
|
8165
8382
|
async getRepositoryContributors() {
|
|
8166
8383
|
this._validateAccessTokenAndUrl();
|
|
8167
8384
|
const { owner, repo } = parseGithubOwnerAndRepo(this.url);
|
|
8168
|
-
const [
|
|
8385
|
+
const [collaboratorsRaw, authUser, authEmails] = await Promise.all([
|
|
8169
8386
|
this.githubSdk.listRepositoryCollaborators({ owner, repo }),
|
|
8170
8387
|
this.githubSdk.getAuthenticatedUser(),
|
|
8171
8388
|
this.githubSdk.getAuthenticatedUserEmails()
|
|
8172
8389
|
]);
|
|
8390
|
+
if (collaboratorsRaw.length === 0) {
|
|
8391
|
+
const fallback = await this.githubSdk.listRepositoryContributors({
|
|
8392
|
+
owner,
|
|
8393
|
+
repo
|
|
8394
|
+
});
|
|
8395
|
+
return fallback.map((c) => ({
|
|
8396
|
+
externalId: String(c.id),
|
|
8397
|
+
username: c.login ?? null,
|
|
8398
|
+
displayName: c.login ?? null,
|
|
8399
|
+
email: null,
|
|
8400
|
+
accessLevel: null
|
|
8401
|
+
}));
|
|
8402
|
+
}
|
|
8403
|
+
const collaborators = collaboratorsRaw;
|
|
8173
8404
|
let authUserPrimaryEmail = null;
|
|
8174
8405
|
if (authEmails.length > 0) {
|
|
8175
8406
|
const primary = authEmails.find((e) => e.primary && e.verified);
|
|
@@ -9342,6 +9573,49 @@ async function getGitlabRecentCommits({
|
|
|
9342
9573
|
}
|
|
9343
9574
|
return allCommits;
|
|
9344
9575
|
}
|
|
9576
|
+
async function getGitlabRecentCommitAuthors({
|
|
9577
|
+
repoUrl,
|
|
9578
|
+
accessToken,
|
|
9579
|
+
since
|
|
9580
|
+
}) {
|
|
9581
|
+
const { projectPath } = parseGitlabOwnerAndRepo(repoUrl);
|
|
9582
|
+
const api2 = getGitBeaker({ url: repoUrl, gitlabAuthToken: accessToken });
|
|
9583
|
+
const byEmail = /* @__PURE__ */ new Map();
|
|
9584
|
+
const perPage = GITLAB_PER_PAGE;
|
|
9585
|
+
let page = 1;
|
|
9586
|
+
let hasMore = true;
|
|
9587
|
+
while (hasMore) {
|
|
9588
|
+
const response = await api2.Commits.all(projectPath, {
|
|
9589
|
+
since,
|
|
9590
|
+
perPage,
|
|
9591
|
+
page,
|
|
9592
|
+
showExpanded: true
|
|
9593
|
+
});
|
|
9594
|
+
const commits = response.data;
|
|
9595
|
+
if (commits.length === 0) {
|
|
9596
|
+
break;
|
|
9597
|
+
}
|
|
9598
|
+
for (const commit of commits) {
|
|
9599
|
+
const email = commit.author_email;
|
|
9600
|
+
if (!email) continue;
|
|
9601
|
+
const date = commit.committed_date ?? since;
|
|
9602
|
+
const key = email.toLowerCase();
|
|
9603
|
+
const prev = byEmail.get(key);
|
|
9604
|
+
if (!prev || new Date(date).getTime() > new Date(prev.date).getTime()) {
|
|
9605
|
+
byEmail.set(key, {
|
|
9606
|
+
email: key,
|
|
9607
|
+
name: commit.author_name ?? null,
|
|
9608
|
+
date,
|
|
9609
|
+
externalId: null,
|
|
9610
|
+
isBot: false
|
|
9611
|
+
});
|
|
9612
|
+
}
|
|
9613
|
+
}
|
|
9614
|
+
hasMore = response.paginationInfo.next !== null;
|
|
9615
|
+
page++;
|
|
9616
|
+
}
|
|
9617
|
+
return [...byEmail.values()];
|
|
9618
|
+
}
|
|
9345
9619
|
async function getGitlabRateLimitStatus({
|
|
9346
9620
|
repoUrl,
|
|
9347
9621
|
accessToken
|
|
@@ -9444,15 +9718,28 @@ async function brokerRequestHandler(endpoint, options) {
|
|
|
9444
9718
|
}
|
|
9445
9719
|
throw lastError ?? new Error("gitbeaker: request failed");
|
|
9446
9720
|
}
|
|
9721
|
+
function isGitlabPermissionDenied(e) {
|
|
9722
|
+
const status = e?.status;
|
|
9723
|
+
const msg = (e instanceof Error ? e.message : String(e)).toLowerCase();
|
|
9724
|
+
return status === 403 || /forbidden|insufficient/.test(msg);
|
|
9725
|
+
}
|
|
9447
9726
|
async function listGitlabProjectMembers({
|
|
9448
9727
|
repoUrl,
|
|
9449
9728
|
accessToken
|
|
9450
9729
|
}) {
|
|
9451
9730
|
const { owner, projectPath } = parseGitlabOwnerAndRepo(repoUrl);
|
|
9452
9731
|
const api2 = getGitBeaker({ url: repoUrl, gitlabAuthToken: accessToken });
|
|
9453
|
-
|
|
9454
|
-
|
|
9455
|
-
|
|
9732
|
+
let projectMembers;
|
|
9733
|
+
try {
|
|
9734
|
+
projectMembers = await api2.ProjectMembers.all(projectPath, {
|
|
9735
|
+
includeInherited: true
|
|
9736
|
+
});
|
|
9737
|
+
} catch (e) {
|
|
9738
|
+
if (isGitlabPermissionDenied(e)) {
|
|
9739
|
+
return [];
|
|
9740
|
+
}
|
|
9741
|
+
throw e;
|
|
9742
|
+
}
|
|
9456
9743
|
if (projectMembers.length > 1 || !owner) return projectMembers;
|
|
9457
9744
|
try {
|
|
9458
9745
|
const groupMembers = await api2.GroupMembers.all(owner, {
|
|
@@ -9482,18 +9769,14 @@ async function listGitlabRepoContributors({
|
|
|
9482
9769
|
repoUrl,
|
|
9483
9770
|
accessToken
|
|
9484
9771
|
}) {
|
|
9485
|
-
|
|
9486
|
-
|
|
9487
|
-
|
|
9488
|
-
|
|
9489
|
-
|
|
9490
|
-
|
|
9491
|
-
|
|
9492
|
-
|
|
9493
|
-
}));
|
|
9494
|
-
} catch {
|
|
9495
|
-
return [];
|
|
9496
|
-
}
|
|
9772
|
+
const { projectPath } = parseGitlabOwnerAndRepo(repoUrl);
|
|
9773
|
+
const api2 = getGitBeaker({ url: repoUrl, gitlabAuthToken: accessToken });
|
|
9774
|
+
const contributors = await api2.Repositories.allContributors(projectPath);
|
|
9775
|
+
return contributors.map((c) => ({
|
|
9776
|
+
name: c.name,
|
|
9777
|
+
email: c.email,
|
|
9778
|
+
commits: c.commits
|
|
9779
|
+
}));
|
|
9497
9780
|
}
|
|
9498
9781
|
async function getGitlabAuthenticatedUser({
|
|
9499
9782
|
repoUrl,
|
|
@@ -9873,6 +10156,14 @@ var GitlabSCMLib = class extends SCMLib {
|
|
|
9873
10156
|
});
|
|
9874
10157
|
return { data: commits };
|
|
9875
10158
|
}
|
|
10159
|
+
async getRecentCommitAuthors(since) {
|
|
10160
|
+
this._validateAccessTokenAndUrl();
|
|
10161
|
+
return getGitlabRecentCommitAuthors({
|
|
10162
|
+
repoUrl: this.url,
|
|
10163
|
+
accessToken: this.accessToken,
|
|
10164
|
+
since
|
|
10165
|
+
});
|
|
10166
|
+
}
|
|
9876
10167
|
async getRateLimitStatus() {
|
|
9877
10168
|
this._validateAccessTokenAndUrl();
|
|
9878
10169
|
return getGitlabRateLimitStatus({
|
|
@@ -9882,7 +10173,7 @@ var GitlabSCMLib = class extends SCMLib {
|
|
|
9882
10173
|
}
|
|
9883
10174
|
async getRepositoryContributors() {
|
|
9884
10175
|
this._validateAccessTokenAndUrl();
|
|
9885
|
-
const [
|
|
10176
|
+
const [membersRes, repoContribRes, authUserRes] = await Promise.allSettled([
|
|
9886
10177
|
listGitlabProjectMembers({
|
|
9887
10178
|
repoUrl: this.url,
|
|
9888
10179
|
accessToken: this.accessToken
|
|
@@ -9896,6 +10187,12 @@ var GitlabSCMLib = class extends SCMLib {
|
|
|
9896
10187
|
accessToken: this.accessToken
|
|
9897
10188
|
})
|
|
9898
10189
|
]);
|
|
10190
|
+
if (membersRes.status === "rejected" && repoContribRes.status === "rejected") {
|
|
10191
|
+
throw membersRes.reason;
|
|
10192
|
+
}
|
|
10193
|
+
const members = membersRes.status === "fulfilled" ? membersRes.value : [];
|
|
10194
|
+
const repoContributors = repoContribRes.status === "fulfilled" ? repoContribRes.value : [];
|
|
10195
|
+
const authUser = authUserRes.status === "fulfilled" ? authUserRes.value : null;
|
|
9899
10196
|
contextLogger.info("[GitLab] Starting contributor enrichment", {
|
|
9900
10197
|
memberCount: members.length,
|
|
9901
10198
|
repoContributorCount: repoContributors.length
|
|
@@ -9959,6 +10256,20 @@ var GitlabSCMLib = class extends SCMLib {
|
|
|
9959
10256
|
})
|
|
9960
10257
|
)
|
|
9961
10258
|
);
|
|
10259
|
+
if (enriched.length === 0 && repoContributors.length > 0) {
|
|
10260
|
+
return repoContributors.filter((rc) => rc.email || rc.name).map((rc) => ({
|
|
10261
|
+
// Synthetic key namespaced away from real numeric member ids so a
|
|
10262
|
+
// commit-author row can't collide with (or later duplicate) the same
|
|
10263
|
+
// person's member row once a privileged token lists them. Prefer email.
|
|
10264
|
+
// Lowercased to match the 90-day stamp path (commit:<lowercased email>),
|
|
10265
|
+
// so the same person isn't split into commit:John@X.com vs commit:john@x.com.
|
|
10266
|
+
externalId: `commit:${(rc.email || rc.name).toLowerCase()}`,
|
|
10267
|
+
username: null,
|
|
10268
|
+
displayName: rc.name ?? null,
|
|
10269
|
+
email: rc.email ?? null,
|
|
10270
|
+
accessLevel: null
|
|
10271
|
+
}));
|
|
10272
|
+
}
|
|
9962
10273
|
const withEmail = enriched.filter((c) => c.email);
|
|
9963
10274
|
contextLogger.info("[GitLab] Contributor enrichment summary", {
|
|
9964
10275
|
total: enriched.length,
|
|
@@ -17732,7 +18043,7 @@ function createLogger(config2) {
|
|
|
17732
18043
|
|
|
17733
18044
|
// src/features/claude_code/hook_logger.ts
|
|
17734
18045
|
var DD_RUM_TOKEN = true ? "pubf59c0182545bfb4c299175119f1abf9b" : "";
|
|
17735
|
-
var CLI_VERSION = true ? "1.4.
|
|
18046
|
+
var CLI_VERSION = true ? "1.4.41" : "unknown";
|
|
17736
18047
|
var NAMESPACE = "mobbdev-claude-code-hook-logs";
|
|
17737
18048
|
var claudeCodeVersion;
|
|
17738
18049
|
function buildDdTags() {
|
|
@@ -19304,7 +19615,6 @@ var FixQuestionSchema = z29.object({
|
|
|
19304
19615
|
inputType: z29.nativeEnum(FixQuestionInputType),
|
|
19305
19616
|
options: z29.array(z29.string()),
|
|
19306
19617
|
index: z29.number(),
|
|
19307
|
-
extraContext: z29.array(UnstructuredFixExtraContextSchema),
|
|
19308
19618
|
// E-2015: analyzer-served question text. default('') so a query that omits it
|
|
19309
19619
|
// still parses, while the output type stays a required string. The analyzer is
|
|
19310
19620
|
// the sole source; consumers use the served value directly (no fallback).
|