mobbdev 1.2.68 → 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 -1008
- 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,28 +16995,108 @@ async function analyzeHandler(args) {
|
|
|
16399
16995
|
await analyze(args, { skipPrompts: args.yes });
|
|
16400
16996
|
}
|
|
16401
16997
|
|
|
16402
|
-
// src/
|
|
16403
|
-
import {
|
|
16404
|
-
|
|
16405
|
-
|
|
16406
|
-
import
|
|
16407
|
-
import {
|
|
16408
|
-
|
|
16409
|
-
|
|
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";
|
|
16410
17009
|
|
|
16411
17010
|
// src/features/claude_code/data_collector_constants.ts
|
|
16412
17011
|
var CC_VERSION_CACHE_KEY = "claudeCode.detectedCCVersion";
|
|
16413
17012
|
var CC_VERSION_CLI_KEY = "claudeCode.detectedCCVersionCli";
|
|
16414
|
-
var GLOBAL_COOLDOWN_MS = 5e3;
|
|
16415
|
-
var HOOK_COOLDOWN_MS = 15e3;
|
|
16416
|
-
var ACTIVE_LOCK_TTL_MS = 6e4;
|
|
16417
17013
|
var GQL_AUTH_TIMEOUT_MS = 15e3;
|
|
16418
17014
|
var STALE_KEY_MAX_AGE_MS = 14 * 24 * 60 * 60 * 1e3;
|
|
16419
17015
|
var CLEANUP_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
16420
|
-
var
|
|
16421
|
-
var
|
|
16422
|
-
var
|
|
16423
|
-
var
|
|
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
|
+
|
|
17093
|
+
// src/features/claude_code/data_collector.ts
|
|
17094
|
+
import { execFile } from "child_process";
|
|
17095
|
+
import { createHash as createHash2 } from "crypto";
|
|
17096
|
+
import { access, open as open4, readdir, readFile, unlink } from "fs/promises";
|
|
17097
|
+
import path14 from "path";
|
|
17098
|
+
import { promisify } from "util";
|
|
17099
|
+
init_client_generates();
|
|
16424
17100
|
|
|
16425
17101
|
// src/utils/shared-logger/create-logger.ts
|
|
16426
17102
|
import Configstore2 from "configstore";
|
|
@@ -16499,8 +17175,8 @@ function createConfigstoreStream(store, opts) {
|
|
|
16499
17175
|
heartbeatBuffer.length = 0;
|
|
16500
17176
|
}
|
|
16501
17177
|
}
|
|
16502
|
-
function setScopePath(
|
|
16503
|
-
scopePath =
|
|
17178
|
+
function setScopePath(path30) {
|
|
17179
|
+
scopePath = path30;
|
|
16504
17180
|
}
|
|
16505
17181
|
return { writable, flush, setScopePath };
|
|
16506
17182
|
}
|
|
@@ -16582,22 +17258,22 @@ function createDdBatch(config2) {
|
|
|
16582
17258
|
|
|
16583
17259
|
// src/utils/shared-logger/hostname.ts
|
|
16584
17260
|
import { createHash } from "crypto";
|
|
16585
|
-
import
|
|
17261
|
+
import os5 from "os";
|
|
16586
17262
|
function hashString(input) {
|
|
16587
17263
|
return createHash("sha256").update(input).digest("hex").slice(0, 16);
|
|
16588
17264
|
}
|
|
16589
17265
|
function getPlainHostname() {
|
|
16590
17266
|
try {
|
|
16591
|
-
return `${
|
|
17267
|
+
return `${os5.userInfo().username}@${os5.hostname()}`;
|
|
16592
17268
|
} catch {
|
|
16593
|
-
return `unknown@${
|
|
17269
|
+
return `unknown@${os5.hostname()}`;
|
|
16594
17270
|
}
|
|
16595
17271
|
}
|
|
16596
17272
|
function getHashedHostname() {
|
|
16597
17273
|
try {
|
|
16598
|
-
return `${hashString(
|
|
17274
|
+
return `${hashString(os5.userInfo().username)}@${hashString(os5.hostname())}`;
|
|
16599
17275
|
} catch {
|
|
16600
|
-
return `unknown@${hashString(
|
|
17276
|
+
return `unknown@${hashString(os5.hostname())}`;
|
|
16601
17277
|
}
|
|
16602
17278
|
}
|
|
16603
17279
|
|
|
@@ -16610,15 +17286,19 @@ function createLogger(config2) {
|
|
|
16610
17286
|
maxLogs,
|
|
16611
17287
|
maxHeartbeat,
|
|
16612
17288
|
dd,
|
|
16613
|
-
additionalStreams = []
|
|
17289
|
+
additionalStreams = [],
|
|
17290
|
+
enableConfigstore = true
|
|
16614
17291
|
} = config2;
|
|
16615
|
-
|
|
16616
|
-
|
|
16617
|
-
|
|
16618
|
-
|
|
16619
|
-
|
|
16620
|
-
|
|
16621
|
-
|
|
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
|
+
}
|
|
16622
17302
|
let ddBatch = null;
|
|
16623
17303
|
if (dd) {
|
|
16624
17304
|
const hostname = dd.hostnameMode === "hashed" ? getHashedHostname() : getPlainHostname();
|
|
@@ -16633,9 +17313,10 @@ function createLogger(config2) {
|
|
|
16633
17313
|
};
|
|
16634
17314
|
ddBatch = createDdBatch(ddConfig);
|
|
16635
17315
|
}
|
|
16636
|
-
const streams = [
|
|
16637
|
-
|
|
16638
|
-
|
|
17316
|
+
const streams = [];
|
|
17317
|
+
if (csStream) {
|
|
17318
|
+
streams.push({ stream: csStream.writable, level: "info" });
|
|
17319
|
+
}
|
|
16639
17320
|
if (ddBatch) {
|
|
16640
17321
|
streams.push({ stream: ddBatch.createPinoStream(), level: "info" });
|
|
16641
17322
|
}
|
|
@@ -16647,6 +17328,11 @@ function createLogger(config2) {
|
|
|
16647
17328
|
}
|
|
16648
17329
|
const pinoLogger = pino(
|
|
16649
17330
|
{
|
|
17331
|
+
serializers: {
|
|
17332
|
+
err: pino.stdSerializers.err,
|
|
17333
|
+
error: pino.stdSerializers.err,
|
|
17334
|
+
e: pino.stdSerializers.err
|
|
17335
|
+
},
|
|
16650
17336
|
formatters: {
|
|
16651
17337
|
level: (label) => ({ level: label })
|
|
16652
17338
|
}
|
|
@@ -16677,8 +17363,8 @@ function createLogger(config2) {
|
|
|
16677
17363
|
throw err;
|
|
16678
17364
|
}
|
|
16679
17365
|
}
|
|
16680
|
-
function
|
|
16681
|
-
csStream
|
|
17366
|
+
function flushLogs() {
|
|
17367
|
+
csStream?.flush();
|
|
16682
17368
|
}
|
|
16683
17369
|
async function flushDdAsync() {
|
|
16684
17370
|
if (ddBatch) {
|
|
@@ -16702,17 +17388,19 @@ function createLogger(config2) {
|
|
|
16702
17388
|
debug: debug23,
|
|
16703
17389
|
heartbeat,
|
|
16704
17390
|
timed,
|
|
16705
|
-
flushLogs
|
|
17391
|
+
flushLogs,
|
|
16706
17392
|
flushDdAsync,
|
|
16707
17393
|
disposeDd,
|
|
16708
|
-
|
|
17394
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
17395
|
+
setScopePath: csStream?.setScopePath ?? (() => {
|
|
17396
|
+
}),
|
|
16709
17397
|
updateDdTags
|
|
16710
17398
|
};
|
|
16711
17399
|
}
|
|
16712
17400
|
|
|
16713
17401
|
// src/features/claude_code/hook_logger.ts
|
|
16714
17402
|
var DD_RUM_TOKEN = true ? "pubf59c0182545bfb4c299175119f1abf9b" : "";
|
|
16715
|
-
var CLI_VERSION = true ? "1.
|
|
17403
|
+
var CLI_VERSION = true ? "1.3.0" : "unknown";
|
|
16716
17404
|
var NAMESPACE = "mobbdev-claude-code-hook-logs";
|
|
16717
17405
|
var claudeCodeVersion;
|
|
16718
17406
|
function buildDdTags() {
|
|
@@ -16722,10 +17410,11 @@ function buildDdTags() {
|
|
|
16722
17410
|
}
|
|
16723
17411
|
return tags.join(",");
|
|
16724
17412
|
}
|
|
16725
|
-
function createHookLogger(
|
|
17413
|
+
function createHookLogger(opts) {
|
|
16726
17414
|
return createLogger({
|
|
16727
17415
|
namespace: NAMESPACE,
|
|
16728
|
-
scopePath,
|
|
17416
|
+
scopePath: opts?.scopePath,
|
|
17417
|
+
enableConfigstore: opts?.enableConfigstore,
|
|
16729
17418
|
dd: {
|
|
16730
17419
|
apiKey: DD_RUM_TOKEN,
|
|
16731
17420
|
ddsource: "mobbdev-cli",
|
|
@@ -16738,6 +17427,7 @@ function createHookLogger(scopePath) {
|
|
|
16738
17427
|
}
|
|
16739
17428
|
var logger = createHookLogger();
|
|
16740
17429
|
var activeScopedLoggers = [];
|
|
17430
|
+
var scopedLoggerCache = /* @__PURE__ */ new Map();
|
|
16741
17431
|
var hookLog = logger;
|
|
16742
17432
|
function setClaudeCodeVersion(version) {
|
|
16743
17433
|
claudeCodeVersion = version;
|
|
@@ -16746,289 +17436,82 @@ function setClaudeCodeVersion(version) {
|
|
|
16746
17436
|
function getClaudeCodeVersion() {
|
|
16747
17437
|
return claudeCodeVersion;
|
|
16748
17438
|
}
|
|
16749
|
-
function flushLogs() {
|
|
16750
|
-
logger.flushLogs();
|
|
16751
|
-
for (const scoped of activeScopedLoggers) {
|
|
16752
|
-
scoped.flushLogs();
|
|
16753
|
-
}
|
|
16754
|
-
}
|
|
16755
17439
|
async function flushDdLogs() {
|
|
16756
17440
|
await logger.flushDdAsync();
|
|
16757
17441
|
for (const scoped of activeScopedLoggers) {
|
|
16758
17442
|
await scoped.flushDdAsync();
|
|
16759
17443
|
}
|
|
16760
|
-
activeScopedLoggers.length = 0;
|
|
16761
17444
|
}
|
|
16762
|
-
function createScopedHookLog(scopePath) {
|
|
16763
|
-
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);
|
|
16764
17453
|
activeScopedLoggers.push(scoped);
|
|
16765
17454
|
return scoped;
|
|
16766
17455
|
}
|
|
16767
17456
|
|
|
16768
|
-
// src/features/claude_code/
|
|
16769
|
-
|
|
16770
|
-
|
|
16771
|
-
|
|
16772
|
-
|
|
16773
|
-
|
|
16774
|
-
|
|
16775
|
-
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
|
+
}
|
|
16776
17464
|
try {
|
|
16777
|
-
await
|
|
16778
|
-
|
|
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;
|
|
16779
17473
|
} catch {
|
|
16780
|
-
|
|
17474
|
+
configStore.set(CC_VERSION_CACHE_KEY, void 0);
|
|
17475
|
+
configStore.set(CC_VERSION_CLI_KEY, packageJson.version);
|
|
17476
|
+
return void 0;
|
|
16781
17477
|
}
|
|
16782
17478
|
}
|
|
16783
|
-
|
|
16784
|
-
const
|
|
16785
|
-
|
|
16786
|
-
|
|
16787
|
-
);
|
|
16788
|
-
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}`;
|
|
16789
17483
|
}
|
|
16790
|
-
|
|
16791
|
-
|
|
16792
|
-
|
|
16793
|
-
JSON.stringify(settings, null, 2),
|
|
16794
|
-
"utf-8"
|
|
16795
|
-
);
|
|
17484
|
+
function getCursorKey(transcriptPath) {
|
|
17485
|
+
const hash = createHash2("sha256").update(transcriptPath).digest("hex").slice(0, 12);
|
|
17486
|
+
return `cursor.${hash}`;
|
|
16796
17487
|
}
|
|
16797
|
-
async function
|
|
17488
|
+
async function resolveTranscriptPath(transcriptPath, sessionId) {
|
|
16798
17489
|
try {
|
|
16799
|
-
|
|
16800
|
-
|
|
16801
|
-
|
|
16802
|
-
|
|
16803
|
-
|
|
16804
|
-
|
|
16805
|
-
|
|
16806
|
-
|
|
17490
|
+
await access(transcriptPath);
|
|
17491
|
+
return transcriptPath;
|
|
17492
|
+
} catch {
|
|
17493
|
+
}
|
|
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"
|
|
16807
17512
|
);
|
|
16808
|
-
|
|
16809
|
-
|
|
16810
|
-
hook.matcher = RECOMMENDED_MATCHER;
|
|
16811
|
-
upgraded = true;
|
|
16812
|
-
}
|
|
16813
|
-
for (const h of hook.hooks) {
|
|
16814
|
-
if (!h.async) {
|
|
16815
|
-
h.async = true;
|
|
16816
|
-
upgraded = true;
|
|
16817
|
-
}
|
|
16818
|
-
}
|
|
16819
|
-
}
|
|
16820
|
-
if (upgraded) {
|
|
16821
|
-
await writeClaudeSettings(settings);
|
|
16822
|
-
}
|
|
16823
|
-
return upgraded;
|
|
16824
|
-
} catch {
|
|
16825
|
-
return false;
|
|
16826
|
-
}
|
|
16827
|
-
}
|
|
16828
|
-
async function installMobbHooks(options = {}) {
|
|
16829
|
-
console.log(chalk11.blue("Installing Mobb hooks in Claude Code settings..."));
|
|
16830
|
-
if (!await claudeSettingsExists()) {
|
|
16831
|
-
console.log(chalk11.red("\u274C Claude Code settings file not found"));
|
|
16832
|
-
console.log(chalk11.yellow(`Expected location: ${CLAUDE_SETTINGS_PATH}`));
|
|
16833
|
-
console.log(chalk11.yellow("Is Claude Code installed on your system?"));
|
|
16834
|
-
console.log(chalk11.yellow("Please install Claude Code and try again."));
|
|
16835
|
-
throw new Error(
|
|
16836
|
-
"Claude Code settings file not found. Is Claude Code installed?"
|
|
16837
|
-
);
|
|
16838
|
-
}
|
|
16839
|
-
const settings = await readClaudeSettings();
|
|
16840
|
-
if (!settings.hooks) {
|
|
16841
|
-
settings.hooks = {};
|
|
16842
|
-
}
|
|
16843
|
-
if (!settings.hooks.PostToolUse) {
|
|
16844
|
-
settings.hooks.PostToolUse = [];
|
|
16845
|
-
}
|
|
16846
|
-
let command = "npx --yes mobbdev@latest claude-code-process-hook";
|
|
16847
|
-
if (options.saveEnv) {
|
|
16848
|
-
const envVars = [];
|
|
16849
|
-
if (process.env["WEB_APP_URL"]) {
|
|
16850
|
-
envVars.push(`WEB_APP_URL="${process.env["WEB_APP_URL"]}"`);
|
|
16851
|
-
}
|
|
16852
|
-
if (process.env["API_URL"]) {
|
|
16853
|
-
envVars.push(`API_URL="${process.env["API_URL"]}"`);
|
|
16854
|
-
}
|
|
16855
|
-
if (envVars.length > 0) {
|
|
16856
|
-
command = `${envVars.join(" ")} ${command}`;
|
|
16857
|
-
console.log(
|
|
16858
|
-
chalk11.blue(
|
|
16859
|
-
`Adding environment variables to hook command: ${envVars.join(", ")}`
|
|
16860
|
-
)
|
|
16861
|
-
);
|
|
16862
|
-
}
|
|
16863
|
-
}
|
|
16864
|
-
const mobbHookConfig = {
|
|
16865
|
-
// Only fire on tools that indicate meaningful work — skip high-frequency
|
|
16866
|
-
// read-only tools (Grep, Glob, WebSearch, WebFetch) to reduce CPU overhead
|
|
16867
|
-
// from process startup (~1.7s user CPU per invocation).
|
|
16868
|
-
matcher: RECOMMENDED_MATCHER,
|
|
16869
|
-
hooks: [
|
|
16870
|
-
{
|
|
16871
|
-
type: "command",
|
|
16872
|
-
command,
|
|
16873
|
-
async: true
|
|
16874
|
-
}
|
|
16875
|
-
]
|
|
16876
|
-
};
|
|
16877
|
-
const existingHookIndex = settings.hooks.PostToolUse.findIndex(
|
|
16878
|
-
(hook) => hook.hooks.some(
|
|
16879
|
-
(h) => h.command?.includes("mobbdev@latest claude-code-process-hook")
|
|
16880
|
-
)
|
|
16881
|
-
);
|
|
16882
|
-
if (existingHookIndex >= 0) {
|
|
16883
|
-
console.log(chalk11.yellow("Mobb hook already exists, updating..."));
|
|
16884
|
-
settings.hooks.PostToolUse[existingHookIndex] = mobbHookConfig;
|
|
16885
|
-
} else {
|
|
16886
|
-
console.log(chalk11.green("Adding new Mobb hook..."));
|
|
16887
|
-
settings.hooks.PostToolUse.push(mobbHookConfig);
|
|
16888
|
-
}
|
|
16889
|
-
await writeClaudeSettings(settings);
|
|
16890
|
-
console.log(
|
|
16891
|
-
chalk11.green(
|
|
16892
|
-
`\u2705 Mobb hooks ${options.saveEnv ? "and environment variables " : ""}installed successfully in ${CLAUDE_SETTINGS_PATH}`
|
|
16893
|
-
)
|
|
16894
|
-
);
|
|
16895
|
-
}
|
|
16896
|
-
|
|
16897
|
-
// src/features/claude_code/data_collector.ts
|
|
16898
|
-
var HookDataSchema = z33.object({
|
|
16899
|
-
session_id: z33.string().nullish(),
|
|
16900
|
-
transcript_path: z33.string().nullish(),
|
|
16901
|
-
cwd: z33.string().nullish(),
|
|
16902
|
-
hook_event_name: z33.string(),
|
|
16903
|
-
tool_name: z33.string(),
|
|
16904
|
-
tool_input: z33.unknown(),
|
|
16905
|
-
tool_response: z33.unknown()
|
|
16906
|
-
});
|
|
16907
|
-
var execFileAsync = promisify(execFile);
|
|
16908
|
-
async function detectClaudeCodeVersion() {
|
|
16909
|
-
const cachedCliVersion = configStore.get(CC_VERSION_CLI_KEY);
|
|
16910
|
-
if (cachedCliVersion === packageJson.version) {
|
|
16911
|
-
return configStore.get(CC_VERSION_CACHE_KEY);
|
|
16912
|
-
}
|
|
16913
|
-
try {
|
|
16914
|
-
const { stdout: stdout2 } = await execFileAsync("claude", ["--version"], {
|
|
16915
|
-
timeout: 3e3,
|
|
16916
|
-
encoding: "utf-8"
|
|
16917
|
-
});
|
|
16918
|
-
const version = stdout2.trim().split(/\s/)[0] || stdout2.trim();
|
|
16919
|
-
configStore.set(CC_VERSION_CACHE_KEY, version);
|
|
16920
|
-
configStore.set(CC_VERSION_CLI_KEY, packageJson.version);
|
|
16921
|
-
return version;
|
|
16922
|
-
} catch {
|
|
16923
|
-
configStore.set(CC_VERSION_CACHE_KEY, void 0);
|
|
16924
|
-
configStore.set(CC_VERSION_CLI_KEY, packageJson.version);
|
|
16925
|
-
return void 0;
|
|
16926
|
-
}
|
|
16927
|
-
}
|
|
16928
|
-
async function readStdinData() {
|
|
16929
|
-
hookLog.debug("Reading stdin data");
|
|
16930
|
-
return new Promise((resolve, reject) => {
|
|
16931
|
-
let inputData = "";
|
|
16932
|
-
let settled = false;
|
|
16933
|
-
const timer = setTimeout(() => {
|
|
16934
|
-
if (!settled) {
|
|
16935
|
-
settled = true;
|
|
16936
|
-
process.stdin.destroy();
|
|
16937
|
-
reject(new Error("Timed out reading from stdin"));
|
|
16938
|
-
}
|
|
16939
|
-
}, STDIN_TIMEOUT_MS);
|
|
16940
|
-
process.stdin.setEncoding("utf-8");
|
|
16941
|
-
process.stdin.on("data", (chunk) => {
|
|
16942
|
-
inputData += chunk;
|
|
16943
|
-
});
|
|
16944
|
-
process.stdin.on("end", () => {
|
|
16945
|
-
if (settled) return;
|
|
16946
|
-
settled = true;
|
|
16947
|
-
clearTimeout(timer);
|
|
16948
|
-
try {
|
|
16949
|
-
const parsedData = JSON.parse(inputData);
|
|
16950
|
-
hookLog.debug(
|
|
16951
|
-
{
|
|
16952
|
-
data: { keys: Object.keys(parsedData) }
|
|
16953
|
-
},
|
|
16954
|
-
"Parsed stdin data"
|
|
16955
|
-
);
|
|
16956
|
-
resolve(parsedData);
|
|
16957
|
-
} catch (error) {
|
|
16958
|
-
const msg = `Failed to parse JSON from stdin: ${error.message}`;
|
|
16959
|
-
hookLog.error(msg);
|
|
16960
|
-
reject(new Error(msg));
|
|
16961
|
-
}
|
|
16962
|
-
});
|
|
16963
|
-
process.stdin.on("error", (error) => {
|
|
16964
|
-
if (settled) return;
|
|
16965
|
-
settled = true;
|
|
16966
|
-
clearTimeout(timer);
|
|
16967
|
-
hookLog.error(
|
|
16968
|
-
{ data: { error: error.message } },
|
|
16969
|
-
"Error reading from stdin"
|
|
16970
|
-
);
|
|
16971
|
-
reject(new Error(`Error reading from stdin: ${error.message}`));
|
|
16972
|
-
});
|
|
16973
|
-
});
|
|
16974
|
-
}
|
|
16975
|
-
function validateHookData(data) {
|
|
16976
|
-
return HookDataSchema.parse(data);
|
|
16977
|
-
}
|
|
16978
|
-
async function extractSessionIdFromTranscript(transcriptPath) {
|
|
16979
|
-
try {
|
|
16980
|
-
const fh = await open4(transcriptPath, "r");
|
|
16981
|
-
try {
|
|
16982
|
-
const buf = Buffer.alloc(4096);
|
|
16983
|
-
const { bytesRead } = await fh.read(buf, 0, 4096, 0);
|
|
16984
|
-
const chunk = buf.toString("utf-8", 0, bytesRead);
|
|
16985
|
-
const firstLine = chunk.split("\n").find((l) => l.trim().length > 0);
|
|
16986
|
-
if (!firstLine) return null;
|
|
16987
|
-
const entry = JSON.parse(firstLine);
|
|
16988
|
-
return entry.sessionId ?? null;
|
|
16989
|
-
} finally {
|
|
16990
|
-
await fh.close();
|
|
16991
|
-
}
|
|
16992
|
-
} catch {
|
|
16993
|
-
return null;
|
|
16994
|
-
}
|
|
16995
|
-
}
|
|
16996
|
-
function generateSyntheticId(sessionId, timestamp, type2, lineIndex) {
|
|
16997
|
-
const input = `${sessionId ?? ""}:${timestamp ?? ""}:${type2 ?? ""}:${lineIndex}`;
|
|
16998
|
-
const hash = createHash2("sha256").update(input).digest("hex").slice(0, 16);
|
|
16999
|
-
return `synth:${hash}`;
|
|
17000
|
-
}
|
|
17001
|
-
function getCursorKey(transcriptPath) {
|
|
17002
|
-
const hash = createHash2("sha256").update(transcriptPath).digest("hex").slice(0, 12);
|
|
17003
|
-
return `cursor.${hash}`;
|
|
17004
|
-
}
|
|
17005
|
-
async function resolveTranscriptPath(transcriptPath, sessionId) {
|
|
17006
|
-
try {
|
|
17007
|
-
await access(transcriptPath);
|
|
17008
|
-
return transcriptPath;
|
|
17009
|
-
} catch {
|
|
17010
|
-
}
|
|
17011
|
-
const filename = path14.basename(transcriptPath);
|
|
17012
|
-
const dirName = path14.basename(path14.dirname(transcriptPath));
|
|
17013
|
-
const projectsDir = path14.dirname(path14.dirname(transcriptPath));
|
|
17014
|
-
const baseDirName = dirName.replace(/[-.]claude-worktrees-.+$/, "");
|
|
17015
|
-
if (baseDirName !== dirName) {
|
|
17016
|
-
const candidate = path14.join(projectsDir, baseDirName, filename);
|
|
17017
|
-
try {
|
|
17018
|
-
await access(candidate);
|
|
17019
|
-
hookLog.info(
|
|
17020
|
-
{
|
|
17021
|
-
data: {
|
|
17022
|
-
original: transcriptPath,
|
|
17023
|
-
resolved: candidate,
|
|
17024
|
-
sessionId,
|
|
17025
|
-
method: "worktree-strip"
|
|
17026
|
-
}
|
|
17027
|
-
},
|
|
17028
|
-
"Transcript path resolved via fallback"
|
|
17029
|
-
);
|
|
17030
|
-
return candidate;
|
|
17031
|
-
} catch {
|
|
17513
|
+
return candidate;
|
|
17514
|
+
} catch {
|
|
17032
17515
|
}
|
|
17033
17516
|
}
|
|
17034
17517
|
try {
|
|
@@ -17057,7 +17540,7 @@ async function resolveTranscriptPath(transcriptPath, sessionId) {
|
|
|
17057
17540
|
}
|
|
17058
17541
|
return transcriptPath;
|
|
17059
17542
|
}
|
|
17060
|
-
async function readNewTranscriptEntries(transcriptPath, sessionId, sessionStore) {
|
|
17543
|
+
async function readNewTranscriptEntries(transcriptPath, sessionId, sessionStore, maxEntries = DAEMON_CHUNK_SIZE) {
|
|
17061
17544
|
transcriptPath = await resolveTranscriptPath(transcriptPath, sessionId);
|
|
17062
17545
|
const cursor = sessionStore.get(getCursorKey(transcriptPath));
|
|
17063
17546
|
let content;
|
|
@@ -17066,9 +17549,9 @@ async function readNewTranscriptEntries(transcriptPath, sessionId, sessionStore)
|
|
|
17066
17549
|
if (cursor?.byteOffset) {
|
|
17067
17550
|
const fh = await open4(transcriptPath, "r");
|
|
17068
17551
|
try {
|
|
17069
|
-
const
|
|
17070
|
-
fileSize =
|
|
17071
|
-
if (cursor.byteOffset >=
|
|
17552
|
+
const stat2 = await fh.stat();
|
|
17553
|
+
fileSize = stat2.size;
|
|
17554
|
+
if (cursor.byteOffset >= stat2.size) {
|
|
17072
17555
|
hookLog.info({ data: { sessionId } }, "No new data in transcript file");
|
|
17073
17556
|
return {
|
|
17074
17557
|
entries: [],
|
|
@@ -17076,7 +17559,7 @@ async function readNewTranscriptEntries(transcriptPath, sessionId, sessionStore)
|
|
|
17076
17559
|
resolvedTranscriptPath: transcriptPath
|
|
17077
17560
|
};
|
|
17078
17561
|
}
|
|
17079
|
-
const buf = Buffer.alloc(
|
|
17562
|
+
const buf = Buffer.alloc(stat2.size - cursor.byteOffset);
|
|
17080
17563
|
await fh.read(buf, 0, buf.length, cursor.byteOffset);
|
|
17081
17564
|
content = buf.toString("utf-8");
|
|
17082
17565
|
} finally {
|
|
@@ -17111,7 +17594,7 @@ async function readNewTranscriptEntries(transcriptPath, sessionId, sessionStore)
|
|
|
17111
17594
|
for (let i = 0; i < allLines.length; i++) {
|
|
17112
17595
|
const line = allLines[i];
|
|
17113
17596
|
const lineBytes = Buffer.byteLength(line, "utf-8") + (i < allLines.length - 1 ? 1 : 0);
|
|
17114
|
-
if (parsed.length >=
|
|
17597
|
+
if (parsed.length >= maxEntries) break;
|
|
17115
17598
|
bytesConsumed += lineBytes;
|
|
17116
17599
|
if (line.trim().length === 0) continue;
|
|
17117
17600
|
try {
|
|
@@ -17129,7 +17612,7 @@ async function readNewTranscriptEntries(transcriptPath, sessionId, sessionStore)
|
|
|
17129
17612
|
parsedLineIndex++;
|
|
17130
17613
|
}
|
|
17131
17614
|
const endByteOffset = startOffset + bytesConsumed;
|
|
17132
|
-
const capped = parsed.length >=
|
|
17615
|
+
const capped = parsed.length >= maxEntries;
|
|
17133
17616
|
if (malformedLines > 0) {
|
|
17134
17617
|
hookLog.warn(
|
|
17135
17618
|
{ data: { malformedLines, transcriptPath } },
|
|
@@ -17142,10 +17625,11 @@ async function readNewTranscriptEntries(transcriptPath, sessionId, sessionStore)
|
|
|
17142
17625
|
data: {
|
|
17143
17626
|
sessionId,
|
|
17144
17627
|
entriesParsed: parsed.length,
|
|
17145
|
-
totalLines: allLines.length
|
|
17628
|
+
totalLines: allLines.length,
|
|
17629
|
+
maxEntries
|
|
17146
17630
|
}
|
|
17147
17631
|
},
|
|
17148
|
-
"Capped at
|
|
17632
|
+
"Capped at maxEntries, remaining entries deferred"
|
|
17149
17633
|
);
|
|
17150
17634
|
} else if (!cursor) {
|
|
17151
17635
|
hookLog.info(
|
|
@@ -17225,14 +17709,13 @@ function filterEntries(entries) {
|
|
|
17225
17709
|
});
|
|
17226
17710
|
return { filtered, filteredOut: entries.length - filtered.length };
|
|
17227
17711
|
}
|
|
17228
|
-
async function cleanupStaleSessions(
|
|
17712
|
+
async function cleanupStaleSessions(configDir) {
|
|
17229
17713
|
const lastCleanup = configStore.get("claudeCode.lastCleanupAt");
|
|
17230
17714
|
if (lastCleanup && Date.now() - lastCleanup < CLEANUP_INTERVAL_MS) {
|
|
17231
17715
|
return;
|
|
17232
17716
|
}
|
|
17233
17717
|
const now = Date.now();
|
|
17234
17718
|
const prefix = getSessionFilePrefix();
|
|
17235
|
-
const configDir = path14.dirname(sessionStore.path);
|
|
17236
17719
|
try {
|
|
17237
17720
|
const files = await readdir(configDir);
|
|
17238
17721
|
let deletedCount = 0;
|
|
@@ -17242,8 +17725,6 @@ async function cleanupStaleSessions(sessionStore) {
|
|
|
17242
17725
|
try {
|
|
17243
17726
|
const content = JSON.parse(await readFile(filePath, "utf-8"));
|
|
17244
17727
|
let newest = 0;
|
|
17245
|
-
const cooldown = content[COOLDOWN_KEY];
|
|
17246
|
-
if (cooldown && cooldown > newest) newest = cooldown;
|
|
17247
17728
|
const cursors = content["cursor"];
|
|
17248
17729
|
if (cursors && typeof cursors === "object") {
|
|
17249
17730
|
for (const val of Object.values(cursors)) {
|
|
@@ -17265,157 +17746,7 @@ async function cleanupStaleSessions(sessionStore) {
|
|
|
17265
17746
|
}
|
|
17266
17747
|
configStore.set("claudeCode.lastCleanupAt", now);
|
|
17267
17748
|
}
|
|
17268
|
-
async function
|
|
17269
|
-
hookLog.info("Hook invoked");
|
|
17270
|
-
const globalLastRun = configStore.get("claudeCode.globalLastHookRunAt");
|
|
17271
|
-
const globalNow = Date.now();
|
|
17272
|
-
if (globalLastRun && globalNow - globalLastRun < GLOBAL_COOLDOWN_MS) {
|
|
17273
|
-
return { entriesUploaded: 0, entriesSkipped: 0, errors: 0 };
|
|
17274
|
-
}
|
|
17275
|
-
configStore.set("claudeCode.globalLastHookRunAt", globalNow);
|
|
17276
|
-
const lastUpgradeVersion = configStore.get(
|
|
17277
|
-
"claudeCode.matcherUpgradeVersion"
|
|
17278
|
-
);
|
|
17279
|
-
if (lastUpgradeVersion !== packageJson.version) {
|
|
17280
|
-
const upgraded = await autoUpgradeMatcherIfStale();
|
|
17281
|
-
configStore.set("claudeCode.matcherUpgradeVersion", packageJson.version);
|
|
17282
|
-
if (upgraded) {
|
|
17283
|
-
hookLog.info("Auto-upgraded hook matcher to reduce CPU usage");
|
|
17284
|
-
}
|
|
17285
|
-
}
|
|
17286
|
-
try {
|
|
17287
|
-
const ccVersion = await detectClaudeCodeVersion();
|
|
17288
|
-
setClaudeCodeVersion(ccVersion);
|
|
17289
|
-
} catch {
|
|
17290
|
-
}
|
|
17291
|
-
const rawData = await readStdinData();
|
|
17292
|
-
const rawObj = rawData;
|
|
17293
|
-
const hookData = (() => {
|
|
17294
|
-
try {
|
|
17295
|
-
return validateHookData(rawData);
|
|
17296
|
-
} catch (err) {
|
|
17297
|
-
hookLog.error(
|
|
17298
|
-
{
|
|
17299
|
-
data: {
|
|
17300
|
-
hook_event_name: rawObj?.["hook_event_name"],
|
|
17301
|
-
tool_name: rawObj?.["tool_name"],
|
|
17302
|
-
session_id: rawObj?.["session_id"],
|
|
17303
|
-
cwd: rawObj?.["cwd"],
|
|
17304
|
-
keys: rawObj ? Object.keys(rawObj) : []
|
|
17305
|
-
}
|
|
17306
|
-
},
|
|
17307
|
-
`Hook validation failed: ${err.message?.slice(0, 200)}`
|
|
17308
|
-
);
|
|
17309
|
-
throw err;
|
|
17310
|
-
}
|
|
17311
|
-
})();
|
|
17312
|
-
if (!hookData.transcript_path) {
|
|
17313
|
-
hookLog.warn(
|
|
17314
|
-
{
|
|
17315
|
-
data: {
|
|
17316
|
-
hook_event_name: hookData.hook_event_name,
|
|
17317
|
-
tool_name: hookData.tool_name,
|
|
17318
|
-
session_id: hookData.session_id,
|
|
17319
|
-
cwd: hookData.cwd
|
|
17320
|
-
}
|
|
17321
|
-
},
|
|
17322
|
-
"Missing transcript_path \u2014 cannot process hook"
|
|
17323
|
-
);
|
|
17324
|
-
return { entriesUploaded: 0, entriesSkipped: 0, errors: 0 };
|
|
17325
|
-
}
|
|
17326
|
-
let sessionId = hookData.session_id;
|
|
17327
|
-
if (!sessionId) {
|
|
17328
|
-
sessionId = await extractSessionIdFromTranscript(hookData.transcript_path);
|
|
17329
|
-
if (sessionId) {
|
|
17330
|
-
hookLog.warn(
|
|
17331
|
-
{
|
|
17332
|
-
data: {
|
|
17333
|
-
hook_event_name: hookData.hook_event_name,
|
|
17334
|
-
tool_name: hookData.tool_name,
|
|
17335
|
-
cwd: hookData.cwd,
|
|
17336
|
-
extractedSessionId: sessionId
|
|
17337
|
-
}
|
|
17338
|
-
},
|
|
17339
|
-
"Missing session_id in hook data \u2014 extracted from transcript"
|
|
17340
|
-
);
|
|
17341
|
-
} else {
|
|
17342
|
-
hookLog.warn(
|
|
17343
|
-
{
|
|
17344
|
-
data: {
|
|
17345
|
-
hook_event_name: hookData.hook_event_name,
|
|
17346
|
-
tool_name: hookData.tool_name,
|
|
17347
|
-
transcript_path: hookData.transcript_path
|
|
17348
|
-
}
|
|
17349
|
-
},
|
|
17350
|
-
"Missing session_id and could not extract from transcript \u2014 cannot process hook"
|
|
17351
|
-
);
|
|
17352
|
-
return { entriesUploaded: 0, entriesSkipped: 0, errors: 0 };
|
|
17353
|
-
}
|
|
17354
|
-
}
|
|
17355
|
-
if (!hookData.cwd) {
|
|
17356
|
-
hookLog.warn(
|
|
17357
|
-
{
|
|
17358
|
-
data: {
|
|
17359
|
-
hook_event_name: hookData.hook_event_name,
|
|
17360
|
-
tool_name: hookData.tool_name,
|
|
17361
|
-
session_id: sessionId
|
|
17362
|
-
}
|
|
17363
|
-
},
|
|
17364
|
-
"Missing cwd in hook data \u2014 scoped logging and repo URL detection disabled"
|
|
17365
|
-
);
|
|
17366
|
-
}
|
|
17367
|
-
const resolvedHookData = {
|
|
17368
|
-
...hookData,
|
|
17369
|
-
session_id: sessionId,
|
|
17370
|
-
transcript_path: hookData.transcript_path,
|
|
17371
|
-
cwd: hookData.cwd ?? void 0
|
|
17372
|
-
};
|
|
17373
|
-
const sessionStore = createSessionConfigStore(resolvedHookData.session_id);
|
|
17374
|
-
await cleanupStaleSessions(sessionStore);
|
|
17375
|
-
const now = Date.now();
|
|
17376
|
-
const lastRunAt = sessionStore.get(COOLDOWN_KEY);
|
|
17377
|
-
if (lastRunAt && now - lastRunAt < HOOK_COOLDOWN_MS) {
|
|
17378
|
-
return { entriesUploaded: 0, entriesSkipped: 0, errors: 0 };
|
|
17379
|
-
}
|
|
17380
|
-
const activeAt = sessionStore.get(ACTIVE_KEY);
|
|
17381
|
-
if (activeAt && now - activeAt < ACTIVE_LOCK_TTL_MS) {
|
|
17382
|
-
const activeDuration = now - activeAt;
|
|
17383
|
-
if (activeDuration > HOOK_COOLDOWN_MS) {
|
|
17384
|
-
hookLog.warn(
|
|
17385
|
-
{
|
|
17386
|
-
data: {
|
|
17387
|
-
activeDurationMs: activeDuration,
|
|
17388
|
-
sessionId: resolvedHookData.session_id
|
|
17389
|
-
}
|
|
17390
|
-
},
|
|
17391
|
-
"Hook still active \u2014 possible slow upload or hung process"
|
|
17392
|
-
);
|
|
17393
|
-
}
|
|
17394
|
-
return { entriesUploaded: 0, entriesSkipped: 0, errors: 0 };
|
|
17395
|
-
}
|
|
17396
|
-
sessionStore.set(ACTIVE_KEY, now);
|
|
17397
|
-
sessionStore.set(COOLDOWN_KEY, now);
|
|
17398
|
-
const log2 = createScopedHookLog(resolvedHookData.cwd ?? process.cwd());
|
|
17399
|
-
log2.info(
|
|
17400
|
-
{
|
|
17401
|
-
data: {
|
|
17402
|
-
sessionId: resolvedHookData.session_id,
|
|
17403
|
-
toolName: resolvedHookData.tool_name,
|
|
17404
|
-
hookEvent: resolvedHookData.hook_event_name,
|
|
17405
|
-
cwd: resolvedHookData.cwd,
|
|
17406
|
-
claudeCodeVersion: getClaudeCodeVersion()
|
|
17407
|
-
}
|
|
17408
|
-
},
|
|
17409
|
-
"Hook data validated"
|
|
17410
|
-
);
|
|
17411
|
-
try {
|
|
17412
|
-
return await processTranscript(resolvedHookData, sessionStore, log2);
|
|
17413
|
-
} finally {
|
|
17414
|
-
sessionStore.delete(ACTIVE_KEY);
|
|
17415
|
-
log2.flushLogs();
|
|
17416
|
-
}
|
|
17417
|
-
}
|
|
17418
|
-
async function processTranscript(hookData, sessionStore, log2) {
|
|
17749
|
+
async function processTranscript(input, sessionStore, log2, maxEntries = DAEMON_CHUNK_SIZE, gqlClientOverride) {
|
|
17419
17750
|
const {
|
|
17420
17751
|
entries: rawEntries,
|
|
17421
17752
|
endByteOffset,
|
|
@@ -17423,9 +17754,10 @@ async function processTranscript(hookData, sessionStore, log2) {
|
|
|
17423
17754
|
} = await log2.timed(
|
|
17424
17755
|
"Read transcript",
|
|
17425
17756
|
() => readNewTranscriptEntries(
|
|
17426
|
-
|
|
17427
|
-
|
|
17428
|
-
sessionStore
|
|
17757
|
+
input.transcript_path,
|
|
17758
|
+
input.session_id,
|
|
17759
|
+
sessionStore,
|
|
17760
|
+
maxEntries
|
|
17429
17761
|
)
|
|
17430
17762
|
);
|
|
17431
17763
|
const cursorKey = getCursorKey(resolvedTranscriptPath);
|
|
@@ -17458,30 +17790,26 @@ async function processTranscript(hookData, sessionStore, log2) {
|
|
|
17458
17790
|
};
|
|
17459
17791
|
}
|
|
17460
17792
|
let gqlClient;
|
|
17461
|
-
|
|
17462
|
-
gqlClient =
|
|
17463
|
-
|
|
17464
|
-
|
|
17465
|
-
|
|
17466
|
-
|
|
17467
|
-
|
|
17468
|
-
|
|
17469
|
-
|
|
17470
|
-
|
|
17471
|
-
|
|
17472
|
-
|
|
17473
|
-
|
|
17474
|
-
|
|
17475
|
-
|
|
17476
|
-
|
|
17477
|
-
|
|
17478
|
-
|
|
17479
|
-
|
|
17480
|
-
|
|
17481
|
-
entriesUploaded: 0,
|
|
17482
|
-
entriesSkipped: filteredOut,
|
|
17483
|
-
errors: entries.length
|
|
17484
|
-
};
|
|
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
|
+
}
|
|
17485
17813
|
}
|
|
17486
17814
|
const cursorForModel = sessionStore.get(cursorKey);
|
|
17487
17815
|
let lastSeenModel = cursorForModel?.lastModel ?? null;
|
|
@@ -17498,68 +17826,508 @@ async function processTranscript(hookData, sessionStore, log2) {
|
|
|
17498
17826
|
rawEntry["message"] = { model: lastSeenModel };
|
|
17499
17827
|
}
|
|
17500
17828
|
}
|
|
17501
|
-
if (!rawEntry["sessionId"]) {
|
|
17502
|
-
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");
|
|
17503
18226
|
}
|
|
17504
|
-
|
|
17505
|
-
|
|
17506
|
-
|
|
17507
|
-
|
|
17508
|
-
|
|
17509
|
-
|
|
17510
|
-
|
|
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
|
|
17511
18264
|
});
|
|
17512
|
-
|
|
17513
|
-
|
|
17514
|
-
|
|
17515
|
-
|
|
17516
|
-
|
|
17517
|
-
|
|
17518
|
-
|
|
17519
|
-
|
|
17520
|
-
|
|
17521
|
-
|
|
17522
|
-
|
|
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;
|
|
17523
18291
|
}
|
|
17524
|
-
}
|
|
17525
|
-
|
|
17526
|
-
|
|
17527
|
-
|
|
17528
|
-
|
|
17529
|
-
|
|
17530
|
-
() => prepareAndSendTracyRecords(gqlClient, records, hookData.cwd, {
|
|
17531
|
-
sanitize
|
|
17532
|
-
})
|
|
17533
|
-
);
|
|
17534
|
-
if (result.ok) {
|
|
17535
|
-
const lastRawEntry = rawEntries[rawEntries.length - 1];
|
|
17536
|
-
const cursor = {
|
|
17537
|
-
id: lastRawEntry._recordId,
|
|
17538
|
-
byteOffset: endByteOffset,
|
|
17539
|
-
updatedAt: Date.now(),
|
|
17540
|
-
lastModel: lastSeenModel ?? void 0
|
|
17541
|
-
};
|
|
17542
|
-
sessionStore.set(cursorKey, cursor);
|
|
17543
|
-
log2.heartbeat("Upload ok", {
|
|
17544
|
-
entriesUploaded: entries.length,
|
|
17545
|
-
entriesSkipped: filteredOut,
|
|
17546
|
-
claudeCodeVersion: getClaudeCodeVersion()
|
|
17547
|
-
});
|
|
17548
|
-
return {
|
|
17549
|
-
entriesUploaded: entries.length,
|
|
17550
|
-
entriesSkipped: filteredOut,
|
|
17551
|
-
errors: 0
|
|
17552
|
-
};
|
|
18292
|
+
}
|
|
18293
|
+
} catch (err) {
|
|
18294
|
+
hookLog.warn(
|
|
18295
|
+
{ err, data: { sessionId: transcript.sessionId } },
|
|
18296
|
+
"Error processing transcript \u2014 skipping"
|
|
18297
|
+
);
|
|
17553
18298
|
}
|
|
17554
|
-
|
|
17555
|
-
|
|
17556
|
-
|
|
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"
|
|
17557
18320
|
);
|
|
17558
|
-
return
|
|
17559
|
-
|
|
17560
|
-
|
|
17561
|
-
|
|
17562
|
-
|
|
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
|
+
}
|
|
17563
18331
|
}
|
|
17564
18332
|
|
|
17565
18333
|
// src/args/commands/claude_code.ts
|
|
@@ -17579,7 +18347,13 @@ var claudeCodeInstallHookBuilder = (yargs2) => {
|
|
|
17579
18347
|
var claudeCodeProcessHookBuilder = (yargs2) => {
|
|
17580
18348
|
return yargs2.example(
|
|
17581
18349
|
"$0 claude-code-process-hook",
|
|
17582
|
-
"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"
|
|
17583
18357
|
).strict();
|
|
17584
18358
|
};
|
|
17585
18359
|
var claudeCodeInstallHookHandler = async (argv) => {
|
|
@@ -17593,69 +18367,43 @@ var claudeCodeInstallHookHandler = async (argv) => {
|
|
|
17593
18367
|
}
|
|
17594
18368
|
};
|
|
17595
18369
|
var claudeCodeProcessHookHandler = async () => {
|
|
17596
|
-
|
|
17597
|
-
|
|
17598
|
-
|
|
17599
|
-
|
|
17600
|
-
|
|
17601
|
-
|
|
17602
|
-
|
|
17603
|
-
|
|
17604
|
-
|
|
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();
|
|
17605
18389
|
}
|
|
18390
|
+
} catch (err) {
|
|
18391
|
+
hookLog.error({ err }, "Error in process-hook shim");
|
|
17606
18392
|
}
|
|
17607
|
-
process.on("uncaughtException", (error) => {
|
|
17608
|
-
hookLog.error(
|
|
17609
|
-
{ data: { error: String(error), stack: error.stack } },
|
|
17610
|
-
"Uncaught exception in hook"
|
|
17611
|
-
);
|
|
17612
|
-
void flushAndExit(1);
|
|
17613
|
-
});
|
|
17614
|
-
process.on("unhandledRejection", (reason) => {
|
|
17615
|
-
hookLog.error(
|
|
17616
|
-
{
|
|
17617
|
-
data: {
|
|
17618
|
-
error: String(reason),
|
|
17619
|
-
stack: reason instanceof Error ? reason.stack : void 0
|
|
17620
|
-
}
|
|
17621
|
-
},
|
|
17622
|
-
"Unhandled rejection in hook"
|
|
17623
|
-
);
|
|
17624
|
-
void flushAndExit(1);
|
|
17625
|
-
});
|
|
17626
|
-
let exitCode = 0;
|
|
17627
|
-
const hookStart = Date.now();
|
|
17628
18393
|
try {
|
|
17629
|
-
|
|
17630
|
-
|
|
17631
|
-
|
|
17632
|
-
|
|
17633
|
-
|
|
17634
|
-
|
|
17635
|
-
|
|
17636
|
-
|
|
17637
|
-
|
|
17638
|
-
|
|
17639
|
-
|
|
17640
|
-
|
|
17641
|
-
}
|
|
17642
|
-
},
|
|
17643
|
-
"Claude Code upload complete"
|
|
17644
|
-
);
|
|
17645
|
-
} catch (error) {
|
|
17646
|
-
exitCode = 1;
|
|
17647
|
-
hookLog.error(
|
|
17648
|
-
{
|
|
17649
|
-
data: {
|
|
17650
|
-
error: String(error),
|
|
17651
|
-
stack: error instanceof Error ? error.stack : void 0,
|
|
17652
|
-
durationMs: Date.now() - hookStart
|
|
17653
|
-
}
|
|
17654
|
-
},
|
|
17655
|
-
"Failed to process Claude Code hook"
|
|
17656
|
-
);
|
|
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);
|
|
17657
18406
|
}
|
|
17658
|
-
await flushAndExit(exitCode);
|
|
17659
18407
|
};
|
|
17660
18408
|
|
|
17661
18409
|
// src/mcp/core/McpServer.ts
|
|
@@ -17677,8 +18425,8 @@ var WorkspaceService = class {
|
|
|
17677
18425
|
* Sets a known workspace path that was discovered through successful validation
|
|
17678
18426
|
* @param path The validated workspace path to store
|
|
17679
18427
|
*/
|
|
17680
|
-
static setKnownWorkspacePath(
|
|
17681
|
-
this.knownWorkspacePath =
|
|
18428
|
+
static setKnownWorkspacePath(path30) {
|
|
18429
|
+
this.knownWorkspacePath = path30;
|
|
17682
18430
|
}
|
|
17683
18431
|
/**
|
|
17684
18432
|
* Gets the known workspace path that was previously validated
|
|
@@ -17825,131 +18573,131 @@ init_configs();
|
|
|
17825
18573
|
|
|
17826
18574
|
// src/mcp/types.ts
|
|
17827
18575
|
init_client_generates();
|
|
17828
|
-
import { z as
|
|
17829
|
-
var ScanAndFixVulnerabilitiesToolSchema =
|
|
17830
|
-
path:
|
|
18576
|
+
import { z as z33 } from "zod";
|
|
18577
|
+
var ScanAndFixVulnerabilitiesToolSchema = z33.object({
|
|
18578
|
+
path: z33.string()
|
|
17831
18579
|
});
|
|
17832
|
-
var VulnerabilityReportIssueTagSchema =
|
|
17833
|
-
vulnerability_report_issue_tag_value:
|
|
18580
|
+
var VulnerabilityReportIssueTagSchema = z33.object({
|
|
18581
|
+
vulnerability_report_issue_tag_value: z33.nativeEnum(
|
|
17834
18582
|
Vulnerability_Report_Issue_Tag_Enum
|
|
17835
18583
|
)
|
|
17836
18584
|
});
|
|
17837
|
-
var VulnerabilityReportIssueSchema =
|
|
17838
|
-
category:
|
|
17839
|
-
parsedIssueType:
|
|
17840
|
-
parsedSeverity:
|
|
17841
|
-
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)
|
|
17842
18590
|
});
|
|
17843
|
-
var SharedStateSchema =
|
|
17844
|
-
__typename:
|
|
17845
|
-
id:
|
|
18591
|
+
var SharedStateSchema = z33.object({
|
|
18592
|
+
__typename: z33.literal("fix_shared_state").optional(),
|
|
18593
|
+
id: z33.any(),
|
|
17846
18594
|
// GraphQL uses `any` type for UUID
|
|
17847
|
-
downloadedBy:
|
|
18595
|
+
downloadedBy: z33.array(z33.any().nullable()).optional().nullable()
|
|
17848
18596
|
});
|
|
17849
|
-
var UnstructuredFixExtraContextSchema =
|
|
17850
|
-
__typename:
|
|
17851
|
-
key:
|
|
17852
|
-
value:
|
|
18597
|
+
var UnstructuredFixExtraContextSchema = z33.object({
|
|
18598
|
+
__typename: z33.literal("UnstructuredFixExtraContext").optional(),
|
|
18599
|
+
key: z33.string(),
|
|
18600
|
+
value: z33.any()
|
|
17853
18601
|
// GraphQL JSON type
|
|
17854
18602
|
});
|
|
17855
|
-
var FixExtraContextResponseSchema =
|
|
17856
|
-
__typename:
|
|
17857
|
-
extraContext:
|
|
17858
|
-
fixDescription:
|
|
18603
|
+
var FixExtraContextResponseSchema = z33.object({
|
|
18604
|
+
__typename: z33.literal("FixExtraContextResponse").optional(),
|
|
18605
|
+
extraContext: z33.array(UnstructuredFixExtraContextSchema),
|
|
18606
|
+
fixDescription: z33.string()
|
|
17859
18607
|
});
|
|
17860
|
-
var FixDataSchema =
|
|
17861
|
-
__typename:
|
|
17862
|
-
patch:
|
|
17863
|
-
patchOriginalEncodingBase64:
|
|
18608
|
+
var FixDataSchema = z33.object({
|
|
18609
|
+
__typename: z33.literal("FixData"),
|
|
18610
|
+
patch: z33.string(),
|
|
18611
|
+
patchOriginalEncodingBase64: z33.string(),
|
|
17864
18612
|
extraContext: FixExtraContextResponseSchema
|
|
17865
18613
|
});
|
|
17866
|
-
var GetFixNoFixErrorSchema =
|
|
17867
|
-
__typename:
|
|
18614
|
+
var GetFixNoFixErrorSchema = z33.object({
|
|
18615
|
+
__typename: z33.literal("GetFixNoFixError")
|
|
17868
18616
|
});
|
|
17869
|
-
var PatchAndQuestionsSchema =
|
|
17870
|
-
var McpFixSchema =
|
|
17871
|
-
__typename:
|
|
17872
|
-
id:
|
|
18617
|
+
var PatchAndQuestionsSchema = z33.union([FixDataSchema, GetFixNoFixErrorSchema]);
|
|
18618
|
+
var McpFixSchema = z33.object({
|
|
18619
|
+
__typename: z33.literal("fix").optional(),
|
|
18620
|
+
id: z33.any(),
|
|
17873
18621
|
// GraphQL uses `any` type for UUID
|
|
17874
|
-
confidence:
|
|
17875
|
-
safeIssueType:
|
|
17876
|
-
severityText:
|
|
17877
|
-
gitBlameLogin:
|
|
18622
|
+
confidence: z33.number(),
|
|
18623
|
+
safeIssueType: z33.string().nullable(),
|
|
18624
|
+
severityText: z33.string().nullable(),
|
|
18625
|
+
gitBlameLogin: z33.string().nullable().optional(),
|
|
17878
18626
|
// Optional in GraphQL
|
|
17879
|
-
severityValue:
|
|
17880
|
-
vulnerabilityReportIssues:
|
|
18627
|
+
severityValue: z33.number().nullable(),
|
|
18628
|
+
vulnerabilityReportIssues: z33.array(VulnerabilityReportIssueSchema),
|
|
17881
18629
|
sharedState: SharedStateSchema.nullable().optional(),
|
|
17882
18630
|
// Optional in GraphQL
|
|
17883
18631
|
patchAndQuestions: PatchAndQuestionsSchema,
|
|
17884
18632
|
// Additional field added by the client
|
|
17885
|
-
fixUrl:
|
|
18633
|
+
fixUrl: z33.string().optional()
|
|
17886
18634
|
});
|
|
17887
|
-
var FixAggregateSchema =
|
|
17888
|
-
__typename:
|
|
17889
|
-
aggregate:
|
|
17890
|
-
__typename:
|
|
17891
|
-
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()
|
|
17892
18640
|
}).nullable()
|
|
17893
18641
|
});
|
|
17894
|
-
var VulnerabilityReportIssueAggregateSchema =
|
|
17895
|
-
__typename:
|
|
17896
|
-
aggregate:
|
|
17897
|
-
__typename:
|
|
17898
|
-
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()
|
|
17899
18647
|
}).nullable()
|
|
17900
18648
|
});
|
|
17901
|
-
var RepoSchema =
|
|
17902
|
-
__typename:
|
|
17903
|
-
originalUrl:
|
|
18649
|
+
var RepoSchema = z33.object({
|
|
18650
|
+
__typename: z33.literal("repo").optional(),
|
|
18651
|
+
originalUrl: z33.string()
|
|
17904
18652
|
});
|
|
17905
|
-
var ProjectSchema =
|
|
17906
|
-
id:
|
|
18653
|
+
var ProjectSchema = z33.object({
|
|
18654
|
+
id: z33.any(),
|
|
17907
18655
|
// GraphQL uses `any` type for UUID
|
|
17908
|
-
organizationId:
|
|
18656
|
+
organizationId: z33.any()
|
|
17909
18657
|
// GraphQL uses `any` type for UUID
|
|
17910
18658
|
});
|
|
17911
|
-
var VulnerabilityReportSchema =
|
|
17912
|
-
scanDate:
|
|
18659
|
+
var VulnerabilityReportSchema = z33.object({
|
|
18660
|
+
scanDate: z33.any().nullable(),
|
|
17913
18661
|
// GraphQL uses `any` type for timestamp
|
|
17914
|
-
vendor:
|
|
18662
|
+
vendor: z33.string(),
|
|
17915
18663
|
// GraphQL generates as string, not enum
|
|
17916
|
-
projectId:
|
|
18664
|
+
projectId: z33.any().optional(),
|
|
17917
18665
|
// GraphQL uses `any` type for UUID
|
|
17918
18666
|
project: ProjectSchema,
|
|
17919
18667
|
totalVulnerabilityReportIssuesCount: VulnerabilityReportIssueAggregateSchema,
|
|
17920
18668
|
notFixableVulnerabilityReportIssuesCount: VulnerabilityReportIssueAggregateSchema
|
|
17921
18669
|
});
|
|
17922
|
-
var FixReportSummarySchema =
|
|
17923
|
-
__typename:
|
|
17924
|
-
id:
|
|
18670
|
+
var FixReportSummarySchema = z33.object({
|
|
18671
|
+
__typename: z33.literal("fixReport").optional(),
|
|
18672
|
+
id: z33.any(),
|
|
17925
18673
|
// GraphQL uses `any` type for UUID
|
|
17926
|
-
createdOn:
|
|
18674
|
+
createdOn: z33.any(),
|
|
17927
18675
|
// GraphQL uses `any` type for timestamp
|
|
17928
18676
|
repo: RepoSchema.nullable(),
|
|
17929
|
-
issueTypes:
|
|
18677
|
+
issueTypes: z33.any().nullable(),
|
|
17930
18678
|
// GraphQL uses `any` type for JSON
|
|
17931
18679
|
CRITICAL: FixAggregateSchema,
|
|
17932
18680
|
HIGH: FixAggregateSchema,
|
|
17933
18681
|
MEDIUM: FixAggregateSchema,
|
|
17934
18682
|
LOW: FixAggregateSchema,
|
|
17935
|
-
fixes:
|
|
17936
|
-
userFixes:
|
|
18683
|
+
fixes: z33.array(McpFixSchema),
|
|
18684
|
+
userFixes: z33.array(McpFixSchema).optional(),
|
|
17937
18685
|
// Present in some responses but can be omitted
|
|
17938
18686
|
filteredFixesCount: FixAggregateSchema,
|
|
17939
18687
|
totalFixesCount: FixAggregateSchema,
|
|
17940
18688
|
vulnerabilityReport: VulnerabilityReportSchema
|
|
17941
18689
|
});
|
|
17942
|
-
var ExpiredReportSchema =
|
|
17943
|
-
__typename:
|
|
17944
|
-
id:
|
|
18690
|
+
var ExpiredReportSchema = z33.object({
|
|
18691
|
+
__typename: z33.literal("fixReport").optional(),
|
|
18692
|
+
id: z33.any(),
|
|
17945
18693
|
// GraphQL uses `any` type for UUID
|
|
17946
|
-
expirationOn:
|
|
18694
|
+
expirationOn: z33.any().nullable()
|
|
17947
18695
|
// GraphQL uses `any` type for timestamp
|
|
17948
18696
|
});
|
|
17949
|
-
var GetLatestReportByRepoUrlResponseSchema =
|
|
17950
|
-
__typename:
|
|
17951
|
-
fixReport:
|
|
17952
|
-
expiredReport:
|
|
18697
|
+
var GetLatestReportByRepoUrlResponseSchema = z33.object({
|
|
18698
|
+
__typename: z33.literal("query_root").optional(),
|
|
18699
|
+
fixReport: z33.array(FixReportSummarySchema),
|
|
18700
|
+
expiredReport: z33.array(ExpiredReportSchema)
|
|
17953
18701
|
});
|
|
17954
18702
|
|
|
17955
18703
|
// src/mcp/services/McpGQLClient.ts
|
|
@@ -18537,9 +19285,9 @@ async function createAuthenticatedMcpGQLClient({
|
|
|
18537
19285
|
|
|
18538
19286
|
// src/mcp/services/McpUsageService/host.ts
|
|
18539
19287
|
import { execSync as execSync2 } from "child_process";
|
|
18540
|
-
import
|
|
18541
|
-
import
|
|
18542
|
-
import
|
|
19288
|
+
import fs15 from "fs";
|
|
19289
|
+
import os8 from "os";
|
|
19290
|
+
import path18 from "path";
|
|
18543
19291
|
var IDEs = ["cursor", "windsurf", "webstorm", "vscode", "claude"];
|
|
18544
19292
|
var runCommand = (cmd) => {
|
|
18545
19293
|
try {
|
|
@@ -18553,18 +19301,18 @@ var gitInfo = {
|
|
|
18553
19301
|
email: runCommand("git config user.email")
|
|
18554
19302
|
};
|
|
18555
19303
|
var getClaudeWorkspacePaths = () => {
|
|
18556
|
-
const home =
|
|
18557
|
-
const claudeIdePath =
|
|
19304
|
+
const home = os8.homedir();
|
|
19305
|
+
const claudeIdePath = path18.join(home, ".claude", "ide");
|
|
18558
19306
|
const workspacePaths = [];
|
|
18559
|
-
if (!
|
|
19307
|
+
if (!fs15.existsSync(claudeIdePath)) {
|
|
18560
19308
|
return workspacePaths;
|
|
18561
19309
|
}
|
|
18562
19310
|
try {
|
|
18563
|
-
const lockFiles =
|
|
19311
|
+
const lockFiles = fs15.readdirSync(claudeIdePath).filter((file) => file.endsWith(".lock"));
|
|
18564
19312
|
for (const lockFile of lockFiles) {
|
|
18565
|
-
const lockFilePath =
|
|
19313
|
+
const lockFilePath = path18.join(claudeIdePath, lockFile);
|
|
18566
19314
|
try {
|
|
18567
|
-
const lockContent = JSON.parse(
|
|
19315
|
+
const lockContent = JSON.parse(fs15.readFileSync(lockFilePath, "utf8"));
|
|
18568
19316
|
if (lockContent.workspaceFolders && Array.isArray(lockContent.workspaceFolders)) {
|
|
18569
19317
|
workspacePaths.push(...lockContent.workspaceFolders);
|
|
18570
19318
|
}
|
|
@@ -18582,29 +19330,29 @@ var getClaudeWorkspacePaths = () => {
|
|
|
18582
19330
|
return workspacePaths;
|
|
18583
19331
|
};
|
|
18584
19332
|
var getMCPConfigPaths = (hostName) => {
|
|
18585
|
-
const home =
|
|
19333
|
+
const home = os8.homedir();
|
|
18586
19334
|
const currentDir = process.env["WORKSPACE_FOLDER_PATHS"] || process.env["PWD"] || process.cwd();
|
|
18587
19335
|
switch (hostName.toLowerCase()) {
|
|
18588
19336
|
case "cursor":
|
|
18589
19337
|
return [
|
|
18590
|
-
|
|
19338
|
+
path18.join(currentDir, ".cursor", "mcp.json"),
|
|
18591
19339
|
// local first
|
|
18592
|
-
|
|
19340
|
+
path18.join(home, ".cursor", "mcp.json")
|
|
18593
19341
|
];
|
|
18594
19342
|
case "windsurf":
|
|
18595
19343
|
return [
|
|
18596
|
-
|
|
19344
|
+
path18.join(currentDir, ".codeium", "mcp_config.json"),
|
|
18597
19345
|
// local first
|
|
18598
|
-
|
|
19346
|
+
path18.join(home, ".codeium", "windsurf", "mcp_config.json")
|
|
18599
19347
|
];
|
|
18600
19348
|
case "webstorm":
|
|
18601
19349
|
return [];
|
|
18602
19350
|
case "visualstudiocode":
|
|
18603
19351
|
case "vscode":
|
|
18604
19352
|
return [
|
|
18605
|
-
|
|
19353
|
+
path18.join(currentDir, ".vscode", "mcp.json"),
|
|
18606
19354
|
// local first
|
|
18607
|
-
process.platform === "win32" ?
|
|
19355
|
+
process.platform === "win32" ? path18.join(home, "AppData", "Roaming", "Code", "User", "mcp.json") : path18.join(
|
|
18608
19356
|
home,
|
|
18609
19357
|
"Library",
|
|
18610
19358
|
"Application Support",
|
|
@@ -18615,13 +19363,13 @@ var getMCPConfigPaths = (hostName) => {
|
|
|
18615
19363
|
];
|
|
18616
19364
|
case "claude": {
|
|
18617
19365
|
const claudePaths = [
|
|
18618
|
-
|
|
19366
|
+
path18.join(currentDir, ".claude.json"),
|
|
18619
19367
|
// local first
|
|
18620
|
-
|
|
19368
|
+
path18.join(home, ".claude.json")
|
|
18621
19369
|
];
|
|
18622
19370
|
const workspacePaths = getClaudeWorkspacePaths();
|
|
18623
19371
|
for (const workspacePath of workspacePaths) {
|
|
18624
|
-
claudePaths.push(
|
|
19372
|
+
claudePaths.push(path18.join(workspacePath, ".mcp.json"));
|
|
18625
19373
|
}
|
|
18626
19374
|
return claudePaths;
|
|
18627
19375
|
}
|
|
@@ -18630,9 +19378,9 @@ var getMCPConfigPaths = (hostName) => {
|
|
|
18630
19378
|
}
|
|
18631
19379
|
};
|
|
18632
19380
|
var readConfigFile = (filePath) => {
|
|
18633
|
-
if (!
|
|
19381
|
+
if (!fs15.existsSync(filePath)) return null;
|
|
18634
19382
|
try {
|
|
18635
|
-
return JSON.parse(
|
|
19383
|
+
return JSON.parse(fs15.readFileSync(filePath, "utf8"));
|
|
18636
19384
|
} catch (error) {
|
|
18637
19385
|
logWarn(`[UsageService] Failed to read MCP config: ${filePath}`);
|
|
18638
19386
|
return null;
|
|
@@ -18672,7 +19420,7 @@ var readMCPConfig = (hostName) => {
|
|
|
18672
19420
|
};
|
|
18673
19421
|
var getRunningProcesses = () => {
|
|
18674
19422
|
try {
|
|
18675
|
-
return
|
|
19423
|
+
return os8.platform() === "win32" ? execSync2("tasklist", { encoding: "utf8" }) : execSync2("ps aux", { encoding: "utf8" });
|
|
18676
19424
|
} catch {
|
|
18677
19425
|
return "";
|
|
18678
19426
|
}
|
|
@@ -18747,7 +19495,7 @@ var versionCommands = {
|
|
|
18747
19495
|
}
|
|
18748
19496
|
};
|
|
18749
19497
|
var getProcessInfo = (pid) => {
|
|
18750
|
-
const platform2 =
|
|
19498
|
+
const platform2 = os8.platform();
|
|
18751
19499
|
try {
|
|
18752
19500
|
if (platform2 === "linux" || platform2 === "darwin") {
|
|
18753
19501
|
const output = execSync2(`ps -o pid=,ppid=,comm= -p ${pid}`, {
|
|
@@ -18782,10 +19530,10 @@ var getHostInfo = (additionalMcpList) => {
|
|
|
18782
19530
|
const ideConfigPaths = /* @__PURE__ */ new Set();
|
|
18783
19531
|
for (const ide of IDEs) {
|
|
18784
19532
|
const configPaths = getMCPConfigPaths(ide);
|
|
18785
|
-
configPaths.forEach((
|
|
19533
|
+
configPaths.forEach((path30) => ideConfigPaths.add(path30));
|
|
18786
19534
|
}
|
|
18787
19535
|
const uniqueAdditionalPaths = additionalMcpList.filter(
|
|
18788
|
-
(
|
|
19536
|
+
(path30) => !ideConfigPaths.has(path30)
|
|
18789
19537
|
);
|
|
18790
19538
|
for (const ide of IDEs) {
|
|
18791
19539
|
const cfg = readMCPConfig(ide);
|
|
@@ -18866,7 +19614,7 @@ var getHostInfo = (additionalMcpList) => {
|
|
|
18866
19614
|
const config2 = allConfigs[ide] || null;
|
|
18867
19615
|
const ideName = ide.charAt(0).toUpperCase() + ide.slice(1) || "Unknown";
|
|
18868
19616
|
let ideVersion = "Unknown";
|
|
18869
|
-
const platform2 =
|
|
19617
|
+
const platform2 = os8.platform();
|
|
18870
19618
|
const cmds = versionCommands[ideName]?.[platform2] ?? [];
|
|
18871
19619
|
for (const cmd of cmds) {
|
|
18872
19620
|
try {
|
|
@@ -18899,15 +19647,15 @@ var getHostInfo = (additionalMcpList) => {
|
|
|
18899
19647
|
|
|
18900
19648
|
// src/mcp/services/McpUsageService/McpUsageService.ts
|
|
18901
19649
|
import fetch6 from "node-fetch";
|
|
18902
|
-
import
|
|
19650
|
+
import os10 from "os";
|
|
18903
19651
|
import { v4 as uuidv42, v5 as uuidv5 } from "uuid";
|
|
18904
19652
|
init_configs();
|
|
18905
19653
|
|
|
18906
19654
|
// src/mcp/services/McpUsageService/system.ts
|
|
18907
19655
|
init_configs();
|
|
18908
|
-
import
|
|
18909
|
-
import
|
|
18910
|
-
import
|
|
19656
|
+
import fs16 from "fs";
|
|
19657
|
+
import os9 from "os";
|
|
19658
|
+
import path19 from "path";
|
|
18911
19659
|
var MAX_DEPTH = 2;
|
|
18912
19660
|
var patterns = ["mcp", "claude"];
|
|
18913
19661
|
var isFileMatch = (fileName) => {
|
|
@@ -18916,7 +19664,7 @@ var isFileMatch = (fileName) => {
|
|
|
18916
19664
|
};
|
|
18917
19665
|
var safeAccess = async (filePath) => {
|
|
18918
19666
|
try {
|
|
18919
|
-
await
|
|
19667
|
+
await fs16.promises.access(filePath, fs16.constants.R_OK);
|
|
18920
19668
|
return true;
|
|
18921
19669
|
} catch {
|
|
18922
19670
|
return false;
|
|
@@ -18925,9 +19673,9 @@ var safeAccess = async (filePath) => {
|
|
|
18925
19673
|
var searchDir = async (dir, depth = 0) => {
|
|
18926
19674
|
const results = [];
|
|
18927
19675
|
if (depth > MAX_DEPTH) return results;
|
|
18928
|
-
const entries = await
|
|
19676
|
+
const entries = await fs16.promises.readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
18929
19677
|
for (const entry of entries) {
|
|
18930
|
-
const fullPath =
|
|
19678
|
+
const fullPath = path19.join(dir, entry.name);
|
|
18931
19679
|
if (entry.isFile() && isFileMatch(entry.name)) {
|
|
18932
19680
|
results.push(fullPath);
|
|
18933
19681
|
} else if (entry.isDirectory()) {
|
|
@@ -18941,17 +19689,17 @@ var searchDir = async (dir, depth = 0) => {
|
|
|
18941
19689
|
};
|
|
18942
19690
|
var findSystemMCPConfigs = async () => {
|
|
18943
19691
|
try {
|
|
18944
|
-
const home =
|
|
18945
|
-
const platform2 =
|
|
19692
|
+
const home = os9.homedir();
|
|
19693
|
+
const platform2 = os9.platform();
|
|
18946
19694
|
const knownDirs = platform2 === "win32" ? [
|
|
18947
|
-
|
|
18948
|
-
|
|
18949
|
-
|
|
19695
|
+
path19.join(home, ".cursor"),
|
|
19696
|
+
path19.join(home, "Documents"),
|
|
19697
|
+
path19.join(home, "Downloads")
|
|
18950
19698
|
] : [
|
|
18951
|
-
|
|
18952
|
-
process.env["XDG_CONFIG_HOME"] ||
|
|
18953
|
-
|
|
18954
|
-
|
|
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")
|
|
18955
19703
|
];
|
|
18956
19704
|
const timeoutPromise = new Promise(
|
|
18957
19705
|
(resolve) => setTimeout(() => {
|
|
@@ -18963,7 +19711,7 @@ var findSystemMCPConfigs = async () => {
|
|
|
18963
19711
|
);
|
|
18964
19712
|
const searchPromise = Promise.all(
|
|
18965
19713
|
knownDirs.map(
|
|
18966
|
-
(dir) =>
|
|
19714
|
+
(dir) => fs16.existsSync(dir) ? searchDir(dir) : Promise.resolve([])
|
|
18967
19715
|
)
|
|
18968
19716
|
).then((results) => results.flat());
|
|
18969
19717
|
return await Promise.race([timeoutPromise, searchPromise]);
|
|
@@ -19014,7 +19762,7 @@ var McpUsageService = class {
|
|
|
19014
19762
|
generateHostId() {
|
|
19015
19763
|
const stored = configStore.get(this.configKey);
|
|
19016
19764
|
if (stored?.mcpHostId) return stored.mcpHostId;
|
|
19017
|
-
const interfaces =
|
|
19765
|
+
const interfaces = os10.networkInterfaces();
|
|
19018
19766
|
const macs = [];
|
|
19019
19767
|
for (const iface of Object.values(interfaces)) {
|
|
19020
19768
|
if (!iface) continue;
|
|
@@ -19022,7 +19770,7 @@ var McpUsageService = class {
|
|
|
19022
19770
|
if (net.mac && net.mac !== "00:00:00:00:00:00") macs.push(net.mac);
|
|
19023
19771
|
}
|
|
19024
19772
|
}
|
|
19025
|
-
const macString = macs.length ? macs.sort().join(",") : `${
|
|
19773
|
+
const macString = macs.length ? macs.sort().join(",") : `${os10.hostname()}-${uuidv42()}`;
|
|
19026
19774
|
const hostId = uuidv5(macString, uuidv5.DNS);
|
|
19027
19775
|
logDebug("[UsageService] Generated new host ID", { hostId });
|
|
19028
19776
|
return hostId;
|
|
@@ -19045,7 +19793,7 @@ var McpUsageService = class {
|
|
|
19045
19793
|
mcpHostId,
|
|
19046
19794
|
organizationId,
|
|
19047
19795
|
mcpVersion: packageJson.version,
|
|
19048
|
-
mcpOsName:
|
|
19796
|
+
mcpOsName: os10.platform(),
|
|
19049
19797
|
mcps: JSON.stringify(mcps),
|
|
19050
19798
|
status,
|
|
19051
19799
|
userName: user.name,
|
|
@@ -19706,10 +20454,10 @@ var McpServer = class {
|
|
|
19706
20454
|
};
|
|
19707
20455
|
|
|
19708
20456
|
// src/mcp/prompts/CheckForNewVulnerabilitiesPrompt.ts
|
|
19709
|
-
import { z as
|
|
20457
|
+
import { z as z35 } from "zod";
|
|
19710
20458
|
|
|
19711
20459
|
// src/mcp/prompts/base/BasePrompt.ts
|
|
19712
|
-
import { z as
|
|
20460
|
+
import { z as z34 } from "zod";
|
|
19713
20461
|
var BasePrompt = class {
|
|
19714
20462
|
getDefinition() {
|
|
19715
20463
|
return {
|
|
@@ -19738,7 +20486,7 @@ var BasePrompt = class {
|
|
|
19738
20486
|
const argsToValidate = args === void 0 ? {} : args;
|
|
19739
20487
|
return this.argumentsValidationSchema.parse(argsToValidate);
|
|
19740
20488
|
} catch (error) {
|
|
19741
|
-
if (error instanceof
|
|
20489
|
+
if (error instanceof z34.ZodError) {
|
|
19742
20490
|
const errorDetails = error.errors.map((e) => {
|
|
19743
20491
|
const fieldPath = e.path.length > 0 ? e.path.join(".") : "root";
|
|
19744
20492
|
const message = e.message === "Required" ? `Missing required argument '${fieldPath}'` : `Invalid value for '${fieldPath}': ${e.message}`;
|
|
@@ -19767,8 +20515,8 @@ var BasePrompt = class {
|
|
|
19767
20515
|
};
|
|
19768
20516
|
|
|
19769
20517
|
// src/mcp/prompts/CheckForNewVulnerabilitiesPrompt.ts
|
|
19770
|
-
var CheckForNewVulnerabilitiesArgsSchema =
|
|
19771
|
-
path:
|
|
20518
|
+
var CheckForNewVulnerabilitiesArgsSchema = z35.object({
|
|
20519
|
+
path: z35.string().optional()
|
|
19772
20520
|
});
|
|
19773
20521
|
var CheckForNewVulnerabilitiesPrompt = class extends BasePrompt {
|
|
19774
20522
|
constructor() {
|
|
@@ -20013,9 +20761,9 @@ Call the \`check_for_new_available_fixes\` tool now${args?.path ? ` for ${args.p
|
|
|
20013
20761
|
};
|
|
20014
20762
|
|
|
20015
20763
|
// src/mcp/prompts/FullSecurityAuditPrompt.ts
|
|
20016
|
-
import { z as
|
|
20017
|
-
var FullSecurityAuditArgsSchema =
|
|
20018
|
-
path:
|
|
20764
|
+
import { z as z36 } from "zod";
|
|
20765
|
+
var FullSecurityAuditArgsSchema = z36.object({
|
|
20766
|
+
path: z36.string().optional()
|
|
20019
20767
|
});
|
|
20020
20768
|
var FullSecurityAuditPrompt = class extends BasePrompt {
|
|
20021
20769
|
constructor() {
|
|
@@ -20466,9 +21214,9 @@ Begin the audit now${args?.path ? ` for ${args.path}` : ""}.
|
|
|
20466
21214
|
};
|
|
20467
21215
|
|
|
20468
21216
|
// src/mcp/prompts/ReviewAndFixCriticalPrompt.ts
|
|
20469
|
-
import { z as
|
|
20470
|
-
var ReviewAndFixCriticalArgsSchema =
|
|
20471
|
-
path:
|
|
21217
|
+
import { z as z37 } from "zod";
|
|
21218
|
+
var ReviewAndFixCriticalArgsSchema = z37.object({
|
|
21219
|
+
path: z37.string().optional()
|
|
20472
21220
|
});
|
|
20473
21221
|
var ReviewAndFixCriticalPrompt = class extends BasePrompt {
|
|
20474
21222
|
constructor() {
|
|
@@ -20772,9 +21520,9 @@ Start by scanning${args?.path ? ` ${args.path}` : " the repository"} and priorit
|
|
|
20772
21520
|
};
|
|
20773
21521
|
|
|
20774
21522
|
// src/mcp/prompts/ScanRecentChangesPrompt.ts
|
|
20775
|
-
import { z as
|
|
20776
|
-
var ScanRecentChangesArgsSchema =
|
|
20777
|
-
path:
|
|
21523
|
+
import { z as z38 } from "zod";
|
|
21524
|
+
var ScanRecentChangesArgsSchema = z38.object({
|
|
21525
|
+
path: z38.string().optional()
|
|
20778
21526
|
});
|
|
20779
21527
|
var ScanRecentChangesPrompt = class extends BasePrompt {
|
|
20780
21528
|
constructor() {
|
|
@@ -20985,9 +21733,9 @@ You now have the guidance needed to perform a fast, targeted security scan of re
|
|
|
20985
21733
|
};
|
|
20986
21734
|
|
|
20987
21735
|
// src/mcp/prompts/ScanRepositoryPrompt.ts
|
|
20988
|
-
import { z as
|
|
20989
|
-
var ScanRepositoryArgsSchema =
|
|
20990
|
-
path:
|
|
21736
|
+
import { z as z39 } from "zod";
|
|
21737
|
+
var ScanRepositoryArgsSchema = z39.object({
|
|
21738
|
+
path: z39.string().optional()
|
|
20991
21739
|
});
|
|
20992
21740
|
var ScanRepositoryPrompt = class extends BasePrompt {
|
|
20993
21741
|
constructor() {
|
|
@@ -21365,31 +22113,31 @@ For a complete security audit workflow, use the \`full-security-audit\` prompt.
|
|
|
21365
22113
|
};
|
|
21366
22114
|
|
|
21367
22115
|
// src/mcp/services/McpDetectionService/CursorMcpDetectionService.ts
|
|
21368
|
-
import * as
|
|
21369
|
-
import * as
|
|
21370
|
-
import * as
|
|
22116
|
+
import * as fs19 from "fs";
|
|
22117
|
+
import * as os12 from "os";
|
|
22118
|
+
import * as path21 from "path";
|
|
21371
22119
|
|
|
21372
22120
|
// src/mcp/services/McpDetectionService/BaseMcpDetectionService.ts
|
|
21373
22121
|
init_configs();
|
|
21374
|
-
import * as
|
|
22122
|
+
import * as fs18 from "fs";
|
|
21375
22123
|
import fetch7 from "node-fetch";
|
|
21376
|
-
import * as
|
|
22124
|
+
import * as path20 from "path";
|
|
21377
22125
|
|
|
21378
22126
|
// src/mcp/services/McpDetectionService/McpDetectionServiceUtils.ts
|
|
21379
|
-
import * as
|
|
21380
|
-
import * as
|
|
22127
|
+
import * as fs17 from "fs";
|
|
22128
|
+
import * as os11 from "os";
|
|
21381
22129
|
|
|
21382
22130
|
// src/mcp/services/McpDetectionService/VscodeMcpDetectionService.ts
|
|
21383
|
-
import * as
|
|
21384
|
-
import * as
|
|
21385
|
-
import * as
|
|
22131
|
+
import * as fs20 from "fs";
|
|
22132
|
+
import * as os13 from "os";
|
|
22133
|
+
import * as path22 from "path";
|
|
21386
22134
|
|
|
21387
22135
|
// src/mcp/tools/checkForNewAvailableFixes/CheckForNewAvailableFixesTool.ts
|
|
21388
|
-
import { z as
|
|
22136
|
+
import { z as z42 } from "zod";
|
|
21389
22137
|
|
|
21390
22138
|
// src/mcp/services/PathValidation.ts
|
|
21391
|
-
import
|
|
21392
|
-
import
|
|
22139
|
+
import fs21 from "fs";
|
|
22140
|
+
import path23 from "path";
|
|
21393
22141
|
async function validatePath(inputPath) {
|
|
21394
22142
|
logDebug("Validating MCP path", { inputPath });
|
|
21395
22143
|
if (/^\/[a-zA-Z]:\//.test(inputPath)) {
|
|
@@ -21421,7 +22169,7 @@ async function validatePath(inputPath) {
|
|
|
21421
22169
|
logError(error);
|
|
21422
22170
|
return { isValid: false, error, path: inputPath };
|
|
21423
22171
|
}
|
|
21424
|
-
const normalizedPath =
|
|
22172
|
+
const normalizedPath = path23.normalize(inputPath);
|
|
21425
22173
|
if (normalizedPath.includes("..")) {
|
|
21426
22174
|
const error = `Normalized path contains path traversal patterns: ${inputPath}`;
|
|
21427
22175
|
logError(error);
|
|
@@ -21448,7 +22196,7 @@ async function validatePath(inputPath) {
|
|
|
21448
22196
|
logDebug("Path validation successful", { inputPath });
|
|
21449
22197
|
logDebug("Checking path existence", { inputPath });
|
|
21450
22198
|
try {
|
|
21451
|
-
await
|
|
22199
|
+
await fs21.promises.access(inputPath);
|
|
21452
22200
|
logDebug("Path exists and is accessible", { inputPath });
|
|
21453
22201
|
WorkspaceService.setKnownWorkspacePath(inputPath);
|
|
21454
22202
|
logDebug("Stored validated path in WorkspaceService", { inputPath });
|
|
@@ -21461,7 +22209,7 @@ async function validatePath(inputPath) {
|
|
|
21461
22209
|
}
|
|
21462
22210
|
|
|
21463
22211
|
// src/mcp/tools/base/BaseTool.ts
|
|
21464
|
-
import { z as
|
|
22212
|
+
import { z as z40 } from "zod";
|
|
21465
22213
|
var BaseTool = class {
|
|
21466
22214
|
getDefinition() {
|
|
21467
22215
|
return {
|
|
@@ -21488,7 +22236,7 @@ var BaseTool = class {
|
|
|
21488
22236
|
try {
|
|
21489
22237
|
return this.inputValidationSchema.parse(args);
|
|
21490
22238
|
} catch (error) {
|
|
21491
|
-
if (error instanceof
|
|
22239
|
+
if (error instanceof z40.ZodError) {
|
|
21492
22240
|
const errorDetails = error.errors.map((e) => {
|
|
21493
22241
|
const fieldPath = e.path.length > 0 ? e.path.join(".") : "root";
|
|
21494
22242
|
const message = e.message === "Required" ? `Missing required parameter '${fieldPath}'` : `Invalid value for '${fieldPath}': ${e.message}`;
|
|
@@ -22070,10 +22818,10 @@ If you wish to scan files that were recently changed in your git history call th
|
|
|
22070
22818
|
init_FileUtils();
|
|
22071
22819
|
init_GitService();
|
|
22072
22820
|
init_configs();
|
|
22073
|
-
import
|
|
22821
|
+
import fs22 from "fs/promises";
|
|
22074
22822
|
import nodePath from "path";
|
|
22075
22823
|
var getLocalFiles = async ({
|
|
22076
|
-
path:
|
|
22824
|
+
path: path30,
|
|
22077
22825
|
maxFileSize = MCP_MAX_FILE_SIZE,
|
|
22078
22826
|
maxFiles,
|
|
22079
22827
|
isAllFilesScan,
|
|
@@ -22081,17 +22829,17 @@ var getLocalFiles = async ({
|
|
|
22081
22829
|
scanRecentlyChangedFiles
|
|
22082
22830
|
}) => {
|
|
22083
22831
|
logDebug(`[${scanContext}] Starting getLocalFiles`, {
|
|
22084
|
-
path:
|
|
22832
|
+
path: path30,
|
|
22085
22833
|
maxFileSize,
|
|
22086
22834
|
maxFiles,
|
|
22087
22835
|
isAllFilesScan,
|
|
22088
22836
|
scanRecentlyChangedFiles
|
|
22089
22837
|
});
|
|
22090
22838
|
try {
|
|
22091
|
-
const resolvedRepoPath = await
|
|
22839
|
+
const resolvedRepoPath = await fs22.realpath(path30);
|
|
22092
22840
|
logDebug(`[${scanContext}] Resolved repository path`, {
|
|
22093
22841
|
resolvedRepoPath,
|
|
22094
|
-
originalPath:
|
|
22842
|
+
originalPath: path30
|
|
22095
22843
|
});
|
|
22096
22844
|
const gitService = new GitService(resolvedRepoPath, log);
|
|
22097
22845
|
const gitValidation = await gitService.validateRepository();
|
|
@@ -22104,7 +22852,7 @@ var getLocalFiles = async ({
|
|
|
22104
22852
|
if (!gitValidation.isValid || isAllFilesScan) {
|
|
22105
22853
|
try {
|
|
22106
22854
|
files = await FileUtils.getLastChangedFiles({
|
|
22107
|
-
dir:
|
|
22855
|
+
dir: path30,
|
|
22108
22856
|
maxFileSize,
|
|
22109
22857
|
maxFiles,
|
|
22110
22858
|
isAllFilesScan
|
|
@@ -22168,7 +22916,7 @@ var getLocalFiles = async ({
|
|
|
22168
22916
|
absoluteFilePath
|
|
22169
22917
|
);
|
|
22170
22918
|
try {
|
|
22171
|
-
const fileStat = await
|
|
22919
|
+
const fileStat = await fs22.stat(absoluteFilePath);
|
|
22172
22920
|
return {
|
|
22173
22921
|
filename: nodePath.basename(absoluteFilePath),
|
|
22174
22922
|
relativePath,
|
|
@@ -22196,7 +22944,7 @@ var getLocalFiles = async ({
|
|
|
22196
22944
|
logError(`${scanContext}Unexpected error in getLocalFiles`, {
|
|
22197
22945
|
error: error instanceof Error ? error.message : String(error),
|
|
22198
22946
|
stack: error instanceof Error ? error.stack : void 0,
|
|
22199
|
-
path:
|
|
22947
|
+
path: path30
|
|
22200
22948
|
});
|
|
22201
22949
|
throw error;
|
|
22202
22950
|
}
|
|
@@ -22205,15 +22953,15 @@ var getLocalFiles = async ({
|
|
|
22205
22953
|
// src/mcp/services/LocalMobbFolderService.ts
|
|
22206
22954
|
init_client_generates();
|
|
22207
22955
|
init_GitService();
|
|
22208
|
-
import
|
|
22209
|
-
import
|
|
22210
|
-
import { z as
|
|
22956
|
+
import fs23 from "fs";
|
|
22957
|
+
import path24 from "path";
|
|
22958
|
+
import { z as z41 } from "zod";
|
|
22211
22959
|
function extractPathFromPatch(patch) {
|
|
22212
22960
|
const match = patch?.match(/diff --git a\/([^\s]+) b\//);
|
|
22213
22961
|
return match?.[1] ?? null;
|
|
22214
22962
|
}
|
|
22215
22963
|
function parsedIssueTypeRes(issueType) {
|
|
22216
|
-
return
|
|
22964
|
+
return z41.nativeEnum(IssueType_Enum).safeParse(issueType);
|
|
22217
22965
|
}
|
|
22218
22966
|
var LocalMobbFolderService = class {
|
|
22219
22967
|
/**
|
|
@@ -22292,19 +23040,19 @@ var LocalMobbFolderService = class {
|
|
|
22292
23040
|
"[LocalMobbFolderService] Non-git repository detected, skipping .gitignore operations"
|
|
22293
23041
|
);
|
|
22294
23042
|
}
|
|
22295
|
-
const mobbFolderPath =
|
|
23043
|
+
const mobbFolderPath = path24.join(
|
|
22296
23044
|
this.repoPath,
|
|
22297
23045
|
this.defaultMobbFolderName
|
|
22298
23046
|
);
|
|
22299
|
-
if (!
|
|
23047
|
+
if (!fs23.existsSync(mobbFolderPath)) {
|
|
22300
23048
|
logInfo("[LocalMobbFolderService] Creating .mobb folder", {
|
|
22301
23049
|
mobbFolderPath
|
|
22302
23050
|
});
|
|
22303
|
-
|
|
23051
|
+
fs23.mkdirSync(mobbFolderPath, { recursive: true });
|
|
22304
23052
|
} else {
|
|
22305
23053
|
logDebug("[LocalMobbFolderService] .mobb folder already exists");
|
|
22306
23054
|
}
|
|
22307
|
-
const stats =
|
|
23055
|
+
const stats = fs23.statSync(mobbFolderPath);
|
|
22308
23056
|
if (!stats.isDirectory()) {
|
|
22309
23057
|
throw new Error(`Path exists but is not a directory: ${mobbFolderPath}`);
|
|
22310
23058
|
}
|
|
@@ -22345,13 +23093,13 @@ var LocalMobbFolderService = class {
|
|
|
22345
23093
|
logDebug("[LocalMobbFolderService] Git repository validated successfully");
|
|
22346
23094
|
} else {
|
|
22347
23095
|
try {
|
|
22348
|
-
const stats =
|
|
23096
|
+
const stats = fs23.statSync(this.repoPath);
|
|
22349
23097
|
if (!stats.isDirectory()) {
|
|
22350
23098
|
throw new Error(
|
|
22351
23099
|
`Path exists but is not a directory: ${this.repoPath}`
|
|
22352
23100
|
);
|
|
22353
23101
|
}
|
|
22354
|
-
|
|
23102
|
+
fs23.accessSync(this.repoPath, fs23.constants.R_OK | fs23.constants.W_OK);
|
|
22355
23103
|
logDebug(
|
|
22356
23104
|
"[LocalMobbFolderService] Non-git directory validated successfully"
|
|
22357
23105
|
);
|
|
@@ -22464,8 +23212,8 @@ var LocalMobbFolderService = class {
|
|
|
22464
23212
|
mobbFolderPath,
|
|
22465
23213
|
baseFileName
|
|
22466
23214
|
);
|
|
22467
|
-
const filePath =
|
|
22468
|
-
await
|
|
23215
|
+
const filePath = path24.join(mobbFolderPath, uniqueFileName);
|
|
23216
|
+
await fs23.promises.writeFile(filePath, patch, "utf8");
|
|
22469
23217
|
logInfo("[LocalMobbFolderService] Patch saved successfully", {
|
|
22470
23218
|
filePath,
|
|
22471
23219
|
fileName: uniqueFileName,
|
|
@@ -22522,11 +23270,11 @@ var LocalMobbFolderService = class {
|
|
|
22522
23270
|
* @returns Unique filename that doesn't conflict with existing files
|
|
22523
23271
|
*/
|
|
22524
23272
|
getUniqueFileName(folderPath, baseFileName) {
|
|
22525
|
-
const baseName =
|
|
22526
|
-
const extension =
|
|
23273
|
+
const baseName = path24.parse(baseFileName).name;
|
|
23274
|
+
const extension = path24.parse(baseFileName).ext;
|
|
22527
23275
|
let uniqueFileName = baseFileName;
|
|
22528
23276
|
let index = 1;
|
|
22529
|
-
while (
|
|
23277
|
+
while (fs23.existsSync(path24.join(folderPath, uniqueFileName))) {
|
|
22530
23278
|
uniqueFileName = `${baseName}-${index}${extension}`;
|
|
22531
23279
|
index++;
|
|
22532
23280
|
if (index > 1e3) {
|
|
@@ -22557,18 +23305,18 @@ var LocalMobbFolderService = class {
|
|
|
22557
23305
|
logDebug("[LocalMobbFolderService] Logging patch info", { fixId: fix.id });
|
|
22558
23306
|
try {
|
|
22559
23307
|
const mobbFolderPath = await this.getFolder();
|
|
22560
|
-
const patchInfoPath =
|
|
23308
|
+
const patchInfoPath = path24.join(mobbFolderPath, "patchInfo.md");
|
|
22561
23309
|
const markdownContent = this.generateFixMarkdown(fix, savedPatchFileName);
|
|
22562
23310
|
let existingContent = "";
|
|
22563
|
-
if (
|
|
22564
|
-
existingContent = await
|
|
23311
|
+
if (fs23.existsSync(patchInfoPath)) {
|
|
23312
|
+
existingContent = await fs23.promises.readFile(patchInfoPath, "utf8");
|
|
22565
23313
|
logDebug("[LocalMobbFolderService] Existing patchInfo.md found");
|
|
22566
23314
|
} else {
|
|
22567
23315
|
logDebug("[LocalMobbFolderService] Creating new patchInfo.md file");
|
|
22568
23316
|
}
|
|
22569
23317
|
const separator = existingContent ? "\n\n================================================================================\n\n" : "";
|
|
22570
23318
|
const updatedContent = `${markdownContent}${separator}${existingContent}`;
|
|
22571
|
-
await
|
|
23319
|
+
await fs23.promises.writeFile(patchInfoPath, updatedContent, "utf8");
|
|
22572
23320
|
logInfo("[LocalMobbFolderService] Patch info logged successfully", {
|
|
22573
23321
|
patchInfoPath,
|
|
22574
23322
|
fixId: fix.id,
|
|
@@ -22599,7 +23347,7 @@ var LocalMobbFolderService = class {
|
|
|
22599
23347
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
22600
23348
|
const patch = this.extractPatchFromFix(fix);
|
|
22601
23349
|
const relativePatchedFilePath = patch ? extractPathFromPatch(patch) : null;
|
|
22602
|
-
const patchedFilePath = relativePatchedFilePath ?
|
|
23350
|
+
const patchedFilePath = relativePatchedFilePath ? path24.resolve(this.repoPath, relativePatchedFilePath) : null;
|
|
22603
23351
|
const fixIdentifier = savedPatchFileName ? savedPatchFileName.replace(".patch", "") : fix.id;
|
|
22604
23352
|
let markdown = `# Fix ${fixIdentifier}
|
|
22605
23353
|
|
|
@@ -22941,16 +23689,16 @@ import {
|
|
|
22941
23689
|
unlinkSync,
|
|
22942
23690
|
writeFileSync as writeFileSync2
|
|
22943
23691
|
} from "fs";
|
|
22944
|
-
import
|
|
23692
|
+
import fs24 from "fs/promises";
|
|
22945
23693
|
import parseDiff2 from "parse-diff";
|
|
22946
|
-
import
|
|
23694
|
+
import path25 from "path";
|
|
22947
23695
|
var PatchApplicationService = class {
|
|
22948
23696
|
/**
|
|
22949
23697
|
* Gets the appropriate comment syntax for a file based on its extension
|
|
22950
23698
|
*/
|
|
22951
23699
|
static getCommentSyntax(filePath) {
|
|
22952
|
-
const ext =
|
|
22953
|
-
const basename2 =
|
|
23700
|
+
const ext = path25.extname(filePath).toLowerCase();
|
|
23701
|
+
const basename2 = path25.basename(filePath);
|
|
22954
23702
|
const commentMap = {
|
|
22955
23703
|
// C-style languages (single line comments)
|
|
22956
23704
|
".js": "//",
|
|
@@ -23158,7 +23906,7 @@ var PatchApplicationService = class {
|
|
|
23158
23906
|
}
|
|
23159
23907
|
);
|
|
23160
23908
|
}
|
|
23161
|
-
const dirPath =
|
|
23909
|
+
const dirPath = path25.dirname(normalizedFilePath);
|
|
23162
23910
|
mkdirSync(dirPath, { recursive: true });
|
|
23163
23911
|
writeFileSync2(normalizedFilePath, finalContent, "utf8");
|
|
23164
23912
|
return normalizedFilePath;
|
|
@@ -23167,9 +23915,9 @@ var PatchApplicationService = class {
|
|
|
23167
23915
|
repositoryPath,
|
|
23168
23916
|
targetPath
|
|
23169
23917
|
}) {
|
|
23170
|
-
const repoRoot =
|
|
23171
|
-
const normalizedPath =
|
|
23172
|
-
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}`;
|
|
23173
23921
|
if (normalizedPath !== repoRoot && !normalizedPath.startsWith(repoRootWithSep)) {
|
|
23174
23922
|
throw new Error(
|
|
23175
23923
|
`Security violation: target path ${targetPath} resolves outside repository`
|
|
@@ -23178,7 +23926,7 @@ var PatchApplicationService = class {
|
|
|
23178
23926
|
return {
|
|
23179
23927
|
repoRoot,
|
|
23180
23928
|
normalizedPath,
|
|
23181
|
-
relativePath:
|
|
23929
|
+
relativePath: path25.relative(repoRoot, normalizedPath)
|
|
23182
23930
|
};
|
|
23183
23931
|
}
|
|
23184
23932
|
/**
|
|
@@ -23460,9 +24208,9 @@ var PatchApplicationService = class {
|
|
|
23460
24208
|
continue;
|
|
23461
24209
|
}
|
|
23462
24210
|
try {
|
|
23463
|
-
const absolutePath =
|
|
24211
|
+
const absolutePath = path25.resolve(repositoryPath, targetFile);
|
|
23464
24212
|
if (existsSync6(absolutePath)) {
|
|
23465
|
-
const stats = await
|
|
24213
|
+
const stats = await fs24.stat(absolutePath);
|
|
23466
24214
|
const fileModTime = stats.mtime.getTime();
|
|
23467
24215
|
if (fileModTime > scanStartTime) {
|
|
23468
24216
|
logError(
|
|
@@ -23503,7 +24251,7 @@ var PatchApplicationService = class {
|
|
|
23503
24251
|
const appliedFixes = [];
|
|
23504
24252
|
const failedFixes = [];
|
|
23505
24253
|
const skippedFixes = [];
|
|
23506
|
-
const resolvedRepoPath = await
|
|
24254
|
+
const resolvedRepoPath = await fs24.realpath(repositoryPath);
|
|
23507
24255
|
logInfo(
|
|
23508
24256
|
`[${scanContext}] Starting patch application for ${fixes.length} fixes`,
|
|
23509
24257
|
{
|
|
@@ -23686,7 +24434,7 @@ var PatchApplicationService = class {
|
|
|
23686
24434
|
fix,
|
|
23687
24435
|
scanContext
|
|
23688
24436
|
});
|
|
23689
|
-
appliedFiles.push(
|
|
24437
|
+
appliedFiles.push(path25.relative(repositoryPath, actualPath));
|
|
23690
24438
|
logDebug(`[${scanContext}] Created new file: ${relativePath}`);
|
|
23691
24439
|
}
|
|
23692
24440
|
/**
|
|
@@ -23735,7 +24483,7 @@ var PatchApplicationService = class {
|
|
|
23735
24483
|
fix,
|
|
23736
24484
|
scanContext
|
|
23737
24485
|
});
|
|
23738
|
-
appliedFiles.push(
|
|
24486
|
+
appliedFiles.push(path25.relative(repositoryPath, actualPath));
|
|
23739
24487
|
logDebug(`[${scanContext}] Modified file: ${relativePath}`);
|
|
23740
24488
|
}
|
|
23741
24489
|
}
|
|
@@ -23931,8 +24679,8 @@ init_configs();
|
|
|
23931
24679
|
|
|
23932
24680
|
// src/mcp/services/FileOperations.ts
|
|
23933
24681
|
init_FileUtils();
|
|
23934
|
-
import
|
|
23935
|
-
import
|
|
24682
|
+
import fs25 from "fs";
|
|
24683
|
+
import path26 from "path";
|
|
23936
24684
|
import AdmZip3 from "adm-zip";
|
|
23937
24685
|
var FileOperations = class {
|
|
23938
24686
|
/**
|
|
@@ -23952,10 +24700,10 @@ var FileOperations = class {
|
|
|
23952
24700
|
let packedFilesCount = 0;
|
|
23953
24701
|
const packedFiles = [];
|
|
23954
24702
|
const excludedFiles = [];
|
|
23955
|
-
const resolvedRepoPath =
|
|
24703
|
+
const resolvedRepoPath = path26.resolve(repositoryPath);
|
|
23956
24704
|
for (const filepath of fileList) {
|
|
23957
|
-
const absoluteFilepath =
|
|
23958
|
-
const resolvedFilePath =
|
|
24705
|
+
const absoluteFilepath = path26.join(repositoryPath, filepath);
|
|
24706
|
+
const resolvedFilePath = path26.resolve(absoluteFilepath);
|
|
23959
24707
|
if (!resolvedFilePath.startsWith(resolvedRepoPath)) {
|
|
23960
24708
|
const reason = "potential path traversal security risk";
|
|
23961
24709
|
logDebug(`[FileOperations] Skipping ${filepath} due to ${reason}`);
|
|
@@ -24002,11 +24750,11 @@ var FileOperations = class {
|
|
|
24002
24750
|
fileList,
|
|
24003
24751
|
repositoryPath
|
|
24004
24752
|
}) {
|
|
24005
|
-
const resolvedRepoPath =
|
|
24753
|
+
const resolvedRepoPath = path26.resolve(repositoryPath);
|
|
24006
24754
|
const validatedPaths = [];
|
|
24007
24755
|
for (const filepath of fileList) {
|
|
24008
|
-
const absoluteFilepath =
|
|
24009
|
-
const resolvedFilePath =
|
|
24756
|
+
const absoluteFilepath = path26.join(repositoryPath, filepath);
|
|
24757
|
+
const resolvedFilePath = path26.resolve(absoluteFilepath);
|
|
24010
24758
|
if (!resolvedFilePath.startsWith(resolvedRepoPath)) {
|
|
24011
24759
|
logDebug(
|
|
24012
24760
|
`[FileOperations] Rejecting ${filepath} - path traversal attempt detected`
|
|
@@ -24014,7 +24762,7 @@ var FileOperations = class {
|
|
|
24014
24762
|
continue;
|
|
24015
24763
|
}
|
|
24016
24764
|
try {
|
|
24017
|
-
await
|
|
24765
|
+
await fs25.promises.access(absoluteFilepath, fs25.constants.R_OK);
|
|
24018
24766
|
validatedPaths.push(filepath);
|
|
24019
24767
|
} catch (error) {
|
|
24020
24768
|
logDebug(
|
|
@@ -24033,8 +24781,8 @@ var FileOperations = class {
|
|
|
24033
24781
|
const fileDataArray = [];
|
|
24034
24782
|
for (const absolutePath of filePaths) {
|
|
24035
24783
|
try {
|
|
24036
|
-
const content = await
|
|
24037
|
-
const relativePath =
|
|
24784
|
+
const content = await fs25.promises.readFile(absolutePath);
|
|
24785
|
+
const relativePath = path26.basename(absolutePath);
|
|
24038
24786
|
fileDataArray.push({
|
|
24039
24787
|
relativePath,
|
|
24040
24788
|
absolutePath,
|
|
@@ -24059,7 +24807,7 @@ var FileOperations = class {
|
|
|
24059
24807
|
relativeFilepath
|
|
24060
24808
|
}) {
|
|
24061
24809
|
try {
|
|
24062
|
-
return await
|
|
24810
|
+
return await fs25.promises.readFile(absoluteFilepath);
|
|
24063
24811
|
} catch (fsError) {
|
|
24064
24812
|
logError(
|
|
24065
24813
|
`[FileOperations] Failed to read ${relativeFilepath} from filesystem: ${fsError}`
|
|
@@ -24346,14 +25094,14 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
|
|
|
24346
25094
|
* since the last scan.
|
|
24347
25095
|
*/
|
|
24348
25096
|
async scanForSecurityVulnerabilities({
|
|
24349
|
-
path:
|
|
25097
|
+
path: path30,
|
|
24350
25098
|
isAllDetectionRulesScan,
|
|
24351
25099
|
isAllFilesScan,
|
|
24352
25100
|
scanContext
|
|
24353
25101
|
}) {
|
|
24354
25102
|
this.hasAuthenticationFailed = false;
|
|
24355
25103
|
logDebug(`[${scanContext}] Scanning for new security vulnerabilities`, {
|
|
24356
|
-
path:
|
|
25104
|
+
path: path30
|
|
24357
25105
|
});
|
|
24358
25106
|
if (!this.gqlClient) {
|
|
24359
25107
|
logInfo(`[${scanContext}] No GQL client found, skipping scan`);
|
|
@@ -24369,11 +25117,11 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
|
|
|
24369
25117
|
}
|
|
24370
25118
|
logDebug(
|
|
24371
25119
|
`[${scanContext}] Connected to the API, assembling list of files to scan`,
|
|
24372
|
-
{ path:
|
|
25120
|
+
{ path: path30 }
|
|
24373
25121
|
);
|
|
24374
25122
|
const isBackgroundScan = scanContext === ScanContext.BACKGROUND_INITIAL || scanContext === ScanContext.BACKGROUND_PERIODIC;
|
|
24375
25123
|
const files = await getLocalFiles({
|
|
24376
|
-
path:
|
|
25124
|
+
path: path30,
|
|
24377
25125
|
isAllFilesScan,
|
|
24378
25126
|
scanContext,
|
|
24379
25127
|
scanRecentlyChangedFiles: !isBackgroundScan
|
|
@@ -24399,13 +25147,13 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
|
|
|
24399
25147
|
});
|
|
24400
25148
|
const { fixReportId, projectId } = await scanFiles({
|
|
24401
25149
|
fileList: filesToScan.map((file) => file.relativePath),
|
|
24402
|
-
repositoryPath:
|
|
25150
|
+
repositoryPath: path30,
|
|
24403
25151
|
gqlClient: this.gqlClient,
|
|
24404
25152
|
isAllDetectionRulesScan,
|
|
24405
25153
|
scanContext
|
|
24406
25154
|
});
|
|
24407
25155
|
logInfo(
|
|
24408
|
-
`[${scanContext}] Security scan completed for ${
|
|
25156
|
+
`[${scanContext}] Security scan completed for ${path30} reportId: ${fixReportId} projectId: ${projectId}`
|
|
24409
25157
|
);
|
|
24410
25158
|
if (isAllFilesScan) {
|
|
24411
25159
|
return;
|
|
@@ -24699,13 +25447,13 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
|
|
|
24699
25447
|
});
|
|
24700
25448
|
return scannedFiles.some((file) => file.relativePath === fixFile);
|
|
24701
25449
|
}
|
|
24702
|
-
async getFreshFixes({ path:
|
|
25450
|
+
async getFreshFixes({ path: path30 }) {
|
|
24703
25451
|
const scanContext = ScanContext.USER_REQUEST;
|
|
24704
|
-
logDebug(`[${scanContext}] Getting fresh fixes`, { path:
|
|
24705
|
-
if (this.path !==
|
|
24706
|
-
this.path =
|
|
25452
|
+
logDebug(`[${scanContext}] Getting fresh fixes`, { path: path30 });
|
|
25453
|
+
if (this.path !== path30) {
|
|
25454
|
+
this.path = path30;
|
|
24707
25455
|
this.reset();
|
|
24708
|
-
logInfo(`[${scanContext}] Reset service state for new path`, { path:
|
|
25456
|
+
logInfo(`[${scanContext}] Reset service state for new path`, { path: path30 });
|
|
24709
25457
|
}
|
|
24710
25458
|
try {
|
|
24711
25459
|
const loginContext = createMcpLoginContext("check_new_fixes");
|
|
@@ -24724,7 +25472,7 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
|
|
|
24724
25472
|
}
|
|
24725
25473
|
throw error;
|
|
24726
25474
|
}
|
|
24727
|
-
this.triggerScan({ path:
|
|
25475
|
+
this.triggerScan({ path: path30, gqlClient: this.gqlClient });
|
|
24728
25476
|
let isMvsAutoFixEnabled = null;
|
|
24729
25477
|
try {
|
|
24730
25478
|
isMvsAutoFixEnabled = await this.gqlClient.getMvsAutoFixSettings();
|
|
@@ -24758,33 +25506,33 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
|
|
|
24758
25506
|
return noFreshFixesPrompt;
|
|
24759
25507
|
}
|
|
24760
25508
|
triggerScan({
|
|
24761
|
-
path:
|
|
25509
|
+
path: path30,
|
|
24762
25510
|
gqlClient
|
|
24763
25511
|
}) {
|
|
24764
|
-
if (this.path !==
|
|
24765
|
-
this.path =
|
|
25512
|
+
if (this.path !== path30) {
|
|
25513
|
+
this.path = path30;
|
|
24766
25514
|
this.reset();
|
|
24767
|
-
logInfo(`Reset service state for new path in triggerScan`, { path:
|
|
25515
|
+
logInfo(`Reset service state for new path in triggerScan`, { path: path30 });
|
|
24768
25516
|
}
|
|
24769
25517
|
this.gqlClient = gqlClient;
|
|
24770
25518
|
if (!this.intervalId) {
|
|
24771
|
-
this.startPeriodicScanning(
|
|
24772
|
-
this.executeInitialScan(
|
|
24773
|
-
void this.executeInitialFullScan(
|
|
25519
|
+
this.startPeriodicScanning(path30);
|
|
25520
|
+
this.executeInitialScan(path30);
|
|
25521
|
+
void this.executeInitialFullScan(path30);
|
|
24774
25522
|
}
|
|
24775
25523
|
}
|
|
24776
|
-
startPeriodicScanning(
|
|
25524
|
+
startPeriodicScanning(path30) {
|
|
24777
25525
|
const scanContext = ScanContext.BACKGROUND_PERIODIC;
|
|
24778
25526
|
logDebug(
|
|
24779
25527
|
`[${scanContext}] Starting periodic scan for new security vulnerabilities`,
|
|
24780
25528
|
{
|
|
24781
|
-
path:
|
|
25529
|
+
path: path30
|
|
24782
25530
|
}
|
|
24783
25531
|
);
|
|
24784
25532
|
this.intervalId = setInterval(() => {
|
|
24785
|
-
logDebug(`[${scanContext}] Triggering periodic security scan`, { path:
|
|
25533
|
+
logDebug(`[${scanContext}] Triggering periodic security scan`, { path: path30 });
|
|
24786
25534
|
this.scanForSecurityVulnerabilities({
|
|
24787
|
-
path:
|
|
25535
|
+
path: path30,
|
|
24788
25536
|
scanContext
|
|
24789
25537
|
}).catch((error) => {
|
|
24790
25538
|
logError(`[${scanContext}] Error during periodic security scan`, {
|
|
@@ -24793,45 +25541,45 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
|
|
|
24793
25541
|
});
|
|
24794
25542
|
}, MCP_PERIODIC_CHECK_INTERVAL);
|
|
24795
25543
|
}
|
|
24796
|
-
async executeInitialFullScan(
|
|
25544
|
+
async executeInitialFullScan(path30) {
|
|
24797
25545
|
const scanContext = ScanContext.FULL_SCAN;
|
|
24798
|
-
logDebug(`[${scanContext}] Triggering initial full security scan`, { path:
|
|
25546
|
+
logDebug(`[${scanContext}] Triggering initial full security scan`, { path: path30 });
|
|
24799
25547
|
logDebug(`[${scanContext}] Full scan paths scanned`, {
|
|
24800
25548
|
fullScanPathsScanned: this.fullScanPathsScanned
|
|
24801
25549
|
});
|
|
24802
|
-
if (this.fullScanPathsScanned.includes(
|
|
25550
|
+
if (this.fullScanPathsScanned.includes(path30)) {
|
|
24803
25551
|
logDebug(`[${scanContext}] Full scan already executed for this path`, {
|
|
24804
|
-
path:
|
|
25552
|
+
path: path30
|
|
24805
25553
|
});
|
|
24806
25554
|
return;
|
|
24807
25555
|
}
|
|
24808
25556
|
configStore.set("fullScanPathsScanned", [
|
|
24809
25557
|
...this.fullScanPathsScanned,
|
|
24810
|
-
|
|
25558
|
+
path30
|
|
24811
25559
|
]);
|
|
24812
25560
|
try {
|
|
24813
25561
|
await this.scanForSecurityVulnerabilities({
|
|
24814
|
-
path:
|
|
25562
|
+
path: path30,
|
|
24815
25563
|
isAllFilesScan: true,
|
|
24816
25564
|
isAllDetectionRulesScan: true,
|
|
24817
25565
|
scanContext: ScanContext.FULL_SCAN
|
|
24818
25566
|
});
|
|
24819
|
-
if (!this.fullScanPathsScanned.includes(
|
|
24820
|
-
this.fullScanPathsScanned.push(
|
|
25567
|
+
if (!this.fullScanPathsScanned.includes(path30)) {
|
|
25568
|
+
this.fullScanPathsScanned.push(path30);
|
|
24821
25569
|
configStore.set("fullScanPathsScanned", this.fullScanPathsScanned);
|
|
24822
25570
|
}
|
|
24823
|
-
logInfo(`[${scanContext}] Full scan completed`, { path:
|
|
25571
|
+
logInfo(`[${scanContext}] Full scan completed`, { path: path30 });
|
|
24824
25572
|
} catch (error) {
|
|
24825
25573
|
logError(`[${scanContext}] Error during initial full security scan`, {
|
|
24826
25574
|
error
|
|
24827
25575
|
});
|
|
24828
25576
|
}
|
|
24829
25577
|
}
|
|
24830
|
-
executeInitialScan(
|
|
25578
|
+
executeInitialScan(path30) {
|
|
24831
25579
|
const scanContext = ScanContext.BACKGROUND_INITIAL;
|
|
24832
|
-
logDebug(`[${scanContext}] Triggering initial security scan`, { path:
|
|
25580
|
+
logDebug(`[${scanContext}] Triggering initial security scan`, { path: path30 });
|
|
24833
25581
|
this.scanForSecurityVulnerabilities({
|
|
24834
|
-
path:
|
|
25582
|
+
path: path30,
|
|
24835
25583
|
scanContext: ScanContext.BACKGROUND_INITIAL
|
|
24836
25584
|
}).catch((error) => {
|
|
24837
25585
|
logError(`[${scanContext}] Error during initial security scan`, { error });
|
|
@@ -24910,8 +25658,8 @@ Example payload:
|
|
|
24910
25658
|
},
|
|
24911
25659
|
required: ["path"]
|
|
24912
25660
|
});
|
|
24913
|
-
__publicField(this, "inputValidationSchema",
|
|
24914
|
-
path:
|
|
25661
|
+
__publicField(this, "inputValidationSchema", z42.object({
|
|
25662
|
+
path: z42.string().describe(
|
|
24915
25663
|
"Full local path to the cloned git repository to check for new available fixes"
|
|
24916
25664
|
)
|
|
24917
25665
|
}));
|
|
@@ -24928,9 +25676,9 @@ Example payload:
|
|
|
24928
25676
|
`Invalid path: potential security risk detected in path: ${pathValidationResult.error}`
|
|
24929
25677
|
);
|
|
24930
25678
|
}
|
|
24931
|
-
const
|
|
25679
|
+
const path30 = pathValidationResult.path;
|
|
24932
25680
|
const resultText = await this.newFixesService.getFreshFixes({
|
|
24933
|
-
path:
|
|
25681
|
+
path: path30
|
|
24934
25682
|
});
|
|
24935
25683
|
logInfo("CheckForNewAvailableFixesTool execution completed", {
|
|
24936
25684
|
resultText
|
|
@@ -24941,7 +25689,7 @@ Example payload:
|
|
|
24941
25689
|
|
|
24942
25690
|
// src/mcp/tools/fetchAvailableFixes/FetchAvailableFixesTool.ts
|
|
24943
25691
|
init_GitService();
|
|
24944
|
-
import { z as
|
|
25692
|
+
import { z as z43 } from "zod";
|
|
24945
25693
|
|
|
24946
25694
|
// src/mcp/tools/fetchAvailableFixes/FetchAvailableFixesService.ts
|
|
24947
25695
|
init_configs();
|
|
@@ -25084,16 +25832,16 @@ Call this tool instead of ${MCP_TOOL_SCAN_AND_FIX_VULNERABILITIES} when you only
|
|
|
25084
25832
|
},
|
|
25085
25833
|
required: ["path"]
|
|
25086
25834
|
});
|
|
25087
|
-
__publicField(this, "inputValidationSchema",
|
|
25088
|
-
path:
|
|
25835
|
+
__publicField(this, "inputValidationSchema", z43.object({
|
|
25836
|
+
path: z43.string().describe(
|
|
25089
25837
|
"Full local path to the cloned git repository to check for available fixes"
|
|
25090
25838
|
),
|
|
25091
|
-
offset:
|
|
25092
|
-
limit:
|
|
25093
|
-
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(
|
|
25094
25842
|
"Optional list of file paths relative to the path parameter to filter fixes by. INCOMPATIBLE with fetchFixesFromAnyFile"
|
|
25095
25843
|
),
|
|
25096
|
-
fetchFixesFromAnyFile:
|
|
25844
|
+
fetchFixesFromAnyFile: z43.boolean().optional().describe(
|
|
25097
25845
|
"Optional boolean to fetch fixes for all files. INCOMPATIBLE with fileFilter"
|
|
25098
25846
|
)
|
|
25099
25847
|
}));
|
|
@@ -25108,8 +25856,8 @@ Call this tool instead of ${MCP_TOOL_SCAN_AND_FIX_VULNERABILITIES} when you only
|
|
|
25108
25856
|
`Invalid path: potential security risk detected in path: ${pathValidationResult.error}`
|
|
25109
25857
|
);
|
|
25110
25858
|
}
|
|
25111
|
-
const
|
|
25112
|
-
const gitService = new GitService(
|
|
25859
|
+
const path30 = pathValidationResult.path;
|
|
25860
|
+
const gitService = new GitService(path30, log);
|
|
25113
25861
|
const gitValidation = await gitService.validateRepository();
|
|
25114
25862
|
if (!gitValidation.isValid) {
|
|
25115
25863
|
throw new Error(`Invalid git repository: ${gitValidation.error}`);
|
|
@@ -25158,7 +25906,7 @@ Call this tool instead of ${MCP_TOOL_SCAN_AND_FIX_VULNERABILITIES} when you only
|
|
|
25158
25906
|
};
|
|
25159
25907
|
|
|
25160
25908
|
// src/mcp/tools/mcpChecker/mcpCheckerTool.ts
|
|
25161
|
-
import
|
|
25909
|
+
import z44 from "zod";
|
|
25162
25910
|
|
|
25163
25911
|
// src/mcp/tools/mcpChecker/mcpCheckerService.ts
|
|
25164
25912
|
var _McpCheckerService = class _McpCheckerService {
|
|
@@ -25219,7 +25967,7 @@ var McpCheckerTool = class extends BaseTool {
|
|
|
25219
25967
|
__publicField(this, "displayName", "MCP Checker");
|
|
25220
25968
|
// A detailed description to guide the LLM on when and how to invoke this tool.
|
|
25221
25969
|
__publicField(this, "description", "Check the MCP servers running on this IDE against organization policies.");
|
|
25222
|
-
__publicField(this, "inputValidationSchema",
|
|
25970
|
+
__publicField(this, "inputValidationSchema", z44.object({}));
|
|
25223
25971
|
__publicField(this, "inputSchema", {
|
|
25224
25972
|
type: "object",
|
|
25225
25973
|
properties: {},
|
|
@@ -25245,7 +25993,7 @@ var McpCheckerTool = class extends BaseTool {
|
|
|
25245
25993
|
};
|
|
25246
25994
|
|
|
25247
25995
|
// src/mcp/tools/scanAndFixVulnerabilities/ScanAndFixVulnerabilitiesTool.ts
|
|
25248
|
-
import
|
|
25996
|
+
import z45 from "zod";
|
|
25249
25997
|
init_configs();
|
|
25250
25998
|
|
|
25251
25999
|
// src/mcp/tools/scanAndFixVulnerabilities/ScanAndFixVulnerabilitiesService.ts
|
|
@@ -25433,17 +26181,17 @@ Example payload:
|
|
|
25433
26181
|
"rescan": false
|
|
25434
26182
|
}`);
|
|
25435
26183
|
__publicField(this, "hasAuthentication", true);
|
|
25436
|
-
__publicField(this, "inputValidationSchema",
|
|
25437
|
-
path:
|
|
26184
|
+
__publicField(this, "inputValidationSchema", z45.object({
|
|
26185
|
+
path: z45.string().describe(
|
|
25438
26186
|
"Full local path to repository to scan and fix vulnerabilities"
|
|
25439
26187
|
),
|
|
25440
|
-
offset:
|
|
25441
|
-
limit:
|
|
25442
|
-
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(
|
|
25443
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.`
|
|
25444
26192
|
),
|
|
25445
|
-
rescan:
|
|
25446
|
-
scanRecentlyChangedFiles:
|
|
26193
|
+
rescan: z45.boolean().optional().describe("Optional whether to rescan the repository"),
|
|
26194
|
+
scanRecentlyChangedFiles: z45.boolean().optional().describe(
|
|
25447
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."
|
|
25448
26196
|
)
|
|
25449
26197
|
}));
|
|
@@ -25494,9 +26242,9 @@ Example payload:
|
|
|
25494
26242
|
`Invalid path: potential security risk detected in path: ${pathValidationResult.error}`
|
|
25495
26243
|
);
|
|
25496
26244
|
}
|
|
25497
|
-
const
|
|
26245
|
+
const path30 = pathValidationResult.path;
|
|
25498
26246
|
const files = await getLocalFiles({
|
|
25499
|
-
path:
|
|
26247
|
+
path: path30,
|
|
25500
26248
|
maxFileSize: MCP_MAX_FILE_SIZE,
|
|
25501
26249
|
maxFiles: args.maxFiles,
|
|
25502
26250
|
scanContext: ScanContext.USER_REQUEST,
|
|
@@ -25516,7 +26264,7 @@ Example payload:
|
|
|
25516
26264
|
try {
|
|
25517
26265
|
const fixResult = await this.vulnerabilityFixService.processVulnerabilities({
|
|
25518
26266
|
fileList: files.map((file) => file.relativePath),
|
|
25519
|
-
repositoryPath:
|
|
26267
|
+
repositoryPath: path30,
|
|
25520
26268
|
offset: args.offset,
|
|
25521
26269
|
limit: args.limit,
|
|
25522
26270
|
isRescan: args.rescan || !!args.maxFiles
|
|
@@ -25620,7 +26368,7 @@ var mcpHandler = async (_args) => {
|
|
|
25620
26368
|
};
|
|
25621
26369
|
|
|
25622
26370
|
// src/args/commands/review.ts
|
|
25623
|
-
import
|
|
26371
|
+
import fs26 from "fs";
|
|
25624
26372
|
import chalk12 from "chalk";
|
|
25625
26373
|
function reviewBuilder(yargs2) {
|
|
25626
26374
|
return yargs2.option("f", {
|
|
@@ -25657,7 +26405,7 @@ function reviewBuilder(yargs2) {
|
|
|
25657
26405
|
).help();
|
|
25658
26406
|
}
|
|
25659
26407
|
function validateReviewOptions(argv) {
|
|
25660
|
-
if (!
|
|
26408
|
+
if (!fs26.existsSync(argv.f)) {
|
|
25661
26409
|
throw new CliError(`
|
|
25662
26410
|
Can't access ${chalk12.bold(argv.f)}`);
|
|
25663
26411
|
}
|
|
@@ -25751,15 +26499,76 @@ async function addScmTokenHandler(args) {
|
|
|
25751
26499
|
}
|
|
25752
26500
|
|
|
25753
26501
|
// src/features/codeium_intellij/data_collector.ts
|
|
25754
|
-
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
|
|
25755
26564
|
init_client_generates();
|
|
25756
26565
|
init_urlParser2();
|
|
25757
26566
|
|
|
25758
26567
|
// src/features/codeium_intellij/codeium_language_server_grpc_client.ts
|
|
25759
|
-
import
|
|
26568
|
+
import path27 from "path";
|
|
25760
26569
|
import * as grpc from "@grpc/grpc-js";
|
|
25761
26570
|
import * as protoLoader from "@grpc/proto-loader";
|
|
25762
|
-
var PROTO_PATH =
|
|
26571
|
+
var PROTO_PATH = path27.join(
|
|
25763
26572
|
getModuleRootDir(),
|
|
25764
26573
|
"src/features/codeium_intellij/proto/exa/language_server_pb/language_server.proto"
|
|
25765
26574
|
);
|
|
@@ -25771,7 +26580,7 @@ function loadProto() {
|
|
|
25771
26580
|
defaults: true,
|
|
25772
26581
|
oneofs: true,
|
|
25773
26582
|
includeDirs: [
|
|
25774
|
-
|
|
26583
|
+
path27.join(getModuleRootDir(), "src/features/codeium_intellij/proto")
|
|
25775
26584
|
]
|
|
25776
26585
|
});
|
|
25777
26586
|
return grpc.loadPackageDefinition(
|
|
@@ -25825,30 +26634,30 @@ async function getGrpcClient(port, csrf3) {
|
|
|
25825
26634
|
}
|
|
25826
26635
|
|
|
25827
26636
|
// src/features/codeium_intellij/parse_intellij_logs.ts
|
|
25828
|
-
import
|
|
25829
|
-
import
|
|
25830
|
-
import
|
|
26637
|
+
import fs27 from "fs";
|
|
26638
|
+
import os14 from "os";
|
|
26639
|
+
import path28 from "path";
|
|
25831
26640
|
function getLogsDir() {
|
|
25832
26641
|
if (process.platform === "darwin") {
|
|
25833
|
-
return
|
|
26642
|
+
return path28.join(os14.homedir(), "Library/Logs/JetBrains");
|
|
25834
26643
|
} else if (process.platform === "win32") {
|
|
25835
|
-
return
|
|
25836
|
-
process.env["LOCALAPPDATA"] ||
|
|
26644
|
+
return path28.join(
|
|
26645
|
+
process.env["LOCALAPPDATA"] || path28.join(os14.homedir(), "AppData/Local"),
|
|
25837
26646
|
"JetBrains"
|
|
25838
26647
|
);
|
|
25839
26648
|
} else {
|
|
25840
|
-
return
|
|
26649
|
+
return path28.join(os14.homedir(), ".cache/JetBrains");
|
|
25841
26650
|
}
|
|
25842
26651
|
}
|
|
25843
26652
|
function parseIdeLogDir(ideLogDir) {
|
|
25844
|
-
const logFiles =
|
|
26653
|
+
const logFiles = fs27.readdirSync(ideLogDir).filter((f) => /^idea(\.\d+)?\.log$/.test(f)).map((f) => ({
|
|
25845
26654
|
name: f,
|
|
25846
|
-
mtime:
|
|
26655
|
+
mtime: fs27.statSync(path28.join(ideLogDir, f)).mtimeMs
|
|
25847
26656
|
})).sort((a, b) => a.mtime - b.mtime).map((f) => f.name);
|
|
25848
26657
|
let latestCsrf = null;
|
|
25849
26658
|
let latestPort = null;
|
|
25850
26659
|
for (const logFile of logFiles) {
|
|
25851
|
-
const lines =
|
|
26660
|
+
const lines = fs27.readFileSync(path28.join(ideLogDir, logFile), "utf-8").split("\n");
|
|
25852
26661
|
for (const line of lines) {
|
|
25853
26662
|
if (!line.includes(
|
|
25854
26663
|
"com.codeium.intellij.language_server.LanguageServerProcessHandler"
|
|
@@ -25874,13 +26683,13 @@ function parseIdeLogDir(ideLogDir) {
|
|
|
25874
26683
|
function findRunningCodeiumLanguageServers() {
|
|
25875
26684
|
const results = [];
|
|
25876
26685
|
const logsDir = getLogsDir();
|
|
25877
|
-
if (!
|
|
25878
|
-
for (const ide of
|
|
25879
|
-
let ideLogDir =
|
|
26686
|
+
if (!fs27.existsSync(logsDir)) return results;
|
|
26687
|
+
for (const ide of fs27.readdirSync(logsDir)) {
|
|
26688
|
+
let ideLogDir = path28.join(logsDir, ide);
|
|
25880
26689
|
if (process.platform !== "darwin") {
|
|
25881
|
-
ideLogDir =
|
|
26690
|
+
ideLogDir = path28.join(ideLogDir, "log");
|
|
25882
26691
|
}
|
|
25883
|
-
if (!
|
|
26692
|
+
if (!fs27.existsSync(ideLogDir) || !fs27.statSync(ideLogDir).isDirectory()) {
|
|
25884
26693
|
continue;
|
|
25885
26694
|
}
|
|
25886
26695
|
const result = parseIdeLogDir(ideLogDir);
|
|
@@ -25892,8 +26701,8 @@ function findRunningCodeiumLanguageServers() {
|
|
|
25892
26701
|
}
|
|
25893
26702
|
|
|
25894
26703
|
// src/features/codeium_intellij/data_collector.ts
|
|
25895
|
-
var
|
|
25896
|
-
trajectory_id:
|
|
26704
|
+
var HookDataSchema = z46.object({
|
|
26705
|
+
trajectory_id: z46.string()
|
|
25897
26706
|
});
|
|
25898
26707
|
async function processAndUploadHookData() {
|
|
25899
26708
|
const tracePayload = await getTraceDataForHook();
|
|
@@ -25921,12 +26730,12 @@ async function processAndUploadHookData() {
|
|
|
25921
26730
|
console.warn("Failed to upload trace data:", e);
|
|
25922
26731
|
}
|
|
25923
26732
|
}
|
|
25924
|
-
function
|
|
25925
|
-
return
|
|
26733
|
+
function validateHookData(data) {
|
|
26734
|
+
return HookDataSchema.parse(data);
|
|
25926
26735
|
}
|
|
25927
26736
|
async function getTraceDataForHook() {
|
|
25928
26737
|
const rawData = await readStdinData();
|
|
25929
|
-
const hookData =
|
|
26738
|
+
const hookData = validateHookData(rawData);
|
|
25930
26739
|
return await getTraceDataForTrajectory(hookData.trajectory_id);
|
|
25931
26740
|
}
|
|
25932
26741
|
async function getTraceDataForTrajectory(trajectoryId) {
|
|
@@ -26060,11 +26869,11 @@ function processChatStepCodeAction(step) {
|
|
|
26060
26869
|
|
|
26061
26870
|
// src/features/codeium_intellij/install_hook.ts
|
|
26062
26871
|
import fsPromises5 from "fs/promises";
|
|
26063
|
-
import
|
|
26064
|
-
import
|
|
26872
|
+
import os15 from "os";
|
|
26873
|
+
import path29 from "path";
|
|
26065
26874
|
import chalk14 from "chalk";
|
|
26066
26875
|
function getCodeiumHooksPath() {
|
|
26067
|
-
return
|
|
26876
|
+
return path29.join(os15.homedir(), ".codeium", "hooks.json");
|
|
26068
26877
|
}
|
|
26069
26878
|
async function readCodeiumHooks() {
|
|
26070
26879
|
const hooksPath = getCodeiumHooksPath();
|
|
@@ -26077,7 +26886,7 @@ async function readCodeiumHooks() {
|
|
|
26077
26886
|
}
|
|
26078
26887
|
async function writeCodeiumHooks(config2) {
|
|
26079
26888
|
const hooksPath = getCodeiumHooksPath();
|
|
26080
|
-
const dir =
|
|
26889
|
+
const dir = path29.dirname(hooksPath);
|
|
26081
26890
|
await fsPromises5.mkdir(dir, { recursive: true });
|
|
26082
26891
|
await fsPromises5.writeFile(
|
|
26083
26892
|
hooksPath,
|
|
@@ -26248,9 +27057,16 @@ var parseArgs = async (args) => {
|
|
|
26248
27057
|
claudeCodeInstallHookHandler
|
|
26249
27058
|
).command(
|
|
26250
27059
|
mobbCliCommand.claudeCodeProcessHook,
|
|
26251
|
-
chalk15.bold("Process Claude Code hook data
|
|
27060
|
+
chalk15.bold("Process Claude Code hook data (legacy \u2014 spawns daemon)."),
|
|
26252
27061
|
claudeCodeProcessHookBuilder,
|
|
26253
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
|
|
26254
27070
|
).command(
|
|
26255
27071
|
mobbCliCommand.windsurfIntellijInstallHook,
|
|
26256
27072
|
chalk15.bold("Install Windsurf IntelliJ hooks for data collection."),
|