mobbdev 1.2.68 → 1.3.0

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