mobbdev 1.3.5 → 1.3.7

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
@@ -326,6 +326,7 @@ var init_client_generates = __esm({
326
326
  IssueType_Enum2["SystemExitShouldReraise"] = "SYSTEM_EXIT_SHOULD_RERAISE";
327
327
  IssueType_Enum2["SystemInformationLeak"] = "SYSTEM_INFORMATION_LEAK";
328
328
  IssueType_Enum2["SystemInformationLeakExternal"] = "SYSTEM_INFORMATION_LEAK_EXTERNAL";
329
+ IssueType_Enum2["TaintedNumericCast"] = "TAINTED_NUMERIC_CAST";
329
330
  IssueType_Enum2["TarSlip"] = "TAR_SLIP";
330
331
  IssueType_Enum2["TrustBoundaryViolation"] = "TRUST_BOUNDARY_VIOLATION";
331
332
  IssueType_Enum2["TypeConfusion"] = "TYPE_CONFUSION";
@@ -1450,7 +1451,8 @@ var init_getIssueType = __esm({
1450
1451
  ["DJANGO_BLANK_FIELD_NEEDS_NULL_OR_DEFAULT" /* DjangoBlankFieldNeedsNullOrDefault */]: "Django Blank Field Needs Null or Default",
1451
1452
  ["REDUNDANT_NIL_ERROR_CHECK" /* RedundantNilErrorCheck */]: "Redundant Nil Error Check",
1452
1453
  ["MISSING_WORKFLOW_PERMISSIONS" /* MissingWorkflowPermissions */]: "Missing Workflow Permissions",
1453
- ["EXCESSIVE_SECRETS_EXPOSURE" /* ExcessiveSecretsExposure */]: "Excessive Secrets Exposure"
1454
+ ["EXCESSIVE_SECRETS_EXPOSURE" /* ExcessiveSecretsExposure */]: "Excessive Secrets Exposure",
1455
+ ["TAINTED_NUMERIC_CAST" /* TaintedNumericCast */]: "Tainted Numeric Cast"
1454
1456
  };
1455
1457
  issueTypeZ = z.nativeEnum(IssueType_Enum);
1456
1458
  getIssueTypeFriendlyString = (issueType) => {
@@ -3577,8 +3579,8 @@ var init_FileUtils = __esm({
3577
3579
  const fullPath = path.join(dir, item);
3578
3580
  try {
3579
3581
  await fsPromises.access(fullPath, fs.constants.R_OK);
3580
- const stat2 = await fsPromises.stat(fullPath);
3581
- if (stat2.isDirectory()) {
3582
+ const stat3 = await fsPromises.stat(fullPath);
3583
+ if (stat3.isDirectory()) {
3582
3584
  if (isRootLevel && excludedRootDirectories.includes(item)) {
3583
3585
  continue;
3584
3586
  }
@@ -3590,7 +3592,7 @@ var init_FileUtils = __esm({
3590
3592
  name: item,
3591
3593
  fullPath,
3592
3594
  relativePath: path.relative(rootDir, fullPath),
3593
- time: stat2.mtime.getTime(),
3595
+ time: stat3.mtime.getTime(),
3594
3596
  isFile: true
3595
3597
  });
3596
3598
  }
@@ -4642,7 +4644,8 @@ var fixDetailsData = {
4642
4644
  ["DJANGO_BLANK_FIELD_NEEDS_NULL_OR_DEFAULT" /* DjangoBlankFieldNeedsNullOrDefault */]: void 0,
4643
4645
  ["REDUNDANT_NIL_ERROR_CHECK" /* RedundantNilErrorCheck */]: void 0,
4644
4646
  ["MISSING_WORKFLOW_PERMISSIONS" /* MissingWorkflowPermissions */]: void 0,
4645
- ["EXCESSIVE_SECRETS_EXPOSURE" /* ExcessiveSecretsExposure */]: void 0
4647
+ ["EXCESSIVE_SECRETS_EXPOSURE" /* ExcessiveSecretsExposure */]: void 0,
4648
+ ["TAINTED_NUMERIC_CAST" /* TaintedNumericCast */]: void 0
4646
4649
  };
4647
4650
 
4648
4651
  // src/features/analysis/scm/shared/src/commitDescriptionMarkup.ts
@@ -6174,6 +6177,17 @@ var openRedirect3 = {
6174
6177
  }
6175
6178
  };
6176
6179
 
6180
+ // src/features/analysis/scm/shared/src/storedQuestionData/python/ssrf.ts
6181
+ var ssrf5 = {
6182
+ domainsAllowlist: {
6183
+ content: () => "Allowed URL prefixes",
6184
+ description: () => `The security risk of this issue is the ability of an attacker to provide input that shoots HTTP requests from your server to arbitrary URLs, including internal ones, like \`https://admin.mycompany.com\`
6185
+  
6186
+   To eliminate the risk and fix the issue, check out your app logic and make a whitelist of URLs this API should be allowed to call.`,
6187
+ guidance: () => ""
6188
+ }
6189
+ };
6190
+
6177
6191
  // src/features/analysis/scm/shared/src/storedQuestionData/python/uncheckedLoopCondition.ts
6178
6192
  var uncheckedLoopCondition3 = {
6179
6193
  loopLimit: {
@@ -6195,7 +6209,8 @@ var vulnerabilities14 = {
6195
6209
  ["OPEN_REDIRECT" /* OpenRedirect */]: openRedirect3,
6196
6210
  ["UNCHECKED_LOOP_CONDITION" /* UncheckedLoopCondition */]: uncheckedLoopCondition3,
6197
6211
  ["DUPLICATED_STRINGS" /* DuplicatedStrings */]: duplicatedStrings2,
6198
- ["MISSING_ENCODING_FILE_OPEN" /* MissingEncodingFileOpen */]: missingEncoding
6212
+ ["MISSING_ENCODING_FILE_OPEN" /* MissingEncodingFileOpen */]: missingEncoding,
6213
+ ["SSRF" /* Ssrf */]: ssrf5
6199
6214
  };
6200
6215
  var python_default2 = vulnerabilities14;
6201
6216
 
@@ -7117,7 +7132,7 @@ async function getAdoSdk(params) {
7117
7132
  const url = new URL(repoUrl);
7118
7133
  const origin = url.origin.toLowerCase().endsWith(".visualstudio.com") ? DEFUALT_ADO_ORIGIN : url.origin.toLowerCase();
7119
7134
  const params2 = `path=/&versionDescriptor[versionOptions]=0&versionDescriptor[versionType]=commit&versionDescriptor[version]=${branch}&resolveLfs=true&$format=zip&api-version=5.0&download=true`;
7120
- const path30 = [
7135
+ const path32 = [
7121
7136
  prefixPath,
7122
7137
  owner,
7123
7138
  projectName,
@@ -7128,7 +7143,7 @@ async function getAdoSdk(params) {
7128
7143
  "items",
7129
7144
  "items"
7130
7145
  ].filter(Boolean).join("/");
7131
- return new URL(`${path30}?${params2}`, origin).toString();
7146
+ return new URL(`${path32}?${params2}`, origin).toString();
7132
7147
  },
7133
7148
  async getAdoBranchList({ repoUrl }) {
7134
7149
  try {
@@ -7217,8 +7232,8 @@ async function getAdoSdk(params) {
7217
7232
  const changeType = entry.changeType;
7218
7233
  return changeType !== 16 && entry.item?.path;
7219
7234
  }).map((entry) => {
7220
- const path30 = entry.item.path;
7221
- return path30.startsWith("/") ? path30.slice(1) : path30;
7235
+ const path32 = entry.item.path;
7236
+ return path32.startsWith("/") ? path32.slice(1) : path32;
7222
7237
  });
7223
7238
  },
7224
7239
  async searchAdoPullRequests({
@@ -8091,6 +8106,15 @@ var BitbucketParseResultZ = z20.object({
8091
8106
  repoName: z20.string(),
8092
8107
  hostname: z20.literal(BITBUCKET_HOSTNAME)
8093
8108
  });
8109
+ var UserWorkspacePermissionsRepositoriesResponseZ = z20.object({
8110
+ values: z20.array(
8111
+ z20.object({
8112
+ repository: z20.object({
8113
+ full_name: z20.string().optional()
8114
+ }).optional()
8115
+ })
8116
+ ).optional()
8117
+ });
8094
8118
  function parseBitbucketOrganizationAndRepo(bitbucketUrl) {
8095
8119
  const parsedGitHubUrl = normalizeUrl(bitbucketUrl);
8096
8120
  const parsingResult = parseScmURL(parsedGitHubUrl, "Bitbucket" /* Bitbucket */);
@@ -8155,12 +8179,17 @@ function getBitbucketSdk(params) {
8155
8179
  const { repoUrl } = params2;
8156
8180
  const { repo_slug, workspace } = parseBitbucketOrganizationAndRepo(repoUrl);
8157
8181
  const fullRepoName = `${workspace}/${repo_slug}`;
8158
- const res = await bitbucketClient.user.listPermissionsForRepos({
8182
+ const res = await bitbucketClient.request({
8183
+ method: "GET",
8184
+ url: "/user/workspaces/{workspace}/permissions/repositories",
8185
+ workspace,
8159
8186
  q: `repository.full_name~"${fullRepoName}"`
8160
8187
  });
8161
- return res.data.values?.some(
8162
- (res2) => res2.repository?.full_name === fullRepoName
8163
- ) ?? false;
8188
+ const parsed = UserWorkspacePermissionsRepositoriesResponseZ.safeParse(
8189
+ res.data
8190
+ );
8191
+ const values = parsed.success ? parsed.data.values : void 0;
8192
+ return values?.some((v) => v.repository?.full_name === fullRepoName) ?? false;
8164
8193
  },
8165
8194
  async createPullRequest(params2) {
8166
8195
  const { repo_slug, workspace } = parseBitbucketOrganizationAndRepo(
@@ -8926,12 +8955,18 @@ function getOctoKit(options) {
8926
8955
  timeout: 1e4
8927
8956
  // 10 second timeout
8928
8957
  },
8929
- retry: options?.isEnableRetries ? {
8930
- doNotRetry: [400, 401, 403, 404, 422],
8931
- // Don't retry on these status codes
8958
+ // Always retry on transient failures. 401 is intentionally retryable:
8959
+ // GitHub briefly returns 401 for an OAuth token in the first few seconds
8960
+ // after it is minted, and without this, validateRepoUrl and every
8961
+ // downstream SCM call can fail permanently on that propagation glitch.
8962
+ // Trade-off: a genuinely revoked/invalid token surfaces after ~14s of
8963
+ // backoff (@octokit/plugin-retry uses retryCount^2 * 1000 ms: 1s, 4s, 9s)
8964
+ // instead of immediately. Acceptable given the alternative is permanent
8965
+ // failure / 10-minute test timeouts.
8966
+ retry: {
8967
+ doNotRetry: [400, 403, 404, 422],
8932
8968
  retries: 3
8933
- // Retry up to 3 times
8934
- } : { enabled: false },
8969
+ },
8935
8970
  throttle: options?.isEnableRetries ? {
8936
8971
  onRateLimit: (retryAfter, options2, octokit, retryCount) => {
8937
8972
  octokit.log.warn(
@@ -9593,10 +9628,13 @@ function getGithubSdk(params = {}) {
9593
9628
  return res;
9594
9629
  },
9595
9630
  /**
9596
- * Search PRs using GitHub's Search API with sorting
9597
- * https://docs.github.com/en/rest/search/search?apiVersion=2022-11-28#search-issues-and-pull-requests
9631
+ * List PRs using GitHub's REST `/repos/{owner}/{repo}/pulls` endpoint.
9632
+ * https://docs.github.com/en/rest/pulls/pulls?apiVersion=2022-11-28#list-pull-requests
9633
+ *
9634
+ * Uses the 5000/hr core rate-limit bucket and reads freshly-created PRs
9635
+ * without the indexing lag of the Search API.
9598
9636
  */
9599
- async searchPullRequests(params2) {
9637
+ async listPullRequests(params2) {
9600
9638
  const {
9601
9639
  owner,
9602
9640
  repo,
@@ -9606,26 +9644,22 @@ function getGithubSdk(params = {}) {
9606
9644
  perPage = 10,
9607
9645
  page = 1
9608
9646
  } = params2;
9609
- let query = `repo:${owner}/${repo} is:pr`;
9610
- if (updatedAfter) {
9611
- const dateStr = updatedAfter.toISOString().split("T")[0];
9612
- query += ` updated:>=${dateStr}`;
9613
- }
9614
- if (state !== "all") {
9615
- query += ` is:${state}`;
9616
- }
9617
- const githubSortField = sort.field === "updated" || sort.field === "created" ? sort.field : "comments";
9618
- const response = await octokit.rest.search.issuesAndPullRequests({
9619
- q: query,
9620
- sort: githubSortField,
9621
- order: sort.order,
9647
+ const restSortField = sort.field === "updated" || sort.field === "created" ? sort.field : "popularity";
9648
+ const response = await octokit.rest.pulls.list({
9649
+ owner,
9650
+ repo,
9651
+ state,
9652
+ sort: restSortField,
9653
+ direction: sort.order,
9622
9654
  per_page: perPage,
9623
9655
  page
9624
9656
  });
9657
+ const filtered = updatedAfter ? response.data.filter((pr) => new Date(pr.updated_at) >= updatedAfter) : response.data;
9658
+ const hitDescCutoff = sort.order === "desc" && updatedAfter !== void 0 && filtered.length < response.data.length;
9659
+ const hasMore = response.data.length === perPage && !hitDescCutoff;
9625
9660
  return {
9626
- items: response.data.items,
9627
- totalCount: response.data.total_count,
9628
- hasMore: page * perPage < response.data.total_count
9661
+ items: filtered,
9662
+ hasMore
9629
9663
  };
9630
9664
  },
9631
9665
  /**
@@ -10225,8 +10259,12 @@ var GithubSCMLib = class extends SCMLib {
10225
10259
  }).map((file) => file.filename);
10226
10260
  }
10227
10261
  /**
10228
- * Override searchSubmitRequests to use GitHub's Search API for efficient pagination.
10229
- * This is much faster than fetching all PRs and filtering in-memory.
10262
+ * Override searchSubmitRequests to use GitHub's REST `/pulls` endpoint.
10263
+ *
10264
+ * The Search API we used previously has a separate 30/min secondary rate
10265
+ * limit and an async index that lags on just-created PRs. `/pulls` hits
10266
+ * GitHub's primary datastore, uses the 5000/hr core bucket, and returns
10267
+ * `head.ref` / `base.ref` so we can populate sourceBranch / targetBranch.
10230
10268
  */
10231
10269
  async searchSubmitRequests(params) {
10232
10270
  this._validateAccessToken();
@@ -10234,7 +10272,7 @@ var GithubSCMLib = class extends SCMLib {
10234
10272
  const page = parseCursorSafe(params.cursor, 1);
10235
10273
  const perPage = params.limit || 10;
10236
10274
  const sort = params.sort || { field: "updated", order: "desc" };
10237
- const searchResult = await this.githubSdk.searchPullRequests({
10275
+ const listResult = await this.githubSdk.listPullRequests({
10238
10276
  owner,
10239
10277
  repo,
10240
10278
  updatedAfter: params.filters?.updatedAfter,
@@ -10243,38 +10281,33 @@ var GithubSCMLib = class extends SCMLib {
10243
10281
  perPage,
10244
10282
  page
10245
10283
  });
10246
- const results = searchResult.items.map((issue) => {
10284
+ const results = listResult.items.map((pr) => {
10247
10285
  let status = "open";
10248
- if (issue.state === "closed") {
10249
- status = issue.pull_request?.merged_at ? "merged" : "closed";
10250
- } else if (issue.draft) {
10286
+ if (pr.state === "closed") {
10287
+ status = pr.merged_at ? "merged" : "closed";
10288
+ } else if (pr.draft) {
10251
10289
  status = "draft";
10252
10290
  }
10253
10291
  return {
10254
- submitRequestId: String(issue.number),
10255
- submitRequestNumber: issue.number,
10256
- title: issue.title,
10292
+ submitRequestId: String(pr.number),
10293
+ submitRequestNumber: pr.number,
10294
+ title: pr.title,
10257
10295
  status,
10258
- sourceBranch: "",
10259
- // Not available in search API
10260
- targetBranch: "",
10261
- // Not available in search API
10262
- authorName: issue.user?.login,
10296
+ sourceBranch: pr.head?.ref ?? "",
10297
+ targetBranch: pr.base?.ref ?? "",
10298
+ authorName: pr.user?.login,
10263
10299
  authorEmail: void 0,
10264
- // Not available in search API
10265
- createdAt: new Date(issue.created_at),
10266
- updatedAt: new Date(issue.updated_at),
10267
- description: issue.body || void 0,
10300
+ createdAt: new Date(pr.created_at),
10301
+ updatedAt: new Date(pr.updated_at),
10302
+ description: pr.body || void 0,
10268
10303
  tickets: [],
10269
- // Would need separate parsing
10270
10304
  changedLines: { added: 0, removed: 0 }
10271
- // Not available in search API
10272
10305
  };
10273
10306
  });
10274
10307
  return {
10275
10308
  results,
10276
- nextCursor: searchResult.hasMore ? String(page + 1) : void 0,
10277
- hasMore: searchResult.hasMore
10309
+ nextCursor: listResult.hasMore ? String(page + 1) : void 0,
10310
+ hasMore: listResult.hasMore
10278
10311
  };
10279
10312
  }
10280
10313
  /**
@@ -13893,13 +13926,13 @@ function maskString(str, showStart = 2, showEnd = 2) {
13893
13926
  }
13894
13927
  return str.slice(0, showStart) + "*".repeat(str.length - showStart - showEnd) + str.slice(-showEnd);
13895
13928
  }
13896
- async function sanitizeDataWithCounts(obj) {
13929
+ async function sanitizeDataWithCounts(obj, options) {
13897
13930
  const counts = {
13898
13931
  detections: { total: 0, high: 0, medium: 0, low: 0 }
13899
13932
  };
13900
13933
  const MAX_SCAN_LENGTH = 1e5;
13901
13934
  const sanitizeString = async (str) => {
13902
- if (str.length > MAX_SCAN_LENGTH) {
13935
+ if (!options?.noSizeLimit && str.length > MAX_SCAN_LENGTH) {
13903
13936
  return str;
13904
13937
  }
13905
13938
  let result = str;
@@ -14358,7 +14391,7 @@ async function prepareAndSendTracyRecords(client, rawRecords, workingDir, option
14358
14391
  `${shouldSanitize ? "sanitize" : "serialize"} ${rawRecords.length} records`,
14359
14392
  () => Promise.all(
14360
14393
  rawRecords.map(async (record, index) => {
14361
- if (record.rawData != null) {
14394
+ if (record.rawData != null && record.rawDataS3Key == null) {
14362
14395
  const serialized = shouldSanitize ? await sanitizeRawData(record.rawData) : JSON.stringify(record.rawData);
14363
14396
  serializedRawDataByIndex.set(index, serialized);
14364
14397
  }
@@ -15028,7 +15061,7 @@ async function postIssueComment(params) {
15028
15061
  fpDescription
15029
15062
  } = params;
15030
15063
  const {
15031
- path: path30,
15064
+ path: path32,
15032
15065
  startLine,
15033
15066
  vulnerabilityReportIssue: {
15034
15067
  vulnerabilityReportIssueTags,
@@ -15043,7 +15076,7 @@ async function postIssueComment(params) {
15043
15076
  Refresh the page in order to see the changes.`,
15044
15077
  pull_number: pullRequest,
15045
15078
  commit_id: commitSha,
15046
- path: path30,
15079
+ path: path32,
15047
15080
  line: startLine
15048
15081
  });
15049
15082
  const commentId = commentRes.data.id;
@@ -15077,7 +15110,7 @@ async function postFixComment(params) {
15077
15110
  scanner
15078
15111
  } = params;
15079
15112
  const {
15080
- path: path30,
15113
+ path: path32,
15081
15114
  startLine,
15082
15115
  vulnerabilityReportIssue: { fixId, vulnerabilityReportIssueTags, category },
15083
15116
  vulnerabilityReportIssueId
@@ -15095,7 +15128,7 @@ async function postFixComment(params) {
15095
15128
  Refresh the page in order to see the changes.`,
15096
15129
  pull_number: pullRequest,
15097
15130
  commit_id: commitSha,
15098
- path: path30,
15131
+ path: path32,
15099
15132
  line: startLine
15100
15133
  });
15101
15134
  const commentId = commentRes.data.id;
@@ -16617,8 +16650,8 @@ async function resolveSkillScanInput(skillInput) {
16617
16650
  if (!fs11.existsSync(resolvedPath)) {
16618
16651
  return skillInput;
16619
16652
  }
16620
- const stat2 = fs11.statSync(resolvedPath);
16621
- if (!stat2.isDirectory()) {
16653
+ const stat3 = fs11.statSync(resolvedPath);
16654
+ if (!stat3.isDirectory()) {
16622
16655
  throw new CliError(
16623
16656
  "Local skill input must be a directory containing SKILL.md"
16624
16657
  );
@@ -17016,8 +17049,10 @@ async function analyzeHandler(args) {
17016
17049
  import { spawn } from "child_process";
17017
17050
 
17018
17051
  // src/features/claude_code/daemon.ts
17019
- import path17 from "path";
17052
+ import { readFileSync, writeFileSync as writeFileSync2 } from "fs";
17053
+ import path19 from "path";
17020
17054
  import { setTimeout as sleep2 } from "timers/promises";
17055
+ import Configstore3 from "configstore";
17021
17056
 
17022
17057
  // src/features/claude_code/daemon_pid_file.ts
17023
17058
  import fs13 from "fs";
@@ -17109,10 +17144,455 @@ var DaemonPidFile = class {
17109
17144
 
17110
17145
  // src/features/claude_code/data_collector.ts
17111
17146
  import { execFile } from "child_process";
17112
- import { createHash as createHash2 } from "crypto";
17113
- import { access, open as open4, readdir, readFile, unlink } from "fs/promises";
17114
- import path14 from "path";
17147
+ import { createHash as createHash3 } from "crypto";
17148
+ import { access, open as open4, readdir, readFile as readFile2, unlink } from "fs/promises";
17149
+ import path16 from "path";
17115
17150
  import { promisify } from "util";
17151
+
17152
+ // src/features/analysis/context_file_processor.ts
17153
+ import { createHash } from "crypto";
17154
+ import path14 from "path";
17155
+ import AdmZip3 from "adm-zip";
17156
+ import pLimit6 from "p-limit";
17157
+ var SANITIZE_CONCURRENCY = 5;
17158
+ function md5Hex(data) {
17159
+ return createHash("md5").update(typeof data === "string" ? Buffer.from(data, "utf-8") : data).digest("hex");
17160
+ }
17161
+ async function sanitizeFileContent(content) {
17162
+ const { sanitizedData } = await sanitizeDataWithCounts(content, {
17163
+ noSizeLimit: true
17164
+ });
17165
+ return sanitizedData;
17166
+ }
17167
+ async function processContextFiles(regularFiles, skillGroups) {
17168
+ const limit = pLimit6(SANITIZE_CONCURRENCY);
17169
+ const processedFiles = await Promise.all(
17170
+ regularFiles.map(
17171
+ (entry) => limit(async () => {
17172
+ const sanitizedContent = await sanitizeFileContent(entry.content);
17173
+ const md5 = md5Hex(sanitizedContent);
17174
+ const sizeBytes = Buffer.byteLength(sanitizedContent, "utf-8");
17175
+ return { entry, sanitizedContent, md5, sizeBytes };
17176
+ })
17177
+ )
17178
+ );
17179
+ const processedSkills = await Promise.all(
17180
+ skillGroups.filter((group) => group.files.length > 0).map(
17181
+ (group) => limit(async () => {
17182
+ const zip = new AdmZip3();
17183
+ const sortedFiles = [...group.files].sort(
17184
+ (a, b) => a.path.localeCompare(b.path)
17185
+ );
17186
+ for (const file of sortedFiles) {
17187
+ const sanitizedContent = await sanitizeFileContent(file.content);
17188
+ const zipEntryName = group.isFolder ? path14.relative(group.skillPath, file.path).replace(/\\/g, "/") : path14.basename(file.path);
17189
+ zip.addFile(zipEntryName, Buffer.from(sanitizedContent, "utf-8"));
17190
+ }
17191
+ const zipBuffer = zip.toBuffer();
17192
+ const md5 = md5Hex(zipBuffer);
17193
+ return { group, zipBuffer, md5, sizeBytes: zipBuffer.byteLength };
17194
+ })
17195
+ )
17196
+ );
17197
+ return { files: processedFiles, skills: processedSkills };
17198
+ }
17199
+
17200
+ // src/features/analysis/context_file_scanner.ts
17201
+ import { readFile, stat } from "fs/promises";
17202
+ import { homedir } from "os";
17203
+ import path15 from "path";
17204
+ import { globby as globby2 } from "globby";
17205
+ var MAX_CONTEXT_FILE_SIZE = 20 * 1024 * 1024;
17206
+ var SKILL_CATEGORY = "skill";
17207
+ var SESSION_TTL_MS = 24 * 60 * 60 * 1e3;
17208
+ var sessionMtimes = /* @__PURE__ */ new Map();
17209
+ function markContextFilesUploaded(sessionId, files, skills) {
17210
+ let entry = sessionMtimes.get(sessionId);
17211
+ if (!entry) {
17212
+ entry = { files: /* @__PURE__ */ new Map(), skills: /* @__PURE__ */ new Map(), lastUpdatedAt: Date.now() };
17213
+ sessionMtimes.set(sessionId, entry);
17214
+ }
17215
+ for (const f of files) {
17216
+ entry.files.set(f.path, f.mtimeMs);
17217
+ }
17218
+ if (skills) {
17219
+ for (const sg of skills) {
17220
+ entry.skills.set(sg.sessionKey, sg.maxMtimeMs);
17221
+ }
17222
+ }
17223
+ entry.lastUpdatedAt = Date.now();
17224
+ }
17225
+ var SCAN_PATHS = {
17226
+ "claude-code": [
17227
+ { glob: "CLAUDE.md", category: "rule", root: "workspace" },
17228
+ { glob: "CLAUDE.local.md", category: "rule", root: "workspace" },
17229
+ { glob: "INSIGHTS.md", category: "rule", root: "workspace" },
17230
+ { glob: "AGENTS.md", category: "rule", root: "workspace" },
17231
+ { glob: ".claude/rules/**/*.md", category: "rule", root: "workspace" },
17232
+ { glob: ".claude/CLAUDE.md", category: "rule", root: "home" },
17233
+ { glob: ".claude/INSIGHTS.md", category: "rule", root: "home" },
17234
+ { glob: ".claude/rules/**/*.md", category: "rule", root: "home" },
17235
+ {
17236
+ glob: ".claude/projects/*/memory/*.md",
17237
+ category: "memory",
17238
+ root: "home"
17239
+ },
17240
+ {
17241
+ glob: ".claude/skills/**/*",
17242
+ category: SKILL_CATEGORY,
17243
+ root: "workspace"
17244
+ },
17245
+ { glob: ".claude/commands/*.md", category: "command", root: "workspace" },
17246
+ {
17247
+ glob: ".claude/agents/*.md",
17248
+ category: "agent-config",
17249
+ root: "workspace"
17250
+ },
17251
+ { glob: ".claude/skills/**/*", category: SKILL_CATEGORY, root: "home" },
17252
+ { glob: ".claude/commands/*.md", category: "command", root: "home" },
17253
+ { glob: ".claude/agents/*.md", category: "agent-config", root: "home" },
17254
+ { glob: ".claude/settings.json", category: "config", root: "workspace" },
17255
+ {
17256
+ glob: ".claude/settings.local.json",
17257
+ category: "config",
17258
+ root: "workspace"
17259
+ },
17260
+ { glob: ".mcp.json", category: "mcp-config", root: "workspace" },
17261
+ { glob: ".claude/.mcp.json", category: "mcp-config", root: "workspace" },
17262
+ { glob: ".claude/settings.json", category: "config", root: "home" },
17263
+ { glob: ".claudeignore", category: "ignore", root: "workspace" }
17264
+ ],
17265
+ cursor: [
17266
+ { glob: ".cursorrules", category: "rule", root: "workspace" },
17267
+ { glob: ".cursor/rules/**/*.mdc", category: "rule", root: "workspace" },
17268
+ { glob: ".cursor/mcp.json", category: "mcp-config", root: "workspace" },
17269
+ { glob: ".cursor/mcp.json", category: "mcp-config", root: "home" },
17270
+ { glob: ".cursorignore", category: "ignore", root: "workspace" }
17271
+ ],
17272
+ copilot: [
17273
+ {
17274
+ glob: ".github/copilot-instructions.md",
17275
+ category: "rule",
17276
+ root: "workspace"
17277
+ },
17278
+ {
17279
+ glob: ".github/instructions/*.instructions.md",
17280
+ category: "rule",
17281
+ root: "workspace"
17282
+ },
17283
+ {
17284
+ glob: ".github/prompts/*.prompt.md",
17285
+ category: SKILL_CATEGORY,
17286
+ root: "workspace"
17287
+ },
17288
+ {
17289
+ glob: ".github/chatmodes/*.chatmode.md",
17290
+ category: SKILL_CATEGORY,
17291
+ root: "workspace"
17292
+ },
17293
+ {
17294
+ glob: ".config/github-copilot/global-copilot-instructions.md",
17295
+ category: "rule",
17296
+ root: "home"
17297
+ }
17298
+ ]
17299
+ };
17300
+ function groupSkills(files, root, baseDir) {
17301
+ const skillFiles = files.filter((f) => f.category === SKILL_CATEGORY);
17302
+ const folderMap = /* @__PURE__ */ new Map();
17303
+ const standalone = [];
17304
+ for (const f of skillFiles) {
17305
+ const rel = path15.relative(baseDir, f.path).replace(/\\/g, "/");
17306
+ const skillsMarker = "skills/";
17307
+ const skillsIdx = rel.indexOf(skillsMarker);
17308
+ if (skillsIdx === -1) {
17309
+ standalone.push(f);
17310
+ continue;
17311
+ }
17312
+ const relFromSkills = rel.slice(skillsIdx + skillsMarker.length);
17313
+ const slashIdx = relFromSkills.indexOf("/");
17314
+ if (slashIdx === -1) {
17315
+ standalone.push(f);
17316
+ } else {
17317
+ const folderName = relFromSkills.slice(0, slashIdx);
17318
+ if (!folderMap.has(folderName)) {
17319
+ folderMap.set(folderName, []);
17320
+ }
17321
+ folderMap.get(folderName).push(f);
17322
+ }
17323
+ }
17324
+ const groups = [];
17325
+ for (const f of standalone) {
17326
+ const name = path15.basename(f.path, path15.extname(f.path));
17327
+ const sessionKey = `skill:${root}:${name}`;
17328
+ groups.push({
17329
+ name,
17330
+ root,
17331
+ skillPath: f.path,
17332
+ files: [f],
17333
+ isFolder: false,
17334
+ maxMtimeMs: f.mtimeMs,
17335
+ sessionKey
17336
+ });
17337
+ }
17338
+ for (const [folderName, folderFiles] of folderMap) {
17339
+ const maxMtimeMs = Math.max(...folderFiles.map((f) => f.mtimeMs));
17340
+ const anyFile = folderFiles[0];
17341
+ const rel = path15.relative(baseDir, anyFile.path).replace(/\\/g, "/");
17342
+ const skillsIdx = rel.indexOf("skills/");
17343
+ const skillRelPath = rel.slice(
17344
+ 0,
17345
+ skillsIdx + "skills/".length + folderName.length
17346
+ );
17347
+ const skillPath = path15.join(baseDir, skillRelPath);
17348
+ const sessionKey = `skill:${root}:${folderName}`;
17349
+ groups.push({
17350
+ name: folderName,
17351
+ root,
17352
+ skillPath,
17353
+ files: folderFiles,
17354
+ isFolder: true,
17355
+ maxMtimeMs,
17356
+ sessionKey
17357
+ });
17358
+ }
17359
+ return groups;
17360
+ }
17361
+ async function scanContextFiles(workspaceRoot, platform2, sessionId) {
17362
+ const entries = SCAN_PATHS[platform2];
17363
+ if (!entries || entries.length === 0) {
17364
+ return { regularFiles: [], skillGroups: [] };
17365
+ }
17366
+ const now = Date.now();
17367
+ for (const [sid, entry] of sessionMtimes) {
17368
+ if (now - entry.lastUpdatedAt > SESSION_TTL_MS) {
17369
+ sessionMtimes.delete(sid);
17370
+ }
17371
+ }
17372
+ const home = homedir();
17373
+ const sessionEntry = sessionId ? sessionMtimes.get(sessionId) : void 0;
17374
+ const allFiles = [];
17375
+ const skillFilesByRoot = /* @__PURE__ */ new Map();
17376
+ const seenPaths = /* @__PURE__ */ new Set();
17377
+ for (const entry of entries) {
17378
+ const baseDir = entry.root === "home" ? home : workspaceRoot;
17379
+ const matchedFiles = await globby2(entry.glob, {
17380
+ cwd: baseDir,
17381
+ absolute: true,
17382
+ onlyFiles: true,
17383
+ dot: true
17384
+ });
17385
+ for (const filePath of matchedFiles) {
17386
+ if (seenPaths.has(filePath)) {
17387
+ continue;
17388
+ }
17389
+ seenPaths.add(filePath);
17390
+ try {
17391
+ const fileStat = await stat(filePath);
17392
+ if (fileStat.size === 0 || fileStat.size > MAX_CONTEXT_FILE_SIZE || !fileStat.isFile()) {
17393
+ continue;
17394
+ }
17395
+ const content = await readFile(filePath, "utf-8");
17396
+ const sizeBytes = Buffer.byteLength(content, "utf-8");
17397
+ const name = deriveIdentifier(filePath, baseDir);
17398
+ const fileEntry = {
17399
+ name,
17400
+ path: filePath,
17401
+ content,
17402
+ sizeBytes,
17403
+ category: entry.category,
17404
+ mtimeMs: fileStat.mtimeMs
17405
+ };
17406
+ if (entry.category === SKILL_CATEGORY) {
17407
+ let rootFiles = skillFilesByRoot.get(entry.root);
17408
+ if (!rootFiles) {
17409
+ rootFiles = [];
17410
+ skillFilesByRoot.set(entry.root, rootFiles);
17411
+ }
17412
+ rootFiles.push(fileEntry);
17413
+ } else {
17414
+ const prevMtime = sessionEntry?.files.get(filePath);
17415
+ if (prevMtime !== void 0 && fileStat.mtimeMs <= prevMtime) {
17416
+ continue;
17417
+ }
17418
+ allFiles.push(fileEntry);
17419
+ }
17420
+ } catch (_err) {
17421
+ }
17422
+ }
17423
+ }
17424
+ const allSkillGroups = [];
17425
+ for (const [root, files] of skillFilesByRoot) {
17426
+ const baseDir = root === "home" ? home : workspaceRoot;
17427
+ const groups = groupSkills(files, root, baseDir);
17428
+ for (const group of groups) {
17429
+ if (sessionEntry) {
17430
+ const prevMtime = sessionEntry.skills.get(group.sessionKey);
17431
+ if (prevMtime !== void 0 && group.maxMtimeMs <= prevMtime) {
17432
+ continue;
17433
+ }
17434
+ }
17435
+ allSkillGroups.push(group);
17436
+ }
17437
+ }
17438
+ return { regularFiles: allFiles, skillGroups: allSkillGroups };
17439
+ }
17440
+ function deriveIdentifier(filePath, baseDir) {
17441
+ const relative2 = path15.relative(baseDir, filePath);
17442
+ if (!relative2.startsWith("..")) {
17443
+ return relative2.replace(/\\/g, "/");
17444
+ }
17445
+ return path15.basename(filePath);
17446
+ }
17447
+
17448
+ // src/features/analysis/context_file_uploader.ts
17449
+ import pLimit7 from "p-limit";
17450
+ init_client_generates();
17451
+ var UPLOAD_CONCURRENCY = 5;
17452
+ async function uploadContextRecords(opts) {
17453
+ const {
17454
+ processedFiles,
17455
+ processedSkills,
17456
+ keyPrefix,
17457
+ url,
17458
+ uploadFields,
17459
+ sessionId,
17460
+ now,
17461
+ platform: platform2,
17462
+ repositoryUrl,
17463
+ clientVersion,
17464
+ onFileError,
17465
+ onSkillError
17466
+ } = opts;
17467
+ const records = [];
17468
+ const uploadedFiles = [];
17469
+ const uploadedSkillGroups = [];
17470
+ const limit = pLimit7(UPLOAD_CONCURRENCY);
17471
+ const extraFields = {
17472
+ ...repositoryUrl !== void 0 && { repositoryUrl },
17473
+ ...clientVersion !== void 0 && { clientVersion }
17474
+ };
17475
+ const tasks = [
17476
+ ...processedFiles.map(
17477
+ (pf) => limit(async () => {
17478
+ const s3Key = `${keyPrefix}ctx-${pf.md5}.bin`;
17479
+ try {
17480
+ await uploadFile({
17481
+ file: Buffer.from(pf.sanitizedContent, "utf-8"),
17482
+ url,
17483
+ uploadKey: s3Key,
17484
+ uploadFields
17485
+ });
17486
+ } catch (err) {
17487
+ onFileError?.(pf.entry.name, err);
17488
+ return;
17489
+ }
17490
+ records.push({
17491
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
17492
+ platform: platform2,
17493
+ recordId: `ctx:${sessionId}:${pf.md5}`,
17494
+ recordTimestamp: now,
17495
+ blameType: "CHAT" /* Chat */,
17496
+ rawDataS3Key: s3Key,
17497
+ ...extraFields,
17498
+ context: {
17499
+ md5: pf.md5,
17500
+ category: pf.entry.category,
17501
+ name: pf.entry.name,
17502
+ sizeBytes: pf.sizeBytes,
17503
+ filePath: pf.entry.path,
17504
+ sessionId
17505
+ }
17506
+ });
17507
+ uploadedFiles.push(pf.entry);
17508
+ })
17509
+ ),
17510
+ ...processedSkills.map(
17511
+ (ps) => limit(async () => {
17512
+ const s3Key = `${keyPrefix}skill-${ps.md5}.zip`;
17513
+ try {
17514
+ await uploadFile({
17515
+ file: ps.zipBuffer,
17516
+ url,
17517
+ uploadKey: s3Key,
17518
+ uploadFields
17519
+ });
17520
+ } catch (err) {
17521
+ onSkillError?.(ps.group.name, err);
17522
+ return;
17523
+ }
17524
+ records.push({
17525
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
17526
+ platform: platform2,
17527
+ recordId: `ctx:${sessionId}:${ps.md5}`,
17528
+ recordTimestamp: now,
17529
+ blameType: "CHAT" /* Chat */,
17530
+ rawDataS3Key: s3Key,
17531
+ ...extraFields,
17532
+ context: {
17533
+ md5: ps.md5,
17534
+ category: SKILL_CATEGORY,
17535
+ name: ps.group.name,
17536
+ sizeBytes: ps.sizeBytes,
17537
+ filePath: ps.group.skillPath,
17538
+ sessionId
17539
+ }
17540
+ });
17541
+ uploadedSkillGroups.push(ps.group);
17542
+ })
17543
+ )
17544
+ ];
17545
+ await Promise.allSettled(tasks);
17546
+ return { records, uploadedFiles, uploadedSkillGroups };
17547
+ }
17548
+ async function runContextFileUploadPipeline(opts) {
17549
+ const {
17550
+ processedFiles,
17551
+ processedSkills,
17552
+ sessionId,
17553
+ platform: platform2,
17554
+ url,
17555
+ uploadFieldsJSON,
17556
+ keyPrefix,
17557
+ repositoryUrl,
17558
+ clientVersion,
17559
+ submitRecords,
17560
+ onFileError,
17561
+ onSkillError
17562
+ } = opts;
17563
+ let uploadFields;
17564
+ try {
17565
+ uploadFields = JSON.parse(uploadFieldsJSON);
17566
+ } catch {
17567
+ return null;
17568
+ }
17569
+ const now = (/* @__PURE__ */ new Date()).toISOString();
17570
+ const { records, uploadedFiles, uploadedSkillGroups } = await uploadContextRecords({
17571
+ processedFiles,
17572
+ processedSkills,
17573
+ keyPrefix,
17574
+ url,
17575
+ uploadFields,
17576
+ sessionId,
17577
+ now,
17578
+ platform: platform2,
17579
+ repositoryUrl,
17580
+ clientVersion,
17581
+ onFileError,
17582
+ onSkillError
17583
+ });
17584
+ if (records.length === 0) {
17585
+ return { fileCount: 0, skillCount: 0 };
17586
+ }
17587
+ await submitRecords(records);
17588
+ markContextFilesUploaded(sessionId, uploadedFiles, uploadedSkillGroups);
17589
+ return {
17590
+ fileCount: uploadedFiles.length,
17591
+ skillCount: uploadedSkillGroups.length
17592
+ };
17593
+ }
17594
+
17595
+ // src/features/claude_code/data_collector.ts
17116
17596
  init_client_generates();
17117
17597
 
17118
17598
  // src/utils/shared-logger/create-logger.ts
@@ -17126,6 +17606,8 @@ var DEFAULT_MAX_LOGS = 1e3;
17126
17606
  var DEFAULT_MAX_HEARTBEAT = 100;
17127
17607
  var LOGS_KEY = "logs";
17128
17608
  var HEARTBEAT_KEY = "heartbeat";
17609
+ var MAX_DATA_CHARS = 2048;
17610
+ var MAX_SCOPE_KEYS = 20;
17129
17611
  function createConfigstoreStream(store, opts) {
17130
17612
  const maxLogs = opts.maxLogs ?? DEFAULT_MAX_LOGS;
17131
17613
  const maxHeartbeat = opts.maxHeartbeat ?? DEFAULT_MAX_HEARTBEAT;
@@ -17141,6 +17623,10 @@ function createConfigstoreStream(store, opts) {
17141
17623
  existing.push(...entries);
17142
17624
  const trimmed = existing.length > max ? existing.slice(-max) : existing;
17143
17625
  store.set(key, trimmed);
17626
+ const prefix = key.includes(":") ? key.split(":")[0] : null;
17627
+ if (prefix) {
17628
+ pruneStaleScopes(prefix);
17629
+ }
17144
17630
  } catch {
17145
17631
  try {
17146
17632
  const lines = `${entries.map((e) => JSON.stringify(e)).join("\n")}
@@ -17150,11 +17636,40 @@ function createConfigstoreStream(store, opts) {
17150
17636
  }
17151
17637
  }
17152
17638
  }
17639
+ function pruneStaleScopes(prefix) {
17640
+ const allKeys = Object.keys(store.all);
17641
+ const scopedKeys = allKeys.filter(
17642
+ (k) => k.startsWith(`${prefix}:`) && Array.isArray(store.get(k))
17643
+ );
17644
+ if (scopedKeys.length <= MAX_SCOPE_KEYS) {
17645
+ return;
17646
+ }
17647
+ const withTimestamp = scopedKeys.map((k) => {
17648
+ const entries = store.get(k);
17649
+ const last = entries.length > 0 ? entries[entries.length - 1] : void 0;
17650
+ return { key: k, lastTs: last?.timestamp ?? "" };
17651
+ });
17652
+ withTimestamp.sort((a, b) => a.lastTs.localeCompare(b.lastTs));
17653
+ const toDelete = withTimestamp.slice(
17654
+ 0,
17655
+ withTimestamp.length - MAX_SCOPE_KEYS
17656
+ );
17657
+ for (const { key: k } of toDelete) {
17658
+ store.delete(k);
17659
+ }
17660
+ }
17153
17661
  const writable = new stream.Writable({
17154
17662
  write(chunk, _encoding, callback) {
17155
17663
  callback();
17156
17664
  try {
17157
17665
  const parsed = JSON.parse(chunk.toString());
17666
+ let data = parsed.data;
17667
+ if (data !== void 0) {
17668
+ const serialized = JSON.stringify(data);
17669
+ if (serialized.length > MAX_DATA_CHARS) {
17670
+ data = `${serialized.slice(0, MAX_DATA_CHARS)}... [truncated, ${serialized.length} chars]`;
17671
+ }
17672
+ }
17158
17673
  const entry = {
17159
17674
  timestamp: parsed.time ? new Date(parsed.time).toISOString() : (/* @__PURE__ */ new Date()).toISOString(),
17160
17675
  level: parsed.level ?? "info",
@@ -17162,7 +17677,7 @@ function createConfigstoreStream(store, opts) {
17162
17677
  ...parsed.durationMs !== void 0 && {
17163
17678
  durationMs: parsed.durationMs
17164
17679
  },
17165
- ...parsed.data !== void 0 && { data: parsed.data }
17680
+ ...data !== void 0 && { data }
17166
17681
  };
17167
17682
  const isHeartbeat = parsed.heartbeat === true;
17168
17683
  if (opts.buffered) {
@@ -17192,8 +17707,8 @@ function createConfigstoreStream(store, opts) {
17192
17707
  heartbeatBuffer.length = 0;
17193
17708
  }
17194
17709
  }
17195
- function setScopePath(path30) {
17196
- scopePath = path30;
17710
+ function setScopePath(path32) {
17711
+ scopePath = path32;
17197
17712
  }
17198
17713
  return { writable, flush, setScopePath };
17199
17714
  }
@@ -17274,10 +17789,10 @@ function createDdBatch(config2) {
17274
17789
  }
17275
17790
 
17276
17791
  // src/utils/shared-logger/hostname.ts
17277
- import { createHash } from "crypto";
17792
+ import { createHash as createHash2 } from "crypto";
17278
17793
  import os5 from "os";
17279
17794
  function hashString(input) {
17280
- return createHash("sha256").update(input).digest("hex").slice(0, 16);
17795
+ return createHash2("sha256").update(input).digest("hex").slice(0, 16);
17281
17796
  }
17282
17797
  function getPlainHostname() {
17283
17798
  try {
@@ -17417,7 +17932,7 @@ function createLogger(config2) {
17417
17932
 
17418
17933
  // src/features/claude_code/hook_logger.ts
17419
17934
  var DD_RUM_TOKEN = true ? "pubf59c0182545bfb4c299175119f1abf9b" : "";
17420
- var CLI_VERSION = true ? "1.3.5" : "unknown";
17935
+ var CLI_VERSION = true ? "1.3.7" : "unknown";
17421
17936
  var NAMESPACE = "mobbdev-claude-code-hook-logs";
17422
17937
  var claudeCodeVersion;
17423
17938
  function buildDdTags() {
@@ -17495,11 +18010,11 @@ async function detectClaudeCodeVersion() {
17495
18010
  }
17496
18011
  function generateSyntheticId(sessionId, timestamp, type2, lineIndex) {
17497
18012
  const input = `${sessionId ?? ""}:${timestamp ?? ""}:${type2 ?? ""}:${lineIndex}`;
17498
- const hash = createHash2("sha256").update(input).digest("hex").slice(0, 16);
18013
+ const hash = createHash3("sha256").update(input).digest("hex").slice(0, 16);
17499
18014
  return `synth:${hash}`;
17500
18015
  }
17501
18016
  function getCursorKey(transcriptPath) {
17502
- const hash = createHash2("sha256").update(transcriptPath).digest("hex").slice(0, 12);
18017
+ const hash = createHash3("sha256").update(transcriptPath).digest("hex").slice(0, 12);
17503
18018
  return `cursor.${hash}`;
17504
18019
  }
17505
18020
  async function resolveTranscriptPath(transcriptPath, sessionId) {
@@ -17508,12 +18023,12 @@ async function resolveTranscriptPath(transcriptPath, sessionId) {
17508
18023
  return transcriptPath;
17509
18024
  } catch {
17510
18025
  }
17511
- const filename = path14.basename(transcriptPath);
17512
- const dirName = path14.basename(path14.dirname(transcriptPath));
17513
- const projectsDir = path14.dirname(path14.dirname(transcriptPath));
18026
+ const filename = path16.basename(transcriptPath);
18027
+ const dirName = path16.basename(path16.dirname(transcriptPath));
18028
+ const projectsDir = path16.dirname(path16.dirname(transcriptPath));
17514
18029
  const baseDirName = dirName.replace(/[-.]claude-worktrees-.+$/, "");
17515
18030
  if (baseDirName !== dirName) {
17516
- const candidate = path14.join(projectsDir, baseDirName, filename);
18031
+ const candidate = path16.join(projectsDir, baseDirName, filename);
17517
18032
  try {
17518
18033
  await access(candidate);
17519
18034
  hookLog.info(
@@ -17535,7 +18050,7 @@ async function resolveTranscriptPath(transcriptPath, sessionId) {
17535
18050
  const dirs = await readdir(projectsDir);
17536
18051
  for (const dir of dirs) {
17537
18052
  if (dir === dirName) continue;
17538
- const candidate = path14.join(projectsDir, dir, filename);
18053
+ const candidate = path16.join(projectsDir, dir, filename);
17539
18054
  try {
17540
18055
  await access(candidate);
17541
18056
  hookLog.info(
@@ -17566,9 +18081,9 @@ async function readNewTranscriptEntries(transcriptPath, sessionId, sessionStore,
17566
18081
  if (cursor?.byteOffset) {
17567
18082
  const fh = await open4(transcriptPath, "r");
17568
18083
  try {
17569
- const stat2 = await fh.stat();
17570
- fileSize = stat2.size;
17571
- if (cursor.byteOffset >= stat2.size) {
18084
+ const stat3 = await fh.stat();
18085
+ fileSize = stat3.size;
18086
+ if (cursor.byteOffset >= stat3.size) {
17572
18087
  hookLog.info({ data: { sessionId } }, "No new data in transcript file");
17573
18088
  return {
17574
18089
  entries: [],
@@ -17576,7 +18091,7 @@ async function readNewTranscriptEntries(transcriptPath, sessionId, sessionStore,
17576
18091
  resolvedTranscriptPath: transcriptPath
17577
18092
  };
17578
18093
  }
17579
- const buf = Buffer.alloc(stat2.size - cursor.byteOffset);
18094
+ const buf = Buffer.alloc(stat3.size - cursor.byteOffset);
17580
18095
  await fh.read(buf, 0, buf.length, cursor.byteOffset);
17581
18096
  content = buf.toString("utf-8");
17582
18097
  } finally {
@@ -17594,7 +18109,7 @@ async function readNewTranscriptEntries(transcriptPath, sessionId, sessionStore,
17594
18109
  "Read transcript file from offset"
17595
18110
  );
17596
18111
  } else {
17597
- content = await readFile(transcriptPath, "utf-8");
18112
+ content = await readFile2(transcriptPath, "utf-8");
17598
18113
  fileSize = Buffer.byteLength(content, "utf-8");
17599
18114
  lineIndexOffset = 0;
17600
18115
  hookLog.debug(
@@ -17693,14 +18208,6 @@ var FILTERED_ENTRY_TYPES = /* @__PURE__ */ new Set([
17693
18208
  // Redundant — the actual user prompt is already captured in the 'user' entry.
17694
18209
  "last-prompt"
17695
18210
  ]);
17696
- var FILTERED_ASSISTANT_TOOLS = /* @__PURE__ */ new Set([
17697
- // Polls for a sub-agent result. The input is just task_id + boilerplate
17698
- // (block, timeout). The actual result is captured in the user:tool_result.
17699
- "TaskOutput",
17700
- // Discovers available deferred/MCP tools. The input is just a search query.
17701
- // The discovered tools are captured in the user:tool_result.
17702
- "ToolSearch"
17703
- ]);
17704
18211
  function filterEntries(entries) {
17705
18212
  const filtered = entries.filter((entry) => {
17706
18213
  const entryType = entry.type ?? "";
@@ -17712,16 +18219,6 @@ function filterEntries(entries) {
17712
18219
  const subtype = typeof data?.["type"] === "string" ? data["type"] : "";
17713
18220
  return !FILTERED_PROGRESS_SUBTYPES.has(subtype);
17714
18221
  }
17715
- if (entryType === "assistant") {
17716
- const message = entry["message"];
17717
- const content = message?.["content"];
17718
- if (Array.isArray(content) && content.length > 0) {
17719
- const block = content[0];
17720
- if (block["type"] === "tool_use" && typeof block["name"] === "string" && FILTERED_ASSISTANT_TOOLS.has(block["name"])) {
17721
- return false;
17722
- }
17723
- }
17724
- }
17725
18222
  return true;
17726
18223
  });
17727
18224
  return { filtered, filteredOut: entries.length - filtered.length };
@@ -17738,9 +18235,9 @@ async function cleanupStaleSessions(configDir) {
17738
18235
  let deletedCount = 0;
17739
18236
  for (const file of files) {
17740
18237
  if (!file.startsWith(prefix) || !file.endsWith(".json")) continue;
17741
- const filePath = path14.join(configDir, file);
18238
+ const filePath = path16.join(configDir, file);
17742
18239
  try {
17743
- const content = JSON.parse(await readFile(filePath, "utf-8"));
18240
+ const content = JSON.parse(await readFile2(filePath, "utf-8"));
17744
18241
  let newest = 0;
17745
18242
  const cursors = content["cursor"];
17746
18243
  if (cursors && typeof cursors === "object") {
@@ -17890,6 +18387,16 @@ async function processTranscript(input, sessionStore, log2, maxEntries = DAEMON_
17890
18387
  entriesSkipped: filteredOut,
17891
18388
  claudeCodeVersion: getClaudeCodeVersion()
17892
18389
  });
18390
+ if (input.cwd) {
18391
+ uploadContextFilesIfNeeded(
18392
+ input.session_id,
18393
+ input.cwd,
18394
+ gqlClient,
18395
+ log2
18396
+ ).catch((err) => {
18397
+ log2.error({ data: { err } }, "uploadContextFilesIfNeeded failed");
18398
+ });
18399
+ }
17893
18400
  return {
17894
18401
  entriesUploaded: entries.length,
17895
18402
  entriesSkipped: filteredOut,
@@ -17906,19 +18413,84 @@ async function processTranscript(input, sessionStore, log2, maxEntries = DAEMON_
17906
18413
  errors: entries.length
17907
18414
  };
17908
18415
  }
18416
+ async function uploadContextFilesIfNeeded(sessionId, cwd, gqlClient, log2) {
18417
+ const { regularFiles, skillGroups } = await scanContextFiles(
18418
+ cwd,
18419
+ "claude-code",
18420
+ sessionId
18421
+ );
18422
+ if (regularFiles.length === 0 && skillGroups.length === 0) {
18423
+ return;
18424
+ }
18425
+ const { files: processedFiles, skills: processedSkills } = await processContextFiles(regularFiles, skillGroups);
18426
+ if (processedFiles.length === 0 && processedSkills.length === 0) {
18427
+ return;
18428
+ }
18429
+ const uploadUrlResult = await gqlClient.getTracyRawDataUploadUrl();
18430
+ const { url, uploadFieldsJSON, keyPrefix } = uploadUrlResult.getTracyRawDataUploadUrl;
18431
+ if (!url || !uploadFieldsJSON || !keyPrefix) {
18432
+ log2.error(
18433
+ { data: { sessionId } },
18434
+ "Failed to get S3 upload URL for context files"
18435
+ );
18436
+ return;
18437
+ }
18438
+ const pipelineResult = await runContextFileUploadPipeline({
18439
+ processedFiles,
18440
+ processedSkills,
18441
+ sessionId,
18442
+ platform: "CLAUDE_CODE" /* ClaudeCode */,
18443
+ url,
18444
+ uploadFieldsJSON,
18445
+ keyPrefix,
18446
+ submitRecords: async (records) => {
18447
+ const r = await prepareAndSendTracyRecords(gqlClient, records, cwd);
18448
+ if (!r.ok) {
18449
+ throw new Error(r.errors?.join(", ") ?? "batch upload failed");
18450
+ }
18451
+ },
18452
+ onFileError: (name, err) => log2.error(
18453
+ { data: { sessionId, name, err } },
18454
+ "Failed to upload context file to S3"
18455
+ ),
18456
+ onSkillError: (name, err) => log2.error(
18457
+ { data: { sessionId, name, err } },
18458
+ "Failed to upload skill zip to S3"
18459
+ )
18460
+ });
18461
+ if (pipelineResult === null) {
18462
+ log2.error(
18463
+ { data: { sessionId } },
18464
+ "Malformed uploadFieldsJSON for context files"
18465
+ );
18466
+ return;
18467
+ }
18468
+ if (pipelineResult.fileCount > 0 || pipelineResult.skillCount > 0) {
18469
+ log2.info(
18470
+ {
18471
+ data: {
18472
+ sessionId,
18473
+ fileCount: pipelineResult.fileCount,
18474
+ skillCount: pipelineResult.skillCount
18475
+ }
18476
+ },
18477
+ "Uploaded context files and skills for session"
18478
+ );
18479
+ }
18480
+ }
17909
18481
 
17910
18482
  // src/features/claude_code/install_hook.ts
17911
18483
  import fs14 from "fs";
17912
18484
  import fsPromises4 from "fs/promises";
17913
18485
  import os6 from "os";
17914
- import path15 from "path";
18486
+ import path17 from "path";
17915
18487
  import chalk11 from "chalk";
17916
18488
 
17917
18489
  // src/features/claude_code/daemon-check-shim.tmpl.js
17918
18490
  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";
17919
18491
 
17920
18492
  // src/features/claude_code/install_hook.ts
17921
- var CLAUDE_SETTINGS_PATH = path15.join(os6.homedir(), ".claude", "settings.json");
18493
+ var CLAUDE_SETTINGS_PATH = path17.join(os6.homedir(), ".claude", "settings.json");
17922
18494
  var RECOMMENDED_MATCHER = "*";
17923
18495
  async function claudeSettingsExists() {
17924
18496
  try {
@@ -18064,18 +18636,18 @@ async function installMobbHooks(options = {}) {
18064
18636
  }
18065
18637
 
18066
18638
  // src/features/claude_code/transcript_scanner.ts
18067
- import { open as open5, readdir as readdir2, stat } from "fs/promises";
18639
+ import { open as open5, readdir as readdir2, stat as stat2 } from "fs/promises";
18068
18640
  import os7 from "os";
18069
- import path16 from "path";
18641
+ import path18 from "path";
18070
18642
  var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
18071
18643
  function getClaudeProjectsDirs() {
18072
18644
  const dirs = [];
18073
18645
  const configDir = process.env["CLAUDE_CONFIG_DIR"];
18074
18646
  if (configDir) {
18075
- dirs.push(path16.join(configDir, "projects"));
18647
+ dirs.push(path18.join(configDir, "projects"));
18076
18648
  }
18077
- dirs.push(path16.join(os7.homedir(), ".config", "claude", "projects"));
18078
- dirs.push(path16.join(os7.homedir(), ".claude", "projects"));
18649
+ dirs.push(path18.join(os7.homedir(), ".config", "claude", "projects"));
18650
+ dirs.push(path18.join(os7.homedir(), ".claude", "projects"));
18079
18651
  return dirs;
18080
18652
  }
18081
18653
  async function collectJsonlFiles(files, dir, projectDir, seen, now, results) {
@@ -18083,12 +18655,12 @@ async function collectJsonlFiles(files, dir, projectDir, seen, now, results) {
18083
18655
  if (!file.endsWith(".jsonl")) continue;
18084
18656
  const sessionId = file.replace(".jsonl", "");
18085
18657
  if (!UUID_RE.test(sessionId)) continue;
18086
- const filePath = path16.join(dir, file);
18658
+ const filePath = path18.join(dir, file);
18087
18659
  if (seen.has(filePath)) continue;
18088
18660
  seen.add(filePath);
18089
18661
  let fileStat;
18090
18662
  try {
18091
- fileStat = await stat(filePath);
18663
+ fileStat = await stat2(filePath);
18092
18664
  } catch {
18093
18665
  continue;
18094
18666
  }
@@ -18114,10 +18686,10 @@ async function scanForTranscripts(projectsDirs = getClaudeProjectsDirs()) {
18114
18686
  continue;
18115
18687
  }
18116
18688
  for (const projName of projectDirs) {
18117
- const projPath = path16.join(projectsDir, projName);
18689
+ const projPath = path18.join(projectsDir, projName);
18118
18690
  let projStat;
18119
18691
  try {
18120
- projStat = await stat(projPath);
18692
+ projStat = await stat2(projPath);
18121
18693
  } catch {
18122
18694
  continue;
18123
18695
  }
@@ -18131,9 +18703,9 @@ async function scanForTranscripts(projectsDirs = getClaudeProjectsDirs()) {
18131
18703
  await collectJsonlFiles(files, projPath, projPath, seen, now, results);
18132
18704
  for (const entry of files) {
18133
18705
  if (!UUID_RE.test(entry)) continue;
18134
- const subagentsDir = path16.join(projPath, entry, "subagents");
18706
+ const subagentsDir = path18.join(projPath, entry, "subagents");
18135
18707
  try {
18136
- const s = await stat(subagentsDir);
18708
+ const s = await stat2(subagentsDir);
18137
18709
  if (!s.isDirectory()) continue;
18138
18710
  const subFiles = await readdir2(subagentsDir);
18139
18711
  await collectJsonlFiles(
@@ -18185,6 +18757,7 @@ async function extractCwdFromTranscript(filePath) {
18185
18757
  // src/features/claude_code/daemon.ts
18186
18758
  async function startDaemon() {
18187
18759
  hookLog.info("Daemon starting");
18760
+ pruneHookLogFile();
18188
18761
  const pidFile = await acquirePidFile();
18189
18762
  async function gracefulExit(code, reason) {
18190
18763
  hookLog.info({ data: { code } }, `Daemon exiting: ${reason}`);
@@ -18231,7 +18804,7 @@ async function startDaemon() {
18231
18804
  for (const transcript of changed) {
18232
18805
  const sessionStore = createSessionConfigStore(transcript.sessionId);
18233
18806
  if (!cleanupConfigDir) {
18234
- cleanupConfigDir = path17.dirname(sessionStore.path);
18807
+ cleanupConfigDir = path19.dirname(sessionStore.path);
18235
18808
  }
18236
18809
  await drainTranscript(transcript, sessionStore, gqlClient);
18237
18810
  }
@@ -18346,6 +18919,53 @@ async function tryAutoUpgradeHooks() {
18346
18919
  hookLog.warn({ err }, "Failed to auto-upgrade hook matcher");
18347
18920
  }
18348
18921
  }
18922
+ var HOOK_LOG_MAX_SCOPE_KEYS = 20;
18923
+ var HOOK_LOG_MAX_ENTRIES_PER_KEY = 200;
18924
+ function pruneHookLogFile() {
18925
+ const logFilePath = new Configstore3("mobbdev-claude-code-hook-logs").path;
18926
+ try {
18927
+ const raw = readFileSync(logFilePath, "utf-8");
18928
+ const data = JSON.parse(raw);
18929
+ const prefixes = /* @__PURE__ */ new Map();
18930
+ for (const key of Object.keys(data)) {
18931
+ const colonIdx = key.indexOf(":");
18932
+ const prefix = colonIdx > 0 ? key.slice(0, colonIdx) : key;
18933
+ const group = prefixes.get(prefix) ?? [];
18934
+ group.push(key);
18935
+ prefixes.set(prefix, group);
18936
+ }
18937
+ let changed = false;
18938
+ for (const [, keys] of prefixes) {
18939
+ if (keys.length <= HOOK_LOG_MAX_SCOPE_KEYS) {
18940
+ continue;
18941
+ }
18942
+ const withTs = keys.map((k) => {
18943
+ const val = data[k];
18944
+ if (!Array.isArray(val) || val.length === 0) {
18945
+ return { key: k, lastTs: "" };
18946
+ }
18947
+ const last = val[val.length - 1];
18948
+ return { key: k, lastTs: last?.timestamp ?? "" };
18949
+ }).sort((a, b) => a.lastTs.localeCompare(b.lastTs));
18950
+ const toDelete = withTs.slice(0, withTs.length - HOOK_LOG_MAX_SCOPE_KEYS);
18951
+ for (const { key } of toDelete) {
18952
+ delete data[key];
18953
+ changed = true;
18954
+ }
18955
+ }
18956
+ for (const [key, val] of Object.entries(data)) {
18957
+ if (Array.isArray(val) && val.length > HOOK_LOG_MAX_ENTRIES_PER_KEY) {
18958
+ data[key] = val.slice(-HOOK_LOG_MAX_ENTRIES_PER_KEY);
18959
+ changed = true;
18960
+ }
18961
+ }
18962
+ if (changed) {
18963
+ writeFileSync2(logFilePath, JSON.stringify(data, null, " "));
18964
+ hookLog.info("Pruned hook log file");
18965
+ }
18966
+ } catch {
18967
+ }
18968
+ }
18349
18969
 
18350
18970
  // src/args/commands/claude_code.ts
18351
18971
  var claudeCodeInstallHookBuilder = (yargs2) => {
@@ -18434,7 +19054,7 @@ import {
18434
19054
  } from "@modelcontextprotocol/sdk/types.js";
18435
19055
 
18436
19056
  // src/mcp/Logger.ts
18437
- import Configstore3 from "configstore";
19057
+ import Configstore4 from "configstore";
18438
19058
 
18439
19059
  // src/mcp/services/WorkspaceService.ts
18440
19060
  var WorkspaceService = class {
@@ -18442,8 +19062,8 @@ var WorkspaceService = class {
18442
19062
  * Sets a known workspace path that was discovered through successful validation
18443
19063
  * @param path The validated workspace path to store
18444
19064
  */
18445
- static setKnownWorkspacePath(path30) {
18446
- this.knownWorkspacePath = path30;
19065
+ static setKnownWorkspacePath(path32) {
19066
+ this.knownWorkspacePath = path32;
18447
19067
  }
18448
19068
  /**
18449
19069
  * Gets the known workspace path that was previously validated
@@ -18520,7 +19140,7 @@ var Logger = class {
18520
19140
  __publicField(this, "lastKnownPath", null);
18521
19141
  this.host = WorkspaceService.getHost();
18522
19142
  this.unknownPathSuffix = Math.floor(1e3 + Math.random() * 9e3).toString();
18523
- this.mobbConfigStore = new Configstore3("mobb-logs", {});
19143
+ this.mobbConfigStore = new Configstore4("mobb-logs", {});
18524
19144
  this.mobbConfigStore.set("version", packageJson.version);
18525
19145
  }
18526
19146
  /**
@@ -19304,7 +19924,7 @@ async function createAuthenticatedMcpGQLClient({
19304
19924
  import { execSync as execSync2 } from "child_process";
19305
19925
  import fs15 from "fs";
19306
19926
  import os8 from "os";
19307
- import path18 from "path";
19927
+ import path20 from "path";
19308
19928
  var IDEs = ["cursor", "windsurf", "webstorm", "vscode", "claude"];
19309
19929
  var runCommand = (cmd) => {
19310
19930
  try {
@@ -19319,7 +19939,7 @@ var gitInfo = {
19319
19939
  };
19320
19940
  var getClaudeWorkspacePaths = () => {
19321
19941
  const home = os8.homedir();
19322
- const claudeIdePath = path18.join(home, ".claude", "ide");
19942
+ const claudeIdePath = path20.join(home, ".claude", "ide");
19323
19943
  const workspacePaths = [];
19324
19944
  if (!fs15.existsSync(claudeIdePath)) {
19325
19945
  return workspacePaths;
@@ -19327,7 +19947,7 @@ var getClaudeWorkspacePaths = () => {
19327
19947
  try {
19328
19948
  const lockFiles = fs15.readdirSync(claudeIdePath).filter((file) => file.endsWith(".lock"));
19329
19949
  for (const lockFile of lockFiles) {
19330
- const lockFilePath = path18.join(claudeIdePath, lockFile);
19950
+ const lockFilePath = path20.join(claudeIdePath, lockFile);
19331
19951
  try {
19332
19952
  const lockContent = JSON.parse(fs15.readFileSync(lockFilePath, "utf8"));
19333
19953
  if (lockContent.workspaceFolders && Array.isArray(lockContent.workspaceFolders)) {
@@ -19352,24 +19972,24 @@ var getMCPConfigPaths = (hostName) => {
19352
19972
  switch (hostName.toLowerCase()) {
19353
19973
  case "cursor":
19354
19974
  return [
19355
- path18.join(currentDir, ".cursor", "mcp.json"),
19975
+ path20.join(currentDir, ".cursor", "mcp.json"),
19356
19976
  // local first
19357
- path18.join(home, ".cursor", "mcp.json")
19977
+ path20.join(home, ".cursor", "mcp.json")
19358
19978
  ];
19359
19979
  case "windsurf":
19360
19980
  return [
19361
- path18.join(currentDir, ".codeium", "mcp_config.json"),
19981
+ path20.join(currentDir, ".codeium", "mcp_config.json"),
19362
19982
  // local first
19363
- path18.join(home, ".codeium", "windsurf", "mcp_config.json")
19983
+ path20.join(home, ".codeium", "windsurf", "mcp_config.json")
19364
19984
  ];
19365
19985
  case "webstorm":
19366
19986
  return [];
19367
19987
  case "visualstudiocode":
19368
19988
  case "vscode":
19369
19989
  return [
19370
- path18.join(currentDir, ".vscode", "mcp.json"),
19990
+ path20.join(currentDir, ".vscode", "mcp.json"),
19371
19991
  // local first
19372
- process.platform === "win32" ? path18.join(home, "AppData", "Roaming", "Code", "User", "mcp.json") : path18.join(
19992
+ process.platform === "win32" ? path20.join(home, "AppData", "Roaming", "Code", "User", "mcp.json") : path20.join(
19373
19993
  home,
19374
19994
  "Library",
19375
19995
  "Application Support",
@@ -19380,13 +20000,13 @@ var getMCPConfigPaths = (hostName) => {
19380
20000
  ];
19381
20001
  case "claude": {
19382
20002
  const claudePaths = [
19383
- path18.join(currentDir, ".claude.json"),
20003
+ path20.join(currentDir, ".claude.json"),
19384
20004
  // local first
19385
- path18.join(home, ".claude.json")
20005
+ path20.join(home, ".claude.json")
19386
20006
  ];
19387
20007
  const workspacePaths = getClaudeWorkspacePaths();
19388
20008
  for (const workspacePath of workspacePaths) {
19389
- claudePaths.push(path18.join(workspacePath, ".mcp.json"));
20009
+ claudePaths.push(path20.join(workspacePath, ".mcp.json"));
19390
20010
  }
19391
20011
  return claudePaths;
19392
20012
  }
@@ -19547,10 +20167,10 @@ var getHostInfo = (additionalMcpList) => {
19547
20167
  const ideConfigPaths = /* @__PURE__ */ new Set();
19548
20168
  for (const ide of IDEs) {
19549
20169
  const configPaths = getMCPConfigPaths(ide);
19550
- configPaths.forEach((path30) => ideConfigPaths.add(path30));
20170
+ configPaths.forEach((path32) => ideConfigPaths.add(path32));
19551
20171
  }
19552
20172
  const uniqueAdditionalPaths = additionalMcpList.filter(
19553
- (path30) => !ideConfigPaths.has(path30)
20173
+ (path32) => !ideConfigPaths.has(path32)
19554
20174
  );
19555
20175
  for (const ide of IDEs) {
19556
20176
  const cfg = readMCPConfig(ide);
@@ -19672,7 +20292,7 @@ init_configs();
19672
20292
  init_configs();
19673
20293
  import fs16 from "fs";
19674
20294
  import os9 from "os";
19675
- import path19 from "path";
20295
+ import path21 from "path";
19676
20296
  var MAX_DEPTH = 2;
19677
20297
  var patterns = ["mcp", "claude"];
19678
20298
  var isFileMatch = (fileName) => {
@@ -19692,7 +20312,7 @@ var searchDir = async (dir, depth = 0) => {
19692
20312
  if (depth > MAX_DEPTH) return results;
19693
20313
  const entries = await fs16.promises.readdir(dir, { withFileTypes: true }).catch(() => []);
19694
20314
  for (const entry of entries) {
19695
- const fullPath = path19.join(dir, entry.name);
20315
+ const fullPath = path21.join(dir, entry.name);
19696
20316
  if (entry.isFile() && isFileMatch(entry.name)) {
19697
20317
  results.push(fullPath);
19698
20318
  } else if (entry.isDirectory()) {
@@ -19709,14 +20329,14 @@ var findSystemMCPConfigs = async () => {
19709
20329
  const home = os9.homedir();
19710
20330
  const platform2 = os9.platform();
19711
20331
  const knownDirs = platform2 === "win32" ? [
19712
- path19.join(home, ".cursor"),
19713
- path19.join(home, "Documents"),
19714
- path19.join(home, "Downloads")
20332
+ path21.join(home, ".cursor"),
20333
+ path21.join(home, "Documents"),
20334
+ path21.join(home, "Downloads")
19715
20335
  ] : [
19716
- path19.join(home, ".cursor"),
19717
- process.env["XDG_CONFIG_HOME"] || path19.join(home, ".config"),
19718
- path19.join(home, "Documents"),
19719
- path19.join(home, "Downloads")
20336
+ path21.join(home, ".cursor"),
20337
+ process.env["XDG_CONFIG_HOME"] || path21.join(home, ".config"),
20338
+ path21.join(home, "Documents"),
20339
+ path21.join(home, "Downloads")
19720
20340
  ];
19721
20341
  const timeoutPromise = new Promise(
19722
20342
  (resolve) => setTimeout(() => {
@@ -22132,13 +22752,13 @@ For a complete security audit workflow, use the \`full-security-audit\` prompt.
22132
22752
  // src/mcp/services/McpDetectionService/CursorMcpDetectionService.ts
22133
22753
  import * as fs19 from "fs";
22134
22754
  import * as os12 from "os";
22135
- import * as path21 from "path";
22755
+ import * as path23 from "path";
22136
22756
 
22137
22757
  // src/mcp/services/McpDetectionService/BaseMcpDetectionService.ts
22138
22758
  init_configs();
22139
22759
  import * as fs18 from "fs";
22140
22760
  import fetch7 from "node-fetch";
22141
- import * as path20 from "path";
22761
+ import * as path22 from "path";
22142
22762
 
22143
22763
  // src/mcp/services/McpDetectionService/McpDetectionServiceUtils.ts
22144
22764
  import * as fs17 from "fs";
@@ -22147,14 +22767,14 @@ import * as os11 from "os";
22147
22767
  // src/mcp/services/McpDetectionService/VscodeMcpDetectionService.ts
22148
22768
  import * as fs20 from "fs";
22149
22769
  import * as os13 from "os";
22150
- import * as path22 from "path";
22770
+ import * as path24 from "path";
22151
22771
 
22152
22772
  // src/mcp/tools/checkForNewAvailableFixes/CheckForNewAvailableFixesTool.ts
22153
22773
  import { z as z42 } from "zod";
22154
22774
 
22155
22775
  // src/mcp/services/PathValidation.ts
22156
22776
  import fs21 from "fs";
22157
- import path23 from "path";
22777
+ import path25 from "path";
22158
22778
  async function validatePath(inputPath) {
22159
22779
  logDebug("Validating MCP path", { inputPath });
22160
22780
  if (/^\/[a-zA-Z]:\//.test(inputPath)) {
@@ -22186,7 +22806,7 @@ async function validatePath(inputPath) {
22186
22806
  logError(error);
22187
22807
  return { isValid: false, error, path: inputPath };
22188
22808
  }
22189
- const normalizedPath = path23.normalize(inputPath);
22809
+ const normalizedPath = path25.normalize(inputPath);
22190
22810
  if (normalizedPath.includes("..")) {
22191
22811
  const error = `Normalized path contains path traversal patterns: ${inputPath}`;
22192
22812
  logError(error);
@@ -22838,7 +23458,7 @@ init_configs();
22838
23458
  import fs22 from "fs/promises";
22839
23459
  import nodePath from "path";
22840
23460
  var getLocalFiles = async ({
22841
- path: path30,
23461
+ path: path32,
22842
23462
  maxFileSize = MCP_MAX_FILE_SIZE,
22843
23463
  maxFiles,
22844
23464
  isAllFilesScan,
@@ -22846,17 +23466,17 @@ var getLocalFiles = async ({
22846
23466
  scanRecentlyChangedFiles
22847
23467
  }) => {
22848
23468
  logDebug(`[${scanContext}] Starting getLocalFiles`, {
22849
- path: path30,
23469
+ path: path32,
22850
23470
  maxFileSize,
22851
23471
  maxFiles,
22852
23472
  isAllFilesScan,
22853
23473
  scanRecentlyChangedFiles
22854
23474
  });
22855
23475
  try {
22856
- const resolvedRepoPath = await fs22.realpath(path30);
23476
+ const resolvedRepoPath = await fs22.realpath(path32);
22857
23477
  logDebug(`[${scanContext}] Resolved repository path`, {
22858
23478
  resolvedRepoPath,
22859
- originalPath: path30
23479
+ originalPath: path32
22860
23480
  });
22861
23481
  const gitService = new GitService(resolvedRepoPath, log);
22862
23482
  const gitValidation = await gitService.validateRepository();
@@ -22869,7 +23489,7 @@ var getLocalFiles = async ({
22869
23489
  if (!gitValidation.isValid || isAllFilesScan) {
22870
23490
  try {
22871
23491
  files = await FileUtils.getLastChangedFiles({
22872
- dir: path30,
23492
+ dir: path32,
22873
23493
  maxFileSize,
22874
23494
  maxFiles,
22875
23495
  isAllFilesScan
@@ -22961,7 +23581,7 @@ var getLocalFiles = async ({
22961
23581
  logError(`${scanContext}Unexpected error in getLocalFiles`, {
22962
23582
  error: error instanceof Error ? error.message : String(error),
22963
23583
  stack: error instanceof Error ? error.stack : void 0,
22964
- path: path30
23584
+ path: path32
22965
23585
  });
22966
23586
  throw error;
22967
23587
  }
@@ -22971,7 +23591,7 @@ var getLocalFiles = async ({
22971
23591
  init_client_generates();
22972
23592
  init_GitService();
22973
23593
  import fs23 from "fs";
22974
- import path24 from "path";
23594
+ import path26 from "path";
22975
23595
  import { z as z41 } from "zod";
22976
23596
  function extractPathFromPatch(patch) {
22977
23597
  const match = patch?.match(/diff --git a\/([^\s]+) b\//);
@@ -23057,7 +23677,7 @@ var LocalMobbFolderService = class {
23057
23677
  "[LocalMobbFolderService] Non-git repository detected, skipping .gitignore operations"
23058
23678
  );
23059
23679
  }
23060
- const mobbFolderPath = path24.join(
23680
+ const mobbFolderPath = path26.join(
23061
23681
  this.repoPath,
23062
23682
  this.defaultMobbFolderName
23063
23683
  );
@@ -23229,7 +23849,7 @@ var LocalMobbFolderService = class {
23229
23849
  mobbFolderPath,
23230
23850
  baseFileName
23231
23851
  );
23232
- const filePath = path24.join(mobbFolderPath, uniqueFileName);
23852
+ const filePath = path26.join(mobbFolderPath, uniqueFileName);
23233
23853
  await fs23.promises.writeFile(filePath, patch, "utf8");
23234
23854
  logInfo("[LocalMobbFolderService] Patch saved successfully", {
23235
23855
  filePath,
@@ -23287,11 +23907,11 @@ var LocalMobbFolderService = class {
23287
23907
  * @returns Unique filename that doesn't conflict with existing files
23288
23908
  */
23289
23909
  getUniqueFileName(folderPath, baseFileName) {
23290
- const baseName = path24.parse(baseFileName).name;
23291
- const extension = path24.parse(baseFileName).ext;
23910
+ const baseName = path26.parse(baseFileName).name;
23911
+ const extension = path26.parse(baseFileName).ext;
23292
23912
  let uniqueFileName = baseFileName;
23293
23913
  let index = 1;
23294
- while (fs23.existsSync(path24.join(folderPath, uniqueFileName))) {
23914
+ while (fs23.existsSync(path26.join(folderPath, uniqueFileName))) {
23295
23915
  uniqueFileName = `${baseName}-${index}${extension}`;
23296
23916
  index++;
23297
23917
  if (index > 1e3) {
@@ -23322,7 +23942,7 @@ var LocalMobbFolderService = class {
23322
23942
  logDebug("[LocalMobbFolderService] Logging patch info", { fixId: fix.id });
23323
23943
  try {
23324
23944
  const mobbFolderPath = await this.getFolder();
23325
- const patchInfoPath = path24.join(mobbFolderPath, "patchInfo.md");
23945
+ const patchInfoPath = path26.join(mobbFolderPath, "patchInfo.md");
23326
23946
  const markdownContent = this.generateFixMarkdown(fix, savedPatchFileName);
23327
23947
  let existingContent = "";
23328
23948
  if (fs23.existsSync(patchInfoPath)) {
@@ -23364,7 +23984,7 @@ var LocalMobbFolderService = class {
23364
23984
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
23365
23985
  const patch = this.extractPatchFromFix(fix);
23366
23986
  const relativePatchedFilePath = patch ? extractPathFromPatch(patch) : null;
23367
- const patchedFilePath = relativePatchedFilePath ? path24.resolve(this.repoPath, relativePatchedFilePath) : null;
23987
+ const patchedFilePath = relativePatchedFilePath ? path26.resolve(this.repoPath, relativePatchedFilePath) : null;
23368
23988
  const fixIdentifier = savedPatchFileName ? savedPatchFileName.replace(".patch", "") : fix.id;
23369
23989
  let markdown = `# Fix ${fixIdentifier}
23370
23990
 
@@ -23702,20 +24322,20 @@ init_configs();
23702
24322
  import {
23703
24323
  existsSync as existsSync6,
23704
24324
  mkdirSync,
23705
- readFileSync as readFileSync3,
24325
+ readFileSync as readFileSync4,
23706
24326
  unlinkSync,
23707
- writeFileSync as writeFileSync2
24327
+ writeFileSync as writeFileSync3
23708
24328
  } from "fs";
23709
24329
  import fs24 from "fs/promises";
23710
24330
  import parseDiff2 from "parse-diff";
23711
- import path25 from "path";
24331
+ import path27 from "path";
23712
24332
  var PatchApplicationService = class {
23713
24333
  /**
23714
24334
  * Gets the appropriate comment syntax for a file based on its extension
23715
24335
  */
23716
24336
  static getCommentSyntax(filePath) {
23717
- const ext = path25.extname(filePath).toLowerCase();
23718
- const basename2 = path25.basename(filePath);
24337
+ const ext = path27.extname(filePath).toLowerCase();
24338
+ const basename2 = path27.basename(filePath);
23719
24339
  const commentMap = {
23720
24340
  // C-style languages (single line comments)
23721
24341
  ".js": "//",
@@ -23923,18 +24543,18 @@ var PatchApplicationService = class {
23923
24543
  }
23924
24544
  );
23925
24545
  }
23926
- const dirPath = path25.dirname(normalizedFilePath);
24546
+ const dirPath = path27.dirname(normalizedFilePath);
23927
24547
  mkdirSync(dirPath, { recursive: true });
23928
- writeFileSync2(normalizedFilePath, finalContent, "utf8");
24548
+ writeFileSync3(normalizedFilePath, finalContent, "utf8");
23929
24549
  return normalizedFilePath;
23930
24550
  }
23931
24551
  static resolvePathWithinRepo({
23932
24552
  repositoryPath,
23933
24553
  targetPath
23934
24554
  }) {
23935
- const repoRoot = path25.resolve(repositoryPath);
23936
- const normalizedPath = path25.resolve(repoRoot, targetPath);
23937
- const repoRootWithSep = repoRoot.endsWith(path25.sep) ? repoRoot : `${repoRoot}${path25.sep}`;
24555
+ const repoRoot = path27.resolve(repositoryPath);
24556
+ const normalizedPath = path27.resolve(repoRoot, targetPath);
24557
+ const repoRootWithSep = repoRoot.endsWith(path27.sep) ? repoRoot : `${repoRoot}${path27.sep}`;
23938
24558
  if (normalizedPath !== repoRoot && !normalizedPath.startsWith(repoRootWithSep)) {
23939
24559
  throw new Error(
23940
24560
  `Security violation: target path ${targetPath} resolves outside repository`
@@ -23943,7 +24563,7 @@ var PatchApplicationService = class {
23943
24563
  return {
23944
24564
  repoRoot,
23945
24565
  normalizedPath,
23946
- relativePath: path25.relative(repoRoot, normalizedPath)
24566
+ relativePath: path27.relative(repoRoot, normalizedPath)
23947
24567
  };
23948
24568
  }
23949
24569
  /**
@@ -24225,7 +24845,7 @@ var PatchApplicationService = class {
24225
24845
  continue;
24226
24846
  }
24227
24847
  try {
24228
- const absolutePath = path25.resolve(repositoryPath, targetFile);
24848
+ const absolutePath = path27.resolve(repositoryPath, targetFile);
24229
24849
  if (existsSync6(absolutePath)) {
24230
24850
  const stats = await fs24.stat(absolutePath);
24231
24851
  const fileModTime = stats.mtime.getTime();
@@ -24451,7 +25071,7 @@ var PatchApplicationService = class {
24451
25071
  fix,
24452
25072
  scanContext
24453
25073
  });
24454
- appliedFiles.push(path25.relative(repositoryPath, actualPath));
25074
+ appliedFiles.push(path27.relative(repositoryPath, actualPath));
24455
25075
  logDebug(`[${scanContext}] Created new file: ${relativePath}`);
24456
25076
  }
24457
25077
  /**
@@ -24487,7 +25107,7 @@ var PatchApplicationService = class {
24487
25107
  `Target file does not exist: ${targetFile} (resolved to: ${absoluteFilePath})`
24488
25108
  );
24489
25109
  }
24490
- const originalContent = readFileSync3(absoluteFilePath, "utf8");
25110
+ const originalContent = readFileSync4(absoluteFilePath, "utf8");
24491
25111
  const modifiedContent = this.applyHunksToFile(
24492
25112
  originalContent,
24493
25113
  fileDiff.chunks
@@ -24500,7 +25120,7 @@ var PatchApplicationService = class {
24500
25120
  fix,
24501
25121
  scanContext
24502
25122
  });
24503
- appliedFiles.push(path25.relative(repositoryPath, actualPath));
25123
+ appliedFiles.push(path27.relative(repositoryPath, actualPath));
24504
25124
  logDebug(`[${scanContext}] Modified file: ${relativePath}`);
24505
25125
  }
24506
25126
  }
@@ -24697,8 +25317,8 @@ init_configs();
24697
25317
  // src/mcp/services/FileOperations.ts
24698
25318
  init_FileUtils();
24699
25319
  import fs25 from "fs";
24700
- import path26 from "path";
24701
- import AdmZip3 from "adm-zip";
25320
+ import path28 from "path";
25321
+ import AdmZip4 from "adm-zip";
24702
25322
  var FileOperations = class {
24703
25323
  /**
24704
25324
  * Creates a ZIP archive containing the specified source files
@@ -24713,14 +25333,14 @@ var FileOperations = class {
24713
25333
  maxFileSize
24714
25334
  }) {
24715
25335
  logDebug("[FileOperations] Packing files");
24716
- const zip = new AdmZip3();
25336
+ const zip = new AdmZip4();
24717
25337
  let packedFilesCount = 0;
24718
25338
  const packedFiles = [];
24719
25339
  const excludedFiles = [];
24720
- const resolvedRepoPath = path26.resolve(repositoryPath);
25340
+ const resolvedRepoPath = path28.resolve(repositoryPath);
24721
25341
  for (const filepath of fileList) {
24722
- const absoluteFilepath = path26.join(repositoryPath, filepath);
24723
- const resolvedFilePath = path26.resolve(absoluteFilepath);
25342
+ const absoluteFilepath = path28.join(repositoryPath, filepath);
25343
+ const resolvedFilePath = path28.resolve(absoluteFilepath);
24724
25344
  if (!resolvedFilePath.startsWith(resolvedRepoPath)) {
24725
25345
  const reason = "potential path traversal security risk";
24726
25346
  logDebug(`[FileOperations] Skipping ${filepath} due to ${reason}`);
@@ -24767,11 +25387,11 @@ var FileOperations = class {
24767
25387
  fileList,
24768
25388
  repositoryPath
24769
25389
  }) {
24770
- const resolvedRepoPath = path26.resolve(repositoryPath);
25390
+ const resolvedRepoPath = path28.resolve(repositoryPath);
24771
25391
  const validatedPaths = [];
24772
25392
  for (const filepath of fileList) {
24773
- const absoluteFilepath = path26.join(repositoryPath, filepath);
24774
- const resolvedFilePath = path26.resolve(absoluteFilepath);
25393
+ const absoluteFilepath = path28.join(repositoryPath, filepath);
25394
+ const resolvedFilePath = path28.resolve(absoluteFilepath);
24775
25395
  if (!resolvedFilePath.startsWith(resolvedRepoPath)) {
24776
25396
  logDebug(
24777
25397
  `[FileOperations] Rejecting ${filepath} - path traversal attempt detected`
@@ -24799,7 +25419,7 @@ var FileOperations = class {
24799
25419
  for (const absolutePath of filePaths) {
24800
25420
  try {
24801
25421
  const content = await fs25.promises.readFile(absolutePath);
24802
- const relativePath = path26.basename(absolutePath);
25422
+ const relativePath = path28.basename(absolutePath);
24803
25423
  fileDataArray.push({
24804
25424
  relativePath,
24805
25425
  absolutePath,
@@ -25111,14 +25731,14 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
25111
25731
  * since the last scan.
25112
25732
  */
25113
25733
  async scanForSecurityVulnerabilities({
25114
- path: path30,
25734
+ path: path32,
25115
25735
  isAllDetectionRulesScan,
25116
25736
  isAllFilesScan,
25117
25737
  scanContext
25118
25738
  }) {
25119
25739
  this.hasAuthenticationFailed = false;
25120
25740
  logDebug(`[${scanContext}] Scanning for new security vulnerabilities`, {
25121
- path: path30
25741
+ path: path32
25122
25742
  });
25123
25743
  if (!this.gqlClient) {
25124
25744
  logInfo(`[${scanContext}] No GQL client found, skipping scan`);
@@ -25134,11 +25754,11 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
25134
25754
  }
25135
25755
  logDebug(
25136
25756
  `[${scanContext}] Connected to the API, assembling list of files to scan`,
25137
- { path: path30 }
25757
+ { path: path32 }
25138
25758
  );
25139
25759
  const isBackgroundScan = scanContext === ScanContext.BACKGROUND_INITIAL || scanContext === ScanContext.BACKGROUND_PERIODIC;
25140
25760
  const files = await getLocalFiles({
25141
- path: path30,
25761
+ path: path32,
25142
25762
  isAllFilesScan,
25143
25763
  scanContext,
25144
25764
  scanRecentlyChangedFiles: !isBackgroundScan
@@ -25164,13 +25784,13 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
25164
25784
  });
25165
25785
  const { fixReportId, projectId } = await scanFiles({
25166
25786
  fileList: filesToScan.map((file) => file.relativePath),
25167
- repositoryPath: path30,
25787
+ repositoryPath: path32,
25168
25788
  gqlClient: this.gqlClient,
25169
25789
  isAllDetectionRulesScan,
25170
25790
  scanContext
25171
25791
  });
25172
25792
  logInfo(
25173
- `[${scanContext}] Security scan completed for ${path30} reportId: ${fixReportId} projectId: ${projectId}`
25793
+ `[${scanContext}] Security scan completed for ${path32} reportId: ${fixReportId} projectId: ${projectId}`
25174
25794
  );
25175
25795
  if (isAllFilesScan) {
25176
25796
  return;
@@ -25464,13 +26084,13 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
25464
26084
  });
25465
26085
  return scannedFiles.some((file) => file.relativePath === fixFile);
25466
26086
  }
25467
- async getFreshFixes({ path: path30 }) {
26087
+ async getFreshFixes({ path: path32 }) {
25468
26088
  const scanContext = ScanContext.USER_REQUEST;
25469
- logDebug(`[${scanContext}] Getting fresh fixes`, { path: path30 });
25470
- if (this.path !== path30) {
25471
- this.path = path30;
26089
+ logDebug(`[${scanContext}] Getting fresh fixes`, { path: path32 });
26090
+ if (this.path !== path32) {
26091
+ this.path = path32;
25472
26092
  this.reset();
25473
- logInfo(`[${scanContext}] Reset service state for new path`, { path: path30 });
26093
+ logInfo(`[${scanContext}] Reset service state for new path`, { path: path32 });
25474
26094
  }
25475
26095
  try {
25476
26096
  const loginContext = createMcpLoginContext("check_new_fixes");
@@ -25489,7 +26109,7 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
25489
26109
  }
25490
26110
  throw error;
25491
26111
  }
25492
- this.triggerScan({ path: path30, gqlClient: this.gqlClient });
26112
+ this.triggerScan({ path: path32, gqlClient: this.gqlClient });
25493
26113
  let isMvsAutoFixEnabled = null;
25494
26114
  try {
25495
26115
  isMvsAutoFixEnabled = await this.gqlClient.getMvsAutoFixSettings();
@@ -25523,33 +26143,33 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
25523
26143
  return noFreshFixesPrompt;
25524
26144
  }
25525
26145
  triggerScan({
25526
- path: path30,
26146
+ path: path32,
25527
26147
  gqlClient
25528
26148
  }) {
25529
- if (this.path !== path30) {
25530
- this.path = path30;
26149
+ if (this.path !== path32) {
26150
+ this.path = path32;
25531
26151
  this.reset();
25532
- logInfo(`Reset service state for new path in triggerScan`, { path: path30 });
26152
+ logInfo(`Reset service state for new path in triggerScan`, { path: path32 });
25533
26153
  }
25534
26154
  this.gqlClient = gqlClient;
25535
26155
  if (!this.intervalId) {
25536
- this.startPeriodicScanning(path30);
25537
- this.executeInitialScan(path30);
25538
- void this.executeInitialFullScan(path30);
26156
+ this.startPeriodicScanning(path32);
26157
+ this.executeInitialScan(path32);
26158
+ void this.executeInitialFullScan(path32);
25539
26159
  }
25540
26160
  }
25541
- startPeriodicScanning(path30) {
26161
+ startPeriodicScanning(path32) {
25542
26162
  const scanContext = ScanContext.BACKGROUND_PERIODIC;
25543
26163
  logDebug(
25544
26164
  `[${scanContext}] Starting periodic scan for new security vulnerabilities`,
25545
26165
  {
25546
- path: path30
26166
+ path: path32
25547
26167
  }
25548
26168
  );
25549
26169
  this.intervalId = setInterval(() => {
25550
- logDebug(`[${scanContext}] Triggering periodic security scan`, { path: path30 });
26170
+ logDebug(`[${scanContext}] Triggering periodic security scan`, { path: path32 });
25551
26171
  this.scanForSecurityVulnerabilities({
25552
- path: path30,
26172
+ path: path32,
25553
26173
  scanContext
25554
26174
  }).catch((error) => {
25555
26175
  logError(`[${scanContext}] Error during periodic security scan`, {
@@ -25558,45 +26178,45 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
25558
26178
  });
25559
26179
  }, MCP_PERIODIC_CHECK_INTERVAL);
25560
26180
  }
25561
- async executeInitialFullScan(path30) {
26181
+ async executeInitialFullScan(path32) {
25562
26182
  const scanContext = ScanContext.FULL_SCAN;
25563
- logDebug(`[${scanContext}] Triggering initial full security scan`, { path: path30 });
26183
+ logDebug(`[${scanContext}] Triggering initial full security scan`, { path: path32 });
25564
26184
  logDebug(`[${scanContext}] Full scan paths scanned`, {
25565
26185
  fullScanPathsScanned: this.fullScanPathsScanned
25566
26186
  });
25567
- if (this.fullScanPathsScanned.includes(path30)) {
26187
+ if (this.fullScanPathsScanned.includes(path32)) {
25568
26188
  logDebug(`[${scanContext}] Full scan already executed for this path`, {
25569
- path: path30
26189
+ path: path32
25570
26190
  });
25571
26191
  return;
25572
26192
  }
25573
26193
  configStore.set("fullScanPathsScanned", [
25574
26194
  ...this.fullScanPathsScanned,
25575
- path30
26195
+ path32
25576
26196
  ]);
25577
26197
  try {
25578
26198
  await this.scanForSecurityVulnerabilities({
25579
- path: path30,
26199
+ path: path32,
25580
26200
  isAllFilesScan: true,
25581
26201
  isAllDetectionRulesScan: true,
25582
26202
  scanContext: ScanContext.FULL_SCAN
25583
26203
  });
25584
- if (!this.fullScanPathsScanned.includes(path30)) {
25585
- this.fullScanPathsScanned.push(path30);
26204
+ if (!this.fullScanPathsScanned.includes(path32)) {
26205
+ this.fullScanPathsScanned.push(path32);
25586
26206
  configStore.set("fullScanPathsScanned", this.fullScanPathsScanned);
25587
26207
  }
25588
- logInfo(`[${scanContext}] Full scan completed`, { path: path30 });
26208
+ logInfo(`[${scanContext}] Full scan completed`, { path: path32 });
25589
26209
  } catch (error) {
25590
26210
  logError(`[${scanContext}] Error during initial full security scan`, {
25591
26211
  error
25592
26212
  });
25593
26213
  }
25594
26214
  }
25595
- executeInitialScan(path30) {
26215
+ executeInitialScan(path32) {
25596
26216
  const scanContext = ScanContext.BACKGROUND_INITIAL;
25597
- logDebug(`[${scanContext}] Triggering initial security scan`, { path: path30 });
26217
+ logDebug(`[${scanContext}] Triggering initial security scan`, { path: path32 });
25598
26218
  this.scanForSecurityVulnerabilities({
25599
- path: path30,
26219
+ path: path32,
25600
26220
  scanContext: ScanContext.BACKGROUND_INITIAL
25601
26221
  }).catch((error) => {
25602
26222
  logError(`[${scanContext}] Error during initial security scan`, { error });
@@ -25693,9 +26313,9 @@ Example payload:
25693
26313
  `Invalid path: potential security risk detected in path: ${pathValidationResult.error}`
25694
26314
  );
25695
26315
  }
25696
- const path30 = pathValidationResult.path;
26316
+ const path32 = pathValidationResult.path;
25697
26317
  const resultText = await this.newFixesService.getFreshFixes({
25698
- path: path30
26318
+ path: path32
25699
26319
  });
25700
26320
  logInfo("CheckForNewAvailableFixesTool execution completed", {
25701
26321
  resultText
@@ -25873,8 +26493,8 @@ Call this tool instead of ${MCP_TOOL_SCAN_AND_FIX_VULNERABILITIES} when you only
25873
26493
  `Invalid path: potential security risk detected in path: ${pathValidationResult.error}`
25874
26494
  );
25875
26495
  }
25876
- const path30 = pathValidationResult.path;
25877
- const gitService = new GitService(path30, log);
26496
+ const path32 = pathValidationResult.path;
26497
+ const gitService = new GitService(path32, log);
25878
26498
  const gitValidation = await gitService.validateRepository();
25879
26499
  if (!gitValidation.isValid) {
25880
26500
  throw new Error(`Invalid git repository: ${gitValidation.error}`);
@@ -26259,9 +26879,9 @@ Example payload:
26259
26879
  `Invalid path: potential security risk detected in path: ${pathValidationResult.error}`
26260
26880
  );
26261
26881
  }
26262
- const path30 = pathValidationResult.path;
26882
+ const path32 = pathValidationResult.path;
26263
26883
  const files = await getLocalFiles({
26264
- path: path30,
26884
+ path: path32,
26265
26885
  maxFileSize: MCP_MAX_FILE_SIZE,
26266
26886
  maxFiles: args.maxFiles,
26267
26887
  scanContext: ScanContext.USER_REQUEST,
@@ -26281,7 +26901,7 @@ Example payload:
26281
26901
  try {
26282
26902
  const fixResult = await this.vulnerabilityFixService.processVulnerabilities({
26283
26903
  fileList: files.map((file) => file.relativePath),
26284
- repositoryPath: path30,
26904
+ repositoryPath: path32,
26285
26905
  offset: args.offset,
26286
26906
  limit: args.limit,
26287
26907
  isRescan: args.rescan || !!args.maxFiles
@@ -26582,10 +27202,10 @@ init_client_generates();
26582
27202
  init_urlParser2();
26583
27203
 
26584
27204
  // src/features/codeium_intellij/codeium_language_server_grpc_client.ts
26585
- import path27 from "path";
27205
+ import path29 from "path";
26586
27206
  import * as grpc from "@grpc/grpc-js";
26587
27207
  import * as protoLoader from "@grpc/proto-loader";
26588
- var PROTO_PATH = path27.join(
27208
+ var PROTO_PATH = path29.join(
26589
27209
  getModuleRootDir(),
26590
27210
  "src/features/codeium_intellij/proto/exa/language_server_pb/language_server.proto"
26591
27211
  );
@@ -26597,7 +27217,7 @@ function loadProto() {
26597
27217
  defaults: true,
26598
27218
  oneofs: true,
26599
27219
  includeDirs: [
26600
- path27.join(getModuleRootDir(), "src/features/codeium_intellij/proto")
27220
+ path29.join(getModuleRootDir(), "src/features/codeium_intellij/proto")
26601
27221
  ]
26602
27222
  });
26603
27223
  return grpc.loadPackageDefinition(
@@ -26653,28 +27273,28 @@ async function getGrpcClient(port, csrf3) {
26653
27273
  // src/features/codeium_intellij/parse_intellij_logs.ts
26654
27274
  import fs27 from "fs";
26655
27275
  import os14 from "os";
26656
- import path28 from "path";
27276
+ import path30 from "path";
26657
27277
  function getLogsDir() {
26658
27278
  if (process.platform === "darwin") {
26659
- return path28.join(os14.homedir(), "Library/Logs/JetBrains");
27279
+ return path30.join(os14.homedir(), "Library/Logs/JetBrains");
26660
27280
  } else if (process.platform === "win32") {
26661
- return path28.join(
26662
- process.env["LOCALAPPDATA"] || path28.join(os14.homedir(), "AppData/Local"),
27281
+ return path30.join(
27282
+ process.env["LOCALAPPDATA"] || path30.join(os14.homedir(), "AppData/Local"),
26663
27283
  "JetBrains"
26664
27284
  );
26665
27285
  } else {
26666
- return path28.join(os14.homedir(), ".cache/JetBrains");
27286
+ return path30.join(os14.homedir(), ".cache/JetBrains");
26667
27287
  }
26668
27288
  }
26669
27289
  function parseIdeLogDir(ideLogDir) {
26670
27290
  const logFiles = fs27.readdirSync(ideLogDir).filter((f) => /^idea(\.\d+)?\.log$/.test(f)).map((f) => ({
26671
27291
  name: f,
26672
- mtime: fs27.statSync(path28.join(ideLogDir, f)).mtimeMs
27292
+ mtime: fs27.statSync(path30.join(ideLogDir, f)).mtimeMs
26673
27293
  })).sort((a, b) => a.mtime - b.mtime).map((f) => f.name);
26674
27294
  let latestCsrf = null;
26675
27295
  let latestPort = null;
26676
27296
  for (const logFile of logFiles) {
26677
- const lines = fs27.readFileSync(path28.join(ideLogDir, logFile), "utf-8").split("\n");
27297
+ const lines = fs27.readFileSync(path30.join(ideLogDir, logFile), "utf-8").split("\n");
26678
27298
  for (const line of lines) {
26679
27299
  if (!line.includes(
26680
27300
  "com.codeium.intellij.language_server.LanguageServerProcessHandler"
@@ -26702,9 +27322,9 @@ function findRunningCodeiumLanguageServers() {
26702
27322
  const logsDir = getLogsDir();
26703
27323
  if (!fs27.existsSync(logsDir)) return results;
26704
27324
  for (const ide of fs27.readdirSync(logsDir)) {
26705
- let ideLogDir = path28.join(logsDir, ide);
27325
+ let ideLogDir = path30.join(logsDir, ide);
26706
27326
  if (process.platform !== "darwin") {
26707
- ideLogDir = path28.join(ideLogDir, "log");
27327
+ ideLogDir = path30.join(ideLogDir, "log");
26708
27328
  }
26709
27329
  if (!fs27.existsSync(ideLogDir) || !fs27.statSync(ideLogDir).isDirectory()) {
26710
27330
  continue;
@@ -26887,10 +27507,10 @@ function processChatStepCodeAction(step) {
26887
27507
  // src/features/codeium_intellij/install_hook.ts
26888
27508
  import fsPromises5 from "fs/promises";
26889
27509
  import os15 from "os";
26890
- import path29 from "path";
27510
+ import path31 from "path";
26891
27511
  import chalk14 from "chalk";
26892
27512
  function getCodeiumHooksPath() {
26893
- return path29.join(os15.homedir(), ".codeium", "hooks.json");
27513
+ return path31.join(os15.homedir(), ".codeium", "hooks.json");
26894
27514
  }
26895
27515
  async function readCodeiumHooks() {
26896
27516
  const hooksPath = getCodeiumHooksPath();
@@ -26903,7 +27523,7 @@ async function readCodeiumHooks() {
26903
27523
  }
26904
27524
  async function writeCodeiumHooks(config2) {
26905
27525
  const hooksPath = getCodeiumHooksPath();
26906
- const dir = path29.dirname(hooksPath);
27526
+ const dir = path31.dirname(hooksPath);
26907
27527
  await fsPromises5.mkdir(dir, { recursive: true });
26908
27528
  await fsPromises5.writeFile(
26909
27529
  hooksPath,