mobbdev 1.0.171 → 1.0.176

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.
Files changed (3) hide show
  1. package/README.md +7 -1
  2. package/dist/index.mjs +2729 -179
  3. package/package.json +8 -2
package/dist/index.mjs CHANGED
@@ -32,7 +32,7 @@ var init_env = __esm({
32
32
  });
33
33
 
34
34
  // src/mcp/core/configs.ts
35
- var MCP_DEFAULT_API_URL, MCP_API_KEY_HEADER_NAME, MCP_LOGIN_MAX_WAIT, MCP_LOGIN_CHECK_DELAY, MCP_VUL_REPORT_DIGEST_TIMEOUT_MS, MCP_MAX_FILE_SIZE, MCP_PERIODIC_CHECK_INTERVAL, MCP_DEFAULT_MAX_FILES_TO_SCAN, MCP_REPORT_ID_EXPIRATION_MS, MCP_TOOLS_BROWSER_COOLDOWN_MS, MCP_DEFAULT_LIMIT, isAutoScan;
35
+ var MCP_DEFAULT_API_URL, MCP_API_KEY_HEADER_NAME, MCP_LOGIN_MAX_WAIT, MCP_LOGIN_CHECK_DELAY, MCP_VUL_REPORT_DIGEST_TIMEOUT_MS, MCP_MAX_FILE_SIZE, MCP_PERIODIC_CHECK_INTERVAL, MCP_DEFAULT_MAX_FILES_TO_SCAN, MCP_REPORT_ID_EXPIRATION_MS, MCP_TOOLS_BROWSER_COOLDOWN_MS, MCP_DEFAULT_LIMIT, isAutoScan, MVS_AUTO_FIX_OVERRIDE, MCP_AUTO_FIX_DEBUG_MODE;
36
36
  var init_configs = __esm({
37
37
  "src/mcp/core/configs.ts"() {
38
38
  "use strict";
@@ -49,6 +49,8 @@ var init_configs = __esm({
49
49
  MCP_TOOLS_BROWSER_COOLDOWN_MS = 24 * 60 * 60 * 1e3;
50
50
  MCP_DEFAULT_LIMIT = 3;
51
51
  isAutoScan = process.env["AUTO_SCAN"] !== "false";
52
+ MVS_AUTO_FIX_OVERRIDE = process.env["MVS_AUTO_FIX"];
53
+ MCP_AUTO_FIX_DEBUG_MODE = true;
52
54
  }
53
55
  });
54
56
 
@@ -578,16 +580,16 @@ var init_FileUtils = __esm({
578
580
  FileUtils = class {
579
581
  // Important project configuration files that should always be included
580
582
  static isExcludedFileType(filepath) {
581
- const basename = path.basename(filepath).toLowerCase();
582
- if (IMPORTANT_PROJECT_FILES.includes(basename)) {
583
+ const basename2 = path.basename(filepath).toLowerCase();
584
+ if (IMPORTANT_PROJECT_FILES.includes(basename2)) {
583
585
  return false;
584
586
  }
585
587
  const ext = path.extname(filepath).toLowerCase();
586
- const isSupported = SUPPORTED_EXTENSIONS.includes(ext) || SUPPORTED_EXTENSIONS.includes(basename);
588
+ const isSupported = SUPPORTED_EXTENSIONS.includes(ext) || SUPPORTED_EXTENSIONS.includes(basename2);
587
589
  if (!isSupported) {
588
590
  return true;
589
591
  }
590
- if (EXCLUDED_FILE_PATTERNS.some((pattern) => basename.endsWith(pattern))) {
592
+ if (EXCLUDED_FILE_PATTERNS.some((pattern) => basename2.endsWith(pattern))) {
591
593
  return true;
592
594
  }
593
595
  return false;
@@ -739,6 +741,26 @@ var init_GitService = __esm({
739
741
  repositoryPath
740
742
  });
741
743
  }
744
+ /**
745
+ * Checks if the current path is within a git repository
746
+ * @returns Promise<boolean> True if it's a git repository, false otherwise
747
+ */
748
+ async isGitRepository() {
749
+ try {
750
+ const isRepo = await this.git.checkIsRepo();
751
+ if (!isRepo) {
752
+ this.log("[GitService] Not a git repository", "debug");
753
+ return false;
754
+ }
755
+ await this.git.revparse(["--show-toplevel"]);
756
+ return true;
757
+ } catch (error) {
758
+ this.log("[GitService] Not a git repository", "debug", {
759
+ error: String(error)
760
+ });
761
+ return false;
762
+ }
763
+ }
742
764
  /**
743
765
  * Validates that the path is a valid git repository
744
766
  */
@@ -782,9 +804,12 @@ var init_GitService = __esm({
782
804
  if (gitRelativePath.startsWith(relativePathFromGitRoot + "/")) {
783
805
  return gitRelativePath.substring(relativePathFromGitRoot.length + 1);
784
806
  }
807
+ const safeInput = path2.basename(
808
+ String(gitRelativePath || "").replace("\0", "").replace(/^(\.\.(\/|\\$))+/, "")
809
+ );
785
810
  return path2.relative(
786
811
  this.repositoryPath,
787
- path2.join(gitRoot, gitRelativePath)
812
+ path2.join(gitRoot, safeInput)
788
813
  );
789
814
  });
790
815
  this.log("[GitService] Git status retrieved", "info", {
@@ -1209,6 +1234,83 @@ ${rootContent}`;
1209
1234
  if (!content) return null;
1210
1235
  return ignore().add(content);
1211
1236
  }
1237
+ /**
1238
+ * Gets the git repository root directory path
1239
+ * @returns Absolute path to the git repository root
1240
+ */
1241
+ async getGitRoot() {
1242
+ this.log("[GitService] Getting git repository root", "debug");
1243
+ try {
1244
+ const gitRoot = await this.git.revparse(["--show-toplevel"]);
1245
+ this.log("[GitService] Git root retrieved", "debug", { gitRoot });
1246
+ return gitRoot;
1247
+ } catch (error) {
1248
+ const errorMessage = `Failed to get git repository root: ${error.message}`;
1249
+ this.log(`[GitService] ${errorMessage}`, "error", { error });
1250
+ throw new Error(errorMessage);
1251
+ }
1252
+ }
1253
+ /**
1254
+ * Ensures that a specific entry exists in the .gitignore file
1255
+ * Creates .gitignore if it doesn't exist, adds the entry if not present
1256
+ * @param entry The entry to add to .gitignore (e.g., '.mobb', 'node_modules')
1257
+ * @returns True if entry was added, false if it already existed
1258
+ */
1259
+ async ensureGitignoreEntry(entry) {
1260
+ this.log("[GitService] Ensuring .gitignore entry", "debug", { entry });
1261
+ try {
1262
+ const gitRoot = await this.getGitRoot();
1263
+ const gitignorePath = path2.join(gitRoot, ".gitignore");
1264
+ let gitignoreContent = "";
1265
+ if (fs3.existsSync(gitignorePath)) {
1266
+ gitignoreContent = fs3.readFileSync(gitignorePath, "utf8");
1267
+ this.log("[GitService] .gitignore file exists", "debug");
1268
+ } else {
1269
+ this.log("[GitService] Creating .gitignore file", "info", {
1270
+ gitignorePath
1271
+ });
1272
+ }
1273
+ if (gitignoreContent.includes(entry)) {
1274
+ this.log("[GitService] Entry already exists in .gitignore", "debug", {
1275
+ entry
1276
+ });
1277
+ return false;
1278
+ }
1279
+ this.log("[GitService] Adding entry to .gitignore", "info", { entry });
1280
+ const newLine = gitignoreContent.endsWith("\n") || gitignoreContent === "" ? "" : "\n";
1281
+ const updatedContent = `${gitignoreContent}${newLine}${entry}
1282
+ `;
1283
+ fs3.writeFileSync(gitignorePath, updatedContent, "utf8");
1284
+ this.log("[GitService] .gitignore updated successfully", "debug", {
1285
+ entry
1286
+ });
1287
+ return true;
1288
+ } catch (error) {
1289
+ const errorMessage = `Failed to ensure .gitignore entry: ${error.message}`;
1290
+ this.log(`[GitService] ${errorMessage}`, "error", { error, entry });
1291
+ throw new Error(errorMessage);
1292
+ }
1293
+ }
1294
+ /**
1295
+ * Checks if the .gitignore file exists in the repository root
1296
+ * @returns True if .gitignore exists, false otherwise
1297
+ */
1298
+ async gitignoreExists() {
1299
+ this.log("[GitService] Checking if .gitignore exists", "debug");
1300
+ try {
1301
+ const gitRoot = await this.getGitRoot();
1302
+ const gitignorePath = path2.join(gitRoot, ".gitignore");
1303
+ const exists = fs3.existsSync(gitignorePath);
1304
+ this.log("[GitService] .gitignore existence check complete", "debug", {
1305
+ exists
1306
+ });
1307
+ return exists;
1308
+ } catch (error) {
1309
+ const errorMessage = `Failed to check .gitignore existence: ${error.message}`;
1310
+ this.log(`[GitService] ${errorMessage}`, "error", { error });
1311
+ throw new Error(errorMessage);
1312
+ }
1313
+ }
1212
1314
  };
1213
1315
  }
1214
1316
  });
@@ -1570,6 +1672,7 @@ var IssueLanguage_Enum = /* @__PURE__ */ ((IssueLanguage_Enum2) => {
1570
1672
  return IssueLanguage_Enum2;
1571
1673
  })(IssueLanguage_Enum || {});
1572
1674
  var IssueType_Enum = /* @__PURE__ */ ((IssueType_Enum2) => {
1675
+ IssueType_Enum2["ActionNotPinnedToCommitSha"] = "ACTION_NOT_PINNED_TO_COMMIT_SHA";
1573
1676
  IssueType_Enum2["AutoEscapeFalse"] = "AUTO_ESCAPE_FALSE";
1574
1677
  IssueType_Enum2["AvoidBuiltinShadowing"] = "AVOID_BUILTIN_SHADOWING";
1575
1678
  IssueType_Enum2["AvoidIdentityComparisonCachedTypes"] = "AVOID_IDENTITY_COMPARISON_CACHED_TYPES";
@@ -1778,6 +1881,7 @@ var FixDetailsFragmentDoc = `
1778
1881
  gitBlameLogin
1779
1882
  severityValue
1780
1883
  vulnerabilityReportIssues {
1884
+ category
1781
1885
  parsedIssueType
1782
1886
  parsedSeverity
1783
1887
  vulnerabilityReportIssueTags {
@@ -1786,6 +1890,9 @@ var FixDetailsFragmentDoc = `
1786
1890
  }
1787
1891
  sharedState {
1788
1892
  id
1893
+ downloadedBy {
1894
+ downloadSource
1895
+ }
1789
1896
  }
1790
1897
  patchAndQuestions {
1791
1898
  __typename
@@ -1870,6 +1977,10 @@ var FixReportSummaryFieldsFragmentDoc = `
1870
1977
  vulnerabilityReport {
1871
1978
  scanDate
1872
1979
  vendor
1980
+ projectId
1981
+ project {
1982
+ organizationId
1983
+ }
1873
1984
  totalVulnerabilityReportIssuesCount: vulnerabilityReportIssues_aggregate {
1874
1985
  aggregate {
1875
1986
  count
@@ -2374,6 +2485,13 @@ var UpdateDownloadedFixDataDocument = `
2374
2485
  }
2375
2486
  }
2376
2487
  `;
2488
+ var GetUserMvsAutoFixDocument = `
2489
+ query GetUserMvsAutoFix($userEmail: String!) {
2490
+ user_email_notification_settings(where: {user: {email: {_eq: $userEmail}}}) {
2491
+ mvs_auto_fix
2492
+ }
2493
+ }
2494
+ `;
2377
2495
  var defaultWrapper = (action, _operationName, _operationType, _variables) => action();
2378
2496
  function getSdk(client, withWrapper = defaultWrapper) {
2379
2497
  return {
@@ -2451,6 +2569,9 @@ function getSdk(client, withWrapper = defaultWrapper) {
2451
2569
  },
2452
2570
  updateDownloadedFixData(variables, requestHeaders, signal) {
2453
2571
  return withWrapper((wrappedRequestHeaders) => client.request({ document: UpdateDownloadedFixDataDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), "updateDownloadedFixData", "mutation", variables);
2572
+ },
2573
+ GetUserMvsAutoFix(variables, requestHeaders, signal) {
2574
+ return withWrapper((wrappedRequestHeaders) => client.request({ document: GetUserMvsAutoFixDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), "GetUserMvsAutoFix", "query", variables);
2454
2575
  }
2455
2576
  };
2456
2577
  }
@@ -2753,7 +2874,8 @@ var fixDetailsData = {
2753
2874
  ["NO_ASSERT" /* NoAssert */]: void 0,
2754
2875
  ["FUNCTION_CALL_WITHOUT_PARENTHESES" /* FunctionCallWithoutParentheses */]: void 0,
2755
2876
  ["SPRING_DEFAULT_PERMIT" /* SpringDefaultPermit */]: void 0,
2756
- ["RETURN_IN_INIT" /* ReturnInInit */]: void 0
2877
+ ["RETURN_IN_INIT" /* ReturnInInit */]: void 0,
2878
+ ["ACTION_NOT_PINNED_TO_COMMIT_SHA" /* ActionNotPinnedToCommitSha */]: void 0
2757
2879
  };
2758
2880
 
2759
2881
  // src/features/analysis/scm/shared/src/getIssueType.ts
@@ -2890,7 +3012,8 @@ var issueTypeMap = {
2890
3012
  ["NO_ASSERT" /* NoAssert */]: "No Assert",
2891
3013
  ["FUNCTION_CALL_WITHOUT_PARENTHESES" /* FunctionCallWithoutParentheses */]: "Function Call Without Parentheses",
2892
3014
  ["SPRING_DEFAULT_PERMIT" /* SpringDefaultPermit */]: "Spring Default Permit",
2893
- ["RETURN_IN_INIT" /* ReturnInInit */]: "Return in Init"
3015
+ ["RETURN_IN_INIT" /* ReturnInInit */]: "Return in Init",
3016
+ ["ACTION_NOT_PINNED_TO_COMMIT_SHA" /* ActionNotPinnedToCommitSha */]: "Action Not Pinned to Commit Sha"
2894
3017
  };
2895
3018
  var issueTypeZ = z.nativeEnum(IssueType_Enum);
2896
3019
  var getIssueTypeFriendlyString = (issueType) => {
@@ -6250,7 +6373,7 @@ async function getAdoSdk(params) {
6250
6373
  const url = new URL(repoUrl);
6251
6374
  const origin2 = url.origin.toLowerCase().endsWith(".visualstudio.com") ? DEFUALT_ADO_ORIGIN : url.origin.toLowerCase();
6252
6375
  const params2 = `path=/&versionDescriptor[versionOptions]=0&versionDescriptor[versionType]=commit&versionDescriptor[version]=${branch}&resolveLfs=true&$format=zip&api-version=5.0&download=true`;
6253
- const path13 = [
6376
+ const path15 = [
6254
6377
  prefixPath,
6255
6378
  owner,
6256
6379
  projectName,
@@ -6261,7 +6384,7 @@ async function getAdoSdk(params) {
6261
6384
  "items",
6262
6385
  "items"
6263
6386
  ].filter(Boolean).join("/");
6264
- return new URL(`${path13}?${params2}`, origin2).toString();
6387
+ return new URL(`${path15}?${params2}`, origin2).toString();
6265
6388
  },
6266
6389
  async getAdoBranchList({ repoUrl }) {
6267
6390
  try {
@@ -7716,14 +7839,14 @@ function getGithubSdk(params = {}) {
7716
7839
  };
7717
7840
  },
7718
7841
  async getGithubBlameRanges(params2) {
7719
- const { ref, gitHubUrl, path: path13 } = params2;
7842
+ const { ref, gitHubUrl, path: path15 } = params2;
7720
7843
  const { owner, repo } = parseGithubOwnerAndRepo(gitHubUrl);
7721
7844
  const res = await octokit.graphql(
7722
7845
  GET_BLAME_DOCUMENT,
7723
7846
  {
7724
7847
  owner,
7725
7848
  repo,
7726
- path: path13,
7849
+ path: path15,
7727
7850
  ref
7728
7851
  }
7729
7852
  );
@@ -8030,11 +8153,11 @@ var GithubSCMLib = class extends SCMLib {
8030
8153
  markdownComment: comment
8031
8154
  });
8032
8155
  }
8033
- async getRepoBlameRanges(ref, path13) {
8156
+ async getRepoBlameRanges(ref, path15) {
8034
8157
  this._validateUrl();
8035
8158
  return await this.githubSdk.getGithubBlameRanges({
8036
8159
  ref,
8037
- path: path13,
8160
+ path: path15,
8038
8161
  gitHubUrl: this.url
8039
8162
  });
8040
8163
  }
@@ -8448,13 +8571,13 @@ function parseGitlabOwnerAndRepo(gitlabUrl) {
8448
8571
  const { organization, repoName, projectPath } = parsingResult;
8449
8572
  return { owner: organization, repo: repoName, projectPath };
8450
8573
  }
8451
- async function getGitlabBlameRanges({ ref, gitlabUrl, path: path13 }, options) {
8574
+ async function getGitlabBlameRanges({ ref, gitlabUrl, path: path15 }, options) {
8452
8575
  const { projectPath } = parseGitlabOwnerAndRepo(gitlabUrl);
8453
8576
  const api2 = getGitBeaker({
8454
8577
  url: gitlabUrl,
8455
8578
  gitlabAuthToken: options?.gitlabAuthToken
8456
8579
  });
8457
- const resp = await api2.RepositoryFiles.allFileBlames(projectPath, path13, ref);
8580
+ const resp = await api2.RepositoryFiles.allFileBlames(projectPath, path15, ref);
8458
8581
  let lineNumber = 1;
8459
8582
  return resp.filter((range) => range.lines).map((range) => {
8460
8583
  const oldLineNumber = lineNumber;
@@ -8630,10 +8753,10 @@ var GitlabSCMLib = class extends SCMLib {
8630
8753
  markdownComment: comment
8631
8754
  });
8632
8755
  }
8633
- async getRepoBlameRanges(ref, path13) {
8756
+ async getRepoBlameRanges(ref, path15) {
8634
8757
  this._validateUrl();
8635
8758
  return await getGitlabBlameRanges(
8636
- { ref, path: path13, gitlabUrl: this.url },
8759
+ { ref, path: path15, gitlabUrl: this.url },
8637
8760
  {
8638
8761
  url: this.url,
8639
8762
  gitlabAuthToken: this.accessToken
@@ -9711,7 +9834,7 @@ async function postIssueComment(params) {
9711
9834
  fpDescription
9712
9835
  } = params;
9713
9836
  const {
9714
- path: path13,
9837
+ path: path15,
9715
9838
  startLine,
9716
9839
  vulnerabilityReportIssue: {
9717
9840
  vulnerabilityReportIssueTags,
@@ -9726,7 +9849,7 @@ async function postIssueComment(params) {
9726
9849
  Refresh the page in order to see the changes.`,
9727
9850
  pull_number: pullRequest,
9728
9851
  commit_id: commitSha,
9729
- path: path13,
9852
+ path: path15,
9730
9853
  line: startLine
9731
9854
  });
9732
9855
  const commentId = commentRes.data.id;
@@ -9760,7 +9883,7 @@ async function postFixComment(params) {
9760
9883
  scanner
9761
9884
  } = params;
9762
9885
  const {
9763
- path: path13,
9886
+ path: path15,
9764
9887
  startLine,
9765
9888
  vulnerabilityReportIssue: { fixId, vulnerabilityReportIssueTags, category },
9766
9889
  vulnerabilityReportIssueId
@@ -9778,7 +9901,7 @@ async function postFixComment(params) {
9778
9901
  Refresh the page in order to see the changes.`,
9779
9902
  pull_number: pullRequest,
9780
9903
  commit_id: commitSha,
9781
- path: path13,
9904
+ path: path15,
9782
9905
  line: startLine
9783
9906
  });
9784
9907
  const commentId = commentRes.data.id;
@@ -12166,6 +12289,132 @@ import { GraphQLClient as GraphQLClient2 } from "graphql-request";
12166
12289
  import { v4 as uuidv42 } from "uuid";
12167
12290
  init_configs();
12168
12291
 
12292
+ // src/mcp/types.ts
12293
+ import { z as z31 } from "zod";
12294
+ var ScanAndFixVulnerabilitiesToolSchema = z31.object({
12295
+ path: z31.string()
12296
+ });
12297
+ var VulnerabilityReportIssueTagSchema = z31.object({
12298
+ vulnerability_report_issue_tag_value: z31.nativeEnum(
12299
+ Vulnerability_Report_Issue_Tag_Enum
12300
+ )
12301
+ });
12302
+ var VulnerabilityReportIssueSchema = z31.object({
12303
+ category: z31.any().optional().nullable(),
12304
+ parsedIssueType: z31.nativeEnum(IssueType_Enum).nullable().optional(),
12305
+ parsedSeverity: z31.nativeEnum(Vulnerability_Severity_Enum).nullable().optional(),
12306
+ vulnerabilityReportIssueTags: z31.array(VulnerabilityReportIssueTagSchema)
12307
+ });
12308
+ var SharedStateSchema = z31.object({
12309
+ __typename: z31.literal("fix_shared_state").optional(),
12310
+ id: z31.any(),
12311
+ // GraphQL uses `any` type for UUID
12312
+ downloadedBy: z31.array(z31.any().nullable()).optional().nullable()
12313
+ });
12314
+ var UnstructuredFixExtraContextSchema = z31.object({
12315
+ __typename: z31.literal("UnstructuredFixExtraContext").optional(),
12316
+ key: z31.string(),
12317
+ value: z31.any()
12318
+ // GraphQL JSON type
12319
+ });
12320
+ var FixExtraContextResponseSchema = z31.object({
12321
+ __typename: z31.literal("FixExtraContextResponse").optional(),
12322
+ extraContext: z31.array(UnstructuredFixExtraContextSchema),
12323
+ fixDescription: z31.string()
12324
+ });
12325
+ var FixDataSchema = z31.object({
12326
+ __typename: z31.literal("FixData"),
12327
+ patch: z31.string(),
12328
+ patchOriginalEncodingBase64: z31.string(),
12329
+ extraContext: FixExtraContextResponseSchema
12330
+ });
12331
+ var GetFixNoFixErrorSchema = z31.object({
12332
+ __typename: z31.literal("GetFixNoFixError")
12333
+ });
12334
+ var PatchAndQuestionsSchema = z31.union([FixDataSchema, GetFixNoFixErrorSchema]);
12335
+ var McpFixSchema = z31.object({
12336
+ __typename: z31.literal("fix").optional(),
12337
+ id: z31.any(),
12338
+ // GraphQL uses `any` type for UUID
12339
+ confidence: z31.number(),
12340
+ safeIssueType: z31.string().nullable(),
12341
+ severityText: z31.string().nullable(),
12342
+ gitBlameLogin: z31.string().nullable().optional(),
12343
+ // Optional in GraphQL
12344
+ severityValue: z31.number().nullable(),
12345
+ vulnerabilityReportIssues: z31.array(VulnerabilityReportIssueSchema),
12346
+ sharedState: SharedStateSchema.nullable().optional(),
12347
+ // Optional in GraphQL
12348
+ patchAndQuestions: PatchAndQuestionsSchema,
12349
+ // Additional field added by the client
12350
+ fixUrl: z31.string().optional()
12351
+ });
12352
+ var FixAggregateSchema = z31.object({
12353
+ __typename: z31.literal("fix_aggregate").optional(),
12354
+ aggregate: z31.object({
12355
+ __typename: z31.literal("fix_aggregate_fields").optional(),
12356
+ count: z31.number()
12357
+ }).nullable()
12358
+ });
12359
+ var VulnerabilityReportIssueAggregateSchema = z31.object({
12360
+ __typename: z31.literal("vulnerability_report_issue_aggregate").optional(),
12361
+ aggregate: z31.object({
12362
+ __typename: z31.literal("vulnerability_report_issue_aggregate_fields").optional(),
12363
+ count: z31.number()
12364
+ }).nullable()
12365
+ });
12366
+ var RepoSchema = z31.object({
12367
+ __typename: z31.literal("repo").optional(),
12368
+ originalUrl: z31.string()
12369
+ });
12370
+ var ProjectSchema = z31.object({
12371
+ id: z31.any(),
12372
+ // GraphQL uses `any` type for UUID
12373
+ organizationId: z31.any()
12374
+ // GraphQL uses `any` type for UUID
12375
+ });
12376
+ var VulnerabilityReportSchema = z31.object({
12377
+ scanDate: z31.any().nullable(),
12378
+ // GraphQL uses `any` type for timestamp
12379
+ vendor: z31.string(),
12380
+ // GraphQL generates as string, not enum
12381
+ project: ProjectSchema,
12382
+ totalVulnerabilityReportIssuesCount: VulnerabilityReportIssueAggregateSchema,
12383
+ notFixableVulnerabilityReportIssuesCount: VulnerabilityReportIssueAggregateSchema
12384
+ });
12385
+ var FixReportSummarySchema = z31.object({
12386
+ __typename: z31.literal("fixReport").optional(),
12387
+ id: z31.any(),
12388
+ // GraphQL uses `any` type for UUID
12389
+ createdOn: z31.any(),
12390
+ // GraphQL uses `any` type for timestamp
12391
+ repo: RepoSchema.nullable(),
12392
+ issueTypes: z31.any().nullable(),
12393
+ // GraphQL uses `any` type for JSON
12394
+ CRITICAL: FixAggregateSchema,
12395
+ HIGH: FixAggregateSchema,
12396
+ MEDIUM: FixAggregateSchema,
12397
+ LOW: FixAggregateSchema,
12398
+ fixes: z31.array(McpFixSchema),
12399
+ userFixes: z31.array(McpFixSchema).optional(),
12400
+ // Present in some responses but can be omitted
12401
+ filteredFixesCount: FixAggregateSchema,
12402
+ totalFixesCount: FixAggregateSchema,
12403
+ vulnerabilityReport: VulnerabilityReportSchema
12404
+ });
12405
+ var ExpiredReportSchema = z31.object({
12406
+ __typename: z31.literal("fixReport").optional(),
12407
+ id: z31.any(),
12408
+ // GraphQL uses `any` type for UUID
12409
+ expirationOn: z31.any().nullable()
12410
+ // GraphQL uses `any` type for timestamp
12411
+ });
12412
+ var GetLatestReportByRepoUrlResponseSchema = z31.object({
12413
+ __typename: z31.literal("query_root").optional(),
12414
+ fixReport: z31.array(FixReportSummarySchema),
12415
+ expiredReport: z31.array(ExpiredReportSchema)
12416
+ });
12417
+
12169
12418
  // src/mcp/services/ConfigStoreService.ts
12170
12419
  init_configs();
12171
12420
  import Configstore4 from "configstore";
@@ -12496,12 +12745,55 @@ var McpGQLClient = class {
12496
12745
  organizationId: organization.id,
12497
12746
  projectName
12498
12747
  });
12499
- const createdProject = await this.clientSdk.CreateProject({
12500
- organizationId: organization.id,
12501
- projectName
12502
- });
12503
- logDebug("[GraphQL] CreateProject successful", { result: createdProject });
12504
- return createdProject.createProject.projectId;
12748
+ try {
12749
+ const createdProject = await this.clientSdk.CreateProject({
12750
+ organizationId: organization.id,
12751
+ projectName
12752
+ });
12753
+ logDebug("[GraphQL] CreateProject successful", {
12754
+ result: createdProject
12755
+ });
12756
+ return createdProject.createProject.projectId;
12757
+ } catch (createError) {
12758
+ const errorMessage = createError instanceof Error ? createError.message : String(createError);
12759
+ const isConstraintViolation = errorMessage.includes(
12760
+ "duplicate key value violates unique constraint"
12761
+ ) && errorMessage.includes("project_name_organization_id_key");
12762
+ if (isConstraintViolation) {
12763
+ logDebug(
12764
+ "[GraphQL] Project creation failed due to constraint violation, retrying fetch",
12765
+ {
12766
+ organizationId: organization.id,
12767
+ projectName,
12768
+ error: errorMessage
12769
+ }
12770
+ );
12771
+ const retryOrgAndProjectRes = await this.clientSdk.getLastOrgAndNamedProject({
12772
+ email: userEmail,
12773
+ projectName
12774
+ });
12775
+ const retryProjectId = retryOrgAndProjectRes.user?.[0]?.userOrganizationsAndUserOrganizationRoles?.[0]?.organization?.projects?.[0]?.id;
12776
+ if (retryProjectId) {
12777
+ logDebug(
12778
+ "[GraphQL] Successfully found existing project after constraint violation",
12779
+ {
12780
+ projectId: retryProjectId,
12781
+ projectName
12782
+ }
12783
+ );
12784
+ return retryProjectId;
12785
+ }
12786
+ logError(
12787
+ "[GraphQL] Failed to find project even after constraint violation retry",
12788
+ {
12789
+ organizationId: organization.id,
12790
+ projectName,
12791
+ retryResult: retryOrgAndProjectRes
12792
+ }
12793
+ );
12794
+ }
12795
+ throw createError;
12796
+ }
12505
12797
  } catch (e) {
12506
12798
  logError("[GraphQL] getProjectId failed", {
12507
12799
  error: e,
@@ -12559,18 +12851,56 @@ var McpGQLClient = class {
12559
12851
  return null;
12560
12852
  }
12561
12853
  }
12562
- mergeUserAndSystemFixes(reportData, limit) {
12854
+ generateFixUrl({
12855
+ fixId,
12856
+ organizationId,
12857
+ projectId,
12858
+ reportId
12859
+ }) {
12860
+ if (!organizationId || !projectId || !reportId) {
12861
+ return void 0;
12862
+ }
12863
+ const appBaseUrl = this.apiUrl.replace("/v1/graphql", "").replace("api.", "");
12864
+ return `${appBaseUrl}/organization/${organizationId}/project/${projectId}/report/${reportId}/fix/${fixId}`;
12865
+ }
12866
+ mergeUserAndSystemFixes({
12867
+ reportData,
12868
+ limit
12869
+ }) {
12563
12870
  if (!reportData) return [];
12871
+ const reportMetadata = {
12872
+ id: reportData.id,
12873
+ organizationId: reportData.vulnerabilityReport?.project?.organizationId,
12874
+ projectId: reportData.vulnerabilityReport?.project?.id
12875
+ };
12564
12876
  const { userFixes = [], fixes = [] } = reportData;
12565
12877
  const fixMap = /* @__PURE__ */ new Map();
12566
12878
  for (const fix of userFixes) {
12567
12879
  if (fix.id) {
12568
- fixMap.set(fix.id, fix);
12880
+ const fixWithUrl = {
12881
+ ...fix,
12882
+ fixUrl: this.generateFixUrl({
12883
+ fixId: fix.id,
12884
+ organizationId: reportMetadata?.organizationId,
12885
+ projectId: reportMetadata?.projectId,
12886
+ reportId: reportMetadata?.id
12887
+ })
12888
+ };
12889
+ fixMap.set(fix.id, fixWithUrl);
12569
12890
  }
12570
12891
  }
12571
12892
  for (const fix of fixes) {
12572
12893
  if (fix.id && !fixMap.has(fix.id)) {
12573
- fixMap.set(fix.id, fix);
12894
+ const fixWithUrl = {
12895
+ ...fix,
12896
+ fixUrl: this.generateFixUrl({
12897
+ fixId: fix.id,
12898
+ organizationId: reportMetadata?.organizationId,
12899
+ projectId: reportMetadata?.projectId,
12900
+ reportId: reportMetadata?.id
12901
+ })
12902
+ };
12903
+ fixMap.set(fix.id, fixWithUrl);
12574
12904
  }
12575
12905
  }
12576
12906
  return Array.from(fixMap.values()).slice(0, limit);
@@ -12589,6 +12919,52 @@ var McpGQLClient = class {
12589
12919
  logDebug("[GraphQL] No fixes found to update download status");
12590
12920
  }
12591
12921
  }
12922
+ async updateAutoAppliedFixesStatus(fixIds) {
12923
+ if (fixIds.length > 0) {
12924
+ const resUpdate = await this.clientSdk.updateDownloadedFixData({
12925
+ fixIds,
12926
+ source: "AUTO_MVS" /* AutoMvs */
12927
+ });
12928
+ logDebug("[GraphQL] updateAutoAppliedFixesStatus successful", {
12929
+ result: resUpdate,
12930
+ fixIds,
12931
+ note: "Auto-applied via MVS automation"
12932
+ });
12933
+ } else {
12934
+ logDebug("[GraphQL] No auto-applied fixes to update status");
12935
+ }
12936
+ }
12937
+ async getMvsAutoFixSettings() {
12938
+ try {
12939
+ const envOverride = process.env["MVS_AUTO_FIX"];
12940
+ if (envOverride !== void 0) {
12941
+ const overrideValue = envOverride.toLowerCase() === "true";
12942
+ logDebug("[Environment] Using MVS_AUTO_FIX override", {
12943
+ envValue: envOverride,
12944
+ resolvedValue: overrideValue
12945
+ });
12946
+ return overrideValue;
12947
+ }
12948
+ const userInfo = await this.getUserInfo();
12949
+ if (!userInfo?.email) {
12950
+ throw new Error("User email not found");
12951
+ }
12952
+ logDebug("[GraphQL] Calling GetUserMvsAutoFix query", {
12953
+ userEmail: userInfo.email
12954
+ });
12955
+ const result = await this.clientSdk.GetUserMvsAutoFix({
12956
+ userEmail: userInfo.email
12957
+ });
12958
+ logDebug("[GraphQL] GetUserMvsAutoFix successful", { result });
12959
+ return result.user_email_notification_settings?.[0]?.mvs_auto_fix ?? true;
12960
+ } catch (e) {
12961
+ logError("[GraphQL] GetUserMvsAutoFix failed", {
12962
+ error: e,
12963
+ ...this.getErrorContext()
12964
+ });
12965
+ throw e;
12966
+ }
12967
+ }
12592
12968
  async getLatestReportByRepoUrl({
12593
12969
  repoUrl,
12594
12970
  limit = MCP_DEFAULT_LIMIT,
@@ -12611,23 +12987,27 @@ var McpGQLClient = class {
12611
12987
  error: err
12612
12988
  });
12613
12989
  }
12614
- const res = await this.clientSdk.GetLatestReportByRepoUrl({
12990
+ const resp = await this.clientSdk.GetLatestReportByRepoUrl({
12615
12991
  repoUrl,
12616
12992
  limit,
12617
12993
  offset,
12618
12994
  currentUserEmail
12619
12995
  });
12620
12996
  logDebug("[GraphQL] GetLatestReportByRepoUrl successful", {
12621
- result: res,
12622
- reportCount: res.fixReport?.length || 0
12997
+ result: resp,
12998
+ reportCount: resp.fixReport?.length || 0
12999
+ });
13000
+ const latestReport = resp.fixReport?.[0] && FixReportSummarySchema.parse(resp.fixReport?.[0]);
13001
+ const fixes = this.mergeUserAndSystemFixes({
13002
+ reportData: latestReport,
13003
+ limit
12623
13004
  });
12624
- const fixes = this.mergeUserAndSystemFixes(res.fixReport?.[0], limit);
12625
13005
  return {
12626
- fixReport: res.fixReport?.[0] ? {
12627
- ...res.fixReport?.[0],
13006
+ fixReport: latestReport ? {
13007
+ ...latestReport,
12628
13008
  fixes
12629
13009
  } : null,
12630
- expiredReport: res.expiredReport?.[0] || null
13010
+ expiredReport: resp.expiredReport?.[0] || null
12631
13011
  };
12632
13012
  } catch (e) {
12633
13013
  logError("[GraphQL] GetLatestReportByRepoUrl failed", {
@@ -12687,11 +13067,20 @@ var McpGQLClient = class {
12687
13067
  if (res.fixReport.length === 0) {
12688
13068
  return null;
12689
13069
  }
12690
- const fixes = this.mergeUserAndSystemFixes(res.fixReport?.[0], limit);
13070
+ const latestReport = FixReportSummarySchema.parse(res.fixReport?.[0]);
13071
+ const fixes = this.mergeUserAndSystemFixes({
13072
+ reportData: latestReport,
13073
+ limit
13074
+ });
12691
13075
  return {
12692
13076
  fixes,
12693
13077
  totalCount: res.fixReport?.[0]?.filteredFixesCount?.aggregate?.count || 0,
12694
- expiredReport: res.expiredReport?.[0] || null
13078
+ expiredReport: res.expiredReport?.[0] || null,
13079
+ fixReport: res.fixReport?.[0] ? {
13080
+ id: res.fixReport[0].id,
13081
+ organizationId: res.fixReport[0].vulnerabilityReport?.project?.organizationId,
13082
+ projectId: res.fixReport[0].vulnerabilityReport?.projectId
13083
+ } : void 0
12695
13084
  };
12696
13085
  } catch (e) {
12697
13086
  logError("[GraphQL] GetReportFixes failed", {
@@ -12778,6 +13167,7 @@ var McpServer = class {
12778
13167
  __publicField(this, "server");
12779
13168
  __publicField(this, "toolRegistry");
12780
13169
  __publicField(this, "isEventHandlersSetup", false);
13170
+ __publicField(this, "eventHandlers", /* @__PURE__ */ new Map());
12781
13171
  this.server = new Server(
12782
13172
  {
12783
13173
  name: config4.name,
@@ -12795,7 +13185,10 @@ var McpServer = class {
12795
13185
  logInfo("MCP server instance created");
12796
13186
  logDebug("MCP server instance config", { config: config4 });
12797
13187
  }
12798
- handleProcessSignal(signal, error) {
13188
+ handleProcessSignal({
13189
+ signal,
13190
+ error
13191
+ }) {
12799
13192
  const messages = {
12800
13193
  SIGINT: "MCP server interrupted",
12801
13194
  SIGTERM: "MCP server terminated",
@@ -12850,9 +13243,11 @@ var McpServer = class {
12850
13243
  "warning"
12851
13244
  ];
12852
13245
  signals.forEach((signal) => {
12853
- process.on(signal, (error) => {
12854
- this.handleProcessSignal(signal, error);
12855
- });
13246
+ const handler = (error) => {
13247
+ this.handleProcessSignal({ signal, error });
13248
+ };
13249
+ this.eventHandlers.set(signal, handler);
13250
+ process.on(signal, handler);
12856
13251
  });
12857
13252
  this.isEventHandlersSetup = true;
12858
13253
  logInfo("Process event handlers registered");
@@ -13005,11 +13400,17 @@ var McpServer = class {
13005
13400
  }
13006
13401
  async stop() {
13007
13402
  logDebug("MCP server shutting down");
13403
+ this.eventHandlers.forEach((handler, signal) => {
13404
+ process.removeListener(signal, handler);
13405
+ });
13406
+ this.eventHandlers.clear();
13407
+ this.isEventHandlersSetup = false;
13408
+ logDebug("Process event handlers cleaned up");
13008
13409
  }
13009
13410
  };
13010
13411
 
13011
13412
  // src/mcp/tools/checkForNewAvailableFixes/CheckForNewAvailableFixesTool.ts
13012
- import { z as z32 } from "zod";
13413
+ import { z as z34 } from "zod";
13013
13414
 
13014
13415
  // src/mcp/services/PathValidation.ts
13015
13416
  import fs10 from "fs";
@@ -13078,7 +13479,7 @@ async function validatePath(inputPath) {
13078
13479
  }
13079
13480
 
13080
13481
  // src/mcp/tools/base/BaseTool.ts
13081
- import { z as z31 } from "zod";
13482
+ import { z as z32 } from "zod";
13082
13483
  var BaseTool = class {
13083
13484
  getDefinition() {
13084
13485
  return {
@@ -13106,7 +13507,7 @@ var BaseTool = class {
13106
13507
  try {
13107
13508
  return this.inputValidationSchema.parse(args);
13108
13509
  } catch (error) {
13109
- if (error instanceof z31.ZodError) {
13510
+ if (error instanceof z32.ZodError) {
13110
13511
  const errorDetails = error.errors.map((e) => {
13111
13512
  const fieldPath = e.path.length > 0 ? e.path.join(".") : "root";
13112
13513
  const message = e.message === "Required" ? `Missing required parameter '${fieldPath}'` : `Invalid value for '${fieldPath}': ${e.message}`;
@@ -13478,7 +13879,71 @@ ${nextStepsPrompt({ scannedFiles })}
13478
13879
  `;
13479
13880
  };
13480
13881
  var noFreshFixesPrompt = `No fresh fixes available for this repository at this time. No user message needed. Do not show any comments to the user.`;
13882
+ var continuousMonitoringSection = `## \u{1F504} Continuous Monitoring Active
13883
+
13884
+ The Mobb MCP is continuously monitoring your code and will **automatically apply fixes** when new vulnerabilities are detected in files you modify or create.`;
13885
+ var autoFixSettingsSection = `## \u2699\uFE0F Auto-Fix Settings
13886
+
13887
+ If you prefer to review fixes manually before they're applied:
13888
+ - **Turn off automatic fixes** in your Mobb app settings at https://app.mobb.ai/ setting section
13889
+ - When disabled, you'll receive available fixes that you can apply manually through the \`${MCP_TOOL_CHECK_FOR_NEW_AVAILABLE_FIXES}\` MCP tool`;
13890
+ var whatHappensNextSection = `## \u{1F50D} What Happens Next
13891
+
13892
+ - Continue coding normally - Mobb is watching in the background
13893
+ - Any new security issues will be automatically patched
13894
+ - You'll be notified when fixes are applied to your codebase
13895
+ - Review and commit changes when ready
13896
+
13897
+ Your code is being actively protected! \u{1F680}`;
13898
+ var noVulnerabilitiesAutoFixPrompt = `\u{1F6E1}\uFE0F **MOBB AUTO-FIX MONITORING ACTIVE**
13899
+
13900
+ ## \u2705 No Fixable Vulnerabilities Detected
13901
+
13902
+ No fixable vulnerabilities were detected so far.
13903
+
13904
+ ${continuousMonitoringSection}
13905
+
13906
+ ${autoFixSettingsSection}
13907
+
13908
+ ${whatHappensNextSection}`;
13481
13909
  var initialScanInProgressPrompt = `Initial scan is still in progress. No user message needed. Do not show any comments to the user.`;
13910
+ var authenticationRequiredPrompt = `# AUTHENTICATION REQUIRED: MOBB ACCESS NOT AUTHORIZED
13911
+
13912
+ ## MCP SERVICE UNAVAILABLE
13913
+
13914
+ The Mobb MCP (Model Context Protocol) service is currently unavailable because authentication with Mobb has not been completed.
13915
+
13916
+ ## REQUIRED ACTION
13917
+
13918
+ To use Mobb's automated security fix capabilities, you need to:
13919
+
13920
+ 1. **Authorize Mobb Access**
13921
+ - Restart Mobb MCP
13922
+ - A browser window will open for you to log in and authorize access
13923
+
13924
+ 2. **Complete Authentication**
13925
+ - Sign in to your Mobb account (or create one if needed)
13926
+ - Authorize the CLI tool to access Mobb services
13927
+ - This creates a secure token for future automated scans
13928
+
13929
+ ## WHAT HAPPENS NEXT
13930
+
13931
+ Once authentication is complete:
13932
+ - The MCP service will automatically start monitoring your repository
13933
+ - New security vulnerabilities will be detected and fixed automatically
13934
+ - You'll receive notifications about applied fixes through this interface
13935
+
13936
+ ## TROUBLESHOOTING
13937
+
13938
+ If you're experiencing authentication issues:
13939
+ - Ensure you have an active internet connection
13940
+ - Check that you can access https://app.mobb.ai in your browser
13941
+ - Make sure your browser isn't blocking authentication pop-ups
13942
+ - Try running a manual scan with the \`--debug\` flag for detailed output
13943
+
13944
+ For assistance:
13945
+ - Documentation: https://docs.mobb.ai
13946
+ - Support: support@mobb.ai`;
13482
13947
  var freshFixesPrompt = ({
13483
13948
  fixes,
13484
13949
  limit,
@@ -13499,6 +13964,67 @@ ${applyFixesPrompt({
13499
13964
  })}
13500
13965
  `;
13501
13966
  };
13967
+ function extractTargetFileFromPatch(patch) {
13968
+ const match = patch?.match(/^diff --git a\/([^\s]+) b\//);
13969
+ return match?.[1] || "Unknown file";
13970
+ }
13971
+ function formatSeverity(severityText, severityValue) {
13972
+ if (severityText) return severityText;
13973
+ if (severityValue !== null && severityValue !== void 0) {
13974
+ if (severityValue >= 9) return "Critical";
13975
+ if (severityValue >= 7) return "High";
13976
+ if (severityValue >= 4) return "Medium";
13977
+ return "Low";
13978
+ }
13979
+ return "Unknown";
13980
+ }
13981
+ var appliedFixesSummaryPrompt = ({
13982
+ fixes,
13983
+ gqlClient
13984
+ }) => {
13985
+ const fixIds = fixes.map((fix) => fix.id);
13986
+ void gqlClient.updateFixesDownloadStatus(fixIds);
13987
+ const patchTime = (/* @__PURE__ */ new Date()).toLocaleString();
13988
+ return `\u{1F6E1}\uFE0F **MOBB AUTO-FIX: VULNERABILITIES PATCHED**
13989
+
13990
+ ## \u2705 ${fixes.length} Security ${fixes.length === 1 ? "Fix" : "Fixes"} Automatically Applied
13991
+
13992
+ Mobb has automatically detected and fixed ${fixes.length} security ${fixes.length === 1 ? "vulnerability" : "vulnerabilities"} in your codebase.
13993
+
13994
+ ## \u{1F4CB} Applied Fixes Summary
13995
+
13996
+ ${fixes.map((fix, index) => {
13997
+ const vulnerabilityType = friendlyType(fix.safeIssueType || "Unknown");
13998
+ const severity = formatSeverity(fix.severityText, fix.severityValue);
13999
+ const description = fix.patchAndQuestions?.__typename === "FixData" ? fix.patchAndQuestions.extraContext?.fixDescription || "Security vulnerability fix" : "Security vulnerability fix";
14000
+ const patch = fix.patchAndQuestions?.__typename === "FixData" ? fix.patchAndQuestions.patch : void 0;
14001
+ const targetFile = extractTargetFileFromPatch(patch);
14002
+ const gitBlameLogin = fix.gitBlameLogin || "Unknown";
14003
+ return `### ${index + 1}. ${vulnerabilityType}
14004
+
14005
+ **\u{1F3AF} Vulnerability Type:** ${vulnerabilityType}
14006
+ **\u{1F4CA} Severity:** ${severity}
14007
+ **\u{1F4DD} Description:** ${description}
14008
+ **\u{1F464} Git Blame:** ${gitBlameLogin}
14009
+ **\u{1F4C1} Target File:** \`${targetFile}\`
14010
+ **\u23F0 Patch Applied:** ${patchTime}
14011
+
14012
+ ---`;
14013
+ }).join("\n")}
14014
+
14015
+ ${continuousMonitoringSection}
14016
+
14017
+ ${autoFixSettingsSection}
14018
+
14019
+ ## \u{1F4CB} Next Steps
14020
+
14021
+ 1. **Review the changes** - Check the modified files to understand what was fixed
14022
+ 2. **Test your application** - Ensure the fixes don't break existing functionality
14023
+ 3. **Commit the changes** - Add and commit the security fixes to your repository
14024
+ 4. **Continue coding** - Mobb will keep protecting your code automatically
14025
+
14026
+ ${whatHappensNextSection}`;
14027
+ };
13502
14028
 
13503
14029
  // src/mcp/services/GetLocalFiles.ts
13504
14030
  init_FileUtils();
@@ -13507,23 +14033,23 @@ init_configs();
13507
14033
  import fs11 from "fs/promises";
13508
14034
  import nodePath from "path";
13509
14035
  var getLocalFiles = async ({
13510
- path: path13,
14036
+ path: path15,
13511
14037
  maxFileSize = MCP_MAX_FILE_SIZE,
13512
14038
  maxFiles,
13513
14039
  isAllFilesScan,
13514
14040
  scanContext
13515
14041
  }) => {
13516
14042
  logDebug(`[${scanContext}] Starting getLocalFiles`, {
13517
- path: path13,
14043
+ path: path15,
13518
14044
  maxFileSize,
13519
14045
  maxFiles,
13520
14046
  isAllFilesScan
13521
14047
  });
13522
14048
  try {
13523
- const resolvedRepoPath = await fs11.realpath(path13);
14049
+ const resolvedRepoPath = await fs11.realpath(path15);
13524
14050
  logDebug(`[${scanContext}] Resolved repository path`, {
13525
14051
  resolvedRepoPath,
13526
- originalPath: path13
14052
+ originalPath: path15
13527
14053
  });
13528
14054
  const gitService = new GitService(resolvedRepoPath, log);
13529
14055
  const gitValidation = await gitService.validateRepository();
@@ -13536,7 +14062,7 @@ var getLocalFiles = async ({
13536
14062
  if (!gitValidation.isValid || isAllFilesScan) {
13537
14063
  try {
13538
14064
  files = await FileUtils.getLastChangedFiles({
13539
- dir: path13,
14065
+ dir: path15,
13540
14066
  maxFileSize,
13541
14067
  maxFiles,
13542
14068
  isAllFilesScan
@@ -13628,53 +14154,1773 @@ var getLocalFiles = async ({
13628
14154
  logError(`${scanContext}Unexpected error in getLocalFiles`, {
13629
14155
  error: error instanceof Error ? error.message : String(error),
13630
14156
  stack: error instanceof Error ? error.stack : void 0,
13631
- path: path13
14157
+ path: path15
13632
14158
  });
13633
14159
  throw error;
13634
14160
  }
13635
14161
  };
13636
14162
 
13637
- // src/mcp/services/ScanFiles.ts
13638
- init_GitService();
13639
- init_configs();
13640
-
13641
- // src/mcp/services/FileOperations.ts
13642
- init_FileUtils();
14163
+ // src/mcp/services/LocalMobbFolderService.ts
13643
14164
  import fs12 from "fs";
13644
14165
  import path12 from "path";
13645
- import AdmZip2 from "adm-zip";
13646
- var FileOperations = class {
14166
+ import { z as z33 } from "zod";
14167
+ init_GitService();
14168
+ function extractPathFromPatch(patch) {
14169
+ const match = patch?.match(/diff --git a\/([^\s]+) b\//);
14170
+ return match?.[1] ?? null;
14171
+ }
14172
+ function parsedIssueTypeRes(issueType) {
14173
+ return z33.nativeEnum(IssueType_Enum).safeParse(issueType);
14174
+ }
14175
+ var LocalMobbFolderService = class {
13647
14176
  /**
13648
- * Creates a ZIP archive containing the specified source files
13649
- * @param fileList Array of relative file paths to include
13650
- * @param repositoryPath Base path for resolving relative file paths
13651
- * @param maxFileSize Maximum size allowed for individual files
13652
- * @returns ZIP archive as a Buffer with metadata
14177
+ * Creates a new LocalMobbFolderService instance
14178
+ * @param repoPath Path to the repository or directory (supports both git and non-git)
13653
14179
  */
13654
- async createSourceCodeArchive(fileList, repositoryPath, maxFileSize) {
13655
- logDebug("[FileOperations] Packing files");
13656
- const zip = new AdmZip2();
13657
- let packedFilesCount = 0;
13658
- const resolvedRepoPath = path12.resolve(repositoryPath);
13659
- for (const filepath of fileList) {
13660
- const absoluteFilepath = path12.join(repositoryPath, filepath);
13661
- const resolvedFilePath = path12.resolve(absoluteFilepath);
13662
- if (!resolvedFilePath.startsWith(resolvedRepoPath)) {
13663
- logDebug(
13664
- `[FileOperations] Skipping ${filepath} due to potential path traversal security risk`
14180
+ constructor(repoPath) {
14181
+ __publicField(this, "defaultMobbFolderName", ".mobb");
14182
+ __publicField(this, "mobbIgnorePattern", "**/.mobb");
14183
+ __publicField(this, "repoPath");
14184
+ __publicField(this, "gitService");
14185
+ this.repoPath = repoPath;
14186
+ try {
14187
+ this.gitService = new GitService(
14188
+ repoPath,
14189
+ (message, level, data) => {
14190
+ if (level === "debug") {
14191
+ logDebug(message, data);
14192
+ } else if (level === "info") {
14193
+ logInfo(message, data);
14194
+ } else if (level === "error") {
14195
+ logError(message, data);
14196
+ }
14197
+ }
14198
+ );
14199
+ } catch (error) {
14200
+ logDebug("[LocalMobbFolderService] GitService initialization failed", {
14201
+ repoPath,
14202
+ error: String(error)
14203
+ });
14204
+ this.gitService = null;
14205
+ }
14206
+ logDebug("[LocalMobbFolderService] Service initialized", { repoPath });
14207
+ }
14208
+ /**
14209
+ * Gets or creates the .mobb folder in the repository path
14210
+ * For git repositories: ensures .gitignore exists and contains wildcard pattern to ignore .mobb folders
14211
+ * For non-git directories: skips gitignore functionality but creates .mobb folder normally
14212
+ * @returns Path to the .mobb folder
14213
+ * @throws Error if path validation fails or folder creation fails
14214
+ */
14215
+ async getFolder() {
14216
+ logDebug("[LocalMobbFolderService] Getting .mobb folder");
14217
+ try {
14218
+ const isGitRepo = this.gitService ? await this.gitService.isGitRepository() : false;
14219
+ logDebug("[LocalMobbFolderService] Repository type detected", {
14220
+ isGitRepo
14221
+ });
14222
+ if (isGitRepo && this.gitService) {
14223
+ try {
14224
+ const gitRoot = await this.gitService.getGitRoot();
14225
+ logDebug("[LocalMobbFolderService] Git root found", { gitRoot });
14226
+ const wasAdded = await this.gitService.ensureGitignoreEntry(
14227
+ this.mobbIgnorePattern
14228
+ );
14229
+ if (wasAdded) {
14230
+ logInfo(
14231
+ "[LocalMobbFolderService] Added .mobb wildcard pattern to .gitignore",
14232
+ {
14233
+ pattern: this.mobbIgnorePattern
14234
+ }
14235
+ );
14236
+ } else {
14237
+ logDebug(
14238
+ "[LocalMobbFolderService] .mobb pattern already in .gitignore"
14239
+ );
14240
+ }
14241
+ } catch (gitError) {
14242
+ logDebug(
14243
+ "[LocalMobbFolderService] Git operations failed, treating as non-git repo",
14244
+ { gitError }
14245
+ );
14246
+ }
14247
+ } else {
14248
+ logInfo(
14249
+ "[LocalMobbFolderService] Non-git repository detected, skipping .gitignore operations"
13665
14250
  );
13666
- continue;
13667
14251
  }
13668
- if (!FileUtils.shouldPackFile(absoluteFilepath, maxFileSize)) {
13669
- logDebug(
13670
- `[FileOperations] Excluding ${filepath} - file is too large, binary, or matches exclusion rules`
13671
- );
13672
- continue;
14252
+ const mobbFolderPath = path12.join(
14253
+ this.repoPath,
14254
+ this.defaultMobbFolderName
14255
+ );
14256
+ if (!fs12.existsSync(mobbFolderPath)) {
14257
+ logInfo("[LocalMobbFolderService] Creating .mobb folder", {
14258
+ mobbFolderPath
14259
+ });
14260
+ fs12.mkdirSync(mobbFolderPath, { recursive: true });
14261
+ } else {
14262
+ logDebug("[LocalMobbFolderService] .mobb folder already exists");
13673
14263
  }
13674
- const fileContent = await this.readSourceFile(absoluteFilepath, filepath);
13675
- if (fileContent) {
13676
- zip.addFile(filepath, fileContent);
13677
- packedFilesCount++;
14264
+ const stats = fs12.statSync(mobbFolderPath);
14265
+ if (!stats.isDirectory()) {
14266
+ throw new Error(`Path exists but is not a directory: ${mobbFolderPath}`);
14267
+ }
14268
+ logDebug("[LocalMobbFolderService] .mobb folder ready", {
14269
+ mobbFolderPath,
14270
+ repoPath: this.repoPath,
14271
+ isGitRepo
14272
+ });
14273
+ return mobbFolderPath;
14274
+ } catch (error) {
14275
+ const errorMessage = `Failed to get/create .mobb folder: ${error}`;
14276
+ logError(errorMessage, { repoPath: this.repoPath, error });
14277
+ throw new Error(errorMessage);
14278
+ }
14279
+ }
14280
+ /**
14281
+ * Public method to get or create the .mobb folder
14282
+ * This is the main entry point for accessing the Mobb folder
14283
+ * @returns Promise<string> Path to the .mobb folder
14284
+ */
14285
+ async getMobbFolder() {
14286
+ return await this.getFolder();
14287
+ }
14288
+ /**
14289
+ * Validates the repository (git or non-git) and ensures it's ready for Mobb operations
14290
+ * For git repositories: validates git structure and accessibility
14291
+ * For non-git directories: validates directory exists and is accessible
14292
+ * @returns Promise<boolean> True if repository/directory is valid
14293
+ * @throws Error if repository validation fails
14294
+ */
14295
+ async validateRepository() {
14296
+ const isGitRepo = this.gitService ? await this.gitService.isGitRepository() : false;
14297
+ if (isGitRepo && this.gitService) {
14298
+ const result = await this.gitService.validateRepository();
14299
+ if (!result.isValid) {
14300
+ throw new Error(result.error || "Git repository validation failed");
14301
+ }
14302
+ logDebug("[LocalMobbFolderService] Git repository validated successfully");
14303
+ } else {
14304
+ try {
14305
+ const stats = fs12.statSync(this.repoPath);
14306
+ if (!stats.isDirectory()) {
14307
+ throw new Error(
14308
+ `Path exists but is not a directory: ${this.repoPath}`
14309
+ );
14310
+ }
14311
+ fs12.accessSync(this.repoPath, fs12.constants.R_OK | fs12.constants.W_OK);
14312
+ logDebug(
14313
+ "[LocalMobbFolderService] Non-git directory validated successfully"
14314
+ );
14315
+ } catch (error) {
14316
+ throw new Error(`Directory validation failed: ${error}`);
14317
+ }
14318
+ }
14319
+ return true;
14320
+ }
14321
+ /**
14322
+ * Extracts patch content from McpFix object
14323
+ * @param fix McpFix object
14324
+ * @returns Patch content or null if not available
14325
+ */
14326
+ extractPatchFromFix(fix) {
14327
+ if (fix.patchAndQuestions.__typename === "FixData") {
14328
+ return fix.patchAndQuestions.patch || null;
14329
+ }
14330
+ return null;
14331
+ }
14332
+ /**
14333
+ * Extracts severity level from McpFix object
14334
+ * @param fix McpFix object
14335
+ * @returns Severity string
14336
+ */
14337
+ extractSeverity(fix) {
14338
+ return fix.severityText?.toLowerCase() || "unknown";
14339
+ }
14340
+ /**
14341
+ * Extracts vulnerability type from McpFix object
14342
+ * @param fix McpFix object
14343
+ * @returns Vulnerability type string
14344
+ */
14345
+ extractVulnerabilityType(fix) {
14346
+ return fix.safeIssueType || "security-vulnerability";
14347
+ }
14348
+ /**
14349
+ * Extracts filename from McpFix object by parsing the patch
14350
+ * @param fix McpFix object
14351
+ * @returns Filename or null if not determinable
14352
+ */
14353
+ extractFileNameFromFix(fix) {
14354
+ const patch = this.extractPatchFromFix(fix);
14355
+ if (!patch) {
14356
+ return null;
14357
+ }
14358
+ const extractedPath = extractPathFromPatch(patch);
14359
+ if (extractedPath) {
14360
+ const pathParts = extractedPath.split("/");
14361
+ const fileName = pathParts[pathParts.length - 1];
14362
+ return fileName || null;
14363
+ }
14364
+ return `fix-${fix.id}`;
14365
+ }
14366
+ /**
14367
+ * Saves a git patch as a file in the .mobb folder
14368
+ * @param fix McpFix object containing all fix information
14369
+ * @param customFileName Optional custom filename (overrides generated format)
14370
+ * @returns Result of the save operation including file path
14371
+ */
14372
+ async savePatch(fix, customFileName) {
14373
+ const patch = this.extractPatchFromFix(fix);
14374
+ const severity = this.extractSeverity(fix);
14375
+ const vulnerabilityType = this.extractVulnerabilityType(fix);
14376
+ const fileName = this.extractFileNameFromFix(fix);
14377
+ logDebug("[LocalMobbFolderService] Saving patch", {
14378
+ fixId: fix.id,
14379
+ fileName,
14380
+ severity,
14381
+ vulnerabilityType,
14382
+ customFileName
14383
+ });
14384
+ try {
14385
+ if (!patch || patch.trim().length === 0) {
14386
+ const error = `No valid patch content found for fix ${fix.id}`;
14387
+ logError("[LocalMobbFolderService] Invalid patch content", {
14388
+ error,
14389
+ fixId: fix.id
14390
+ });
14391
+ return {
14392
+ success: false,
14393
+ error,
14394
+ message: "Failed to save patch: no valid patch content"
14395
+ };
14396
+ }
14397
+ if (!fileName) {
14398
+ const error = `Could not determine filename for fix ${fix.id}`;
14399
+ logError("[LocalMobbFolderService] Missing fileName", {
14400
+ error,
14401
+ fixId: fix.id
14402
+ });
14403
+ return {
14404
+ success: false,
14405
+ error,
14406
+ message: "Failed to save patch: could not determine filename"
14407
+ };
14408
+ }
14409
+ const mobbFolderPath = await this.getFolder();
14410
+ let baseFileName;
14411
+ if (customFileName) {
14412
+ baseFileName = customFileName.endsWith(".patch") ? customFileName : `${customFileName}.patch`;
14413
+ } else {
14414
+ baseFileName = this.generatePatchFileName(
14415
+ severity,
14416
+ vulnerabilityType,
14417
+ fileName
14418
+ );
14419
+ }
14420
+ const uniqueFileName = this.getUniqueFileName(
14421
+ mobbFolderPath,
14422
+ baseFileName
14423
+ );
14424
+ const filePath = path12.join(mobbFolderPath, uniqueFileName);
14425
+ await fs12.promises.writeFile(filePath, patch, "utf8");
14426
+ logInfo("[LocalMobbFolderService] Patch saved successfully", {
14427
+ filePath,
14428
+ fileName: uniqueFileName,
14429
+ patchSize: patch.length
14430
+ });
14431
+ return {
14432
+ success: true,
14433
+ filePath,
14434
+ fileName: uniqueFileName,
14435
+ message: `Patch saved successfully as ${uniqueFileName}`
14436
+ };
14437
+ } catch (error) {
14438
+ const errorMessage = `Failed to save patch for fix ${fix.id}: ${error}`;
14439
+ logError(errorMessage, {
14440
+ fixId: fix.id,
14441
+ fileName,
14442
+ severity,
14443
+ vulnerabilityType,
14444
+ customFileName,
14445
+ error
14446
+ });
14447
+ return {
14448
+ success: false,
14449
+ error: errorMessage,
14450
+ message: errorMessage
14451
+ };
14452
+ }
14453
+ }
14454
+ /**
14455
+ * Generates a filename for a patch using the format: severity-vulnerabilityType-in-fileName.patch
14456
+ * @param severity Severity level of the vulnerability
14457
+ * @param vulnerabilityType Type of vulnerability
14458
+ * @param fileName Name of the file being patched
14459
+ * @returns Generated filename with .patch extension
14460
+ */
14461
+ generatePatchFileName(severity, vulnerabilityType, fileName) {
14462
+ const safeSeverity = this.sanitizeForFilename(severity);
14463
+ const safeVulnType = this.sanitizeForFilename(vulnerabilityType);
14464
+ const safeFileName = this.sanitizeForFilename(fileName);
14465
+ return `${safeSeverity}-${safeVulnType}-in-${safeFileName}.patch`;
14466
+ }
14467
+ /**
14468
+ * Sanitizes a string to be safe for use in filenames
14469
+ * @param input String to sanitize
14470
+ * @returns Sanitized string safe for filenames
14471
+ */
14472
+ sanitizeForFilename(input) {
14473
+ return input.toLowerCase().replace(/[^a-z0-9.-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
14474
+ }
14475
+ /**
14476
+ * Gets a unique filename by adding index suffix if file already exists
14477
+ * @param folderPath Path to the folder where file will be saved
14478
+ * @param baseFileName Base filename to make unique
14479
+ * @returns Unique filename that doesn't conflict with existing files
14480
+ */
14481
+ getUniqueFileName(folderPath, baseFileName) {
14482
+ const baseName = path12.parse(baseFileName).name;
14483
+ const extension = path12.parse(baseFileName).ext;
14484
+ let uniqueFileName = baseFileName;
14485
+ let index = 1;
14486
+ while (fs12.existsSync(path12.join(folderPath, uniqueFileName))) {
14487
+ uniqueFileName = `${baseName}-${index}${extension}`;
14488
+ index++;
14489
+ if (index > 1e3) {
14490
+ const timestamp = Date.now();
14491
+ uniqueFileName = `${baseName}-${timestamp}${extension}`;
14492
+ break;
14493
+ }
14494
+ }
14495
+ if (uniqueFileName !== baseFileName) {
14496
+ logDebug(
14497
+ "[LocalMobbFolderService] Generated unique filename to avoid conflict",
14498
+ {
14499
+ original: baseFileName,
14500
+ unique: uniqueFileName
14501
+ }
14502
+ );
14503
+ }
14504
+ return uniqueFileName;
14505
+ }
14506
+ /**
14507
+ * Logs fix details in markdown format to patchInfo.md file
14508
+ * Prepends the new fix information to maintain chronological order (newest first)
14509
+ * @param fix McpFix object containing fix details
14510
+ * @param savedPatchFileName The filename of the saved patch file
14511
+ * @returns Result of the log operation
14512
+ */
14513
+ async logPatch(fix, savedPatchFileName) {
14514
+ logDebug("[LocalMobbFolderService] Logging patch info", { fixId: fix.id });
14515
+ try {
14516
+ const mobbFolderPath = await this.getFolder();
14517
+ const patchInfoPath = path12.join(mobbFolderPath, "patchInfo.md");
14518
+ const markdownContent = this.generateFixMarkdown(fix, savedPatchFileName);
14519
+ let existingContent = "";
14520
+ if (fs12.existsSync(patchInfoPath)) {
14521
+ existingContent = await fs12.promises.readFile(patchInfoPath, "utf8");
14522
+ logDebug("[LocalMobbFolderService] Existing patchInfo.md found");
14523
+ } else {
14524
+ logDebug("[LocalMobbFolderService] Creating new patchInfo.md file");
14525
+ }
14526
+ const separator = existingContent ? "\n\n================================================================================\n\n" : "";
14527
+ const updatedContent = `${markdownContent}${separator}${existingContent}`;
14528
+ await fs12.promises.writeFile(patchInfoPath, updatedContent, "utf8");
14529
+ logInfo("[LocalMobbFolderService] Patch info logged successfully", {
14530
+ patchInfoPath,
14531
+ fixId: fix.id,
14532
+ contentLength: updatedContent.length
14533
+ });
14534
+ return {
14535
+ success: true,
14536
+ filePath: patchInfoPath,
14537
+ message: `Fix details logged to patchInfo.md`
14538
+ };
14539
+ } catch (error) {
14540
+ const errorMessage = `Failed to log patch info: ${error}`;
14541
+ logError(errorMessage, { fixId: fix.id, error });
14542
+ return {
14543
+ success: false,
14544
+ error: errorMessage,
14545
+ message: errorMessage
14546
+ };
14547
+ }
14548
+ }
14549
+ /**
14550
+ * Generates markdown formatted content for a fix
14551
+ * @param fix McpFix object to format
14552
+ * @param savedPatchFileName The filename of the saved patch file
14553
+ * @returns Markdown formatted string
14554
+ */
14555
+ generateFixMarkdown(fix, savedPatchFileName) {
14556
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
14557
+ const patch = this.extractPatchFromFix(fix);
14558
+ const relativePatchedFilePath = patch ? extractPathFromPatch(patch) : null;
14559
+ const patchedFilePath = relativePatchedFilePath ? path12.resolve(this.repoPath, relativePatchedFilePath) : null;
14560
+ const fixIdentifier = savedPatchFileName ? savedPatchFileName.replace(".patch", "") : fix.id;
14561
+ let markdown = `# Fix ${fixIdentifier}
14562
+
14563
+ `;
14564
+ markdown += `**Logged:** ${timestamp}
14565
+
14566
+ `;
14567
+ if (patchedFilePath) {
14568
+ markdown += `**Patched File:** ${patchedFilePath}
14569
+
14570
+ `;
14571
+ }
14572
+ if (fix.fixUrl) {
14573
+ markdown += `**Fix URL:** [View Fix](${fix.fixUrl})
14574
+
14575
+ `;
14576
+ }
14577
+ if (fix.severityText) {
14578
+ markdown += `**Severity:** ${fix.severityText}`;
14579
+ if (fix.severityValue !== null && fix.severityValue !== void 0) {
14580
+ markdown += ` (${fix.severityValue})`;
14581
+ }
14582
+ markdown += `
14583
+
14584
+ `;
14585
+ }
14586
+ markdown += `**Confidence:** ${fix.confidence}
14587
+
14588
+ `;
14589
+ if (fix.safeIssueType) {
14590
+ markdown += `**Issue Type:** ${fix.safeIssueType}
14591
+
14592
+ `;
14593
+ }
14594
+ const parseIssueTypeRes = parsedIssueTypeRes(fix.safeIssueType || void 0);
14595
+ const staticData = parseIssueTypeRes.success ? fixDetailsData[parseIssueTypeRes.data] : null;
14596
+ const { issueDescription: issueDescription2, fixInstructions } = staticData || {};
14597
+ markdown += `
14598
+ ## Vulnerability Details
14599
+
14600
+ `;
14601
+ if (issueDescription2) {
14602
+ markdown += `### Issue
14603
+
14604
+ `;
14605
+ markdown += `${issueDescription2}
14606
+
14607
+ `;
14608
+ }
14609
+ if (fixInstructions) {
14610
+ markdown += `### How to Fix
14611
+
14612
+ `;
14613
+ markdown += `${fixInstructions}
14614
+
14615
+ `;
14616
+ }
14617
+ if (fix.vulnerabilityReportIssues.length > 0) {
14618
+ fix.vulnerabilityReportIssues.forEach((issue) => {
14619
+ if (issue.vulnerabilityReportIssueTags && issue.vulnerabilityReportIssueTags.length > 0) {
14620
+ markdown += `**Tags:** ${issue.vulnerabilityReportIssueTags.map((tag) => tag.vulnerability_report_issue_tag_value).join(", ")}
14621
+
14622
+ `;
14623
+ }
14624
+ });
14625
+ }
14626
+ if (fix.patchAndQuestions.__typename === "FixData") {
14627
+ const fixData = fix.patchAndQuestions;
14628
+ if (fixData.extraContext.fixDescription) {
14629
+ markdown += `## Fix Description
14630
+
14631
+ `;
14632
+ markdown += `${fixData.extraContext.fixDescription}
14633
+
14634
+ `;
14635
+ }
14636
+ if (fixData.extraContext.extraContext.length > 0) {
14637
+ markdown += `## Additional Context
14638
+
14639
+ `;
14640
+ fixData.extraContext.extraContext.forEach((context) => {
14641
+ markdown += `**${context.key}:** ${JSON.stringify(context.value)}
14642
+
14643
+ `;
14644
+ });
14645
+ }
14646
+ const patchLines = fixData.patch.split("\n");
14647
+ const previewLines = patchLines.slice(0, 10);
14648
+ markdown += `## Patch Preview
14649
+
14650
+ `;
14651
+ markdown += `\`\`\`diff
14652
+ `;
14653
+ markdown += `${previewLines.join("\n")}`;
14654
+ if (patchLines.length > 10) {
14655
+ markdown += `
14656
+ ... (${patchLines.length - 10} more lines)`;
14657
+ }
14658
+ markdown += `
14659
+ \`\`\`
14660
+
14661
+ `;
14662
+ } else if (fix.patchAndQuestions.__typename === "GetFixNoFixError") {
14663
+ markdown += `## Fix Status
14664
+
14665
+ `;
14666
+ markdown += `\u26A0\uFE0F **No fix available** - This vulnerability could not be automatically fixed.
14667
+
14668
+ `;
14669
+ }
14670
+ return markdown;
14671
+ }
14672
+ // /**
14673
+ // * Gets information about a local Mobb folder
14674
+ // * @param folderPath Path to the folder to examine
14675
+ // * @returns Folder information including existence and basic stats
14676
+ // */
14677
+ // public async getFolderInfo(folderPath: string): Promise<FolderInfo> {
14678
+ // logDebug('[LocalMobbFolderService] Getting folder info', { folderPath })
14679
+ // const folderInfo: FolderInfo = {
14680
+ // path: folderPath,
14681
+ // exists: false,
14682
+ // isDirectory: false,
14683
+ // }
14684
+ // try {
14685
+ // const stats = await fs.promises.stat(folderPath)
14686
+ // folderInfo.exists = true
14687
+ // folderInfo.isDirectory = stats.isDirectory()
14688
+ // if (folderInfo.isDirectory) {
14689
+ // // Count files in directory
14690
+ // const files = await fs.promises.readdir(folderPath)
14691
+ // folderInfo.filesCount = files.length
14692
+ // // Calculate directory size (optional, can be expensive for large directories)
14693
+ // let totalSize = 0
14694
+ // for (const file of files) {
14695
+ // try {
14696
+ // const filePath = path.join(folderPath, file)
14697
+ // const fileStats = await fs.promises.stat(filePath)
14698
+ // if (fileStats.isFile()) {
14699
+ // totalSize += fileStats.size
14700
+ // }
14701
+ // } catch (error) {
14702
+ // logDebug('[LocalMobbFolderService] Error getting file size', { file, error })
14703
+ // }
14704
+ // }
14705
+ // folderInfo.size = totalSize
14706
+ // }
14707
+ // } catch (error) {
14708
+ // logDebug('[LocalMobbFolderService] Folder does not exist or is not accessible', {
14709
+ // folderPath,
14710
+ // error,
14711
+ // })
14712
+ // }
14713
+ // return folderInfo
14714
+ // }
14715
+ // /**
14716
+ // * Creates a local Mobb folder at the specified path
14717
+ // * @param basePath Base directory where the Mobb folder should be created
14718
+ // * @param folderName Optional custom folder name (defaults to '.mobb')
14719
+ // * @returns Result of the folder creation operation
14720
+ // */
14721
+ // public async createMobbFolder(
14722
+ // basePath: string,
14723
+ // folderName: string = this.defaultMobbFolderName
14724
+ // ): Promise<FolderCreationResult> {
14725
+ // const targetPath = path.join(basePath, folderName)
14726
+ // logInfo('[LocalMobbFolderService] Creating Mobb folder', { targetPath })
14727
+ // try {
14728
+ // // Check if base path exists
14729
+ // const basePathExists = await this.pathExists(basePath)
14730
+ // if (!basePathExists) {
14731
+ // const error = `Base path does not exist: ${basePath}`
14732
+ // logError(error)
14733
+ // return {
14734
+ // success: false,
14735
+ // path: targetPath,
14736
+ // error,
14737
+ // }
14738
+ // }
14739
+ // // Check if target folder already exists
14740
+ // const folderExists = await this.pathExists(targetPath)
14741
+ // if (folderExists) {
14742
+ // logInfo('[LocalMobbFolderService] Mobb folder already exists', { targetPath })
14743
+ // return {
14744
+ // success: true,
14745
+ // path: targetPath,
14746
+ // }
14747
+ // }
14748
+ // // Create the folder
14749
+ // await fs.promises.mkdir(targetPath, { recursive: true })
14750
+ // logInfo('[LocalMobbFolderService] Mobb folder created successfully', { targetPath })
14751
+ // return {
14752
+ // success: true,
14753
+ // path: targetPath,
14754
+ // }
14755
+ // } catch (error) {
14756
+ // const errorMessage = `Failed to create Mobb folder: ${error}`
14757
+ // logError(errorMessage, { targetPath, error })
14758
+ // return {
14759
+ // success: false,
14760
+ // path: targetPath,
14761
+ // error: errorMessage,
14762
+ // }
14763
+ // }
14764
+ // }
14765
+ // /**
14766
+ // * Finds the nearest Mobb folder by traversing up the directory tree
14767
+ // * @param startPath Starting directory to search from
14768
+ // * @param folderName Optional custom folder name to search for (defaults to '.mobb')
14769
+ // * @returns Path to the found Mobb folder or null if not found
14770
+ // */
14771
+ // public async findNearestMobbFolder(
14772
+ // startPath: string,
14773
+ // folderName: string = this.defaultMobbFolderName
14774
+ // ): Promise<string | null> {
14775
+ // logDebug('[LocalMobbFolderService] Searching for nearest Mobb folder', {
14776
+ // startPath,
14777
+ // folderName,
14778
+ // })
14779
+ // let currentPath = path.resolve(startPath)
14780
+ // const rootPath = path.parse(currentPath).root
14781
+ // while (currentPath !== rootPath) {
14782
+ // const mobbFolderPath = path.join(currentPath, folderName)
14783
+ // const folderInfo = await this.getFolderInfo(mobbFolderPath)
14784
+ // if (folderInfo.exists && folderInfo.isDirectory) {
14785
+ // logDebug('[LocalMobbFolderService] Found Mobb folder', { mobbFolderPath })
14786
+ // return mobbFolderPath
14787
+ // }
14788
+ // // Move up one directory
14789
+ // const parentPath = path.dirname(currentPath)
14790
+ // if (parentPath === currentPath) {
14791
+ // break // Reached the root
14792
+ // }
14793
+ // currentPath = parentPath
14794
+ // }
14795
+ // logDebug('[LocalMobbFolderService] No Mobb folder found')
14796
+ // return null
14797
+ // }
14798
+ // /**
14799
+ // * Cleans up a Mobb folder by removing its contents
14800
+ // * @param folderPath Path to the Mobb folder to clean
14801
+ // * @returns Result of the cleanup operation
14802
+ // */
14803
+ // public async cleanMobbFolder(folderPath: string): Promise<FolderOperationResult> {
14804
+ // logInfo('[LocalMobbFolderService] Cleaning Mobb folder', { folderPath })
14805
+ // try {
14806
+ // const folderInfo = await this.getFolderInfo(folderPath)
14807
+ // if (!folderInfo.exists) {
14808
+ // return {
14809
+ // success: false,
14810
+ // message: `Folder does not exist: ${folderPath}`,
14811
+ // error: 'Folder not found',
14812
+ // }
14813
+ // }
14814
+ // if (!folderInfo.isDirectory) {
14815
+ // return {
14816
+ // success: false,
14817
+ // message: `Path is not a directory: ${folderPath}`,
14818
+ // error: 'Not a directory',
14819
+ // }
14820
+ // }
14821
+ // // Remove all contents
14822
+ // const files = await fs.promises.readdir(folderPath)
14823
+ // for (const file of files) {
14824
+ // const filePath = path.join(folderPath, file)
14825
+ // try {
14826
+ // const stats = await fs.promises.stat(filePath)
14827
+ // if (stats.isDirectory()) {
14828
+ // await fs.promises.rmdir(filePath, { recursive: true })
14829
+ // } else {
14830
+ // await fs.promises.unlink(filePath)
14831
+ // }
14832
+ // } catch (error) {
14833
+ // logError('[LocalMobbFolderService] Error removing file/folder', {
14834
+ // filePath,
14835
+ // error,
14836
+ // })
14837
+ // }
14838
+ // }
14839
+ // logInfo('[LocalMobbFolderService] Mobb folder cleaned successfully', { folderPath })
14840
+ // return {
14841
+ // success: true,
14842
+ // message: `Successfully cleaned folder: ${folderPath}`,
14843
+ // }
14844
+ // } catch (error) {
14845
+ // const errorMessage = `Failed to clean Mobb folder: ${error}`
14846
+ // logError(errorMessage, { folderPath, error })
14847
+ // return {
14848
+ // success: false,
14849
+ // message: errorMessage,
14850
+ // error: String(error),
14851
+ // }
14852
+ // }
14853
+ // }
14854
+ // /**
14855
+ // * Validates that a path is safe for Mobb folder operations
14856
+ // * @param folderPath Path to validate
14857
+ // * @returns True if the path is safe to use
14858
+ // */
14859
+ // public validatePath(folderPath: string): boolean {
14860
+ // logDebug('[LocalMobbFolderService] Validating path', { folderPath })
14861
+ // // Check for path traversal attempts
14862
+ // if (folderPath.includes('..') || folderPath.includes('\0')) {
14863
+ // logError('[LocalMobbFolderService] Dangerous path detected', { folderPath })
14864
+ // return false
14865
+ // }
14866
+ // // Check for absolute path requirements (optional, depending on your needs)
14867
+ // const normalizedPath = path.normalize(folderPath)
14868
+ // if (normalizedPath.includes('..')) {
14869
+ // logError('[LocalMobbFolderService] Path traversal detected after normalization', {
14870
+ // folderPath,
14871
+ // normalizedPath,
14872
+ // })
14873
+ // return false
14874
+ // }
14875
+ // return true
14876
+ // }
14877
+ // /**
14878
+ // * Helper method to check if a path exists
14879
+ // * @param targetPath Path to check
14880
+ // * @returns True if the path exists
14881
+ // */
14882
+ // private async pathExists(targetPath: string): Promise<boolean> {
14883
+ // try {
14884
+ // await fs.promises.access(targetPath)
14885
+ // return true
14886
+ // } catch {
14887
+ // return false
14888
+ // }
14889
+ // }
14890
+ };
14891
+
14892
+ // src/mcp/services/PatchApplicationService.ts
14893
+ init_configs();
14894
+ import {
14895
+ existsSync as existsSync2,
14896
+ mkdirSync,
14897
+ readFileSync,
14898
+ unlinkSync,
14899
+ writeFileSync
14900
+ } from "fs";
14901
+ import fs13 from "fs/promises";
14902
+ import parseDiff2 from "parse-diff";
14903
+ import path13 from "path";
14904
+ var PatchApplicationService = class {
14905
+ /**
14906
+ * Gets the appropriate comment syntax for a file based on its extension
14907
+ */
14908
+ static getCommentSyntax(filePath) {
14909
+ const ext = path13.extname(filePath).toLowerCase();
14910
+ const basename2 = path13.basename(filePath);
14911
+ const commentMap = {
14912
+ // C-style languages (single line comments)
14913
+ ".js": "//",
14914
+ ".jsx": "//",
14915
+ ".mjs": "//",
14916
+ ".cjs": "//",
14917
+ ".ts": "//",
14918
+ ".tsx": "//",
14919
+ ".java": "//",
14920
+ ".c": "//",
14921
+ ".cpp": "//",
14922
+ ".cc": "//",
14923
+ ".cxx": "//",
14924
+ ".c++": "//",
14925
+ ".pcc": "//",
14926
+ ".tpp": "//",
14927
+ ".C": "//",
14928
+ ".h": "//",
14929
+ ".hpp": "//",
14930
+ ".hh": "//",
14931
+ ".hxx": "//",
14932
+ ".inl": "//",
14933
+ ".ipp": "//",
14934
+ ".cs": "//",
14935
+ ".php": "//",
14936
+ ".tpl": "//",
14937
+ ".phtml": "//",
14938
+ ".go": "//",
14939
+ ".rs": "//",
14940
+ ".swift": "//",
14941
+ ".kt": "//",
14942
+ ".kts": "//",
14943
+ ".ktm": "//",
14944
+ ".scala": "//",
14945
+ ".dart": "//",
14946
+ ".cls": "//",
14947
+ // Apex
14948
+ ".sol": "//",
14949
+ // Solidity
14950
+ ".move": "//",
14951
+ // Move
14952
+ ".hack": "//",
14953
+ // Hack
14954
+ ".hck": "//",
14955
+ // Hack
14956
+ ".jl": "//",
14957
+ // Julia
14958
+ ".proto": "//",
14959
+ // Protocol Buffers
14960
+ // Python-style languages
14961
+ ".py": "#",
14962
+ ".pyx": "#",
14963
+ ".pyi": "#",
14964
+ ".sh": "#",
14965
+ ".bash": "#",
14966
+ ".zsh": "#",
14967
+ ".fish": "#",
14968
+ ".pl": "#",
14969
+ ".pm": "#",
14970
+ ".rb": "#",
14971
+ ".r": "#",
14972
+ ".R": "#",
14973
+ ".yaml": "#",
14974
+ ".yml": "#",
14975
+ ".toml": "#",
14976
+ ".conf": "#",
14977
+ ".cfg": "#",
14978
+ ".ini": "#",
14979
+ ".dockerfile": "#",
14980
+ ".Dockerfile": "#",
14981
+ ".tf": "#",
14982
+ // Terraform
14983
+ ".tfvars": "#",
14984
+ // Terraform
14985
+ ".hcl": "#",
14986
+ // Terraform/HCL
14987
+ ".promql": "#",
14988
+ // PromQL
14989
+ ".cairo": "#",
14990
+ // Cairo
14991
+ ".circom": "#",
14992
+ // Circom
14993
+ // SQL
14994
+ ".sql": "--",
14995
+ // HTML/XML style
14996
+ ".html": "<!--",
14997
+ ".htm": "<!--",
14998
+ ".xml": "<!--",
14999
+ ".plist": "<!--",
15000
+ ".xhtml": "<!--",
15001
+ ".svg": "<!--",
15002
+ ".vue": "<!--",
15003
+ // Vue templates
15004
+ // CSS
15005
+ ".css": "/*",
15006
+ ".less": "//",
15007
+ ".scss": "//",
15008
+ ".sass": "//",
15009
+ // Other scripting
15010
+ ".lua": "--",
15011
+ ".vim": '"',
15012
+ ".vimrc": '"',
15013
+ ".ql": "//",
15014
+ // QL
15015
+ ".qll": "//",
15016
+ // QL
15017
+ // Assembly
15018
+ ".asm": ";",
15019
+ ".s": ";",
15020
+ // Batch/PowerShell
15021
+ ".bat": "REM",
15022
+ ".cmd": "REM",
15023
+ ".ps1": "#",
15024
+ // Functional languages
15025
+ ".hs": "--",
15026
+ ".lhs": "--",
15027
+ ".elm": "--",
15028
+ ".ml": "(*",
15029
+ ".mli": "(*",
15030
+ ".fs": "//",
15031
+ ".fsx": "//",
15032
+ ".fsi": "//",
15033
+ // Lisp family
15034
+ ".lisp": ";",
15035
+ ".lsp": ";",
15036
+ ".cl": ";",
15037
+ ".el": ";",
15038
+ // Emacs Lisp
15039
+ ".scm": ";",
15040
+ ".ss": ";",
15041
+ ".clj": ";",
15042
+ ".cljs": ";",
15043
+ ".cljc": ";",
15044
+ ".edn": ";",
15045
+ // Clojure EDN
15046
+ // Configuration files
15047
+ ".properties": "#",
15048
+ ".gitignore": "#",
15049
+ ".env": "#",
15050
+ ".editorconfig": "#",
15051
+ ".ex": "#",
15052
+ // Elixir
15053
+ ".exs": "#",
15054
+ // Elixir
15055
+ ".json": "//",
15056
+ // JSON (though rarely commented)
15057
+ ".ipynb": "#",
15058
+ // Jupyter notebooks (JSON-based)
15059
+ ".jsonnet": "//",
15060
+ // Jsonnet
15061
+ ".libsonnet": "//",
15062
+ // Jsonnet
15063
+ Dockerfile: "#",
15064
+ // Docker files without extension
15065
+ // Default fallback
15066
+ "": "//"
15067
+ };
15068
+ if (commentMap[basename2]) {
15069
+ return commentMap[basename2];
15070
+ }
15071
+ return commentMap[ext] || commentMap[""] || "//";
15072
+ }
15073
+ /**
15074
+ * Writes content to a file and adds Mobb fix comment in debug mode
15075
+ */
15076
+ static writeFileWithFixComment({
15077
+ filePath,
15078
+ content,
15079
+ fix,
15080
+ scanContext
15081
+ }) {
15082
+ let finalContent = content;
15083
+ if (MCP_AUTO_FIX_DEBUG_MODE) {
15084
+ const fixType = fix.safeIssueType || "Security Issue";
15085
+ let fixLink;
15086
+ if (fix.fixUrl) {
15087
+ fixLink = fix.fixUrl;
15088
+ } else {
15089
+ const apiUrl = process.env["API_URL"] || MCP_DEFAULT_API_URL;
15090
+ const appBaseUrl = apiUrl.replace("/v1/graphql", "").replace("api.", "");
15091
+ fixLink = `${appBaseUrl}/fixes/${fix.id}`;
15092
+ }
15093
+ const commentPrefix = this.getCommentSyntax(filePath);
15094
+ const lines = content.split("\n");
15095
+ const lastLine = lines[lines.length - 1]?.trim() || "";
15096
+ const isMobbComment = lastLine.includes("Mobb security fix applied:");
15097
+ const spacing = isMobbComment ? "\n" : "\n\n";
15098
+ let comment;
15099
+ if (commentPrefix === "<!--") {
15100
+ comment = `${spacing}<!-- Mobb security fix applied: ${fixType} ${fixLink} -->`;
15101
+ } else if (commentPrefix === "/*") {
15102
+ comment = `${spacing}/* Mobb security fix applied: ${fixType} ${fixLink} */`;
15103
+ } else if (commentPrefix === "(*") {
15104
+ comment = `${spacing}(* Mobb security fix applied: ${fixType} ${fixLink} *)`;
15105
+ } else {
15106
+ comment = `${spacing}${commentPrefix} Mobb security fix applied: ${fixType} ${fixLink}`;
15107
+ }
15108
+ finalContent = content + comment;
15109
+ logInfo(
15110
+ `[${scanContext}] Debug mode: Adding fix comment to ${filePath}`,
15111
+ {
15112
+ fixId: fix.id,
15113
+ fixType,
15114
+ fixLink,
15115
+ commentSyntax: commentPrefix,
15116
+ spacing: isMobbComment ? "single line" : "empty line above"
15117
+ }
15118
+ );
15119
+ }
15120
+ const dirPath = path13.dirname(filePath);
15121
+ mkdirSync(dirPath, { recursive: true });
15122
+ writeFileSync(filePath, finalContent, "utf8");
15123
+ return filePath;
15124
+ }
15125
+ /**
15126
+ * Extracts target file path from a fix
15127
+ */
15128
+ static getFixTargetFile(fix) {
15129
+ if (fix.patchAndQuestions?.__typename !== "FixData") {
15130
+ return null;
15131
+ }
15132
+ const patch = fix.patchAndQuestions.patch;
15133
+ if (!patch) return null;
15134
+ const match = patch.match(/^\+\+\+ b\/(.+)$/m);
15135
+ return match?.[1] || null;
15136
+ }
15137
+ /**
15138
+ * Groups fixes by target file and returns ranked fixes with fallback options
15139
+ */
15140
+ static selectBestFixesPerFile(fixes) {
15141
+ const fileToFixMap = /* @__PURE__ */ new Map();
15142
+ const standaloneFixesFixes = [];
15143
+ for (const fix of fixes) {
15144
+ const targetFile = this.getFixTargetFile(fix);
15145
+ if (!targetFile) {
15146
+ standaloneFixesFixes.push(fix);
15147
+ continue;
15148
+ }
15149
+ if (!fileToFixMap.has(targetFile)) {
15150
+ fileToFixMap.set(targetFile, []);
15151
+ }
15152
+ const fixes2 = fileToFixMap.get(targetFile);
15153
+ if (fixes2) {
15154
+ fixes2.push(fix);
15155
+ }
15156
+ }
15157
+ for (const [_targetFile, fileFixes] of fileToFixMap) {
15158
+ fileFixes.sort((a, b) => (b.severityValue ?? 0) - (a.severityValue ?? 0));
15159
+ }
15160
+ return { fileFixGroups: fileToFixMap, standaloneFixesFixes };
15161
+ }
15162
+ /**
15163
+ * Processes invalid fixes from file modification checks
15164
+ */
15165
+ static processInvalidFixes(invalidFixes, failedFixes, scanContext) {
15166
+ if (invalidFixes.length === 0) return;
15167
+ for (const invalidFix of invalidFixes) {
15168
+ logWarn(
15169
+ `[${scanContext}] Patch ${invalidFix.fix.id} not applied - file modified after scan`,
15170
+ {
15171
+ fixId: invalidFix.fix.id,
15172
+ issueType: invalidFix.fix.safeIssueType,
15173
+ severity: invalidFix.fix.severityText,
15174
+ severityValue: invalidFix.fix.severityValue,
15175
+ confidence: invalidFix.fix.confidence,
15176
+ reason: invalidFix.reason
15177
+ }
15178
+ );
15179
+ }
15180
+ logInfo(
15181
+ `[${scanContext}] Discarded ${invalidFixes.length} fixes due to file modifications after scan`,
15182
+ {
15183
+ discardedFixes: invalidFixes.map((f) => ({
15184
+ fixId: f.fix.id,
15185
+ reason: f.reason
15186
+ }))
15187
+ }
15188
+ );
15189
+ failedFixes.push(
15190
+ ...invalidFixes.map((f) => ({ fix: f.fix, error: f.reason }))
15191
+ );
15192
+ }
15193
+ /**
15194
+ * Applies standalone fixes (fixes that don't target specific files)
15195
+ */
15196
+ static async applyStandaloneFixes({
15197
+ fixes,
15198
+ repositoryPath,
15199
+ appliedFixes,
15200
+ failedFixes,
15201
+ scanContext
15202
+ }) {
15203
+ for (const fix of fixes) {
15204
+ try {
15205
+ const result = await this.applySingleFix({
15206
+ fix,
15207
+ repositoryPath,
15208
+ scanContext
15209
+ });
15210
+ if (result.success) {
15211
+ appliedFixes.push(fix);
15212
+ logInfo(
15213
+ `[${scanContext}] Successfully applied standalone fix ${fix.id}`,
15214
+ {
15215
+ issueType: fix.safeIssueType,
15216
+ severity: fix.severityText
15217
+ }
15218
+ );
15219
+ } else {
15220
+ failedFixes.push({ fix, error: result.error || "Unknown error" });
15221
+ }
15222
+ } catch (error) {
15223
+ const errorMessage = error instanceof Error ? error.message : String(error);
15224
+ logError(`[${scanContext}] Failed to apply standalone fix ${fix.id}`, {
15225
+ error: errorMessage,
15226
+ issueType: fix.safeIssueType
15227
+ });
15228
+ failedFixes.push({ fix, error: errorMessage });
15229
+ }
15230
+ }
15231
+ }
15232
+ /**
15233
+ * Attempts to apply a single fix and handles the result
15234
+ */
15235
+ static async attemptFixApplication({
15236
+ fix,
15237
+ targetFile,
15238
+ attemptNumber,
15239
+ totalAttempts,
15240
+ repositoryPath,
15241
+ scanContext
15242
+ }) {
15243
+ logInfo(
15244
+ `[${scanContext}] Attempting fix ${fix.id} for ${targetFile} (priority ${attemptNumber}/${totalAttempts})`,
15245
+ {
15246
+ issueType: fix.safeIssueType,
15247
+ severity: fix.severityText,
15248
+ severityValue: fix.severityValue
15249
+ }
15250
+ );
15251
+ try {
15252
+ const result = await this.applySingleFix({
15253
+ fix,
15254
+ repositoryPath,
15255
+ scanContext
15256
+ });
15257
+ if (result.success) {
15258
+ logInfo(
15259
+ `[${scanContext}] Successfully applied fix ${fix.id} to ${targetFile}`,
15260
+ {
15261
+ issueType: fix.safeIssueType,
15262
+ severity: fix.severityText,
15263
+ attemptNumber
15264
+ }
15265
+ );
15266
+ return { success: true };
15267
+ } else {
15268
+ logInfo(
15269
+ `[${scanContext}] Fix ${fix.id} failed for ${targetFile}, trying next option if available`,
15270
+ {
15271
+ error: result.error,
15272
+ attemptNumber,
15273
+ remainingOptions: totalAttempts - attemptNumber
15274
+ }
15275
+ );
15276
+ return { success: false, error: result.error };
15277
+ }
15278
+ } catch (error) {
15279
+ const errorMessage = error instanceof Error ? error.message : String(error);
15280
+ logInfo(
15281
+ `[${scanContext}] Fix ${fix.id} threw exception for ${targetFile}, trying next option if available`,
15282
+ {
15283
+ error: errorMessage,
15284
+ issueType: fix.safeIssueType,
15285
+ attemptNumber,
15286
+ remainingOptions: totalAttempts - attemptNumber
15287
+ }
15288
+ );
15289
+ return { success: false, error: errorMessage };
15290
+ }
15291
+ }
15292
+ /**
15293
+ * Applies fixes for a specific file with fallback logic
15294
+ */
15295
+ static async applyFixesForFile({
15296
+ targetFile,
15297
+ rankedFixes,
15298
+ repositoryPath,
15299
+ appliedFixes,
15300
+ failedFixes,
15301
+ skippedFixes,
15302
+ scanContext
15303
+ }) {
15304
+ let appliedForFile = false;
15305
+ for (let i = 0; i < rankedFixes.length; i++) {
15306
+ const fix = rankedFixes[i];
15307
+ if (!fix) continue;
15308
+ if (appliedForFile) {
15309
+ const reason = `Another fix was already successfully applied to ${targetFile}`;
15310
+ skippedFixes.push({
15311
+ fix,
15312
+ reason
15313
+ });
15314
+ logWarn(
15315
+ `[${scanContext}] Patch ${fix.id} not applied - file already patched`,
15316
+ {
15317
+ fixId: fix.id,
15318
+ targetFile,
15319
+ issueType: fix.safeIssueType,
15320
+ severity: fix.severityText,
15321
+ severityValue: fix.severityValue,
15322
+ reason,
15323
+ confidence: fix.confidence
15324
+ }
15325
+ );
15326
+ continue;
15327
+ }
15328
+ const result = await this.attemptFixApplication({
15329
+ fix,
15330
+ targetFile,
15331
+ attemptNumber: i + 1,
15332
+ totalAttempts: rankedFixes.length,
15333
+ repositoryPath,
15334
+ scanContext
15335
+ });
15336
+ if (result.success) {
15337
+ appliedFixes.push(fix);
15338
+ appliedForFile = true;
15339
+ } else {
15340
+ if (i === rankedFixes.length - 1) {
15341
+ failedFixes.push({ fix, error: result.error || "Unknown error" });
15342
+ }
15343
+ }
15344
+ }
15345
+ }
15346
+ /**
15347
+ * Applies fixes grouped by file with fallback logic
15348
+ */
15349
+ static async applyFileGroupFixes({
15350
+ fileFixGroups,
15351
+ repositoryPath,
15352
+ appliedFixes,
15353
+ failedFixes,
15354
+ skippedFixes,
15355
+ scanContext
15356
+ }) {
15357
+ for (const [targetFile, rankedFixes] of fileFixGroups) {
15358
+ await this.applyFixesForFile({
15359
+ targetFile,
15360
+ rankedFixes,
15361
+ repositoryPath,
15362
+ appliedFixes,
15363
+ failedFixes,
15364
+ skippedFixes,
15365
+ scanContext
15366
+ });
15367
+ }
15368
+ }
15369
+ /**
15370
+ * Logs skipped fixes as informational warnings
15371
+ */
15372
+ static logSkippedFixes(skippedFixes, scanContext) {
15373
+ if (skippedFixes.length === 0) return;
15374
+ logWarn(
15375
+ `[${scanContext}] ${skippedFixes.length} fixes skipped because other fixes were successfully applied to their target files`,
15376
+ {
15377
+ skippedFixes: skippedFixes.map((f) => ({
15378
+ fixId: f.fix.id,
15379
+ reason: f.reason,
15380
+ severity: f.fix.severityText,
15381
+ issueType: f.fix.safeIssueType
15382
+ }))
15383
+ }
15384
+ );
15385
+ }
15386
+ /**
15387
+ * Checks if target files have been modified since scan time
15388
+ */
15389
+ static async checkFileModificationTimes({
15390
+ fixes,
15391
+ repositoryPath,
15392
+ scanStartTime,
15393
+ scanContext
15394
+ }) {
15395
+ const validFixes = [];
15396
+ const invalidFixes = [];
15397
+ for (const fix of fixes) {
15398
+ const targetFile = this.getFixTargetFile(fix);
15399
+ if (!targetFile) {
15400
+ validFixes.push(fix);
15401
+ continue;
15402
+ }
15403
+ try {
15404
+ const absolutePath = path13.resolve(repositoryPath, targetFile);
15405
+ if (existsSync2(absolutePath)) {
15406
+ const stats = await fs13.stat(absolutePath);
15407
+ const fileModTime = stats.mtime.getTime();
15408
+ if (fileModTime > scanStartTime) {
15409
+ logError(
15410
+ `[${scanContext}] Target file ${targetFile} was modified after scan started (file: ${new Date(fileModTime).toISOString()}, scan: ${new Date(scanStartTime).toISOString()})`
15411
+ );
15412
+ invalidFixes.push({
15413
+ fix,
15414
+ reason: `Target file ${targetFile} was modified after scan started (file: ${new Date(fileModTime).toISOString()}, scan: ${new Date(scanStartTime).toISOString()})`
15415
+ });
15416
+ } else {
15417
+ validFixes.push(fix);
15418
+ }
15419
+ } else {
15420
+ validFixes.push(fix);
15421
+ }
15422
+ } catch (error) {
15423
+ logError(
15424
+ `[${scanContext}] Error checking modification time for ${targetFile}`,
15425
+ {
15426
+ error: error instanceof Error ? error.message : String(error)
15427
+ }
15428
+ );
15429
+ validFixes.push(fix);
15430
+ }
15431
+ }
15432
+ return { validFixes, invalidFixes };
15433
+ }
15434
+ /**
15435
+ * Applies multiple patches to the repository
15436
+ */
15437
+ static async applyFixes({
15438
+ fixes,
15439
+ repositoryPath,
15440
+ scanStartTime,
15441
+ gqlClient,
15442
+ scanContext
15443
+ }) {
15444
+ const appliedFixes = [];
15445
+ const failedFixes = [];
15446
+ const skippedFixes = [];
15447
+ const resolvedRepoPath = await fs13.realpath(repositoryPath);
15448
+ logInfo(
15449
+ `[${scanContext}] Starting patch application for ${fixes.length} fixes`,
15450
+ {
15451
+ repositoryPath,
15452
+ resolvedRepoPath,
15453
+ fixIds: fixes.map((f) => f.id),
15454
+ scanStartTime: scanStartTime ? new Date(scanStartTime).toISOString() : "not provided"
15455
+ }
15456
+ );
15457
+ let processedFixes = fixes;
15458
+ if (scanStartTime) {
15459
+ const { validFixes, invalidFixes } = await this.checkFileModificationTimes({
15460
+ fixes,
15461
+ repositoryPath: resolvedRepoPath,
15462
+ scanStartTime,
15463
+ scanContext
15464
+ });
15465
+ this.processInvalidFixes(invalidFixes, failedFixes, scanContext);
15466
+ processedFixes = validFixes;
15467
+ }
15468
+ const { fileFixGroups, standaloneFixesFixes } = this.selectBestFixesPerFile(processedFixes);
15469
+ logInfo(
15470
+ `[${scanContext}] Grouped fixes: ${standaloneFixesFixes.length} standalone fixes, ${fileFixGroups.size} file groups`,
15471
+ {
15472
+ originalCount: fixes.length,
15473
+ totalFixesToApply: standaloneFixesFixes.length + fileFixGroups.size
15474
+ }
15475
+ );
15476
+ await this.applyStandaloneFixes({
15477
+ fixes: standaloneFixesFixes,
15478
+ repositoryPath: resolvedRepoPath,
15479
+ appliedFixes,
15480
+ failedFixes,
15481
+ scanContext
15482
+ });
15483
+ await this.applyFileGroupFixes({
15484
+ fileFixGroups,
15485
+ repositoryPath: resolvedRepoPath,
15486
+ appliedFixes,
15487
+ failedFixes,
15488
+ skippedFixes,
15489
+ scanContext
15490
+ });
15491
+ this.logSkippedFixes(skippedFixes, scanContext);
15492
+ logInfo(`[${scanContext}] Patch application completed`, {
15493
+ total: fixes.length,
15494
+ applied: appliedFixes.length,
15495
+ failed: failedFixes.length,
15496
+ skipped: skippedFixes.length
15497
+ });
15498
+ if (appliedFixes.length > 0 && gqlClient) {
15499
+ try {
15500
+ const appliedFixIds = appliedFixes.map((fix) => fix.id).filter(Boolean);
15501
+ await gqlClient.updateAutoAppliedFixesStatus(appliedFixIds);
15502
+ logDebug(
15503
+ `[${scanContext}] Successfully updated download status for auto-applied fixes`,
15504
+ {
15505
+ appliedFixIds,
15506
+ count: appliedFixIds.length
15507
+ }
15508
+ );
15509
+ } catch (error) {
15510
+ logError(
15511
+ `[${scanContext}] Failed to update download status for auto-applied fixes`,
15512
+ {
15513
+ error: error instanceof Error ? error.message : String(error),
15514
+ appliedFixCount: appliedFixes.length
15515
+ }
15516
+ );
15517
+ }
15518
+ }
15519
+ return {
15520
+ success: appliedFixes.length > 0,
15521
+ appliedFixes,
15522
+ failedFixes
15523
+ };
15524
+ }
15525
+ /**
15526
+ * Validates fix data and returns patch content
15527
+ */
15528
+ static validateFixData(fix, scanContext) {
15529
+ if (fix.patchAndQuestions?.__typename !== "FixData") {
15530
+ const error = "Fix does not contain patch data";
15531
+ logError(`[${scanContext}] Fix ${fix.id} does not contain patch data`);
15532
+ return { success: false, error };
15533
+ }
15534
+ const patch = fix.patchAndQuestions.patch;
15535
+ if (!patch) {
15536
+ const error = "Fix has empty patch content";
15537
+ logError(`[${scanContext}] Fix ${fix.id} has empty patch content`);
15538
+ return { success: false, error };
15539
+ }
15540
+ return { success: true, patch };
15541
+ }
15542
+ /**
15543
+ * Logs patch application information with error handling
15544
+ */
15545
+ static logPatchInfo(fix, patch, targetFiles, scanContext) {
15546
+ try {
15547
+ logInfo(`[${scanContext}] Applying patch for fix ${fix.id}`, {
15548
+ fixType: fix.safeIssueType,
15549
+ severity: fix.severityText,
15550
+ confidence: fix.confidence,
15551
+ targetFiles,
15552
+ patchLength: patch.length,
15553
+ patchPreview: patch.substring(0, 200) + (patch.length > 200 ? "..." : ""),
15554
+ description: fix.patchAndQuestions.__typename === "FixData" ? fix.patchAndQuestions.extraContext?.fixDescription : void 0
15555
+ });
15556
+ } catch (logError2) {
15557
+ logInfo(
15558
+ `[${scanContext}] Applying patch for fix ${fix.id} - fixType: ${fix.safeIssueType}`
15559
+ );
15560
+ }
15561
+ }
15562
+ /**
15563
+ * Parses a patch and validates it contains changes
15564
+ */
15565
+ static parsePatchSafely({
15566
+ patch,
15567
+ fixId,
15568
+ scanContext
15569
+ }) {
15570
+ const parsedPatch = parseDiff2(patch);
15571
+ if (!parsedPatch || parsedPatch.length === 0) {
15572
+ throw new Error("Failed to parse patch - no changes found");
15573
+ }
15574
+ logDebug(`[${scanContext}] Parsed patch for fix ${fixId}`, {
15575
+ filesCount: parsedPatch.length,
15576
+ files: parsedPatch.map((f) => ({
15577
+ from: f.from,
15578
+ to: f.to,
15579
+ deletions: f.deletions,
15580
+ additions: f.additions
15581
+ }))
15582
+ });
15583
+ return parsedPatch;
15584
+ }
15585
+ /**
15586
+ * Resolves file paths for patch application
15587
+ */
15588
+ static resolveFilePaths({
15589
+ targetFile,
15590
+ repositoryPath,
15591
+ scanContext
15592
+ }) {
15593
+ const sanitizedRepoPath = String(repositoryPath || "").replace("\0", "").replace(/^(\.\.(\/|\\))+/, "");
15594
+ const sanitizedTargetFile = String(targetFile || "").replace("\0", "").replace(/^(\.\.(\/|\\))+/, "");
15595
+ const absoluteFilePath = path13.resolve(
15596
+ sanitizedRepoPath,
15597
+ sanitizedTargetFile
15598
+ );
15599
+ const relativePath = path13.relative(sanitizedRepoPath, absoluteFilePath);
15600
+ if (relativePath.startsWith("..")) {
15601
+ throw new Error(
15602
+ `Security violation: target file ${targetFile} resolves outside repository`
15603
+ );
15604
+ }
15605
+ logDebug(`[${scanContext}] Resolving file path for ${targetFile}`, {
15606
+ repositoryPath: sanitizedRepoPath,
15607
+ targetFile: sanitizedTargetFile,
15608
+ absoluteFilePath,
15609
+ relativePath,
15610
+ exists: existsSync2(absoluteFilePath)
15611
+ });
15612
+ return { absoluteFilePath, relativePath };
15613
+ }
15614
+ /**
15615
+ * Handles file creation from patch
15616
+ */
15617
+ static handleFileCreation({
15618
+ fileDiff,
15619
+ absoluteFilePath,
15620
+ relativePath,
15621
+ repositoryPath,
15622
+ fix,
15623
+ appliedFiles,
15624
+ scanContext
15625
+ }) {
15626
+ const newContent = this.applyHunksToEmptyFile(fileDiff.chunks);
15627
+ const actualPath = this.writeFileWithFixComment({
15628
+ filePath: absoluteFilePath,
15629
+ content: newContent,
15630
+ fix,
15631
+ scanContext
15632
+ });
15633
+ appliedFiles.push(path13.relative(repositoryPath, actualPath));
15634
+ logDebug(`[${scanContext}] Created new file: ${relativePath}`);
15635
+ }
15636
+ /**
15637
+ * Handles file deletion from patch
15638
+ */
15639
+ static handleFileDeletion({
15640
+ absoluteFilePath,
15641
+ relativePath,
15642
+ appliedFiles,
15643
+ scanContext
15644
+ }) {
15645
+ if (existsSync2(absoluteFilePath)) {
15646
+ unlinkSync(absoluteFilePath);
15647
+ appliedFiles.push(relativePath);
15648
+ logDebug(`[${scanContext}] Deleted file: ${relativePath}`);
15649
+ }
15650
+ }
15651
+ /**
15652
+ * Handles file modification from patch
15653
+ */
15654
+ static handleFileModification({
15655
+ fileDiff,
15656
+ absoluteFilePath,
15657
+ relativePath,
15658
+ targetFile,
15659
+ repositoryPath,
15660
+ fix,
15661
+ appliedFiles,
15662
+ scanContext
15663
+ }) {
15664
+ if (!existsSync2(absoluteFilePath)) {
15665
+ throw new Error(
15666
+ `Target file does not exist: ${targetFile} (resolved to: ${absoluteFilePath})`
15667
+ );
15668
+ }
15669
+ const originalContent = readFileSync(absoluteFilePath, "utf8");
15670
+ const modifiedContent = this.applyHunksToFile(
15671
+ originalContent,
15672
+ fileDiff.chunks
15673
+ );
15674
+ if (modifiedContent !== originalContent) {
15675
+ const actualPath = this.writeFileWithFixComment({
15676
+ filePath: absoluteFilePath,
15677
+ content: modifiedContent,
15678
+ fix,
15679
+ scanContext
15680
+ });
15681
+ appliedFiles.push(path13.relative(repositoryPath, actualPath));
15682
+ logDebug(`[${scanContext}] Modified file: ${relativePath}`);
15683
+ }
15684
+ }
15685
+ /**
15686
+ * Processes a single file diff from a parsed patch
15687
+ */
15688
+ static processFileDiff({
15689
+ fileDiff,
15690
+ repositoryPath,
15691
+ fix,
15692
+ appliedFiles,
15693
+ scanContext
15694
+ }) {
15695
+ const targetFile = fileDiff.to || fileDiff.from;
15696
+ if (!targetFile || targetFile === "/dev/null") {
15697
+ return;
15698
+ }
15699
+ const { absoluteFilePath, relativePath } = this.resolveFilePaths({
15700
+ targetFile,
15701
+ repositoryPath,
15702
+ scanContext
15703
+ });
15704
+ if (fileDiff.from === "/dev/null") {
15705
+ this.handleFileCreation({
15706
+ fileDiff,
15707
+ absoluteFilePath,
15708
+ relativePath,
15709
+ repositoryPath,
15710
+ fix,
15711
+ appliedFiles,
15712
+ scanContext
15713
+ });
15714
+ } else if (fileDiff.to === "/dev/null") {
15715
+ this.handleFileDeletion({
15716
+ absoluteFilePath,
15717
+ relativePath,
15718
+ appliedFiles,
15719
+ scanContext
15720
+ });
15721
+ } else {
15722
+ this.handleFileModification({
15723
+ fileDiff,
15724
+ absoluteFilePath,
15725
+ relativePath,
15726
+ targetFile,
15727
+ repositoryPath,
15728
+ fix,
15729
+ appliedFiles,
15730
+ scanContext
15731
+ });
15732
+ }
15733
+ }
15734
+ /**
15735
+ * Applies a single patch using git apply
15736
+ */
15737
+ static async applySingleFix({
15738
+ fix,
15739
+ repositoryPath,
15740
+ scanContext
15741
+ }) {
15742
+ const validation = this.validateFixData(fix, scanContext);
15743
+ if (!validation.success) {
15744
+ return { success: false, error: validation.error };
15745
+ }
15746
+ const patch = validation.patch;
15747
+ let targetFiles = [];
15748
+ try {
15749
+ targetFiles = this.extractTargetFilesFromPatch(patch);
15750
+ } catch (extractError) {
15751
+ logError(
15752
+ `[${scanContext}] Failed to extract target files from patch for fix ${fix.id}`,
15753
+ {
15754
+ error: extractError instanceof Error ? extractError.message : String(extractError)
15755
+ }
15756
+ );
15757
+ }
15758
+ this.logPatchInfo(fix, patch, targetFiles, scanContext);
15759
+ try {
15760
+ logDebug(`[${scanContext}] Applying patch in memory for fix ${fix.id}`, {
15761
+ repositoryPath,
15762
+ targetFiles
15763
+ });
15764
+ const parsedPatch = this.parsePatchSafely({
15765
+ patch,
15766
+ fixId: fix.id,
15767
+ scanContext
15768
+ });
15769
+ const appliedFiles = [];
15770
+ for (const fileDiff of parsedPatch) {
15771
+ this.processFileDiff({
15772
+ fileDiff,
15773
+ repositoryPath,
15774
+ fix,
15775
+ appliedFiles,
15776
+ scanContext
15777
+ });
15778
+ }
15779
+ logInfo(
15780
+ `[${scanContext}] In-memory patch application successful for fix ${fix.id}`,
15781
+ {
15782
+ appliedFiles,
15783
+ filesCount: appliedFiles.length
15784
+ }
15785
+ );
15786
+ return { success: true };
15787
+ } catch (patchError) {
15788
+ const errorMessage = patchError instanceof Error ? patchError.message : String(patchError);
15789
+ logError(
15790
+ `[${scanContext}] In-memory patch application failed for fix ${fix.id}`,
15791
+ {
15792
+ errorMessage,
15793
+ patchErrorType: patchError instanceof Error ? patchError.name : typeof patchError,
15794
+ targetFiles
15795
+ }
15796
+ );
15797
+ return {
15798
+ success: false,
15799
+ error: `Patch application failed: ${errorMessage}`
15800
+ };
15801
+ }
15802
+ }
15803
+ /**
15804
+ * Extracts the file path from a git patch
15805
+ */
15806
+ static extractPathFromPatch(patch) {
15807
+ if (!patch) return null;
15808
+ const match = patch.match(/^diff --git a\/([^\s]+) b\//);
15809
+ return match?.[1] ?? null;
15810
+ }
15811
+ /**
15812
+ * Extracts target file paths from a git patch
15813
+ */
15814
+ static extractTargetFilesFromPatch(patch) {
15815
+ const targetFiles = [];
15816
+ const lines = patch.split("\n");
15817
+ for (const line of lines) {
15818
+ if (line.startsWith("+++ b/")) {
15819
+ const filePath = line.substring(6);
15820
+ if (filePath && !targetFiles.includes(filePath)) {
15821
+ targetFiles.push(filePath);
15822
+ }
15823
+ } else if (line.startsWith("diff --git a/") && line.includes(" b/")) {
15824
+ const match = line.match(/diff --git a\/.+ b\/(.+)/);
15825
+ if (match?.[1] && !targetFiles.includes(match[1])) {
15826
+ targetFiles.push(match[1]);
15827
+ }
15828
+ }
15829
+ }
15830
+ return targetFiles;
15831
+ }
15832
+ /**
15833
+ * Apply hunks to an empty file (for file creation)
15834
+ */
15835
+ static applyHunksToEmptyFile(chunks) {
15836
+ const lines = [];
15837
+ for (const chunk of chunks) {
15838
+ for (const change of chunk.changes) {
15839
+ if (change.type === "add") {
15840
+ lines.push(change.content.substring(1));
15841
+ }
15842
+ }
15843
+ }
15844
+ return lines.join("\n");
15845
+ }
15846
+ /**
15847
+ * Apply hunks to an existing file content
15848
+ */
15849
+ static applyHunksToFile(originalContent, chunks) {
15850
+ const originalLines = originalContent.split("\n");
15851
+ const result = [...originalLines];
15852
+ const sortedChunks = chunks.sort((a, b) => b.oldStart - a.oldStart);
15853
+ for (const chunk of sortedChunks) {
15854
+ const { oldStart, oldLines } = chunk;
15855
+ const startIndex = oldStart - 1;
15856
+ const newLines = [];
15857
+ for (const change of chunk.changes) {
15858
+ if (change.type === "add") {
15859
+ newLines.push(change.content.substring(1));
15860
+ } else if (change.type === "normal") {
15861
+ newLines.push(change.content.substring(1));
15862
+ }
15863
+ }
15864
+ result.splice(startIndex, oldLines, ...newLines);
15865
+ }
15866
+ return result.join("\n");
15867
+ }
15868
+ };
15869
+
15870
+ // src/mcp/services/ScanFiles.ts
15871
+ init_GitService();
15872
+ init_configs();
15873
+
15874
+ // src/mcp/services/FileOperations.ts
15875
+ init_FileUtils();
15876
+ import fs14 from "fs";
15877
+ import path14 from "path";
15878
+ import AdmZip2 from "adm-zip";
15879
+ var FileOperations = class {
15880
+ /**
15881
+ * Creates a ZIP archive containing the specified source files
15882
+ * @param fileList Array of relative file paths to include
15883
+ * @param repositoryPath Base path for resolving relative file paths
15884
+ * @param maxFileSize Maximum size allowed for individual files
15885
+ * @returns ZIP archive as a Buffer with metadata
15886
+ */
15887
+ async createSourceCodeArchive({
15888
+ fileList,
15889
+ repositoryPath,
15890
+ maxFileSize
15891
+ }) {
15892
+ logDebug("[FileOperations] Packing files");
15893
+ const zip = new AdmZip2();
15894
+ let packedFilesCount = 0;
15895
+ const packedFiles = [];
15896
+ const excludedFiles = [];
15897
+ const resolvedRepoPath = path14.resolve(repositoryPath);
15898
+ for (const filepath of fileList) {
15899
+ const absoluteFilepath = path14.join(repositoryPath, filepath);
15900
+ const resolvedFilePath = path14.resolve(absoluteFilepath);
15901
+ if (!resolvedFilePath.startsWith(resolvedRepoPath)) {
15902
+ const reason = "potential path traversal security risk";
15903
+ logDebug(`[FileOperations] Skipping ${filepath} due to ${reason}`);
15904
+ excludedFiles.push({ file: filepath, reason });
15905
+ continue;
15906
+ }
15907
+ if (!FileUtils.shouldPackFile(absoluteFilepath, maxFileSize)) {
15908
+ const reason = "file is too large, binary, or matches exclusion rules";
15909
+ logDebug(`[FileOperations] Excluding ${filepath} - ${reason}`);
15910
+ excludedFiles.push({ file: filepath, reason });
15911
+ continue;
15912
+ }
15913
+ const fileContent = await this.readSourceFile({
15914
+ absoluteFilepath,
15915
+ relativeFilepath: filepath
15916
+ });
15917
+ if (fileContent) {
15918
+ zip.addFile(filepath, fileContent);
15919
+ packedFilesCount++;
15920
+ packedFiles.push(filepath);
15921
+ } else {
15922
+ const reason = "failed to read file content";
15923
+ excludedFiles.push({ file: filepath, reason });
13678
15924
  }
13679
15925
  }
13680
15926
  const archiveBuffer = zip.toBuffer();
@@ -13694,12 +15940,15 @@ var FileOperations = class {
13694
15940
  * @param repositoryPath Base path for validation
13695
15941
  * @returns Array of validated file paths
13696
15942
  */
13697
- async validateFilePaths(fileList, repositoryPath) {
13698
- const resolvedRepoPath = path12.resolve(repositoryPath);
15943
+ async validateFilePaths({
15944
+ fileList,
15945
+ repositoryPath
15946
+ }) {
15947
+ const resolvedRepoPath = path14.resolve(repositoryPath);
13699
15948
  const validatedPaths = [];
13700
15949
  for (const filepath of fileList) {
13701
- const absoluteFilepath = path12.join(repositoryPath, filepath);
13702
- const resolvedFilePath = path12.resolve(absoluteFilepath);
15950
+ const absoluteFilepath = path14.join(repositoryPath, filepath);
15951
+ const resolvedFilePath = path14.resolve(absoluteFilepath);
13703
15952
  if (!resolvedFilePath.startsWith(resolvedRepoPath)) {
13704
15953
  logDebug(
13705
15954
  `[FileOperations] Rejecting ${filepath} - path traversal attempt detected`
@@ -13707,7 +15956,7 @@ var FileOperations = class {
13707
15956
  continue;
13708
15957
  }
13709
15958
  try {
13710
- await fs12.promises.access(absoluteFilepath, fs12.constants.R_OK);
15959
+ await fs14.promises.access(absoluteFilepath, fs14.constants.R_OK);
13711
15960
  validatedPaths.push(filepath);
13712
15961
  } catch (error) {
13713
15962
  logDebug(
@@ -13726,8 +15975,8 @@ var FileOperations = class {
13726
15975
  const fileDataArray = [];
13727
15976
  for (const absolutePath of filePaths) {
13728
15977
  try {
13729
- const content = await fs12.promises.readFile(absolutePath);
13730
- const relativePath = path12.basename(absolutePath);
15978
+ const content = await fs14.promises.readFile(absolutePath);
15979
+ const relativePath = path14.basename(absolutePath);
13731
15980
  fileDataArray.push({
13732
15981
  relativePath,
13733
15982
  absolutePath,
@@ -13747,9 +15996,12 @@ var FileOperations = class {
13747
15996
  * @param relativeFilepath Relative path for logging purposes
13748
15997
  * @returns File content as Buffer or null if failed
13749
15998
  */
13750
- async readSourceFile(absoluteFilepath, relativeFilepath) {
15999
+ async readSourceFile({
16000
+ absoluteFilepath,
16001
+ relativeFilepath
16002
+ }) {
13751
16003
  try {
13752
- return await fs12.promises.readFile(absoluteFilepath);
16004
+ return await fs14.promises.readFile(absoluteFilepath);
13753
16005
  } catch (fsError) {
13754
16006
  logError(
13755
16007
  `[FileOperations] Failed to read ${relativeFilepath} from filesystem: ${fsError}`
@@ -13770,19 +16022,19 @@ var scanFiles = async ({
13770
16022
  const repoUploadInfo = await initializeSecurityReport(gqlClient, scanContext);
13771
16023
  const fixReportId = repoUploadInfo.fixReportId;
13772
16024
  const fileOperations = new FileOperations();
13773
- const packingResult = await fileOperations.createSourceCodeArchive(
16025
+ const packingResult = await fileOperations.createSourceCodeArchive({
13774
16026
  fileList,
13775
16027
  repositoryPath,
13776
- MCP_MAX_FILE_SIZE
13777
- );
16028
+ maxFileSize: MCP_MAX_FILE_SIZE
16029
+ });
13778
16030
  logDebug(
13779
16031
  `[${scanContext}] Files ${packingResult.packedFilesCount} packed successfully, ${packingResult.totalSize} bytes`
13780
16032
  );
13781
- await uploadSourceCodeArchive(
13782
- packingResult.archive,
16033
+ await uploadSourceCodeArchive({
16034
+ archiveBuffer: packingResult.archive,
13783
16035
  repoUploadInfo,
13784
16036
  scanContext
13785
- );
16037
+ });
13786
16038
  const projectId = await getProjectId(gqlClient, scanContext);
13787
16039
  const gitService = new GitService(repositoryPath);
13788
16040
  const { branch } = await gitService.getCurrentCommitAndBranch();
@@ -13832,7 +16084,11 @@ var initializeSecurityReport = async (gqlClient, scanContext) => {
13832
16084
  );
13833
16085
  }
13834
16086
  };
13835
- var uploadSourceCodeArchive = async (archiveBuffer, repoUploadInfo, scanContext) => {
16087
+ var uploadSourceCodeArchive = async ({
16088
+ archiveBuffer,
16089
+ repoUploadInfo,
16090
+ scanContext
16091
+ }) => {
13836
16092
  if (!repoUploadInfo) {
13837
16093
  throw new FileUploadError("Upload info is required for source code archive");
13838
16094
  }
@@ -13875,6 +16131,7 @@ var executeSecurityScan = async ({
13875
16131
  if (!gqlClient) {
13876
16132
  throw new GqlClientError();
13877
16133
  }
16134
+ const scanStartTime = Date.now();
13878
16135
  logInfo(`[${scanContext}] Starting scan`);
13879
16136
  const submitVulnerabilityReportVariables = {
13880
16137
  fixReportId,
@@ -13973,14 +16230,17 @@ var executeSecurityScan = async ({
13973
16230
  `Security analysis failed: ${error.message || "Unknown error"}`
13974
16231
  );
13975
16232
  }
16233
+ const scanDuration = Date.now() - scanStartTime;
13976
16234
  logDebug(`[${scanContext}] Security scan completed successfully`, {
13977
16235
  fixReportId,
13978
- projectId
16236
+ projectId,
16237
+ filesScanned: fileCount,
16238
+ scanDurationMs: scanDuration
13979
16239
  });
13980
16240
  };
13981
16241
 
13982
16242
  // src/mcp/tools/checkForNewAvailableFixes/CheckForNewAvailableFixesService.ts
13983
- function extractPathFromPatch(patch) {
16243
+ function extractPathFromPatch2(patch) {
13984
16244
  const match = patch?.match(/^diff --git a\/([^\s]+) b\//);
13985
16245
  return match?.[1] ?? null;
13986
16246
  }
@@ -13997,6 +16257,7 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
13997
16257
  __publicField(this, "reportedFixes", []);
13998
16258
  __publicField(this, "intervalId", null);
13999
16259
  __publicField(this, "isInitialScanComplete", false);
16260
+ __publicField(this, "hasAuthenticationFailed", false);
14000
16261
  __publicField(this, "gqlClient", null);
14001
16262
  __publicField(this, "fullScanPathsScanned", []);
14002
16263
  this.fullScanPathsScanned = configStore.get("fullScanPathsScanned") || [];
@@ -14015,6 +16276,7 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
14015
16276
  this.filesLastScanned = {};
14016
16277
  this.freshFixes = [];
14017
16278
  this.reportedFixes = [];
16279
+ this.hasAuthenticationFailed = false;
14018
16280
  this.fullScanPathsScanned = configStore.get("fullScanPathsScanned") || [];
14019
16281
  if (this.intervalId) {
14020
16282
  clearInterval(this.intervalId);
@@ -14026,33 +16288,40 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
14026
16288
  * since the last scan.
14027
16289
  */
14028
16290
  async scanForSecurityVulnerabilities({
14029
- path: path13,
16291
+ path: path15,
14030
16292
  isAllDetectionRulesScan,
14031
16293
  isAllFilesScan,
14032
16294
  scanContext
14033
16295
  }) {
16296
+ this.hasAuthenticationFailed = false;
14034
16297
  logDebug(`[${scanContext}] Scanning for new security vulnerabilities`, {
14035
- path: path13
16298
+ path: path15
14036
16299
  });
14037
16300
  if (!this.gqlClient) {
14038
16301
  logInfo(`[${scanContext}] No GQL client found, skipping scan`);
16302
+ this.hasAuthenticationFailed = true;
14039
16303
  throw new Error("No GQL client found");
14040
16304
  }
14041
16305
  try {
14042
16306
  const isConnected = await this.gqlClient.verifyApiConnection();
14043
16307
  if (!isConnected) {
14044
16308
  logError(`[${scanContext}] Failed to connect to the API, scan aborted`);
16309
+ this.hasAuthenticationFailed = true;
14045
16310
  throw new ApiConnectionError();
14046
16311
  }
14047
16312
  logDebug(
14048
16313
  `[${scanContext}] Connected to the API, assembling list of files to scan`,
14049
- { path: path13 }
16314
+ { path: path15 }
14050
16315
  );
14051
16316
  const files = await getLocalFiles({
14052
- path: path13,
16317
+ path: path15,
14053
16318
  isAllFilesScan,
14054
16319
  scanContext
14055
16320
  });
16321
+ const scanStartTime = Date.now();
16322
+ logDebug(
16323
+ `[${scanContext}] setting scan start time to ${new Date(scanStartTime).toISOString()}`
16324
+ );
14056
16325
  logDebug(`[${scanContext}] Active files`, { files });
14057
16326
  const filesToScan = files.filter((file) => {
14058
16327
  const lastScannedEditTime = this.filesLastScanned[file.fullPath];
@@ -14070,13 +16339,13 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
14070
16339
  });
14071
16340
  const { fixReportId, projectId } = await scanFiles({
14072
16341
  fileList: filesToScan.map((file) => file.relativePath),
14073
- repositoryPath: path13,
16342
+ repositoryPath: path15,
14074
16343
  gqlClient: this.gqlClient,
14075
16344
  isAllDetectionRulesScan,
14076
16345
  scanContext
14077
16346
  });
14078
16347
  logInfo(
14079
- `[${scanContext}] Security scan completed for ${path13} reportId: ${fixReportId} projectId: ${projectId}`
16348
+ `[${scanContext}] Security scan completed for ${path15} reportId: ${fixReportId} projectId: ${projectId}`
14080
16349
  );
14081
16350
  if (isAllFilesScan) {
14082
16351
  return;
@@ -14092,7 +16361,43 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
14092
16361
  logInfo(
14093
16362
  `[${scanContext}] Security fixes retrieved, total: ${fixes?.fixes?.length || 0}, new: ${newFixes?.length || 0}`
14094
16363
  );
14095
- this.updateFreshFixesCache(newFixes || [], filesToScan, scanContext);
16364
+ if (newFixes && newFixes.length > 0) {
16365
+ try {
16366
+ const isMvsAutoFixEnabled = await this.gqlClient.getMvsAutoFixSettings();
16367
+ logDebug(
16368
+ `[${scanContext}] mvs_auto_fix setting: ${isMvsAutoFixEnabled}`
16369
+ );
16370
+ if (isMvsAutoFixEnabled) {
16371
+ const mvsAutoFixFilteredFixes = this.filterFixesForMvsAutoFix(
16372
+ newFixes,
16373
+ scanContext
16374
+ );
16375
+ await this.applyFixes({
16376
+ newFixes: mvsAutoFixFilteredFixes,
16377
+ scanContext,
16378
+ scanStartTime
16379
+ });
16380
+ } else {
16381
+ this.updateAvailableFixes({
16382
+ newFixes,
16383
+ scannedFiles: filesToScan,
16384
+ scanContext
16385
+ });
16386
+ }
16387
+ } catch (error) {
16388
+ logError(
16389
+ `[${scanContext}] Failed to check mvs_auto_fix setting, falling back to cache`,
16390
+ {
16391
+ error: error instanceof Error ? error.message : String(error)
16392
+ }
16393
+ );
16394
+ this.updateAvailableFixes({
16395
+ newFixes,
16396
+ scannedFiles: filesToScan,
16397
+ scanContext
16398
+ });
16399
+ }
16400
+ }
14096
16401
  this.updateFilesScanTimestamps(filesToScan);
14097
16402
  this.isInitialScanComplete = true;
14098
16403
  } catch (error) {
@@ -14104,6 +16409,7 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
14104
16409
  error: errorMessage
14105
16410
  }
14106
16411
  );
16412
+ this.hasAuthenticationFailed = true;
14107
16413
  return;
14108
16414
  }
14109
16415
  if (errorMessage.includes("ReportInitializationError")) {
@@ -14122,8 +16428,134 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
14122
16428
  throw error;
14123
16429
  }
14124
16430
  }
14125
- updateFreshFixesCache(newFixes, filesToScan, scanContext) {
14126
- this.freshFixes = this.freshFixes.filter((fix) => !this.isFixFromOldScan(fix, filesToScan, scanContext)).concat(newFixes).sort((a, b) => {
16431
+ /**
16432
+ * Applies fixes directly to the repository when mvs_auto_fix is enabled
16433
+ */
16434
+ async applyFixes({
16435
+ newFixes,
16436
+ scanContext,
16437
+ scanStartTime
16438
+ }) {
16439
+ if (newFixes.length === 0) {
16440
+ logInfo(`[${scanContext}] No new fixes to apply (all already reported)`);
16441
+ return;
16442
+ }
16443
+ logInfo(`[${scanContext}] Auto-applying ${newFixes.length} new fixes`);
16444
+ const repositoryPath = this.path;
16445
+ logDebug(`[${scanContext}] Repository path for patch application`, {
16446
+ repositoryPath
16447
+ });
16448
+ const applicationResult = await PatchApplicationService.applyFixes({
16449
+ fixes: newFixes,
16450
+ repositoryPath,
16451
+ scanStartTime,
16452
+ gqlClient: this.gqlClient || void 0,
16453
+ scanContext
16454
+ });
16455
+ if (applicationResult.appliedFixes.length > 0) {
16456
+ logInfo(
16457
+ `[${scanContext}] Successfully auto-applied ${applicationResult.appliedFixes.length} fixes`
16458
+ );
16459
+ this.reportedFixes.push(...applicationResult.appliedFixes);
16460
+ await this.saveAndLogAppliedFixes({
16461
+ appliedFixes: applicationResult.appliedFixes,
16462
+ repositoryPath,
16463
+ scanContext
16464
+ });
16465
+ }
16466
+ if (applicationResult.failedFixes.length > 0) {
16467
+ logError(
16468
+ `[${scanContext}] Failed to auto-apply ${applicationResult.failedFixes.length} fixes`,
16469
+ {
16470
+ failedFixes: applicationResult.failedFixes.map((f) => ({
16471
+ fixId: f.fix.id,
16472
+ error: f.error
16473
+ }))
16474
+ }
16475
+ );
16476
+ }
16477
+ }
16478
+ /**
16479
+ * Saves patch files and logs fix details for successfully applied fixes
16480
+ */
16481
+ async saveAndLogAppliedFixes({
16482
+ appliedFixes,
16483
+ repositoryPath,
16484
+ scanContext
16485
+ }) {
16486
+ try {
16487
+ const localMobbService = new LocalMobbFolderService(repositoryPath);
16488
+ logDebug(
16489
+ `[${scanContext}] Saving and logging ${appliedFixes.length} applied fixes`,
16490
+ {
16491
+ repositoryPath,
16492
+ fixIds: appliedFixes.map((f) => f.id)
16493
+ }
16494
+ );
16495
+ for (const fix of appliedFixes) {
16496
+ try {
16497
+ const saveResult = await localMobbService.savePatch(fix);
16498
+ if (saveResult.success) {
16499
+ logDebug(
16500
+ `[${scanContext}] Patch saved successfully for fix ${fix.id}`,
16501
+ {
16502
+ filePath: saveResult.filePath,
16503
+ fileName: saveResult.fileName
16504
+ }
16505
+ );
16506
+ } else {
16507
+ logDebug(
16508
+ `[${scanContext}] Failed to save patch for fix ${fix.id}`,
16509
+ {
16510
+ error: saveResult.error
16511
+ }
16512
+ );
16513
+ }
16514
+ const logResult = await localMobbService.logPatch(
16515
+ fix,
16516
+ saveResult.fileName
16517
+ );
16518
+ if (logResult.success) {
16519
+ logDebug(
16520
+ `[${scanContext}] Fix details logged successfully for fix ${fix.id}`,
16521
+ {
16522
+ filePath: logResult.filePath
16523
+ }
16524
+ );
16525
+ } else {
16526
+ logError(
16527
+ `[${scanContext}] Failed to log fix details for fix ${fix.id}`,
16528
+ {
16529
+ error: logResult.error
16530
+ }
16531
+ );
16532
+ }
16533
+ } catch (error) {
16534
+ logError(`[${scanContext}] Error processing applied fix ${fix.id}`, {
16535
+ error: String(error),
16536
+ fix
16537
+ });
16538
+ }
16539
+ }
16540
+ logInfo(`[${scanContext}] Finished saving and logging applied fixes`, {
16541
+ processedCount: appliedFixes.length
16542
+ });
16543
+ } catch (error) {
16544
+ logError(`[${scanContext}] Error in saveAndLogAppliedFixes`, {
16545
+ error: String(error),
16546
+ repositoryPath,
16547
+ appliedFixCount: appliedFixes.length
16548
+ });
16549
+ }
16550
+ }
16551
+ updateAvailableFixes({
16552
+ newFixes,
16553
+ scannedFiles,
16554
+ scanContext
16555
+ }) {
16556
+ this.freshFixes = this.freshFixes.filter(
16557
+ (fix) => !this.isFixFromOldScan({ fix, scannedFiles, scanContext })
16558
+ ).concat(newFixes).sort((a, b) => {
14127
16559
  return (b.severityValue ?? 0) - (a.severityValue ?? 0);
14128
16560
  });
14129
16561
  logInfo(
@@ -14137,63 +16569,161 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
14137
16569
  }
14138
16570
  isFixAlreadyReported(fix) {
14139
16571
  return this.reportedFixes.some(
14140
- (reportedFix) => reportedFix.sharedState?.id === fix.sharedState?.id
16572
+ (reportedFix) => reportedFix.sharedState?.id === fix.sharedState?.id || reportedFix.id === fix.id
14141
16573
  );
14142
16574
  }
14143
- isFixFromOldScan(fix, filesToScan, scanContext) {
16575
+ /**
16576
+ * Filters fixes for MVS_AUTO_FIX flow to only include fixes with:
16577
+ * - category 'Fixable' (or no category field - backward compatibility)
16578
+ * - do NOT have downloadSource 'AUTO_MVS'
16579
+ */
16580
+ filterFixesForMvsAutoFix(fixes, scanContext) {
16581
+ const filteredFixes = fixes.filter((fix) => {
16582
+ const hasFixableCategory = fix.vulnerabilityReportIssues?.some(
16583
+ (issue) => !issue.category || // Handle missing field gracefully
16584
+ issue.category === "Fixable" /* Fixable */
16585
+ ) ?? true;
16586
+ const hasAutoMvsDownloadSource = fix.sharedState?.downloadedBy?.some(
16587
+ (d) => d.downloadSource === "AUTO_MVS" /* AutoMvs */
16588
+ ) ?? false;
16589
+ const doesNotHaveAutoMvsDownloadSource = !hasAutoMvsDownloadSource;
16590
+ const shouldInclude = hasFixableCategory && doesNotHaveAutoMvsDownloadSource;
16591
+ if (!shouldInclude) {
16592
+ const filteringReasons = [];
16593
+ if (!hasFixableCategory) {
16594
+ filteringReasons.push("not fixable category");
16595
+ }
16596
+ if (!doesNotHaveAutoMvsDownloadSource) {
16597
+ filteringReasons.push("has AUTO_MVS download source");
16598
+ }
16599
+ const reasonText = filteringReasons.join(" and ");
16600
+ logDebug(
16601
+ `[${scanContext}] Fix ${fix.id} filtered out - ${reasonText}`,
16602
+ {
16603
+ fixId: fix.id,
16604
+ hasFixableCategory,
16605
+ doesNotHaveAutoMvsDownloadSource,
16606
+ filteringReasons,
16607
+ categories: fix.vulnerabilityReportIssues?.map(
16608
+ (issue) => issue.category
16609
+ ),
16610
+ downloadSources: fix.sharedState?.downloadedBy?.map(
16611
+ (d) => d.downloadSource
16612
+ )
16613
+ }
16614
+ );
16615
+ }
16616
+ return shouldInclude;
16617
+ });
16618
+ logInfo(`[${scanContext}] MVS_AUTO_FIX filtering completed`, {
16619
+ originalCount: fixes.length,
16620
+ filteredCount: filteredFixes.length,
16621
+ removedCount: fixes.length - filteredFixes.length
16622
+ });
16623
+ return filteredFixes;
16624
+ }
16625
+ isFixFromOldScan({
16626
+ fix,
16627
+ scannedFiles,
16628
+ scanContext
16629
+ }) {
14144
16630
  const patch = fix.patchAndQuestions?.__typename === "FixData" ? fix.patchAndQuestions.patch : void 0;
14145
- const fixFile = extractPathFromPatch(patch);
16631
+ const fixFile = extractPathFromPatch2(patch);
14146
16632
  if (!fixFile) {
14147
16633
  return false;
14148
16634
  }
14149
16635
  logDebug(`[${scanContext}] Checking if fix is from old scan`, {
14150
16636
  fixFile,
14151
- filesToScan,
14152
- isFromOldScan: filesToScan.some((file) => file.relativePath === fixFile)
16637
+ scannedFiles,
16638
+ isFromOldScan: scannedFiles.some((file) => file.relativePath === fixFile)
14153
16639
  });
14154
- return filesToScan.some((file) => file.relativePath === fixFile);
16640
+ return scannedFiles.some((file) => file.relativePath === fixFile);
14155
16641
  }
14156
- async getFreshFixes({ path: path13 }) {
16642
+ async getFreshFixes({ path: path15 }) {
14157
16643
  const scanContext = ScanContext.USER_REQUEST;
14158
- logDebug(`[${scanContext}] Getting fresh fixes`, { path: path13 });
14159
- if (this.path !== path13) {
14160
- this.path = path13;
16644
+ logDebug(`[${scanContext}] Getting fresh fixes`, { path: path15 });
16645
+ if (this.path !== path15) {
16646
+ this.path = path15;
14161
16647
  this.reset();
14162
- logInfo(`[${scanContext}] Reset service state for new path`, { path: path13 });
16648
+ logInfo(`[${scanContext}] Reset service state for new path`, { path: path15 });
16649
+ }
16650
+ try {
16651
+ this.gqlClient = await createAuthenticatedMcpGQLClient();
16652
+ } catch (error) {
16653
+ const errorMessage = error.message;
16654
+ if (errorMessage.includes("Authentication failed") || errorMessage.includes("access-denied") || errorMessage.includes("Authentication hook unauthorized")) {
16655
+ logError(
16656
+ `[${scanContext}] Failed to create authenticated client due to authentication failure`,
16657
+ {
16658
+ error: errorMessage
16659
+ }
16660
+ );
16661
+ this.hasAuthenticationFailed = true;
16662
+ return authenticationRequiredPrompt;
16663
+ }
16664
+ throw error;
16665
+ }
16666
+ this.triggerScan({ path: path15, gqlClient: this.gqlClient });
16667
+ let isMvsAutoFixEnabled = null;
16668
+ try {
16669
+ isMvsAutoFixEnabled = await this.gqlClient.getMvsAutoFixSettings();
16670
+ logDebug(`[${scanContext}] mvs_auto_fix setting: ${isMvsAutoFixEnabled}`);
16671
+ if (isMvsAutoFixEnabled) {
16672
+ if (this.reportedFixes.length > 0) {
16673
+ return this.generateAppliedFixesResponse(scanContext);
16674
+ }
16675
+ } else {
16676
+ if (this.freshFixes.length > 0) {
16677
+ return this.generateFreshFixesResponse(scanContext);
16678
+ }
16679
+ }
16680
+ } catch (error) {
16681
+ logError(`[${scanContext}] Failed to check mvs_auto_fix setting`, {
16682
+ error: error instanceof Error ? error.message : String(error)
16683
+ });
16684
+ if (this.freshFixes.length > 0) {
16685
+ return this.generateFreshFixesResponse(scanContext);
16686
+ }
14163
16687
  }
14164
- this.gqlClient = await createAuthenticatedMcpGQLClient();
14165
- this.triggerScan({ path: path13, gqlClient: this.gqlClient });
14166
- if (this.freshFixes.length > 0) {
14167
- return this.generateFreshFixesResponse(scanContext);
16688
+ if (this.hasAuthenticationFailed) {
16689
+ return authenticationRequiredPrompt;
14168
16690
  }
14169
16691
  if (!this.isInitialScanComplete) {
14170
16692
  return initialScanInProgressPrompt;
14171
16693
  }
16694
+ if (isMvsAutoFixEnabled === true) {
16695
+ return noVulnerabilitiesAutoFixPrompt;
16696
+ }
14172
16697
  return noFreshFixesPrompt;
14173
16698
  }
14174
16699
  triggerScan({
14175
- path: path13,
16700
+ path: path15,
14176
16701
  gqlClient
14177
16702
  }) {
16703
+ if (this.path !== path15) {
16704
+ this.path = path15;
16705
+ this.reset();
16706
+ logInfo(`Reset service state for new path in triggerScan`, { path: path15 });
16707
+ }
14178
16708
  this.gqlClient = gqlClient;
14179
16709
  if (!this.intervalId) {
14180
- this.startPeriodicScanning(path13);
14181
- this.executeInitialScan(path13);
14182
- void this.executeInitialFullScan(path13);
16710
+ this.startPeriodicScanning(path15);
16711
+ this.executeInitialScan(path15);
16712
+ void this.executeInitialFullScan(path15);
14183
16713
  }
14184
16714
  }
14185
- startPeriodicScanning(path13) {
16715
+ startPeriodicScanning(path15) {
14186
16716
  const scanContext = ScanContext.BACKGROUND_PERIODIC;
14187
16717
  logDebug(
14188
16718
  `[${scanContext}] Starting periodic scan for new security vulnerabilities`,
14189
16719
  {
14190
- path: path13
16720
+ path: path15
14191
16721
  }
14192
16722
  );
14193
16723
  this.intervalId = setInterval(() => {
14194
- logDebug(`[${scanContext}] Triggering periodic security scan`, { path: path13 });
16724
+ logDebug(`[${scanContext}] Triggering periodic security scan`, { path: path15 });
14195
16725
  this.scanForSecurityVulnerabilities({
14196
- path: path13,
16726
+ path: path15,
14197
16727
  scanContext
14198
16728
  }).catch((error) => {
14199
16729
  logError(`[${scanContext}] Error during periodic security scan`, {
@@ -14202,45 +16732,45 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
14202
16732
  });
14203
16733
  }, MCP_PERIODIC_CHECK_INTERVAL);
14204
16734
  }
14205
- async executeInitialFullScan(path13) {
16735
+ async executeInitialFullScan(path15) {
14206
16736
  const scanContext = ScanContext.FULL_SCAN;
14207
- logDebug(`[${scanContext}] Triggering initial full security scan`, { path: path13 });
16737
+ logDebug(`[${scanContext}] Triggering initial full security scan`, { path: path15 });
14208
16738
  logDebug(`[${scanContext}] Full scan paths scanned`, {
14209
16739
  fullScanPathsScanned: this.fullScanPathsScanned
14210
16740
  });
14211
- if (this.fullScanPathsScanned.includes(path13)) {
16741
+ if (this.fullScanPathsScanned.includes(path15)) {
14212
16742
  logDebug(`[${scanContext}] Full scan already executed for this path`, {
14213
- path: path13
16743
+ path: path15
14214
16744
  });
14215
16745
  return;
14216
16746
  }
14217
16747
  configStore.set("fullScanPathsScanned", [
14218
16748
  ...this.fullScanPathsScanned,
14219
- path13
16749
+ path15
14220
16750
  ]);
14221
16751
  try {
14222
16752
  await this.scanForSecurityVulnerabilities({
14223
- path: path13,
16753
+ path: path15,
14224
16754
  isAllFilesScan: true,
14225
16755
  isAllDetectionRulesScan: true,
14226
16756
  scanContext: ScanContext.FULL_SCAN
14227
16757
  });
14228
- if (!this.fullScanPathsScanned.includes(path13)) {
14229
- this.fullScanPathsScanned.push(path13);
16758
+ if (!this.fullScanPathsScanned.includes(path15)) {
16759
+ this.fullScanPathsScanned.push(path15);
14230
16760
  configStore.set("fullScanPathsScanned", this.fullScanPathsScanned);
14231
16761
  }
14232
- logInfo(`[${scanContext}] Full scan completed`, { path: path13 });
16762
+ logInfo(`[${scanContext}] Full scan completed`, { path: path15 });
14233
16763
  } catch (error) {
14234
16764
  logError(`[${scanContext}] Error during initial full security scan`, {
14235
16765
  error
14236
16766
  });
14237
16767
  }
14238
16768
  }
14239
- executeInitialScan(path13) {
16769
+ executeInitialScan(path15) {
14240
16770
  const scanContext = ScanContext.BACKGROUND_INITIAL;
14241
- logDebug(`[${scanContext}] Triggering initial security scan`, { path: path13 });
16771
+ logDebug(`[${scanContext}] Triggering initial security scan`, { path: path15 });
14242
16772
  this.scanForSecurityVulnerabilities({
14243
- path: path13,
16773
+ path: path15,
14244
16774
  scanContext: ScanContext.BACKGROUND_INITIAL
14245
16775
  }).catch((error) => {
14246
16776
  logError(`[${scanContext}] Error during initial security scan`, { error });
@@ -14262,6 +16792,20 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
14262
16792
  logInfo(`[${scanContext}] No fresh fixes to report`);
14263
16793
  return noFreshFixesPrompt;
14264
16794
  }
16795
+ generateAppliedFixesResponse(scanContext = ScanContext.USER_REQUEST) {
16796
+ const appliedFixesToShow = this.reportedFixes;
16797
+ if (appliedFixesToShow.length > 0) {
16798
+ logInfo(
16799
+ `[${scanContext}] Reporting ${appliedFixesToShow.length} applied fixes to user`
16800
+ );
16801
+ return appliedFixesSummaryPrompt({
16802
+ fixes: appliedFixesToShow,
16803
+ gqlClient: this.gqlClient
16804
+ });
16805
+ }
16806
+ logInfo(`[${scanContext}] No applied fixes to report`);
16807
+ return noFreshFixesPrompt;
16808
+ }
14265
16809
  };
14266
16810
  __publicField(_CheckForNewAvailableFixesService, "instance");
14267
16811
  var CheckForNewAvailableFixesService = _CheckForNewAvailableFixesService;
@@ -14285,8 +16829,10 @@ How to invoke:
14285
16829
  \u2013 path (string): absolute path to the repository root.
14286
16830
 
14287
16831
  Behaviour:
14288
- \u2022 If no new fixes are available, it returns a concise message indicating so.
14289
- \u2022 If fixes are found, it returns a human-readable summary including total count and severity breakdown.
16832
+ \u2022 If auto-fix is enabled: Returns a list of fixes that were automatically applied to your codebase.
16833
+ \u2022 If auto-fix is disabled: Returns a list of fresh fixes available for manual review and application.
16834
+ \u2022 If no fixes are available, it returns a concise message indicating so.
16835
+ \u2022 Results include a human-readable summary with total count and severity breakdown.
14290
16836
 
14291
16837
  Example payload:
14292
16838
  {
@@ -14302,8 +16848,8 @@ Example payload:
14302
16848
  },
14303
16849
  required: ["path"]
14304
16850
  });
14305
- __publicField(this, "inputValidationSchema", z32.object({
14306
- path: z32.string().describe(
16851
+ __publicField(this, "inputValidationSchema", z34.object({
16852
+ path: z34.string().describe(
14307
16853
  "Full local path to the cloned git repository to check for new available fixes"
14308
16854
  )
14309
16855
  }));
@@ -14320,9 +16866,9 @@ Example payload:
14320
16866
  `Invalid path: potential security risk detected in path: ${pathValidationResult.error}`
14321
16867
  );
14322
16868
  }
14323
- const path13 = pathValidationResult.path;
16869
+ const path15 = pathValidationResult.path;
14324
16870
  const resultText = await this.newFixesService.getFreshFixes({
14325
- path: path13
16871
+ path: path15
14326
16872
  });
14327
16873
  logInfo("CheckForNewAvailableFixesTool execution completed", {
14328
16874
  resultText
@@ -14333,7 +16879,7 @@ Example payload:
14333
16879
 
14334
16880
  // src/mcp/tools/fetchAvailableFixes/FetchAvailableFixesTool.ts
14335
16881
  init_GitService();
14336
- import { z as z33 } from "zod";
16882
+ import { z as z35 } from "zod";
14337
16883
 
14338
16884
  // src/mcp/tools/fetchAvailableFixes/FetchAvailableFixesService.ts
14339
16885
  init_configs();
@@ -14452,12 +16998,12 @@ Call this tool instead of ${MCP_TOOL_SCAN_AND_FIX_VULNERABILITIES} when you only
14452
16998
  },
14453
16999
  required: ["path"]
14454
17000
  });
14455
- __publicField(this, "inputValidationSchema", z33.object({
14456
- path: z33.string().describe(
17001
+ __publicField(this, "inputValidationSchema", z35.object({
17002
+ path: z35.string().describe(
14457
17003
  "Full local path to the cloned git repository to check for available fixes"
14458
17004
  ),
14459
- offset: z33.number().optional().describe("Optional offset for pagination"),
14460
- limit: z33.number().optional().describe("Optional maximum number of fixes to return")
17005
+ offset: z35.number().optional().describe("Optional offset for pagination"),
17006
+ limit: z35.number().optional().describe("Optional maximum number of fixes to return")
14461
17007
  }));
14462
17008
  __publicField(this, "availableFixesService");
14463
17009
  this.availableFixesService = FetchAvailableFixesService.getInstance();
@@ -14470,8 +17016,8 @@ Call this tool instead of ${MCP_TOOL_SCAN_AND_FIX_VULNERABILITIES} when you only
14470
17016
  `Invalid path: potential security risk detected in path: ${pathValidationResult.error}`
14471
17017
  );
14472
17018
  }
14473
- const path13 = pathValidationResult.path;
14474
- const gitService = new GitService(path13, log);
17019
+ const path15 = pathValidationResult.path;
17020
+ const gitService = new GitService(path15, log);
14475
17021
  const gitValidation = await gitService.validateRepository();
14476
17022
  if (!gitValidation.isValid) {
14477
17023
  throw new Error(`Invalid git repository: ${gitValidation.error}`);
@@ -14498,7 +17044,7 @@ Call this tool instead of ${MCP_TOOL_SCAN_AND_FIX_VULNERABILITIES} when you only
14498
17044
 
14499
17045
  // src/mcp/tools/scanAndFixVulnerabilities/ScanAndFixVulnerabilitiesTool.ts
14500
17046
  init_configs();
14501
- import z34 from "zod";
17047
+ import z36 from "zod";
14502
17048
 
14503
17049
  // src/mcp/tools/scanAndFixVulnerabilities/ScanAndFixVulnerabilitiesService.ts
14504
17050
  init_configs();
@@ -14573,11 +17119,11 @@ var _ScanAndFixVulnerabilitiesService = class _ScanAndFixVulnerabilitiesService
14573
17119
  const effectiveOffset = offset ?? (this.currentOffset || 0);
14574
17120
  const effectiveLimit = limit ?? MCP_DEFAULT_LIMIT;
14575
17121
  logDebug("effectiveOffset", { effectiveOffset });
14576
- const fixes = await this.getReportFixes(
17122
+ const fixes = await this.getReportFixes({
14577
17123
  fixReportId,
14578
- effectiveOffset,
14579
- effectiveLimit
14580
- );
17124
+ offset: effectiveOffset,
17125
+ limit: effectiveLimit
17126
+ });
14581
17127
  logInfo(`Found ${fixes.totalCount} fixes`);
14582
17128
  if (fixes.totalCount > 0) {
14583
17129
  this.storedFixReportId = fixReportId;
@@ -14617,7 +17163,11 @@ var _ScanAndFixVulnerabilitiesService = class _ScanAndFixVulnerabilitiesService
14617
17163
  this.gqlClient = gqlClient;
14618
17164
  return gqlClient;
14619
17165
  }
14620
- async getReportFixes(fixReportId, offset, limit) {
17166
+ async getReportFixes({
17167
+ fixReportId,
17168
+ offset,
17169
+ limit
17170
+ }) {
14621
17171
  logDebug("getReportFixes", { fixReportId, offset, limit });
14622
17172
  if (!this.gqlClient) {
14623
17173
  throw new GqlClientError();
@@ -14679,16 +17229,16 @@ Example payload:
14679
17229
  "maxFiles": 50,
14680
17230
  "rescan": false
14681
17231
  }`);
14682
- __publicField(this, "inputValidationSchema", z34.object({
14683
- path: z34.string().describe(
17232
+ __publicField(this, "inputValidationSchema", z36.object({
17233
+ path: z36.string().describe(
14684
17234
  "Full local path to repository to scan and fix vulnerabilities"
14685
17235
  ),
14686
- offset: z34.number().optional().describe("Optional offset for pagination"),
14687
- limit: z34.number().optional().describe("Optional maximum number of results to return"),
14688
- maxFiles: z34.number().optional().describe(
17236
+ offset: z36.number().optional().describe("Optional offset for pagination"),
17237
+ limit: z36.number().optional().describe("Optional maximum number of results to return"),
17238
+ maxFiles: z36.number().optional().describe(
14689
17239
  `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.`
14690
17240
  ),
14691
- rescan: z34.boolean().optional().describe("Optional whether to rescan the repository")
17241
+ rescan: z36.boolean().optional().describe("Optional whether to rescan the repository")
14692
17242
  }));
14693
17243
  __publicField(this, "inputSchema", {
14694
17244
  type: "object",
@@ -14733,9 +17283,9 @@ Example payload:
14733
17283
  `Invalid path: potential security risk detected in path: ${pathValidationResult.error}`
14734
17284
  );
14735
17285
  }
14736
- const path13 = pathValidationResult.path;
17286
+ const path15 = pathValidationResult.path;
14737
17287
  const files = await getLocalFiles({
14738
- path: path13,
17288
+ path: path15,
14739
17289
  maxFileSize: MCP_MAX_FILE_SIZE,
14740
17290
  maxFiles: args.maxFiles,
14741
17291
  scanContext: ScanContext.USER_REQUEST
@@ -14754,7 +17304,7 @@ Example payload:
14754
17304
  try {
14755
17305
  const fixResult = await this.vulnerabilityFixService.processVulnerabilities({
14756
17306
  fileList: files.map((file) => file.relativePath),
14757
- repositoryPath: path13,
17307
+ repositoryPath: path15,
14758
17308
  offset: args.offset,
14759
17309
  limit: args.limit,
14760
17310
  isRescan: args.rescan || !!args.maxFiles
@@ -14835,7 +17385,7 @@ var mcpHandler = async (_args) => {
14835
17385
  };
14836
17386
 
14837
17387
  // src/args/commands/review.ts
14838
- import fs13 from "fs";
17388
+ import fs15 from "fs";
14839
17389
  import chalk9 from "chalk";
14840
17390
  function reviewBuilder(yargs2) {
14841
17391
  return yargs2.option("f", {
@@ -14872,7 +17422,7 @@ function reviewBuilder(yargs2) {
14872
17422
  ).help();
14873
17423
  }
14874
17424
  function validateReviewOptions(argv) {
14875
- if (!fs13.existsSync(argv.f)) {
17425
+ if (!fs15.existsSync(argv.f)) {
14876
17426
  throw new CliError(`
14877
17427
  Can't access ${chalk9.bold(argv.f)}`);
14878
17428
  }