mobbdev 1.2.67 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/args/commands/upload_ai_blame.mjs +10 -10
- package/dist/index.mjs +1824 -1006
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -3560,8 +3560,8 @@ var init_FileUtils = __esm({
|
|
|
3560
3560
|
const fullPath = path.join(dir, item);
|
|
3561
3561
|
try {
|
|
3562
3562
|
await fsPromises.access(fullPath, fs.constants.R_OK);
|
|
3563
|
-
const
|
|
3564
|
-
if (
|
|
3563
|
+
const stat2 = await fsPromises.stat(fullPath);
|
|
3564
|
+
if (stat2.isDirectory()) {
|
|
3565
3565
|
if (isRootLevel && excludedRootDirectories.includes(item)) {
|
|
3566
3566
|
continue;
|
|
3567
3567
|
}
|
|
@@ -3573,7 +3573,7 @@ var init_FileUtils = __esm({
|
|
|
3573
3573
|
name: item,
|
|
3574
3574
|
fullPath,
|
|
3575
3575
|
relativePath: path.relative(rootDir, fullPath),
|
|
3576
|
-
time:
|
|
3576
|
+
time: stat2.mtime.getTime(),
|
|
3577
3577
|
isFile: true
|
|
3578
3578
|
});
|
|
3579
3579
|
}
|
|
@@ -4205,6 +4205,64 @@ import tmp from "tmp";
|
|
|
4205
4205
|
// src/features/analysis/scm/ado/ado.ts
|
|
4206
4206
|
import pLimit from "p-limit";
|
|
4207
4207
|
|
|
4208
|
+
// src/utils/contextLogger.ts
|
|
4209
|
+
import debugModule from "debug";
|
|
4210
|
+
var debug = debugModule("mobb:shared");
|
|
4211
|
+
var _contextLogger = null;
|
|
4212
|
+
var createContextLogger = async () => {
|
|
4213
|
+
if (_contextLogger) return _contextLogger;
|
|
4214
|
+
try {
|
|
4215
|
+
let logger3;
|
|
4216
|
+
try {
|
|
4217
|
+
let module;
|
|
4218
|
+
try {
|
|
4219
|
+
const buildPath = "../../../../../tscommon/backend/build/src/utils/logger";
|
|
4220
|
+
module = await import(buildPath);
|
|
4221
|
+
} catch (e) {
|
|
4222
|
+
const sourcePath = "../../../../../tscommon/backend/src/utils/logger";
|
|
4223
|
+
module = await import(sourcePath);
|
|
4224
|
+
}
|
|
4225
|
+
logger3 = module.logger;
|
|
4226
|
+
} catch {
|
|
4227
|
+
}
|
|
4228
|
+
if (logger3) {
|
|
4229
|
+
_contextLogger = {
|
|
4230
|
+
info: (message, data) => data ? logger3.info(data, message) : logger3.info(message),
|
|
4231
|
+
warn: (message, data) => data ? logger3.warn(data, message) : logger3.warn(message),
|
|
4232
|
+
debug: (message, data) => data ? logger3.debug(data, message) : logger3.debug(message),
|
|
4233
|
+
error: (message, data) => data ? logger3.error(data, message) : logger3.error(message)
|
|
4234
|
+
};
|
|
4235
|
+
return _contextLogger;
|
|
4236
|
+
}
|
|
4237
|
+
} catch {
|
|
4238
|
+
}
|
|
4239
|
+
_contextLogger = {
|
|
4240
|
+
info: (message, data) => debug(message, data),
|
|
4241
|
+
warn: (message, data) => debug(message, data),
|
|
4242
|
+
debug: (message, data) => debug(message, data),
|
|
4243
|
+
error: (message, data) => debug(message, data)
|
|
4244
|
+
};
|
|
4245
|
+
return _contextLogger;
|
|
4246
|
+
};
|
|
4247
|
+
var contextLogger = {
|
|
4248
|
+
info: async (message, data) => {
|
|
4249
|
+
const logger3 = await createContextLogger();
|
|
4250
|
+
return logger3.info(message, data);
|
|
4251
|
+
},
|
|
4252
|
+
debug: async (message, data) => {
|
|
4253
|
+
const logger3 = await createContextLogger();
|
|
4254
|
+
return logger3.debug(message, data);
|
|
4255
|
+
},
|
|
4256
|
+
warn: async (message, data) => {
|
|
4257
|
+
const logger3 = await createContextLogger();
|
|
4258
|
+
return logger3.warn(message, data);
|
|
4259
|
+
},
|
|
4260
|
+
error: async (message, data) => {
|
|
4261
|
+
const logger3 = await createContextLogger();
|
|
4262
|
+
return logger3.error(message, data);
|
|
4263
|
+
}
|
|
4264
|
+
};
|
|
4265
|
+
|
|
4208
4266
|
// src/features/analysis/scm/errors.ts
|
|
4209
4267
|
var InvalidAccessTokenError = class extends Error {
|
|
4210
4268
|
constructor(m, scmType) {
|
|
@@ -6680,7 +6738,7 @@ var accountsZ = z16.object({
|
|
|
6680
6738
|
});
|
|
6681
6739
|
|
|
6682
6740
|
// src/features/analysis/scm/ado/utils.ts
|
|
6683
|
-
var
|
|
6741
|
+
var debug2 = Debug("mobbdev:scm:ado");
|
|
6684
6742
|
function transformVisualStudioUrl(url) {
|
|
6685
6743
|
const match = url.match(/^https:\/\/([^.]+)\.visualstudio\.com/i);
|
|
6686
6744
|
if (match) {
|
|
@@ -7042,7 +7100,7 @@ async function getAdoSdk(params) {
|
|
|
7042
7100
|
const url = new URL(repoUrl);
|
|
7043
7101
|
const origin = url.origin.toLowerCase().endsWith(".visualstudio.com") ? DEFUALT_ADO_ORIGIN : url.origin.toLowerCase();
|
|
7044
7102
|
const params2 = `path=/&versionDescriptor[versionOptions]=0&versionDescriptor[versionType]=commit&versionDescriptor[version]=${branch}&resolveLfs=true&$format=zip&api-version=5.0&download=true`;
|
|
7045
|
-
const
|
|
7103
|
+
const path30 = [
|
|
7046
7104
|
prefixPath,
|
|
7047
7105
|
owner,
|
|
7048
7106
|
projectName,
|
|
@@ -7053,7 +7111,7 @@ async function getAdoSdk(params) {
|
|
|
7053
7111
|
"items",
|
|
7054
7112
|
"items"
|
|
7055
7113
|
].filter(Boolean).join("/");
|
|
7056
|
-
return new URL(`${
|
|
7114
|
+
return new URL(`${path30}?${params2}`, origin).toString();
|
|
7057
7115
|
},
|
|
7058
7116
|
async getAdoBranchList({ repoUrl }) {
|
|
7059
7117
|
try {
|
|
@@ -7142,8 +7200,8 @@ async function getAdoSdk(params) {
|
|
|
7142
7200
|
const changeType = entry.changeType;
|
|
7143
7201
|
return changeType !== 16 && entry.item?.path;
|
|
7144
7202
|
}).map((entry) => {
|
|
7145
|
-
const
|
|
7146
|
-
return
|
|
7203
|
+
const path30 = entry.item.path;
|
|
7204
|
+
return path30.startsWith("/") ? path30.slice(1) : path30;
|
|
7147
7205
|
});
|
|
7148
7206
|
},
|
|
7149
7207
|
async searchAdoPullRequests({
|
|
@@ -7328,6 +7386,41 @@ async function getAdoSdk(params) {
|
|
|
7328
7386
|
return commitRes.value;
|
|
7329
7387
|
}
|
|
7330
7388
|
throw new RefNotFoundError(`ref: ${ref} does not exist`);
|
|
7389
|
+
},
|
|
7390
|
+
async listProjectMembers({ repoUrl }) {
|
|
7391
|
+
try {
|
|
7392
|
+
const { projectName } = parseAdoOwnerAndRepo(repoUrl);
|
|
7393
|
+
if (!projectName) return [];
|
|
7394
|
+
const coreApi = await api2.getCoreApi();
|
|
7395
|
+
const teams = await coreApi.getTeams(projectName);
|
|
7396
|
+
const allMembers = [];
|
|
7397
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
7398
|
+
for (const team of teams) {
|
|
7399
|
+
if (!team.id || !team.projectId) continue;
|
|
7400
|
+
const members = await coreApi.getTeamMembersWithExtendedProperties(
|
|
7401
|
+
team.projectId,
|
|
7402
|
+
team.id
|
|
7403
|
+
);
|
|
7404
|
+
for (const member of members) {
|
|
7405
|
+
const identity = member.identity;
|
|
7406
|
+
if (!identity?.id || seenIds.has(identity.id)) continue;
|
|
7407
|
+
seenIds.add(identity.id);
|
|
7408
|
+
allMembers.push({
|
|
7409
|
+
id: identity.id,
|
|
7410
|
+
displayName: identity.displayName ?? "",
|
|
7411
|
+
uniqueName: identity["uniqueName"] ?? "",
|
|
7412
|
+
imageUrl: identity["imageUrl"] ?? ""
|
|
7413
|
+
});
|
|
7414
|
+
}
|
|
7415
|
+
}
|
|
7416
|
+
return allMembers;
|
|
7417
|
+
} catch (e) {
|
|
7418
|
+
contextLogger.warn(
|
|
7419
|
+
"[listProjectMembers] Failed to list ADO project members \u2014 scope may be insufficient",
|
|
7420
|
+
{ error: e instanceof Error ? e.message : String(e), repoUrl }
|
|
7421
|
+
);
|
|
7422
|
+
return [];
|
|
7423
|
+
}
|
|
7331
7424
|
}
|
|
7332
7425
|
};
|
|
7333
7426
|
}
|
|
@@ -7932,6 +8025,19 @@ var AdoSCMLib = class extends SCMLib {
|
|
|
7932
8025
|
reset: new Date(Date.now() + 36e5)
|
|
7933
8026
|
};
|
|
7934
8027
|
}
|
|
8028
|
+
async getRepositoryContributors() {
|
|
8029
|
+
this._validateAccessTokenAndUrl();
|
|
8030
|
+
const adoSdk = await this.getAdoSdk();
|
|
8031
|
+
const members = await adoSdk.listProjectMembers({ repoUrl: this.url });
|
|
8032
|
+
const isLikelyEmail = (v) => !!v && /^[^@\s\\]+@[^@\s\\]+\.[^@\s\\]+$/.test(v);
|
|
8033
|
+
return members.map((m) => ({
|
|
8034
|
+
externalId: m.id,
|
|
8035
|
+
username: m.uniqueName || null,
|
|
8036
|
+
displayName: m.displayName || null,
|
|
8037
|
+
email: isLikelyEmail(m.uniqueName) ? m.uniqueName : null,
|
|
8038
|
+
accessLevel: null
|
|
8039
|
+
}));
|
|
8040
|
+
}
|
|
7935
8041
|
};
|
|
7936
8042
|
|
|
7937
8043
|
// src/features/analysis/scm/bitbucket/bitbucket.ts
|
|
@@ -7950,7 +8056,7 @@ var BitbucketAuthResultZ = z19.object({
|
|
|
7950
8056
|
});
|
|
7951
8057
|
|
|
7952
8058
|
// src/features/analysis/scm/bitbucket/bitbucket.ts
|
|
7953
|
-
var
|
|
8059
|
+
var debug3 = Debug2("scm:bitbucket");
|
|
7954
8060
|
var BITBUCKET_HOSTNAME = "bitbucket.org";
|
|
7955
8061
|
var TokenExpiredErrorZ = z20.object({
|
|
7956
8062
|
status: z20.number(),
|
|
@@ -8195,6 +8301,70 @@ function getBitbucketSdk(params) {
|
|
|
8195
8301
|
workspace
|
|
8196
8302
|
});
|
|
8197
8303
|
return res.data;
|
|
8304
|
+
},
|
|
8305
|
+
async getWorkspaceMembers(params2) {
|
|
8306
|
+
const allMembers = [];
|
|
8307
|
+
let hasMore = true;
|
|
8308
|
+
let page = 1;
|
|
8309
|
+
while (hasMore) {
|
|
8310
|
+
const res = await bitbucketClient.workspaces.getMembersForWorkspace({
|
|
8311
|
+
workspace: params2.workspace,
|
|
8312
|
+
page: String(page),
|
|
8313
|
+
pagelen: 100
|
|
8314
|
+
});
|
|
8315
|
+
const values = res.data.values ?? [];
|
|
8316
|
+
allMembers.push(...values);
|
|
8317
|
+
hasMore = Boolean(res.data.next) && values.length > 0;
|
|
8318
|
+
page++;
|
|
8319
|
+
}
|
|
8320
|
+
return allMembers;
|
|
8321
|
+
},
|
|
8322
|
+
async getCurrentUserWithEmail() {
|
|
8323
|
+
try {
|
|
8324
|
+
const [userRes, emailsRes] = await Promise.all([
|
|
8325
|
+
bitbucketClient.user.get({}),
|
|
8326
|
+
bitbucketClient.user.listEmails({})
|
|
8327
|
+
]);
|
|
8328
|
+
const accountId = userRes.data?.["account_id"] ?? null;
|
|
8329
|
+
const emails = emailsRes.data?.values ?? [];
|
|
8330
|
+
const primary = emails.find((e) => e.is_primary && e.is_confirmed);
|
|
8331
|
+
const confirmed = emails.find((e) => e.is_confirmed);
|
|
8332
|
+
const email = primary?.email ?? confirmed?.email ?? emails[0]?.email ?? null;
|
|
8333
|
+
return { accountId, email };
|
|
8334
|
+
} catch {
|
|
8335
|
+
return { accountId: null, email: null };
|
|
8336
|
+
}
|
|
8337
|
+
},
|
|
8338
|
+
async getRepoCommitAuthors(params2) {
|
|
8339
|
+
try {
|
|
8340
|
+
const res = await bitbucketClient.repositories.listCommits({
|
|
8341
|
+
repo_slug: params2.repo_slug,
|
|
8342
|
+
workspace: params2.workspace,
|
|
8343
|
+
pagelen: 100
|
|
8344
|
+
});
|
|
8345
|
+
const commits = res.data?.values ?? [];
|
|
8346
|
+
const authorMap = /* @__PURE__ */ new Map();
|
|
8347
|
+
for (const commit of commits) {
|
|
8348
|
+
const raw = commit?.["author"];
|
|
8349
|
+
if (!raw?.raw) continue;
|
|
8350
|
+
const match = raw.raw.match(/^(.+?)\s*<([^>]+)>/);
|
|
8351
|
+
if (!match) continue;
|
|
8352
|
+
const [, name, email] = match;
|
|
8353
|
+
if (!email) continue;
|
|
8354
|
+
const accountId = raw.user?.account_id ?? null;
|
|
8355
|
+
const dedupeKey = accountId ?? raw.user?.nickname ?? email;
|
|
8356
|
+
if (!authorMap.has(dedupeKey)) {
|
|
8357
|
+
authorMap.set(dedupeKey, {
|
|
8358
|
+
name: name.trim(),
|
|
8359
|
+
email,
|
|
8360
|
+
accountId
|
|
8361
|
+
});
|
|
8362
|
+
}
|
|
8363
|
+
}
|
|
8364
|
+
return Array.from(authorMap.values());
|
|
8365
|
+
} catch {
|
|
8366
|
+
return [];
|
|
8367
|
+
}
|
|
8198
8368
|
}
|
|
8199
8369
|
};
|
|
8200
8370
|
}
|
|
@@ -8514,6 +8684,79 @@ var BitbucketSCMLib = class extends SCMLib {
|
|
|
8514
8684
|
async getRateLimitStatus() {
|
|
8515
8685
|
return null;
|
|
8516
8686
|
}
|
|
8687
|
+
async getRepositoryContributors() {
|
|
8688
|
+
this._validateAccessTokenAndUrl();
|
|
8689
|
+
const { workspace, repo_slug } = parseBitbucketOrganizationAndRepo(this.url);
|
|
8690
|
+
const [members, commitAuthors, currentUser] = await Promise.all([
|
|
8691
|
+
this.bitbucketSdk.getWorkspaceMembers({ workspace }),
|
|
8692
|
+
this.bitbucketSdk.getRepoCommitAuthors({ workspace, repo_slug }),
|
|
8693
|
+
this.bitbucketSdk.getCurrentUserWithEmail()
|
|
8694
|
+
]);
|
|
8695
|
+
const emailByAccountId = /* @__PURE__ */ new Map();
|
|
8696
|
+
const emailByName = /* @__PURE__ */ new Map();
|
|
8697
|
+
for (const author of commitAuthors) {
|
|
8698
|
+
if (author.accountId) {
|
|
8699
|
+
emailByAccountId.set(author.accountId, author.email);
|
|
8700
|
+
}
|
|
8701
|
+
const nameLower = author.name.toLowerCase();
|
|
8702
|
+
if (!emailByName.has(nameLower)) {
|
|
8703
|
+
emailByName.set(nameLower, author.email);
|
|
8704
|
+
}
|
|
8705
|
+
}
|
|
8706
|
+
contextLogger.info("[Bitbucket] Starting contributor enrichment", {
|
|
8707
|
+
memberCount: members.length,
|
|
8708
|
+
commitAuthorCount: commitAuthors.length,
|
|
8709
|
+
byAccountId: emailByAccountId.size,
|
|
8710
|
+
byName: emailByName.size
|
|
8711
|
+
});
|
|
8712
|
+
const result = members.map((m) => {
|
|
8713
|
+
const user = m["user"];
|
|
8714
|
+
let email = null;
|
|
8715
|
+
let emailSource = "none";
|
|
8716
|
+
if (currentUser.email && currentUser.accountId && user?.account_id === currentUser.accountId) {
|
|
8717
|
+
email = currentUser.email;
|
|
8718
|
+
emailSource = "authenticated_user";
|
|
8719
|
+
}
|
|
8720
|
+
if (!email && user?.account_id) {
|
|
8721
|
+
email = emailByAccountId.get(user.account_id) ?? null;
|
|
8722
|
+
if (email) emailSource = "commit_by_account_id";
|
|
8723
|
+
}
|
|
8724
|
+
if (!email && user?.nickname) {
|
|
8725
|
+
email = emailByName.get(user.nickname.toLowerCase()) ?? null;
|
|
8726
|
+
if (email) emailSource = "commit_by_nickname";
|
|
8727
|
+
}
|
|
8728
|
+
if (!email && user?.display_name) {
|
|
8729
|
+
email = emailByName.get(user.display_name.toLowerCase()) ?? null;
|
|
8730
|
+
if (email) emailSource = "commit_by_displayname";
|
|
8731
|
+
}
|
|
8732
|
+
if (email) {
|
|
8733
|
+
contextLogger.info("[Bitbucket] Resolved contributor email", {
|
|
8734
|
+
nickname: user?.nickname,
|
|
8735
|
+
emailSource
|
|
8736
|
+
});
|
|
8737
|
+
} else {
|
|
8738
|
+
contextLogger.debug("[Bitbucket] No email resolved for member", {
|
|
8739
|
+
nickname: user?.nickname,
|
|
8740
|
+
displayName: user?.display_name,
|
|
8741
|
+
accountId: user?.account_id
|
|
8742
|
+
});
|
|
8743
|
+
}
|
|
8744
|
+
return {
|
|
8745
|
+
externalId: user?.account_id ?? user?.uuid ?? "",
|
|
8746
|
+
username: user?.nickname ?? null,
|
|
8747
|
+
displayName: user?.display_name ?? null,
|
|
8748
|
+
email,
|
|
8749
|
+
accessLevel: null
|
|
8750
|
+
};
|
|
8751
|
+
});
|
|
8752
|
+
const withEmail = result.filter((c) => c.email);
|
|
8753
|
+
contextLogger.info("[Bitbucket] Contributor enrichment summary", {
|
|
8754
|
+
total: result.length,
|
|
8755
|
+
withEmail: withEmail.length,
|
|
8756
|
+
withoutEmail: result.length - withEmail.length
|
|
8757
|
+
});
|
|
8758
|
+
return result;
|
|
8759
|
+
}
|
|
8517
8760
|
};
|
|
8518
8761
|
|
|
8519
8762
|
// src/features/analysis/scm/constants.ts
|
|
@@ -8525,6 +8768,7 @@ var REPORT_DEFAULT_FILE_NAME = "report.json";
|
|
|
8525
8768
|
init_env();
|
|
8526
8769
|
|
|
8527
8770
|
// src/features/analysis/scm/github/GithubSCMLib.ts
|
|
8771
|
+
import pLimit3 from "p-limit";
|
|
8528
8772
|
import { z as z22 } from "zod";
|
|
8529
8773
|
init_client_generates();
|
|
8530
8774
|
|
|
@@ -9395,6 +9639,104 @@ function getGithubSdk(params = {}) {
|
|
|
9395
9639
|
totalCount: response.data.total_count,
|
|
9396
9640
|
hasMore: page * perPage < response.data.total_count
|
|
9397
9641
|
};
|
|
9642
|
+
},
|
|
9643
|
+
async listRepositoryCollaborators(params2) {
|
|
9644
|
+
const collaborators = await octokit.paginate(
|
|
9645
|
+
octokit.rest.repos.listCollaborators,
|
|
9646
|
+
{
|
|
9647
|
+
owner: params2.owner,
|
|
9648
|
+
repo: params2.repo,
|
|
9649
|
+
per_page: 100
|
|
9650
|
+
}
|
|
9651
|
+
);
|
|
9652
|
+
return collaborators;
|
|
9653
|
+
},
|
|
9654
|
+
async listOrgMembers(params2) {
|
|
9655
|
+
const members = await octokit.paginate(octokit.rest.orgs.listMembers, {
|
|
9656
|
+
org: params2.org,
|
|
9657
|
+
per_page: 100
|
|
9658
|
+
});
|
|
9659
|
+
return members;
|
|
9660
|
+
},
|
|
9661
|
+
async getUserProfile(params2) {
|
|
9662
|
+
const { data } = await octokit.rest.users.getByUsername({
|
|
9663
|
+
username: params2.username
|
|
9664
|
+
});
|
|
9665
|
+
return data;
|
|
9666
|
+
},
|
|
9667
|
+
async getLatestRepoCommitByAuthor(params2) {
|
|
9668
|
+
try {
|
|
9669
|
+
const { data } = await octokit.rest.repos.listCommits({
|
|
9670
|
+
owner: params2.owner,
|
|
9671
|
+
repo: params2.repo,
|
|
9672
|
+
author: params2.author,
|
|
9673
|
+
per_page: 1
|
|
9674
|
+
});
|
|
9675
|
+
return data[0] ?? null;
|
|
9676
|
+
} catch {
|
|
9677
|
+
return null;
|
|
9678
|
+
}
|
|
9679
|
+
},
|
|
9680
|
+
async getAuthenticatedUser() {
|
|
9681
|
+
try {
|
|
9682
|
+
const { data } = await octokit.rest.users.getAuthenticated();
|
|
9683
|
+
return { id: data.id, login: data.login, email: data.email ?? null };
|
|
9684
|
+
} catch {
|
|
9685
|
+
return null;
|
|
9686
|
+
}
|
|
9687
|
+
},
|
|
9688
|
+
async getAuthenticatedUserEmails() {
|
|
9689
|
+
try {
|
|
9690
|
+
const { data } = await octokit.rest.users.listEmailsForAuthenticatedUser({
|
|
9691
|
+
per_page: 100
|
|
9692
|
+
});
|
|
9693
|
+
return data;
|
|
9694
|
+
} catch {
|
|
9695
|
+
return [];
|
|
9696
|
+
}
|
|
9697
|
+
},
|
|
9698
|
+
async getEmailFromPublicEvents(params2) {
|
|
9699
|
+
try {
|
|
9700
|
+
const { data: events } = await octokit.rest.activity.listPublicEventsForUser({
|
|
9701
|
+
username: params2.username,
|
|
9702
|
+
per_page: 30
|
|
9703
|
+
});
|
|
9704
|
+
let fallback = null;
|
|
9705
|
+
for (const event of events) {
|
|
9706
|
+
if (event.type !== "PushEvent") continue;
|
|
9707
|
+
const payload = event.payload;
|
|
9708
|
+
if (!payload.commits) continue;
|
|
9709
|
+
for (const commit of payload.commits) {
|
|
9710
|
+
const email = commit.author?.email;
|
|
9711
|
+
if (!email) continue;
|
|
9712
|
+
if (!email.includes("noreply")) return email;
|
|
9713
|
+
if (!fallback) fallback = email;
|
|
9714
|
+
}
|
|
9715
|
+
}
|
|
9716
|
+
return fallback;
|
|
9717
|
+
} catch {
|
|
9718
|
+
return null;
|
|
9719
|
+
}
|
|
9720
|
+
},
|
|
9721
|
+
async getEmailFromCommitSearch(params2) {
|
|
9722
|
+
try {
|
|
9723
|
+
const { data } = await octokit.rest.search.commits({
|
|
9724
|
+
q: `author:${params2.username}`,
|
|
9725
|
+
sort: "author-date",
|
|
9726
|
+
order: "desc",
|
|
9727
|
+
per_page: 5
|
|
9728
|
+
});
|
|
9729
|
+
let fallback = null;
|
|
9730
|
+
for (const item of data.items) {
|
|
9731
|
+
const email = item.commit?.author?.email;
|
|
9732
|
+
if (!email) continue;
|
|
9733
|
+
if (!email.includes("noreply")) return email;
|
|
9734
|
+
if (!fallback) fallback = email;
|
|
9735
|
+
}
|
|
9736
|
+
return fallback;
|
|
9737
|
+
} catch {
|
|
9738
|
+
return null;
|
|
9739
|
+
}
|
|
9398
9740
|
}
|
|
9399
9741
|
};
|
|
9400
9742
|
}
|
|
@@ -9545,6 +9887,160 @@ var GithubSCMLib = class extends SCMLib {
|
|
|
9545
9887
|
limit: result.limit
|
|
9546
9888
|
};
|
|
9547
9889
|
}
|
|
9890
|
+
async getRepositoryContributors() {
|
|
9891
|
+
this._validateAccessTokenAndUrl();
|
|
9892
|
+
const { owner, repo } = parseGithubOwnerAndRepo(this.url);
|
|
9893
|
+
const [collaborators, authUser, authEmails] = await Promise.all([
|
|
9894
|
+
this.githubSdk.listRepositoryCollaborators({ owner, repo }),
|
|
9895
|
+
this.githubSdk.getAuthenticatedUser(),
|
|
9896
|
+
this.githubSdk.getAuthenticatedUserEmails()
|
|
9897
|
+
]);
|
|
9898
|
+
let authUserPrimaryEmail = null;
|
|
9899
|
+
if (authEmails.length > 0) {
|
|
9900
|
+
const primary = authEmails.find((e) => e.primary && e.verified);
|
|
9901
|
+
const verified = authEmails.find((e) => e.verified);
|
|
9902
|
+
authUserPrimaryEmail = primary?.email ?? verified?.email ?? authEmails[0]?.email ?? null;
|
|
9903
|
+
}
|
|
9904
|
+
const enrichLimit = pLimit3(5);
|
|
9905
|
+
const enriched = await Promise.all(
|
|
9906
|
+
collaborators.map(
|
|
9907
|
+
(c) => enrichLimit(async () => {
|
|
9908
|
+
let profileEmail = c.email ?? null;
|
|
9909
|
+
let displayName = c.login ?? null;
|
|
9910
|
+
let emailSource = profileEmail ? "collaborator_api" : "none";
|
|
9911
|
+
if (!profileEmail && authUser && authUserPrimaryEmail && c.id === authUser.id) {
|
|
9912
|
+
profileEmail = authUserPrimaryEmail;
|
|
9913
|
+
emailSource = "authenticated_user";
|
|
9914
|
+
}
|
|
9915
|
+
const isRealEmail = (e) => e && !e.includes("noreply");
|
|
9916
|
+
let noreplyFallback = null;
|
|
9917
|
+
let noreplySource = "";
|
|
9918
|
+
if (c.login) {
|
|
9919
|
+
try {
|
|
9920
|
+
const profile = await this.githubSdk.getUserProfile({
|
|
9921
|
+
username: c.login
|
|
9922
|
+
});
|
|
9923
|
+
if (profile.email) {
|
|
9924
|
+
profileEmail = profile.email;
|
|
9925
|
+
emailSource = "user_profile";
|
|
9926
|
+
}
|
|
9927
|
+
displayName = profile.name ?? displayName;
|
|
9928
|
+
} catch (err) {
|
|
9929
|
+
contextLogger.warn("[GitHub] getUserProfile failed", {
|
|
9930
|
+
username: c.login,
|
|
9931
|
+
error: err instanceof Error ? err.message : String(err)
|
|
9932
|
+
});
|
|
9933
|
+
}
|
|
9934
|
+
if (!isRealEmail(profileEmail)) {
|
|
9935
|
+
if (profileEmail && !noreplyFallback) {
|
|
9936
|
+
noreplyFallback = profileEmail;
|
|
9937
|
+
noreplySource = emailSource;
|
|
9938
|
+
}
|
|
9939
|
+
try {
|
|
9940
|
+
const commit = await this.githubSdk.getLatestRepoCommitByAuthor(
|
|
9941
|
+
{
|
|
9942
|
+
owner,
|
|
9943
|
+
repo,
|
|
9944
|
+
author: c.login
|
|
9945
|
+
}
|
|
9946
|
+
);
|
|
9947
|
+
const commitEmail = commit?.commit?.author?.email;
|
|
9948
|
+
if (commitEmail) {
|
|
9949
|
+
if (isRealEmail(commitEmail)) {
|
|
9950
|
+
profileEmail = commitEmail;
|
|
9951
|
+
emailSource = "commit_author";
|
|
9952
|
+
} else if (!noreplyFallback) {
|
|
9953
|
+
noreplyFallback = commitEmail;
|
|
9954
|
+
noreplySource = "commit_noreply";
|
|
9955
|
+
}
|
|
9956
|
+
}
|
|
9957
|
+
} catch (err) {
|
|
9958
|
+
contextLogger.warn(
|
|
9959
|
+
"[GitHub] getLatestRepoCommitByAuthor failed",
|
|
9960
|
+
{
|
|
9961
|
+
username: c.login,
|
|
9962
|
+
owner,
|
|
9963
|
+
repo,
|
|
9964
|
+
error: err instanceof Error ? err.message : String(err)
|
|
9965
|
+
}
|
|
9966
|
+
);
|
|
9967
|
+
}
|
|
9968
|
+
}
|
|
9969
|
+
if (!isRealEmail(profileEmail)) {
|
|
9970
|
+
try {
|
|
9971
|
+
const eventEmail = await this.githubSdk.getEmailFromPublicEvents({
|
|
9972
|
+
username: c.login
|
|
9973
|
+
});
|
|
9974
|
+
if (eventEmail) {
|
|
9975
|
+
if (isRealEmail(eventEmail)) {
|
|
9976
|
+
profileEmail = eventEmail;
|
|
9977
|
+
emailSource = "public_events";
|
|
9978
|
+
} else if (!noreplyFallback) {
|
|
9979
|
+
noreplyFallback = eventEmail;
|
|
9980
|
+
noreplySource = "events_noreply";
|
|
9981
|
+
}
|
|
9982
|
+
}
|
|
9983
|
+
} catch (err) {
|
|
9984
|
+
contextLogger.warn("[GitHub] getEmailFromPublicEvents failed", {
|
|
9985
|
+
username: c.login,
|
|
9986
|
+
error: err instanceof Error ? err.message : String(err)
|
|
9987
|
+
});
|
|
9988
|
+
}
|
|
9989
|
+
}
|
|
9990
|
+
if (!isRealEmail(profileEmail)) {
|
|
9991
|
+
try {
|
|
9992
|
+
const searchEmail = await this.githubSdk.getEmailFromCommitSearch({
|
|
9993
|
+
username: c.login
|
|
9994
|
+
});
|
|
9995
|
+
if (searchEmail) {
|
|
9996
|
+
if (isRealEmail(searchEmail)) {
|
|
9997
|
+
profileEmail = searchEmail;
|
|
9998
|
+
emailSource = "commit_search";
|
|
9999
|
+
} else if (!noreplyFallback) {
|
|
10000
|
+
noreplyFallback = searchEmail;
|
|
10001
|
+
noreplySource = "search_noreply";
|
|
10002
|
+
}
|
|
10003
|
+
}
|
|
10004
|
+
} catch (err) {
|
|
10005
|
+
contextLogger.warn("[GitHub] getEmailFromCommitSearch failed", {
|
|
10006
|
+
username: c.login,
|
|
10007
|
+
error: err instanceof Error ? err.message : String(err)
|
|
10008
|
+
});
|
|
10009
|
+
}
|
|
10010
|
+
}
|
|
10011
|
+
if (!isRealEmail(profileEmail) && noreplyFallback) {
|
|
10012
|
+
profileEmail = noreplyFallback;
|
|
10013
|
+
emailSource = noreplySource;
|
|
10014
|
+
}
|
|
10015
|
+
}
|
|
10016
|
+
if (profileEmail) {
|
|
10017
|
+
contextLogger.info("[GitHub] Resolved contributor email", {
|
|
10018
|
+
username: c.login,
|
|
10019
|
+
emailSource
|
|
10020
|
+
});
|
|
10021
|
+
} else {
|
|
10022
|
+
contextLogger.debug("[GitHub] No email resolved for contributor", {
|
|
10023
|
+
username: c.login
|
|
10024
|
+
});
|
|
10025
|
+
}
|
|
10026
|
+
return {
|
|
10027
|
+
externalId: String(c.id),
|
|
10028
|
+
username: c.login ?? null,
|
|
10029
|
+
displayName,
|
|
10030
|
+
email: profileEmail,
|
|
10031
|
+
accessLevel: c.role_name ?? null
|
|
10032
|
+
};
|
|
10033
|
+
})
|
|
10034
|
+
)
|
|
10035
|
+
);
|
|
10036
|
+
const withEmail = enriched.filter((c) => c.email);
|
|
10037
|
+
contextLogger.info("[GitHub] Contributor enrichment summary", {
|
|
10038
|
+
total: enriched.length,
|
|
10039
|
+
withEmail: withEmail.length,
|
|
10040
|
+
withoutEmail: enriched.length - withEmail.length
|
|
10041
|
+
});
|
|
10042
|
+
return enriched;
|
|
10043
|
+
}
|
|
9548
10044
|
get scmLibType() {
|
|
9549
10045
|
return "GITHUB" /* GITHUB */;
|
|
9550
10046
|
}
|
|
@@ -9907,72 +10403,12 @@ import {
|
|
|
9907
10403
|
Gitlab
|
|
9908
10404
|
} from "@gitbeaker/rest";
|
|
9909
10405
|
import Debug3 from "debug";
|
|
9910
|
-
import
|
|
10406
|
+
import pLimit4 from "p-limit";
|
|
9911
10407
|
import {
|
|
9912
10408
|
Agent,
|
|
9913
10409
|
fetch as undiciFetch,
|
|
9914
10410
|
ProxyAgent as ProxyAgent2
|
|
9915
10411
|
} from "undici";
|
|
9916
|
-
|
|
9917
|
-
// src/utils/contextLogger.ts
|
|
9918
|
-
import debugModule from "debug";
|
|
9919
|
-
var debug3 = debugModule("mobb:shared");
|
|
9920
|
-
var _contextLogger = null;
|
|
9921
|
-
var createContextLogger = async () => {
|
|
9922
|
-
if (_contextLogger) return _contextLogger;
|
|
9923
|
-
try {
|
|
9924
|
-
let logger3;
|
|
9925
|
-
try {
|
|
9926
|
-
let module;
|
|
9927
|
-
try {
|
|
9928
|
-
const buildPath = "../../../../../tscommon/backend/build/src/utils/logger";
|
|
9929
|
-
module = await import(buildPath);
|
|
9930
|
-
} catch (e) {
|
|
9931
|
-
const sourcePath = "../../../../../tscommon/backend/src/utils/logger";
|
|
9932
|
-
module = await import(sourcePath);
|
|
9933
|
-
}
|
|
9934
|
-
logger3 = module.logger;
|
|
9935
|
-
} catch {
|
|
9936
|
-
}
|
|
9937
|
-
if (logger3) {
|
|
9938
|
-
_contextLogger = {
|
|
9939
|
-
info: (message, data) => data ? logger3.info(data, message) : logger3.info(message),
|
|
9940
|
-
warn: (message, data) => data ? logger3.warn(data, message) : logger3.warn(message),
|
|
9941
|
-
debug: (message, data) => data ? logger3.debug(data, message) : logger3.debug(message),
|
|
9942
|
-
error: (message, data) => data ? logger3.error(data, message) : logger3.error(message)
|
|
9943
|
-
};
|
|
9944
|
-
return _contextLogger;
|
|
9945
|
-
}
|
|
9946
|
-
} catch {
|
|
9947
|
-
}
|
|
9948
|
-
_contextLogger = {
|
|
9949
|
-
info: (message, data) => debug3(message, data),
|
|
9950
|
-
warn: (message, data) => debug3(message, data),
|
|
9951
|
-
debug: (message, data) => debug3(message, data),
|
|
9952
|
-
error: (message, data) => debug3(message, data)
|
|
9953
|
-
};
|
|
9954
|
-
return _contextLogger;
|
|
9955
|
-
};
|
|
9956
|
-
var contextLogger = {
|
|
9957
|
-
info: async (message, data) => {
|
|
9958
|
-
const logger3 = await createContextLogger();
|
|
9959
|
-
return logger3.info(message, data);
|
|
9960
|
-
},
|
|
9961
|
-
debug: async (message, data) => {
|
|
9962
|
-
const logger3 = await createContextLogger();
|
|
9963
|
-
return logger3.debug(message, data);
|
|
9964
|
-
},
|
|
9965
|
-
warn: async (message, data) => {
|
|
9966
|
-
const logger3 = await createContextLogger();
|
|
9967
|
-
return logger3.warn(message, data);
|
|
9968
|
-
},
|
|
9969
|
-
error: async (message, data) => {
|
|
9970
|
-
const logger3 = await createContextLogger();
|
|
9971
|
-
return logger3.error(message, data);
|
|
9972
|
-
}
|
|
9973
|
-
};
|
|
9974
|
-
|
|
9975
|
-
// src/features/analysis/scm/gitlab/gitlab.ts
|
|
9976
10412
|
init_env();
|
|
9977
10413
|
|
|
9978
10414
|
// src/features/analysis/scm/gitlab/types.ts
|
|
@@ -10335,7 +10771,7 @@ async function getGitlabMrCommitsBatch({
|
|
|
10335
10771
|
url: repoUrl,
|
|
10336
10772
|
gitlabAuthToken: accessToken
|
|
10337
10773
|
});
|
|
10338
|
-
const limit =
|
|
10774
|
+
const limit = pLimit4(GITLAB_API_CONCURRENCY);
|
|
10339
10775
|
const results = await Promise.all(
|
|
10340
10776
|
mrNumbers.map(
|
|
10341
10777
|
(mrNumber) => limit(async () => {
|
|
@@ -10374,7 +10810,7 @@ async function getGitlabMrDataBatch({
|
|
|
10374
10810
|
url: repoUrl,
|
|
10375
10811
|
gitlabAuthToken: accessToken
|
|
10376
10812
|
});
|
|
10377
|
-
const limit =
|
|
10813
|
+
const limit = pLimit4(GITLAB_API_CONCURRENCY);
|
|
10378
10814
|
const results = await Promise.all(
|
|
10379
10815
|
mrNumbers.map(
|
|
10380
10816
|
(mrNumber) => limit(async () => {
|
|
@@ -10651,18 +11087,86 @@ async function brokerRequestHandler(endpoint, options) {
|
|
|
10651
11087
|
};
|
|
10652
11088
|
throw new Error(`gitbeaker: ${response.statusText}`);
|
|
10653
11089
|
}
|
|
10654
|
-
|
|
10655
|
-
|
|
10656
|
-
|
|
10657
|
-
|
|
10658
|
-
|
|
10659
|
-
|
|
10660
|
-
|
|
10661
|
-
|
|
10662
|
-
|
|
10663
|
-
|
|
10664
|
-
|
|
10665
|
-
|
|
11090
|
+
async function listGitlabProjectMembers({
|
|
11091
|
+
repoUrl,
|
|
11092
|
+
accessToken
|
|
11093
|
+
}) {
|
|
11094
|
+
const { owner, projectPath } = parseGitlabOwnerAndRepo(repoUrl);
|
|
11095
|
+
const api2 = getGitBeaker({ url: repoUrl, gitlabAuthToken: accessToken });
|
|
11096
|
+
const projectMembers = await api2.ProjectMembers.all(projectPath, {
|
|
11097
|
+
includeInherited: true
|
|
11098
|
+
});
|
|
11099
|
+
if (projectMembers.length > 1 || !owner) return projectMembers;
|
|
11100
|
+
try {
|
|
11101
|
+
const groupMembers = await api2.GroupMembers.all(owner, {
|
|
11102
|
+
includeInherited: true
|
|
11103
|
+
});
|
|
11104
|
+
if (groupMembers.length > projectMembers.length) {
|
|
11105
|
+
return groupMembers;
|
|
11106
|
+
}
|
|
11107
|
+
} catch {
|
|
11108
|
+
}
|
|
11109
|
+
return projectMembers;
|
|
11110
|
+
}
|
|
11111
|
+
async function getGitlabUserPublicEmail({
|
|
11112
|
+
repoUrl,
|
|
11113
|
+
accessToken,
|
|
11114
|
+
userId
|
|
11115
|
+
}) {
|
|
11116
|
+
try {
|
|
11117
|
+
const api2 = getGitBeaker({ url: repoUrl, gitlabAuthToken: accessToken });
|
|
11118
|
+
const user = await api2.Users.show(userId);
|
|
11119
|
+
return user["public_email"];
|
|
11120
|
+
} catch {
|
|
11121
|
+
return null;
|
|
11122
|
+
}
|
|
11123
|
+
}
|
|
11124
|
+
async function listGitlabRepoContributors({
|
|
11125
|
+
repoUrl,
|
|
11126
|
+
accessToken
|
|
11127
|
+
}) {
|
|
11128
|
+
try {
|
|
11129
|
+
const { projectPath } = parseGitlabOwnerAndRepo(repoUrl);
|
|
11130
|
+
const api2 = getGitBeaker({ url: repoUrl, gitlabAuthToken: accessToken });
|
|
11131
|
+
const contributors = await api2.Repositories.allContributors(projectPath);
|
|
11132
|
+
return contributors.map((c) => ({
|
|
11133
|
+
name: c.name,
|
|
11134
|
+
email: c.email,
|
|
11135
|
+
commits: c.commits
|
|
11136
|
+
}));
|
|
11137
|
+
} catch {
|
|
11138
|
+
return [];
|
|
11139
|
+
}
|
|
11140
|
+
}
|
|
11141
|
+
async function getGitlabAuthenticatedUser({
|
|
11142
|
+
repoUrl,
|
|
11143
|
+
accessToken
|
|
11144
|
+
}) {
|
|
11145
|
+
try {
|
|
11146
|
+
const api2 = getGitBeaker({ url: repoUrl, gitlabAuthToken: accessToken });
|
|
11147
|
+
const user = await api2.Users.showCurrentUser();
|
|
11148
|
+
const record = user;
|
|
11149
|
+
return {
|
|
11150
|
+
id: record["id"],
|
|
11151
|
+
email: record["email"] ?? record["public_email"] ?? null
|
|
11152
|
+
};
|
|
11153
|
+
} catch {
|
|
11154
|
+
return null;
|
|
11155
|
+
}
|
|
11156
|
+
}
|
|
11157
|
+
|
|
11158
|
+
// src/features/analysis/scm/gitlab/GitlabSCMLib.ts
|
|
11159
|
+
import pLimit5 from "p-limit";
|
|
11160
|
+
init_client_generates();
|
|
11161
|
+
var GitlabSCMLib = class extends SCMLib {
|
|
11162
|
+
constructor(url, accessToken, scmOrg) {
|
|
11163
|
+
super(url, accessToken, scmOrg);
|
|
11164
|
+
}
|
|
11165
|
+
async createSubmitRequest(params) {
|
|
11166
|
+
this._validateAccessTokenAndUrl();
|
|
11167
|
+
const { targetBranchName, sourceBranchName, title, body } = params;
|
|
11168
|
+
return String(
|
|
11169
|
+
await createMergeRequest({
|
|
10666
11170
|
title,
|
|
10667
11171
|
body,
|
|
10668
11172
|
targetBranchName,
|
|
@@ -11019,6 +11523,93 @@ var GitlabSCMLib = class extends SCMLib {
|
|
|
11019
11523
|
accessToken: this.accessToken
|
|
11020
11524
|
});
|
|
11021
11525
|
}
|
|
11526
|
+
async getRepositoryContributors() {
|
|
11527
|
+
this._validateAccessTokenAndUrl();
|
|
11528
|
+
const [members, repoContributors, authUser] = await Promise.all([
|
|
11529
|
+
listGitlabProjectMembers({
|
|
11530
|
+
repoUrl: this.url,
|
|
11531
|
+
accessToken: this.accessToken
|
|
11532
|
+
}),
|
|
11533
|
+
listGitlabRepoContributors({
|
|
11534
|
+
repoUrl: this.url,
|
|
11535
|
+
accessToken: this.accessToken
|
|
11536
|
+
}),
|
|
11537
|
+
getGitlabAuthenticatedUser({
|
|
11538
|
+
repoUrl: this.url,
|
|
11539
|
+
accessToken: this.accessToken
|
|
11540
|
+
})
|
|
11541
|
+
]);
|
|
11542
|
+
contextLogger.info("[GitLab] Starting contributor enrichment", {
|
|
11543
|
+
memberCount: members.length,
|
|
11544
|
+
repoContributorCount: repoContributors.length
|
|
11545
|
+
});
|
|
11546
|
+
const enrichLimit = pLimit5(5);
|
|
11547
|
+
const enriched = await Promise.all(
|
|
11548
|
+
members.map(
|
|
11549
|
+
(m) => enrichLimit(async () => {
|
|
11550
|
+
let email = null;
|
|
11551
|
+
let emailSource = "none";
|
|
11552
|
+
if (authUser?.email && authUser.id === m.id) {
|
|
11553
|
+
email = authUser.email;
|
|
11554
|
+
emailSource = "authenticated_user";
|
|
11555
|
+
}
|
|
11556
|
+
if (!email) {
|
|
11557
|
+
try {
|
|
11558
|
+
email = await getGitlabUserPublicEmail({
|
|
11559
|
+
repoUrl: this.url,
|
|
11560
|
+
accessToken: this.accessToken,
|
|
11561
|
+
userId: m.id
|
|
11562
|
+
});
|
|
11563
|
+
if (email) emailSource = "public_email";
|
|
11564
|
+
} catch (err) {
|
|
11565
|
+
contextLogger.warn("[GitLab] getGitlabUserPublicEmail failed", {
|
|
11566
|
+
username: m.username,
|
|
11567
|
+
userId: m.id,
|
|
11568
|
+
error: err instanceof Error ? err.message : String(err)
|
|
11569
|
+
});
|
|
11570
|
+
}
|
|
11571
|
+
}
|
|
11572
|
+
if (!email && m.username) {
|
|
11573
|
+
const match = repoContributors.find(
|
|
11574
|
+
(rc) => rc.name?.toLowerCase() === m.username?.toLowerCase() || rc.name?.toLowerCase() === m.name?.toLowerCase()
|
|
11575
|
+
);
|
|
11576
|
+
if (match?.email) {
|
|
11577
|
+
email = match.email;
|
|
11578
|
+
emailSource = match.email.includes("noreply") ? "commit_noreply" : "commit_author";
|
|
11579
|
+
} else {
|
|
11580
|
+
contextLogger.debug(
|
|
11581
|
+
"[GitLab] No commit author match for member",
|
|
11582
|
+
{
|
|
11583
|
+
username: m.username,
|
|
11584
|
+
displayName: m.name
|
|
11585
|
+
}
|
|
11586
|
+
);
|
|
11587
|
+
}
|
|
11588
|
+
}
|
|
11589
|
+
if (email) {
|
|
11590
|
+
contextLogger.info("[GitLab] Resolved contributor email", {
|
|
11591
|
+
username: m.username,
|
|
11592
|
+
emailSource
|
|
11593
|
+
});
|
|
11594
|
+
}
|
|
11595
|
+
return {
|
|
11596
|
+
externalId: String(m.id),
|
|
11597
|
+
username: m.username ?? null,
|
|
11598
|
+
displayName: m.name ?? null,
|
|
11599
|
+
email,
|
|
11600
|
+
accessLevel: m.access_level != null ? String(m.access_level) : null
|
|
11601
|
+
};
|
|
11602
|
+
})
|
|
11603
|
+
)
|
|
11604
|
+
);
|
|
11605
|
+
const withEmail = enriched.filter((c) => c.email);
|
|
11606
|
+
contextLogger.info("[GitLab] Contributor enrichment summary", {
|
|
11607
|
+
total: enriched.length,
|
|
11608
|
+
withEmail: withEmail.length,
|
|
11609
|
+
withoutEmail: enriched.length - withEmail.length
|
|
11610
|
+
});
|
|
11611
|
+
return enriched;
|
|
11612
|
+
}
|
|
11022
11613
|
};
|
|
11023
11614
|
|
|
11024
11615
|
// src/features/analysis/scm/scmFactory.ts
|
|
@@ -11130,6 +11721,10 @@ var StubSCMLib = class extends SCMLib {
|
|
|
11130
11721
|
console.warn("getRateLimitStatus() returning null");
|
|
11131
11722
|
return null;
|
|
11132
11723
|
}
|
|
11724
|
+
async getRepositoryContributors() {
|
|
11725
|
+
console.warn("getRepositoryContributors() returning empty array");
|
|
11726
|
+
return [];
|
|
11727
|
+
}
|
|
11133
11728
|
};
|
|
11134
11729
|
|
|
11135
11730
|
// src/features/analysis/scm/scmFactory.ts
|
|
@@ -12041,6 +12636,7 @@ var mobbCliCommand = {
|
|
|
12041
12636
|
uploadAiBlame: "upload-ai-blame",
|
|
12042
12637
|
claudeCodeInstallHook: "claude-code-install-hook",
|
|
12043
12638
|
claudeCodeProcessHook: "claude-code-process-hook",
|
|
12639
|
+
claudeCodeDaemon: "claude-code-daemon",
|
|
12044
12640
|
windsurfIntellijInstallHook: "windsurf-intellij-install-hook",
|
|
12045
12641
|
windsurfIntellijProcessHook: "windsurf-intellij-process-hook",
|
|
12046
12642
|
scanSkill: "scan-skill"
|
|
@@ -12986,7 +13582,7 @@ async function handleMobbLogin({
|
|
|
12986
13582
|
});
|
|
12987
13583
|
throw new CliError("Failed to generate login URL");
|
|
12988
13584
|
}
|
|
12989
|
-
|
|
13585
|
+
console.log(
|
|
12990
13586
|
`If the page does not open automatically, kindly access it through ${loginUrl}.`
|
|
12991
13587
|
);
|
|
12992
13588
|
authManager.openUrlInBrowser();
|
|
@@ -14415,7 +15011,7 @@ async function postIssueComment(params) {
|
|
|
14415
15011
|
fpDescription
|
|
14416
15012
|
} = params;
|
|
14417
15013
|
const {
|
|
14418
|
-
path:
|
|
15014
|
+
path: path30,
|
|
14419
15015
|
startLine,
|
|
14420
15016
|
vulnerabilityReportIssue: {
|
|
14421
15017
|
vulnerabilityReportIssueTags,
|
|
@@ -14430,7 +15026,7 @@ async function postIssueComment(params) {
|
|
|
14430
15026
|
Refresh the page in order to see the changes.`,
|
|
14431
15027
|
pull_number: pullRequest,
|
|
14432
15028
|
commit_id: commitSha,
|
|
14433
|
-
path:
|
|
15029
|
+
path: path30,
|
|
14434
15030
|
line: startLine
|
|
14435
15031
|
});
|
|
14436
15032
|
const commentId = commentRes.data.id;
|
|
@@ -14464,7 +15060,7 @@ async function postFixComment(params) {
|
|
|
14464
15060
|
scanner
|
|
14465
15061
|
} = params;
|
|
14466
15062
|
const {
|
|
14467
|
-
path:
|
|
15063
|
+
path: path30,
|
|
14468
15064
|
startLine,
|
|
14469
15065
|
vulnerabilityReportIssue: { fixId, vulnerabilityReportIssueTags, category },
|
|
14470
15066
|
vulnerabilityReportIssueId
|
|
@@ -14482,7 +15078,7 @@ async function postFixComment(params) {
|
|
|
14482
15078
|
Refresh the page in order to see the changes.`,
|
|
14483
15079
|
pull_number: pullRequest,
|
|
14484
15080
|
commit_id: commitSha,
|
|
14485
|
-
path:
|
|
15081
|
+
path: path30,
|
|
14486
15082
|
line: startLine
|
|
14487
15083
|
});
|
|
14488
15084
|
const commentId = commentRes.data.id;
|
|
@@ -15086,8 +15682,8 @@ if (typeof __filename !== "undefined") {
|
|
|
15086
15682
|
}
|
|
15087
15683
|
var costumeRequire = createRequire(moduleUrl);
|
|
15088
15684
|
var getCheckmarxPath = () => {
|
|
15089
|
-
const
|
|
15090
|
-
const cxFileName =
|
|
15685
|
+
const os16 = type();
|
|
15686
|
+
const cxFileName = os16 === "Windows_NT" ? "cx.exe" : "cx";
|
|
15091
15687
|
try {
|
|
15092
15688
|
return costumeRequire.resolve(`.bin/${cxFileName}`);
|
|
15093
15689
|
} catch (e) {
|
|
@@ -16004,8 +16600,8 @@ async function resolveSkillScanInput(skillInput) {
|
|
|
16004
16600
|
if (!fs11.existsSync(resolvedPath)) {
|
|
16005
16601
|
return skillInput;
|
|
16006
16602
|
}
|
|
16007
|
-
const
|
|
16008
|
-
if (!
|
|
16603
|
+
const stat2 = fs11.statSync(resolvedPath);
|
|
16604
|
+
if (!stat2.isDirectory()) {
|
|
16009
16605
|
throw new CliError(
|
|
16010
16606
|
"Local skill input must be a directory containing SKILL.md"
|
|
16011
16607
|
);
|
|
@@ -16399,13 +16995,107 @@ async function analyzeHandler(args) {
|
|
|
16399
16995
|
await analyze(args, { skipPrompts: args.yes });
|
|
16400
16996
|
}
|
|
16401
16997
|
|
|
16998
|
+
// src/args/commands/claude_code.ts
|
|
16999
|
+
import { spawn } from "child_process";
|
|
17000
|
+
|
|
17001
|
+
// src/features/claude_code/daemon.ts
|
|
17002
|
+
import path17 from "path";
|
|
17003
|
+
import { setTimeout as sleep2 } from "timers/promises";
|
|
17004
|
+
|
|
17005
|
+
// src/features/claude_code/daemon_pid_file.ts
|
|
17006
|
+
import fs13 from "fs";
|
|
17007
|
+
import os4 from "os";
|
|
17008
|
+
import path13 from "path";
|
|
17009
|
+
|
|
17010
|
+
// src/features/claude_code/data_collector_constants.ts
|
|
17011
|
+
var CC_VERSION_CACHE_KEY = "claudeCode.detectedCCVersion";
|
|
17012
|
+
var CC_VERSION_CLI_KEY = "claudeCode.detectedCCVersionCli";
|
|
17013
|
+
var GQL_AUTH_TIMEOUT_MS = 15e3;
|
|
17014
|
+
var STALE_KEY_MAX_AGE_MS = 14 * 24 * 60 * 60 * 1e3;
|
|
17015
|
+
var CLEANUP_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
17016
|
+
var DAEMON_TTL_MS = 30 * 60 * 1e3;
|
|
17017
|
+
var DAEMON_POLL_INTERVAL_MS = 1e4;
|
|
17018
|
+
var HEARTBEAT_STALE_MS = 3e4;
|
|
17019
|
+
var TRANSCRIPT_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
17020
|
+
var DAEMON_CHUNK_SIZE = 50;
|
|
17021
|
+
|
|
17022
|
+
// src/features/claude_code/daemon_pid_file.ts
|
|
17023
|
+
function getMobbdevDir() {
|
|
17024
|
+
return path13.join(os4.homedir(), ".mobbdev");
|
|
17025
|
+
}
|
|
17026
|
+
function getDaemonCheckScriptPath() {
|
|
17027
|
+
return path13.join(getMobbdevDir(), "daemon-check.js");
|
|
17028
|
+
}
|
|
17029
|
+
var DaemonPidFile = class {
|
|
17030
|
+
constructor() {
|
|
17031
|
+
__publicField(this, "data", null);
|
|
17032
|
+
}
|
|
17033
|
+
get filePath() {
|
|
17034
|
+
return path13.join(getMobbdevDir(), "daemon.pid");
|
|
17035
|
+
}
|
|
17036
|
+
/** Ensure ~/.mobbdev/ directory exists. */
|
|
17037
|
+
ensureDir() {
|
|
17038
|
+
fs13.mkdirSync(getMobbdevDir(), { recursive: true });
|
|
17039
|
+
}
|
|
17040
|
+
/** Read the PID file from disk. Returns the parsed data or null. */
|
|
17041
|
+
read() {
|
|
17042
|
+
try {
|
|
17043
|
+
const raw = fs13.readFileSync(this.filePath, "utf8");
|
|
17044
|
+
const parsed = JSON.parse(raw);
|
|
17045
|
+
if (typeof parsed.pid !== "number" || typeof parsed.startedAt !== "number" || typeof parsed.heartbeat !== "number") {
|
|
17046
|
+
this.data = null;
|
|
17047
|
+
} else {
|
|
17048
|
+
this.data = parsed;
|
|
17049
|
+
}
|
|
17050
|
+
} catch {
|
|
17051
|
+
this.data = null;
|
|
17052
|
+
}
|
|
17053
|
+
return this.data;
|
|
17054
|
+
}
|
|
17055
|
+
/** Write a new PID file for the given process id. */
|
|
17056
|
+
write(pid, version) {
|
|
17057
|
+
this.data = {
|
|
17058
|
+
pid,
|
|
17059
|
+
startedAt: Date.now(),
|
|
17060
|
+
heartbeat: Date.now(),
|
|
17061
|
+
version
|
|
17062
|
+
};
|
|
17063
|
+
fs13.writeFileSync(this.filePath, JSON.stringify(this.data), "utf8");
|
|
17064
|
+
}
|
|
17065
|
+
/** Update the heartbeat timestamp of the current PID file. */
|
|
17066
|
+
updateHeartbeat() {
|
|
17067
|
+
if (!this.data) this.read();
|
|
17068
|
+
if (!this.data) return;
|
|
17069
|
+
this.data.heartbeat = Date.now();
|
|
17070
|
+
fs13.writeFileSync(this.filePath, JSON.stringify(this.data), "utf8");
|
|
17071
|
+
}
|
|
17072
|
+
/** Check whether the previously read PID data represents a live daemon. */
|
|
17073
|
+
isAlive() {
|
|
17074
|
+
if (!this.data) return false;
|
|
17075
|
+
if (Date.now() - this.data.heartbeat > HEARTBEAT_STALE_MS) return false;
|
|
17076
|
+
try {
|
|
17077
|
+
process.kill(this.data.pid, 0);
|
|
17078
|
+
return true;
|
|
17079
|
+
} catch {
|
|
17080
|
+
return false;
|
|
17081
|
+
}
|
|
17082
|
+
}
|
|
17083
|
+
/** Remove the PID file from disk (best-effort). */
|
|
17084
|
+
remove() {
|
|
17085
|
+
this.data = null;
|
|
17086
|
+
try {
|
|
17087
|
+
fs13.unlinkSync(this.filePath);
|
|
17088
|
+
} catch {
|
|
17089
|
+
}
|
|
17090
|
+
}
|
|
17091
|
+
};
|
|
17092
|
+
|
|
16402
17093
|
// src/features/claude_code/data_collector.ts
|
|
16403
17094
|
import { execFile } from "child_process";
|
|
16404
17095
|
import { createHash as createHash2 } from "crypto";
|
|
16405
17096
|
import { access, open as open4, readdir, readFile, unlink } from "fs/promises";
|
|
16406
17097
|
import path14 from "path";
|
|
16407
17098
|
import { promisify } from "util";
|
|
16408
|
-
import { z as z33 } from "zod";
|
|
16409
17099
|
init_client_generates();
|
|
16410
17100
|
|
|
16411
17101
|
// src/utils/shared-logger/create-logger.ts
|
|
@@ -16485,8 +17175,8 @@ function createConfigstoreStream(store, opts) {
|
|
|
16485
17175
|
heartbeatBuffer.length = 0;
|
|
16486
17176
|
}
|
|
16487
17177
|
}
|
|
16488
|
-
function setScopePath(
|
|
16489
|
-
scopePath =
|
|
17178
|
+
function setScopePath(path30) {
|
|
17179
|
+
scopePath = path30;
|
|
16490
17180
|
}
|
|
16491
17181
|
return { writable, flush, setScopePath };
|
|
16492
17182
|
}
|
|
@@ -16568,22 +17258,22 @@ function createDdBatch(config2) {
|
|
|
16568
17258
|
|
|
16569
17259
|
// src/utils/shared-logger/hostname.ts
|
|
16570
17260
|
import { createHash } from "crypto";
|
|
16571
|
-
import
|
|
17261
|
+
import os5 from "os";
|
|
16572
17262
|
function hashString(input) {
|
|
16573
17263
|
return createHash("sha256").update(input).digest("hex").slice(0, 16);
|
|
16574
17264
|
}
|
|
16575
17265
|
function getPlainHostname() {
|
|
16576
17266
|
try {
|
|
16577
|
-
return `${
|
|
17267
|
+
return `${os5.userInfo().username}@${os5.hostname()}`;
|
|
16578
17268
|
} catch {
|
|
16579
|
-
return `unknown@${
|
|
17269
|
+
return `unknown@${os5.hostname()}`;
|
|
16580
17270
|
}
|
|
16581
17271
|
}
|
|
16582
17272
|
function getHashedHostname() {
|
|
16583
17273
|
try {
|
|
16584
|
-
return `${hashString(
|
|
17274
|
+
return `${hashString(os5.userInfo().username)}@${hashString(os5.hostname())}`;
|
|
16585
17275
|
} catch {
|
|
16586
|
-
return `unknown@${hashString(
|
|
17276
|
+
return `unknown@${hashString(os5.hostname())}`;
|
|
16587
17277
|
}
|
|
16588
17278
|
}
|
|
16589
17279
|
|
|
@@ -16596,15 +17286,19 @@ function createLogger(config2) {
|
|
|
16596
17286
|
maxLogs,
|
|
16597
17287
|
maxHeartbeat,
|
|
16598
17288
|
dd,
|
|
16599
|
-
additionalStreams = []
|
|
17289
|
+
additionalStreams = [],
|
|
17290
|
+
enableConfigstore = true
|
|
16600
17291
|
} = config2;
|
|
16601
|
-
|
|
16602
|
-
|
|
16603
|
-
|
|
16604
|
-
|
|
16605
|
-
|
|
16606
|
-
|
|
16607
|
-
|
|
17292
|
+
let csStream = null;
|
|
17293
|
+
if (enableConfigstore) {
|
|
17294
|
+
const store = new Configstore2(namespace, {});
|
|
17295
|
+
csStream = createConfigstoreStream(store, {
|
|
17296
|
+
buffered,
|
|
17297
|
+
scopePath,
|
|
17298
|
+
maxLogs,
|
|
17299
|
+
maxHeartbeat
|
|
17300
|
+
});
|
|
17301
|
+
}
|
|
16608
17302
|
let ddBatch = null;
|
|
16609
17303
|
if (dd) {
|
|
16610
17304
|
const hostname = dd.hostnameMode === "hashed" ? getHashedHostname() : getPlainHostname();
|
|
@@ -16619,9 +17313,10 @@ function createLogger(config2) {
|
|
|
16619
17313
|
};
|
|
16620
17314
|
ddBatch = createDdBatch(ddConfig);
|
|
16621
17315
|
}
|
|
16622
|
-
const streams = [
|
|
16623
|
-
|
|
16624
|
-
|
|
17316
|
+
const streams = [];
|
|
17317
|
+
if (csStream) {
|
|
17318
|
+
streams.push({ stream: csStream.writable, level: "info" });
|
|
17319
|
+
}
|
|
16625
17320
|
if (ddBatch) {
|
|
16626
17321
|
streams.push({ stream: ddBatch.createPinoStream(), level: "info" });
|
|
16627
17322
|
}
|
|
@@ -16633,6 +17328,11 @@ function createLogger(config2) {
|
|
|
16633
17328
|
}
|
|
16634
17329
|
const pinoLogger = pino(
|
|
16635
17330
|
{
|
|
17331
|
+
serializers: {
|
|
17332
|
+
err: pino.stdSerializers.err,
|
|
17333
|
+
error: pino.stdSerializers.err,
|
|
17334
|
+
e: pino.stdSerializers.err
|
|
17335
|
+
},
|
|
16636
17336
|
formatters: {
|
|
16637
17337
|
level: (label) => ({ level: label })
|
|
16638
17338
|
}
|
|
@@ -16663,8 +17363,8 @@ function createLogger(config2) {
|
|
|
16663
17363
|
throw err;
|
|
16664
17364
|
}
|
|
16665
17365
|
}
|
|
16666
|
-
function
|
|
16667
|
-
csStream
|
|
17366
|
+
function flushLogs() {
|
|
17367
|
+
csStream?.flush();
|
|
16668
17368
|
}
|
|
16669
17369
|
async function flushDdAsync() {
|
|
16670
17370
|
if (ddBatch) {
|
|
@@ -16688,17 +17388,19 @@ function createLogger(config2) {
|
|
|
16688
17388
|
debug: debug23,
|
|
16689
17389
|
heartbeat,
|
|
16690
17390
|
timed,
|
|
16691
|
-
flushLogs
|
|
17391
|
+
flushLogs,
|
|
16692
17392
|
flushDdAsync,
|
|
16693
17393
|
disposeDd,
|
|
16694
|
-
|
|
17394
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
17395
|
+
setScopePath: csStream?.setScopePath ?? (() => {
|
|
17396
|
+
}),
|
|
16695
17397
|
updateDdTags
|
|
16696
17398
|
};
|
|
16697
17399
|
}
|
|
16698
17400
|
|
|
16699
17401
|
// src/features/claude_code/hook_logger.ts
|
|
16700
17402
|
var DD_RUM_TOKEN = true ? "pubf59c0182545bfb4c299175119f1abf9b" : "";
|
|
16701
|
-
var CLI_VERSION = true ? "1.
|
|
17403
|
+
var CLI_VERSION = true ? "1.3.0" : "unknown";
|
|
16702
17404
|
var NAMESPACE = "mobbdev-claude-code-hook-logs";
|
|
16703
17405
|
var claudeCodeVersion;
|
|
16704
17406
|
function buildDdTags() {
|
|
@@ -16708,10 +17410,11 @@ function buildDdTags() {
|
|
|
16708
17410
|
}
|
|
16709
17411
|
return tags.join(",");
|
|
16710
17412
|
}
|
|
16711
|
-
function createHookLogger(
|
|
17413
|
+
function createHookLogger(opts) {
|
|
16712
17414
|
return createLogger({
|
|
16713
17415
|
namespace: NAMESPACE,
|
|
16714
|
-
scopePath,
|
|
17416
|
+
scopePath: opts?.scopePath,
|
|
17417
|
+
enableConfigstore: opts?.enableConfigstore,
|
|
16715
17418
|
dd: {
|
|
16716
17419
|
apiKey: DD_RUM_TOKEN,
|
|
16717
17420
|
ddsource: "mobbdev-cli",
|
|
@@ -16724,6 +17427,7 @@ function createHookLogger(scopePath) {
|
|
|
16724
17427
|
}
|
|
16725
17428
|
var logger = createHookLogger();
|
|
16726
17429
|
var activeScopedLoggers = [];
|
|
17430
|
+
var scopedLoggerCache = /* @__PURE__ */ new Map();
|
|
16727
17431
|
var hookLog = logger;
|
|
16728
17432
|
function setClaudeCodeVersion(version) {
|
|
16729
17433
|
claudeCodeVersion = version;
|
|
@@ -16732,302 +17436,83 @@ function setClaudeCodeVersion(version) {
|
|
|
16732
17436
|
function getClaudeCodeVersion() {
|
|
16733
17437
|
return claudeCodeVersion;
|
|
16734
17438
|
}
|
|
16735
|
-
function flushLogs() {
|
|
16736
|
-
logger.flushLogs();
|
|
16737
|
-
for (const scoped of activeScopedLoggers) {
|
|
16738
|
-
scoped.flushLogs();
|
|
16739
|
-
}
|
|
16740
|
-
}
|
|
16741
17439
|
async function flushDdLogs() {
|
|
16742
17440
|
await logger.flushDdAsync();
|
|
16743
17441
|
for (const scoped of activeScopedLoggers) {
|
|
16744
17442
|
await scoped.flushDdAsync();
|
|
16745
17443
|
}
|
|
16746
|
-
activeScopedLoggers.length = 0;
|
|
16747
17444
|
}
|
|
16748
|
-
function createScopedHookLog(scopePath) {
|
|
16749
|
-
const
|
|
17445
|
+
function createScopedHookLog(scopePath, opts) {
|
|
17446
|
+
const cached = scopedLoggerCache.get(scopePath);
|
|
17447
|
+
if (cached) return cached;
|
|
17448
|
+
const scoped = createHookLogger({
|
|
17449
|
+
scopePath,
|
|
17450
|
+
enableConfigstore: opts?.daemonMode ? false : void 0
|
|
17451
|
+
});
|
|
17452
|
+
scopedLoggerCache.set(scopePath, scoped);
|
|
16750
17453
|
activeScopedLoggers.push(scoped);
|
|
16751
17454
|
return scoped;
|
|
16752
17455
|
}
|
|
16753
17456
|
|
|
16754
|
-
// src/features/claude_code/
|
|
16755
|
-
|
|
16756
|
-
|
|
16757
|
-
|
|
16758
|
-
|
|
16759
|
-
|
|
16760
|
-
|
|
16761
|
-
async function claudeSettingsExists() {
|
|
17457
|
+
// src/features/claude_code/data_collector.ts
|
|
17458
|
+
var execFileAsync = promisify(execFile);
|
|
17459
|
+
async function detectClaudeCodeVersion() {
|
|
17460
|
+
const cachedCliVersion = configStore.get(CC_VERSION_CLI_KEY);
|
|
17461
|
+
if (cachedCliVersion === packageJson.version) {
|
|
17462
|
+
return configStore.get(CC_VERSION_CACHE_KEY);
|
|
17463
|
+
}
|
|
16762
17464
|
try {
|
|
16763
|
-
await
|
|
16764
|
-
|
|
17465
|
+
const { stdout: stdout2 } = await execFileAsync("claude", ["--version"], {
|
|
17466
|
+
timeout: 3e3,
|
|
17467
|
+
encoding: "utf-8"
|
|
17468
|
+
});
|
|
17469
|
+
const version = stdout2.trim().split(/\s/)[0] || stdout2.trim();
|
|
17470
|
+
configStore.set(CC_VERSION_CACHE_KEY, version);
|
|
17471
|
+
configStore.set(CC_VERSION_CLI_KEY, packageJson.version);
|
|
17472
|
+
return version;
|
|
16765
17473
|
} catch {
|
|
16766
|
-
|
|
17474
|
+
configStore.set(CC_VERSION_CACHE_KEY, void 0);
|
|
17475
|
+
configStore.set(CC_VERSION_CLI_KEY, packageJson.version);
|
|
17476
|
+
return void 0;
|
|
16767
17477
|
}
|
|
16768
17478
|
}
|
|
16769
|
-
|
|
16770
|
-
const
|
|
16771
|
-
|
|
16772
|
-
|
|
16773
|
-
);
|
|
16774
|
-
return JSON.parse(settingsContent);
|
|
17479
|
+
function generateSyntheticId(sessionId, timestamp, type2, lineIndex) {
|
|
17480
|
+
const input = `${sessionId ?? ""}:${timestamp ?? ""}:${type2 ?? ""}:${lineIndex}`;
|
|
17481
|
+
const hash = createHash2("sha256").update(input).digest("hex").slice(0, 16);
|
|
17482
|
+
return `synth:${hash}`;
|
|
16775
17483
|
}
|
|
16776
|
-
|
|
16777
|
-
|
|
16778
|
-
|
|
16779
|
-
JSON.stringify(settings, null, 2),
|
|
16780
|
-
"utf-8"
|
|
16781
|
-
);
|
|
17484
|
+
function getCursorKey(transcriptPath) {
|
|
17485
|
+
const hash = createHash2("sha256").update(transcriptPath).digest("hex").slice(0, 12);
|
|
17486
|
+
return `cursor.${hash}`;
|
|
16782
17487
|
}
|
|
16783
|
-
async function
|
|
17488
|
+
async function resolveTranscriptPath(transcriptPath, sessionId) {
|
|
16784
17489
|
try {
|
|
16785
|
-
|
|
16786
|
-
|
|
16787
|
-
const hooks = settings.hooks?.PostToolUse;
|
|
16788
|
-
if (!hooks) return false;
|
|
16789
|
-
let upgraded = false;
|
|
16790
|
-
for (const hook of hooks) {
|
|
16791
|
-
const isMobbHook = hook.hooks.some(
|
|
16792
|
-
(h) => h.command?.includes("claude-code-process-hook")
|
|
16793
|
-
);
|
|
16794
|
-
if (!isMobbHook) continue;
|
|
16795
|
-
if (hook.matcher !== RECOMMENDED_MATCHER) {
|
|
16796
|
-
hook.matcher = RECOMMENDED_MATCHER;
|
|
16797
|
-
upgraded = true;
|
|
16798
|
-
}
|
|
16799
|
-
for (const h of hook.hooks) {
|
|
16800
|
-
if (!h.async) {
|
|
16801
|
-
h.async = true;
|
|
16802
|
-
upgraded = true;
|
|
16803
|
-
}
|
|
16804
|
-
}
|
|
16805
|
-
}
|
|
16806
|
-
if (upgraded) {
|
|
16807
|
-
await writeClaudeSettings(settings);
|
|
16808
|
-
}
|
|
16809
|
-
return upgraded;
|
|
17490
|
+
await access(transcriptPath);
|
|
17491
|
+
return transcriptPath;
|
|
16810
17492
|
} catch {
|
|
16811
|
-
return false;
|
|
16812
17493
|
}
|
|
16813
|
-
|
|
16814
|
-
|
|
16815
|
-
|
|
16816
|
-
|
|
16817
|
-
|
|
16818
|
-
|
|
16819
|
-
|
|
16820
|
-
|
|
16821
|
-
|
|
16822
|
-
|
|
16823
|
-
|
|
16824
|
-
|
|
16825
|
-
|
|
16826
|
-
|
|
16827
|
-
|
|
16828
|
-
|
|
16829
|
-
|
|
16830
|
-
|
|
16831
|
-
|
|
16832
|
-
|
|
16833
|
-
|
|
16834
|
-
|
|
16835
|
-
if (process.env["WEB_APP_URL"]) {
|
|
16836
|
-
envVars.push(`WEB_APP_URL="${process.env["WEB_APP_URL"]}"`);
|
|
16837
|
-
}
|
|
16838
|
-
if (process.env["API_URL"]) {
|
|
16839
|
-
envVars.push(`API_URL="${process.env["API_URL"]}"`);
|
|
16840
|
-
}
|
|
16841
|
-
if (envVars.length > 0) {
|
|
16842
|
-
command = `${envVars.join(" ")} ${command}`;
|
|
16843
|
-
console.log(
|
|
16844
|
-
chalk11.blue(
|
|
16845
|
-
`Adding environment variables to hook command: ${envVars.join(", ")}`
|
|
16846
|
-
)
|
|
16847
|
-
);
|
|
16848
|
-
}
|
|
16849
|
-
}
|
|
16850
|
-
const mobbHookConfig = {
|
|
16851
|
-
// Only fire on tools that indicate meaningful work — skip high-frequency
|
|
16852
|
-
// read-only tools (Grep, Glob, WebSearch, WebFetch) to reduce CPU overhead
|
|
16853
|
-
// from process startup (~1.7s user CPU per invocation).
|
|
16854
|
-
matcher: RECOMMENDED_MATCHER,
|
|
16855
|
-
hooks: [
|
|
16856
|
-
{
|
|
16857
|
-
type: "command",
|
|
16858
|
-
command,
|
|
16859
|
-
async: true
|
|
16860
|
-
}
|
|
16861
|
-
]
|
|
16862
|
-
};
|
|
16863
|
-
const existingHookIndex = settings.hooks.PostToolUse.findIndex(
|
|
16864
|
-
(hook) => hook.hooks.some(
|
|
16865
|
-
(h) => h.command?.includes("mobbdev@latest claude-code-process-hook")
|
|
16866
|
-
)
|
|
16867
|
-
);
|
|
16868
|
-
if (existingHookIndex >= 0) {
|
|
16869
|
-
console.log(chalk11.yellow("Mobb hook already exists, updating..."));
|
|
16870
|
-
settings.hooks.PostToolUse[existingHookIndex] = mobbHookConfig;
|
|
16871
|
-
} else {
|
|
16872
|
-
console.log(chalk11.green("Adding new Mobb hook..."));
|
|
16873
|
-
settings.hooks.PostToolUse.push(mobbHookConfig);
|
|
16874
|
-
}
|
|
16875
|
-
await writeClaudeSettings(settings);
|
|
16876
|
-
console.log(
|
|
16877
|
-
chalk11.green(
|
|
16878
|
-
`\u2705 Mobb hooks ${options.saveEnv ? "and environment variables " : ""}installed successfully in ${CLAUDE_SETTINGS_PATH}`
|
|
16879
|
-
)
|
|
16880
|
-
);
|
|
16881
|
-
}
|
|
16882
|
-
|
|
16883
|
-
// src/features/claude_code/data_collector.ts
|
|
16884
|
-
var CC_VERSION_CACHE_KEY = "claudeCode.detectedCCVersion";
|
|
16885
|
-
var CC_VERSION_CLI_KEY = "claudeCode.detectedCCVersionCli";
|
|
16886
|
-
var GLOBAL_COOLDOWN_MS = 5e3;
|
|
16887
|
-
var HOOK_COOLDOWN_MS = 15e3;
|
|
16888
|
-
var ACTIVE_LOCK_TTL_MS = 6e4;
|
|
16889
|
-
var GQL_AUTH_TIMEOUT_MS = 15e3;
|
|
16890
|
-
var STALE_KEY_MAX_AGE_MS = 14 * 24 * 60 * 60 * 1e3;
|
|
16891
|
-
var CLEANUP_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
16892
|
-
var MAX_ENTRIES_PER_INVOCATION = 50;
|
|
16893
|
-
var COOLDOWN_KEY = "lastHookRunAt";
|
|
16894
|
-
var ACTIVE_KEY = "hookActiveAt";
|
|
16895
|
-
var HookDataSchema = z33.object({
|
|
16896
|
-
session_id: z33.string().nullish(),
|
|
16897
|
-
transcript_path: z33.string().nullish(),
|
|
16898
|
-
cwd: z33.string().nullish(),
|
|
16899
|
-
hook_event_name: z33.string(),
|
|
16900
|
-
tool_name: z33.string(),
|
|
16901
|
-
tool_input: z33.unknown(),
|
|
16902
|
-
tool_response: z33.unknown()
|
|
16903
|
-
});
|
|
16904
|
-
var execFileAsync = promisify(execFile);
|
|
16905
|
-
async function detectClaudeCodeVersion() {
|
|
16906
|
-
const cachedCliVersion = configStore.get(CC_VERSION_CLI_KEY);
|
|
16907
|
-
if (cachedCliVersion === packageJson.version) {
|
|
16908
|
-
return configStore.get(CC_VERSION_CACHE_KEY);
|
|
16909
|
-
}
|
|
16910
|
-
try {
|
|
16911
|
-
const { stdout: stdout2 } = await execFileAsync("claude", ["--version"], {
|
|
16912
|
-
timeout: 3e3,
|
|
16913
|
-
encoding: "utf-8"
|
|
16914
|
-
});
|
|
16915
|
-
const version = stdout2.trim().split(/\s/)[0] || stdout2.trim();
|
|
16916
|
-
configStore.set(CC_VERSION_CACHE_KEY, version);
|
|
16917
|
-
configStore.set(CC_VERSION_CLI_KEY, packageJson.version);
|
|
16918
|
-
return version;
|
|
16919
|
-
} catch {
|
|
16920
|
-
configStore.set(CC_VERSION_CACHE_KEY, void 0);
|
|
16921
|
-
configStore.set(CC_VERSION_CLI_KEY, packageJson.version);
|
|
16922
|
-
return void 0;
|
|
16923
|
-
}
|
|
16924
|
-
}
|
|
16925
|
-
var STDIN_TIMEOUT_MS = 1e4;
|
|
16926
|
-
async function readStdinData() {
|
|
16927
|
-
hookLog.debug("Reading stdin data");
|
|
16928
|
-
return new Promise((resolve, reject) => {
|
|
16929
|
-
let inputData = "";
|
|
16930
|
-
let settled = false;
|
|
16931
|
-
const timer = setTimeout(() => {
|
|
16932
|
-
if (!settled) {
|
|
16933
|
-
settled = true;
|
|
16934
|
-
process.stdin.destroy();
|
|
16935
|
-
reject(new Error("Timed out reading from stdin"));
|
|
16936
|
-
}
|
|
16937
|
-
}, STDIN_TIMEOUT_MS);
|
|
16938
|
-
process.stdin.setEncoding("utf-8");
|
|
16939
|
-
process.stdin.on("data", (chunk) => {
|
|
16940
|
-
inputData += chunk;
|
|
16941
|
-
});
|
|
16942
|
-
process.stdin.on("end", () => {
|
|
16943
|
-
if (settled) return;
|
|
16944
|
-
settled = true;
|
|
16945
|
-
clearTimeout(timer);
|
|
16946
|
-
try {
|
|
16947
|
-
const parsedData = JSON.parse(inputData);
|
|
16948
|
-
hookLog.debug(
|
|
16949
|
-
{
|
|
16950
|
-
data: { keys: Object.keys(parsedData) }
|
|
16951
|
-
},
|
|
16952
|
-
"Parsed stdin data"
|
|
16953
|
-
);
|
|
16954
|
-
resolve(parsedData);
|
|
16955
|
-
} catch (error) {
|
|
16956
|
-
const msg = `Failed to parse JSON from stdin: ${error.message}`;
|
|
16957
|
-
hookLog.error(msg);
|
|
16958
|
-
reject(new Error(msg));
|
|
16959
|
-
}
|
|
16960
|
-
});
|
|
16961
|
-
process.stdin.on("error", (error) => {
|
|
16962
|
-
if (settled) return;
|
|
16963
|
-
settled = true;
|
|
16964
|
-
clearTimeout(timer);
|
|
16965
|
-
hookLog.error(
|
|
16966
|
-
{ data: { error: error.message } },
|
|
16967
|
-
"Error reading from stdin"
|
|
16968
|
-
);
|
|
16969
|
-
reject(new Error(`Error reading from stdin: ${error.message}`));
|
|
16970
|
-
});
|
|
16971
|
-
});
|
|
16972
|
-
}
|
|
16973
|
-
function validateHookData(data) {
|
|
16974
|
-
return HookDataSchema.parse(data);
|
|
16975
|
-
}
|
|
16976
|
-
async function extractSessionIdFromTranscript(transcriptPath) {
|
|
16977
|
-
try {
|
|
16978
|
-
const fh = await open4(transcriptPath, "r");
|
|
16979
|
-
try {
|
|
16980
|
-
const buf = Buffer.alloc(4096);
|
|
16981
|
-
const { bytesRead } = await fh.read(buf, 0, 4096, 0);
|
|
16982
|
-
const chunk = buf.toString("utf-8", 0, bytesRead);
|
|
16983
|
-
const firstLine = chunk.split("\n").find((l) => l.trim().length > 0);
|
|
16984
|
-
if (!firstLine) return null;
|
|
16985
|
-
const entry = JSON.parse(firstLine);
|
|
16986
|
-
return entry.sessionId ?? null;
|
|
16987
|
-
} finally {
|
|
16988
|
-
await fh.close();
|
|
16989
|
-
}
|
|
16990
|
-
} catch {
|
|
16991
|
-
return null;
|
|
16992
|
-
}
|
|
16993
|
-
}
|
|
16994
|
-
function generateSyntheticId(sessionId, timestamp, type2, lineIndex) {
|
|
16995
|
-
const input = `${sessionId ?? ""}:${timestamp ?? ""}:${type2 ?? ""}:${lineIndex}`;
|
|
16996
|
-
const hash = createHash2("sha256").update(input).digest("hex").slice(0, 16);
|
|
16997
|
-
return `synth:${hash}`;
|
|
16998
|
-
}
|
|
16999
|
-
function getCursorKey(transcriptPath) {
|
|
17000
|
-
const hash = createHash2("sha256").update(transcriptPath).digest("hex").slice(0, 12);
|
|
17001
|
-
return `cursor.${hash}`;
|
|
17002
|
-
}
|
|
17003
|
-
async function resolveTranscriptPath(transcriptPath, sessionId) {
|
|
17004
|
-
try {
|
|
17005
|
-
await access(transcriptPath);
|
|
17006
|
-
return transcriptPath;
|
|
17007
|
-
} catch {
|
|
17008
|
-
}
|
|
17009
|
-
const filename = path14.basename(transcriptPath);
|
|
17010
|
-
const dirName = path14.basename(path14.dirname(transcriptPath));
|
|
17011
|
-
const projectsDir = path14.dirname(path14.dirname(transcriptPath));
|
|
17012
|
-
const baseDirName = dirName.replace(/[-.]claude-worktrees-.+$/, "");
|
|
17013
|
-
if (baseDirName !== dirName) {
|
|
17014
|
-
const candidate = path14.join(projectsDir, baseDirName, filename);
|
|
17015
|
-
try {
|
|
17016
|
-
await access(candidate);
|
|
17017
|
-
hookLog.info(
|
|
17018
|
-
{
|
|
17019
|
-
data: {
|
|
17020
|
-
original: transcriptPath,
|
|
17021
|
-
resolved: candidate,
|
|
17022
|
-
sessionId,
|
|
17023
|
-
method: "worktree-strip"
|
|
17024
|
-
}
|
|
17025
|
-
},
|
|
17026
|
-
"Transcript path resolved via fallback"
|
|
17027
|
-
);
|
|
17028
|
-
return candidate;
|
|
17029
|
-
} catch {
|
|
17030
|
-
}
|
|
17494
|
+
const filename = path14.basename(transcriptPath);
|
|
17495
|
+
const dirName = path14.basename(path14.dirname(transcriptPath));
|
|
17496
|
+
const projectsDir = path14.dirname(path14.dirname(transcriptPath));
|
|
17497
|
+
const baseDirName = dirName.replace(/[-.]claude-worktrees-.+$/, "");
|
|
17498
|
+
if (baseDirName !== dirName) {
|
|
17499
|
+
const candidate = path14.join(projectsDir, baseDirName, filename);
|
|
17500
|
+
try {
|
|
17501
|
+
await access(candidate);
|
|
17502
|
+
hookLog.info(
|
|
17503
|
+
{
|
|
17504
|
+
data: {
|
|
17505
|
+
original: transcriptPath,
|
|
17506
|
+
resolved: candidate,
|
|
17507
|
+
sessionId,
|
|
17508
|
+
method: "worktree-strip"
|
|
17509
|
+
}
|
|
17510
|
+
},
|
|
17511
|
+
"Transcript path resolved via fallback"
|
|
17512
|
+
);
|
|
17513
|
+
return candidate;
|
|
17514
|
+
} catch {
|
|
17515
|
+
}
|
|
17031
17516
|
}
|
|
17032
17517
|
try {
|
|
17033
17518
|
const dirs = await readdir(projectsDir);
|
|
@@ -17055,7 +17540,7 @@ async function resolveTranscriptPath(transcriptPath, sessionId) {
|
|
|
17055
17540
|
}
|
|
17056
17541
|
return transcriptPath;
|
|
17057
17542
|
}
|
|
17058
|
-
async function readNewTranscriptEntries(transcriptPath, sessionId, sessionStore) {
|
|
17543
|
+
async function readNewTranscriptEntries(transcriptPath, sessionId, sessionStore, maxEntries = DAEMON_CHUNK_SIZE) {
|
|
17059
17544
|
transcriptPath = await resolveTranscriptPath(transcriptPath, sessionId);
|
|
17060
17545
|
const cursor = sessionStore.get(getCursorKey(transcriptPath));
|
|
17061
17546
|
let content;
|
|
@@ -17064,9 +17549,9 @@ async function readNewTranscriptEntries(transcriptPath, sessionId, sessionStore)
|
|
|
17064
17549
|
if (cursor?.byteOffset) {
|
|
17065
17550
|
const fh = await open4(transcriptPath, "r");
|
|
17066
17551
|
try {
|
|
17067
|
-
const
|
|
17068
|
-
fileSize =
|
|
17069
|
-
if (cursor.byteOffset >=
|
|
17552
|
+
const stat2 = await fh.stat();
|
|
17553
|
+
fileSize = stat2.size;
|
|
17554
|
+
if (cursor.byteOffset >= stat2.size) {
|
|
17070
17555
|
hookLog.info({ data: { sessionId } }, "No new data in transcript file");
|
|
17071
17556
|
return {
|
|
17072
17557
|
entries: [],
|
|
@@ -17074,7 +17559,7 @@ async function readNewTranscriptEntries(transcriptPath, sessionId, sessionStore)
|
|
|
17074
17559
|
resolvedTranscriptPath: transcriptPath
|
|
17075
17560
|
};
|
|
17076
17561
|
}
|
|
17077
|
-
const buf = Buffer.alloc(
|
|
17562
|
+
const buf = Buffer.alloc(stat2.size - cursor.byteOffset);
|
|
17078
17563
|
await fh.read(buf, 0, buf.length, cursor.byteOffset);
|
|
17079
17564
|
content = buf.toString("utf-8");
|
|
17080
17565
|
} finally {
|
|
@@ -17109,7 +17594,7 @@ async function readNewTranscriptEntries(transcriptPath, sessionId, sessionStore)
|
|
|
17109
17594
|
for (let i = 0; i < allLines.length; i++) {
|
|
17110
17595
|
const line = allLines[i];
|
|
17111
17596
|
const lineBytes = Buffer.byteLength(line, "utf-8") + (i < allLines.length - 1 ? 1 : 0);
|
|
17112
|
-
if (parsed.length >=
|
|
17597
|
+
if (parsed.length >= maxEntries) break;
|
|
17113
17598
|
bytesConsumed += lineBytes;
|
|
17114
17599
|
if (line.trim().length === 0) continue;
|
|
17115
17600
|
try {
|
|
@@ -17127,7 +17612,7 @@ async function readNewTranscriptEntries(transcriptPath, sessionId, sessionStore)
|
|
|
17127
17612
|
parsedLineIndex++;
|
|
17128
17613
|
}
|
|
17129
17614
|
const endByteOffset = startOffset + bytesConsumed;
|
|
17130
|
-
const capped = parsed.length >=
|
|
17615
|
+
const capped = parsed.length >= maxEntries;
|
|
17131
17616
|
if (malformedLines > 0) {
|
|
17132
17617
|
hookLog.warn(
|
|
17133
17618
|
{ data: { malformedLines, transcriptPath } },
|
|
@@ -17140,10 +17625,11 @@ async function readNewTranscriptEntries(transcriptPath, sessionId, sessionStore)
|
|
|
17140
17625
|
data: {
|
|
17141
17626
|
sessionId,
|
|
17142
17627
|
entriesParsed: parsed.length,
|
|
17143
|
-
totalLines: allLines.length
|
|
17628
|
+
totalLines: allLines.length,
|
|
17629
|
+
maxEntries
|
|
17144
17630
|
}
|
|
17145
17631
|
},
|
|
17146
|
-
"Capped at
|
|
17632
|
+
"Capped at maxEntries, remaining entries deferred"
|
|
17147
17633
|
);
|
|
17148
17634
|
} else if (!cursor) {
|
|
17149
17635
|
hookLog.info(
|
|
@@ -17223,14 +17709,13 @@ function filterEntries(entries) {
|
|
|
17223
17709
|
});
|
|
17224
17710
|
return { filtered, filteredOut: entries.length - filtered.length };
|
|
17225
17711
|
}
|
|
17226
|
-
async function cleanupStaleSessions(
|
|
17712
|
+
async function cleanupStaleSessions(configDir) {
|
|
17227
17713
|
const lastCleanup = configStore.get("claudeCode.lastCleanupAt");
|
|
17228
17714
|
if (lastCleanup && Date.now() - lastCleanup < CLEANUP_INTERVAL_MS) {
|
|
17229
17715
|
return;
|
|
17230
17716
|
}
|
|
17231
17717
|
const now = Date.now();
|
|
17232
17718
|
const prefix = getSessionFilePrefix();
|
|
17233
|
-
const configDir = path14.dirname(sessionStore.path);
|
|
17234
17719
|
try {
|
|
17235
17720
|
const files = await readdir(configDir);
|
|
17236
17721
|
let deletedCount = 0;
|
|
@@ -17240,8 +17725,6 @@ async function cleanupStaleSessions(sessionStore) {
|
|
|
17240
17725
|
try {
|
|
17241
17726
|
const content = JSON.parse(await readFile(filePath, "utf-8"));
|
|
17242
17727
|
let newest = 0;
|
|
17243
|
-
const cooldown = content[COOLDOWN_KEY];
|
|
17244
|
-
if (cooldown && cooldown > newest) newest = cooldown;
|
|
17245
17728
|
const cursors = content["cursor"];
|
|
17246
17729
|
if (cursors && typeof cursors === "object") {
|
|
17247
17730
|
for (const val of Object.values(cursors)) {
|
|
@@ -17263,157 +17746,7 @@ async function cleanupStaleSessions(sessionStore) {
|
|
|
17263
17746
|
}
|
|
17264
17747
|
configStore.set("claudeCode.lastCleanupAt", now);
|
|
17265
17748
|
}
|
|
17266
|
-
async function
|
|
17267
|
-
hookLog.info("Hook invoked");
|
|
17268
|
-
const globalLastRun = configStore.get("claudeCode.globalLastHookRunAt");
|
|
17269
|
-
const globalNow = Date.now();
|
|
17270
|
-
if (globalLastRun && globalNow - globalLastRun < GLOBAL_COOLDOWN_MS) {
|
|
17271
|
-
return { entriesUploaded: 0, entriesSkipped: 0, errors: 0 };
|
|
17272
|
-
}
|
|
17273
|
-
configStore.set("claudeCode.globalLastHookRunAt", globalNow);
|
|
17274
|
-
const lastUpgradeVersion = configStore.get(
|
|
17275
|
-
"claudeCode.matcherUpgradeVersion"
|
|
17276
|
-
);
|
|
17277
|
-
if (lastUpgradeVersion !== packageJson.version) {
|
|
17278
|
-
const upgraded = await autoUpgradeMatcherIfStale();
|
|
17279
|
-
configStore.set("claudeCode.matcherUpgradeVersion", packageJson.version);
|
|
17280
|
-
if (upgraded) {
|
|
17281
|
-
hookLog.info("Auto-upgraded hook matcher to reduce CPU usage");
|
|
17282
|
-
}
|
|
17283
|
-
}
|
|
17284
|
-
try {
|
|
17285
|
-
const ccVersion = await detectClaudeCodeVersion();
|
|
17286
|
-
setClaudeCodeVersion(ccVersion);
|
|
17287
|
-
} catch {
|
|
17288
|
-
}
|
|
17289
|
-
const rawData = await readStdinData();
|
|
17290
|
-
const rawObj = rawData;
|
|
17291
|
-
const hookData = (() => {
|
|
17292
|
-
try {
|
|
17293
|
-
return validateHookData(rawData);
|
|
17294
|
-
} catch (err) {
|
|
17295
|
-
hookLog.error(
|
|
17296
|
-
{
|
|
17297
|
-
data: {
|
|
17298
|
-
hook_event_name: rawObj?.["hook_event_name"],
|
|
17299
|
-
tool_name: rawObj?.["tool_name"],
|
|
17300
|
-
session_id: rawObj?.["session_id"],
|
|
17301
|
-
cwd: rawObj?.["cwd"],
|
|
17302
|
-
keys: rawObj ? Object.keys(rawObj) : []
|
|
17303
|
-
}
|
|
17304
|
-
},
|
|
17305
|
-
`Hook validation failed: ${err.message?.slice(0, 200)}`
|
|
17306
|
-
);
|
|
17307
|
-
throw err;
|
|
17308
|
-
}
|
|
17309
|
-
})();
|
|
17310
|
-
if (!hookData.transcript_path) {
|
|
17311
|
-
hookLog.warn(
|
|
17312
|
-
{
|
|
17313
|
-
data: {
|
|
17314
|
-
hook_event_name: hookData.hook_event_name,
|
|
17315
|
-
tool_name: hookData.tool_name,
|
|
17316
|
-
session_id: hookData.session_id,
|
|
17317
|
-
cwd: hookData.cwd
|
|
17318
|
-
}
|
|
17319
|
-
},
|
|
17320
|
-
"Missing transcript_path \u2014 cannot process hook"
|
|
17321
|
-
);
|
|
17322
|
-
return { entriesUploaded: 0, entriesSkipped: 0, errors: 0 };
|
|
17323
|
-
}
|
|
17324
|
-
let sessionId = hookData.session_id;
|
|
17325
|
-
if (!sessionId) {
|
|
17326
|
-
sessionId = await extractSessionIdFromTranscript(hookData.transcript_path);
|
|
17327
|
-
if (sessionId) {
|
|
17328
|
-
hookLog.warn(
|
|
17329
|
-
{
|
|
17330
|
-
data: {
|
|
17331
|
-
hook_event_name: hookData.hook_event_name,
|
|
17332
|
-
tool_name: hookData.tool_name,
|
|
17333
|
-
cwd: hookData.cwd,
|
|
17334
|
-
extractedSessionId: sessionId
|
|
17335
|
-
}
|
|
17336
|
-
},
|
|
17337
|
-
"Missing session_id in hook data \u2014 extracted from transcript"
|
|
17338
|
-
);
|
|
17339
|
-
} else {
|
|
17340
|
-
hookLog.warn(
|
|
17341
|
-
{
|
|
17342
|
-
data: {
|
|
17343
|
-
hook_event_name: hookData.hook_event_name,
|
|
17344
|
-
tool_name: hookData.tool_name,
|
|
17345
|
-
transcript_path: hookData.transcript_path
|
|
17346
|
-
}
|
|
17347
|
-
},
|
|
17348
|
-
"Missing session_id and could not extract from transcript \u2014 cannot process hook"
|
|
17349
|
-
);
|
|
17350
|
-
return { entriesUploaded: 0, entriesSkipped: 0, errors: 0 };
|
|
17351
|
-
}
|
|
17352
|
-
}
|
|
17353
|
-
if (!hookData.cwd) {
|
|
17354
|
-
hookLog.warn(
|
|
17355
|
-
{
|
|
17356
|
-
data: {
|
|
17357
|
-
hook_event_name: hookData.hook_event_name,
|
|
17358
|
-
tool_name: hookData.tool_name,
|
|
17359
|
-
session_id: sessionId
|
|
17360
|
-
}
|
|
17361
|
-
},
|
|
17362
|
-
"Missing cwd in hook data \u2014 scoped logging and repo URL detection disabled"
|
|
17363
|
-
);
|
|
17364
|
-
}
|
|
17365
|
-
const resolvedHookData = {
|
|
17366
|
-
...hookData,
|
|
17367
|
-
session_id: sessionId,
|
|
17368
|
-
transcript_path: hookData.transcript_path,
|
|
17369
|
-
cwd: hookData.cwd ?? void 0
|
|
17370
|
-
};
|
|
17371
|
-
const sessionStore = createSessionConfigStore(resolvedHookData.session_id);
|
|
17372
|
-
await cleanupStaleSessions(sessionStore);
|
|
17373
|
-
const now = Date.now();
|
|
17374
|
-
const lastRunAt = sessionStore.get(COOLDOWN_KEY);
|
|
17375
|
-
if (lastRunAt && now - lastRunAt < HOOK_COOLDOWN_MS) {
|
|
17376
|
-
return { entriesUploaded: 0, entriesSkipped: 0, errors: 0 };
|
|
17377
|
-
}
|
|
17378
|
-
const activeAt = sessionStore.get(ACTIVE_KEY);
|
|
17379
|
-
if (activeAt && now - activeAt < ACTIVE_LOCK_TTL_MS) {
|
|
17380
|
-
const activeDuration = now - activeAt;
|
|
17381
|
-
if (activeDuration > HOOK_COOLDOWN_MS) {
|
|
17382
|
-
hookLog.warn(
|
|
17383
|
-
{
|
|
17384
|
-
data: {
|
|
17385
|
-
activeDurationMs: activeDuration,
|
|
17386
|
-
sessionId: resolvedHookData.session_id
|
|
17387
|
-
}
|
|
17388
|
-
},
|
|
17389
|
-
"Hook still active \u2014 possible slow upload or hung process"
|
|
17390
|
-
);
|
|
17391
|
-
}
|
|
17392
|
-
return { entriesUploaded: 0, entriesSkipped: 0, errors: 0 };
|
|
17393
|
-
}
|
|
17394
|
-
sessionStore.set(ACTIVE_KEY, now);
|
|
17395
|
-
sessionStore.set(COOLDOWN_KEY, now);
|
|
17396
|
-
const log2 = createScopedHookLog(resolvedHookData.cwd ?? process.cwd());
|
|
17397
|
-
log2.info(
|
|
17398
|
-
{
|
|
17399
|
-
data: {
|
|
17400
|
-
sessionId: resolvedHookData.session_id,
|
|
17401
|
-
toolName: resolvedHookData.tool_name,
|
|
17402
|
-
hookEvent: resolvedHookData.hook_event_name,
|
|
17403
|
-
cwd: resolvedHookData.cwd,
|
|
17404
|
-
claudeCodeVersion: getClaudeCodeVersion()
|
|
17405
|
-
}
|
|
17406
|
-
},
|
|
17407
|
-
"Hook data validated"
|
|
17408
|
-
);
|
|
17409
|
-
try {
|
|
17410
|
-
return await processTranscript(resolvedHookData, sessionStore, log2);
|
|
17411
|
-
} finally {
|
|
17412
|
-
sessionStore.delete(ACTIVE_KEY);
|
|
17413
|
-
log2.flushLogs();
|
|
17414
|
-
}
|
|
17415
|
-
}
|
|
17416
|
-
async function processTranscript(hookData, sessionStore, log2) {
|
|
17749
|
+
async function processTranscript(input, sessionStore, log2, maxEntries = DAEMON_CHUNK_SIZE, gqlClientOverride) {
|
|
17417
17750
|
const {
|
|
17418
17751
|
entries: rawEntries,
|
|
17419
17752
|
endByteOffset,
|
|
@@ -17421,9 +17754,10 @@ async function processTranscript(hookData, sessionStore, log2) {
|
|
|
17421
17754
|
} = await log2.timed(
|
|
17422
17755
|
"Read transcript",
|
|
17423
17756
|
() => readNewTranscriptEntries(
|
|
17424
|
-
|
|
17425
|
-
|
|
17426
|
-
sessionStore
|
|
17757
|
+
input.transcript_path,
|
|
17758
|
+
input.session_id,
|
|
17759
|
+
sessionStore,
|
|
17760
|
+
maxEntries
|
|
17427
17761
|
)
|
|
17428
17762
|
);
|
|
17429
17763
|
const cursorKey = getCursorKey(resolvedTranscriptPath);
|
|
@@ -17456,30 +17790,26 @@ async function processTranscript(hookData, sessionStore, log2) {
|
|
|
17456
17790
|
};
|
|
17457
17791
|
}
|
|
17458
17792
|
let gqlClient;
|
|
17459
|
-
|
|
17460
|
-
gqlClient =
|
|
17461
|
-
|
|
17462
|
-
|
|
17463
|
-
|
|
17464
|
-
|
|
17465
|
-
|
|
17466
|
-
|
|
17467
|
-
|
|
17468
|
-
|
|
17469
|
-
|
|
17470
|
-
|
|
17471
|
-
|
|
17472
|
-
|
|
17473
|
-
|
|
17474
|
-
|
|
17475
|
-
|
|
17476
|
-
|
|
17477
|
-
|
|
17478
|
-
|
|
17479
|
-
entriesUploaded: 0,
|
|
17480
|
-
entriesSkipped: filteredOut,
|
|
17481
|
-
errors: entries.length
|
|
17482
|
-
};
|
|
17793
|
+
if (gqlClientOverride) {
|
|
17794
|
+
gqlClient = gqlClientOverride;
|
|
17795
|
+
} else {
|
|
17796
|
+
try {
|
|
17797
|
+
gqlClient = await log2.timed(
|
|
17798
|
+
"GQL auth",
|
|
17799
|
+
() => withTimeout(
|
|
17800
|
+
getAuthenticatedGQLClient({ isSkipPrompts: true }),
|
|
17801
|
+
GQL_AUTH_TIMEOUT_MS,
|
|
17802
|
+
"GQL auth"
|
|
17803
|
+
)
|
|
17804
|
+
);
|
|
17805
|
+
} catch (err) {
|
|
17806
|
+
log2.error({ err }, "GQL auth failed");
|
|
17807
|
+
return {
|
|
17808
|
+
entriesUploaded: 0,
|
|
17809
|
+
entriesSkipped: filteredOut,
|
|
17810
|
+
errors: entries.length
|
|
17811
|
+
};
|
|
17812
|
+
}
|
|
17483
17813
|
}
|
|
17484
17814
|
const cursorForModel = sessionStore.get(cursorKey);
|
|
17485
17815
|
let lastSeenModel = cursorForModel?.lastModel ?? null;
|
|
@@ -17496,68 +17826,508 @@ async function processTranscript(hookData, sessionStore, log2) {
|
|
|
17496
17826
|
rawEntry["message"] = { model: lastSeenModel };
|
|
17497
17827
|
}
|
|
17498
17828
|
}
|
|
17499
|
-
if (!rawEntry["sessionId"]) {
|
|
17500
|
-
rawEntry["sessionId"] =
|
|
17829
|
+
if (!rawEntry["sessionId"]) {
|
|
17830
|
+
rawEntry["sessionId"] = input.session_id;
|
|
17831
|
+
}
|
|
17832
|
+
return {
|
|
17833
|
+
platform: "CLAUDE_CODE" /* ClaudeCode */,
|
|
17834
|
+
recordId: _recordId,
|
|
17835
|
+
recordTimestamp: entry.timestamp ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
17836
|
+
blameType: "CHAT" /* Chat */,
|
|
17837
|
+
rawData: rawEntry
|
|
17838
|
+
};
|
|
17839
|
+
});
|
|
17840
|
+
const totalRawDataBytes = records.reduce((sum, r) => {
|
|
17841
|
+
return sum + (r.rawData ? JSON.stringify(r.rawData).length : 0);
|
|
17842
|
+
}, 0);
|
|
17843
|
+
log2.info(
|
|
17844
|
+
{
|
|
17845
|
+
data: {
|
|
17846
|
+
count: records.length,
|
|
17847
|
+
skipped: filteredOut,
|
|
17848
|
+
rawDataBytes: totalRawDataBytes,
|
|
17849
|
+
firstRecordId: records[0]?.recordId,
|
|
17850
|
+
lastRecordId: records[records.length - 1]?.recordId
|
|
17851
|
+
}
|
|
17852
|
+
},
|
|
17853
|
+
"Uploading batch"
|
|
17854
|
+
);
|
|
17855
|
+
const sanitize = process.env["MOBBDEV_HOOK_SANITIZE"] === "1";
|
|
17856
|
+
const result = await log2.timed(
|
|
17857
|
+
"Batch upload",
|
|
17858
|
+
() => prepareAndSendTracyRecords(gqlClient, records, input.cwd, {
|
|
17859
|
+
sanitize
|
|
17860
|
+
})
|
|
17861
|
+
);
|
|
17862
|
+
if (result.ok) {
|
|
17863
|
+
const lastRawEntry = rawEntries[rawEntries.length - 1];
|
|
17864
|
+
const cursor = {
|
|
17865
|
+
id: lastRawEntry._recordId,
|
|
17866
|
+
byteOffset: endByteOffset,
|
|
17867
|
+
updatedAt: Date.now(),
|
|
17868
|
+
lastModel: lastSeenModel ?? void 0
|
|
17869
|
+
};
|
|
17870
|
+
sessionStore.set(cursorKey, cursor);
|
|
17871
|
+
log2.heartbeat("Upload ok", {
|
|
17872
|
+
entriesUploaded: entries.length,
|
|
17873
|
+
entriesSkipped: filteredOut,
|
|
17874
|
+
claudeCodeVersion: getClaudeCodeVersion()
|
|
17875
|
+
});
|
|
17876
|
+
return {
|
|
17877
|
+
entriesUploaded: entries.length,
|
|
17878
|
+
entriesSkipped: filteredOut,
|
|
17879
|
+
errors: 0
|
|
17880
|
+
};
|
|
17881
|
+
}
|
|
17882
|
+
log2.error(
|
|
17883
|
+
{ data: { errors: result.errors, recordCount: entries.length } },
|
|
17884
|
+
"Batch upload had errors"
|
|
17885
|
+
);
|
|
17886
|
+
return {
|
|
17887
|
+
entriesUploaded: 0,
|
|
17888
|
+
entriesSkipped: filteredOut,
|
|
17889
|
+
errors: entries.length
|
|
17890
|
+
};
|
|
17891
|
+
}
|
|
17892
|
+
|
|
17893
|
+
// src/features/claude_code/install_hook.ts
|
|
17894
|
+
import fs14 from "fs";
|
|
17895
|
+
import fsPromises4 from "fs/promises";
|
|
17896
|
+
import os6 from "os";
|
|
17897
|
+
import path15 from "path";
|
|
17898
|
+
import chalk11 from "chalk";
|
|
17899
|
+
|
|
17900
|
+
// src/features/claude_code/daemon-check-shim.tmpl.js
|
|
17901
|
+
var daemon_check_shim_tmpl_default = "// Mobb daemon shim \u2014 checks if daemon is alive, spawns if dead.\n// Auto-generated by mobbdev CLI. Do not edit.\nvar fs = require('fs')\nvar spawn = require('child_process').spawn\nvar path = require('path')\nvar os = require('os')\n\nvar pidFile = path.join(os.homedir(), '.mobbdev', 'daemon.pid')\nvar HEARTBEAT_STALE_MS = __HEARTBEAT_STALE_MS__\n\ntry {\n var data = JSON.parse(fs.readFileSync(pidFile, 'utf8'))\n if (Date.now() - data.heartbeat > HEARTBEAT_STALE_MS) throw new Error('stale')\n process.kill(data.pid, 0) // throws ESRCH if the process is gone\n} catch (e) {\n var localCli = process.env.MOBBDEV_LOCAL_CLI\n var child = localCli\n ? spawn('node', [localCli, 'claude-code-daemon'], { detached: true, stdio: 'ignore', windowsHide: true })\n : spawn('npx', ['--yes', 'mobbdev@latest', 'claude-code-daemon'], { detached: true, stdio: 'ignore', shell: true, windowsHide: true })\n child.unref()\n}\n";
|
|
17902
|
+
|
|
17903
|
+
// src/features/claude_code/install_hook.ts
|
|
17904
|
+
var CLAUDE_SETTINGS_PATH = path15.join(os6.homedir(), ".claude", "settings.json");
|
|
17905
|
+
var RECOMMENDED_MATCHER = "*";
|
|
17906
|
+
async function claudeSettingsExists() {
|
|
17907
|
+
try {
|
|
17908
|
+
await fsPromises4.access(CLAUDE_SETTINGS_PATH);
|
|
17909
|
+
return true;
|
|
17910
|
+
} catch {
|
|
17911
|
+
return false;
|
|
17912
|
+
}
|
|
17913
|
+
}
|
|
17914
|
+
async function readClaudeSettings() {
|
|
17915
|
+
const settingsContent = await fsPromises4.readFile(
|
|
17916
|
+
CLAUDE_SETTINGS_PATH,
|
|
17917
|
+
"utf-8"
|
|
17918
|
+
);
|
|
17919
|
+
return JSON.parse(settingsContent);
|
|
17920
|
+
}
|
|
17921
|
+
async function writeClaudeSettings(settings) {
|
|
17922
|
+
await fsPromises4.writeFile(
|
|
17923
|
+
CLAUDE_SETTINGS_PATH,
|
|
17924
|
+
JSON.stringify(settings, null, 2),
|
|
17925
|
+
"utf-8"
|
|
17926
|
+
);
|
|
17927
|
+
}
|
|
17928
|
+
function isMobbHookCommand(command) {
|
|
17929
|
+
if (!command) return false;
|
|
17930
|
+
return command.includes("mobbdev@latest") || command.includes("/.mobbdev/") || command.includes("\\.mobbdev\\");
|
|
17931
|
+
}
|
|
17932
|
+
function getDaemonCheckScript() {
|
|
17933
|
+
return daemon_check_shim_tmpl_default.replace(
|
|
17934
|
+
"__HEARTBEAT_STALE_MS__",
|
|
17935
|
+
String(HEARTBEAT_STALE_MS)
|
|
17936
|
+
);
|
|
17937
|
+
}
|
|
17938
|
+
function writeDaemonCheckScript() {
|
|
17939
|
+
fs14.mkdirSync(getMobbdevDir(), { recursive: true });
|
|
17940
|
+
fs14.writeFileSync(getDaemonCheckScriptPath(), getDaemonCheckScript(), "utf8");
|
|
17941
|
+
}
|
|
17942
|
+
function buildHookCommand(envPrefix) {
|
|
17943
|
+
const nodeBin = process.execPath || "node";
|
|
17944
|
+
const base = `${nodeBin} ${getDaemonCheckScriptPath()}`;
|
|
17945
|
+
return envPrefix ? `${envPrefix} ${base}` : base;
|
|
17946
|
+
}
|
|
17947
|
+
async function autoUpgradeMatcherIfStale() {
|
|
17948
|
+
try {
|
|
17949
|
+
if (!await claudeSettingsExists()) return false;
|
|
17950
|
+
const settings = await readClaudeSettings();
|
|
17951
|
+
const hooks = settings.hooks?.PostToolUse;
|
|
17952
|
+
if (!hooks) return false;
|
|
17953
|
+
let upgraded = false;
|
|
17954
|
+
for (const hook of hooks) {
|
|
17955
|
+
const isMobbHook = hook.hooks.some((h) => isMobbHookCommand(h.command));
|
|
17956
|
+
if (!isMobbHook) continue;
|
|
17957
|
+
if (hook.matcher !== RECOMMENDED_MATCHER) {
|
|
17958
|
+
hook.matcher = RECOMMENDED_MATCHER;
|
|
17959
|
+
upgraded = true;
|
|
17960
|
+
}
|
|
17961
|
+
for (const h of hook.hooks) {
|
|
17962
|
+
if (h.command && !h.command.includes("daemon-check.js")) {
|
|
17963
|
+
const envMatch = h.command.match(/^((?:\w+="[^"]*"\s*)+)/);
|
|
17964
|
+
const envPrefix = envMatch?.[1]?.trim();
|
|
17965
|
+
h.command = buildHookCommand(envPrefix);
|
|
17966
|
+
upgraded = true;
|
|
17967
|
+
}
|
|
17968
|
+
if (!h.async) {
|
|
17969
|
+
h.async = true;
|
|
17970
|
+
upgraded = true;
|
|
17971
|
+
}
|
|
17972
|
+
}
|
|
17973
|
+
}
|
|
17974
|
+
if (upgraded) {
|
|
17975
|
+
writeDaemonCheckScript();
|
|
17976
|
+
await writeClaudeSettings(settings);
|
|
17977
|
+
}
|
|
17978
|
+
return upgraded;
|
|
17979
|
+
} catch {
|
|
17980
|
+
return false;
|
|
17981
|
+
}
|
|
17982
|
+
}
|
|
17983
|
+
async function installMobbHooks(options = {}) {
|
|
17984
|
+
console.log(chalk11.blue("Installing Mobb hooks in Claude Code settings..."));
|
|
17985
|
+
if (!await claudeSettingsExists()) {
|
|
17986
|
+
console.log(chalk11.red("\u274C Claude Code settings file not found"));
|
|
17987
|
+
console.log(chalk11.yellow(`Expected location: ${CLAUDE_SETTINGS_PATH}`));
|
|
17988
|
+
console.log(chalk11.yellow("Is Claude Code installed on your system?"));
|
|
17989
|
+
console.log(chalk11.yellow("Please install Claude Code and try again."));
|
|
17990
|
+
throw new Error(
|
|
17991
|
+
"Claude Code settings file not found. Is Claude Code installed?"
|
|
17992
|
+
);
|
|
17993
|
+
}
|
|
17994
|
+
writeDaemonCheckScript();
|
|
17995
|
+
const settings = await readClaudeSettings();
|
|
17996
|
+
if (!settings.hooks) {
|
|
17997
|
+
settings.hooks = {};
|
|
17998
|
+
}
|
|
17999
|
+
if (!settings.hooks.PostToolUse) {
|
|
18000
|
+
settings.hooks.PostToolUse = [];
|
|
18001
|
+
}
|
|
18002
|
+
let envPrefix;
|
|
18003
|
+
if (options.saveEnv) {
|
|
18004
|
+
const envVars = [];
|
|
18005
|
+
if (process.env["WEB_APP_URL"]) {
|
|
18006
|
+
envVars.push(`WEB_APP_URL="${process.env["WEB_APP_URL"]}"`);
|
|
18007
|
+
}
|
|
18008
|
+
if (process.env["API_URL"]) {
|
|
18009
|
+
envVars.push(`API_URL="${process.env["API_URL"]}"`);
|
|
18010
|
+
}
|
|
18011
|
+
if (envVars.length > 0) {
|
|
18012
|
+
envPrefix = envVars.join(" ");
|
|
18013
|
+
console.log(
|
|
18014
|
+
chalk11.blue(
|
|
18015
|
+
`Adding environment variables to hook command: ${envVars.join(", ")}`
|
|
18016
|
+
)
|
|
18017
|
+
);
|
|
18018
|
+
}
|
|
18019
|
+
}
|
|
18020
|
+
const command = buildHookCommand(envPrefix);
|
|
18021
|
+
const mobbHookConfig = {
|
|
18022
|
+
matcher: RECOMMENDED_MATCHER,
|
|
18023
|
+
hooks: [
|
|
18024
|
+
{
|
|
18025
|
+
type: "command",
|
|
18026
|
+
command,
|
|
18027
|
+
async: true
|
|
18028
|
+
}
|
|
18029
|
+
]
|
|
18030
|
+
};
|
|
18031
|
+
const existingHookIndex = settings.hooks.PostToolUse.findIndex(
|
|
18032
|
+
(hook) => hook.hooks.some((h) => isMobbHookCommand(h.command))
|
|
18033
|
+
);
|
|
18034
|
+
if (existingHookIndex >= 0) {
|
|
18035
|
+
console.log(chalk11.yellow("Mobb hook already exists, updating..."));
|
|
18036
|
+
settings.hooks.PostToolUse[existingHookIndex] = mobbHookConfig;
|
|
18037
|
+
} else {
|
|
18038
|
+
console.log(chalk11.green("Adding new Mobb hook..."));
|
|
18039
|
+
settings.hooks.PostToolUse.push(mobbHookConfig);
|
|
18040
|
+
}
|
|
18041
|
+
await writeClaudeSettings(settings);
|
|
18042
|
+
console.log(
|
|
18043
|
+
chalk11.green(
|
|
18044
|
+
`\u2705 Mobb hooks ${options.saveEnv ? "and environment variables " : ""}installed successfully in ${CLAUDE_SETTINGS_PATH}`
|
|
18045
|
+
)
|
|
18046
|
+
);
|
|
18047
|
+
}
|
|
18048
|
+
|
|
18049
|
+
// src/features/claude_code/transcript_scanner.ts
|
|
18050
|
+
import { open as open5, readdir as readdir2, stat } from "fs/promises";
|
|
18051
|
+
import os7 from "os";
|
|
18052
|
+
import path16 from "path";
|
|
18053
|
+
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
18054
|
+
function getClaudeProjectsDirs() {
|
|
18055
|
+
const dirs = [];
|
|
18056
|
+
const configDir = process.env["CLAUDE_CONFIG_DIR"];
|
|
18057
|
+
if (configDir) {
|
|
18058
|
+
dirs.push(path16.join(configDir, "projects"));
|
|
18059
|
+
}
|
|
18060
|
+
dirs.push(path16.join(os7.homedir(), ".config", "claude", "projects"));
|
|
18061
|
+
dirs.push(path16.join(os7.homedir(), ".claude", "projects"));
|
|
18062
|
+
return dirs;
|
|
18063
|
+
}
|
|
18064
|
+
async function collectJsonlFiles(files, dir, projectDir, seen, now, results) {
|
|
18065
|
+
for (const file of files) {
|
|
18066
|
+
if (!file.endsWith(".jsonl")) continue;
|
|
18067
|
+
const sessionId = file.replace(".jsonl", "");
|
|
18068
|
+
if (!UUID_RE.test(sessionId)) continue;
|
|
18069
|
+
const filePath = path16.join(dir, file);
|
|
18070
|
+
if (seen.has(filePath)) continue;
|
|
18071
|
+
seen.add(filePath);
|
|
18072
|
+
let fileStat;
|
|
18073
|
+
try {
|
|
18074
|
+
fileStat = await stat(filePath);
|
|
18075
|
+
} catch {
|
|
18076
|
+
continue;
|
|
18077
|
+
}
|
|
18078
|
+
if (now - fileStat.mtimeMs > TRANSCRIPT_MAX_AGE_MS) continue;
|
|
18079
|
+
results.push({
|
|
18080
|
+
filePath,
|
|
18081
|
+
sessionId,
|
|
18082
|
+
projectDir,
|
|
18083
|
+
mtimeMs: fileStat.mtimeMs,
|
|
18084
|
+
size: fileStat.size
|
|
18085
|
+
});
|
|
18086
|
+
}
|
|
18087
|
+
}
|
|
18088
|
+
async function scanForTranscripts(projectsDirs = getClaudeProjectsDirs()) {
|
|
18089
|
+
const results = [];
|
|
18090
|
+
const seen = /* @__PURE__ */ new Set();
|
|
18091
|
+
const now = Date.now();
|
|
18092
|
+
for (const projectsDir of projectsDirs) {
|
|
18093
|
+
let projectDirs;
|
|
18094
|
+
try {
|
|
18095
|
+
projectDirs = await readdir2(projectsDir);
|
|
18096
|
+
} catch {
|
|
18097
|
+
continue;
|
|
18098
|
+
}
|
|
18099
|
+
for (const projName of projectDirs) {
|
|
18100
|
+
const projPath = path16.join(projectsDir, projName);
|
|
18101
|
+
let projStat;
|
|
18102
|
+
try {
|
|
18103
|
+
projStat = await stat(projPath);
|
|
18104
|
+
} catch {
|
|
18105
|
+
continue;
|
|
18106
|
+
}
|
|
18107
|
+
if (!projStat.isDirectory()) continue;
|
|
18108
|
+
let files;
|
|
18109
|
+
try {
|
|
18110
|
+
files = await readdir2(projPath);
|
|
18111
|
+
} catch {
|
|
18112
|
+
continue;
|
|
18113
|
+
}
|
|
18114
|
+
await collectJsonlFiles(files, projPath, projPath, seen, now, results);
|
|
18115
|
+
for (const entry of files) {
|
|
18116
|
+
if (!UUID_RE.test(entry)) continue;
|
|
18117
|
+
const subagentsDir = path16.join(projPath, entry, "subagents");
|
|
18118
|
+
try {
|
|
18119
|
+
const s = await stat(subagentsDir);
|
|
18120
|
+
if (!s.isDirectory()) continue;
|
|
18121
|
+
const subFiles = await readdir2(subagentsDir);
|
|
18122
|
+
await collectJsonlFiles(
|
|
18123
|
+
subFiles,
|
|
18124
|
+
subagentsDir,
|
|
18125
|
+
projPath,
|
|
18126
|
+
seen,
|
|
18127
|
+
now,
|
|
18128
|
+
results
|
|
18129
|
+
);
|
|
18130
|
+
} catch {
|
|
18131
|
+
}
|
|
18132
|
+
}
|
|
18133
|
+
}
|
|
18134
|
+
}
|
|
18135
|
+
return results;
|
|
18136
|
+
}
|
|
18137
|
+
var CWD_READ_BYTES = 8192;
|
|
18138
|
+
var cwdCache = /* @__PURE__ */ new Map();
|
|
18139
|
+
async function extractCwdFromTranscript(filePath) {
|
|
18140
|
+
const cached = cwdCache.get(filePath);
|
|
18141
|
+
if (cached !== void 0) return cached;
|
|
18142
|
+
try {
|
|
18143
|
+
const fh = await open5(filePath, "r");
|
|
18144
|
+
try {
|
|
18145
|
+
const buf = Buffer.alloc(CWD_READ_BYTES);
|
|
18146
|
+
const { bytesRead } = await fh.read(buf, 0, CWD_READ_BYTES, 0);
|
|
18147
|
+
const text = buf.toString("utf8", 0, bytesRead);
|
|
18148
|
+
const lines = text.split("\n");
|
|
18149
|
+
for (const line of lines) {
|
|
18150
|
+
if (!line.trim()) continue;
|
|
18151
|
+
try {
|
|
18152
|
+
const entry = JSON.parse(line);
|
|
18153
|
+
if (typeof entry.cwd === "string") {
|
|
18154
|
+
cwdCache.set(filePath, entry.cwd);
|
|
18155
|
+
return entry.cwd;
|
|
18156
|
+
}
|
|
18157
|
+
} catch {
|
|
18158
|
+
}
|
|
18159
|
+
}
|
|
18160
|
+
} finally {
|
|
18161
|
+
await fh.close();
|
|
18162
|
+
}
|
|
18163
|
+
} catch {
|
|
18164
|
+
}
|
|
18165
|
+
return void 0;
|
|
18166
|
+
}
|
|
18167
|
+
|
|
18168
|
+
// src/features/claude_code/daemon.ts
|
|
18169
|
+
async function startDaemon() {
|
|
18170
|
+
hookLog.info("Daemon starting");
|
|
18171
|
+
const pidFile = await acquirePidFile();
|
|
18172
|
+
async function gracefulExit(code, reason) {
|
|
18173
|
+
hookLog.info({ data: { code } }, `Daemon exiting: ${reason}`);
|
|
18174
|
+
pidFile.remove();
|
|
18175
|
+
await flushDdLogs();
|
|
18176
|
+
process.exit(code);
|
|
18177
|
+
}
|
|
18178
|
+
let shuttingDown = false;
|
|
18179
|
+
process.once("SIGTERM", () => {
|
|
18180
|
+
shuttingDown = true;
|
|
18181
|
+
});
|
|
18182
|
+
process.once("SIGINT", () => {
|
|
18183
|
+
shuttingDown = true;
|
|
18184
|
+
});
|
|
18185
|
+
process.on("uncaughtException", (err) => {
|
|
18186
|
+
hookLog.error({ err }, "Daemon uncaughtException");
|
|
18187
|
+
void gracefulExit(1, "uncaughtException");
|
|
18188
|
+
});
|
|
18189
|
+
process.on("unhandledRejection", (reason) => {
|
|
18190
|
+
hookLog.error({ err: reason }, "Daemon unhandledRejection");
|
|
18191
|
+
void gracefulExit(1, "unhandledRejection");
|
|
18192
|
+
});
|
|
18193
|
+
await tryAutoUpgradeHooks();
|
|
18194
|
+
writeDaemonCheckScript();
|
|
18195
|
+
try {
|
|
18196
|
+
const ccVersion = await detectClaudeCodeVersion();
|
|
18197
|
+
setClaudeCodeVersion(ccVersion);
|
|
18198
|
+
} catch {
|
|
18199
|
+
}
|
|
18200
|
+
const gqlClient = await authenticateOrExit(gracefulExit);
|
|
18201
|
+
const startedAt = Date.now();
|
|
18202
|
+
const lastSeen = /* @__PURE__ */ new Map();
|
|
18203
|
+
let cleanupConfigDir;
|
|
18204
|
+
while (true) {
|
|
18205
|
+
if (shuttingDown) {
|
|
18206
|
+
await gracefulExit(0, "signal");
|
|
18207
|
+
}
|
|
18208
|
+
if (Date.now() - startedAt >= DAEMON_TTL_MS) {
|
|
18209
|
+
await gracefulExit(0, "TTL reached");
|
|
18210
|
+
}
|
|
18211
|
+
pidFile.updateHeartbeat();
|
|
18212
|
+
try {
|
|
18213
|
+
const changed = await detectChangedTranscripts(lastSeen);
|
|
18214
|
+
for (const transcript of changed) {
|
|
18215
|
+
const sessionStore = createSessionConfigStore(transcript.sessionId);
|
|
18216
|
+
if (!cleanupConfigDir) {
|
|
18217
|
+
cleanupConfigDir = path17.dirname(sessionStore.path);
|
|
18218
|
+
}
|
|
18219
|
+
await drainTranscript(transcript, sessionStore, gqlClient);
|
|
18220
|
+
}
|
|
18221
|
+
if (cleanupConfigDir) {
|
|
18222
|
+
await cleanupStaleSessions(cleanupConfigDir);
|
|
18223
|
+
}
|
|
18224
|
+
} catch (err) {
|
|
18225
|
+
hookLog.warn({ err }, "Unexpected error in daemon cycle");
|
|
17501
18226
|
}
|
|
17502
|
-
|
|
17503
|
-
|
|
17504
|
-
|
|
17505
|
-
|
|
17506
|
-
|
|
17507
|
-
|
|
17508
|
-
|
|
18227
|
+
await sleep2(DAEMON_POLL_INTERVAL_MS);
|
|
18228
|
+
}
|
|
18229
|
+
}
|
|
18230
|
+
async function acquirePidFile() {
|
|
18231
|
+
const pidFile = new DaemonPidFile();
|
|
18232
|
+
pidFile.ensureDir();
|
|
18233
|
+
pidFile.read();
|
|
18234
|
+
if (pidFile.isAlive()) {
|
|
18235
|
+
hookLog.info(
|
|
18236
|
+
{ data: { existingPid: pidFile.data?.pid } },
|
|
18237
|
+
"Another daemon is alive, exiting"
|
|
18238
|
+
);
|
|
18239
|
+
await flushDdLogs();
|
|
18240
|
+
process.exit(0);
|
|
18241
|
+
}
|
|
18242
|
+
pidFile.write(process.pid, packageJson.version);
|
|
18243
|
+
hookLog.info({ data: { pid: process.pid } }, "Daemon PID file written");
|
|
18244
|
+
return pidFile;
|
|
18245
|
+
}
|
|
18246
|
+
async function authenticateOrExit(exit) {
|
|
18247
|
+
try {
|
|
18248
|
+
const client = await withTimeout(
|
|
18249
|
+
getAuthenticatedGQLClient({ isSkipPrompts: true }),
|
|
18250
|
+
GQL_AUTH_TIMEOUT_MS,
|
|
18251
|
+
"GQL auth"
|
|
18252
|
+
);
|
|
18253
|
+
hookLog.info("Daemon authenticated");
|
|
18254
|
+
return client;
|
|
18255
|
+
} catch (err) {
|
|
18256
|
+
hookLog.error({ err }, "Daemon auth failed");
|
|
18257
|
+
return exit(1, "auth failed");
|
|
18258
|
+
}
|
|
18259
|
+
}
|
|
18260
|
+
async function drainTranscript(transcript, sessionStore, gqlClient) {
|
|
18261
|
+
const cwd = await extractCwdFromTranscript(transcript.filePath);
|
|
18262
|
+
const log2 = createScopedHookLog(cwd ?? transcript.projectDir, {
|
|
18263
|
+
daemonMode: true
|
|
17509
18264
|
});
|
|
17510
|
-
|
|
17511
|
-
|
|
17512
|
-
|
|
17513
|
-
|
|
17514
|
-
|
|
17515
|
-
|
|
17516
|
-
|
|
17517
|
-
|
|
17518
|
-
|
|
17519
|
-
|
|
17520
|
-
|
|
18265
|
+
try {
|
|
18266
|
+
let hasMore = true;
|
|
18267
|
+
while (hasMore) {
|
|
18268
|
+
const result = await processTranscript(
|
|
18269
|
+
{
|
|
18270
|
+
session_id: transcript.sessionId,
|
|
18271
|
+
transcript_path: transcript.filePath,
|
|
18272
|
+
cwd
|
|
18273
|
+
},
|
|
18274
|
+
sessionStore,
|
|
18275
|
+
log2,
|
|
18276
|
+
DAEMON_CHUNK_SIZE,
|
|
18277
|
+
gqlClient
|
|
18278
|
+
);
|
|
18279
|
+
hasMore = result.entriesUploaded + result.entriesSkipped >= DAEMON_CHUNK_SIZE;
|
|
18280
|
+
if (result.errors > 0) {
|
|
18281
|
+
hookLog.warn(
|
|
18282
|
+
{
|
|
18283
|
+
data: {
|
|
18284
|
+
sessionId: transcript.sessionId,
|
|
18285
|
+
errors: result.errors
|
|
18286
|
+
}
|
|
18287
|
+
},
|
|
18288
|
+
"Upload error \u2014 will retry next cycle"
|
|
18289
|
+
);
|
|
18290
|
+
break;
|
|
17521
18291
|
}
|
|
17522
|
-
}
|
|
17523
|
-
|
|
17524
|
-
|
|
17525
|
-
|
|
17526
|
-
|
|
17527
|
-
|
|
17528
|
-
() => prepareAndSendTracyRecords(gqlClient, records, hookData.cwd, {
|
|
17529
|
-
sanitize
|
|
17530
|
-
})
|
|
17531
|
-
);
|
|
17532
|
-
if (result.ok) {
|
|
17533
|
-
const lastRawEntry = rawEntries[rawEntries.length - 1];
|
|
17534
|
-
const cursor = {
|
|
17535
|
-
id: lastRawEntry._recordId,
|
|
17536
|
-
byteOffset: endByteOffset,
|
|
17537
|
-
updatedAt: Date.now(),
|
|
17538
|
-
lastModel: lastSeenModel ?? void 0
|
|
17539
|
-
};
|
|
17540
|
-
sessionStore.set(cursorKey, cursor);
|
|
17541
|
-
log2.heartbeat("Upload ok", {
|
|
17542
|
-
entriesUploaded: entries.length,
|
|
17543
|
-
entriesSkipped: filteredOut,
|
|
17544
|
-
claudeCodeVersion: getClaudeCodeVersion()
|
|
17545
|
-
});
|
|
17546
|
-
return {
|
|
17547
|
-
entriesUploaded: entries.length,
|
|
17548
|
-
entriesSkipped: filteredOut,
|
|
17549
|
-
errors: 0
|
|
17550
|
-
};
|
|
18292
|
+
}
|
|
18293
|
+
} catch (err) {
|
|
18294
|
+
hookLog.warn(
|
|
18295
|
+
{ err, data: { sessionId: transcript.sessionId } },
|
|
18296
|
+
"Error processing transcript \u2014 skipping"
|
|
18297
|
+
);
|
|
17551
18298
|
}
|
|
17552
|
-
|
|
17553
|
-
|
|
17554
|
-
|
|
18299
|
+
}
|
|
18300
|
+
async function detectChangedTranscripts(lastSeen) {
|
|
18301
|
+
const transcripts = await scanForTranscripts();
|
|
18302
|
+
const changed = [];
|
|
18303
|
+
const currentPaths = /* @__PURE__ */ new Set();
|
|
18304
|
+
for (const t of transcripts) {
|
|
18305
|
+
currentPaths.add(t.filePath);
|
|
18306
|
+
const prev = lastSeen.get(t.filePath);
|
|
18307
|
+
if (!prev || t.mtimeMs > prev.mtimeMs || t.size > prev.size) {
|
|
18308
|
+
changed.push(t);
|
|
18309
|
+
}
|
|
18310
|
+
lastSeen.set(t.filePath, { mtimeMs: t.mtimeMs, size: t.size });
|
|
18311
|
+
}
|
|
18312
|
+
for (const p of lastSeen.keys()) {
|
|
18313
|
+
if (!currentPaths.has(p)) lastSeen.delete(p);
|
|
18314
|
+
}
|
|
18315
|
+
return changed;
|
|
18316
|
+
}
|
|
18317
|
+
async function tryAutoUpgradeHooks() {
|
|
18318
|
+
const lastUpgradeVersion = configStore.get(
|
|
18319
|
+
"claudeCode.matcherUpgradeVersion"
|
|
17555
18320
|
);
|
|
17556
|
-
return
|
|
17557
|
-
|
|
17558
|
-
|
|
17559
|
-
|
|
17560
|
-
|
|
18321
|
+
if (lastUpgradeVersion === packageJson.version) return;
|
|
18322
|
+
try {
|
|
18323
|
+
const upgraded = await autoUpgradeMatcherIfStale();
|
|
18324
|
+
configStore.set("claudeCode.matcherUpgradeVersion", packageJson.version);
|
|
18325
|
+
if (upgraded) {
|
|
18326
|
+
hookLog.info("Auto-upgraded hook matcher");
|
|
18327
|
+
}
|
|
18328
|
+
} catch (err) {
|
|
18329
|
+
hookLog.warn({ err }, "Failed to auto-upgrade hook matcher");
|
|
18330
|
+
}
|
|
17561
18331
|
}
|
|
17562
18332
|
|
|
17563
18333
|
// src/args/commands/claude_code.ts
|
|
@@ -17577,7 +18347,13 @@ var claudeCodeInstallHookBuilder = (yargs2) => {
|
|
|
17577
18347
|
var claudeCodeProcessHookBuilder = (yargs2) => {
|
|
17578
18348
|
return yargs2.example(
|
|
17579
18349
|
"$0 claude-code-process-hook",
|
|
17580
|
-
"Process Claude Code hook data
|
|
18350
|
+
"Process Claude Code hook data (legacy \u2014 spawns daemon)"
|
|
18351
|
+
).strict();
|
|
18352
|
+
};
|
|
18353
|
+
var claudeCodeDaemonBuilder = (yargs2) => {
|
|
18354
|
+
return yargs2.example(
|
|
18355
|
+
"$0 claude-code-daemon",
|
|
18356
|
+
"Run the background daemon that processes Claude Code transcripts"
|
|
17581
18357
|
).strict();
|
|
17582
18358
|
};
|
|
17583
18359
|
var claudeCodeInstallHookHandler = async (argv) => {
|
|
@@ -17591,69 +18367,43 @@ var claudeCodeInstallHookHandler = async (argv) => {
|
|
|
17591
18367
|
}
|
|
17592
18368
|
};
|
|
17593
18369
|
var claudeCodeProcessHookHandler = async () => {
|
|
17594
|
-
|
|
17595
|
-
|
|
17596
|
-
|
|
17597
|
-
|
|
17598
|
-
|
|
17599
|
-
|
|
17600
|
-
|
|
17601
|
-
|
|
17602
|
-
|
|
18370
|
+
try {
|
|
18371
|
+
await autoUpgradeMatcherIfStale();
|
|
18372
|
+
writeDaemonCheckScript();
|
|
18373
|
+
const pidFile = new DaemonPidFile();
|
|
18374
|
+
pidFile.read();
|
|
18375
|
+
if (!pidFile.isAlive()) {
|
|
18376
|
+
hookLog.info("Daemon not alive \u2014 spawning");
|
|
18377
|
+
const localCli = process.env["MOBBDEV_LOCAL_CLI"];
|
|
18378
|
+
const child = localCli ? spawn("node", [localCli, "claude-code-daemon"], {
|
|
18379
|
+
detached: true,
|
|
18380
|
+
stdio: "ignore",
|
|
18381
|
+
windowsHide: true
|
|
18382
|
+
}) : spawn("npx", ["--yes", "mobbdev@latest", "claude-code-daemon"], {
|
|
18383
|
+
detached: true,
|
|
18384
|
+
stdio: "ignore",
|
|
18385
|
+
shell: true,
|
|
18386
|
+
windowsHide: true
|
|
18387
|
+
});
|
|
18388
|
+
child.unref();
|
|
17603
18389
|
}
|
|
18390
|
+
} catch (err) {
|
|
18391
|
+
hookLog.error({ err }, "Error in process-hook shim");
|
|
17604
18392
|
}
|
|
17605
|
-
process.on("uncaughtException", (error) => {
|
|
17606
|
-
hookLog.error(
|
|
17607
|
-
{ data: { error: String(error), stack: error.stack } },
|
|
17608
|
-
"Uncaught exception in hook"
|
|
17609
|
-
);
|
|
17610
|
-
void flushAndExit(1);
|
|
17611
|
-
});
|
|
17612
|
-
process.on("unhandledRejection", (reason) => {
|
|
17613
|
-
hookLog.error(
|
|
17614
|
-
{
|
|
17615
|
-
data: {
|
|
17616
|
-
error: String(reason),
|
|
17617
|
-
stack: reason instanceof Error ? reason.stack : void 0
|
|
17618
|
-
}
|
|
17619
|
-
},
|
|
17620
|
-
"Unhandled rejection in hook"
|
|
17621
|
-
);
|
|
17622
|
-
void flushAndExit(1);
|
|
17623
|
-
});
|
|
17624
|
-
let exitCode = 0;
|
|
17625
|
-
const hookStart = Date.now();
|
|
17626
18393
|
try {
|
|
17627
|
-
|
|
17628
|
-
|
|
17629
|
-
|
|
17630
|
-
|
|
17631
|
-
|
|
17632
|
-
|
|
17633
|
-
|
|
17634
|
-
|
|
17635
|
-
|
|
17636
|
-
|
|
17637
|
-
|
|
17638
|
-
|
|
17639
|
-
}
|
|
17640
|
-
},
|
|
17641
|
-
"Claude Code upload complete"
|
|
17642
|
-
);
|
|
17643
|
-
} catch (error) {
|
|
17644
|
-
exitCode = 1;
|
|
17645
|
-
hookLog.error(
|
|
17646
|
-
{
|
|
17647
|
-
data: {
|
|
17648
|
-
error: String(error),
|
|
17649
|
-
stack: error instanceof Error ? error.stack : void 0,
|
|
17650
|
-
durationMs: Date.now() - hookStart
|
|
17651
|
-
}
|
|
17652
|
-
},
|
|
17653
|
-
"Failed to process Claude Code hook"
|
|
17654
|
-
);
|
|
18394
|
+
await flushDdLogs();
|
|
18395
|
+
} catch {
|
|
18396
|
+
}
|
|
18397
|
+
process.exit(0);
|
|
18398
|
+
};
|
|
18399
|
+
var claudeCodeDaemonHandler = async () => {
|
|
18400
|
+
try {
|
|
18401
|
+
await startDaemon();
|
|
18402
|
+
} catch (err) {
|
|
18403
|
+
hookLog.error({ err }, "Daemon crashed");
|
|
18404
|
+
await flushDdLogs();
|
|
18405
|
+
process.exit(1);
|
|
17655
18406
|
}
|
|
17656
|
-
await flushAndExit(exitCode);
|
|
17657
18407
|
};
|
|
17658
18408
|
|
|
17659
18409
|
// src/mcp/core/McpServer.ts
|
|
@@ -17675,8 +18425,8 @@ var WorkspaceService = class {
|
|
|
17675
18425
|
* Sets a known workspace path that was discovered through successful validation
|
|
17676
18426
|
* @param path The validated workspace path to store
|
|
17677
18427
|
*/
|
|
17678
|
-
static setKnownWorkspacePath(
|
|
17679
|
-
this.knownWorkspacePath =
|
|
18428
|
+
static setKnownWorkspacePath(path30) {
|
|
18429
|
+
this.knownWorkspacePath = path30;
|
|
17680
18430
|
}
|
|
17681
18431
|
/**
|
|
17682
18432
|
* Gets the known workspace path that was previously validated
|
|
@@ -17823,131 +18573,131 @@ init_configs();
|
|
|
17823
18573
|
|
|
17824
18574
|
// src/mcp/types.ts
|
|
17825
18575
|
init_client_generates();
|
|
17826
|
-
import { z as
|
|
17827
|
-
var ScanAndFixVulnerabilitiesToolSchema =
|
|
17828
|
-
path:
|
|
18576
|
+
import { z as z33 } from "zod";
|
|
18577
|
+
var ScanAndFixVulnerabilitiesToolSchema = z33.object({
|
|
18578
|
+
path: z33.string()
|
|
17829
18579
|
});
|
|
17830
|
-
var VulnerabilityReportIssueTagSchema =
|
|
17831
|
-
vulnerability_report_issue_tag_value:
|
|
18580
|
+
var VulnerabilityReportIssueTagSchema = z33.object({
|
|
18581
|
+
vulnerability_report_issue_tag_value: z33.nativeEnum(
|
|
17832
18582
|
Vulnerability_Report_Issue_Tag_Enum
|
|
17833
18583
|
)
|
|
17834
18584
|
});
|
|
17835
|
-
var VulnerabilityReportIssueSchema =
|
|
17836
|
-
category:
|
|
17837
|
-
parsedIssueType:
|
|
17838
|
-
parsedSeverity:
|
|
17839
|
-
vulnerabilityReportIssueTags:
|
|
18585
|
+
var VulnerabilityReportIssueSchema = z33.object({
|
|
18586
|
+
category: z33.any().optional().nullable(),
|
|
18587
|
+
parsedIssueType: z33.nativeEnum(IssueType_Enum).nullable().optional(),
|
|
18588
|
+
parsedSeverity: z33.nativeEnum(Vulnerability_Severity_Enum).nullable().optional(),
|
|
18589
|
+
vulnerabilityReportIssueTags: z33.array(VulnerabilityReportIssueTagSchema)
|
|
17840
18590
|
});
|
|
17841
|
-
var SharedStateSchema =
|
|
17842
|
-
__typename:
|
|
17843
|
-
id:
|
|
18591
|
+
var SharedStateSchema = z33.object({
|
|
18592
|
+
__typename: z33.literal("fix_shared_state").optional(),
|
|
18593
|
+
id: z33.any(),
|
|
17844
18594
|
// GraphQL uses `any` type for UUID
|
|
17845
|
-
downloadedBy:
|
|
18595
|
+
downloadedBy: z33.array(z33.any().nullable()).optional().nullable()
|
|
17846
18596
|
});
|
|
17847
|
-
var UnstructuredFixExtraContextSchema =
|
|
17848
|
-
__typename:
|
|
17849
|
-
key:
|
|
17850
|
-
value:
|
|
18597
|
+
var UnstructuredFixExtraContextSchema = z33.object({
|
|
18598
|
+
__typename: z33.literal("UnstructuredFixExtraContext").optional(),
|
|
18599
|
+
key: z33.string(),
|
|
18600
|
+
value: z33.any()
|
|
17851
18601
|
// GraphQL JSON type
|
|
17852
18602
|
});
|
|
17853
|
-
var FixExtraContextResponseSchema =
|
|
17854
|
-
__typename:
|
|
17855
|
-
extraContext:
|
|
17856
|
-
fixDescription:
|
|
18603
|
+
var FixExtraContextResponseSchema = z33.object({
|
|
18604
|
+
__typename: z33.literal("FixExtraContextResponse").optional(),
|
|
18605
|
+
extraContext: z33.array(UnstructuredFixExtraContextSchema),
|
|
18606
|
+
fixDescription: z33.string()
|
|
17857
18607
|
});
|
|
17858
|
-
var FixDataSchema =
|
|
17859
|
-
__typename:
|
|
17860
|
-
patch:
|
|
17861
|
-
patchOriginalEncodingBase64:
|
|
18608
|
+
var FixDataSchema = z33.object({
|
|
18609
|
+
__typename: z33.literal("FixData"),
|
|
18610
|
+
patch: z33.string(),
|
|
18611
|
+
patchOriginalEncodingBase64: z33.string(),
|
|
17862
18612
|
extraContext: FixExtraContextResponseSchema
|
|
17863
18613
|
});
|
|
17864
|
-
var GetFixNoFixErrorSchema =
|
|
17865
|
-
__typename:
|
|
18614
|
+
var GetFixNoFixErrorSchema = z33.object({
|
|
18615
|
+
__typename: z33.literal("GetFixNoFixError")
|
|
17866
18616
|
});
|
|
17867
|
-
var PatchAndQuestionsSchema =
|
|
17868
|
-
var McpFixSchema =
|
|
17869
|
-
__typename:
|
|
17870
|
-
id:
|
|
18617
|
+
var PatchAndQuestionsSchema = z33.union([FixDataSchema, GetFixNoFixErrorSchema]);
|
|
18618
|
+
var McpFixSchema = z33.object({
|
|
18619
|
+
__typename: z33.literal("fix").optional(),
|
|
18620
|
+
id: z33.any(),
|
|
17871
18621
|
// GraphQL uses `any` type for UUID
|
|
17872
|
-
confidence:
|
|
17873
|
-
safeIssueType:
|
|
17874
|
-
severityText:
|
|
17875
|
-
gitBlameLogin:
|
|
18622
|
+
confidence: z33.number(),
|
|
18623
|
+
safeIssueType: z33.string().nullable(),
|
|
18624
|
+
severityText: z33.string().nullable(),
|
|
18625
|
+
gitBlameLogin: z33.string().nullable().optional(),
|
|
17876
18626
|
// Optional in GraphQL
|
|
17877
|
-
severityValue:
|
|
17878
|
-
vulnerabilityReportIssues:
|
|
18627
|
+
severityValue: z33.number().nullable(),
|
|
18628
|
+
vulnerabilityReportIssues: z33.array(VulnerabilityReportIssueSchema),
|
|
17879
18629
|
sharedState: SharedStateSchema.nullable().optional(),
|
|
17880
18630
|
// Optional in GraphQL
|
|
17881
18631
|
patchAndQuestions: PatchAndQuestionsSchema,
|
|
17882
18632
|
// Additional field added by the client
|
|
17883
|
-
fixUrl:
|
|
18633
|
+
fixUrl: z33.string().optional()
|
|
17884
18634
|
});
|
|
17885
|
-
var FixAggregateSchema =
|
|
17886
|
-
__typename:
|
|
17887
|
-
aggregate:
|
|
17888
|
-
__typename:
|
|
17889
|
-
count:
|
|
18635
|
+
var FixAggregateSchema = z33.object({
|
|
18636
|
+
__typename: z33.literal("fix_aggregate").optional(),
|
|
18637
|
+
aggregate: z33.object({
|
|
18638
|
+
__typename: z33.literal("fix_aggregate_fields").optional(),
|
|
18639
|
+
count: z33.number()
|
|
17890
18640
|
}).nullable()
|
|
17891
18641
|
});
|
|
17892
|
-
var VulnerabilityReportIssueAggregateSchema =
|
|
17893
|
-
__typename:
|
|
17894
|
-
aggregate:
|
|
17895
|
-
__typename:
|
|
17896
|
-
count:
|
|
18642
|
+
var VulnerabilityReportIssueAggregateSchema = z33.object({
|
|
18643
|
+
__typename: z33.literal("vulnerability_report_issue_aggregate").optional(),
|
|
18644
|
+
aggregate: z33.object({
|
|
18645
|
+
__typename: z33.literal("vulnerability_report_issue_aggregate_fields").optional(),
|
|
18646
|
+
count: z33.number()
|
|
17897
18647
|
}).nullable()
|
|
17898
18648
|
});
|
|
17899
|
-
var RepoSchema =
|
|
17900
|
-
__typename:
|
|
17901
|
-
originalUrl:
|
|
18649
|
+
var RepoSchema = z33.object({
|
|
18650
|
+
__typename: z33.literal("repo").optional(),
|
|
18651
|
+
originalUrl: z33.string()
|
|
17902
18652
|
});
|
|
17903
|
-
var ProjectSchema =
|
|
17904
|
-
id:
|
|
18653
|
+
var ProjectSchema = z33.object({
|
|
18654
|
+
id: z33.any(),
|
|
17905
18655
|
// GraphQL uses `any` type for UUID
|
|
17906
|
-
organizationId:
|
|
18656
|
+
organizationId: z33.any()
|
|
17907
18657
|
// GraphQL uses `any` type for UUID
|
|
17908
18658
|
});
|
|
17909
|
-
var VulnerabilityReportSchema =
|
|
17910
|
-
scanDate:
|
|
18659
|
+
var VulnerabilityReportSchema = z33.object({
|
|
18660
|
+
scanDate: z33.any().nullable(),
|
|
17911
18661
|
// GraphQL uses `any` type for timestamp
|
|
17912
|
-
vendor:
|
|
18662
|
+
vendor: z33.string(),
|
|
17913
18663
|
// GraphQL generates as string, not enum
|
|
17914
|
-
projectId:
|
|
18664
|
+
projectId: z33.any().optional(),
|
|
17915
18665
|
// GraphQL uses `any` type for UUID
|
|
17916
18666
|
project: ProjectSchema,
|
|
17917
18667
|
totalVulnerabilityReportIssuesCount: VulnerabilityReportIssueAggregateSchema,
|
|
17918
18668
|
notFixableVulnerabilityReportIssuesCount: VulnerabilityReportIssueAggregateSchema
|
|
17919
18669
|
});
|
|
17920
|
-
var FixReportSummarySchema =
|
|
17921
|
-
__typename:
|
|
17922
|
-
id:
|
|
18670
|
+
var FixReportSummarySchema = z33.object({
|
|
18671
|
+
__typename: z33.literal("fixReport").optional(),
|
|
18672
|
+
id: z33.any(),
|
|
17923
18673
|
// GraphQL uses `any` type for UUID
|
|
17924
|
-
createdOn:
|
|
18674
|
+
createdOn: z33.any(),
|
|
17925
18675
|
// GraphQL uses `any` type for timestamp
|
|
17926
18676
|
repo: RepoSchema.nullable(),
|
|
17927
|
-
issueTypes:
|
|
18677
|
+
issueTypes: z33.any().nullable(),
|
|
17928
18678
|
// GraphQL uses `any` type for JSON
|
|
17929
18679
|
CRITICAL: FixAggregateSchema,
|
|
17930
18680
|
HIGH: FixAggregateSchema,
|
|
17931
18681
|
MEDIUM: FixAggregateSchema,
|
|
17932
18682
|
LOW: FixAggregateSchema,
|
|
17933
|
-
fixes:
|
|
17934
|
-
userFixes:
|
|
18683
|
+
fixes: z33.array(McpFixSchema),
|
|
18684
|
+
userFixes: z33.array(McpFixSchema).optional(),
|
|
17935
18685
|
// Present in some responses but can be omitted
|
|
17936
18686
|
filteredFixesCount: FixAggregateSchema,
|
|
17937
18687
|
totalFixesCount: FixAggregateSchema,
|
|
17938
18688
|
vulnerabilityReport: VulnerabilityReportSchema
|
|
17939
18689
|
});
|
|
17940
|
-
var ExpiredReportSchema =
|
|
17941
|
-
__typename:
|
|
17942
|
-
id:
|
|
18690
|
+
var ExpiredReportSchema = z33.object({
|
|
18691
|
+
__typename: z33.literal("fixReport").optional(),
|
|
18692
|
+
id: z33.any(),
|
|
17943
18693
|
// GraphQL uses `any` type for UUID
|
|
17944
|
-
expirationOn:
|
|
18694
|
+
expirationOn: z33.any().nullable()
|
|
17945
18695
|
// GraphQL uses `any` type for timestamp
|
|
17946
18696
|
});
|
|
17947
|
-
var GetLatestReportByRepoUrlResponseSchema =
|
|
17948
|
-
__typename:
|
|
17949
|
-
fixReport:
|
|
17950
|
-
expiredReport:
|
|
18697
|
+
var GetLatestReportByRepoUrlResponseSchema = z33.object({
|
|
18698
|
+
__typename: z33.literal("query_root").optional(),
|
|
18699
|
+
fixReport: z33.array(FixReportSummarySchema),
|
|
18700
|
+
expiredReport: z33.array(ExpiredReportSchema)
|
|
17951
18701
|
});
|
|
17952
18702
|
|
|
17953
18703
|
// src/mcp/services/McpGQLClient.ts
|
|
@@ -18535,9 +19285,9 @@ async function createAuthenticatedMcpGQLClient({
|
|
|
18535
19285
|
|
|
18536
19286
|
// src/mcp/services/McpUsageService/host.ts
|
|
18537
19287
|
import { execSync as execSync2 } from "child_process";
|
|
18538
|
-
import
|
|
18539
|
-
import
|
|
18540
|
-
import
|
|
19288
|
+
import fs15 from "fs";
|
|
19289
|
+
import os8 from "os";
|
|
19290
|
+
import path18 from "path";
|
|
18541
19291
|
var IDEs = ["cursor", "windsurf", "webstorm", "vscode", "claude"];
|
|
18542
19292
|
var runCommand = (cmd) => {
|
|
18543
19293
|
try {
|
|
@@ -18551,18 +19301,18 @@ var gitInfo = {
|
|
|
18551
19301
|
email: runCommand("git config user.email")
|
|
18552
19302
|
};
|
|
18553
19303
|
var getClaudeWorkspacePaths = () => {
|
|
18554
|
-
const home =
|
|
18555
|
-
const claudeIdePath =
|
|
19304
|
+
const home = os8.homedir();
|
|
19305
|
+
const claudeIdePath = path18.join(home, ".claude", "ide");
|
|
18556
19306
|
const workspacePaths = [];
|
|
18557
|
-
if (!
|
|
19307
|
+
if (!fs15.existsSync(claudeIdePath)) {
|
|
18558
19308
|
return workspacePaths;
|
|
18559
19309
|
}
|
|
18560
19310
|
try {
|
|
18561
|
-
const lockFiles =
|
|
19311
|
+
const lockFiles = fs15.readdirSync(claudeIdePath).filter((file) => file.endsWith(".lock"));
|
|
18562
19312
|
for (const lockFile of lockFiles) {
|
|
18563
|
-
const lockFilePath =
|
|
19313
|
+
const lockFilePath = path18.join(claudeIdePath, lockFile);
|
|
18564
19314
|
try {
|
|
18565
|
-
const lockContent = JSON.parse(
|
|
19315
|
+
const lockContent = JSON.parse(fs15.readFileSync(lockFilePath, "utf8"));
|
|
18566
19316
|
if (lockContent.workspaceFolders && Array.isArray(lockContent.workspaceFolders)) {
|
|
18567
19317
|
workspacePaths.push(...lockContent.workspaceFolders);
|
|
18568
19318
|
}
|
|
@@ -18580,29 +19330,29 @@ var getClaudeWorkspacePaths = () => {
|
|
|
18580
19330
|
return workspacePaths;
|
|
18581
19331
|
};
|
|
18582
19332
|
var getMCPConfigPaths = (hostName) => {
|
|
18583
|
-
const home =
|
|
19333
|
+
const home = os8.homedir();
|
|
18584
19334
|
const currentDir = process.env["WORKSPACE_FOLDER_PATHS"] || process.env["PWD"] || process.cwd();
|
|
18585
19335
|
switch (hostName.toLowerCase()) {
|
|
18586
19336
|
case "cursor":
|
|
18587
19337
|
return [
|
|
18588
|
-
|
|
19338
|
+
path18.join(currentDir, ".cursor", "mcp.json"),
|
|
18589
19339
|
// local first
|
|
18590
|
-
|
|
19340
|
+
path18.join(home, ".cursor", "mcp.json")
|
|
18591
19341
|
];
|
|
18592
19342
|
case "windsurf":
|
|
18593
19343
|
return [
|
|
18594
|
-
|
|
19344
|
+
path18.join(currentDir, ".codeium", "mcp_config.json"),
|
|
18595
19345
|
// local first
|
|
18596
|
-
|
|
19346
|
+
path18.join(home, ".codeium", "windsurf", "mcp_config.json")
|
|
18597
19347
|
];
|
|
18598
19348
|
case "webstorm":
|
|
18599
19349
|
return [];
|
|
18600
19350
|
case "visualstudiocode":
|
|
18601
19351
|
case "vscode":
|
|
18602
19352
|
return [
|
|
18603
|
-
|
|
19353
|
+
path18.join(currentDir, ".vscode", "mcp.json"),
|
|
18604
19354
|
// local first
|
|
18605
|
-
process.platform === "win32" ?
|
|
19355
|
+
process.platform === "win32" ? path18.join(home, "AppData", "Roaming", "Code", "User", "mcp.json") : path18.join(
|
|
18606
19356
|
home,
|
|
18607
19357
|
"Library",
|
|
18608
19358
|
"Application Support",
|
|
@@ -18613,13 +19363,13 @@ var getMCPConfigPaths = (hostName) => {
|
|
|
18613
19363
|
];
|
|
18614
19364
|
case "claude": {
|
|
18615
19365
|
const claudePaths = [
|
|
18616
|
-
|
|
19366
|
+
path18.join(currentDir, ".claude.json"),
|
|
18617
19367
|
// local first
|
|
18618
|
-
|
|
19368
|
+
path18.join(home, ".claude.json")
|
|
18619
19369
|
];
|
|
18620
19370
|
const workspacePaths = getClaudeWorkspacePaths();
|
|
18621
19371
|
for (const workspacePath of workspacePaths) {
|
|
18622
|
-
claudePaths.push(
|
|
19372
|
+
claudePaths.push(path18.join(workspacePath, ".mcp.json"));
|
|
18623
19373
|
}
|
|
18624
19374
|
return claudePaths;
|
|
18625
19375
|
}
|
|
@@ -18628,9 +19378,9 @@ var getMCPConfigPaths = (hostName) => {
|
|
|
18628
19378
|
}
|
|
18629
19379
|
};
|
|
18630
19380
|
var readConfigFile = (filePath) => {
|
|
18631
|
-
if (!
|
|
19381
|
+
if (!fs15.existsSync(filePath)) return null;
|
|
18632
19382
|
try {
|
|
18633
|
-
return JSON.parse(
|
|
19383
|
+
return JSON.parse(fs15.readFileSync(filePath, "utf8"));
|
|
18634
19384
|
} catch (error) {
|
|
18635
19385
|
logWarn(`[UsageService] Failed to read MCP config: ${filePath}`);
|
|
18636
19386
|
return null;
|
|
@@ -18670,7 +19420,7 @@ var readMCPConfig = (hostName) => {
|
|
|
18670
19420
|
};
|
|
18671
19421
|
var getRunningProcesses = () => {
|
|
18672
19422
|
try {
|
|
18673
|
-
return
|
|
19423
|
+
return os8.platform() === "win32" ? execSync2("tasklist", { encoding: "utf8" }) : execSync2("ps aux", { encoding: "utf8" });
|
|
18674
19424
|
} catch {
|
|
18675
19425
|
return "";
|
|
18676
19426
|
}
|
|
@@ -18745,7 +19495,7 @@ var versionCommands = {
|
|
|
18745
19495
|
}
|
|
18746
19496
|
};
|
|
18747
19497
|
var getProcessInfo = (pid) => {
|
|
18748
|
-
const platform2 =
|
|
19498
|
+
const platform2 = os8.platform();
|
|
18749
19499
|
try {
|
|
18750
19500
|
if (platform2 === "linux" || platform2 === "darwin") {
|
|
18751
19501
|
const output = execSync2(`ps -o pid=,ppid=,comm= -p ${pid}`, {
|
|
@@ -18780,10 +19530,10 @@ var getHostInfo = (additionalMcpList) => {
|
|
|
18780
19530
|
const ideConfigPaths = /* @__PURE__ */ new Set();
|
|
18781
19531
|
for (const ide of IDEs) {
|
|
18782
19532
|
const configPaths = getMCPConfigPaths(ide);
|
|
18783
|
-
configPaths.forEach((
|
|
19533
|
+
configPaths.forEach((path30) => ideConfigPaths.add(path30));
|
|
18784
19534
|
}
|
|
18785
19535
|
const uniqueAdditionalPaths = additionalMcpList.filter(
|
|
18786
|
-
(
|
|
19536
|
+
(path30) => !ideConfigPaths.has(path30)
|
|
18787
19537
|
);
|
|
18788
19538
|
for (const ide of IDEs) {
|
|
18789
19539
|
const cfg = readMCPConfig(ide);
|
|
@@ -18864,7 +19614,7 @@ var getHostInfo = (additionalMcpList) => {
|
|
|
18864
19614
|
const config2 = allConfigs[ide] || null;
|
|
18865
19615
|
const ideName = ide.charAt(0).toUpperCase() + ide.slice(1) || "Unknown";
|
|
18866
19616
|
let ideVersion = "Unknown";
|
|
18867
|
-
const platform2 =
|
|
19617
|
+
const platform2 = os8.platform();
|
|
18868
19618
|
const cmds = versionCommands[ideName]?.[platform2] ?? [];
|
|
18869
19619
|
for (const cmd of cmds) {
|
|
18870
19620
|
try {
|
|
@@ -18897,15 +19647,15 @@ var getHostInfo = (additionalMcpList) => {
|
|
|
18897
19647
|
|
|
18898
19648
|
// src/mcp/services/McpUsageService/McpUsageService.ts
|
|
18899
19649
|
import fetch6 from "node-fetch";
|
|
18900
|
-
import
|
|
19650
|
+
import os10 from "os";
|
|
18901
19651
|
import { v4 as uuidv42, v5 as uuidv5 } from "uuid";
|
|
18902
19652
|
init_configs();
|
|
18903
19653
|
|
|
18904
19654
|
// src/mcp/services/McpUsageService/system.ts
|
|
18905
19655
|
init_configs();
|
|
18906
|
-
import
|
|
18907
|
-
import
|
|
18908
|
-
import
|
|
19656
|
+
import fs16 from "fs";
|
|
19657
|
+
import os9 from "os";
|
|
19658
|
+
import path19 from "path";
|
|
18909
19659
|
var MAX_DEPTH = 2;
|
|
18910
19660
|
var patterns = ["mcp", "claude"];
|
|
18911
19661
|
var isFileMatch = (fileName) => {
|
|
@@ -18914,7 +19664,7 @@ var isFileMatch = (fileName) => {
|
|
|
18914
19664
|
};
|
|
18915
19665
|
var safeAccess = async (filePath) => {
|
|
18916
19666
|
try {
|
|
18917
|
-
await
|
|
19667
|
+
await fs16.promises.access(filePath, fs16.constants.R_OK);
|
|
18918
19668
|
return true;
|
|
18919
19669
|
} catch {
|
|
18920
19670
|
return false;
|
|
@@ -18923,9 +19673,9 @@ var safeAccess = async (filePath) => {
|
|
|
18923
19673
|
var searchDir = async (dir, depth = 0) => {
|
|
18924
19674
|
const results = [];
|
|
18925
19675
|
if (depth > MAX_DEPTH) return results;
|
|
18926
|
-
const entries = await
|
|
19676
|
+
const entries = await fs16.promises.readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
18927
19677
|
for (const entry of entries) {
|
|
18928
|
-
const fullPath =
|
|
19678
|
+
const fullPath = path19.join(dir, entry.name);
|
|
18929
19679
|
if (entry.isFile() && isFileMatch(entry.name)) {
|
|
18930
19680
|
results.push(fullPath);
|
|
18931
19681
|
} else if (entry.isDirectory()) {
|
|
@@ -18939,17 +19689,17 @@ var searchDir = async (dir, depth = 0) => {
|
|
|
18939
19689
|
};
|
|
18940
19690
|
var findSystemMCPConfigs = async () => {
|
|
18941
19691
|
try {
|
|
18942
|
-
const home =
|
|
18943
|
-
const platform2 =
|
|
19692
|
+
const home = os9.homedir();
|
|
19693
|
+
const platform2 = os9.platform();
|
|
18944
19694
|
const knownDirs = platform2 === "win32" ? [
|
|
18945
|
-
|
|
18946
|
-
|
|
18947
|
-
|
|
19695
|
+
path19.join(home, ".cursor"),
|
|
19696
|
+
path19.join(home, "Documents"),
|
|
19697
|
+
path19.join(home, "Downloads")
|
|
18948
19698
|
] : [
|
|
18949
|
-
|
|
18950
|
-
process.env["XDG_CONFIG_HOME"] ||
|
|
18951
|
-
|
|
18952
|
-
|
|
19699
|
+
path19.join(home, ".cursor"),
|
|
19700
|
+
process.env["XDG_CONFIG_HOME"] || path19.join(home, ".config"),
|
|
19701
|
+
path19.join(home, "Documents"),
|
|
19702
|
+
path19.join(home, "Downloads")
|
|
18953
19703
|
];
|
|
18954
19704
|
const timeoutPromise = new Promise(
|
|
18955
19705
|
(resolve) => setTimeout(() => {
|
|
@@ -18961,7 +19711,7 @@ var findSystemMCPConfigs = async () => {
|
|
|
18961
19711
|
);
|
|
18962
19712
|
const searchPromise = Promise.all(
|
|
18963
19713
|
knownDirs.map(
|
|
18964
|
-
(dir) =>
|
|
19714
|
+
(dir) => fs16.existsSync(dir) ? searchDir(dir) : Promise.resolve([])
|
|
18965
19715
|
)
|
|
18966
19716
|
).then((results) => results.flat());
|
|
18967
19717
|
return await Promise.race([timeoutPromise, searchPromise]);
|
|
@@ -19012,7 +19762,7 @@ var McpUsageService = class {
|
|
|
19012
19762
|
generateHostId() {
|
|
19013
19763
|
const stored = configStore.get(this.configKey);
|
|
19014
19764
|
if (stored?.mcpHostId) return stored.mcpHostId;
|
|
19015
|
-
const interfaces =
|
|
19765
|
+
const interfaces = os10.networkInterfaces();
|
|
19016
19766
|
const macs = [];
|
|
19017
19767
|
for (const iface of Object.values(interfaces)) {
|
|
19018
19768
|
if (!iface) continue;
|
|
@@ -19020,7 +19770,7 @@ var McpUsageService = class {
|
|
|
19020
19770
|
if (net.mac && net.mac !== "00:00:00:00:00:00") macs.push(net.mac);
|
|
19021
19771
|
}
|
|
19022
19772
|
}
|
|
19023
|
-
const macString = macs.length ? macs.sort().join(",") : `${
|
|
19773
|
+
const macString = macs.length ? macs.sort().join(",") : `${os10.hostname()}-${uuidv42()}`;
|
|
19024
19774
|
const hostId = uuidv5(macString, uuidv5.DNS);
|
|
19025
19775
|
logDebug("[UsageService] Generated new host ID", { hostId });
|
|
19026
19776
|
return hostId;
|
|
@@ -19043,7 +19793,7 @@ var McpUsageService = class {
|
|
|
19043
19793
|
mcpHostId,
|
|
19044
19794
|
organizationId,
|
|
19045
19795
|
mcpVersion: packageJson.version,
|
|
19046
|
-
mcpOsName:
|
|
19796
|
+
mcpOsName: os10.platform(),
|
|
19047
19797
|
mcps: JSON.stringify(mcps),
|
|
19048
19798
|
status,
|
|
19049
19799
|
userName: user.name,
|
|
@@ -19704,10 +20454,10 @@ var McpServer = class {
|
|
|
19704
20454
|
};
|
|
19705
20455
|
|
|
19706
20456
|
// src/mcp/prompts/CheckForNewVulnerabilitiesPrompt.ts
|
|
19707
|
-
import { z as
|
|
20457
|
+
import { z as z35 } from "zod";
|
|
19708
20458
|
|
|
19709
20459
|
// src/mcp/prompts/base/BasePrompt.ts
|
|
19710
|
-
import { z as
|
|
20460
|
+
import { z as z34 } from "zod";
|
|
19711
20461
|
var BasePrompt = class {
|
|
19712
20462
|
getDefinition() {
|
|
19713
20463
|
return {
|
|
@@ -19736,7 +20486,7 @@ var BasePrompt = class {
|
|
|
19736
20486
|
const argsToValidate = args === void 0 ? {} : args;
|
|
19737
20487
|
return this.argumentsValidationSchema.parse(argsToValidate);
|
|
19738
20488
|
} catch (error) {
|
|
19739
|
-
if (error instanceof
|
|
20489
|
+
if (error instanceof z34.ZodError) {
|
|
19740
20490
|
const errorDetails = error.errors.map((e) => {
|
|
19741
20491
|
const fieldPath = e.path.length > 0 ? e.path.join(".") : "root";
|
|
19742
20492
|
const message = e.message === "Required" ? `Missing required argument '${fieldPath}'` : `Invalid value for '${fieldPath}': ${e.message}`;
|
|
@@ -19765,8 +20515,8 @@ var BasePrompt = class {
|
|
|
19765
20515
|
};
|
|
19766
20516
|
|
|
19767
20517
|
// src/mcp/prompts/CheckForNewVulnerabilitiesPrompt.ts
|
|
19768
|
-
var CheckForNewVulnerabilitiesArgsSchema =
|
|
19769
|
-
path:
|
|
20518
|
+
var CheckForNewVulnerabilitiesArgsSchema = z35.object({
|
|
20519
|
+
path: z35.string().optional()
|
|
19770
20520
|
});
|
|
19771
20521
|
var CheckForNewVulnerabilitiesPrompt = class extends BasePrompt {
|
|
19772
20522
|
constructor() {
|
|
@@ -20011,9 +20761,9 @@ Call the \`check_for_new_available_fixes\` tool now${args?.path ? ` for ${args.p
|
|
|
20011
20761
|
};
|
|
20012
20762
|
|
|
20013
20763
|
// src/mcp/prompts/FullSecurityAuditPrompt.ts
|
|
20014
|
-
import { z as
|
|
20015
|
-
var FullSecurityAuditArgsSchema =
|
|
20016
|
-
path:
|
|
20764
|
+
import { z as z36 } from "zod";
|
|
20765
|
+
var FullSecurityAuditArgsSchema = z36.object({
|
|
20766
|
+
path: z36.string().optional()
|
|
20017
20767
|
});
|
|
20018
20768
|
var FullSecurityAuditPrompt = class extends BasePrompt {
|
|
20019
20769
|
constructor() {
|
|
@@ -20464,9 +21214,9 @@ Begin the audit now${args?.path ? ` for ${args.path}` : ""}.
|
|
|
20464
21214
|
};
|
|
20465
21215
|
|
|
20466
21216
|
// src/mcp/prompts/ReviewAndFixCriticalPrompt.ts
|
|
20467
|
-
import { z as
|
|
20468
|
-
var ReviewAndFixCriticalArgsSchema =
|
|
20469
|
-
path:
|
|
21217
|
+
import { z as z37 } from "zod";
|
|
21218
|
+
var ReviewAndFixCriticalArgsSchema = z37.object({
|
|
21219
|
+
path: z37.string().optional()
|
|
20470
21220
|
});
|
|
20471
21221
|
var ReviewAndFixCriticalPrompt = class extends BasePrompt {
|
|
20472
21222
|
constructor() {
|
|
@@ -20770,9 +21520,9 @@ Start by scanning${args?.path ? ` ${args.path}` : " the repository"} and priorit
|
|
|
20770
21520
|
};
|
|
20771
21521
|
|
|
20772
21522
|
// src/mcp/prompts/ScanRecentChangesPrompt.ts
|
|
20773
|
-
import { z as
|
|
20774
|
-
var ScanRecentChangesArgsSchema =
|
|
20775
|
-
path:
|
|
21523
|
+
import { z as z38 } from "zod";
|
|
21524
|
+
var ScanRecentChangesArgsSchema = z38.object({
|
|
21525
|
+
path: z38.string().optional()
|
|
20776
21526
|
});
|
|
20777
21527
|
var ScanRecentChangesPrompt = class extends BasePrompt {
|
|
20778
21528
|
constructor() {
|
|
@@ -20983,9 +21733,9 @@ You now have the guidance needed to perform a fast, targeted security scan of re
|
|
|
20983
21733
|
};
|
|
20984
21734
|
|
|
20985
21735
|
// src/mcp/prompts/ScanRepositoryPrompt.ts
|
|
20986
|
-
import { z as
|
|
20987
|
-
var ScanRepositoryArgsSchema =
|
|
20988
|
-
path:
|
|
21736
|
+
import { z as z39 } from "zod";
|
|
21737
|
+
var ScanRepositoryArgsSchema = z39.object({
|
|
21738
|
+
path: z39.string().optional()
|
|
20989
21739
|
});
|
|
20990
21740
|
var ScanRepositoryPrompt = class extends BasePrompt {
|
|
20991
21741
|
constructor() {
|
|
@@ -21363,31 +22113,31 @@ For a complete security audit workflow, use the \`full-security-audit\` prompt.
|
|
|
21363
22113
|
};
|
|
21364
22114
|
|
|
21365
22115
|
// src/mcp/services/McpDetectionService/CursorMcpDetectionService.ts
|
|
21366
|
-
import * as
|
|
21367
|
-
import * as
|
|
21368
|
-
import * as
|
|
22116
|
+
import * as fs19 from "fs";
|
|
22117
|
+
import * as os12 from "os";
|
|
22118
|
+
import * as path21 from "path";
|
|
21369
22119
|
|
|
21370
22120
|
// src/mcp/services/McpDetectionService/BaseMcpDetectionService.ts
|
|
21371
22121
|
init_configs();
|
|
21372
|
-
import * as
|
|
22122
|
+
import * as fs18 from "fs";
|
|
21373
22123
|
import fetch7 from "node-fetch";
|
|
21374
|
-
import * as
|
|
22124
|
+
import * as path20 from "path";
|
|
21375
22125
|
|
|
21376
22126
|
// src/mcp/services/McpDetectionService/McpDetectionServiceUtils.ts
|
|
21377
|
-
import * as
|
|
21378
|
-
import * as
|
|
22127
|
+
import * as fs17 from "fs";
|
|
22128
|
+
import * as os11 from "os";
|
|
21379
22129
|
|
|
21380
22130
|
// src/mcp/services/McpDetectionService/VscodeMcpDetectionService.ts
|
|
21381
|
-
import * as
|
|
21382
|
-
import * as
|
|
21383
|
-
import * as
|
|
22131
|
+
import * as fs20 from "fs";
|
|
22132
|
+
import * as os13 from "os";
|
|
22133
|
+
import * as path22 from "path";
|
|
21384
22134
|
|
|
21385
22135
|
// src/mcp/tools/checkForNewAvailableFixes/CheckForNewAvailableFixesTool.ts
|
|
21386
|
-
import { z as
|
|
22136
|
+
import { z as z42 } from "zod";
|
|
21387
22137
|
|
|
21388
22138
|
// src/mcp/services/PathValidation.ts
|
|
21389
|
-
import
|
|
21390
|
-
import
|
|
22139
|
+
import fs21 from "fs";
|
|
22140
|
+
import path23 from "path";
|
|
21391
22141
|
async function validatePath(inputPath) {
|
|
21392
22142
|
logDebug("Validating MCP path", { inputPath });
|
|
21393
22143
|
if (/^\/[a-zA-Z]:\//.test(inputPath)) {
|
|
@@ -21419,7 +22169,7 @@ async function validatePath(inputPath) {
|
|
|
21419
22169
|
logError(error);
|
|
21420
22170
|
return { isValid: false, error, path: inputPath };
|
|
21421
22171
|
}
|
|
21422
|
-
const normalizedPath =
|
|
22172
|
+
const normalizedPath = path23.normalize(inputPath);
|
|
21423
22173
|
if (normalizedPath.includes("..")) {
|
|
21424
22174
|
const error = `Normalized path contains path traversal patterns: ${inputPath}`;
|
|
21425
22175
|
logError(error);
|
|
@@ -21446,7 +22196,7 @@ async function validatePath(inputPath) {
|
|
|
21446
22196
|
logDebug("Path validation successful", { inputPath });
|
|
21447
22197
|
logDebug("Checking path existence", { inputPath });
|
|
21448
22198
|
try {
|
|
21449
|
-
await
|
|
22199
|
+
await fs21.promises.access(inputPath);
|
|
21450
22200
|
logDebug("Path exists and is accessible", { inputPath });
|
|
21451
22201
|
WorkspaceService.setKnownWorkspacePath(inputPath);
|
|
21452
22202
|
logDebug("Stored validated path in WorkspaceService", { inputPath });
|
|
@@ -21459,7 +22209,7 @@ async function validatePath(inputPath) {
|
|
|
21459
22209
|
}
|
|
21460
22210
|
|
|
21461
22211
|
// src/mcp/tools/base/BaseTool.ts
|
|
21462
|
-
import { z as
|
|
22212
|
+
import { z as z40 } from "zod";
|
|
21463
22213
|
var BaseTool = class {
|
|
21464
22214
|
getDefinition() {
|
|
21465
22215
|
return {
|
|
@@ -21486,7 +22236,7 @@ var BaseTool = class {
|
|
|
21486
22236
|
try {
|
|
21487
22237
|
return this.inputValidationSchema.parse(args);
|
|
21488
22238
|
} catch (error) {
|
|
21489
|
-
if (error instanceof
|
|
22239
|
+
if (error instanceof z40.ZodError) {
|
|
21490
22240
|
const errorDetails = error.errors.map((e) => {
|
|
21491
22241
|
const fieldPath = e.path.length > 0 ? e.path.join(".") : "root";
|
|
21492
22242
|
const message = e.message === "Required" ? `Missing required parameter '${fieldPath}'` : `Invalid value for '${fieldPath}': ${e.message}`;
|
|
@@ -22068,10 +22818,10 @@ If you wish to scan files that were recently changed in your git history call th
|
|
|
22068
22818
|
init_FileUtils();
|
|
22069
22819
|
init_GitService();
|
|
22070
22820
|
init_configs();
|
|
22071
|
-
import
|
|
22821
|
+
import fs22 from "fs/promises";
|
|
22072
22822
|
import nodePath from "path";
|
|
22073
22823
|
var getLocalFiles = async ({
|
|
22074
|
-
path:
|
|
22824
|
+
path: path30,
|
|
22075
22825
|
maxFileSize = MCP_MAX_FILE_SIZE,
|
|
22076
22826
|
maxFiles,
|
|
22077
22827
|
isAllFilesScan,
|
|
@@ -22079,17 +22829,17 @@ var getLocalFiles = async ({
|
|
|
22079
22829
|
scanRecentlyChangedFiles
|
|
22080
22830
|
}) => {
|
|
22081
22831
|
logDebug(`[${scanContext}] Starting getLocalFiles`, {
|
|
22082
|
-
path:
|
|
22832
|
+
path: path30,
|
|
22083
22833
|
maxFileSize,
|
|
22084
22834
|
maxFiles,
|
|
22085
22835
|
isAllFilesScan,
|
|
22086
22836
|
scanRecentlyChangedFiles
|
|
22087
22837
|
});
|
|
22088
22838
|
try {
|
|
22089
|
-
const resolvedRepoPath = await
|
|
22839
|
+
const resolvedRepoPath = await fs22.realpath(path30);
|
|
22090
22840
|
logDebug(`[${scanContext}] Resolved repository path`, {
|
|
22091
22841
|
resolvedRepoPath,
|
|
22092
|
-
originalPath:
|
|
22842
|
+
originalPath: path30
|
|
22093
22843
|
});
|
|
22094
22844
|
const gitService = new GitService(resolvedRepoPath, log);
|
|
22095
22845
|
const gitValidation = await gitService.validateRepository();
|
|
@@ -22102,7 +22852,7 @@ var getLocalFiles = async ({
|
|
|
22102
22852
|
if (!gitValidation.isValid || isAllFilesScan) {
|
|
22103
22853
|
try {
|
|
22104
22854
|
files = await FileUtils.getLastChangedFiles({
|
|
22105
|
-
dir:
|
|
22855
|
+
dir: path30,
|
|
22106
22856
|
maxFileSize,
|
|
22107
22857
|
maxFiles,
|
|
22108
22858
|
isAllFilesScan
|
|
@@ -22166,7 +22916,7 @@ var getLocalFiles = async ({
|
|
|
22166
22916
|
absoluteFilePath
|
|
22167
22917
|
);
|
|
22168
22918
|
try {
|
|
22169
|
-
const fileStat = await
|
|
22919
|
+
const fileStat = await fs22.stat(absoluteFilePath);
|
|
22170
22920
|
return {
|
|
22171
22921
|
filename: nodePath.basename(absoluteFilePath),
|
|
22172
22922
|
relativePath,
|
|
@@ -22194,7 +22944,7 @@ var getLocalFiles = async ({
|
|
|
22194
22944
|
logError(`${scanContext}Unexpected error in getLocalFiles`, {
|
|
22195
22945
|
error: error instanceof Error ? error.message : String(error),
|
|
22196
22946
|
stack: error instanceof Error ? error.stack : void 0,
|
|
22197
|
-
path:
|
|
22947
|
+
path: path30
|
|
22198
22948
|
});
|
|
22199
22949
|
throw error;
|
|
22200
22950
|
}
|
|
@@ -22203,15 +22953,15 @@ var getLocalFiles = async ({
|
|
|
22203
22953
|
// src/mcp/services/LocalMobbFolderService.ts
|
|
22204
22954
|
init_client_generates();
|
|
22205
22955
|
init_GitService();
|
|
22206
|
-
import
|
|
22207
|
-
import
|
|
22208
|
-
import { z as
|
|
22956
|
+
import fs23 from "fs";
|
|
22957
|
+
import path24 from "path";
|
|
22958
|
+
import { z as z41 } from "zod";
|
|
22209
22959
|
function extractPathFromPatch(patch) {
|
|
22210
22960
|
const match = patch?.match(/diff --git a\/([^\s]+) b\//);
|
|
22211
22961
|
return match?.[1] ?? null;
|
|
22212
22962
|
}
|
|
22213
22963
|
function parsedIssueTypeRes(issueType) {
|
|
22214
|
-
return
|
|
22964
|
+
return z41.nativeEnum(IssueType_Enum).safeParse(issueType);
|
|
22215
22965
|
}
|
|
22216
22966
|
var LocalMobbFolderService = class {
|
|
22217
22967
|
/**
|
|
@@ -22290,19 +23040,19 @@ var LocalMobbFolderService = class {
|
|
|
22290
23040
|
"[LocalMobbFolderService] Non-git repository detected, skipping .gitignore operations"
|
|
22291
23041
|
);
|
|
22292
23042
|
}
|
|
22293
|
-
const mobbFolderPath =
|
|
23043
|
+
const mobbFolderPath = path24.join(
|
|
22294
23044
|
this.repoPath,
|
|
22295
23045
|
this.defaultMobbFolderName
|
|
22296
23046
|
);
|
|
22297
|
-
if (!
|
|
23047
|
+
if (!fs23.existsSync(mobbFolderPath)) {
|
|
22298
23048
|
logInfo("[LocalMobbFolderService] Creating .mobb folder", {
|
|
22299
23049
|
mobbFolderPath
|
|
22300
23050
|
});
|
|
22301
|
-
|
|
23051
|
+
fs23.mkdirSync(mobbFolderPath, { recursive: true });
|
|
22302
23052
|
} else {
|
|
22303
23053
|
logDebug("[LocalMobbFolderService] .mobb folder already exists");
|
|
22304
23054
|
}
|
|
22305
|
-
const stats =
|
|
23055
|
+
const stats = fs23.statSync(mobbFolderPath);
|
|
22306
23056
|
if (!stats.isDirectory()) {
|
|
22307
23057
|
throw new Error(`Path exists but is not a directory: ${mobbFolderPath}`);
|
|
22308
23058
|
}
|
|
@@ -22343,13 +23093,13 @@ var LocalMobbFolderService = class {
|
|
|
22343
23093
|
logDebug("[LocalMobbFolderService] Git repository validated successfully");
|
|
22344
23094
|
} else {
|
|
22345
23095
|
try {
|
|
22346
|
-
const stats =
|
|
23096
|
+
const stats = fs23.statSync(this.repoPath);
|
|
22347
23097
|
if (!stats.isDirectory()) {
|
|
22348
23098
|
throw new Error(
|
|
22349
23099
|
`Path exists but is not a directory: ${this.repoPath}`
|
|
22350
23100
|
);
|
|
22351
23101
|
}
|
|
22352
|
-
|
|
23102
|
+
fs23.accessSync(this.repoPath, fs23.constants.R_OK | fs23.constants.W_OK);
|
|
22353
23103
|
logDebug(
|
|
22354
23104
|
"[LocalMobbFolderService] Non-git directory validated successfully"
|
|
22355
23105
|
);
|
|
@@ -22462,8 +23212,8 @@ var LocalMobbFolderService = class {
|
|
|
22462
23212
|
mobbFolderPath,
|
|
22463
23213
|
baseFileName
|
|
22464
23214
|
);
|
|
22465
|
-
const filePath =
|
|
22466
|
-
await
|
|
23215
|
+
const filePath = path24.join(mobbFolderPath, uniqueFileName);
|
|
23216
|
+
await fs23.promises.writeFile(filePath, patch, "utf8");
|
|
22467
23217
|
logInfo("[LocalMobbFolderService] Patch saved successfully", {
|
|
22468
23218
|
filePath,
|
|
22469
23219
|
fileName: uniqueFileName,
|
|
@@ -22520,11 +23270,11 @@ var LocalMobbFolderService = class {
|
|
|
22520
23270
|
* @returns Unique filename that doesn't conflict with existing files
|
|
22521
23271
|
*/
|
|
22522
23272
|
getUniqueFileName(folderPath, baseFileName) {
|
|
22523
|
-
const baseName =
|
|
22524
|
-
const extension =
|
|
23273
|
+
const baseName = path24.parse(baseFileName).name;
|
|
23274
|
+
const extension = path24.parse(baseFileName).ext;
|
|
22525
23275
|
let uniqueFileName = baseFileName;
|
|
22526
23276
|
let index = 1;
|
|
22527
|
-
while (
|
|
23277
|
+
while (fs23.existsSync(path24.join(folderPath, uniqueFileName))) {
|
|
22528
23278
|
uniqueFileName = `${baseName}-${index}${extension}`;
|
|
22529
23279
|
index++;
|
|
22530
23280
|
if (index > 1e3) {
|
|
@@ -22555,18 +23305,18 @@ var LocalMobbFolderService = class {
|
|
|
22555
23305
|
logDebug("[LocalMobbFolderService] Logging patch info", { fixId: fix.id });
|
|
22556
23306
|
try {
|
|
22557
23307
|
const mobbFolderPath = await this.getFolder();
|
|
22558
|
-
const patchInfoPath =
|
|
23308
|
+
const patchInfoPath = path24.join(mobbFolderPath, "patchInfo.md");
|
|
22559
23309
|
const markdownContent = this.generateFixMarkdown(fix, savedPatchFileName);
|
|
22560
23310
|
let existingContent = "";
|
|
22561
|
-
if (
|
|
22562
|
-
existingContent = await
|
|
23311
|
+
if (fs23.existsSync(patchInfoPath)) {
|
|
23312
|
+
existingContent = await fs23.promises.readFile(patchInfoPath, "utf8");
|
|
22563
23313
|
logDebug("[LocalMobbFolderService] Existing patchInfo.md found");
|
|
22564
23314
|
} else {
|
|
22565
23315
|
logDebug("[LocalMobbFolderService] Creating new patchInfo.md file");
|
|
22566
23316
|
}
|
|
22567
23317
|
const separator = existingContent ? "\n\n================================================================================\n\n" : "";
|
|
22568
23318
|
const updatedContent = `${markdownContent}${separator}${existingContent}`;
|
|
22569
|
-
await
|
|
23319
|
+
await fs23.promises.writeFile(patchInfoPath, updatedContent, "utf8");
|
|
22570
23320
|
logInfo("[LocalMobbFolderService] Patch info logged successfully", {
|
|
22571
23321
|
patchInfoPath,
|
|
22572
23322
|
fixId: fix.id,
|
|
@@ -22597,7 +23347,7 @@ var LocalMobbFolderService = class {
|
|
|
22597
23347
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
22598
23348
|
const patch = this.extractPatchFromFix(fix);
|
|
22599
23349
|
const relativePatchedFilePath = patch ? extractPathFromPatch(patch) : null;
|
|
22600
|
-
const patchedFilePath = relativePatchedFilePath ?
|
|
23350
|
+
const patchedFilePath = relativePatchedFilePath ? path24.resolve(this.repoPath, relativePatchedFilePath) : null;
|
|
22601
23351
|
const fixIdentifier = savedPatchFileName ? savedPatchFileName.replace(".patch", "") : fix.id;
|
|
22602
23352
|
let markdown = `# Fix ${fixIdentifier}
|
|
22603
23353
|
|
|
@@ -22939,16 +23689,16 @@ import {
|
|
|
22939
23689
|
unlinkSync,
|
|
22940
23690
|
writeFileSync as writeFileSync2
|
|
22941
23691
|
} from "fs";
|
|
22942
|
-
import
|
|
23692
|
+
import fs24 from "fs/promises";
|
|
22943
23693
|
import parseDiff2 from "parse-diff";
|
|
22944
|
-
import
|
|
23694
|
+
import path25 from "path";
|
|
22945
23695
|
var PatchApplicationService = class {
|
|
22946
23696
|
/**
|
|
22947
23697
|
* Gets the appropriate comment syntax for a file based on its extension
|
|
22948
23698
|
*/
|
|
22949
23699
|
static getCommentSyntax(filePath) {
|
|
22950
|
-
const ext =
|
|
22951
|
-
const basename2 =
|
|
23700
|
+
const ext = path25.extname(filePath).toLowerCase();
|
|
23701
|
+
const basename2 = path25.basename(filePath);
|
|
22952
23702
|
const commentMap = {
|
|
22953
23703
|
// C-style languages (single line comments)
|
|
22954
23704
|
".js": "//",
|
|
@@ -23156,7 +23906,7 @@ var PatchApplicationService = class {
|
|
|
23156
23906
|
}
|
|
23157
23907
|
);
|
|
23158
23908
|
}
|
|
23159
|
-
const dirPath =
|
|
23909
|
+
const dirPath = path25.dirname(normalizedFilePath);
|
|
23160
23910
|
mkdirSync(dirPath, { recursive: true });
|
|
23161
23911
|
writeFileSync2(normalizedFilePath, finalContent, "utf8");
|
|
23162
23912
|
return normalizedFilePath;
|
|
@@ -23165,9 +23915,9 @@ var PatchApplicationService = class {
|
|
|
23165
23915
|
repositoryPath,
|
|
23166
23916
|
targetPath
|
|
23167
23917
|
}) {
|
|
23168
|
-
const repoRoot =
|
|
23169
|
-
const normalizedPath =
|
|
23170
|
-
const repoRootWithSep = repoRoot.endsWith(
|
|
23918
|
+
const repoRoot = path25.resolve(repositoryPath);
|
|
23919
|
+
const normalizedPath = path25.resolve(repoRoot, targetPath);
|
|
23920
|
+
const repoRootWithSep = repoRoot.endsWith(path25.sep) ? repoRoot : `${repoRoot}${path25.sep}`;
|
|
23171
23921
|
if (normalizedPath !== repoRoot && !normalizedPath.startsWith(repoRootWithSep)) {
|
|
23172
23922
|
throw new Error(
|
|
23173
23923
|
`Security violation: target path ${targetPath} resolves outside repository`
|
|
@@ -23176,7 +23926,7 @@ var PatchApplicationService = class {
|
|
|
23176
23926
|
return {
|
|
23177
23927
|
repoRoot,
|
|
23178
23928
|
normalizedPath,
|
|
23179
|
-
relativePath:
|
|
23929
|
+
relativePath: path25.relative(repoRoot, normalizedPath)
|
|
23180
23930
|
};
|
|
23181
23931
|
}
|
|
23182
23932
|
/**
|
|
@@ -23458,9 +24208,9 @@ var PatchApplicationService = class {
|
|
|
23458
24208
|
continue;
|
|
23459
24209
|
}
|
|
23460
24210
|
try {
|
|
23461
|
-
const absolutePath =
|
|
24211
|
+
const absolutePath = path25.resolve(repositoryPath, targetFile);
|
|
23462
24212
|
if (existsSync6(absolutePath)) {
|
|
23463
|
-
const stats = await
|
|
24213
|
+
const stats = await fs24.stat(absolutePath);
|
|
23464
24214
|
const fileModTime = stats.mtime.getTime();
|
|
23465
24215
|
if (fileModTime > scanStartTime) {
|
|
23466
24216
|
logError(
|
|
@@ -23501,7 +24251,7 @@ var PatchApplicationService = class {
|
|
|
23501
24251
|
const appliedFixes = [];
|
|
23502
24252
|
const failedFixes = [];
|
|
23503
24253
|
const skippedFixes = [];
|
|
23504
|
-
const resolvedRepoPath = await
|
|
24254
|
+
const resolvedRepoPath = await fs24.realpath(repositoryPath);
|
|
23505
24255
|
logInfo(
|
|
23506
24256
|
`[${scanContext}] Starting patch application for ${fixes.length} fixes`,
|
|
23507
24257
|
{
|
|
@@ -23684,7 +24434,7 @@ var PatchApplicationService = class {
|
|
|
23684
24434
|
fix,
|
|
23685
24435
|
scanContext
|
|
23686
24436
|
});
|
|
23687
|
-
appliedFiles.push(
|
|
24437
|
+
appliedFiles.push(path25.relative(repositoryPath, actualPath));
|
|
23688
24438
|
logDebug(`[${scanContext}] Created new file: ${relativePath}`);
|
|
23689
24439
|
}
|
|
23690
24440
|
/**
|
|
@@ -23733,7 +24483,7 @@ var PatchApplicationService = class {
|
|
|
23733
24483
|
fix,
|
|
23734
24484
|
scanContext
|
|
23735
24485
|
});
|
|
23736
|
-
appliedFiles.push(
|
|
24486
|
+
appliedFiles.push(path25.relative(repositoryPath, actualPath));
|
|
23737
24487
|
logDebug(`[${scanContext}] Modified file: ${relativePath}`);
|
|
23738
24488
|
}
|
|
23739
24489
|
}
|
|
@@ -23929,8 +24679,8 @@ init_configs();
|
|
|
23929
24679
|
|
|
23930
24680
|
// src/mcp/services/FileOperations.ts
|
|
23931
24681
|
init_FileUtils();
|
|
23932
|
-
import
|
|
23933
|
-
import
|
|
24682
|
+
import fs25 from "fs";
|
|
24683
|
+
import path26 from "path";
|
|
23934
24684
|
import AdmZip3 from "adm-zip";
|
|
23935
24685
|
var FileOperations = class {
|
|
23936
24686
|
/**
|
|
@@ -23950,10 +24700,10 @@ var FileOperations = class {
|
|
|
23950
24700
|
let packedFilesCount = 0;
|
|
23951
24701
|
const packedFiles = [];
|
|
23952
24702
|
const excludedFiles = [];
|
|
23953
|
-
const resolvedRepoPath =
|
|
24703
|
+
const resolvedRepoPath = path26.resolve(repositoryPath);
|
|
23954
24704
|
for (const filepath of fileList) {
|
|
23955
|
-
const absoluteFilepath =
|
|
23956
|
-
const resolvedFilePath =
|
|
24705
|
+
const absoluteFilepath = path26.join(repositoryPath, filepath);
|
|
24706
|
+
const resolvedFilePath = path26.resolve(absoluteFilepath);
|
|
23957
24707
|
if (!resolvedFilePath.startsWith(resolvedRepoPath)) {
|
|
23958
24708
|
const reason = "potential path traversal security risk";
|
|
23959
24709
|
logDebug(`[FileOperations] Skipping ${filepath} due to ${reason}`);
|
|
@@ -24000,11 +24750,11 @@ var FileOperations = class {
|
|
|
24000
24750
|
fileList,
|
|
24001
24751
|
repositoryPath
|
|
24002
24752
|
}) {
|
|
24003
|
-
const resolvedRepoPath =
|
|
24753
|
+
const resolvedRepoPath = path26.resolve(repositoryPath);
|
|
24004
24754
|
const validatedPaths = [];
|
|
24005
24755
|
for (const filepath of fileList) {
|
|
24006
|
-
const absoluteFilepath =
|
|
24007
|
-
const resolvedFilePath =
|
|
24756
|
+
const absoluteFilepath = path26.join(repositoryPath, filepath);
|
|
24757
|
+
const resolvedFilePath = path26.resolve(absoluteFilepath);
|
|
24008
24758
|
if (!resolvedFilePath.startsWith(resolvedRepoPath)) {
|
|
24009
24759
|
logDebug(
|
|
24010
24760
|
`[FileOperations] Rejecting ${filepath} - path traversal attempt detected`
|
|
@@ -24012,7 +24762,7 @@ var FileOperations = class {
|
|
|
24012
24762
|
continue;
|
|
24013
24763
|
}
|
|
24014
24764
|
try {
|
|
24015
|
-
await
|
|
24765
|
+
await fs25.promises.access(absoluteFilepath, fs25.constants.R_OK);
|
|
24016
24766
|
validatedPaths.push(filepath);
|
|
24017
24767
|
} catch (error) {
|
|
24018
24768
|
logDebug(
|
|
@@ -24031,8 +24781,8 @@ var FileOperations = class {
|
|
|
24031
24781
|
const fileDataArray = [];
|
|
24032
24782
|
for (const absolutePath of filePaths) {
|
|
24033
24783
|
try {
|
|
24034
|
-
const content = await
|
|
24035
|
-
const relativePath =
|
|
24784
|
+
const content = await fs25.promises.readFile(absolutePath);
|
|
24785
|
+
const relativePath = path26.basename(absolutePath);
|
|
24036
24786
|
fileDataArray.push({
|
|
24037
24787
|
relativePath,
|
|
24038
24788
|
absolutePath,
|
|
@@ -24057,7 +24807,7 @@ var FileOperations = class {
|
|
|
24057
24807
|
relativeFilepath
|
|
24058
24808
|
}) {
|
|
24059
24809
|
try {
|
|
24060
|
-
return await
|
|
24810
|
+
return await fs25.promises.readFile(absoluteFilepath);
|
|
24061
24811
|
} catch (fsError) {
|
|
24062
24812
|
logError(
|
|
24063
24813
|
`[FileOperations] Failed to read ${relativeFilepath} from filesystem: ${fsError}`
|
|
@@ -24344,14 +25094,14 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
|
|
|
24344
25094
|
* since the last scan.
|
|
24345
25095
|
*/
|
|
24346
25096
|
async scanForSecurityVulnerabilities({
|
|
24347
|
-
path:
|
|
25097
|
+
path: path30,
|
|
24348
25098
|
isAllDetectionRulesScan,
|
|
24349
25099
|
isAllFilesScan,
|
|
24350
25100
|
scanContext
|
|
24351
25101
|
}) {
|
|
24352
25102
|
this.hasAuthenticationFailed = false;
|
|
24353
25103
|
logDebug(`[${scanContext}] Scanning for new security vulnerabilities`, {
|
|
24354
|
-
path:
|
|
25104
|
+
path: path30
|
|
24355
25105
|
});
|
|
24356
25106
|
if (!this.gqlClient) {
|
|
24357
25107
|
logInfo(`[${scanContext}] No GQL client found, skipping scan`);
|
|
@@ -24367,11 +25117,11 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
|
|
|
24367
25117
|
}
|
|
24368
25118
|
logDebug(
|
|
24369
25119
|
`[${scanContext}] Connected to the API, assembling list of files to scan`,
|
|
24370
|
-
{ path:
|
|
25120
|
+
{ path: path30 }
|
|
24371
25121
|
);
|
|
24372
25122
|
const isBackgroundScan = scanContext === ScanContext.BACKGROUND_INITIAL || scanContext === ScanContext.BACKGROUND_PERIODIC;
|
|
24373
25123
|
const files = await getLocalFiles({
|
|
24374
|
-
path:
|
|
25124
|
+
path: path30,
|
|
24375
25125
|
isAllFilesScan,
|
|
24376
25126
|
scanContext,
|
|
24377
25127
|
scanRecentlyChangedFiles: !isBackgroundScan
|
|
@@ -24397,13 +25147,13 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
|
|
|
24397
25147
|
});
|
|
24398
25148
|
const { fixReportId, projectId } = await scanFiles({
|
|
24399
25149
|
fileList: filesToScan.map((file) => file.relativePath),
|
|
24400
|
-
repositoryPath:
|
|
25150
|
+
repositoryPath: path30,
|
|
24401
25151
|
gqlClient: this.gqlClient,
|
|
24402
25152
|
isAllDetectionRulesScan,
|
|
24403
25153
|
scanContext
|
|
24404
25154
|
});
|
|
24405
25155
|
logInfo(
|
|
24406
|
-
`[${scanContext}] Security scan completed for ${
|
|
25156
|
+
`[${scanContext}] Security scan completed for ${path30} reportId: ${fixReportId} projectId: ${projectId}`
|
|
24407
25157
|
);
|
|
24408
25158
|
if (isAllFilesScan) {
|
|
24409
25159
|
return;
|
|
@@ -24697,13 +25447,13 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
|
|
|
24697
25447
|
});
|
|
24698
25448
|
return scannedFiles.some((file) => file.relativePath === fixFile);
|
|
24699
25449
|
}
|
|
24700
|
-
async getFreshFixes({ path:
|
|
25450
|
+
async getFreshFixes({ path: path30 }) {
|
|
24701
25451
|
const scanContext = ScanContext.USER_REQUEST;
|
|
24702
|
-
logDebug(`[${scanContext}] Getting fresh fixes`, { path:
|
|
24703
|
-
if (this.path !==
|
|
24704
|
-
this.path =
|
|
25452
|
+
logDebug(`[${scanContext}] Getting fresh fixes`, { path: path30 });
|
|
25453
|
+
if (this.path !== path30) {
|
|
25454
|
+
this.path = path30;
|
|
24705
25455
|
this.reset();
|
|
24706
|
-
logInfo(`[${scanContext}] Reset service state for new path`, { path:
|
|
25456
|
+
logInfo(`[${scanContext}] Reset service state for new path`, { path: path30 });
|
|
24707
25457
|
}
|
|
24708
25458
|
try {
|
|
24709
25459
|
const loginContext = createMcpLoginContext("check_new_fixes");
|
|
@@ -24722,7 +25472,7 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
|
|
|
24722
25472
|
}
|
|
24723
25473
|
throw error;
|
|
24724
25474
|
}
|
|
24725
|
-
this.triggerScan({ path:
|
|
25475
|
+
this.triggerScan({ path: path30, gqlClient: this.gqlClient });
|
|
24726
25476
|
let isMvsAutoFixEnabled = null;
|
|
24727
25477
|
try {
|
|
24728
25478
|
isMvsAutoFixEnabled = await this.gqlClient.getMvsAutoFixSettings();
|
|
@@ -24756,33 +25506,33 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
|
|
|
24756
25506
|
return noFreshFixesPrompt;
|
|
24757
25507
|
}
|
|
24758
25508
|
triggerScan({
|
|
24759
|
-
path:
|
|
25509
|
+
path: path30,
|
|
24760
25510
|
gqlClient
|
|
24761
25511
|
}) {
|
|
24762
|
-
if (this.path !==
|
|
24763
|
-
this.path =
|
|
25512
|
+
if (this.path !== path30) {
|
|
25513
|
+
this.path = path30;
|
|
24764
25514
|
this.reset();
|
|
24765
|
-
logInfo(`Reset service state for new path in triggerScan`, { path:
|
|
25515
|
+
logInfo(`Reset service state for new path in triggerScan`, { path: path30 });
|
|
24766
25516
|
}
|
|
24767
25517
|
this.gqlClient = gqlClient;
|
|
24768
25518
|
if (!this.intervalId) {
|
|
24769
|
-
this.startPeriodicScanning(
|
|
24770
|
-
this.executeInitialScan(
|
|
24771
|
-
void this.executeInitialFullScan(
|
|
25519
|
+
this.startPeriodicScanning(path30);
|
|
25520
|
+
this.executeInitialScan(path30);
|
|
25521
|
+
void this.executeInitialFullScan(path30);
|
|
24772
25522
|
}
|
|
24773
25523
|
}
|
|
24774
|
-
startPeriodicScanning(
|
|
25524
|
+
startPeriodicScanning(path30) {
|
|
24775
25525
|
const scanContext = ScanContext.BACKGROUND_PERIODIC;
|
|
24776
25526
|
logDebug(
|
|
24777
25527
|
`[${scanContext}] Starting periodic scan for new security vulnerabilities`,
|
|
24778
25528
|
{
|
|
24779
|
-
path:
|
|
25529
|
+
path: path30
|
|
24780
25530
|
}
|
|
24781
25531
|
);
|
|
24782
25532
|
this.intervalId = setInterval(() => {
|
|
24783
|
-
logDebug(`[${scanContext}] Triggering periodic security scan`, { path:
|
|
25533
|
+
logDebug(`[${scanContext}] Triggering periodic security scan`, { path: path30 });
|
|
24784
25534
|
this.scanForSecurityVulnerabilities({
|
|
24785
|
-
path:
|
|
25535
|
+
path: path30,
|
|
24786
25536
|
scanContext
|
|
24787
25537
|
}).catch((error) => {
|
|
24788
25538
|
logError(`[${scanContext}] Error during periodic security scan`, {
|
|
@@ -24791,45 +25541,45 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
|
|
|
24791
25541
|
});
|
|
24792
25542
|
}, MCP_PERIODIC_CHECK_INTERVAL);
|
|
24793
25543
|
}
|
|
24794
|
-
async executeInitialFullScan(
|
|
25544
|
+
async executeInitialFullScan(path30) {
|
|
24795
25545
|
const scanContext = ScanContext.FULL_SCAN;
|
|
24796
|
-
logDebug(`[${scanContext}] Triggering initial full security scan`, { path:
|
|
25546
|
+
logDebug(`[${scanContext}] Triggering initial full security scan`, { path: path30 });
|
|
24797
25547
|
logDebug(`[${scanContext}] Full scan paths scanned`, {
|
|
24798
25548
|
fullScanPathsScanned: this.fullScanPathsScanned
|
|
24799
25549
|
});
|
|
24800
|
-
if (this.fullScanPathsScanned.includes(
|
|
25550
|
+
if (this.fullScanPathsScanned.includes(path30)) {
|
|
24801
25551
|
logDebug(`[${scanContext}] Full scan already executed for this path`, {
|
|
24802
|
-
path:
|
|
25552
|
+
path: path30
|
|
24803
25553
|
});
|
|
24804
25554
|
return;
|
|
24805
25555
|
}
|
|
24806
25556
|
configStore.set("fullScanPathsScanned", [
|
|
24807
25557
|
...this.fullScanPathsScanned,
|
|
24808
|
-
|
|
25558
|
+
path30
|
|
24809
25559
|
]);
|
|
24810
25560
|
try {
|
|
24811
25561
|
await this.scanForSecurityVulnerabilities({
|
|
24812
|
-
path:
|
|
25562
|
+
path: path30,
|
|
24813
25563
|
isAllFilesScan: true,
|
|
24814
25564
|
isAllDetectionRulesScan: true,
|
|
24815
25565
|
scanContext: ScanContext.FULL_SCAN
|
|
24816
25566
|
});
|
|
24817
|
-
if (!this.fullScanPathsScanned.includes(
|
|
24818
|
-
this.fullScanPathsScanned.push(
|
|
25567
|
+
if (!this.fullScanPathsScanned.includes(path30)) {
|
|
25568
|
+
this.fullScanPathsScanned.push(path30);
|
|
24819
25569
|
configStore.set("fullScanPathsScanned", this.fullScanPathsScanned);
|
|
24820
25570
|
}
|
|
24821
|
-
logInfo(`[${scanContext}] Full scan completed`, { path:
|
|
25571
|
+
logInfo(`[${scanContext}] Full scan completed`, { path: path30 });
|
|
24822
25572
|
} catch (error) {
|
|
24823
25573
|
logError(`[${scanContext}] Error during initial full security scan`, {
|
|
24824
25574
|
error
|
|
24825
25575
|
});
|
|
24826
25576
|
}
|
|
24827
25577
|
}
|
|
24828
|
-
executeInitialScan(
|
|
25578
|
+
executeInitialScan(path30) {
|
|
24829
25579
|
const scanContext = ScanContext.BACKGROUND_INITIAL;
|
|
24830
|
-
logDebug(`[${scanContext}] Triggering initial security scan`, { path:
|
|
25580
|
+
logDebug(`[${scanContext}] Triggering initial security scan`, { path: path30 });
|
|
24831
25581
|
this.scanForSecurityVulnerabilities({
|
|
24832
|
-
path:
|
|
25582
|
+
path: path30,
|
|
24833
25583
|
scanContext: ScanContext.BACKGROUND_INITIAL
|
|
24834
25584
|
}).catch((error) => {
|
|
24835
25585
|
logError(`[${scanContext}] Error during initial security scan`, { error });
|
|
@@ -24908,8 +25658,8 @@ Example payload:
|
|
|
24908
25658
|
},
|
|
24909
25659
|
required: ["path"]
|
|
24910
25660
|
});
|
|
24911
|
-
__publicField(this, "inputValidationSchema",
|
|
24912
|
-
path:
|
|
25661
|
+
__publicField(this, "inputValidationSchema", z42.object({
|
|
25662
|
+
path: z42.string().describe(
|
|
24913
25663
|
"Full local path to the cloned git repository to check for new available fixes"
|
|
24914
25664
|
)
|
|
24915
25665
|
}));
|
|
@@ -24926,9 +25676,9 @@ Example payload:
|
|
|
24926
25676
|
`Invalid path: potential security risk detected in path: ${pathValidationResult.error}`
|
|
24927
25677
|
);
|
|
24928
25678
|
}
|
|
24929
|
-
const
|
|
25679
|
+
const path30 = pathValidationResult.path;
|
|
24930
25680
|
const resultText = await this.newFixesService.getFreshFixes({
|
|
24931
|
-
path:
|
|
25681
|
+
path: path30
|
|
24932
25682
|
});
|
|
24933
25683
|
logInfo("CheckForNewAvailableFixesTool execution completed", {
|
|
24934
25684
|
resultText
|
|
@@ -24939,7 +25689,7 @@ Example payload:
|
|
|
24939
25689
|
|
|
24940
25690
|
// src/mcp/tools/fetchAvailableFixes/FetchAvailableFixesTool.ts
|
|
24941
25691
|
init_GitService();
|
|
24942
|
-
import { z as
|
|
25692
|
+
import { z as z43 } from "zod";
|
|
24943
25693
|
|
|
24944
25694
|
// src/mcp/tools/fetchAvailableFixes/FetchAvailableFixesService.ts
|
|
24945
25695
|
init_configs();
|
|
@@ -25082,16 +25832,16 @@ Call this tool instead of ${MCP_TOOL_SCAN_AND_FIX_VULNERABILITIES} when you only
|
|
|
25082
25832
|
},
|
|
25083
25833
|
required: ["path"]
|
|
25084
25834
|
});
|
|
25085
|
-
__publicField(this, "inputValidationSchema",
|
|
25086
|
-
path:
|
|
25835
|
+
__publicField(this, "inputValidationSchema", z43.object({
|
|
25836
|
+
path: z43.string().describe(
|
|
25087
25837
|
"Full local path to the cloned git repository to check for available fixes"
|
|
25088
25838
|
),
|
|
25089
|
-
offset:
|
|
25090
|
-
limit:
|
|
25091
|
-
fileFilter:
|
|
25839
|
+
offset: z43.number().optional().describe("Optional offset for pagination"),
|
|
25840
|
+
limit: z43.number().optional().describe("Optional maximum number of fixes to return"),
|
|
25841
|
+
fileFilter: z43.array(z43.string()).optional().describe(
|
|
25092
25842
|
"Optional list of file paths relative to the path parameter to filter fixes by. INCOMPATIBLE with fetchFixesFromAnyFile"
|
|
25093
25843
|
),
|
|
25094
|
-
fetchFixesFromAnyFile:
|
|
25844
|
+
fetchFixesFromAnyFile: z43.boolean().optional().describe(
|
|
25095
25845
|
"Optional boolean to fetch fixes for all files. INCOMPATIBLE with fileFilter"
|
|
25096
25846
|
)
|
|
25097
25847
|
}));
|
|
@@ -25106,8 +25856,8 @@ Call this tool instead of ${MCP_TOOL_SCAN_AND_FIX_VULNERABILITIES} when you only
|
|
|
25106
25856
|
`Invalid path: potential security risk detected in path: ${pathValidationResult.error}`
|
|
25107
25857
|
);
|
|
25108
25858
|
}
|
|
25109
|
-
const
|
|
25110
|
-
const gitService = new GitService(
|
|
25859
|
+
const path30 = pathValidationResult.path;
|
|
25860
|
+
const gitService = new GitService(path30, log);
|
|
25111
25861
|
const gitValidation = await gitService.validateRepository();
|
|
25112
25862
|
if (!gitValidation.isValid) {
|
|
25113
25863
|
throw new Error(`Invalid git repository: ${gitValidation.error}`);
|
|
@@ -25156,7 +25906,7 @@ Call this tool instead of ${MCP_TOOL_SCAN_AND_FIX_VULNERABILITIES} when you only
|
|
|
25156
25906
|
};
|
|
25157
25907
|
|
|
25158
25908
|
// src/mcp/tools/mcpChecker/mcpCheckerTool.ts
|
|
25159
|
-
import
|
|
25909
|
+
import z44 from "zod";
|
|
25160
25910
|
|
|
25161
25911
|
// src/mcp/tools/mcpChecker/mcpCheckerService.ts
|
|
25162
25912
|
var _McpCheckerService = class _McpCheckerService {
|
|
@@ -25217,7 +25967,7 @@ var McpCheckerTool = class extends BaseTool {
|
|
|
25217
25967
|
__publicField(this, "displayName", "MCP Checker");
|
|
25218
25968
|
// A detailed description to guide the LLM on when and how to invoke this tool.
|
|
25219
25969
|
__publicField(this, "description", "Check the MCP servers running on this IDE against organization policies.");
|
|
25220
|
-
__publicField(this, "inputValidationSchema",
|
|
25970
|
+
__publicField(this, "inputValidationSchema", z44.object({}));
|
|
25221
25971
|
__publicField(this, "inputSchema", {
|
|
25222
25972
|
type: "object",
|
|
25223
25973
|
properties: {},
|
|
@@ -25243,7 +25993,7 @@ var McpCheckerTool = class extends BaseTool {
|
|
|
25243
25993
|
};
|
|
25244
25994
|
|
|
25245
25995
|
// src/mcp/tools/scanAndFixVulnerabilities/ScanAndFixVulnerabilitiesTool.ts
|
|
25246
|
-
import
|
|
25996
|
+
import z45 from "zod";
|
|
25247
25997
|
init_configs();
|
|
25248
25998
|
|
|
25249
25999
|
// src/mcp/tools/scanAndFixVulnerabilities/ScanAndFixVulnerabilitiesService.ts
|
|
@@ -25431,17 +26181,17 @@ Example payload:
|
|
|
25431
26181
|
"rescan": false
|
|
25432
26182
|
}`);
|
|
25433
26183
|
__publicField(this, "hasAuthentication", true);
|
|
25434
|
-
__publicField(this, "inputValidationSchema",
|
|
25435
|
-
path:
|
|
26184
|
+
__publicField(this, "inputValidationSchema", z45.object({
|
|
26185
|
+
path: z45.string().describe(
|
|
25436
26186
|
"Full local path to repository to scan and fix vulnerabilities"
|
|
25437
26187
|
),
|
|
25438
|
-
offset:
|
|
25439
|
-
limit:
|
|
25440
|
-
maxFiles:
|
|
26188
|
+
offset: z45.number().optional().describe("Optional offset for pagination"),
|
|
26189
|
+
limit: z45.number().optional().describe("Optional maximum number of results to return"),
|
|
26190
|
+
maxFiles: z45.number().optional().describe(
|
|
25441
26191
|
`Optional maximum number of files to scan (default: ${MCP_DEFAULT_MAX_FILES_TO_SCAN}). Increase for comprehensive scans of larger codebases or decrease for faster focused scans.`
|
|
25442
26192
|
),
|
|
25443
|
-
rescan:
|
|
25444
|
-
scanRecentlyChangedFiles:
|
|
26193
|
+
rescan: z45.boolean().optional().describe("Optional whether to rescan the repository"),
|
|
26194
|
+
scanRecentlyChangedFiles: z45.boolean().optional().describe(
|
|
25445
26195
|
"Optional whether to automatically scan recently changed files when no changed files are found in git status. If false, the tool will prompt the user instead."
|
|
25446
26196
|
)
|
|
25447
26197
|
}));
|
|
@@ -25492,9 +26242,9 @@ Example payload:
|
|
|
25492
26242
|
`Invalid path: potential security risk detected in path: ${pathValidationResult.error}`
|
|
25493
26243
|
);
|
|
25494
26244
|
}
|
|
25495
|
-
const
|
|
26245
|
+
const path30 = pathValidationResult.path;
|
|
25496
26246
|
const files = await getLocalFiles({
|
|
25497
|
-
path:
|
|
26247
|
+
path: path30,
|
|
25498
26248
|
maxFileSize: MCP_MAX_FILE_SIZE,
|
|
25499
26249
|
maxFiles: args.maxFiles,
|
|
25500
26250
|
scanContext: ScanContext.USER_REQUEST,
|
|
@@ -25514,7 +26264,7 @@ Example payload:
|
|
|
25514
26264
|
try {
|
|
25515
26265
|
const fixResult = await this.vulnerabilityFixService.processVulnerabilities({
|
|
25516
26266
|
fileList: files.map((file) => file.relativePath),
|
|
25517
|
-
repositoryPath:
|
|
26267
|
+
repositoryPath: path30,
|
|
25518
26268
|
offset: args.offset,
|
|
25519
26269
|
limit: args.limit,
|
|
25520
26270
|
isRescan: args.rescan || !!args.maxFiles
|
|
@@ -25618,7 +26368,7 @@ var mcpHandler = async (_args) => {
|
|
|
25618
26368
|
};
|
|
25619
26369
|
|
|
25620
26370
|
// src/args/commands/review.ts
|
|
25621
|
-
import
|
|
26371
|
+
import fs26 from "fs";
|
|
25622
26372
|
import chalk12 from "chalk";
|
|
25623
26373
|
function reviewBuilder(yargs2) {
|
|
25624
26374
|
return yargs2.option("f", {
|
|
@@ -25655,7 +26405,7 @@ function reviewBuilder(yargs2) {
|
|
|
25655
26405
|
).help();
|
|
25656
26406
|
}
|
|
25657
26407
|
function validateReviewOptions(argv) {
|
|
25658
|
-
if (!
|
|
26408
|
+
if (!fs26.existsSync(argv.f)) {
|
|
25659
26409
|
throw new CliError(`
|
|
25660
26410
|
Can't access ${chalk12.bold(argv.f)}`);
|
|
25661
26411
|
}
|
|
@@ -25749,15 +26499,76 @@ async function addScmTokenHandler(args) {
|
|
|
25749
26499
|
}
|
|
25750
26500
|
|
|
25751
26501
|
// src/features/codeium_intellij/data_collector.ts
|
|
25752
|
-
import { z as
|
|
26502
|
+
import { z as z46 } from "zod";
|
|
26503
|
+
|
|
26504
|
+
// src/utils/read-stdin.ts
|
|
26505
|
+
import { setTimeout as setTimeout4 } from "timers";
|
|
26506
|
+
var DEFAULT_TIMEOUT_MS = 1e4;
|
|
26507
|
+
var noop = () => {
|
|
26508
|
+
};
|
|
26509
|
+
var noopLogger = {
|
|
26510
|
+
debug: noop,
|
|
26511
|
+
error: noop
|
|
26512
|
+
};
|
|
26513
|
+
async function readStdinData(config2) {
|
|
26514
|
+
const logger3 = config2?.logger ?? noopLogger;
|
|
26515
|
+
const timeoutMs = config2?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
26516
|
+
logger3.debug("Reading stdin data");
|
|
26517
|
+
return new Promise((resolve, reject) => {
|
|
26518
|
+
let inputData = "";
|
|
26519
|
+
let settled = false;
|
|
26520
|
+
const timer = setTimeout4(() => {
|
|
26521
|
+
if (!settled) {
|
|
26522
|
+
settled = true;
|
|
26523
|
+
process.stdin.destroy();
|
|
26524
|
+
reject(new Error("Timed out reading from stdin"));
|
|
26525
|
+
}
|
|
26526
|
+
}, timeoutMs);
|
|
26527
|
+
process.stdin.setEncoding("utf-8");
|
|
26528
|
+
process.stdin.on("data", (chunk) => {
|
|
26529
|
+
inputData += chunk;
|
|
26530
|
+
});
|
|
26531
|
+
process.stdin.on("end", () => {
|
|
26532
|
+
if (settled) return;
|
|
26533
|
+
settled = true;
|
|
26534
|
+
clearTimeout(timer);
|
|
26535
|
+
try {
|
|
26536
|
+
const parsedData = JSON.parse(inputData);
|
|
26537
|
+
logger3.debug(
|
|
26538
|
+
{
|
|
26539
|
+
data: { keys: Object.keys(parsedData) }
|
|
26540
|
+
},
|
|
26541
|
+
"Parsed stdin data"
|
|
26542
|
+
);
|
|
26543
|
+
resolve(parsedData);
|
|
26544
|
+
} catch (error) {
|
|
26545
|
+
const msg = `Failed to parse JSON from stdin: ${error.message}`;
|
|
26546
|
+
logger3.error(msg);
|
|
26547
|
+
reject(new Error(msg));
|
|
26548
|
+
}
|
|
26549
|
+
});
|
|
26550
|
+
process.stdin.on("error", (error) => {
|
|
26551
|
+
if (settled) return;
|
|
26552
|
+
settled = true;
|
|
26553
|
+
clearTimeout(timer);
|
|
26554
|
+
logger3.error(
|
|
26555
|
+
{ data: { error: error.message } },
|
|
26556
|
+
"Error reading from stdin"
|
|
26557
|
+
);
|
|
26558
|
+
reject(new Error(`Error reading from stdin: ${error.message}`));
|
|
26559
|
+
});
|
|
26560
|
+
});
|
|
26561
|
+
}
|
|
26562
|
+
|
|
26563
|
+
// src/features/codeium_intellij/data_collector.ts
|
|
25753
26564
|
init_client_generates();
|
|
25754
26565
|
init_urlParser2();
|
|
25755
26566
|
|
|
25756
26567
|
// src/features/codeium_intellij/codeium_language_server_grpc_client.ts
|
|
25757
|
-
import
|
|
26568
|
+
import path27 from "path";
|
|
25758
26569
|
import * as grpc from "@grpc/grpc-js";
|
|
25759
26570
|
import * as protoLoader from "@grpc/proto-loader";
|
|
25760
|
-
var PROTO_PATH =
|
|
26571
|
+
var PROTO_PATH = path27.join(
|
|
25761
26572
|
getModuleRootDir(),
|
|
25762
26573
|
"src/features/codeium_intellij/proto/exa/language_server_pb/language_server.proto"
|
|
25763
26574
|
);
|
|
@@ -25769,7 +26580,7 @@ function loadProto() {
|
|
|
25769
26580
|
defaults: true,
|
|
25770
26581
|
oneofs: true,
|
|
25771
26582
|
includeDirs: [
|
|
25772
|
-
|
|
26583
|
+
path27.join(getModuleRootDir(), "src/features/codeium_intellij/proto")
|
|
25773
26584
|
]
|
|
25774
26585
|
});
|
|
25775
26586
|
return grpc.loadPackageDefinition(
|
|
@@ -25823,30 +26634,30 @@ async function getGrpcClient(port, csrf3) {
|
|
|
25823
26634
|
}
|
|
25824
26635
|
|
|
25825
26636
|
// src/features/codeium_intellij/parse_intellij_logs.ts
|
|
25826
|
-
import
|
|
25827
|
-
import
|
|
25828
|
-
import
|
|
26637
|
+
import fs27 from "fs";
|
|
26638
|
+
import os14 from "os";
|
|
26639
|
+
import path28 from "path";
|
|
25829
26640
|
function getLogsDir() {
|
|
25830
26641
|
if (process.platform === "darwin") {
|
|
25831
|
-
return
|
|
26642
|
+
return path28.join(os14.homedir(), "Library/Logs/JetBrains");
|
|
25832
26643
|
} else if (process.platform === "win32") {
|
|
25833
|
-
return
|
|
25834
|
-
process.env["LOCALAPPDATA"] ||
|
|
26644
|
+
return path28.join(
|
|
26645
|
+
process.env["LOCALAPPDATA"] || path28.join(os14.homedir(), "AppData/Local"),
|
|
25835
26646
|
"JetBrains"
|
|
25836
26647
|
);
|
|
25837
26648
|
} else {
|
|
25838
|
-
return
|
|
26649
|
+
return path28.join(os14.homedir(), ".cache/JetBrains");
|
|
25839
26650
|
}
|
|
25840
26651
|
}
|
|
25841
26652
|
function parseIdeLogDir(ideLogDir) {
|
|
25842
|
-
const logFiles =
|
|
26653
|
+
const logFiles = fs27.readdirSync(ideLogDir).filter((f) => /^idea(\.\d+)?\.log$/.test(f)).map((f) => ({
|
|
25843
26654
|
name: f,
|
|
25844
|
-
mtime:
|
|
26655
|
+
mtime: fs27.statSync(path28.join(ideLogDir, f)).mtimeMs
|
|
25845
26656
|
})).sort((a, b) => a.mtime - b.mtime).map((f) => f.name);
|
|
25846
26657
|
let latestCsrf = null;
|
|
25847
26658
|
let latestPort = null;
|
|
25848
26659
|
for (const logFile of logFiles) {
|
|
25849
|
-
const lines =
|
|
26660
|
+
const lines = fs27.readFileSync(path28.join(ideLogDir, logFile), "utf-8").split("\n");
|
|
25850
26661
|
for (const line of lines) {
|
|
25851
26662
|
if (!line.includes(
|
|
25852
26663
|
"com.codeium.intellij.language_server.LanguageServerProcessHandler"
|
|
@@ -25872,13 +26683,13 @@ function parseIdeLogDir(ideLogDir) {
|
|
|
25872
26683
|
function findRunningCodeiumLanguageServers() {
|
|
25873
26684
|
const results = [];
|
|
25874
26685
|
const logsDir = getLogsDir();
|
|
25875
|
-
if (!
|
|
25876
|
-
for (const ide of
|
|
25877
|
-
let ideLogDir =
|
|
26686
|
+
if (!fs27.existsSync(logsDir)) return results;
|
|
26687
|
+
for (const ide of fs27.readdirSync(logsDir)) {
|
|
26688
|
+
let ideLogDir = path28.join(logsDir, ide);
|
|
25878
26689
|
if (process.platform !== "darwin") {
|
|
25879
|
-
ideLogDir =
|
|
26690
|
+
ideLogDir = path28.join(ideLogDir, "log");
|
|
25880
26691
|
}
|
|
25881
|
-
if (!
|
|
26692
|
+
if (!fs27.existsSync(ideLogDir) || !fs27.statSync(ideLogDir).isDirectory()) {
|
|
25882
26693
|
continue;
|
|
25883
26694
|
}
|
|
25884
26695
|
const result = parseIdeLogDir(ideLogDir);
|
|
@@ -25890,8 +26701,8 @@ function findRunningCodeiumLanguageServers() {
|
|
|
25890
26701
|
}
|
|
25891
26702
|
|
|
25892
26703
|
// src/features/codeium_intellij/data_collector.ts
|
|
25893
|
-
var
|
|
25894
|
-
trajectory_id:
|
|
26704
|
+
var HookDataSchema = z46.object({
|
|
26705
|
+
trajectory_id: z46.string()
|
|
25895
26706
|
});
|
|
25896
26707
|
async function processAndUploadHookData() {
|
|
25897
26708
|
const tracePayload = await getTraceDataForHook();
|
|
@@ -25919,12 +26730,12 @@ async function processAndUploadHookData() {
|
|
|
25919
26730
|
console.warn("Failed to upload trace data:", e);
|
|
25920
26731
|
}
|
|
25921
26732
|
}
|
|
25922
|
-
function
|
|
25923
|
-
return
|
|
26733
|
+
function validateHookData(data) {
|
|
26734
|
+
return HookDataSchema.parse(data);
|
|
25924
26735
|
}
|
|
25925
26736
|
async function getTraceDataForHook() {
|
|
25926
26737
|
const rawData = await readStdinData();
|
|
25927
|
-
const hookData =
|
|
26738
|
+
const hookData = validateHookData(rawData);
|
|
25928
26739
|
return await getTraceDataForTrajectory(hookData.trajectory_id);
|
|
25929
26740
|
}
|
|
25930
26741
|
async function getTraceDataForTrajectory(trajectoryId) {
|
|
@@ -26058,11 +26869,11 @@ function processChatStepCodeAction(step) {
|
|
|
26058
26869
|
|
|
26059
26870
|
// src/features/codeium_intellij/install_hook.ts
|
|
26060
26871
|
import fsPromises5 from "fs/promises";
|
|
26061
|
-
import
|
|
26062
|
-
import
|
|
26872
|
+
import os15 from "os";
|
|
26873
|
+
import path29 from "path";
|
|
26063
26874
|
import chalk14 from "chalk";
|
|
26064
26875
|
function getCodeiumHooksPath() {
|
|
26065
|
-
return
|
|
26876
|
+
return path29.join(os15.homedir(), ".codeium", "hooks.json");
|
|
26066
26877
|
}
|
|
26067
26878
|
async function readCodeiumHooks() {
|
|
26068
26879
|
const hooksPath = getCodeiumHooksPath();
|
|
@@ -26075,7 +26886,7 @@ async function readCodeiumHooks() {
|
|
|
26075
26886
|
}
|
|
26076
26887
|
async function writeCodeiumHooks(config2) {
|
|
26077
26888
|
const hooksPath = getCodeiumHooksPath();
|
|
26078
|
-
const dir =
|
|
26889
|
+
const dir = path29.dirname(hooksPath);
|
|
26079
26890
|
await fsPromises5.mkdir(dir, { recursive: true });
|
|
26080
26891
|
await fsPromises5.writeFile(
|
|
26081
26892
|
hooksPath,
|
|
@@ -26246,9 +27057,16 @@ var parseArgs = async (args) => {
|
|
|
26246
27057
|
claudeCodeInstallHookHandler
|
|
26247
27058
|
).command(
|
|
26248
27059
|
mobbCliCommand.claudeCodeProcessHook,
|
|
26249
|
-
chalk15.bold("Process Claude Code hook data
|
|
27060
|
+
chalk15.bold("Process Claude Code hook data (legacy \u2014 spawns daemon)."),
|
|
26250
27061
|
claudeCodeProcessHookBuilder,
|
|
26251
27062
|
claudeCodeProcessHookHandler
|
|
27063
|
+
).command(
|
|
27064
|
+
mobbCliCommand.claudeCodeDaemon,
|
|
27065
|
+
chalk15.bold(
|
|
27066
|
+
"Run the background daemon for Claude Code transcript processing."
|
|
27067
|
+
),
|
|
27068
|
+
claudeCodeDaemonBuilder,
|
|
27069
|
+
claudeCodeDaemonHandler
|
|
26252
27070
|
).command(
|
|
26253
27071
|
mobbCliCommand.windsurfIntellijInstallHook,
|
|
26254
27072
|
chalk15.bold("Install Windsurf IntelliJ hooks for data collection."),
|