mobbdev 1.2.67 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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 stat = await fsPromises.stat(fullPath);
3564
- if (stat.isDirectory()) {
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: stat.mtime.getTime(),
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 debug = Debug("mobbdev:scm:ado");
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 path27 = [
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(`${path27}?${params2}`, origin).toString();
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 path27 = entry.item.path;
7146
- return path27.startsWith("/") ? path27.slice(1) : path27;
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 debug2 = Debug2("scm:bitbucket");
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 pLimit3 from "p-limit";
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 = pLimit3(GITLAB_API_CONCURRENCY);
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 = pLimit3(GITLAB_API_CONCURRENCY);
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
- // src/features/analysis/scm/gitlab/GitlabSCMLib.ts
10656
- init_client_generates();
10657
- var GitlabSCMLib = class extends SCMLib {
10658
- constructor(url, accessToken, scmOrg) {
10659
- super(url, accessToken, scmOrg);
10660
- }
10661
- async createSubmitRequest(params) {
10662
- this._validateAccessTokenAndUrl();
10663
- const { targetBranchName, sourceBranchName, title, body } = params;
10664
- return String(
10665
- await createMergeRequest({
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
- !skipPrompts && console.log(
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: path27,
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: path27,
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: path27,
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: path27,
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 os14 = type();
15090
- const cxFileName = os14 === "Windows_NT" ? "cx.exe" : "cx";
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 stat = fs11.statSync(resolvedPath);
16008
- if (!stat.isDirectory()) {
16603
+ const stat2 = fs11.statSync(resolvedPath);
16604
+ if (!stat2.isDirectory()) {
16009
16605
  throw new CliError(
16010
16606
  "Local skill input must be a directory containing SKILL.md"
16011
16607
  );
@@ -16399,13 +16995,107 @@ async function analyzeHandler(args) {
16399
16995
  await analyze(args, { skipPrompts: args.yes });
16400
16996
  }
16401
16997
 
16998
+ // src/args/commands/claude_code.ts
16999
+ import { spawn } from "child_process";
17000
+
17001
+ // src/features/claude_code/daemon.ts
17002
+ import path17 from "path";
17003
+ import { setTimeout as sleep2 } from "timers/promises";
17004
+
17005
+ // src/features/claude_code/daemon_pid_file.ts
17006
+ import fs13 from "fs";
17007
+ import os4 from "os";
17008
+ import path13 from "path";
17009
+
17010
+ // src/features/claude_code/data_collector_constants.ts
17011
+ var CC_VERSION_CACHE_KEY = "claudeCode.detectedCCVersion";
17012
+ var CC_VERSION_CLI_KEY = "claudeCode.detectedCCVersionCli";
17013
+ var GQL_AUTH_TIMEOUT_MS = 15e3;
17014
+ var STALE_KEY_MAX_AGE_MS = 14 * 24 * 60 * 60 * 1e3;
17015
+ var CLEANUP_INTERVAL_MS = 24 * 60 * 60 * 1e3;
17016
+ var DAEMON_TTL_MS = 30 * 60 * 1e3;
17017
+ var DAEMON_POLL_INTERVAL_MS = 1e4;
17018
+ var HEARTBEAT_STALE_MS = 3e4;
17019
+ var TRANSCRIPT_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
17020
+ var DAEMON_CHUNK_SIZE = 50;
17021
+
17022
+ // src/features/claude_code/daemon_pid_file.ts
17023
+ function getMobbdevDir() {
17024
+ return path13.join(os4.homedir(), ".mobbdev");
17025
+ }
17026
+ function getDaemonCheckScriptPath() {
17027
+ return path13.join(getMobbdevDir(), "daemon-check.js");
17028
+ }
17029
+ var DaemonPidFile = class {
17030
+ constructor() {
17031
+ __publicField(this, "data", null);
17032
+ }
17033
+ get filePath() {
17034
+ return path13.join(getMobbdevDir(), "daemon.pid");
17035
+ }
17036
+ /** Ensure ~/.mobbdev/ directory exists. */
17037
+ ensureDir() {
17038
+ fs13.mkdirSync(getMobbdevDir(), { recursive: true });
17039
+ }
17040
+ /** Read the PID file from disk. Returns the parsed data or null. */
17041
+ read() {
17042
+ try {
17043
+ const raw = fs13.readFileSync(this.filePath, "utf8");
17044
+ const parsed = JSON.parse(raw);
17045
+ if (typeof parsed.pid !== "number" || typeof parsed.startedAt !== "number" || typeof parsed.heartbeat !== "number") {
17046
+ this.data = null;
17047
+ } else {
17048
+ this.data = parsed;
17049
+ }
17050
+ } catch {
17051
+ this.data = null;
17052
+ }
17053
+ return this.data;
17054
+ }
17055
+ /** Write a new PID file for the given process id. */
17056
+ write(pid, version) {
17057
+ this.data = {
17058
+ pid,
17059
+ startedAt: Date.now(),
17060
+ heartbeat: Date.now(),
17061
+ version
17062
+ };
17063
+ fs13.writeFileSync(this.filePath, JSON.stringify(this.data), "utf8");
17064
+ }
17065
+ /** Update the heartbeat timestamp of the current PID file. */
17066
+ updateHeartbeat() {
17067
+ if (!this.data) this.read();
17068
+ if (!this.data) return;
17069
+ this.data.heartbeat = Date.now();
17070
+ fs13.writeFileSync(this.filePath, JSON.stringify(this.data), "utf8");
17071
+ }
17072
+ /** Check whether the previously read PID data represents a live daemon. */
17073
+ isAlive() {
17074
+ if (!this.data) return false;
17075
+ if (Date.now() - this.data.heartbeat > HEARTBEAT_STALE_MS) return false;
17076
+ try {
17077
+ process.kill(this.data.pid, 0);
17078
+ return true;
17079
+ } catch {
17080
+ return false;
17081
+ }
17082
+ }
17083
+ /** Remove the PID file from disk (best-effort). */
17084
+ remove() {
17085
+ this.data = null;
17086
+ try {
17087
+ fs13.unlinkSync(this.filePath);
17088
+ } catch {
17089
+ }
17090
+ }
17091
+ };
17092
+
16402
17093
  // src/features/claude_code/data_collector.ts
16403
17094
  import { execFile } from "child_process";
16404
17095
  import { createHash as createHash2 } from "crypto";
16405
17096
  import { access, open as open4, readdir, readFile, unlink } from "fs/promises";
16406
17097
  import path14 from "path";
16407
17098
  import { promisify } from "util";
16408
- import { z as z33 } from "zod";
16409
17099
  init_client_generates();
16410
17100
 
16411
17101
  // src/utils/shared-logger/create-logger.ts
@@ -16485,8 +17175,8 @@ function createConfigstoreStream(store, opts) {
16485
17175
  heartbeatBuffer.length = 0;
16486
17176
  }
16487
17177
  }
16488
- function setScopePath(path27) {
16489
- scopePath = path27;
17178
+ function setScopePath(path30) {
17179
+ scopePath = path30;
16490
17180
  }
16491
17181
  return { writable, flush, setScopePath };
16492
17182
  }
@@ -16568,22 +17258,22 @@ function createDdBatch(config2) {
16568
17258
 
16569
17259
  // src/utils/shared-logger/hostname.ts
16570
17260
  import { createHash } from "crypto";
16571
- import os4 from "os";
17261
+ import os5 from "os";
16572
17262
  function hashString(input) {
16573
17263
  return createHash("sha256").update(input).digest("hex").slice(0, 16);
16574
17264
  }
16575
17265
  function getPlainHostname() {
16576
17266
  try {
16577
- return `${os4.userInfo().username}@${os4.hostname()}`;
17267
+ return `${os5.userInfo().username}@${os5.hostname()}`;
16578
17268
  } catch {
16579
- return `unknown@${os4.hostname()}`;
17269
+ return `unknown@${os5.hostname()}`;
16580
17270
  }
16581
17271
  }
16582
17272
  function getHashedHostname() {
16583
17273
  try {
16584
- return `${hashString(os4.userInfo().username)}@${hashString(os4.hostname())}`;
17274
+ return `${hashString(os5.userInfo().username)}@${hashString(os5.hostname())}`;
16585
17275
  } catch {
16586
- return `unknown@${hashString(os4.hostname())}`;
17276
+ return `unknown@${hashString(os5.hostname())}`;
16587
17277
  }
16588
17278
  }
16589
17279
 
@@ -16596,15 +17286,19 @@ function createLogger(config2) {
16596
17286
  maxLogs,
16597
17287
  maxHeartbeat,
16598
17288
  dd,
16599
- additionalStreams = []
17289
+ additionalStreams = [],
17290
+ enableConfigstore = true
16600
17291
  } = config2;
16601
- const store = new Configstore2(namespace, {});
16602
- const csStream = createConfigstoreStream(store, {
16603
- buffered,
16604
- scopePath,
16605
- maxLogs,
16606
- maxHeartbeat
16607
- });
17292
+ let csStream = null;
17293
+ if (enableConfigstore) {
17294
+ const store = new Configstore2(namespace, {});
17295
+ csStream = createConfigstoreStream(store, {
17296
+ buffered,
17297
+ scopePath,
17298
+ maxLogs,
17299
+ maxHeartbeat
17300
+ });
17301
+ }
16608
17302
  let ddBatch = null;
16609
17303
  if (dd) {
16610
17304
  const hostname = dd.hostnameMode === "hashed" ? getHashedHostname() : getPlainHostname();
@@ -16619,9 +17313,10 @@ function createLogger(config2) {
16619
17313
  };
16620
17314
  ddBatch = createDdBatch(ddConfig);
16621
17315
  }
16622
- const streams = [
16623
- { stream: csStream.writable, level: "info" }
16624
- ];
17316
+ const streams = [];
17317
+ if (csStream) {
17318
+ streams.push({ stream: csStream.writable, level: "info" });
17319
+ }
16625
17320
  if (ddBatch) {
16626
17321
  streams.push({ stream: ddBatch.createPinoStream(), level: "info" });
16627
17322
  }
@@ -16633,6 +17328,11 @@ function createLogger(config2) {
16633
17328
  }
16634
17329
  const pinoLogger = pino(
16635
17330
  {
17331
+ serializers: {
17332
+ err: pino.stdSerializers.err,
17333
+ error: pino.stdSerializers.err,
17334
+ e: pino.stdSerializers.err
17335
+ },
16636
17336
  formatters: {
16637
17337
  level: (label) => ({ level: label })
16638
17338
  }
@@ -16663,8 +17363,8 @@ function createLogger(config2) {
16663
17363
  throw err;
16664
17364
  }
16665
17365
  }
16666
- function flushLogs2() {
16667
- csStream.flush();
17366
+ function flushLogs() {
17367
+ csStream?.flush();
16668
17368
  }
16669
17369
  async function flushDdAsync() {
16670
17370
  if (ddBatch) {
@@ -16688,17 +17388,19 @@ function createLogger(config2) {
16688
17388
  debug: debug23,
16689
17389
  heartbeat,
16690
17390
  timed,
16691
- flushLogs: flushLogs2,
17391
+ flushLogs,
16692
17392
  flushDdAsync,
16693
17393
  disposeDd,
16694
- setScopePath: csStream.setScopePath,
17394
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
17395
+ setScopePath: csStream?.setScopePath ?? (() => {
17396
+ }),
16695
17397
  updateDdTags
16696
17398
  };
16697
17399
  }
16698
17400
 
16699
17401
  // src/features/claude_code/hook_logger.ts
16700
17402
  var DD_RUM_TOKEN = true ? "pubf59c0182545bfb4c299175119f1abf9b" : "";
16701
- var CLI_VERSION = true ? "1.2.67" : "unknown";
17403
+ var CLI_VERSION = true ? "1.3.0" : "unknown";
16702
17404
  var NAMESPACE = "mobbdev-claude-code-hook-logs";
16703
17405
  var claudeCodeVersion;
16704
17406
  function buildDdTags() {
@@ -16708,10 +17410,11 @@ function buildDdTags() {
16708
17410
  }
16709
17411
  return tags.join(",");
16710
17412
  }
16711
- function createHookLogger(scopePath) {
17413
+ function createHookLogger(opts) {
16712
17414
  return createLogger({
16713
17415
  namespace: NAMESPACE,
16714
- scopePath,
17416
+ scopePath: opts?.scopePath,
17417
+ enableConfigstore: opts?.enableConfigstore,
16715
17418
  dd: {
16716
17419
  apiKey: DD_RUM_TOKEN,
16717
17420
  ddsource: "mobbdev-cli",
@@ -16724,6 +17427,7 @@ function createHookLogger(scopePath) {
16724
17427
  }
16725
17428
  var logger = createHookLogger();
16726
17429
  var activeScopedLoggers = [];
17430
+ var scopedLoggerCache = /* @__PURE__ */ new Map();
16727
17431
  var hookLog = logger;
16728
17432
  function setClaudeCodeVersion(version) {
16729
17433
  claudeCodeVersion = version;
@@ -16732,302 +17436,83 @@ function setClaudeCodeVersion(version) {
16732
17436
  function getClaudeCodeVersion() {
16733
17437
  return claudeCodeVersion;
16734
17438
  }
16735
- function flushLogs() {
16736
- logger.flushLogs();
16737
- for (const scoped of activeScopedLoggers) {
16738
- scoped.flushLogs();
16739
- }
16740
- }
16741
17439
  async function flushDdLogs() {
16742
17440
  await logger.flushDdAsync();
16743
17441
  for (const scoped of activeScopedLoggers) {
16744
17442
  await scoped.flushDdAsync();
16745
17443
  }
16746
- activeScopedLoggers.length = 0;
16747
17444
  }
16748
- function createScopedHookLog(scopePath) {
16749
- const scoped = createHookLogger(scopePath);
17445
+ function createScopedHookLog(scopePath, opts) {
17446
+ const cached = scopedLoggerCache.get(scopePath);
17447
+ if (cached) return cached;
17448
+ const scoped = createHookLogger({
17449
+ scopePath,
17450
+ enableConfigstore: opts?.daemonMode ? false : void 0
17451
+ });
17452
+ scopedLoggerCache.set(scopePath, scoped);
16750
17453
  activeScopedLoggers.push(scoped);
16751
17454
  return scoped;
16752
17455
  }
16753
17456
 
16754
- // src/features/claude_code/install_hook.ts
16755
- import fsPromises4 from "fs/promises";
16756
- import os5 from "os";
16757
- import path13 from "path";
16758
- import chalk11 from "chalk";
16759
- var CLAUDE_SETTINGS_PATH = path13.join(os5.homedir(), ".claude", "settings.json");
16760
- var RECOMMENDED_MATCHER = "Write|Edit";
16761
- async function claudeSettingsExists() {
17457
+ // src/features/claude_code/data_collector.ts
17458
+ var execFileAsync = promisify(execFile);
17459
+ async function detectClaudeCodeVersion() {
17460
+ const cachedCliVersion = configStore.get(CC_VERSION_CLI_KEY);
17461
+ if (cachedCliVersion === packageJson.version) {
17462
+ return configStore.get(CC_VERSION_CACHE_KEY);
17463
+ }
16762
17464
  try {
16763
- await fsPromises4.access(CLAUDE_SETTINGS_PATH);
16764
- return true;
17465
+ const { stdout: stdout2 } = await execFileAsync("claude", ["--version"], {
17466
+ timeout: 3e3,
17467
+ encoding: "utf-8"
17468
+ });
17469
+ const version = stdout2.trim().split(/\s/)[0] || stdout2.trim();
17470
+ configStore.set(CC_VERSION_CACHE_KEY, version);
17471
+ configStore.set(CC_VERSION_CLI_KEY, packageJson.version);
17472
+ return version;
16765
17473
  } catch {
16766
- return false;
17474
+ configStore.set(CC_VERSION_CACHE_KEY, void 0);
17475
+ configStore.set(CC_VERSION_CLI_KEY, packageJson.version);
17476
+ return void 0;
16767
17477
  }
16768
17478
  }
16769
- async function readClaudeSettings() {
16770
- const settingsContent = await fsPromises4.readFile(
16771
- CLAUDE_SETTINGS_PATH,
16772
- "utf-8"
16773
- );
16774
- return JSON.parse(settingsContent);
17479
+ function generateSyntheticId(sessionId, timestamp, type2, lineIndex) {
17480
+ const input = `${sessionId ?? ""}:${timestamp ?? ""}:${type2 ?? ""}:${lineIndex}`;
17481
+ const hash = createHash2("sha256").update(input).digest("hex").slice(0, 16);
17482
+ return `synth:${hash}`;
16775
17483
  }
16776
- async function writeClaudeSettings(settings) {
16777
- await fsPromises4.writeFile(
16778
- CLAUDE_SETTINGS_PATH,
16779
- JSON.stringify(settings, null, 2),
16780
- "utf-8"
16781
- );
17484
+ function getCursorKey(transcriptPath) {
17485
+ const hash = createHash2("sha256").update(transcriptPath).digest("hex").slice(0, 12);
17486
+ return `cursor.${hash}`;
16782
17487
  }
16783
- async function autoUpgradeMatcherIfStale() {
17488
+ async function resolveTranscriptPath(transcriptPath, sessionId) {
16784
17489
  try {
16785
- if (!await claudeSettingsExists()) return false;
16786
- const settings = await readClaudeSettings();
16787
- const hooks = settings.hooks?.PostToolUse;
16788
- if (!hooks) return false;
16789
- let upgraded = false;
16790
- for (const hook of hooks) {
16791
- const isMobbHook = hook.hooks.some(
16792
- (h) => h.command?.includes("claude-code-process-hook")
16793
- );
16794
- if (!isMobbHook) continue;
16795
- if (hook.matcher !== RECOMMENDED_MATCHER) {
16796
- hook.matcher = RECOMMENDED_MATCHER;
16797
- upgraded = true;
16798
- }
16799
- for (const h of hook.hooks) {
16800
- if (!h.async) {
16801
- h.async = true;
16802
- upgraded = true;
16803
- }
16804
- }
16805
- }
16806
- if (upgraded) {
16807
- await writeClaudeSettings(settings);
16808
- }
16809
- return upgraded;
17490
+ await access(transcriptPath);
17491
+ return transcriptPath;
16810
17492
  } catch {
16811
- return false;
16812
17493
  }
16813
- }
16814
- async function installMobbHooks(options = {}) {
16815
- console.log(chalk11.blue("Installing Mobb hooks in Claude Code settings..."));
16816
- if (!await claudeSettingsExists()) {
16817
- console.log(chalk11.red("\u274C Claude Code settings file not found"));
16818
- console.log(chalk11.yellow(`Expected location: ${CLAUDE_SETTINGS_PATH}`));
16819
- console.log(chalk11.yellow("Is Claude Code installed on your system?"));
16820
- console.log(chalk11.yellow("Please install Claude Code and try again."));
16821
- throw new Error(
16822
- "Claude Code settings file not found. Is Claude Code installed?"
16823
- );
16824
- }
16825
- const settings = await readClaudeSettings();
16826
- if (!settings.hooks) {
16827
- settings.hooks = {};
16828
- }
16829
- if (!settings.hooks.PostToolUse) {
16830
- settings.hooks.PostToolUse = [];
16831
- }
16832
- let command = "npx --yes mobbdev@latest claude-code-process-hook";
16833
- if (options.saveEnv) {
16834
- const envVars = [];
16835
- if (process.env["WEB_APP_URL"]) {
16836
- envVars.push(`WEB_APP_URL="${process.env["WEB_APP_URL"]}"`);
16837
- }
16838
- if (process.env["API_URL"]) {
16839
- envVars.push(`API_URL="${process.env["API_URL"]}"`);
16840
- }
16841
- if (envVars.length > 0) {
16842
- command = `${envVars.join(" ")} ${command}`;
16843
- console.log(
16844
- chalk11.blue(
16845
- `Adding environment variables to hook command: ${envVars.join(", ")}`
16846
- )
16847
- );
16848
- }
16849
- }
16850
- const mobbHookConfig = {
16851
- // Only fire on tools that indicate meaningful work — skip high-frequency
16852
- // read-only tools (Grep, Glob, WebSearch, WebFetch) to reduce CPU overhead
16853
- // from process startup (~1.7s user CPU per invocation).
16854
- matcher: RECOMMENDED_MATCHER,
16855
- hooks: [
16856
- {
16857
- type: "command",
16858
- command,
16859
- async: true
16860
- }
16861
- ]
16862
- };
16863
- const existingHookIndex = settings.hooks.PostToolUse.findIndex(
16864
- (hook) => hook.hooks.some(
16865
- (h) => h.command?.includes("mobbdev@latest claude-code-process-hook")
16866
- )
16867
- );
16868
- if (existingHookIndex >= 0) {
16869
- console.log(chalk11.yellow("Mobb hook already exists, updating..."));
16870
- settings.hooks.PostToolUse[existingHookIndex] = mobbHookConfig;
16871
- } else {
16872
- console.log(chalk11.green("Adding new Mobb hook..."));
16873
- settings.hooks.PostToolUse.push(mobbHookConfig);
16874
- }
16875
- await writeClaudeSettings(settings);
16876
- console.log(
16877
- chalk11.green(
16878
- `\u2705 Mobb hooks ${options.saveEnv ? "and environment variables " : ""}installed successfully in ${CLAUDE_SETTINGS_PATH}`
16879
- )
16880
- );
16881
- }
16882
-
16883
- // src/features/claude_code/data_collector.ts
16884
- var CC_VERSION_CACHE_KEY = "claudeCode.detectedCCVersion";
16885
- var CC_VERSION_CLI_KEY = "claudeCode.detectedCCVersionCli";
16886
- var GLOBAL_COOLDOWN_MS = 5e3;
16887
- var HOOK_COOLDOWN_MS = 15e3;
16888
- var ACTIVE_LOCK_TTL_MS = 6e4;
16889
- var GQL_AUTH_TIMEOUT_MS = 15e3;
16890
- var STALE_KEY_MAX_AGE_MS = 14 * 24 * 60 * 60 * 1e3;
16891
- var CLEANUP_INTERVAL_MS = 24 * 60 * 60 * 1e3;
16892
- var MAX_ENTRIES_PER_INVOCATION = 50;
16893
- var COOLDOWN_KEY = "lastHookRunAt";
16894
- var ACTIVE_KEY = "hookActiveAt";
16895
- var HookDataSchema = z33.object({
16896
- session_id: z33.string().nullish(),
16897
- transcript_path: z33.string().nullish(),
16898
- cwd: z33.string().nullish(),
16899
- hook_event_name: z33.string(),
16900
- tool_name: z33.string(),
16901
- tool_input: z33.unknown(),
16902
- tool_response: z33.unknown()
16903
- });
16904
- var execFileAsync = promisify(execFile);
16905
- async function detectClaudeCodeVersion() {
16906
- const cachedCliVersion = configStore.get(CC_VERSION_CLI_KEY);
16907
- if (cachedCliVersion === packageJson.version) {
16908
- return configStore.get(CC_VERSION_CACHE_KEY);
16909
- }
16910
- try {
16911
- const { stdout: stdout2 } = await execFileAsync("claude", ["--version"], {
16912
- timeout: 3e3,
16913
- encoding: "utf-8"
16914
- });
16915
- const version = stdout2.trim().split(/\s/)[0] || stdout2.trim();
16916
- configStore.set(CC_VERSION_CACHE_KEY, version);
16917
- configStore.set(CC_VERSION_CLI_KEY, packageJson.version);
16918
- return version;
16919
- } catch {
16920
- configStore.set(CC_VERSION_CACHE_KEY, void 0);
16921
- configStore.set(CC_VERSION_CLI_KEY, packageJson.version);
16922
- return void 0;
16923
- }
16924
- }
16925
- var STDIN_TIMEOUT_MS = 1e4;
16926
- async function readStdinData() {
16927
- hookLog.debug("Reading stdin data");
16928
- return new Promise((resolve, reject) => {
16929
- let inputData = "";
16930
- let settled = false;
16931
- const timer = setTimeout(() => {
16932
- if (!settled) {
16933
- settled = true;
16934
- process.stdin.destroy();
16935
- reject(new Error("Timed out reading from stdin"));
16936
- }
16937
- }, STDIN_TIMEOUT_MS);
16938
- process.stdin.setEncoding("utf-8");
16939
- process.stdin.on("data", (chunk) => {
16940
- inputData += chunk;
16941
- });
16942
- process.stdin.on("end", () => {
16943
- if (settled) return;
16944
- settled = true;
16945
- clearTimeout(timer);
16946
- try {
16947
- const parsedData = JSON.parse(inputData);
16948
- hookLog.debug(
16949
- {
16950
- data: { keys: Object.keys(parsedData) }
16951
- },
16952
- "Parsed stdin data"
16953
- );
16954
- resolve(parsedData);
16955
- } catch (error) {
16956
- const msg = `Failed to parse JSON from stdin: ${error.message}`;
16957
- hookLog.error(msg);
16958
- reject(new Error(msg));
16959
- }
16960
- });
16961
- process.stdin.on("error", (error) => {
16962
- if (settled) return;
16963
- settled = true;
16964
- clearTimeout(timer);
16965
- hookLog.error(
16966
- { data: { error: error.message } },
16967
- "Error reading from stdin"
16968
- );
16969
- reject(new Error(`Error reading from stdin: ${error.message}`));
16970
- });
16971
- });
16972
- }
16973
- function validateHookData(data) {
16974
- return HookDataSchema.parse(data);
16975
- }
16976
- async function extractSessionIdFromTranscript(transcriptPath) {
16977
- try {
16978
- const fh = await open4(transcriptPath, "r");
16979
- try {
16980
- const buf = Buffer.alloc(4096);
16981
- const { bytesRead } = await fh.read(buf, 0, 4096, 0);
16982
- const chunk = buf.toString("utf-8", 0, bytesRead);
16983
- const firstLine = chunk.split("\n").find((l) => l.trim().length > 0);
16984
- if (!firstLine) return null;
16985
- const entry = JSON.parse(firstLine);
16986
- return entry.sessionId ?? null;
16987
- } finally {
16988
- await fh.close();
16989
- }
16990
- } catch {
16991
- return null;
16992
- }
16993
- }
16994
- function generateSyntheticId(sessionId, timestamp, type2, lineIndex) {
16995
- const input = `${sessionId ?? ""}:${timestamp ?? ""}:${type2 ?? ""}:${lineIndex}`;
16996
- const hash = createHash2("sha256").update(input).digest("hex").slice(0, 16);
16997
- return `synth:${hash}`;
16998
- }
16999
- function getCursorKey(transcriptPath) {
17000
- const hash = createHash2("sha256").update(transcriptPath).digest("hex").slice(0, 12);
17001
- return `cursor.${hash}`;
17002
- }
17003
- async function resolveTranscriptPath(transcriptPath, sessionId) {
17004
- try {
17005
- await access(transcriptPath);
17006
- return transcriptPath;
17007
- } catch {
17008
- }
17009
- const filename = path14.basename(transcriptPath);
17010
- const dirName = path14.basename(path14.dirname(transcriptPath));
17011
- const projectsDir = path14.dirname(path14.dirname(transcriptPath));
17012
- const baseDirName = dirName.replace(/[-.]claude-worktrees-.+$/, "");
17013
- if (baseDirName !== dirName) {
17014
- const candidate = path14.join(projectsDir, baseDirName, filename);
17015
- try {
17016
- await access(candidate);
17017
- hookLog.info(
17018
- {
17019
- data: {
17020
- original: transcriptPath,
17021
- resolved: candidate,
17022
- sessionId,
17023
- method: "worktree-strip"
17024
- }
17025
- },
17026
- "Transcript path resolved via fallback"
17027
- );
17028
- return candidate;
17029
- } catch {
17030
- }
17494
+ const filename = path14.basename(transcriptPath);
17495
+ const dirName = path14.basename(path14.dirname(transcriptPath));
17496
+ const projectsDir = path14.dirname(path14.dirname(transcriptPath));
17497
+ const baseDirName = dirName.replace(/[-.]claude-worktrees-.+$/, "");
17498
+ if (baseDirName !== dirName) {
17499
+ const candidate = path14.join(projectsDir, baseDirName, filename);
17500
+ try {
17501
+ await access(candidate);
17502
+ hookLog.info(
17503
+ {
17504
+ data: {
17505
+ original: transcriptPath,
17506
+ resolved: candidate,
17507
+ sessionId,
17508
+ method: "worktree-strip"
17509
+ }
17510
+ },
17511
+ "Transcript path resolved via fallback"
17512
+ );
17513
+ return candidate;
17514
+ } catch {
17515
+ }
17031
17516
  }
17032
17517
  try {
17033
17518
  const dirs = await readdir(projectsDir);
@@ -17055,7 +17540,7 @@ async function resolveTranscriptPath(transcriptPath, sessionId) {
17055
17540
  }
17056
17541
  return transcriptPath;
17057
17542
  }
17058
- async function readNewTranscriptEntries(transcriptPath, sessionId, sessionStore) {
17543
+ async function readNewTranscriptEntries(transcriptPath, sessionId, sessionStore, maxEntries = DAEMON_CHUNK_SIZE) {
17059
17544
  transcriptPath = await resolveTranscriptPath(transcriptPath, sessionId);
17060
17545
  const cursor = sessionStore.get(getCursorKey(transcriptPath));
17061
17546
  let content;
@@ -17064,9 +17549,9 @@ async function readNewTranscriptEntries(transcriptPath, sessionId, sessionStore)
17064
17549
  if (cursor?.byteOffset) {
17065
17550
  const fh = await open4(transcriptPath, "r");
17066
17551
  try {
17067
- const stat = await fh.stat();
17068
- fileSize = stat.size;
17069
- if (cursor.byteOffset >= stat.size) {
17552
+ const stat2 = await fh.stat();
17553
+ fileSize = stat2.size;
17554
+ if (cursor.byteOffset >= stat2.size) {
17070
17555
  hookLog.info({ data: { sessionId } }, "No new data in transcript file");
17071
17556
  return {
17072
17557
  entries: [],
@@ -17074,7 +17559,7 @@ async function readNewTranscriptEntries(transcriptPath, sessionId, sessionStore)
17074
17559
  resolvedTranscriptPath: transcriptPath
17075
17560
  };
17076
17561
  }
17077
- const buf = Buffer.alloc(stat.size - cursor.byteOffset);
17562
+ const buf = Buffer.alloc(stat2.size - cursor.byteOffset);
17078
17563
  await fh.read(buf, 0, buf.length, cursor.byteOffset);
17079
17564
  content = buf.toString("utf-8");
17080
17565
  } finally {
@@ -17109,7 +17594,7 @@ async function readNewTranscriptEntries(transcriptPath, sessionId, sessionStore)
17109
17594
  for (let i = 0; i < allLines.length; i++) {
17110
17595
  const line = allLines[i];
17111
17596
  const lineBytes = Buffer.byteLength(line, "utf-8") + (i < allLines.length - 1 ? 1 : 0);
17112
- if (parsed.length >= MAX_ENTRIES_PER_INVOCATION) break;
17597
+ if (parsed.length >= maxEntries) break;
17113
17598
  bytesConsumed += lineBytes;
17114
17599
  if (line.trim().length === 0) continue;
17115
17600
  try {
@@ -17127,7 +17612,7 @@ async function readNewTranscriptEntries(transcriptPath, sessionId, sessionStore)
17127
17612
  parsedLineIndex++;
17128
17613
  }
17129
17614
  const endByteOffset = startOffset + bytesConsumed;
17130
- const capped = parsed.length >= MAX_ENTRIES_PER_INVOCATION;
17615
+ const capped = parsed.length >= maxEntries;
17131
17616
  if (malformedLines > 0) {
17132
17617
  hookLog.warn(
17133
17618
  { data: { malformedLines, transcriptPath } },
@@ -17140,10 +17625,11 @@ async function readNewTranscriptEntries(transcriptPath, sessionId, sessionStore)
17140
17625
  data: {
17141
17626
  sessionId,
17142
17627
  entriesParsed: parsed.length,
17143
- totalLines: allLines.length
17628
+ totalLines: allLines.length,
17629
+ maxEntries
17144
17630
  }
17145
17631
  },
17146
- "Capped at MAX_ENTRIES_PER_INVOCATION, remaining entries deferred"
17632
+ "Capped at maxEntries, remaining entries deferred"
17147
17633
  );
17148
17634
  } else if (!cursor) {
17149
17635
  hookLog.info(
@@ -17223,14 +17709,13 @@ function filterEntries(entries) {
17223
17709
  });
17224
17710
  return { filtered, filteredOut: entries.length - filtered.length };
17225
17711
  }
17226
- async function cleanupStaleSessions(sessionStore) {
17712
+ async function cleanupStaleSessions(configDir) {
17227
17713
  const lastCleanup = configStore.get("claudeCode.lastCleanupAt");
17228
17714
  if (lastCleanup && Date.now() - lastCleanup < CLEANUP_INTERVAL_MS) {
17229
17715
  return;
17230
17716
  }
17231
17717
  const now = Date.now();
17232
17718
  const prefix = getSessionFilePrefix();
17233
- const configDir = path14.dirname(sessionStore.path);
17234
17719
  try {
17235
17720
  const files = await readdir(configDir);
17236
17721
  let deletedCount = 0;
@@ -17240,8 +17725,6 @@ async function cleanupStaleSessions(sessionStore) {
17240
17725
  try {
17241
17726
  const content = JSON.parse(await readFile(filePath, "utf-8"));
17242
17727
  let newest = 0;
17243
- const cooldown = content[COOLDOWN_KEY];
17244
- if (cooldown && cooldown > newest) newest = cooldown;
17245
17728
  const cursors = content["cursor"];
17246
17729
  if (cursors && typeof cursors === "object") {
17247
17730
  for (const val of Object.values(cursors)) {
@@ -17263,157 +17746,7 @@ async function cleanupStaleSessions(sessionStore) {
17263
17746
  }
17264
17747
  configStore.set("claudeCode.lastCleanupAt", now);
17265
17748
  }
17266
- async function processAndUploadTranscriptEntries() {
17267
- hookLog.info("Hook invoked");
17268
- const globalLastRun = configStore.get("claudeCode.globalLastHookRunAt");
17269
- const globalNow = Date.now();
17270
- if (globalLastRun && globalNow - globalLastRun < GLOBAL_COOLDOWN_MS) {
17271
- return { entriesUploaded: 0, entriesSkipped: 0, errors: 0 };
17272
- }
17273
- configStore.set("claudeCode.globalLastHookRunAt", globalNow);
17274
- const lastUpgradeVersion = configStore.get(
17275
- "claudeCode.matcherUpgradeVersion"
17276
- );
17277
- if (lastUpgradeVersion !== packageJson.version) {
17278
- const upgraded = await autoUpgradeMatcherIfStale();
17279
- configStore.set("claudeCode.matcherUpgradeVersion", packageJson.version);
17280
- if (upgraded) {
17281
- hookLog.info("Auto-upgraded hook matcher to reduce CPU usage");
17282
- }
17283
- }
17284
- try {
17285
- const ccVersion = await detectClaudeCodeVersion();
17286
- setClaudeCodeVersion(ccVersion);
17287
- } catch {
17288
- }
17289
- const rawData = await readStdinData();
17290
- const rawObj = rawData;
17291
- const hookData = (() => {
17292
- try {
17293
- return validateHookData(rawData);
17294
- } catch (err) {
17295
- hookLog.error(
17296
- {
17297
- data: {
17298
- hook_event_name: rawObj?.["hook_event_name"],
17299
- tool_name: rawObj?.["tool_name"],
17300
- session_id: rawObj?.["session_id"],
17301
- cwd: rawObj?.["cwd"],
17302
- keys: rawObj ? Object.keys(rawObj) : []
17303
- }
17304
- },
17305
- `Hook validation failed: ${err.message?.slice(0, 200)}`
17306
- );
17307
- throw err;
17308
- }
17309
- })();
17310
- if (!hookData.transcript_path) {
17311
- hookLog.warn(
17312
- {
17313
- data: {
17314
- hook_event_name: hookData.hook_event_name,
17315
- tool_name: hookData.tool_name,
17316
- session_id: hookData.session_id,
17317
- cwd: hookData.cwd
17318
- }
17319
- },
17320
- "Missing transcript_path \u2014 cannot process hook"
17321
- );
17322
- return { entriesUploaded: 0, entriesSkipped: 0, errors: 0 };
17323
- }
17324
- let sessionId = hookData.session_id;
17325
- if (!sessionId) {
17326
- sessionId = await extractSessionIdFromTranscript(hookData.transcript_path);
17327
- if (sessionId) {
17328
- hookLog.warn(
17329
- {
17330
- data: {
17331
- hook_event_name: hookData.hook_event_name,
17332
- tool_name: hookData.tool_name,
17333
- cwd: hookData.cwd,
17334
- extractedSessionId: sessionId
17335
- }
17336
- },
17337
- "Missing session_id in hook data \u2014 extracted from transcript"
17338
- );
17339
- } else {
17340
- hookLog.warn(
17341
- {
17342
- data: {
17343
- hook_event_name: hookData.hook_event_name,
17344
- tool_name: hookData.tool_name,
17345
- transcript_path: hookData.transcript_path
17346
- }
17347
- },
17348
- "Missing session_id and could not extract from transcript \u2014 cannot process hook"
17349
- );
17350
- return { entriesUploaded: 0, entriesSkipped: 0, errors: 0 };
17351
- }
17352
- }
17353
- if (!hookData.cwd) {
17354
- hookLog.warn(
17355
- {
17356
- data: {
17357
- hook_event_name: hookData.hook_event_name,
17358
- tool_name: hookData.tool_name,
17359
- session_id: sessionId
17360
- }
17361
- },
17362
- "Missing cwd in hook data \u2014 scoped logging and repo URL detection disabled"
17363
- );
17364
- }
17365
- const resolvedHookData = {
17366
- ...hookData,
17367
- session_id: sessionId,
17368
- transcript_path: hookData.transcript_path,
17369
- cwd: hookData.cwd ?? void 0
17370
- };
17371
- const sessionStore = createSessionConfigStore(resolvedHookData.session_id);
17372
- await cleanupStaleSessions(sessionStore);
17373
- const now = Date.now();
17374
- const lastRunAt = sessionStore.get(COOLDOWN_KEY);
17375
- if (lastRunAt && now - lastRunAt < HOOK_COOLDOWN_MS) {
17376
- return { entriesUploaded: 0, entriesSkipped: 0, errors: 0 };
17377
- }
17378
- const activeAt = sessionStore.get(ACTIVE_KEY);
17379
- if (activeAt && now - activeAt < ACTIVE_LOCK_TTL_MS) {
17380
- const activeDuration = now - activeAt;
17381
- if (activeDuration > HOOK_COOLDOWN_MS) {
17382
- hookLog.warn(
17383
- {
17384
- data: {
17385
- activeDurationMs: activeDuration,
17386
- sessionId: resolvedHookData.session_id
17387
- }
17388
- },
17389
- "Hook still active \u2014 possible slow upload or hung process"
17390
- );
17391
- }
17392
- return { entriesUploaded: 0, entriesSkipped: 0, errors: 0 };
17393
- }
17394
- sessionStore.set(ACTIVE_KEY, now);
17395
- sessionStore.set(COOLDOWN_KEY, now);
17396
- const log2 = createScopedHookLog(resolvedHookData.cwd ?? process.cwd());
17397
- log2.info(
17398
- {
17399
- data: {
17400
- sessionId: resolvedHookData.session_id,
17401
- toolName: resolvedHookData.tool_name,
17402
- hookEvent: resolvedHookData.hook_event_name,
17403
- cwd: resolvedHookData.cwd,
17404
- claudeCodeVersion: getClaudeCodeVersion()
17405
- }
17406
- },
17407
- "Hook data validated"
17408
- );
17409
- try {
17410
- return await processTranscript(resolvedHookData, sessionStore, log2);
17411
- } finally {
17412
- sessionStore.delete(ACTIVE_KEY);
17413
- log2.flushLogs();
17414
- }
17415
- }
17416
- async function processTranscript(hookData, sessionStore, log2) {
17749
+ async function processTranscript(input, sessionStore, log2, maxEntries = DAEMON_CHUNK_SIZE, gqlClientOverride) {
17417
17750
  const {
17418
17751
  entries: rawEntries,
17419
17752
  endByteOffset,
@@ -17421,9 +17754,10 @@ async function processTranscript(hookData, sessionStore, log2) {
17421
17754
  } = await log2.timed(
17422
17755
  "Read transcript",
17423
17756
  () => readNewTranscriptEntries(
17424
- hookData.transcript_path,
17425
- hookData.session_id,
17426
- sessionStore
17757
+ input.transcript_path,
17758
+ input.session_id,
17759
+ sessionStore,
17760
+ maxEntries
17427
17761
  )
17428
17762
  );
17429
17763
  const cursorKey = getCursorKey(resolvedTranscriptPath);
@@ -17456,30 +17790,26 @@ async function processTranscript(hookData, sessionStore, log2) {
17456
17790
  };
17457
17791
  }
17458
17792
  let gqlClient;
17459
- try {
17460
- gqlClient = await log2.timed(
17461
- "GQL auth",
17462
- () => withTimeout(
17463
- getAuthenticatedGQLClient({ isSkipPrompts: true }),
17464
- GQL_AUTH_TIMEOUT_MS,
17465
- "GQL auth"
17466
- )
17467
- );
17468
- } catch (err) {
17469
- log2.error(
17470
- {
17471
- data: {
17472
- error: String(err),
17473
- stack: err instanceof Error ? err.stack : void 0
17474
- }
17475
- },
17476
- "GQL auth failed"
17477
- );
17478
- return {
17479
- entriesUploaded: 0,
17480
- entriesSkipped: filteredOut,
17481
- errors: entries.length
17482
- };
17793
+ if (gqlClientOverride) {
17794
+ gqlClient = gqlClientOverride;
17795
+ } else {
17796
+ try {
17797
+ gqlClient = await log2.timed(
17798
+ "GQL auth",
17799
+ () => withTimeout(
17800
+ getAuthenticatedGQLClient({ isSkipPrompts: true }),
17801
+ GQL_AUTH_TIMEOUT_MS,
17802
+ "GQL auth"
17803
+ )
17804
+ );
17805
+ } catch (err) {
17806
+ log2.error({ err }, "GQL auth failed");
17807
+ return {
17808
+ entriesUploaded: 0,
17809
+ entriesSkipped: filteredOut,
17810
+ errors: entries.length
17811
+ };
17812
+ }
17483
17813
  }
17484
17814
  const cursorForModel = sessionStore.get(cursorKey);
17485
17815
  let lastSeenModel = cursorForModel?.lastModel ?? null;
@@ -17496,68 +17826,508 @@ async function processTranscript(hookData, sessionStore, log2) {
17496
17826
  rawEntry["message"] = { model: lastSeenModel };
17497
17827
  }
17498
17828
  }
17499
- if (!rawEntry["sessionId"]) {
17500
- rawEntry["sessionId"] = hookData.session_id;
17829
+ if (!rawEntry["sessionId"]) {
17830
+ rawEntry["sessionId"] = input.session_id;
17831
+ }
17832
+ return {
17833
+ platform: "CLAUDE_CODE" /* ClaudeCode */,
17834
+ recordId: _recordId,
17835
+ recordTimestamp: entry.timestamp ?? (/* @__PURE__ */ new Date()).toISOString(),
17836
+ blameType: "CHAT" /* Chat */,
17837
+ rawData: rawEntry
17838
+ };
17839
+ });
17840
+ const totalRawDataBytes = records.reduce((sum, r) => {
17841
+ return sum + (r.rawData ? JSON.stringify(r.rawData).length : 0);
17842
+ }, 0);
17843
+ log2.info(
17844
+ {
17845
+ data: {
17846
+ count: records.length,
17847
+ skipped: filteredOut,
17848
+ rawDataBytes: totalRawDataBytes,
17849
+ firstRecordId: records[0]?.recordId,
17850
+ lastRecordId: records[records.length - 1]?.recordId
17851
+ }
17852
+ },
17853
+ "Uploading batch"
17854
+ );
17855
+ const sanitize = process.env["MOBBDEV_HOOK_SANITIZE"] === "1";
17856
+ const result = await log2.timed(
17857
+ "Batch upload",
17858
+ () => prepareAndSendTracyRecords(gqlClient, records, input.cwd, {
17859
+ sanitize
17860
+ })
17861
+ );
17862
+ if (result.ok) {
17863
+ const lastRawEntry = rawEntries[rawEntries.length - 1];
17864
+ const cursor = {
17865
+ id: lastRawEntry._recordId,
17866
+ byteOffset: endByteOffset,
17867
+ updatedAt: Date.now(),
17868
+ lastModel: lastSeenModel ?? void 0
17869
+ };
17870
+ sessionStore.set(cursorKey, cursor);
17871
+ log2.heartbeat("Upload ok", {
17872
+ entriesUploaded: entries.length,
17873
+ entriesSkipped: filteredOut,
17874
+ claudeCodeVersion: getClaudeCodeVersion()
17875
+ });
17876
+ return {
17877
+ entriesUploaded: entries.length,
17878
+ entriesSkipped: filteredOut,
17879
+ errors: 0
17880
+ };
17881
+ }
17882
+ log2.error(
17883
+ { data: { errors: result.errors, recordCount: entries.length } },
17884
+ "Batch upload had errors"
17885
+ );
17886
+ return {
17887
+ entriesUploaded: 0,
17888
+ entriesSkipped: filteredOut,
17889
+ errors: entries.length
17890
+ };
17891
+ }
17892
+
17893
+ // src/features/claude_code/install_hook.ts
17894
+ import fs14 from "fs";
17895
+ import fsPromises4 from "fs/promises";
17896
+ import os6 from "os";
17897
+ import path15 from "path";
17898
+ import chalk11 from "chalk";
17899
+
17900
+ // src/features/claude_code/daemon-check-shim.tmpl.js
17901
+ var daemon_check_shim_tmpl_default = "// Mobb daemon shim \u2014 checks if daemon is alive, spawns if dead.\n// Auto-generated by mobbdev CLI. Do not edit.\nvar fs = require('fs')\nvar spawn = require('child_process').spawn\nvar path = require('path')\nvar os = require('os')\n\nvar pidFile = path.join(os.homedir(), '.mobbdev', 'daemon.pid')\nvar HEARTBEAT_STALE_MS = __HEARTBEAT_STALE_MS__\n\ntry {\n var data = JSON.parse(fs.readFileSync(pidFile, 'utf8'))\n if (Date.now() - data.heartbeat > HEARTBEAT_STALE_MS) throw new Error('stale')\n process.kill(data.pid, 0) // throws ESRCH if the process is gone\n} catch (e) {\n var localCli = process.env.MOBBDEV_LOCAL_CLI\n var child = localCli\n ? spawn('node', [localCli, 'claude-code-daemon'], { detached: true, stdio: 'ignore', windowsHide: true })\n : spawn('npx', ['--yes', 'mobbdev@latest', 'claude-code-daemon'], { detached: true, stdio: 'ignore', shell: true, windowsHide: true })\n child.unref()\n}\n";
17902
+
17903
+ // src/features/claude_code/install_hook.ts
17904
+ var CLAUDE_SETTINGS_PATH = path15.join(os6.homedir(), ".claude", "settings.json");
17905
+ var RECOMMENDED_MATCHER = "*";
17906
+ async function claudeSettingsExists() {
17907
+ try {
17908
+ await fsPromises4.access(CLAUDE_SETTINGS_PATH);
17909
+ return true;
17910
+ } catch {
17911
+ return false;
17912
+ }
17913
+ }
17914
+ async function readClaudeSettings() {
17915
+ const settingsContent = await fsPromises4.readFile(
17916
+ CLAUDE_SETTINGS_PATH,
17917
+ "utf-8"
17918
+ );
17919
+ return JSON.parse(settingsContent);
17920
+ }
17921
+ async function writeClaudeSettings(settings) {
17922
+ await fsPromises4.writeFile(
17923
+ CLAUDE_SETTINGS_PATH,
17924
+ JSON.stringify(settings, null, 2),
17925
+ "utf-8"
17926
+ );
17927
+ }
17928
+ function isMobbHookCommand(command) {
17929
+ if (!command) return false;
17930
+ return command.includes("mobbdev@latest") || command.includes("/.mobbdev/") || command.includes("\\.mobbdev\\");
17931
+ }
17932
+ function getDaemonCheckScript() {
17933
+ return daemon_check_shim_tmpl_default.replace(
17934
+ "__HEARTBEAT_STALE_MS__",
17935
+ String(HEARTBEAT_STALE_MS)
17936
+ );
17937
+ }
17938
+ function writeDaemonCheckScript() {
17939
+ fs14.mkdirSync(getMobbdevDir(), { recursive: true });
17940
+ fs14.writeFileSync(getDaemonCheckScriptPath(), getDaemonCheckScript(), "utf8");
17941
+ }
17942
+ function buildHookCommand(envPrefix) {
17943
+ const nodeBin = process.execPath || "node";
17944
+ const base = `${nodeBin} ${getDaemonCheckScriptPath()}`;
17945
+ return envPrefix ? `${envPrefix} ${base}` : base;
17946
+ }
17947
+ async function autoUpgradeMatcherIfStale() {
17948
+ try {
17949
+ if (!await claudeSettingsExists()) return false;
17950
+ const settings = await readClaudeSettings();
17951
+ const hooks = settings.hooks?.PostToolUse;
17952
+ if (!hooks) return false;
17953
+ let upgraded = false;
17954
+ for (const hook of hooks) {
17955
+ const isMobbHook = hook.hooks.some((h) => isMobbHookCommand(h.command));
17956
+ if (!isMobbHook) continue;
17957
+ if (hook.matcher !== RECOMMENDED_MATCHER) {
17958
+ hook.matcher = RECOMMENDED_MATCHER;
17959
+ upgraded = true;
17960
+ }
17961
+ for (const h of hook.hooks) {
17962
+ if (h.command && !h.command.includes("daemon-check.js")) {
17963
+ const envMatch = h.command.match(/^((?:\w+="[^"]*"\s*)+)/);
17964
+ const envPrefix = envMatch?.[1]?.trim();
17965
+ h.command = buildHookCommand(envPrefix);
17966
+ upgraded = true;
17967
+ }
17968
+ if (!h.async) {
17969
+ h.async = true;
17970
+ upgraded = true;
17971
+ }
17972
+ }
17973
+ }
17974
+ if (upgraded) {
17975
+ writeDaemonCheckScript();
17976
+ await writeClaudeSettings(settings);
17977
+ }
17978
+ return upgraded;
17979
+ } catch {
17980
+ return false;
17981
+ }
17982
+ }
17983
+ async function installMobbHooks(options = {}) {
17984
+ console.log(chalk11.blue("Installing Mobb hooks in Claude Code settings..."));
17985
+ if (!await claudeSettingsExists()) {
17986
+ console.log(chalk11.red("\u274C Claude Code settings file not found"));
17987
+ console.log(chalk11.yellow(`Expected location: ${CLAUDE_SETTINGS_PATH}`));
17988
+ console.log(chalk11.yellow("Is Claude Code installed on your system?"));
17989
+ console.log(chalk11.yellow("Please install Claude Code and try again."));
17990
+ throw new Error(
17991
+ "Claude Code settings file not found. Is Claude Code installed?"
17992
+ );
17993
+ }
17994
+ writeDaemonCheckScript();
17995
+ const settings = await readClaudeSettings();
17996
+ if (!settings.hooks) {
17997
+ settings.hooks = {};
17998
+ }
17999
+ if (!settings.hooks.PostToolUse) {
18000
+ settings.hooks.PostToolUse = [];
18001
+ }
18002
+ let envPrefix;
18003
+ if (options.saveEnv) {
18004
+ const envVars = [];
18005
+ if (process.env["WEB_APP_URL"]) {
18006
+ envVars.push(`WEB_APP_URL="${process.env["WEB_APP_URL"]}"`);
18007
+ }
18008
+ if (process.env["API_URL"]) {
18009
+ envVars.push(`API_URL="${process.env["API_URL"]}"`);
18010
+ }
18011
+ if (envVars.length > 0) {
18012
+ envPrefix = envVars.join(" ");
18013
+ console.log(
18014
+ chalk11.blue(
18015
+ `Adding environment variables to hook command: ${envVars.join(", ")}`
18016
+ )
18017
+ );
18018
+ }
18019
+ }
18020
+ const command = buildHookCommand(envPrefix);
18021
+ const mobbHookConfig = {
18022
+ matcher: RECOMMENDED_MATCHER,
18023
+ hooks: [
18024
+ {
18025
+ type: "command",
18026
+ command,
18027
+ async: true
18028
+ }
18029
+ ]
18030
+ };
18031
+ const existingHookIndex = settings.hooks.PostToolUse.findIndex(
18032
+ (hook) => hook.hooks.some((h) => isMobbHookCommand(h.command))
18033
+ );
18034
+ if (existingHookIndex >= 0) {
18035
+ console.log(chalk11.yellow("Mobb hook already exists, updating..."));
18036
+ settings.hooks.PostToolUse[existingHookIndex] = mobbHookConfig;
18037
+ } else {
18038
+ console.log(chalk11.green("Adding new Mobb hook..."));
18039
+ settings.hooks.PostToolUse.push(mobbHookConfig);
18040
+ }
18041
+ await writeClaudeSettings(settings);
18042
+ console.log(
18043
+ chalk11.green(
18044
+ `\u2705 Mobb hooks ${options.saveEnv ? "and environment variables " : ""}installed successfully in ${CLAUDE_SETTINGS_PATH}`
18045
+ )
18046
+ );
18047
+ }
18048
+
18049
+ // src/features/claude_code/transcript_scanner.ts
18050
+ import { open as open5, readdir as readdir2, stat } from "fs/promises";
18051
+ import os7 from "os";
18052
+ import path16 from "path";
18053
+ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
18054
+ function getClaudeProjectsDirs() {
18055
+ const dirs = [];
18056
+ const configDir = process.env["CLAUDE_CONFIG_DIR"];
18057
+ if (configDir) {
18058
+ dirs.push(path16.join(configDir, "projects"));
18059
+ }
18060
+ dirs.push(path16.join(os7.homedir(), ".config", "claude", "projects"));
18061
+ dirs.push(path16.join(os7.homedir(), ".claude", "projects"));
18062
+ return dirs;
18063
+ }
18064
+ async function collectJsonlFiles(files, dir, projectDir, seen, now, results) {
18065
+ for (const file of files) {
18066
+ if (!file.endsWith(".jsonl")) continue;
18067
+ const sessionId = file.replace(".jsonl", "");
18068
+ if (!UUID_RE.test(sessionId)) continue;
18069
+ const filePath = path16.join(dir, file);
18070
+ if (seen.has(filePath)) continue;
18071
+ seen.add(filePath);
18072
+ let fileStat;
18073
+ try {
18074
+ fileStat = await stat(filePath);
18075
+ } catch {
18076
+ continue;
18077
+ }
18078
+ if (now - fileStat.mtimeMs > TRANSCRIPT_MAX_AGE_MS) continue;
18079
+ results.push({
18080
+ filePath,
18081
+ sessionId,
18082
+ projectDir,
18083
+ mtimeMs: fileStat.mtimeMs,
18084
+ size: fileStat.size
18085
+ });
18086
+ }
18087
+ }
18088
+ async function scanForTranscripts(projectsDirs = getClaudeProjectsDirs()) {
18089
+ const results = [];
18090
+ const seen = /* @__PURE__ */ new Set();
18091
+ const now = Date.now();
18092
+ for (const projectsDir of projectsDirs) {
18093
+ let projectDirs;
18094
+ try {
18095
+ projectDirs = await readdir2(projectsDir);
18096
+ } catch {
18097
+ continue;
18098
+ }
18099
+ for (const projName of projectDirs) {
18100
+ const projPath = path16.join(projectsDir, projName);
18101
+ let projStat;
18102
+ try {
18103
+ projStat = await stat(projPath);
18104
+ } catch {
18105
+ continue;
18106
+ }
18107
+ if (!projStat.isDirectory()) continue;
18108
+ let files;
18109
+ try {
18110
+ files = await readdir2(projPath);
18111
+ } catch {
18112
+ continue;
18113
+ }
18114
+ await collectJsonlFiles(files, projPath, projPath, seen, now, results);
18115
+ for (const entry of files) {
18116
+ if (!UUID_RE.test(entry)) continue;
18117
+ const subagentsDir = path16.join(projPath, entry, "subagents");
18118
+ try {
18119
+ const s = await stat(subagentsDir);
18120
+ if (!s.isDirectory()) continue;
18121
+ const subFiles = await readdir2(subagentsDir);
18122
+ await collectJsonlFiles(
18123
+ subFiles,
18124
+ subagentsDir,
18125
+ projPath,
18126
+ seen,
18127
+ now,
18128
+ results
18129
+ );
18130
+ } catch {
18131
+ }
18132
+ }
18133
+ }
18134
+ }
18135
+ return results;
18136
+ }
18137
+ var CWD_READ_BYTES = 8192;
18138
+ var cwdCache = /* @__PURE__ */ new Map();
18139
+ async function extractCwdFromTranscript(filePath) {
18140
+ const cached = cwdCache.get(filePath);
18141
+ if (cached !== void 0) return cached;
18142
+ try {
18143
+ const fh = await open5(filePath, "r");
18144
+ try {
18145
+ const buf = Buffer.alloc(CWD_READ_BYTES);
18146
+ const { bytesRead } = await fh.read(buf, 0, CWD_READ_BYTES, 0);
18147
+ const text = buf.toString("utf8", 0, bytesRead);
18148
+ const lines = text.split("\n");
18149
+ for (const line of lines) {
18150
+ if (!line.trim()) continue;
18151
+ try {
18152
+ const entry = JSON.parse(line);
18153
+ if (typeof entry.cwd === "string") {
18154
+ cwdCache.set(filePath, entry.cwd);
18155
+ return entry.cwd;
18156
+ }
18157
+ } catch {
18158
+ }
18159
+ }
18160
+ } finally {
18161
+ await fh.close();
18162
+ }
18163
+ } catch {
18164
+ }
18165
+ return void 0;
18166
+ }
18167
+
18168
+ // src/features/claude_code/daemon.ts
18169
+ async function startDaemon() {
18170
+ hookLog.info("Daemon starting");
18171
+ const pidFile = await acquirePidFile();
18172
+ async function gracefulExit(code, reason) {
18173
+ hookLog.info({ data: { code } }, `Daemon exiting: ${reason}`);
18174
+ pidFile.remove();
18175
+ await flushDdLogs();
18176
+ process.exit(code);
18177
+ }
18178
+ let shuttingDown = false;
18179
+ process.once("SIGTERM", () => {
18180
+ shuttingDown = true;
18181
+ });
18182
+ process.once("SIGINT", () => {
18183
+ shuttingDown = true;
18184
+ });
18185
+ process.on("uncaughtException", (err) => {
18186
+ hookLog.error({ err }, "Daemon uncaughtException");
18187
+ void gracefulExit(1, "uncaughtException");
18188
+ });
18189
+ process.on("unhandledRejection", (reason) => {
18190
+ hookLog.error({ err: reason }, "Daemon unhandledRejection");
18191
+ void gracefulExit(1, "unhandledRejection");
18192
+ });
18193
+ await tryAutoUpgradeHooks();
18194
+ writeDaemonCheckScript();
18195
+ try {
18196
+ const ccVersion = await detectClaudeCodeVersion();
18197
+ setClaudeCodeVersion(ccVersion);
18198
+ } catch {
18199
+ }
18200
+ const gqlClient = await authenticateOrExit(gracefulExit);
18201
+ const startedAt = Date.now();
18202
+ const lastSeen = /* @__PURE__ */ new Map();
18203
+ let cleanupConfigDir;
18204
+ while (true) {
18205
+ if (shuttingDown) {
18206
+ await gracefulExit(0, "signal");
18207
+ }
18208
+ if (Date.now() - startedAt >= DAEMON_TTL_MS) {
18209
+ await gracefulExit(0, "TTL reached");
18210
+ }
18211
+ pidFile.updateHeartbeat();
18212
+ try {
18213
+ const changed = await detectChangedTranscripts(lastSeen);
18214
+ for (const transcript of changed) {
18215
+ const sessionStore = createSessionConfigStore(transcript.sessionId);
18216
+ if (!cleanupConfigDir) {
18217
+ cleanupConfigDir = path17.dirname(sessionStore.path);
18218
+ }
18219
+ await drainTranscript(transcript, sessionStore, gqlClient);
18220
+ }
18221
+ if (cleanupConfigDir) {
18222
+ await cleanupStaleSessions(cleanupConfigDir);
18223
+ }
18224
+ } catch (err) {
18225
+ hookLog.warn({ err }, "Unexpected error in daemon cycle");
17501
18226
  }
17502
- return {
17503
- platform: "CLAUDE_CODE" /* ClaudeCode */,
17504
- recordId: _recordId,
17505
- recordTimestamp: entry.timestamp ?? (/* @__PURE__ */ new Date()).toISOString(),
17506
- blameType: "CHAT" /* Chat */,
17507
- rawData: rawEntry
17508
- };
18227
+ await sleep2(DAEMON_POLL_INTERVAL_MS);
18228
+ }
18229
+ }
18230
+ async function acquirePidFile() {
18231
+ const pidFile = new DaemonPidFile();
18232
+ pidFile.ensureDir();
18233
+ pidFile.read();
18234
+ if (pidFile.isAlive()) {
18235
+ hookLog.info(
18236
+ { data: { existingPid: pidFile.data?.pid } },
18237
+ "Another daemon is alive, exiting"
18238
+ );
18239
+ await flushDdLogs();
18240
+ process.exit(0);
18241
+ }
18242
+ pidFile.write(process.pid, packageJson.version);
18243
+ hookLog.info({ data: { pid: process.pid } }, "Daemon PID file written");
18244
+ return pidFile;
18245
+ }
18246
+ async function authenticateOrExit(exit) {
18247
+ try {
18248
+ const client = await withTimeout(
18249
+ getAuthenticatedGQLClient({ isSkipPrompts: true }),
18250
+ GQL_AUTH_TIMEOUT_MS,
18251
+ "GQL auth"
18252
+ );
18253
+ hookLog.info("Daemon authenticated");
18254
+ return client;
18255
+ } catch (err) {
18256
+ hookLog.error({ err }, "Daemon auth failed");
18257
+ return exit(1, "auth failed");
18258
+ }
18259
+ }
18260
+ async function drainTranscript(transcript, sessionStore, gqlClient) {
18261
+ const cwd = await extractCwdFromTranscript(transcript.filePath);
18262
+ const log2 = createScopedHookLog(cwd ?? transcript.projectDir, {
18263
+ daemonMode: true
17509
18264
  });
17510
- const totalRawDataBytes = records.reduce((sum, r) => {
17511
- return sum + (r.rawData ? JSON.stringify(r.rawData).length : 0);
17512
- }, 0);
17513
- log2.info(
17514
- {
17515
- data: {
17516
- count: records.length,
17517
- skipped: filteredOut,
17518
- rawDataBytes: totalRawDataBytes,
17519
- firstRecordId: records[0]?.recordId,
17520
- lastRecordId: records[records.length - 1]?.recordId
18265
+ try {
18266
+ let hasMore = true;
18267
+ while (hasMore) {
18268
+ const result = await processTranscript(
18269
+ {
18270
+ session_id: transcript.sessionId,
18271
+ transcript_path: transcript.filePath,
18272
+ cwd
18273
+ },
18274
+ sessionStore,
18275
+ log2,
18276
+ DAEMON_CHUNK_SIZE,
18277
+ gqlClient
18278
+ );
18279
+ hasMore = result.entriesUploaded + result.entriesSkipped >= DAEMON_CHUNK_SIZE;
18280
+ if (result.errors > 0) {
18281
+ hookLog.warn(
18282
+ {
18283
+ data: {
18284
+ sessionId: transcript.sessionId,
18285
+ errors: result.errors
18286
+ }
18287
+ },
18288
+ "Upload error \u2014 will retry next cycle"
18289
+ );
18290
+ break;
17521
18291
  }
17522
- },
17523
- "Uploading batch"
17524
- );
17525
- const sanitize = process.env["MOBBDEV_HOOK_SANITIZE"] === "1";
17526
- const result = await log2.timed(
17527
- "Batch upload",
17528
- () => prepareAndSendTracyRecords(gqlClient, records, hookData.cwd, {
17529
- sanitize
17530
- })
17531
- );
17532
- if (result.ok) {
17533
- const lastRawEntry = rawEntries[rawEntries.length - 1];
17534
- const cursor = {
17535
- id: lastRawEntry._recordId,
17536
- byteOffset: endByteOffset,
17537
- updatedAt: Date.now(),
17538
- lastModel: lastSeenModel ?? void 0
17539
- };
17540
- sessionStore.set(cursorKey, cursor);
17541
- log2.heartbeat("Upload ok", {
17542
- entriesUploaded: entries.length,
17543
- entriesSkipped: filteredOut,
17544
- claudeCodeVersion: getClaudeCodeVersion()
17545
- });
17546
- return {
17547
- entriesUploaded: entries.length,
17548
- entriesSkipped: filteredOut,
17549
- errors: 0
17550
- };
18292
+ }
18293
+ } catch (err) {
18294
+ hookLog.warn(
18295
+ { err, data: { sessionId: transcript.sessionId } },
18296
+ "Error processing transcript \u2014 skipping"
18297
+ );
17551
18298
  }
17552
- log2.error(
17553
- { data: { errors: result.errors, recordCount: entries.length } },
17554
- "Batch upload had errors"
18299
+ }
18300
+ async function detectChangedTranscripts(lastSeen) {
18301
+ const transcripts = await scanForTranscripts();
18302
+ const changed = [];
18303
+ const currentPaths = /* @__PURE__ */ new Set();
18304
+ for (const t of transcripts) {
18305
+ currentPaths.add(t.filePath);
18306
+ const prev = lastSeen.get(t.filePath);
18307
+ if (!prev || t.mtimeMs > prev.mtimeMs || t.size > prev.size) {
18308
+ changed.push(t);
18309
+ }
18310
+ lastSeen.set(t.filePath, { mtimeMs: t.mtimeMs, size: t.size });
18311
+ }
18312
+ for (const p of lastSeen.keys()) {
18313
+ if (!currentPaths.has(p)) lastSeen.delete(p);
18314
+ }
18315
+ return changed;
18316
+ }
18317
+ async function tryAutoUpgradeHooks() {
18318
+ const lastUpgradeVersion = configStore.get(
18319
+ "claudeCode.matcherUpgradeVersion"
17555
18320
  );
17556
- return {
17557
- entriesUploaded: 0,
17558
- entriesSkipped: filteredOut,
17559
- errors: entries.length
17560
- };
18321
+ if (lastUpgradeVersion === packageJson.version) return;
18322
+ try {
18323
+ const upgraded = await autoUpgradeMatcherIfStale();
18324
+ configStore.set("claudeCode.matcherUpgradeVersion", packageJson.version);
18325
+ if (upgraded) {
18326
+ hookLog.info("Auto-upgraded hook matcher");
18327
+ }
18328
+ } catch (err) {
18329
+ hookLog.warn({ err }, "Failed to auto-upgrade hook matcher");
18330
+ }
17561
18331
  }
17562
18332
 
17563
18333
  // src/args/commands/claude_code.ts
@@ -17577,7 +18347,13 @@ var claudeCodeInstallHookBuilder = (yargs2) => {
17577
18347
  var claudeCodeProcessHookBuilder = (yargs2) => {
17578
18348
  return yargs2.example(
17579
18349
  "$0 claude-code-process-hook",
17580
- "Process Claude Code hook data and upload to backend"
18350
+ "Process Claude Code hook data (legacy \u2014 spawns daemon)"
18351
+ ).strict();
18352
+ };
18353
+ var claudeCodeDaemonBuilder = (yargs2) => {
18354
+ return yargs2.example(
18355
+ "$0 claude-code-daemon",
18356
+ "Run the background daemon that processes Claude Code transcripts"
17581
18357
  ).strict();
17582
18358
  };
17583
18359
  var claudeCodeInstallHookHandler = async (argv) => {
@@ -17591,69 +18367,43 @@ var claudeCodeInstallHookHandler = async (argv) => {
17591
18367
  }
17592
18368
  };
17593
18369
  var claudeCodeProcessHookHandler = async () => {
17594
- const startupMs = Math.round(process.uptime() * 1e3);
17595
- const debugMode = process.env["MOBBDEV_HOOK_DEBUG"] === "1";
17596
- async function flushAndExit(code) {
17597
- try {
17598
- flushLogs();
17599
- await flushDdLogs();
17600
- } catch {
17601
- } finally {
17602
- process.exit(debugMode ? code : 0);
18370
+ try {
18371
+ await autoUpgradeMatcherIfStale();
18372
+ writeDaemonCheckScript();
18373
+ const pidFile = new DaemonPidFile();
18374
+ pidFile.read();
18375
+ if (!pidFile.isAlive()) {
18376
+ hookLog.info("Daemon not alive \u2014 spawning");
18377
+ const localCli = process.env["MOBBDEV_LOCAL_CLI"];
18378
+ const child = localCli ? spawn("node", [localCli, "claude-code-daemon"], {
18379
+ detached: true,
18380
+ stdio: "ignore",
18381
+ windowsHide: true
18382
+ }) : spawn("npx", ["--yes", "mobbdev@latest", "claude-code-daemon"], {
18383
+ detached: true,
18384
+ stdio: "ignore",
18385
+ shell: true,
18386
+ windowsHide: true
18387
+ });
18388
+ child.unref();
17603
18389
  }
18390
+ } catch (err) {
18391
+ hookLog.error({ err }, "Error in process-hook shim");
17604
18392
  }
17605
- process.on("uncaughtException", (error) => {
17606
- hookLog.error(
17607
- { data: { error: String(error), stack: error.stack } },
17608
- "Uncaught exception in hook"
17609
- );
17610
- void flushAndExit(1);
17611
- });
17612
- process.on("unhandledRejection", (reason) => {
17613
- hookLog.error(
17614
- {
17615
- data: {
17616
- error: String(reason),
17617
- stack: reason instanceof Error ? reason.stack : void 0
17618
- }
17619
- },
17620
- "Unhandled rejection in hook"
17621
- );
17622
- void flushAndExit(1);
17623
- });
17624
- let exitCode = 0;
17625
- const hookStart = Date.now();
17626
18393
  try {
17627
- const result = await processAndUploadTranscriptEntries();
17628
- if (result.errors > 0) {
17629
- exitCode = 1;
17630
- }
17631
- hookLog.info(
17632
- {
17633
- data: {
17634
- entriesUploaded: result.entriesUploaded,
17635
- entriesSkipped: result.entriesSkipped,
17636
- errors: result.errors,
17637
- startupMs,
17638
- durationMs: Date.now() - hookStart
17639
- }
17640
- },
17641
- "Claude Code upload complete"
17642
- );
17643
- } catch (error) {
17644
- exitCode = 1;
17645
- hookLog.error(
17646
- {
17647
- data: {
17648
- error: String(error),
17649
- stack: error instanceof Error ? error.stack : void 0,
17650
- durationMs: Date.now() - hookStart
17651
- }
17652
- },
17653
- "Failed to process Claude Code hook"
17654
- );
18394
+ await flushDdLogs();
18395
+ } catch {
18396
+ }
18397
+ process.exit(0);
18398
+ };
18399
+ var claudeCodeDaemonHandler = async () => {
18400
+ try {
18401
+ await startDaemon();
18402
+ } catch (err) {
18403
+ hookLog.error({ err }, "Daemon crashed");
18404
+ await flushDdLogs();
18405
+ process.exit(1);
17655
18406
  }
17656
- await flushAndExit(exitCode);
17657
18407
  };
17658
18408
 
17659
18409
  // src/mcp/core/McpServer.ts
@@ -17675,8 +18425,8 @@ var WorkspaceService = class {
17675
18425
  * Sets a known workspace path that was discovered through successful validation
17676
18426
  * @param path The validated workspace path to store
17677
18427
  */
17678
- static setKnownWorkspacePath(path27) {
17679
- this.knownWorkspacePath = path27;
18428
+ static setKnownWorkspacePath(path30) {
18429
+ this.knownWorkspacePath = path30;
17680
18430
  }
17681
18431
  /**
17682
18432
  * Gets the known workspace path that was previously validated
@@ -17823,131 +18573,131 @@ init_configs();
17823
18573
 
17824
18574
  // src/mcp/types.ts
17825
18575
  init_client_generates();
17826
- import { z as z34 } from "zod";
17827
- var ScanAndFixVulnerabilitiesToolSchema = z34.object({
17828
- path: z34.string()
18576
+ import { z as z33 } from "zod";
18577
+ var ScanAndFixVulnerabilitiesToolSchema = z33.object({
18578
+ path: z33.string()
17829
18579
  });
17830
- var VulnerabilityReportIssueTagSchema = z34.object({
17831
- vulnerability_report_issue_tag_value: z34.nativeEnum(
18580
+ var VulnerabilityReportIssueTagSchema = z33.object({
18581
+ vulnerability_report_issue_tag_value: z33.nativeEnum(
17832
18582
  Vulnerability_Report_Issue_Tag_Enum
17833
18583
  )
17834
18584
  });
17835
- var VulnerabilityReportIssueSchema = z34.object({
17836
- category: z34.any().optional().nullable(),
17837
- parsedIssueType: z34.nativeEnum(IssueType_Enum).nullable().optional(),
17838
- parsedSeverity: z34.nativeEnum(Vulnerability_Severity_Enum).nullable().optional(),
17839
- vulnerabilityReportIssueTags: z34.array(VulnerabilityReportIssueTagSchema)
18585
+ var VulnerabilityReportIssueSchema = z33.object({
18586
+ category: z33.any().optional().nullable(),
18587
+ parsedIssueType: z33.nativeEnum(IssueType_Enum).nullable().optional(),
18588
+ parsedSeverity: z33.nativeEnum(Vulnerability_Severity_Enum).nullable().optional(),
18589
+ vulnerabilityReportIssueTags: z33.array(VulnerabilityReportIssueTagSchema)
17840
18590
  });
17841
- var SharedStateSchema = z34.object({
17842
- __typename: z34.literal("fix_shared_state").optional(),
17843
- id: z34.any(),
18591
+ var SharedStateSchema = z33.object({
18592
+ __typename: z33.literal("fix_shared_state").optional(),
18593
+ id: z33.any(),
17844
18594
  // GraphQL uses `any` type for UUID
17845
- downloadedBy: z34.array(z34.any().nullable()).optional().nullable()
18595
+ downloadedBy: z33.array(z33.any().nullable()).optional().nullable()
17846
18596
  });
17847
- var UnstructuredFixExtraContextSchema = z34.object({
17848
- __typename: z34.literal("UnstructuredFixExtraContext").optional(),
17849
- key: z34.string(),
17850
- value: z34.any()
18597
+ var UnstructuredFixExtraContextSchema = z33.object({
18598
+ __typename: z33.literal("UnstructuredFixExtraContext").optional(),
18599
+ key: z33.string(),
18600
+ value: z33.any()
17851
18601
  // GraphQL JSON type
17852
18602
  });
17853
- var FixExtraContextResponseSchema = z34.object({
17854
- __typename: z34.literal("FixExtraContextResponse").optional(),
17855
- extraContext: z34.array(UnstructuredFixExtraContextSchema),
17856
- fixDescription: z34.string()
18603
+ var FixExtraContextResponseSchema = z33.object({
18604
+ __typename: z33.literal("FixExtraContextResponse").optional(),
18605
+ extraContext: z33.array(UnstructuredFixExtraContextSchema),
18606
+ fixDescription: z33.string()
17857
18607
  });
17858
- var FixDataSchema = z34.object({
17859
- __typename: z34.literal("FixData"),
17860
- patch: z34.string(),
17861
- patchOriginalEncodingBase64: z34.string(),
18608
+ var FixDataSchema = z33.object({
18609
+ __typename: z33.literal("FixData"),
18610
+ patch: z33.string(),
18611
+ patchOriginalEncodingBase64: z33.string(),
17862
18612
  extraContext: FixExtraContextResponseSchema
17863
18613
  });
17864
- var GetFixNoFixErrorSchema = z34.object({
17865
- __typename: z34.literal("GetFixNoFixError")
18614
+ var GetFixNoFixErrorSchema = z33.object({
18615
+ __typename: z33.literal("GetFixNoFixError")
17866
18616
  });
17867
- var PatchAndQuestionsSchema = z34.union([FixDataSchema, GetFixNoFixErrorSchema]);
17868
- var McpFixSchema = z34.object({
17869
- __typename: z34.literal("fix").optional(),
17870
- id: z34.any(),
18617
+ var PatchAndQuestionsSchema = z33.union([FixDataSchema, GetFixNoFixErrorSchema]);
18618
+ var McpFixSchema = z33.object({
18619
+ __typename: z33.literal("fix").optional(),
18620
+ id: z33.any(),
17871
18621
  // GraphQL uses `any` type for UUID
17872
- confidence: z34.number(),
17873
- safeIssueType: z34.string().nullable(),
17874
- severityText: z34.string().nullable(),
17875
- gitBlameLogin: z34.string().nullable().optional(),
18622
+ confidence: z33.number(),
18623
+ safeIssueType: z33.string().nullable(),
18624
+ severityText: z33.string().nullable(),
18625
+ gitBlameLogin: z33.string().nullable().optional(),
17876
18626
  // Optional in GraphQL
17877
- severityValue: z34.number().nullable(),
17878
- vulnerabilityReportIssues: z34.array(VulnerabilityReportIssueSchema),
18627
+ severityValue: z33.number().nullable(),
18628
+ vulnerabilityReportIssues: z33.array(VulnerabilityReportIssueSchema),
17879
18629
  sharedState: SharedStateSchema.nullable().optional(),
17880
18630
  // Optional in GraphQL
17881
18631
  patchAndQuestions: PatchAndQuestionsSchema,
17882
18632
  // Additional field added by the client
17883
- fixUrl: z34.string().optional()
18633
+ fixUrl: z33.string().optional()
17884
18634
  });
17885
- var FixAggregateSchema = z34.object({
17886
- __typename: z34.literal("fix_aggregate").optional(),
17887
- aggregate: z34.object({
17888
- __typename: z34.literal("fix_aggregate_fields").optional(),
17889
- count: z34.number()
18635
+ var FixAggregateSchema = z33.object({
18636
+ __typename: z33.literal("fix_aggregate").optional(),
18637
+ aggregate: z33.object({
18638
+ __typename: z33.literal("fix_aggregate_fields").optional(),
18639
+ count: z33.number()
17890
18640
  }).nullable()
17891
18641
  });
17892
- var VulnerabilityReportIssueAggregateSchema = z34.object({
17893
- __typename: z34.literal("vulnerability_report_issue_aggregate").optional(),
17894
- aggregate: z34.object({
17895
- __typename: z34.literal("vulnerability_report_issue_aggregate_fields").optional(),
17896
- count: z34.number()
18642
+ var VulnerabilityReportIssueAggregateSchema = z33.object({
18643
+ __typename: z33.literal("vulnerability_report_issue_aggregate").optional(),
18644
+ aggregate: z33.object({
18645
+ __typename: z33.literal("vulnerability_report_issue_aggregate_fields").optional(),
18646
+ count: z33.number()
17897
18647
  }).nullable()
17898
18648
  });
17899
- var RepoSchema = z34.object({
17900
- __typename: z34.literal("repo").optional(),
17901
- originalUrl: z34.string()
18649
+ var RepoSchema = z33.object({
18650
+ __typename: z33.literal("repo").optional(),
18651
+ originalUrl: z33.string()
17902
18652
  });
17903
- var ProjectSchema = z34.object({
17904
- id: z34.any(),
18653
+ var ProjectSchema = z33.object({
18654
+ id: z33.any(),
17905
18655
  // GraphQL uses `any` type for UUID
17906
- organizationId: z34.any()
18656
+ organizationId: z33.any()
17907
18657
  // GraphQL uses `any` type for UUID
17908
18658
  });
17909
- var VulnerabilityReportSchema = z34.object({
17910
- scanDate: z34.any().nullable(),
18659
+ var VulnerabilityReportSchema = z33.object({
18660
+ scanDate: z33.any().nullable(),
17911
18661
  // GraphQL uses `any` type for timestamp
17912
- vendor: z34.string(),
18662
+ vendor: z33.string(),
17913
18663
  // GraphQL generates as string, not enum
17914
- projectId: z34.any().optional(),
18664
+ projectId: z33.any().optional(),
17915
18665
  // GraphQL uses `any` type for UUID
17916
18666
  project: ProjectSchema,
17917
18667
  totalVulnerabilityReportIssuesCount: VulnerabilityReportIssueAggregateSchema,
17918
18668
  notFixableVulnerabilityReportIssuesCount: VulnerabilityReportIssueAggregateSchema
17919
18669
  });
17920
- var FixReportSummarySchema = z34.object({
17921
- __typename: z34.literal("fixReport").optional(),
17922
- id: z34.any(),
18670
+ var FixReportSummarySchema = z33.object({
18671
+ __typename: z33.literal("fixReport").optional(),
18672
+ id: z33.any(),
17923
18673
  // GraphQL uses `any` type for UUID
17924
- createdOn: z34.any(),
18674
+ createdOn: z33.any(),
17925
18675
  // GraphQL uses `any` type for timestamp
17926
18676
  repo: RepoSchema.nullable(),
17927
- issueTypes: z34.any().nullable(),
18677
+ issueTypes: z33.any().nullable(),
17928
18678
  // GraphQL uses `any` type for JSON
17929
18679
  CRITICAL: FixAggregateSchema,
17930
18680
  HIGH: FixAggregateSchema,
17931
18681
  MEDIUM: FixAggregateSchema,
17932
18682
  LOW: FixAggregateSchema,
17933
- fixes: z34.array(McpFixSchema),
17934
- userFixes: z34.array(McpFixSchema).optional(),
18683
+ fixes: z33.array(McpFixSchema),
18684
+ userFixes: z33.array(McpFixSchema).optional(),
17935
18685
  // Present in some responses but can be omitted
17936
18686
  filteredFixesCount: FixAggregateSchema,
17937
18687
  totalFixesCount: FixAggregateSchema,
17938
18688
  vulnerabilityReport: VulnerabilityReportSchema
17939
18689
  });
17940
- var ExpiredReportSchema = z34.object({
17941
- __typename: z34.literal("fixReport").optional(),
17942
- id: z34.any(),
18690
+ var ExpiredReportSchema = z33.object({
18691
+ __typename: z33.literal("fixReport").optional(),
18692
+ id: z33.any(),
17943
18693
  // GraphQL uses `any` type for UUID
17944
- expirationOn: z34.any().nullable()
18694
+ expirationOn: z33.any().nullable()
17945
18695
  // GraphQL uses `any` type for timestamp
17946
18696
  });
17947
- var GetLatestReportByRepoUrlResponseSchema = z34.object({
17948
- __typename: z34.literal("query_root").optional(),
17949
- fixReport: z34.array(FixReportSummarySchema),
17950
- expiredReport: z34.array(ExpiredReportSchema)
18697
+ var GetLatestReportByRepoUrlResponseSchema = z33.object({
18698
+ __typename: z33.literal("query_root").optional(),
18699
+ fixReport: z33.array(FixReportSummarySchema),
18700
+ expiredReport: z33.array(ExpiredReportSchema)
17951
18701
  });
17952
18702
 
17953
18703
  // src/mcp/services/McpGQLClient.ts
@@ -18535,9 +19285,9 @@ async function createAuthenticatedMcpGQLClient({
18535
19285
 
18536
19286
  // src/mcp/services/McpUsageService/host.ts
18537
19287
  import { execSync as execSync2 } from "child_process";
18538
- import fs13 from "fs";
18539
- import os6 from "os";
18540
- import path15 from "path";
19288
+ import fs15 from "fs";
19289
+ import os8 from "os";
19290
+ import path18 from "path";
18541
19291
  var IDEs = ["cursor", "windsurf", "webstorm", "vscode", "claude"];
18542
19292
  var runCommand = (cmd) => {
18543
19293
  try {
@@ -18551,18 +19301,18 @@ var gitInfo = {
18551
19301
  email: runCommand("git config user.email")
18552
19302
  };
18553
19303
  var getClaudeWorkspacePaths = () => {
18554
- const home = os6.homedir();
18555
- const claudeIdePath = path15.join(home, ".claude", "ide");
19304
+ const home = os8.homedir();
19305
+ const claudeIdePath = path18.join(home, ".claude", "ide");
18556
19306
  const workspacePaths = [];
18557
- if (!fs13.existsSync(claudeIdePath)) {
19307
+ if (!fs15.existsSync(claudeIdePath)) {
18558
19308
  return workspacePaths;
18559
19309
  }
18560
19310
  try {
18561
- const lockFiles = fs13.readdirSync(claudeIdePath).filter((file) => file.endsWith(".lock"));
19311
+ const lockFiles = fs15.readdirSync(claudeIdePath).filter((file) => file.endsWith(".lock"));
18562
19312
  for (const lockFile of lockFiles) {
18563
- const lockFilePath = path15.join(claudeIdePath, lockFile);
19313
+ const lockFilePath = path18.join(claudeIdePath, lockFile);
18564
19314
  try {
18565
- const lockContent = JSON.parse(fs13.readFileSync(lockFilePath, "utf8"));
19315
+ const lockContent = JSON.parse(fs15.readFileSync(lockFilePath, "utf8"));
18566
19316
  if (lockContent.workspaceFolders && Array.isArray(lockContent.workspaceFolders)) {
18567
19317
  workspacePaths.push(...lockContent.workspaceFolders);
18568
19318
  }
@@ -18580,29 +19330,29 @@ var getClaudeWorkspacePaths = () => {
18580
19330
  return workspacePaths;
18581
19331
  };
18582
19332
  var getMCPConfigPaths = (hostName) => {
18583
- const home = os6.homedir();
19333
+ const home = os8.homedir();
18584
19334
  const currentDir = process.env["WORKSPACE_FOLDER_PATHS"] || process.env["PWD"] || process.cwd();
18585
19335
  switch (hostName.toLowerCase()) {
18586
19336
  case "cursor":
18587
19337
  return [
18588
- path15.join(currentDir, ".cursor", "mcp.json"),
19338
+ path18.join(currentDir, ".cursor", "mcp.json"),
18589
19339
  // local first
18590
- path15.join(home, ".cursor", "mcp.json")
19340
+ path18.join(home, ".cursor", "mcp.json")
18591
19341
  ];
18592
19342
  case "windsurf":
18593
19343
  return [
18594
- path15.join(currentDir, ".codeium", "mcp_config.json"),
19344
+ path18.join(currentDir, ".codeium", "mcp_config.json"),
18595
19345
  // local first
18596
- path15.join(home, ".codeium", "windsurf", "mcp_config.json")
19346
+ path18.join(home, ".codeium", "windsurf", "mcp_config.json")
18597
19347
  ];
18598
19348
  case "webstorm":
18599
19349
  return [];
18600
19350
  case "visualstudiocode":
18601
19351
  case "vscode":
18602
19352
  return [
18603
- path15.join(currentDir, ".vscode", "mcp.json"),
19353
+ path18.join(currentDir, ".vscode", "mcp.json"),
18604
19354
  // local first
18605
- process.platform === "win32" ? path15.join(home, "AppData", "Roaming", "Code", "User", "mcp.json") : path15.join(
19355
+ process.platform === "win32" ? path18.join(home, "AppData", "Roaming", "Code", "User", "mcp.json") : path18.join(
18606
19356
  home,
18607
19357
  "Library",
18608
19358
  "Application Support",
@@ -18613,13 +19363,13 @@ var getMCPConfigPaths = (hostName) => {
18613
19363
  ];
18614
19364
  case "claude": {
18615
19365
  const claudePaths = [
18616
- path15.join(currentDir, ".claude.json"),
19366
+ path18.join(currentDir, ".claude.json"),
18617
19367
  // local first
18618
- path15.join(home, ".claude.json")
19368
+ path18.join(home, ".claude.json")
18619
19369
  ];
18620
19370
  const workspacePaths = getClaudeWorkspacePaths();
18621
19371
  for (const workspacePath of workspacePaths) {
18622
- claudePaths.push(path15.join(workspacePath, ".mcp.json"));
19372
+ claudePaths.push(path18.join(workspacePath, ".mcp.json"));
18623
19373
  }
18624
19374
  return claudePaths;
18625
19375
  }
@@ -18628,9 +19378,9 @@ var getMCPConfigPaths = (hostName) => {
18628
19378
  }
18629
19379
  };
18630
19380
  var readConfigFile = (filePath) => {
18631
- if (!fs13.existsSync(filePath)) return null;
19381
+ if (!fs15.existsSync(filePath)) return null;
18632
19382
  try {
18633
- return JSON.parse(fs13.readFileSync(filePath, "utf8"));
19383
+ return JSON.parse(fs15.readFileSync(filePath, "utf8"));
18634
19384
  } catch (error) {
18635
19385
  logWarn(`[UsageService] Failed to read MCP config: ${filePath}`);
18636
19386
  return null;
@@ -18670,7 +19420,7 @@ var readMCPConfig = (hostName) => {
18670
19420
  };
18671
19421
  var getRunningProcesses = () => {
18672
19422
  try {
18673
- return os6.platform() === "win32" ? execSync2("tasklist", { encoding: "utf8" }) : execSync2("ps aux", { encoding: "utf8" });
19423
+ return os8.platform() === "win32" ? execSync2("tasklist", { encoding: "utf8" }) : execSync2("ps aux", { encoding: "utf8" });
18674
19424
  } catch {
18675
19425
  return "";
18676
19426
  }
@@ -18745,7 +19495,7 @@ var versionCommands = {
18745
19495
  }
18746
19496
  };
18747
19497
  var getProcessInfo = (pid) => {
18748
- const platform2 = os6.platform();
19498
+ const platform2 = os8.platform();
18749
19499
  try {
18750
19500
  if (platform2 === "linux" || platform2 === "darwin") {
18751
19501
  const output = execSync2(`ps -o pid=,ppid=,comm= -p ${pid}`, {
@@ -18780,10 +19530,10 @@ var getHostInfo = (additionalMcpList) => {
18780
19530
  const ideConfigPaths = /* @__PURE__ */ new Set();
18781
19531
  for (const ide of IDEs) {
18782
19532
  const configPaths = getMCPConfigPaths(ide);
18783
- configPaths.forEach((path27) => ideConfigPaths.add(path27));
19533
+ configPaths.forEach((path30) => ideConfigPaths.add(path30));
18784
19534
  }
18785
19535
  const uniqueAdditionalPaths = additionalMcpList.filter(
18786
- (path27) => !ideConfigPaths.has(path27)
19536
+ (path30) => !ideConfigPaths.has(path30)
18787
19537
  );
18788
19538
  for (const ide of IDEs) {
18789
19539
  const cfg = readMCPConfig(ide);
@@ -18864,7 +19614,7 @@ var getHostInfo = (additionalMcpList) => {
18864
19614
  const config2 = allConfigs[ide] || null;
18865
19615
  const ideName = ide.charAt(0).toUpperCase() + ide.slice(1) || "Unknown";
18866
19616
  let ideVersion = "Unknown";
18867
- const platform2 = os6.platform();
19617
+ const platform2 = os8.platform();
18868
19618
  const cmds = versionCommands[ideName]?.[platform2] ?? [];
18869
19619
  for (const cmd of cmds) {
18870
19620
  try {
@@ -18897,15 +19647,15 @@ var getHostInfo = (additionalMcpList) => {
18897
19647
 
18898
19648
  // src/mcp/services/McpUsageService/McpUsageService.ts
18899
19649
  import fetch6 from "node-fetch";
18900
- import os8 from "os";
19650
+ import os10 from "os";
18901
19651
  import { v4 as uuidv42, v5 as uuidv5 } from "uuid";
18902
19652
  init_configs();
18903
19653
 
18904
19654
  // src/mcp/services/McpUsageService/system.ts
18905
19655
  init_configs();
18906
- import fs14 from "fs";
18907
- import os7 from "os";
18908
- import path16 from "path";
19656
+ import fs16 from "fs";
19657
+ import os9 from "os";
19658
+ import path19 from "path";
18909
19659
  var MAX_DEPTH = 2;
18910
19660
  var patterns = ["mcp", "claude"];
18911
19661
  var isFileMatch = (fileName) => {
@@ -18914,7 +19664,7 @@ var isFileMatch = (fileName) => {
18914
19664
  };
18915
19665
  var safeAccess = async (filePath) => {
18916
19666
  try {
18917
- await fs14.promises.access(filePath, fs14.constants.R_OK);
19667
+ await fs16.promises.access(filePath, fs16.constants.R_OK);
18918
19668
  return true;
18919
19669
  } catch {
18920
19670
  return false;
@@ -18923,9 +19673,9 @@ var safeAccess = async (filePath) => {
18923
19673
  var searchDir = async (dir, depth = 0) => {
18924
19674
  const results = [];
18925
19675
  if (depth > MAX_DEPTH) return results;
18926
- const entries = await fs14.promises.readdir(dir, { withFileTypes: true }).catch(() => []);
19676
+ const entries = await fs16.promises.readdir(dir, { withFileTypes: true }).catch(() => []);
18927
19677
  for (const entry of entries) {
18928
- const fullPath = path16.join(dir, entry.name);
19678
+ const fullPath = path19.join(dir, entry.name);
18929
19679
  if (entry.isFile() && isFileMatch(entry.name)) {
18930
19680
  results.push(fullPath);
18931
19681
  } else if (entry.isDirectory()) {
@@ -18939,17 +19689,17 @@ var searchDir = async (dir, depth = 0) => {
18939
19689
  };
18940
19690
  var findSystemMCPConfigs = async () => {
18941
19691
  try {
18942
- const home = os7.homedir();
18943
- const platform2 = os7.platform();
19692
+ const home = os9.homedir();
19693
+ const platform2 = os9.platform();
18944
19694
  const knownDirs = platform2 === "win32" ? [
18945
- path16.join(home, ".cursor"),
18946
- path16.join(home, "Documents"),
18947
- path16.join(home, "Downloads")
19695
+ path19.join(home, ".cursor"),
19696
+ path19.join(home, "Documents"),
19697
+ path19.join(home, "Downloads")
18948
19698
  ] : [
18949
- path16.join(home, ".cursor"),
18950
- process.env["XDG_CONFIG_HOME"] || path16.join(home, ".config"),
18951
- path16.join(home, "Documents"),
18952
- path16.join(home, "Downloads")
19699
+ path19.join(home, ".cursor"),
19700
+ process.env["XDG_CONFIG_HOME"] || path19.join(home, ".config"),
19701
+ path19.join(home, "Documents"),
19702
+ path19.join(home, "Downloads")
18953
19703
  ];
18954
19704
  const timeoutPromise = new Promise(
18955
19705
  (resolve) => setTimeout(() => {
@@ -18961,7 +19711,7 @@ var findSystemMCPConfigs = async () => {
18961
19711
  );
18962
19712
  const searchPromise = Promise.all(
18963
19713
  knownDirs.map(
18964
- (dir) => fs14.existsSync(dir) ? searchDir(dir) : Promise.resolve([])
19714
+ (dir) => fs16.existsSync(dir) ? searchDir(dir) : Promise.resolve([])
18965
19715
  )
18966
19716
  ).then((results) => results.flat());
18967
19717
  return await Promise.race([timeoutPromise, searchPromise]);
@@ -19012,7 +19762,7 @@ var McpUsageService = class {
19012
19762
  generateHostId() {
19013
19763
  const stored = configStore.get(this.configKey);
19014
19764
  if (stored?.mcpHostId) return stored.mcpHostId;
19015
- const interfaces = os8.networkInterfaces();
19765
+ const interfaces = os10.networkInterfaces();
19016
19766
  const macs = [];
19017
19767
  for (const iface of Object.values(interfaces)) {
19018
19768
  if (!iface) continue;
@@ -19020,7 +19770,7 @@ var McpUsageService = class {
19020
19770
  if (net.mac && net.mac !== "00:00:00:00:00:00") macs.push(net.mac);
19021
19771
  }
19022
19772
  }
19023
- const macString = macs.length ? macs.sort().join(",") : `${os8.hostname()}-${uuidv42()}`;
19773
+ const macString = macs.length ? macs.sort().join(",") : `${os10.hostname()}-${uuidv42()}`;
19024
19774
  const hostId = uuidv5(macString, uuidv5.DNS);
19025
19775
  logDebug("[UsageService] Generated new host ID", { hostId });
19026
19776
  return hostId;
@@ -19043,7 +19793,7 @@ var McpUsageService = class {
19043
19793
  mcpHostId,
19044
19794
  organizationId,
19045
19795
  mcpVersion: packageJson.version,
19046
- mcpOsName: os8.platform(),
19796
+ mcpOsName: os10.platform(),
19047
19797
  mcps: JSON.stringify(mcps),
19048
19798
  status,
19049
19799
  userName: user.name,
@@ -19704,10 +20454,10 @@ var McpServer = class {
19704
20454
  };
19705
20455
 
19706
20456
  // src/mcp/prompts/CheckForNewVulnerabilitiesPrompt.ts
19707
- import { z as z36 } from "zod";
20457
+ import { z as z35 } from "zod";
19708
20458
 
19709
20459
  // src/mcp/prompts/base/BasePrompt.ts
19710
- import { z as z35 } from "zod";
20460
+ import { z as z34 } from "zod";
19711
20461
  var BasePrompt = class {
19712
20462
  getDefinition() {
19713
20463
  return {
@@ -19736,7 +20486,7 @@ var BasePrompt = class {
19736
20486
  const argsToValidate = args === void 0 ? {} : args;
19737
20487
  return this.argumentsValidationSchema.parse(argsToValidate);
19738
20488
  } catch (error) {
19739
- if (error instanceof z35.ZodError) {
20489
+ if (error instanceof z34.ZodError) {
19740
20490
  const errorDetails = error.errors.map((e) => {
19741
20491
  const fieldPath = e.path.length > 0 ? e.path.join(".") : "root";
19742
20492
  const message = e.message === "Required" ? `Missing required argument '${fieldPath}'` : `Invalid value for '${fieldPath}': ${e.message}`;
@@ -19765,8 +20515,8 @@ var BasePrompt = class {
19765
20515
  };
19766
20516
 
19767
20517
  // src/mcp/prompts/CheckForNewVulnerabilitiesPrompt.ts
19768
- var CheckForNewVulnerabilitiesArgsSchema = z36.object({
19769
- path: z36.string().optional()
20518
+ var CheckForNewVulnerabilitiesArgsSchema = z35.object({
20519
+ path: z35.string().optional()
19770
20520
  });
19771
20521
  var CheckForNewVulnerabilitiesPrompt = class extends BasePrompt {
19772
20522
  constructor() {
@@ -20011,9 +20761,9 @@ Call the \`check_for_new_available_fixes\` tool now${args?.path ? ` for ${args.p
20011
20761
  };
20012
20762
 
20013
20763
  // src/mcp/prompts/FullSecurityAuditPrompt.ts
20014
- import { z as z37 } from "zod";
20015
- var FullSecurityAuditArgsSchema = z37.object({
20016
- path: z37.string().optional()
20764
+ import { z as z36 } from "zod";
20765
+ var FullSecurityAuditArgsSchema = z36.object({
20766
+ path: z36.string().optional()
20017
20767
  });
20018
20768
  var FullSecurityAuditPrompt = class extends BasePrompt {
20019
20769
  constructor() {
@@ -20464,9 +21214,9 @@ Begin the audit now${args?.path ? ` for ${args.path}` : ""}.
20464
21214
  };
20465
21215
 
20466
21216
  // src/mcp/prompts/ReviewAndFixCriticalPrompt.ts
20467
- import { z as z38 } from "zod";
20468
- var ReviewAndFixCriticalArgsSchema = z38.object({
20469
- path: z38.string().optional()
21217
+ import { z as z37 } from "zod";
21218
+ var ReviewAndFixCriticalArgsSchema = z37.object({
21219
+ path: z37.string().optional()
20470
21220
  });
20471
21221
  var ReviewAndFixCriticalPrompt = class extends BasePrompt {
20472
21222
  constructor() {
@@ -20770,9 +21520,9 @@ Start by scanning${args?.path ? ` ${args.path}` : " the repository"} and priorit
20770
21520
  };
20771
21521
 
20772
21522
  // src/mcp/prompts/ScanRecentChangesPrompt.ts
20773
- import { z as z39 } from "zod";
20774
- var ScanRecentChangesArgsSchema = z39.object({
20775
- path: z39.string().optional()
21523
+ import { z as z38 } from "zod";
21524
+ var ScanRecentChangesArgsSchema = z38.object({
21525
+ path: z38.string().optional()
20776
21526
  });
20777
21527
  var ScanRecentChangesPrompt = class extends BasePrompt {
20778
21528
  constructor() {
@@ -20983,9 +21733,9 @@ You now have the guidance needed to perform a fast, targeted security scan of re
20983
21733
  };
20984
21734
 
20985
21735
  // src/mcp/prompts/ScanRepositoryPrompt.ts
20986
- import { z as z40 } from "zod";
20987
- var ScanRepositoryArgsSchema = z40.object({
20988
- path: z40.string().optional()
21736
+ import { z as z39 } from "zod";
21737
+ var ScanRepositoryArgsSchema = z39.object({
21738
+ path: z39.string().optional()
20989
21739
  });
20990
21740
  var ScanRepositoryPrompt = class extends BasePrompt {
20991
21741
  constructor() {
@@ -21363,31 +22113,31 @@ For a complete security audit workflow, use the \`full-security-audit\` prompt.
21363
22113
  };
21364
22114
 
21365
22115
  // src/mcp/services/McpDetectionService/CursorMcpDetectionService.ts
21366
- import * as fs17 from "fs";
21367
- import * as os10 from "os";
21368
- import * as path18 from "path";
22116
+ import * as fs19 from "fs";
22117
+ import * as os12 from "os";
22118
+ import * as path21 from "path";
21369
22119
 
21370
22120
  // src/mcp/services/McpDetectionService/BaseMcpDetectionService.ts
21371
22121
  init_configs();
21372
- import * as fs16 from "fs";
22122
+ import * as fs18 from "fs";
21373
22123
  import fetch7 from "node-fetch";
21374
- import * as path17 from "path";
22124
+ import * as path20 from "path";
21375
22125
 
21376
22126
  // src/mcp/services/McpDetectionService/McpDetectionServiceUtils.ts
21377
- import * as fs15 from "fs";
21378
- import * as os9 from "os";
22127
+ import * as fs17 from "fs";
22128
+ import * as os11 from "os";
21379
22129
 
21380
22130
  // src/mcp/services/McpDetectionService/VscodeMcpDetectionService.ts
21381
- import * as fs18 from "fs";
21382
- import * as os11 from "os";
21383
- import * as path19 from "path";
22131
+ import * as fs20 from "fs";
22132
+ import * as os13 from "os";
22133
+ import * as path22 from "path";
21384
22134
 
21385
22135
  // src/mcp/tools/checkForNewAvailableFixes/CheckForNewAvailableFixesTool.ts
21386
- import { z as z43 } from "zod";
22136
+ import { z as z42 } from "zod";
21387
22137
 
21388
22138
  // src/mcp/services/PathValidation.ts
21389
- import fs19 from "fs";
21390
- import path20 from "path";
22139
+ import fs21 from "fs";
22140
+ import path23 from "path";
21391
22141
  async function validatePath(inputPath) {
21392
22142
  logDebug("Validating MCP path", { inputPath });
21393
22143
  if (/^\/[a-zA-Z]:\//.test(inputPath)) {
@@ -21419,7 +22169,7 @@ async function validatePath(inputPath) {
21419
22169
  logError(error);
21420
22170
  return { isValid: false, error, path: inputPath };
21421
22171
  }
21422
- const normalizedPath = path20.normalize(inputPath);
22172
+ const normalizedPath = path23.normalize(inputPath);
21423
22173
  if (normalizedPath.includes("..")) {
21424
22174
  const error = `Normalized path contains path traversal patterns: ${inputPath}`;
21425
22175
  logError(error);
@@ -21446,7 +22196,7 @@ async function validatePath(inputPath) {
21446
22196
  logDebug("Path validation successful", { inputPath });
21447
22197
  logDebug("Checking path existence", { inputPath });
21448
22198
  try {
21449
- await fs19.promises.access(inputPath);
22199
+ await fs21.promises.access(inputPath);
21450
22200
  logDebug("Path exists and is accessible", { inputPath });
21451
22201
  WorkspaceService.setKnownWorkspacePath(inputPath);
21452
22202
  logDebug("Stored validated path in WorkspaceService", { inputPath });
@@ -21459,7 +22209,7 @@ async function validatePath(inputPath) {
21459
22209
  }
21460
22210
 
21461
22211
  // src/mcp/tools/base/BaseTool.ts
21462
- import { z as z41 } from "zod";
22212
+ import { z as z40 } from "zod";
21463
22213
  var BaseTool = class {
21464
22214
  getDefinition() {
21465
22215
  return {
@@ -21486,7 +22236,7 @@ var BaseTool = class {
21486
22236
  try {
21487
22237
  return this.inputValidationSchema.parse(args);
21488
22238
  } catch (error) {
21489
- if (error instanceof z41.ZodError) {
22239
+ if (error instanceof z40.ZodError) {
21490
22240
  const errorDetails = error.errors.map((e) => {
21491
22241
  const fieldPath = e.path.length > 0 ? e.path.join(".") : "root";
21492
22242
  const message = e.message === "Required" ? `Missing required parameter '${fieldPath}'` : `Invalid value for '${fieldPath}': ${e.message}`;
@@ -22068,10 +22818,10 @@ If you wish to scan files that were recently changed in your git history call th
22068
22818
  init_FileUtils();
22069
22819
  init_GitService();
22070
22820
  init_configs();
22071
- import fs20 from "fs/promises";
22821
+ import fs22 from "fs/promises";
22072
22822
  import nodePath from "path";
22073
22823
  var getLocalFiles = async ({
22074
- path: path27,
22824
+ path: path30,
22075
22825
  maxFileSize = MCP_MAX_FILE_SIZE,
22076
22826
  maxFiles,
22077
22827
  isAllFilesScan,
@@ -22079,17 +22829,17 @@ var getLocalFiles = async ({
22079
22829
  scanRecentlyChangedFiles
22080
22830
  }) => {
22081
22831
  logDebug(`[${scanContext}] Starting getLocalFiles`, {
22082
- path: path27,
22832
+ path: path30,
22083
22833
  maxFileSize,
22084
22834
  maxFiles,
22085
22835
  isAllFilesScan,
22086
22836
  scanRecentlyChangedFiles
22087
22837
  });
22088
22838
  try {
22089
- const resolvedRepoPath = await fs20.realpath(path27);
22839
+ const resolvedRepoPath = await fs22.realpath(path30);
22090
22840
  logDebug(`[${scanContext}] Resolved repository path`, {
22091
22841
  resolvedRepoPath,
22092
- originalPath: path27
22842
+ originalPath: path30
22093
22843
  });
22094
22844
  const gitService = new GitService(resolvedRepoPath, log);
22095
22845
  const gitValidation = await gitService.validateRepository();
@@ -22102,7 +22852,7 @@ var getLocalFiles = async ({
22102
22852
  if (!gitValidation.isValid || isAllFilesScan) {
22103
22853
  try {
22104
22854
  files = await FileUtils.getLastChangedFiles({
22105
- dir: path27,
22855
+ dir: path30,
22106
22856
  maxFileSize,
22107
22857
  maxFiles,
22108
22858
  isAllFilesScan
@@ -22166,7 +22916,7 @@ var getLocalFiles = async ({
22166
22916
  absoluteFilePath
22167
22917
  );
22168
22918
  try {
22169
- const fileStat = await fs20.stat(absoluteFilePath);
22919
+ const fileStat = await fs22.stat(absoluteFilePath);
22170
22920
  return {
22171
22921
  filename: nodePath.basename(absoluteFilePath),
22172
22922
  relativePath,
@@ -22194,7 +22944,7 @@ var getLocalFiles = async ({
22194
22944
  logError(`${scanContext}Unexpected error in getLocalFiles`, {
22195
22945
  error: error instanceof Error ? error.message : String(error),
22196
22946
  stack: error instanceof Error ? error.stack : void 0,
22197
- path: path27
22947
+ path: path30
22198
22948
  });
22199
22949
  throw error;
22200
22950
  }
@@ -22203,15 +22953,15 @@ var getLocalFiles = async ({
22203
22953
  // src/mcp/services/LocalMobbFolderService.ts
22204
22954
  init_client_generates();
22205
22955
  init_GitService();
22206
- import fs21 from "fs";
22207
- import path21 from "path";
22208
- import { z as z42 } from "zod";
22956
+ import fs23 from "fs";
22957
+ import path24 from "path";
22958
+ import { z as z41 } from "zod";
22209
22959
  function extractPathFromPatch(patch) {
22210
22960
  const match = patch?.match(/diff --git a\/([^\s]+) b\//);
22211
22961
  return match?.[1] ?? null;
22212
22962
  }
22213
22963
  function parsedIssueTypeRes(issueType) {
22214
- return z42.nativeEnum(IssueType_Enum).safeParse(issueType);
22964
+ return z41.nativeEnum(IssueType_Enum).safeParse(issueType);
22215
22965
  }
22216
22966
  var LocalMobbFolderService = class {
22217
22967
  /**
@@ -22290,19 +23040,19 @@ var LocalMobbFolderService = class {
22290
23040
  "[LocalMobbFolderService] Non-git repository detected, skipping .gitignore operations"
22291
23041
  );
22292
23042
  }
22293
- const mobbFolderPath = path21.join(
23043
+ const mobbFolderPath = path24.join(
22294
23044
  this.repoPath,
22295
23045
  this.defaultMobbFolderName
22296
23046
  );
22297
- if (!fs21.existsSync(mobbFolderPath)) {
23047
+ if (!fs23.existsSync(mobbFolderPath)) {
22298
23048
  logInfo("[LocalMobbFolderService] Creating .mobb folder", {
22299
23049
  mobbFolderPath
22300
23050
  });
22301
- fs21.mkdirSync(mobbFolderPath, { recursive: true });
23051
+ fs23.mkdirSync(mobbFolderPath, { recursive: true });
22302
23052
  } else {
22303
23053
  logDebug("[LocalMobbFolderService] .mobb folder already exists");
22304
23054
  }
22305
- const stats = fs21.statSync(mobbFolderPath);
23055
+ const stats = fs23.statSync(mobbFolderPath);
22306
23056
  if (!stats.isDirectory()) {
22307
23057
  throw new Error(`Path exists but is not a directory: ${mobbFolderPath}`);
22308
23058
  }
@@ -22343,13 +23093,13 @@ var LocalMobbFolderService = class {
22343
23093
  logDebug("[LocalMobbFolderService] Git repository validated successfully");
22344
23094
  } else {
22345
23095
  try {
22346
- const stats = fs21.statSync(this.repoPath);
23096
+ const stats = fs23.statSync(this.repoPath);
22347
23097
  if (!stats.isDirectory()) {
22348
23098
  throw new Error(
22349
23099
  `Path exists but is not a directory: ${this.repoPath}`
22350
23100
  );
22351
23101
  }
22352
- fs21.accessSync(this.repoPath, fs21.constants.R_OK | fs21.constants.W_OK);
23102
+ fs23.accessSync(this.repoPath, fs23.constants.R_OK | fs23.constants.W_OK);
22353
23103
  logDebug(
22354
23104
  "[LocalMobbFolderService] Non-git directory validated successfully"
22355
23105
  );
@@ -22462,8 +23212,8 @@ var LocalMobbFolderService = class {
22462
23212
  mobbFolderPath,
22463
23213
  baseFileName
22464
23214
  );
22465
- const filePath = path21.join(mobbFolderPath, uniqueFileName);
22466
- await fs21.promises.writeFile(filePath, patch, "utf8");
23215
+ const filePath = path24.join(mobbFolderPath, uniqueFileName);
23216
+ await fs23.promises.writeFile(filePath, patch, "utf8");
22467
23217
  logInfo("[LocalMobbFolderService] Patch saved successfully", {
22468
23218
  filePath,
22469
23219
  fileName: uniqueFileName,
@@ -22520,11 +23270,11 @@ var LocalMobbFolderService = class {
22520
23270
  * @returns Unique filename that doesn't conflict with existing files
22521
23271
  */
22522
23272
  getUniqueFileName(folderPath, baseFileName) {
22523
- const baseName = path21.parse(baseFileName).name;
22524
- const extension = path21.parse(baseFileName).ext;
23273
+ const baseName = path24.parse(baseFileName).name;
23274
+ const extension = path24.parse(baseFileName).ext;
22525
23275
  let uniqueFileName = baseFileName;
22526
23276
  let index = 1;
22527
- while (fs21.existsSync(path21.join(folderPath, uniqueFileName))) {
23277
+ while (fs23.existsSync(path24.join(folderPath, uniqueFileName))) {
22528
23278
  uniqueFileName = `${baseName}-${index}${extension}`;
22529
23279
  index++;
22530
23280
  if (index > 1e3) {
@@ -22555,18 +23305,18 @@ var LocalMobbFolderService = class {
22555
23305
  logDebug("[LocalMobbFolderService] Logging patch info", { fixId: fix.id });
22556
23306
  try {
22557
23307
  const mobbFolderPath = await this.getFolder();
22558
- const patchInfoPath = path21.join(mobbFolderPath, "patchInfo.md");
23308
+ const patchInfoPath = path24.join(mobbFolderPath, "patchInfo.md");
22559
23309
  const markdownContent = this.generateFixMarkdown(fix, savedPatchFileName);
22560
23310
  let existingContent = "";
22561
- if (fs21.existsSync(patchInfoPath)) {
22562
- existingContent = await fs21.promises.readFile(patchInfoPath, "utf8");
23311
+ if (fs23.existsSync(patchInfoPath)) {
23312
+ existingContent = await fs23.promises.readFile(patchInfoPath, "utf8");
22563
23313
  logDebug("[LocalMobbFolderService] Existing patchInfo.md found");
22564
23314
  } else {
22565
23315
  logDebug("[LocalMobbFolderService] Creating new patchInfo.md file");
22566
23316
  }
22567
23317
  const separator = existingContent ? "\n\n================================================================================\n\n" : "";
22568
23318
  const updatedContent = `${markdownContent}${separator}${existingContent}`;
22569
- await fs21.promises.writeFile(patchInfoPath, updatedContent, "utf8");
23319
+ await fs23.promises.writeFile(patchInfoPath, updatedContent, "utf8");
22570
23320
  logInfo("[LocalMobbFolderService] Patch info logged successfully", {
22571
23321
  patchInfoPath,
22572
23322
  fixId: fix.id,
@@ -22597,7 +23347,7 @@ var LocalMobbFolderService = class {
22597
23347
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
22598
23348
  const patch = this.extractPatchFromFix(fix);
22599
23349
  const relativePatchedFilePath = patch ? extractPathFromPatch(patch) : null;
22600
- const patchedFilePath = relativePatchedFilePath ? path21.resolve(this.repoPath, relativePatchedFilePath) : null;
23350
+ const patchedFilePath = relativePatchedFilePath ? path24.resolve(this.repoPath, relativePatchedFilePath) : null;
22601
23351
  const fixIdentifier = savedPatchFileName ? savedPatchFileName.replace(".patch", "") : fix.id;
22602
23352
  let markdown = `# Fix ${fixIdentifier}
22603
23353
 
@@ -22939,16 +23689,16 @@ import {
22939
23689
  unlinkSync,
22940
23690
  writeFileSync as writeFileSync2
22941
23691
  } from "fs";
22942
- import fs22 from "fs/promises";
23692
+ import fs24 from "fs/promises";
22943
23693
  import parseDiff2 from "parse-diff";
22944
- import path22 from "path";
23694
+ import path25 from "path";
22945
23695
  var PatchApplicationService = class {
22946
23696
  /**
22947
23697
  * Gets the appropriate comment syntax for a file based on its extension
22948
23698
  */
22949
23699
  static getCommentSyntax(filePath) {
22950
- const ext = path22.extname(filePath).toLowerCase();
22951
- const basename2 = path22.basename(filePath);
23700
+ const ext = path25.extname(filePath).toLowerCase();
23701
+ const basename2 = path25.basename(filePath);
22952
23702
  const commentMap = {
22953
23703
  // C-style languages (single line comments)
22954
23704
  ".js": "//",
@@ -23156,7 +23906,7 @@ var PatchApplicationService = class {
23156
23906
  }
23157
23907
  );
23158
23908
  }
23159
- const dirPath = path22.dirname(normalizedFilePath);
23909
+ const dirPath = path25.dirname(normalizedFilePath);
23160
23910
  mkdirSync(dirPath, { recursive: true });
23161
23911
  writeFileSync2(normalizedFilePath, finalContent, "utf8");
23162
23912
  return normalizedFilePath;
@@ -23165,9 +23915,9 @@ var PatchApplicationService = class {
23165
23915
  repositoryPath,
23166
23916
  targetPath
23167
23917
  }) {
23168
- const repoRoot = path22.resolve(repositoryPath);
23169
- const normalizedPath = path22.resolve(repoRoot, targetPath);
23170
- const repoRootWithSep = repoRoot.endsWith(path22.sep) ? repoRoot : `${repoRoot}${path22.sep}`;
23918
+ const repoRoot = path25.resolve(repositoryPath);
23919
+ const normalizedPath = path25.resolve(repoRoot, targetPath);
23920
+ const repoRootWithSep = repoRoot.endsWith(path25.sep) ? repoRoot : `${repoRoot}${path25.sep}`;
23171
23921
  if (normalizedPath !== repoRoot && !normalizedPath.startsWith(repoRootWithSep)) {
23172
23922
  throw new Error(
23173
23923
  `Security violation: target path ${targetPath} resolves outside repository`
@@ -23176,7 +23926,7 @@ var PatchApplicationService = class {
23176
23926
  return {
23177
23927
  repoRoot,
23178
23928
  normalizedPath,
23179
- relativePath: path22.relative(repoRoot, normalizedPath)
23929
+ relativePath: path25.relative(repoRoot, normalizedPath)
23180
23930
  };
23181
23931
  }
23182
23932
  /**
@@ -23458,9 +24208,9 @@ var PatchApplicationService = class {
23458
24208
  continue;
23459
24209
  }
23460
24210
  try {
23461
- const absolutePath = path22.resolve(repositoryPath, targetFile);
24211
+ const absolutePath = path25.resolve(repositoryPath, targetFile);
23462
24212
  if (existsSync6(absolutePath)) {
23463
- const stats = await fs22.stat(absolutePath);
24213
+ const stats = await fs24.stat(absolutePath);
23464
24214
  const fileModTime = stats.mtime.getTime();
23465
24215
  if (fileModTime > scanStartTime) {
23466
24216
  logError(
@@ -23501,7 +24251,7 @@ var PatchApplicationService = class {
23501
24251
  const appliedFixes = [];
23502
24252
  const failedFixes = [];
23503
24253
  const skippedFixes = [];
23504
- const resolvedRepoPath = await fs22.realpath(repositoryPath);
24254
+ const resolvedRepoPath = await fs24.realpath(repositoryPath);
23505
24255
  logInfo(
23506
24256
  `[${scanContext}] Starting patch application for ${fixes.length} fixes`,
23507
24257
  {
@@ -23684,7 +24434,7 @@ var PatchApplicationService = class {
23684
24434
  fix,
23685
24435
  scanContext
23686
24436
  });
23687
- appliedFiles.push(path22.relative(repositoryPath, actualPath));
24437
+ appliedFiles.push(path25.relative(repositoryPath, actualPath));
23688
24438
  logDebug(`[${scanContext}] Created new file: ${relativePath}`);
23689
24439
  }
23690
24440
  /**
@@ -23733,7 +24483,7 @@ var PatchApplicationService = class {
23733
24483
  fix,
23734
24484
  scanContext
23735
24485
  });
23736
- appliedFiles.push(path22.relative(repositoryPath, actualPath));
24486
+ appliedFiles.push(path25.relative(repositoryPath, actualPath));
23737
24487
  logDebug(`[${scanContext}] Modified file: ${relativePath}`);
23738
24488
  }
23739
24489
  }
@@ -23929,8 +24679,8 @@ init_configs();
23929
24679
 
23930
24680
  // src/mcp/services/FileOperations.ts
23931
24681
  init_FileUtils();
23932
- import fs23 from "fs";
23933
- import path23 from "path";
24682
+ import fs25 from "fs";
24683
+ import path26 from "path";
23934
24684
  import AdmZip3 from "adm-zip";
23935
24685
  var FileOperations = class {
23936
24686
  /**
@@ -23950,10 +24700,10 @@ var FileOperations = class {
23950
24700
  let packedFilesCount = 0;
23951
24701
  const packedFiles = [];
23952
24702
  const excludedFiles = [];
23953
- const resolvedRepoPath = path23.resolve(repositoryPath);
24703
+ const resolvedRepoPath = path26.resolve(repositoryPath);
23954
24704
  for (const filepath of fileList) {
23955
- const absoluteFilepath = path23.join(repositoryPath, filepath);
23956
- const resolvedFilePath = path23.resolve(absoluteFilepath);
24705
+ const absoluteFilepath = path26.join(repositoryPath, filepath);
24706
+ const resolvedFilePath = path26.resolve(absoluteFilepath);
23957
24707
  if (!resolvedFilePath.startsWith(resolvedRepoPath)) {
23958
24708
  const reason = "potential path traversal security risk";
23959
24709
  logDebug(`[FileOperations] Skipping ${filepath} due to ${reason}`);
@@ -24000,11 +24750,11 @@ var FileOperations = class {
24000
24750
  fileList,
24001
24751
  repositoryPath
24002
24752
  }) {
24003
- const resolvedRepoPath = path23.resolve(repositoryPath);
24753
+ const resolvedRepoPath = path26.resolve(repositoryPath);
24004
24754
  const validatedPaths = [];
24005
24755
  for (const filepath of fileList) {
24006
- const absoluteFilepath = path23.join(repositoryPath, filepath);
24007
- const resolvedFilePath = path23.resolve(absoluteFilepath);
24756
+ const absoluteFilepath = path26.join(repositoryPath, filepath);
24757
+ const resolvedFilePath = path26.resolve(absoluteFilepath);
24008
24758
  if (!resolvedFilePath.startsWith(resolvedRepoPath)) {
24009
24759
  logDebug(
24010
24760
  `[FileOperations] Rejecting ${filepath} - path traversal attempt detected`
@@ -24012,7 +24762,7 @@ var FileOperations = class {
24012
24762
  continue;
24013
24763
  }
24014
24764
  try {
24015
- await fs23.promises.access(absoluteFilepath, fs23.constants.R_OK);
24765
+ await fs25.promises.access(absoluteFilepath, fs25.constants.R_OK);
24016
24766
  validatedPaths.push(filepath);
24017
24767
  } catch (error) {
24018
24768
  logDebug(
@@ -24031,8 +24781,8 @@ var FileOperations = class {
24031
24781
  const fileDataArray = [];
24032
24782
  for (const absolutePath of filePaths) {
24033
24783
  try {
24034
- const content = await fs23.promises.readFile(absolutePath);
24035
- const relativePath = path23.basename(absolutePath);
24784
+ const content = await fs25.promises.readFile(absolutePath);
24785
+ const relativePath = path26.basename(absolutePath);
24036
24786
  fileDataArray.push({
24037
24787
  relativePath,
24038
24788
  absolutePath,
@@ -24057,7 +24807,7 @@ var FileOperations = class {
24057
24807
  relativeFilepath
24058
24808
  }) {
24059
24809
  try {
24060
- return await fs23.promises.readFile(absoluteFilepath);
24810
+ return await fs25.promises.readFile(absoluteFilepath);
24061
24811
  } catch (fsError) {
24062
24812
  logError(
24063
24813
  `[FileOperations] Failed to read ${relativeFilepath} from filesystem: ${fsError}`
@@ -24344,14 +25094,14 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
24344
25094
  * since the last scan.
24345
25095
  */
24346
25096
  async scanForSecurityVulnerabilities({
24347
- path: path27,
25097
+ path: path30,
24348
25098
  isAllDetectionRulesScan,
24349
25099
  isAllFilesScan,
24350
25100
  scanContext
24351
25101
  }) {
24352
25102
  this.hasAuthenticationFailed = false;
24353
25103
  logDebug(`[${scanContext}] Scanning for new security vulnerabilities`, {
24354
- path: path27
25104
+ path: path30
24355
25105
  });
24356
25106
  if (!this.gqlClient) {
24357
25107
  logInfo(`[${scanContext}] No GQL client found, skipping scan`);
@@ -24367,11 +25117,11 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
24367
25117
  }
24368
25118
  logDebug(
24369
25119
  `[${scanContext}] Connected to the API, assembling list of files to scan`,
24370
- { path: path27 }
25120
+ { path: path30 }
24371
25121
  );
24372
25122
  const isBackgroundScan = scanContext === ScanContext.BACKGROUND_INITIAL || scanContext === ScanContext.BACKGROUND_PERIODIC;
24373
25123
  const files = await getLocalFiles({
24374
- path: path27,
25124
+ path: path30,
24375
25125
  isAllFilesScan,
24376
25126
  scanContext,
24377
25127
  scanRecentlyChangedFiles: !isBackgroundScan
@@ -24397,13 +25147,13 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
24397
25147
  });
24398
25148
  const { fixReportId, projectId } = await scanFiles({
24399
25149
  fileList: filesToScan.map((file) => file.relativePath),
24400
- repositoryPath: path27,
25150
+ repositoryPath: path30,
24401
25151
  gqlClient: this.gqlClient,
24402
25152
  isAllDetectionRulesScan,
24403
25153
  scanContext
24404
25154
  });
24405
25155
  logInfo(
24406
- `[${scanContext}] Security scan completed for ${path27} reportId: ${fixReportId} projectId: ${projectId}`
25156
+ `[${scanContext}] Security scan completed for ${path30} reportId: ${fixReportId} projectId: ${projectId}`
24407
25157
  );
24408
25158
  if (isAllFilesScan) {
24409
25159
  return;
@@ -24697,13 +25447,13 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
24697
25447
  });
24698
25448
  return scannedFiles.some((file) => file.relativePath === fixFile);
24699
25449
  }
24700
- async getFreshFixes({ path: path27 }) {
25450
+ async getFreshFixes({ path: path30 }) {
24701
25451
  const scanContext = ScanContext.USER_REQUEST;
24702
- logDebug(`[${scanContext}] Getting fresh fixes`, { path: path27 });
24703
- if (this.path !== path27) {
24704
- this.path = path27;
25452
+ logDebug(`[${scanContext}] Getting fresh fixes`, { path: path30 });
25453
+ if (this.path !== path30) {
25454
+ this.path = path30;
24705
25455
  this.reset();
24706
- logInfo(`[${scanContext}] Reset service state for new path`, { path: path27 });
25456
+ logInfo(`[${scanContext}] Reset service state for new path`, { path: path30 });
24707
25457
  }
24708
25458
  try {
24709
25459
  const loginContext = createMcpLoginContext("check_new_fixes");
@@ -24722,7 +25472,7 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
24722
25472
  }
24723
25473
  throw error;
24724
25474
  }
24725
- this.triggerScan({ path: path27, gqlClient: this.gqlClient });
25475
+ this.triggerScan({ path: path30, gqlClient: this.gqlClient });
24726
25476
  let isMvsAutoFixEnabled = null;
24727
25477
  try {
24728
25478
  isMvsAutoFixEnabled = await this.gqlClient.getMvsAutoFixSettings();
@@ -24756,33 +25506,33 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
24756
25506
  return noFreshFixesPrompt;
24757
25507
  }
24758
25508
  triggerScan({
24759
- path: path27,
25509
+ path: path30,
24760
25510
  gqlClient
24761
25511
  }) {
24762
- if (this.path !== path27) {
24763
- this.path = path27;
25512
+ if (this.path !== path30) {
25513
+ this.path = path30;
24764
25514
  this.reset();
24765
- logInfo(`Reset service state for new path in triggerScan`, { path: path27 });
25515
+ logInfo(`Reset service state for new path in triggerScan`, { path: path30 });
24766
25516
  }
24767
25517
  this.gqlClient = gqlClient;
24768
25518
  if (!this.intervalId) {
24769
- this.startPeriodicScanning(path27);
24770
- this.executeInitialScan(path27);
24771
- void this.executeInitialFullScan(path27);
25519
+ this.startPeriodicScanning(path30);
25520
+ this.executeInitialScan(path30);
25521
+ void this.executeInitialFullScan(path30);
24772
25522
  }
24773
25523
  }
24774
- startPeriodicScanning(path27) {
25524
+ startPeriodicScanning(path30) {
24775
25525
  const scanContext = ScanContext.BACKGROUND_PERIODIC;
24776
25526
  logDebug(
24777
25527
  `[${scanContext}] Starting periodic scan for new security vulnerabilities`,
24778
25528
  {
24779
- path: path27
25529
+ path: path30
24780
25530
  }
24781
25531
  );
24782
25532
  this.intervalId = setInterval(() => {
24783
- logDebug(`[${scanContext}] Triggering periodic security scan`, { path: path27 });
25533
+ logDebug(`[${scanContext}] Triggering periodic security scan`, { path: path30 });
24784
25534
  this.scanForSecurityVulnerabilities({
24785
- path: path27,
25535
+ path: path30,
24786
25536
  scanContext
24787
25537
  }).catch((error) => {
24788
25538
  logError(`[${scanContext}] Error during periodic security scan`, {
@@ -24791,45 +25541,45 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
24791
25541
  });
24792
25542
  }, MCP_PERIODIC_CHECK_INTERVAL);
24793
25543
  }
24794
- async executeInitialFullScan(path27) {
25544
+ async executeInitialFullScan(path30) {
24795
25545
  const scanContext = ScanContext.FULL_SCAN;
24796
- logDebug(`[${scanContext}] Triggering initial full security scan`, { path: path27 });
25546
+ logDebug(`[${scanContext}] Triggering initial full security scan`, { path: path30 });
24797
25547
  logDebug(`[${scanContext}] Full scan paths scanned`, {
24798
25548
  fullScanPathsScanned: this.fullScanPathsScanned
24799
25549
  });
24800
- if (this.fullScanPathsScanned.includes(path27)) {
25550
+ if (this.fullScanPathsScanned.includes(path30)) {
24801
25551
  logDebug(`[${scanContext}] Full scan already executed for this path`, {
24802
- path: path27
25552
+ path: path30
24803
25553
  });
24804
25554
  return;
24805
25555
  }
24806
25556
  configStore.set("fullScanPathsScanned", [
24807
25557
  ...this.fullScanPathsScanned,
24808
- path27
25558
+ path30
24809
25559
  ]);
24810
25560
  try {
24811
25561
  await this.scanForSecurityVulnerabilities({
24812
- path: path27,
25562
+ path: path30,
24813
25563
  isAllFilesScan: true,
24814
25564
  isAllDetectionRulesScan: true,
24815
25565
  scanContext: ScanContext.FULL_SCAN
24816
25566
  });
24817
- if (!this.fullScanPathsScanned.includes(path27)) {
24818
- this.fullScanPathsScanned.push(path27);
25567
+ if (!this.fullScanPathsScanned.includes(path30)) {
25568
+ this.fullScanPathsScanned.push(path30);
24819
25569
  configStore.set("fullScanPathsScanned", this.fullScanPathsScanned);
24820
25570
  }
24821
- logInfo(`[${scanContext}] Full scan completed`, { path: path27 });
25571
+ logInfo(`[${scanContext}] Full scan completed`, { path: path30 });
24822
25572
  } catch (error) {
24823
25573
  logError(`[${scanContext}] Error during initial full security scan`, {
24824
25574
  error
24825
25575
  });
24826
25576
  }
24827
25577
  }
24828
- executeInitialScan(path27) {
25578
+ executeInitialScan(path30) {
24829
25579
  const scanContext = ScanContext.BACKGROUND_INITIAL;
24830
- logDebug(`[${scanContext}] Triggering initial security scan`, { path: path27 });
25580
+ logDebug(`[${scanContext}] Triggering initial security scan`, { path: path30 });
24831
25581
  this.scanForSecurityVulnerabilities({
24832
- path: path27,
25582
+ path: path30,
24833
25583
  scanContext: ScanContext.BACKGROUND_INITIAL
24834
25584
  }).catch((error) => {
24835
25585
  logError(`[${scanContext}] Error during initial security scan`, { error });
@@ -24908,8 +25658,8 @@ Example payload:
24908
25658
  },
24909
25659
  required: ["path"]
24910
25660
  });
24911
- __publicField(this, "inputValidationSchema", z43.object({
24912
- path: z43.string().describe(
25661
+ __publicField(this, "inputValidationSchema", z42.object({
25662
+ path: z42.string().describe(
24913
25663
  "Full local path to the cloned git repository to check for new available fixes"
24914
25664
  )
24915
25665
  }));
@@ -24926,9 +25676,9 @@ Example payload:
24926
25676
  `Invalid path: potential security risk detected in path: ${pathValidationResult.error}`
24927
25677
  );
24928
25678
  }
24929
- const path27 = pathValidationResult.path;
25679
+ const path30 = pathValidationResult.path;
24930
25680
  const resultText = await this.newFixesService.getFreshFixes({
24931
- path: path27
25681
+ path: path30
24932
25682
  });
24933
25683
  logInfo("CheckForNewAvailableFixesTool execution completed", {
24934
25684
  resultText
@@ -24939,7 +25689,7 @@ Example payload:
24939
25689
 
24940
25690
  // src/mcp/tools/fetchAvailableFixes/FetchAvailableFixesTool.ts
24941
25691
  init_GitService();
24942
- import { z as z44 } from "zod";
25692
+ import { z as z43 } from "zod";
24943
25693
 
24944
25694
  // src/mcp/tools/fetchAvailableFixes/FetchAvailableFixesService.ts
24945
25695
  init_configs();
@@ -25082,16 +25832,16 @@ Call this tool instead of ${MCP_TOOL_SCAN_AND_FIX_VULNERABILITIES} when you only
25082
25832
  },
25083
25833
  required: ["path"]
25084
25834
  });
25085
- __publicField(this, "inputValidationSchema", z44.object({
25086
- path: z44.string().describe(
25835
+ __publicField(this, "inputValidationSchema", z43.object({
25836
+ path: z43.string().describe(
25087
25837
  "Full local path to the cloned git repository to check for available fixes"
25088
25838
  ),
25089
- offset: z44.number().optional().describe("Optional offset for pagination"),
25090
- limit: z44.number().optional().describe("Optional maximum number of fixes to return"),
25091
- fileFilter: z44.array(z44.string()).optional().describe(
25839
+ offset: z43.number().optional().describe("Optional offset for pagination"),
25840
+ limit: z43.number().optional().describe("Optional maximum number of fixes to return"),
25841
+ fileFilter: z43.array(z43.string()).optional().describe(
25092
25842
  "Optional list of file paths relative to the path parameter to filter fixes by. INCOMPATIBLE with fetchFixesFromAnyFile"
25093
25843
  ),
25094
- fetchFixesFromAnyFile: z44.boolean().optional().describe(
25844
+ fetchFixesFromAnyFile: z43.boolean().optional().describe(
25095
25845
  "Optional boolean to fetch fixes for all files. INCOMPATIBLE with fileFilter"
25096
25846
  )
25097
25847
  }));
@@ -25106,8 +25856,8 @@ Call this tool instead of ${MCP_TOOL_SCAN_AND_FIX_VULNERABILITIES} when you only
25106
25856
  `Invalid path: potential security risk detected in path: ${pathValidationResult.error}`
25107
25857
  );
25108
25858
  }
25109
- const path27 = pathValidationResult.path;
25110
- const gitService = new GitService(path27, log);
25859
+ const path30 = pathValidationResult.path;
25860
+ const gitService = new GitService(path30, log);
25111
25861
  const gitValidation = await gitService.validateRepository();
25112
25862
  if (!gitValidation.isValid) {
25113
25863
  throw new Error(`Invalid git repository: ${gitValidation.error}`);
@@ -25156,7 +25906,7 @@ Call this tool instead of ${MCP_TOOL_SCAN_AND_FIX_VULNERABILITIES} when you only
25156
25906
  };
25157
25907
 
25158
25908
  // src/mcp/tools/mcpChecker/mcpCheckerTool.ts
25159
- import z45 from "zod";
25909
+ import z44 from "zod";
25160
25910
 
25161
25911
  // src/mcp/tools/mcpChecker/mcpCheckerService.ts
25162
25912
  var _McpCheckerService = class _McpCheckerService {
@@ -25217,7 +25967,7 @@ var McpCheckerTool = class extends BaseTool {
25217
25967
  __publicField(this, "displayName", "MCP Checker");
25218
25968
  // A detailed description to guide the LLM on when and how to invoke this tool.
25219
25969
  __publicField(this, "description", "Check the MCP servers running on this IDE against organization policies.");
25220
- __publicField(this, "inputValidationSchema", z45.object({}));
25970
+ __publicField(this, "inputValidationSchema", z44.object({}));
25221
25971
  __publicField(this, "inputSchema", {
25222
25972
  type: "object",
25223
25973
  properties: {},
@@ -25243,7 +25993,7 @@ var McpCheckerTool = class extends BaseTool {
25243
25993
  };
25244
25994
 
25245
25995
  // src/mcp/tools/scanAndFixVulnerabilities/ScanAndFixVulnerabilitiesTool.ts
25246
- import z46 from "zod";
25996
+ import z45 from "zod";
25247
25997
  init_configs();
25248
25998
 
25249
25999
  // src/mcp/tools/scanAndFixVulnerabilities/ScanAndFixVulnerabilitiesService.ts
@@ -25431,17 +26181,17 @@ Example payload:
25431
26181
  "rescan": false
25432
26182
  }`);
25433
26183
  __publicField(this, "hasAuthentication", true);
25434
- __publicField(this, "inputValidationSchema", z46.object({
25435
- path: z46.string().describe(
26184
+ __publicField(this, "inputValidationSchema", z45.object({
26185
+ path: z45.string().describe(
25436
26186
  "Full local path to repository to scan and fix vulnerabilities"
25437
26187
  ),
25438
- offset: z46.number().optional().describe("Optional offset for pagination"),
25439
- limit: z46.number().optional().describe("Optional maximum number of results to return"),
25440
- maxFiles: z46.number().optional().describe(
26188
+ offset: z45.number().optional().describe("Optional offset for pagination"),
26189
+ limit: z45.number().optional().describe("Optional maximum number of results to return"),
26190
+ maxFiles: z45.number().optional().describe(
25441
26191
  `Optional maximum number of files to scan (default: ${MCP_DEFAULT_MAX_FILES_TO_SCAN}). Increase for comprehensive scans of larger codebases or decrease for faster focused scans.`
25442
26192
  ),
25443
- rescan: z46.boolean().optional().describe("Optional whether to rescan the repository"),
25444
- scanRecentlyChangedFiles: z46.boolean().optional().describe(
26193
+ rescan: z45.boolean().optional().describe("Optional whether to rescan the repository"),
26194
+ scanRecentlyChangedFiles: z45.boolean().optional().describe(
25445
26195
  "Optional whether to automatically scan recently changed files when no changed files are found in git status. If false, the tool will prompt the user instead."
25446
26196
  )
25447
26197
  }));
@@ -25492,9 +26242,9 @@ Example payload:
25492
26242
  `Invalid path: potential security risk detected in path: ${pathValidationResult.error}`
25493
26243
  );
25494
26244
  }
25495
- const path27 = pathValidationResult.path;
26245
+ const path30 = pathValidationResult.path;
25496
26246
  const files = await getLocalFiles({
25497
- path: path27,
26247
+ path: path30,
25498
26248
  maxFileSize: MCP_MAX_FILE_SIZE,
25499
26249
  maxFiles: args.maxFiles,
25500
26250
  scanContext: ScanContext.USER_REQUEST,
@@ -25514,7 +26264,7 @@ Example payload:
25514
26264
  try {
25515
26265
  const fixResult = await this.vulnerabilityFixService.processVulnerabilities({
25516
26266
  fileList: files.map((file) => file.relativePath),
25517
- repositoryPath: path27,
26267
+ repositoryPath: path30,
25518
26268
  offset: args.offset,
25519
26269
  limit: args.limit,
25520
26270
  isRescan: args.rescan || !!args.maxFiles
@@ -25618,7 +26368,7 @@ var mcpHandler = async (_args) => {
25618
26368
  };
25619
26369
 
25620
26370
  // src/args/commands/review.ts
25621
- import fs24 from "fs";
26371
+ import fs26 from "fs";
25622
26372
  import chalk12 from "chalk";
25623
26373
  function reviewBuilder(yargs2) {
25624
26374
  return yargs2.option("f", {
@@ -25655,7 +26405,7 @@ function reviewBuilder(yargs2) {
25655
26405
  ).help();
25656
26406
  }
25657
26407
  function validateReviewOptions(argv) {
25658
- if (!fs24.existsSync(argv.f)) {
26408
+ if (!fs26.existsSync(argv.f)) {
25659
26409
  throw new CliError(`
25660
26410
  Can't access ${chalk12.bold(argv.f)}`);
25661
26411
  }
@@ -25749,15 +26499,76 @@ async function addScmTokenHandler(args) {
25749
26499
  }
25750
26500
 
25751
26501
  // src/features/codeium_intellij/data_collector.ts
25752
- import { z as z47 } from "zod";
26502
+ import { z as z46 } from "zod";
26503
+
26504
+ // src/utils/read-stdin.ts
26505
+ import { setTimeout as setTimeout4 } from "timers";
26506
+ var DEFAULT_TIMEOUT_MS = 1e4;
26507
+ var noop = () => {
26508
+ };
26509
+ var noopLogger = {
26510
+ debug: noop,
26511
+ error: noop
26512
+ };
26513
+ async function readStdinData(config2) {
26514
+ const logger3 = config2?.logger ?? noopLogger;
26515
+ const timeoutMs = config2?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
26516
+ logger3.debug("Reading stdin data");
26517
+ return new Promise((resolve, reject) => {
26518
+ let inputData = "";
26519
+ let settled = false;
26520
+ const timer = setTimeout4(() => {
26521
+ if (!settled) {
26522
+ settled = true;
26523
+ process.stdin.destroy();
26524
+ reject(new Error("Timed out reading from stdin"));
26525
+ }
26526
+ }, timeoutMs);
26527
+ process.stdin.setEncoding("utf-8");
26528
+ process.stdin.on("data", (chunk) => {
26529
+ inputData += chunk;
26530
+ });
26531
+ process.stdin.on("end", () => {
26532
+ if (settled) return;
26533
+ settled = true;
26534
+ clearTimeout(timer);
26535
+ try {
26536
+ const parsedData = JSON.parse(inputData);
26537
+ logger3.debug(
26538
+ {
26539
+ data: { keys: Object.keys(parsedData) }
26540
+ },
26541
+ "Parsed stdin data"
26542
+ );
26543
+ resolve(parsedData);
26544
+ } catch (error) {
26545
+ const msg = `Failed to parse JSON from stdin: ${error.message}`;
26546
+ logger3.error(msg);
26547
+ reject(new Error(msg));
26548
+ }
26549
+ });
26550
+ process.stdin.on("error", (error) => {
26551
+ if (settled) return;
26552
+ settled = true;
26553
+ clearTimeout(timer);
26554
+ logger3.error(
26555
+ { data: { error: error.message } },
26556
+ "Error reading from stdin"
26557
+ );
26558
+ reject(new Error(`Error reading from stdin: ${error.message}`));
26559
+ });
26560
+ });
26561
+ }
26562
+
26563
+ // src/features/codeium_intellij/data_collector.ts
25753
26564
  init_client_generates();
25754
26565
  init_urlParser2();
25755
26566
 
25756
26567
  // src/features/codeium_intellij/codeium_language_server_grpc_client.ts
25757
- import path24 from "path";
26568
+ import path27 from "path";
25758
26569
  import * as grpc from "@grpc/grpc-js";
25759
26570
  import * as protoLoader from "@grpc/proto-loader";
25760
- var PROTO_PATH = path24.join(
26571
+ var PROTO_PATH = path27.join(
25761
26572
  getModuleRootDir(),
25762
26573
  "src/features/codeium_intellij/proto/exa/language_server_pb/language_server.proto"
25763
26574
  );
@@ -25769,7 +26580,7 @@ function loadProto() {
25769
26580
  defaults: true,
25770
26581
  oneofs: true,
25771
26582
  includeDirs: [
25772
- path24.join(getModuleRootDir(), "src/features/codeium_intellij/proto")
26583
+ path27.join(getModuleRootDir(), "src/features/codeium_intellij/proto")
25773
26584
  ]
25774
26585
  });
25775
26586
  return grpc.loadPackageDefinition(
@@ -25823,30 +26634,30 @@ async function getGrpcClient(port, csrf3) {
25823
26634
  }
25824
26635
 
25825
26636
  // src/features/codeium_intellij/parse_intellij_logs.ts
25826
- import fs25 from "fs";
25827
- import os12 from "os";
25828
- import path25 from "path";
26637
+ import fs27 from "fs";
26638
+ import os14 from "os";
26639
+ import path28 from "path";
25829
26640
  function getLogsDir() {
25830
26641
  if (process.platform === "darwin") {
25831
- return path25.join(os12.homedir(), "Library/Logs/JetBrains");
26642
+ return path28.join(os14.homedir(), "Library/Logs/JetBrains");
25832
26643
  } else if (process.platform === "win32") {
25833
- return path25.join(
25834
- process.env["LOCALAPPDATA"] || path25.join(os12.homedir(), "AppData/Local"),
26644
+ return path28.join(
26645
+ process.env["LOCALAPPDATA"] || path28.join(os14.homedir(), "AppData/Local"),
25835
26646
  "JetBrains"
25836
26647
  );
25837
26648
  } else {
25838
- return path25.join(os12.homedir(), ".cache/JetBrains");
26649
+ return path28.join(os14.homedir(), ".cache/JetBrains");
25839
26650
  }
25840
26651
  }
25841
26652
  function parseIdeLogDir(ideLogDir) {
25842
- const logFiles = fs25.readdirSync(ideLogDir).filter((f) => /^idea(\.\d+)?\.log$/.test(f)).map((f) => ({
26653
+ const logFiles = fs27.readdirSync(ideLogDir).filter((f) => /^idea(\.\d+)?\.log$/.test(f)).map((f) => ({
25843
26654
  name: f,
25844
- mtime: fs25.statSync(path25.join(ideLogDir, f)).mtimeMs
26655
+ mtime: fs27.statSync(path28.join(ideLogDir, f)).mtimeMs
25845
26656
  })).sort((a, b) => a.mtime - b.mtime).map((f) => f.name);
25846
26657
  let latestCsrf = null;
25847
26658
  let latestPort = null;
25848
26659
  for (const logFile of logFiles) {
25849
- const lines = fs25.readFileSync(path25.join(ideLogDir, logFile), "utf-8").split("\n");
26660
+ const lines = fs27.readFileSync(path28.join(ideLogDir, logFile), "utf-8").split("\n");
25850
26661
  for (const line of lines) {
25851
26662
  if (!line.includes(
25852
26663
  "com.codeium.intellij.language_server.LanguageServerProcessHandler"
@@ -25872,13 +26683,13 @@ function parseIdeLogDir(ideLogDir) {
25872
26683
  function findRunningCodeiumLanguageServers() {
25873
26684
  const results = [];
25874
26685
  const logsDir = getLogsDir();
25875
- if (!fs25.existsSync(logsDir)) return results;
25876
- for (const ide of fs25.readdirSync(logsDir)) {
25877
- let ideLogDir = path25.join(logsDir, ide);
26686
+ if (!fs27.existsSync(logsDir)) return results;
26687
+ for (const ide of fs27.readdirSync(logsDir)) {
26688
+ let ideLogDir = path28.join(logsDir, ide);
25878
26689
  if (process.platform !== "darwin") {
25879
- ideLogDir = path25.join(ideLogDir, "log");
26690
+ ideLogDir = path28.join(ideLogDir, "log");
25880
26691
  }
25881
- if (!fs25.existsSync(ideLogDir) || !fs25.statSync(ideLogDir).isDirectory()) {
26692
+ if (!fs27.existsSync(ideLogDir) || !fs27.statSync(ideLogDir).isDirectory()) {
25882
26693
  continue;
25883
26694
  }
25884
26695
  const result = parseIdeLogDir(ideLogDir);
@@ -25890,8 +26701,8 @@ function findRunningCodeiumLanguageServers() {
25890
26701
  }
25891
26702
 
25892
26703
  // src/features/codeium_intellij/data_collector.ts
25893
- var HookDataSchema2 = z47.object({
25894
- trajectory_id: z47.string()
26704
+ var HookDataSchema = z46.object({
26705
+ trajectory_id: z46.string()
25895
26706
  });
25896
26707
  async function processAndUploadHookData() {
25897
26708
  const tracePayload = await getTraceDataForHook();
@@ -25919,12 +26730,12 @@ async function processAndUploadHookData() {
25919
26730
  console.warn("Failed to upload trace data:", e);
25920
26731
  }
25921
26732
  }
25922
- function validateHookData2(data) {
25923
- return HookDataSchema2.parse(data);
26733
+ function validateHookData(data) {
26734
+ return HookDataSchema.parse(data);
25924
26735
  }
25925
26736
  async function getTraceDataForHook() {
25926
26737
  const rawData = await readStdinData();
25927
- const hookData = validateHookData2(rawData);
26738
+ const hookData = validateHookData(rawData);
25928
26739
  return await getTraceDataForTrajectory(hookData.trajectory_id);
25929
26740
  }
25930
26741
  async function getTraceDataForTrajectory(trajectoryId) {
@@ -26058,11 +26869,11 @@ function processChatStepCodeAction(step) {
26058
26869
 
26059
26870
  // src/features/codeium_intellij/install_hook.ts
26060
26871
  import fsPromises5 from "fs/promises";
26061
- import os13 from "os";
26062
- import path26 from "path";
26872
+ import os15 from "os";
26873
+ import path29 from "path";
26063
26874
  import chalk14 from "chalk";
26064
26875
  function getCodeiumHooksPath() {
26065
- return path26.join(os13.homedir(), ".codeium", "hooks.json");
26876
+ return path29.join(os15.homedir(), ".codeium", "hooks.json");
26066
26877
  }
26067
26878
  async function readCodeiumHooks() {
26068
26879
  const hooksPath = getCodeiumHooksPath();
@@ -26075,7 +26886,7 @@ async function readCodeiumHooks() {
26075
26886
  }
26076
26887
  async function writeCodeiumHooks(config2) {
26077
26888
  const hooksPath = getCodeiumHooksPath();
26078
- const dir = path26.dirname(hooksPath);
26889
+ const dir = path29.dirname(hooksPath);
26079
26890
  await fsPromises5.mkdir(dir, { recursive: true });
26080
26891
  await fsPromises5.writeFile(
26081
26892
  hooksPath,
@@ -26246,9 +27057,16 @@ var parseArgs = async (args) => {
26246
27057
  claudeCodeInstallHookHandler
26247
27058
  ).command(
26248
27059
  mobbCliCommand.claudeCodeProcessHook,
26249
- chalk15.bold("Process Claude Code hook data and upload to backend."),
27060
+ chalk15.bold("Process Claude Code hook data (legacy \u2014 spawns daemon)."),
26250
27061
  claudeCodeProcessHookBuilder,
26251
27062
  claudeCodeProcessHookHandler
27063
+ ).command(
27064
+ mobbCliCommand.claudeCodeDaemon,
27065
+ chalk15.bold(
27066
+ "Run the background daemon for Claude Code transcript processing."
27067
+ ),
27068
+ claudeCodeDaemonBuilder,
27069
+ claudeCodeDaemonHandler
26252
27070
  ).command(
26253
27071
  mobbCliCommand.windsurfIntellijInstallHook,
26254
27072
  chalk15.bold("Install Windsurf IntelliJ hooks for data collection."),