mobbdev 1.0.125 → 1.0.127

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 (2) hide show
  1. package/dist/index.mjs +283 -218
  2. package/package.json +5 -4
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;
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;
36
36
  var init_configs = __esm({
37
37
  "src/mcp/core/configs.ts"() {
38
38
  "use strict";
@@ -48,6 +48,7 @@ var init_configs = __esm({
48
48
  MCP_REPORT_ID_EXPIRATION_MS = 2 * 60 * 60 * 1e3;
49
49
  MCP_TOOLS_BROWSER_COOLDOWN_MS = 24 * 60 * 60 * 1e3;
50
50
  MCP_DEFAULT_LIMIT = 3;
51
+ isAutoScan = process.env["AUTO_SCAN"] !== "false";
51
52
  }
52
53
  });
53
54
 
@@ -2259,14 +2260,14 @@ var GetReportFixesDocument = `
2259
2260
  var GetLatestReportByRepoUrlDocument = `
2260
2261
  query GetLatestReportByRepoUrl($repoUrl: String!, $filters: fix_bool_exp = {}, $limit: Int!, $offset: Int!, $currentUserEmail: String!) {
2261
2262
  fixReport(
2262
- where: {_and: [{repo: {originalUrl: {_eq: $repoUrl}}}, {state: {_eq: Finished}}]}
2263
+ where: {_and: [{repo: {originalUrl: {_eq: $repoUrl}}}, {state: {_eq: Finished}}, {vulnerabilityReport: {_or: [{vendor: {_is_null: true}}, {vendor: {_nin: [semgrep, opengrep]}}]}}]}
2263
2264
  order_by: {createdOn: desc}
2264
2265
  limit: 1
2265
2266
  ) {
2266
2267
  ...FixReportSummaryFields
2267
2268
  }
2268
2269
  expiredReport: fixReport(
2269
- where: {_and: [{repo: {originalUrl: {_eq: $repoUrl}}}, {state: {_eq: Expired}}]}
2270
+ where: {_and: [{repo: {originalUrl: {_eq: $repoUrl}}}, {state: {_eq: Expired}}, {_or: [{vulnerabilityReport: {vendor: {_is_null: true}}}, {vulnerabilityReport: {vendor: {_nin: [semgrep, opengrep]}}}]}]}
2270
2271
  order_by: {createdOn: desc}
2271
2272
  limit: 1
2272
2273
  ) {
@@ -11635,6 +11636,7 @@ async function analyze({
11635
11636
  mobbProjectName,
11636
11637
  organizationId,
11637
11638
  autoPr,
11639
+ createOnePr,
11638
11640
  commitDirectly,
11639
11641
  pullRequest
11640
11642
  }, { skipPrompts = false } = {}) {
@@ -11653,7 +11655,8 @@ async function analyze({
11653
11655
  command: "analyze",
11654
11656
  autoPr,
11655
11657
  commitDirectly,
11656
- pullRequest
11658
+ pullRequest,
11659
+ createOnePr
11657
11660
  },
11658
11661
  { skipPrompts }
11659
11662
  );
@@ -11953,104 +11956,34 @@ import {
11953
11956
  } from "@modelcontextprotocol/sdk/types.js";
11954
11957
 
11955
11958
  // src/mcp/Logger.ts
11956
- var loggerUrl = "http://localhost:4444/log";
11957
- var isTestEnvironment = process.env["VITEST"] || process.env["TEST"];
11958
- var CIRCUIT_BREAKER_TIME = 5e3;
11959
- var URL_CHECK_TIMEOUT = 200;
11960
- var MAX_QUEUE_SIZE = 100;
11959
+ import Configstore3 from "configstore";
11960
+ var MAX_LOGS_SIZE = 1e3;
11961
11961
  var Logger = class {
11962
11962
  constructor() {
11963
- __publicField(this, "queue", []);
11964
- __publicField(this, "isProcessing", false);
11965
- __publicField(this, "isCircuitBroken", false);
11966
- __publicField(this, "circuitBreakerTimer", null);
11963
+ __publicField(this, "mobbConfigStore");
11964
+ __publicField(this, "path");
11965
+ this.path = process.env["WORKSPACE_FOLDER_PATHS"] || "unknown";
11966
+ this.mobbConfigStore = new Configstore3("mobb-logs", {});
11967
+ this.mobbConfigStore.set("version", packageJson.version);
11967
11968
  }
11969
+ /**
11970
+ * Log a message to the console.
11971
+ * @param message - The message to log.
11972
+ * @param level - The level of the message.
11973
+ * @param data - The data to log.
11974
+ */
11968
11975
  log(message, level = "info", data) {
11969
- if (isTestEnvironment) return;
11970
- if (this.queue.length >= MAX_QUEUE_SIZE) {
11971
- this.queue.shift();
11972
- }
11973
- this.queue.push({ message, level, data });
11974
- if (!this.isProcessing && !this.isCircuitBroken) {
11975
- this.processQueue();
11976
- }
11977
- }
11978
- async isUrlReachable(url) {
11979
- try {
11980
- const controller = new AbortController();
11981
- const timeoutId = setTimeout(() => controller.abort(), URL_CHECK_TIMEOUT);
11982
- await fetch(url, {
11983
- method: "HEAD",
11984
- signal: controller.signal
11985
- });
11986
- clearTimeout(timeoutId);
11987
- return true;
11988
- } catch (error) {
11989
- return false;
11990
- }
11991
- }
11992
- async processQueue() {
11993
- if (this.queue.length === 0 || this.isCircuitBroken) {
11994
- this.isProcessing = false;
11995
- return;
11996
- }
11997
- this.isProcessing = true;
11998
- const logEntry = this.queue[0];
11999
- if (!logEntry) {
12000
- this.isProcessing = false;
12001
- return;
12002
- }
12003
- const isReachable = await this.isUrlReachable(loggerUrl);
12004
- if (!isReachable) {
12005
- this.triggerCircuitBreaker();
12006
- return;
12007
- }
12008
- await this.sendLogEntry(logEntry);
12009
- }
12010
- async sendLogEntry(logEntry) {
12011
11976
  const logMessage = {
12012
11977
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
12013
- level: logEntry.level,
12014
- message: logEntry.message,
12015
- data: logEntry.data,
12016
- version: packageJson.version
11978
+ level,
11979
+ message,
11980
+ data
12017
11981
  };
12018
- const controller = new AbortController();
12019
- const timeoutId = setTimeout(() => {
12020
- controller.abort();
12021
- }, 500);
12022
- try {
12023
- await fetch(loggerUrl, {
12024
- method: "POST",
12025
- headers: { "Content-Type": "application/json" },
12026
- body: JSON.stringify(logMessage),
12027
- redirect: "error",
12028
- // do not follow redirects
12029
- signal: controller.signal
12030
- });
12031
- this.queue.shift();
12032
- setTimeout(() => this.processQueue(), 0);
12033
- } catch (error) {
12034
- this.triggerCircuitBreaker();
12035
- logError("Failed to send log entry", error);
12036
- } finally {
12037
- clearTimeout(timeoutId);
11982
+ const logs = this.mobbConfigStore.get(this.path) || [];
11983
+ if (logs.length >= MAX_LOGS_SIZE) {
11984
+ logs.shift();
12038
11985
  }
12039
- }
12040
- triggerCircuitBreaker() {
12041
- this.isCircuitBroken = true;
12042
- this.queue = [];
12043
- this.isProcessing = false;
12044
- if (this.circuitBreakerTimer) {
12045
- clearTimeout(this.circuitBreakerTimer);
12046
- }
12047
- this.circuitBreakerTimer = setTimeout(() => {
12048
- this.isCircuitBroken = false;
12049
- this.circuitBreakerTimer = null;
12050
- if (this.queue.length > 0 && !this.isProcessing) {
12051
- this.processQueue();
12052
- }
12053
- }, CIRCUIT_BREAKER_TIME);
11986
+ this.mobbConfigStore.set(this.path, [...logs, logMessage]);
12054
11987
  }
12055
11988
  };
12056
11989
  var logger = new Logger();
@@ -12061,12 +11994,34 @@ var logDebug = (message, data) => logger.log(message, "debug", data);
12061
11994
  var log = logger.log.bind(logger);
12062
11995
 
12063
11996
  // src/mcp/services/McpGQLClient.ts
12064
- import Configstore3 from "configstore";
12065
11997
  import crypto3 from "crypto";
12066
11998
  import { GraphQLClient as GraphQLClient2 } from "graphql-request";
12067
11999
  import { v4 as uuidv42 } from "uuid";
12068
12000
  init_configs();
12069
12001
 
12002
+ // src/mcp/services/ConfigStoreService.ts
12003
+ init_configs();
12004
+ import Configstore4 from "configstore";
12005
+ function createConfigStore(defaultValues = { apiToken: "" }) {
12006
+ const API_URL2 = process.env["API_URL"] || MCP_DEFAULT_API_URL;
12007
+ let domain = "";
12008
+ try {
12009
+ const url = new URL(API_URL2);
12010
+ domain = url.hostname;
12011
+ } catch (e) {
12012
+ domain = API_URL2.replace(/^https?:\/\//, "").replace(/\/.*$/, "").replace(/:\d+$/, "");
12013
+ }
12014
+ const sanitizedDomain = domain.replace(/\./g, "_");
12015
+ return new Configstore4(
12016
+ `${packageJson.name}-${sanitizedDomain}`,
12017
+ defaultValues
12018
+ );
12019
+ }
12020
+ function getConfigStore() {
12021
+ return createConfigStore();
12022
+ }
12023
+ var configStore = getConfigStore();
12024
+
12070
12025
  // src/mcp/services/McpAuthService.ts
12071
12026
  import crypto2 from "crypto";
12072
12027
  import os2 from "os";
@@ -12147,16 +12102,17 @@ var McpAuthService = class {
12147
12102
  };
12148
12103
 
12149
12104
  // src/mcp/services/McpGQLClient.ts
12150
- var mobbConfigStore = new Configstore3(packageJson.name, { apiToken: "" });
12151
12105
  var McpGQLClient = class {
12152
12106
  constructor(args) {
12153
12107
  __publicField(this, "client");
12154
12108
  __publicField(this, "clientSdk");
12155
12109
  __publicField(this, "_auth");
12110
+ __publicField(this, "currentUser", null);
12111
+ __publicField(this, "apiUrl");
12156
12112
  this._auth = args;
12157
- const API_URL2 = process.env["API_URL"] || MCP_DEFAULT_API_URL;
12158
- logDebug("creating graphql client", { API_URL: API_URL2, args });
12159
- this.client = new GraphQLClient2(API_URL2, {
12113
+ this.apiUrl = process.env["API_URL"] || MCP_DEFAULT_API_URL;
12114
+ logDebug(`creating graphql client with api url ${this.apiUrl}`, { args });
12115
+ this.client = new GraphQLClient2(this.apiUrl, {
12160
12116
  headers: args.type === "apiKey" ? { [MCP_API_KEY_HEADER_NAME]: args.apiKey || "" } : {
12161
12117
  Authorization: `Bearer ${args.token}`
12162
12118
  },
@@ -12175,7 +12131,7 @@ var McpGQLClient = class {
12175
12131
  }
12176
12132
  getErrorContext() {
12177
12133
  return {
12178
- endpoint: process.env["API_URL"] || MCP_DEFAULT_API_URL,
12134
+ endpoint: this.apiUrl,
12179
12135
  apiKey: this._auth.type === "apiKey" ? this._auth.apiKey : "",
12180
12136
  headers: {
12181
12137
  [MCP_API_KEY_HEADER_NAME]: this._auth.type === "apiKey" ? "[REDACTED]" : "undefined",
@@ -12183,10 +12139,10 @@ var McpGQLClient = class {
12183
12139
  }
12184
12140
  };
12185
12141
  }
12186
- async verifyApiConnection() {
12142
+ async isApiEndpointReachable() {
12187
12143
  try {
12188
12144
  logDebug("GraphQL: Calling Me query for API connection verification");
12189
- const result = await this.clientSdk.Me();
12145
+ const result = await this.getUserInfo();
12190
12146
  logDebug("GraphQL: Me query successful", { result });
12191
12147
  return true;
12192
12148
  } catch (e) {
@@ -12199,6 +12155,23 @@ var McpGQLClient = class {
12199
12155
  }
12200
12156
  return true;
12201
12157
  }
12158
+ /**
12159
+ * Verifies both API endpoint reachability and user authentication
12160
+ * @returns true if both API is reachable and user is authenticated
12161
+ */
12162
+ async verifyApiConnection() {
12163
+ const isReachable = await this.isApiEndpointReachable();
12164
+ if (!isReachable) {
12165
+ return false;
12166
+ }
12167
+ try {
12168
+ await this.validateUserToken();
12169
+ return true;
12170
+ } catch (e) {
12171
+ logError("User token validation failed", { error: e });
12172
+ return false;
12173
+ }
12174
+ }
12202
12175
  async uploadS3BucketInfo() {
12203
12176
  try {
12204
12177
  logDebug("GraphQL: Calling uploadS3BucketInfo mutation");
@@ -12253,8 +12226,9 @@ var McpGQLClient = class {
12253
12226
  }
12254
12227
  }
12255
12228
  async subscribeToGetAnalysis(params) {
12229
+ const { scanContext } = params;
12256
12230
  try {
12257
- logDebug("GraphQL: Starting GetAnalysis subscription", {
12231
+ logDebug(`[${scanContext}] GraphQL: Starting GetAnalysis subscription`, {
12258
12232
  params: params.subscribeToAnalysisParams
12259
12233
  });
12260
12234
  const { callbackStates } = params;
@@ -12262,10 +12236,13 @@ var McpGQLClient = class {
12262
12236
  GetAnalysisSubscriptionDocument,
12263
12237
  params.subscribeToAnalysisParams,
12264
12238
  async (resolve, reject, data) => {
12265
- logDebug("GraphQL: GetAnalysis subscription data received", { data });
12239
+ logDebug(
12240
+ `[${scanContext}] GraphQL: GetAnalysis subscription data received ${data.analysis?.state}`,
12241
+ { data }
12242
+ );
12266
12243
  if (!data.analysis?.state || data.analysis?.state === "Failed" /* Failed */) {
12267
12244
  const errorMessage = data.analysis?.failReason || `Analysis failed with id: ${data.analysis?.id}`;
12268
- logError("GraphQL: Analysis failed", {
12245
+ logError(`[${scanContext}] GraphQL: Analysis failed`, {
12269
12246
  analysisId: data.analysis?.id,
12270
12247
  state: data.analysis?.state,
12271
12248
  failReason: data.analysis?.failReason,
@@ -12275,11 +12252,14 @@ var McpGQLClient = class {
12275
12252
  return;
12276
12253
  }
12277
12254
  if (callbackStates.includes(data.analysis?.state)) {
12278
- logDebug("GraphQL: Analysis state matches callback states", {
12279
- analysisId: data.analysis.id,
12280
- state: data.analysis.state,
12281
- callbackStates
12282
- });
12255
+ logDebug(
12256
+ `[${scanContext}] GraphQL: Analysis state matches callback states: ${data.analysis.state}`,
12257
+ {
12258
+ analysisId: data.analysis.id,
12259
+ state: data.analysis.state,
12260
+ callbackStates
12261
+ }
12262
+ );
12283
12263
  await params.callback(data.analysis.id);
12284
12264
  resolve(data);
12285
12265
  }
@@ -12294,10 +12274,12 @@ var McpGQLClient = class {
12294
12274
  timeoutInMs: params.timeoutInMs
12295
12275
  }
12296
12276
  );
12297
- logDebug("GraphQL: GetAnalysis subscription completed", { result });
12277
+ logDebug(`[${scanContext}] GraphQL: GetAnalysis subscription completed`, {
12278
+ result
12279
+ });
12298
12280
  return result;
12299
12281
  } catch (e) {
12300
- logError("GraphQL: GetAnalysis subscription failed", {
12282
+ logError(`[${scanContext}] GraphQL: GetAnalysis subscription failed`, {
12301
12283
  error: e,
12302
12284
  params: params.subscribeToAnalysisParams,
12303
12285
  ...this.getErrorContext()
@@ -12361,8 +12343,12 @@ var McpGQLClient = class {
12361
12343
  }
12362
12344
  async getUserInfo() {
12363
12345
  const { me } = await this.clientSdk.Me();
12346
+ this.currentUser = me;
12364
12347
  return me;
12365
12348
  }
12349
+ getCurrentUser() {
12350
+ return this.currentUser;
12351
+ }
12366
12352
  async validateUserToken() {
12367
12353
  logDebug("validating user token");
12368
12354
  try {
@@ -12555,16 +12541,16 @@ var McpGQLClient = class {
12555
12541
  async function createAuthenticatedMcpGQLClient({
12556
12542
  isBackgoundCall = false
12557
12543
  } = {}) {
12558
- logDebug("getting config", { apiToken: mobbConfigStore.get("apiToken") });
12544
+ logDebug("getting config", { apiToken: configStore.get("apiToken") });
12559
12545
  const initialClient = new McpGQLClient({
12560
12546
  apiKey: process.env["MOBB_API_KEY"] || process.env["API_KEY"] || // fallback for backward compatibility
12561
- mobbConfigStore.get("apiToken") || "",
12547
+ configStore.get("apiToken") || "",
12562
12548
  type: "apiKey"
12563
12549
  });
12564
- const isConnected = await initialClient.verifyApiConnection();
12565
- logDebug("API connection status", { isConnected });
12566
- if (!isConnected) {
12567
- throw new ApiConnectionError("Error: failed to connect to Mobb API");
12550
+ const isApiEndpointReachable = await initialClient.isApiEndpointReachable();
12551
+ logDebug("API connection status", { isApiEndpointReachable });
12552
+ if (!isApiEndpointReachable) {
12553
+ throw new ApiConnectionError("Error: failed to reach Mobb GraphQL endpoint");
12568
12554
  }
12569
12555
  logDebug("validating user token");
12570
12556
  const userVerify = await initialClient.validateUserToken();
@@ -12573,7 +12559,7 @@ async function createAuthenticatedMcpGQLClient({
12573
12559
  }
12574
12560
  const authService = new McpAuthService(initialClient);
12575
12561
  const newApiToken = await authService.authenticate(isBackgoundCall);
12576
- mobbConfigStore.set("apiToken", newApiToken);
12562
+ configStore.set("apiToken", newApiToken);
12577
12563
  return new McpGQLClient({ apiKey: newApiToken, type: "apiKey" });
12578
12564
  }
12579
12565
 
@@ -12582,6 +12568,9 @@ var MCP_TOOL_CHECK_FOR_NEW_AVAILABLE_FIXES = "check_for_new_available_fixes";
12582
12568
  var MCP_TOOL_FETCH_AVAILABLE_FIXES = "fetch_available_fixes";
12583
12569
  var MCP_TOOL_SCAN_AND_FIX_VULNERABILITIES = "scan_and_fix_vulnerabilities";
12584
12570
 
12571
+ // src/mcp/core/McpServer.ts
12572
+ init_configs();
12573
+
12585
12574
  // src/mcp/core/ToolRegistry.ts
12586
12575
  var ToolRegistry = class {
12587
12576
  constructor() {
@@ -12656,6 +12645,18 @@ var McpServer = class {
12656
12645
  } else {
12657
12646
  logWarn(`${message} (exit code: ${exitCode})`, { signal, exitCode });
12658
12647
  }
12648
+ } else if (signal === "unhandledRejection") {
12649
+ const errorDetails = {
12650
+ signal,
12651
+ errorType: error?.constructor?.name || "Unknown",
12652
+ errorMessage: error instanceof Error ? error.message : String(error),
12653
+ errorStack: error instanceof Error ? error.stack : void 0,
12654
+ errorString: error?.toString(),
12655
+ errorJson: JSON.stringify(error, null, 2),
12656
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
12657
+ };
12658
+ logError(`${message} - Enhanced error details`, errorDetails);
12659
+ logError(`${message} - Raw error object`, { error, signal });
12659
12660
  } else if (error) {
12660
12661
  logError(`${message}`, { error, signal });
12661
12662
  } else {
@@ -12700,30 +12701,43 @@ var McpServer = class {
12700
12701
  });
12701
12702
  }
12702
12703
  async triggerScanForNewAvailableFixes() {
12703
- const gqlClient = await createAuthenticatedMcpGQLClient({
12704
- isBackgoundCall: true
12705
- });
12706
- const isConnected = await gqlClient.verifyApiConnection();
12707
- if (!isConnected) {
12708
- logError("Failed to connect to the API, skipping scan");
12709
- return;
12710
- }
12711
- if (process.env["WORKSPACE_FOLDER_PATHS"]) {
12712
- logDebug("WORKSPACE_FOLDER_PATHS is set", {
12713
- WORKSPACE_FOLDER_PATHS: process.env["WORKSPACE_FOLDER_PATHS"]
12704
+ try {
12705
+ const gqlClient = await createAuthenticatedMcpGQLClient({
12706
+ isBackgoundCall: true
12714
12707
  });
12715
- try {
12716
- const checkForNewAvailableFixesTool = this.toolRegistry.getTool(
12717
- MCP_TOOL_CHECK_FOR_NEW_AVAILABLE_FIXES
12718
- );
12719
- logInfo("Triggering periodic scan for new available fixes");
12720
- checkForNewAvailableFixesTool.triggerScan({
12721
- path: process.env["WORKSPACE_FOLDER_PATHS"],
12722
- gqlClient
12708
+ const isConnected = await gqlClient.verifyApiConnection();
12709
+ if (!isConnected) {
12710
+ logError("Failed to connect to the API, skipping background scan");
12711
+ return;
12712
+ }
12713
+ if (process.env["WORKSPACE_FOLDER_PATHS"]) {
12714
+ logDebug("WORKSPACE_FOLDER_PATHS is set", {
12715
+ WORKSPACE_FOLDER_PATHS: process.env["WORKSPACE_FOLDER_PATHS"]
12723
12716
  });
12724
- } catch (error) {
12725
- logError("Error getting workspace folder path tool", { error });
12717
+ try {
12718
+ const checkForNewAvailableFixesTool = this.toolRegistry.getTool(
12719
+ MCP_TOOL_CHECK_FOR_NEW_AVAILABLE_FIXES
12720
+ );
12721
+ logInfo("Triggering periodic scan for new available fixes");
12722
+ checkForNewAvailableFixesTool.triggerScan({
12723
+ path: process.env["WORKSPACE_FOLDER_PATHS"],
12724
+ gqlClient
12725
+ });
12726
+ } catch (error) {
12727
+ logError("Error getting workspace folder path tool", { error });
12728
+ }
12726
12729
  }
12730
+ } catch (error) {
12731
+ if (error instanceof Error && (error.message.includes("Authentication") || error.message.includes("failed to connect to Mobb API"))) {
12732
+ logError(
12733
+ "Background scan skipped due to authentication failure. Please re-authenticate by running a manual scan.",
12734
+ {
12735
+ error: error.message
12736
+ }
12737
+ );
12738
+ return;
12739
+ }
12740
+ logError("Unexpected error during background scan", { error });
12727
12741
  }
12728
12742
  }
12729
12743
  async handleListToolsRequest(request) {
@@ -12737,7 +12751,11 @@ var McpServer = class {
12737
12751
  logDebug("env", {
12738
12752
  env: process.env
12739
12753
  });
12740
- void this.triggerScanForNewAvailableFixes();
12754
+ if (isAutoScan) {
12755
+ void this.triggerScanForNewAvailableFixes();
12756
+ } else {
12757
+ logDebug("Auto scan disabled, skipping triggerScanForNewAvailableFixes");
12758
+ }
12741
12759
  const toolsDefinitions = this.toolRegistry.getAllTools();
12742
12760
  const response = {
12743
12761
  tools: toolsDefinitions.map((tool) => ({
@@ -12947,7 +12965,6 @@ var BaseTool = class {
12947
12965
 
12948
12966
  // src/mcp/tools/checkForNewAvailableFixes/CheckForNewAvailableFixesService.ts
12949
12967
  init_configs();
12950
- import Configstore4 from "configstore";
12951
12968
 
12952
12969
  // src/mcp/core/prompts.ts
12953
12970
  init_configs();
@@ -13566,6 +13583,18 @@ var initializeSecurityReport = async (gqlClient, scanContext) => {
13566
13583
  return repoUploadInfo;
13567
13584
  } catch (error) {
13568
13585
  const message = error.message;
13586
+ if (message.includes("Authentication hook unauthorized") || message.includes("access-denied")) {
13587
+ logError(
13588
+ "Authentication failed during security report initialization. Please re-authenticate.",
13589
+ {
13590
+ error: message
13591
+ }
13592
+ );
13593
+ throw new ReportInitializationError(
13594
+ "Authentication failed. Please re-authenticate and try again."
13595
+ );
13596
+ }
13597
+ logError("Error initializing security report", { error: message });
13569
13598
  throw new ReportInitializationError(
13570
13599
  `Error initializing security report: ${message}`
13571
13600
  );
@@ -13646,7 +13675,8 @@ var executeSecurityScan = async ({
13646
13675
  });
13647
13676
  },
13648
13677
  callbackStates: ["Finished" /* Finished */],
13649
- timeoutInMs: MCP_VUL_REPORT_DIGEST_TIMEOUT_MS
13678
+ timeoutInMs: MCP_VUL_REPORT_DIGEST_TIMEOUT_MS,
13679
+ scanContext
13650
13680
  });
13651
13681
  } catch (error) {
13652
13682
  logError(`[${scanContext}] Security analysis failed or timed out`, {
@@ -13680,6 +13710,8 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
13680
13710
  __publicField(this, "intervalId", null);
13681
13711
  __publicField(this, "isInitialScanComplete", false);
13682
13712
  __publicField(this, "gqlClient", null);
13713
+ __publicField(this, "fullScanPathsScanned", []);
13714
+ this.fullScanPathsScanned = configStore.get("fullScanPathsScanned") || [];
13683
13715
  }
13684
13716
  static getInstance() {
13685
13717
  if (!_CheckForNewAvailableFixesService.instance) {
@@ -13695,6 +13727,7 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
13695
13727
  this.filesLastScanned = {};
13696
13728
  this.freshFixes = [];
13697
13729
  this.reportedFixes = [];
13730
+ this.fullScanPathsScanned = configStore.get("fullScanPathsScanned") || [];
13698
13731
  if (this.intervalId) {
13699
13732
  clearInterval(this.intervalId);
13700
13733
  this.intervalId = null;
@@ -13715,61 +13748,84 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
13715
13748
  });
13716
13749
  if (!this.gqlClient) {
13717
13750
  logInfo(`[${scanContext}] No GQL client found, skipping scan`);
13718
- return;
13719
- }
13720
- const isConnected = await this.gqlClient.verifyApiConnection();
13721
- if (!isConnected) {
13722
- logError(`[${scanContext}] Failed to connect to the API, scan aborted`);
13723
- return;
13751
+ throw new Error("No GQL client found");
13724
13752
  }
13725
- logDebug(
13726
- `[${scanContext}] Connected to the API, assembling list of files to scan`,
13727
- { path: path13 }
13728
- );
13729
- const files = await getLocalFiles({
13730
- path: path13,
13731
- isAllFilesScan
13732
- });
13733
- logDebug(`[${scanContext}] Active files`, { files });
13734
- const filesToScan = files.filter((file) => {
13735
- const lastScannedEditTime = this.filesLastScanned[file.fullPath];
13736
- if (!lastScannedEditTime) {
13737
- return true;
13753
+ try {
13754
+ const isConnected = await this.gqlClient.verifyApiConnection();
13755
+ if (!isConnected) {
13756
+ logError(`[${scanContext}] Failed to connect to the API, scan aborted`);
13757
+ throw new ApiConnectionError();
13738
13758
  }
13739
- return file.lastEdited > lastScannedEditTime;
13740
- });
13741
- if (filesToScan.length === 0) {
13742
- logInfo(`[${scanContext}] No files require scanning`);
13743
- return;
13744
- }
13745
- logDebug(`[${scanContext}] Files requiring security scan`, { filesToScan });
13746
- const { fixReportId, projectId } = await scanFiles({
13747
- fileList: filesToScan.map((file) => file.relativePath),
13748
- repositoryPath: path13,
13749
- gqlClient: this.gqlClient,
13750
- isAllDetectionRulesScan,
13751
- scanContext
13752
- });
13753
- logInfo(
13754
- `[${scanContext}] Security scan completed for ${path13} reportId: ${fixReportId} projectId: ${projectId}`
13755
- );
13756
- if (isAllFilesScan) {
13757
- return;
13759
+ logDebug(
13760
+ `[${scanContext}] Connected to the API, assembling list of files to scan`,
13761
+ { path: path13 }
13762
+ );
13763
+ const files = await getLocalFiles({
13764
+ path: path13,
13765
+ isAllFilesScan
13766
+ });
13767
+ logDebug(`[${scanContext}] Active files`, { files });
13768
+ const filesToScan = files.filter((file) => {
13769
+ const lastScannedEditTime = this.filesLastScanned[file.fullPath];
13770
+ if (!lastScannedEditTime) {
13771
+ return true;
13772
+ }
13773
+ return file.lastEdited > lastScannedEditTime;
13774
+ });
13775
+ if (filesToScan.length === 0) {
13776
+ logInfo(`[${scanContext}] No files require scanning`);
13777
+ return;
13778
+ }
13779
+ logDebug(`[${scanContext}] Files requiring security scan`, {
13780
+ filesToScan
13781
+ });
13782
+ const { fixReportId, projectId } = await scanFiles({
13783
+ fileList: filesToScan.map((file) => file.relativePath),
13784
+ repositoryPath: path13,
13785
+ gqlClient: this.gqlClient,
13786
+ isAllDetectionRulesScan,
13787
+ scanContext
13788
+ });
13789
+ logInfo(
13790
+ `[${scanContext}] Security scan completed for ${path13} reportId: ${fixReportId} projectId: ${projectId}`
13791
+ );
13792
+ if (isAllFilesScan) {
13793
+ return;
13794
+ }
13795
+ const fixes = await this.gqlClient.getReportFixesPaginated({
13796
+ reportId: fixReportId,
13797
+ offset: 0,
13798
+ limit: 1e3
13799
+ });
13800
+ const newFixes = fixes?.fixes?.filter(
13801
+ (fix) => !this.isFixAlreadyReported(fix)
13802
+ );
13803
+ logInfo(
13804
+ `[${scanContext}] Security fixes retrieved, total: ${fixes?.fixes?.length || 0}, new: ${newFixes?.length || 0}`
13805
+ );
13806
+ this.updateFreshFixesCache(newFixes || [], filesToScan);
13807
+ this.updateFilesScanTimestamps(filesToScan);
13808
+ this.isInitialScanComplete = true;
13809
+ } catch (error) {
13810
+ const errorMessage = error.message;
13811
+ if (errorMessage.includes("Authentication failed") || errorMessage.includes("access-denied") || errorMessage.includes("Authentication hook unauthorized")) {
13812
+ logError(
13813
+ "Periodic scan skipped due to authentication failure. Please re-authenticate by running a manual scan.",
13814
+ {
13815
+ error: errorMessage
13816
+ }
13817
+ );
13818
+ return;
13819
+ }
13820
+ if (errorMessage.includes("ReportInitializationError")) {
13821
+ logError("Periodic scan failed during report initialization", {
13822
+ error: errorMessage
13823
+ });
13824
+ return;
13825
+ }
13826
+ logError("Unexpected error during periodic security scan", { error });
13827
+ throw error;
13758
13828
  }
13759
- const fixes = await this.gqlClient.getReportFixesPaginated({
13760
- reportId: fixReportId,
13761
- offset: 0,
13762
- limit: 1e3
13763
- });
13764
- const newFixes = fixes?.fixes?.filter(
13765
- (fix) => !this.isFixAlreadyReported(fix)
13766
- );
13767
- logInfo(
13768
- `[${scanContext}] Security fixes retrieved, total: ${fixes?.fixes?.length || 0}, new: ${newFixes?.length || 0}`
13769
- );
13770
- this.updateFreshFixesCache(newFixes || [], filesToScan);
13771
- this.updateFilesScanTimestamps(filesToScan);
13772
- this.isInitialScanComplete = true;
13773
13829
  }
13774
13830
  updateFreshFixesCache(newFixes, filesToScan) {
13775
13831
  this.freshFixes = this.freshFixes.filter((fix) => !this.isFixFromOldScan(fix, filesToScan)).concat(newFixes).sort((a, b) => {
@@ -13823,7 +13879,7 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
13823
13879
  if (!this.intervalId) {
13824
13880
  this.startPeriodicScanning(path13);
13825
13881
  this.executeInitialScan(path13);
13826
- this.executeInitialFullScan(path13);
13882
+ void this.executeInitialFullScan(path13);
13827
13883
  }
13828
13884
  }
13829
13885
  startPeriodicScanning(path13) {
@@ -13840,37 +13896,46 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
13840
13896
  });
13841
13897
  }, MCP_PERIODIC_CHECK_INTERVAL);
13842
13898
  }
13843
- executeInitialFullScan(path13) {
13844
- logDebug("Triggering initial full security scan", { path: path13 });
13845
- const mobbConfigStore2 = new Configstore4(packageJson.name, { apiToken: "" });
13846
- const fullScanPathsScanned = mobbConfigStore2.get("fullScanPathsScanned") || [];
13847
- logDebug("Full scan paths scanned", { fullScanPathsScanned });
13848
- if (fullScanPathsScanned.includes(path13)) {
13849
- logDebug("Full scan already executed for this path", { path: path13 });
13899
+ async executeInitialFullScan(path13) {
13900
+ const scanContext = "FULL_SCAN";
13901
+ logDebug(`[${scanContext}] Triggering initial full security scan`, { path: path13 });
13902
+ logDebug(`[${scanContext}] Full scan paths scanned`, {
13903
+ fullScanPathsScanned: this.fullScanPathsScanned
13904
+ });
13905
+ if (this.fullScanPathsScanned.includes(path13)) {
13906
+ logDebug(`[${scanContext}] Full scan already executed for this path`, {
13907
+ path: path13
13908
+ });
13850
13909
  return;
13851
13910
  }
13852
- mobbConfigStore2.set("fullScanPathsScanned", [...fullScanPathsScanned, path13]);
13853
- this.scanForSecurityVulnerabilities({
13854
- path: path13,
13855
- isAllFilesScan: true,
13856
- isAllDetectionRulesScan: true,
13857
- scanContext: "FULL_SCAN"
13858
- }).catch((error) => {
13911
+ configStore.set("fullScanPathsScanned", [
13912
+ ...this.fullScanPathsScanned,
13913
+ path13
13914
+ ]);
13915
+ try {
13916
+ await this.scanForSecurityVulnerabilities({
13917
+ path: path13,
13918
+ isAllFilesScan: true,
13919
+ isAllDetectionRulesScan: true,
13920
+ scanContext: "FULL_SCAN"
13921
+ });
13922
+ if (!this.fullScanPathsScanned.includes(path13)) {
13923
+ this.fullScanPathsScanned.push(path13);
13924
+ configStore.set("fullScanPathsScanned", this.fullScanPathsScanned);
13925
+ }
13926
+ logInfo(`[${scanContext}] Full scan completed`, { path: path13 });
13927
+ } catch (error) {
13859
13928
  logError("Error during initial full security scan", { error });
13860
- }).then(() => {
13861
- const fullScanPathsScanned2 = mobbConfigStore2.get("fullScanPathsScanned") || [];
13862
- fullScanPathsScanned2.push(path13);
13863
- mobbConfigStore2.set("fullScanPathsScanned", fullScanPathsScanned2);
13864
- logDebug("Full scan completed", { path: path13 });
13865
- });
13929
+ }
13866
13930
  }
13867
13931
  executeInitialScan(path13) {
13868
- logDebug("Triggering initial security scan", { path: path13 });
13932
+ const scanContext = "BACKGROUND_INITIAL";
13933
+ logDebug(`[${scanContext}] Triggering initial security scan`, { path: path13 });
13869
13934
  this.scanForSecurityVulnerabilities({
13870
13935
  path: path13,
13871
13936
  scanContext: "BACKGROUND_INITIAL"
13872
13937
  }).catch((error) => {
13873
- logError("Error during initial security scan", { error });
13938
+ logError(`[${scanContext}] Error during initial security scan`, { error });
13874
13939
  });
13875
13940
  }
13876
13941
  generateFreshFixesResponse() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mobbdev",
3
- "version": "1.0.125",
3
+ "version": "1.0.127",
4
4
  "description": "Automated secure code remediation tool",
5
5
  "repository": "git+https://github.com/mobb-dev/bugsy.git",
6
6
  "main": "dist/index.js",
@@ -11,13 +11,14 @@
11
11
  "postinstall": "node ./src/post_install/cx_install.mjs",
12
12
  "build": "tsc && tsup-node --env.NODE_ENV production",
13
13
  "build:dev": "tsup-node --env.NODE_ENV development",
14
+ "increment-version": "./src/scripts/increment-version.sh",
14
15
  "test:mcp": "pnpm run build && vitest run __tests__/mcp/",
15
16
  "test:mcp:watch": "vitest watch __tests__/mcp/",
16
17
  "test:mcp:verbose": "pnpm run build && NODE_ENV=test VERBOSE=true vitest run __tests__/mcp/",
17
- "test:mcp:integration": "pnpm run build && GIT_PROXY_HOST=http://tinyproxy:8888 API_URL=http://app-api:8080/v1/graphql TOKEN=$(../../scripts/login_auth0.sh) vitest run __tests__/integration.test.ts -t 'mcp|MCP'",
18
+ "test:mcp:integration": "pnpm run build && GIT_PROXY_HOST=http://tinyproxy:8888 API_URL=http://app-api:8080/v1/graphql TOKEN=$(../../scripts/login_auth0.sh) vitest run --sequence.concurrent=false false __tests__/integration.test.ts -t 'mcp|MCP'",
18
19
  "test:mcp:all": "pnpm run test:mcp && pnpm run test:mcp:integration && cd ./__e2e__ && npm i && npm run test:mcp",
19
20
  "test:unit": "GIT_PROXY_HOST=http://tinyproxy:8888 TOKEN=$(../../scripts/login_auth0.sh) vitest run --exclude='**/__tests__/integration.test.ts' --exclude='**/__tests__/mcp/**'",
20
- "test:integration": "GIT_PROXY_HOST=http://tinyproxy:8888 TOKEN=$(../../scripts/login_auth0.sh) vitest run __tests__/integration.test.ts",
21
+ "test:integration": "GIT_PROXY_HOST=http://tinyproxy:8888 TOKEN=$(../../scripts/login_auth0.sh) vitest run --sequence.concurrent=false false __tests__/integration.test.ts",
21
22
  "test:integration:watch": "GIT_PROXY_HOST=http://tinyproxy:8888 TOKEN=$(../../scripts/login_auth0.sh) vitest watch run __tests__/integration.test.ts",
22
23
  "test": "pnpm run test:unit && pnpm run test:mcp && pnpm run test:integration",
23
24
  "test:ado": "GIT_PROXY_HOST=http://tinyproxy:8888 TOKEN=$(../../scripts/login_auth0.sh) vitest run ado.test",
@@ -27,7 +28,7 @@
27
28
  "test:cli:main": "GIT_PROXY_HOST=http://tinyproxy:8888 TOKEN=$(../../scripts/login_auth0.sh) vitest run cli-main.test",
28
29
  "test:coverage": "GIT_PROXY_HOST=http://tinyproxy:8888 TOKEN=$(../../scripts/login_auth0.sh) vitest run --coverage",
29
30
  "test:watch": "TOKEN=$(../../scripts/login_auth0.sh) vitest",
30
- "test:integration:proxy": "GIT_PROXY_HOST=http://tinyproxy:8888 HTTP_PROXY=http://localhost:8888 API_URL=http://app-api:8080/v1/graphql TOKEN=$(../../scripts/login_auth0.sh) vitest run integration.test.ts",
31
+ "test:integration:proxy": "GIT_PROXY_HOST=http://tinyproxy:8888 HTTP_PROXY=http://localhost:8888 API_URL=http://app-api:8080/v1/graphql TOKEN=$(../../scripts/login_auth0.sh) vitest run --sequence.concurrent=false false integration.test.ts",
31
32
  "lint": "eslint --cache --max-warnings 0 --ignore-path .eslintignore --ext .ts,.tsx,.jsx,.graphql .",
32
33
  "lint:fix": "eslint --fix --cache --max-warnings 0 --ignore-path .eslintignore --ext .js,.ts,.tsx,.jsx,.graphql . && prettier --write \"src/**/*.graphql\"",
33
34
  "lint:fix:files": "eslint --fix --cache --max-warnings 0 --ignore-path .eslintignore --ext .js,.ts,.tsx,.jsx,.graphql",