mobbdev 1.4.1 → 1.4.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -94,6 +94,9 @@ function getSdk(client, withWrapper = defaultWrapper) {
94
94
  performCliLogin(variables, requestHeaders, signal) {
95
95
  return withWrapper((wrappedRequestHeaders) => client.request({ document: PerformCliLoginDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), "performCliLogin", "mutation", variables);
96
96
  },
97
+ SetQuarantineEnabled(variables, requestHeaders, signal) {
98
+ return withWrapper((wrappedRequestHeaders) => client.request({ document: SetQuarantineEnabledDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), "SetQuarantineEnabled", "mutation", variables);
99
+ },
97
100
  CreateProject(variables, requestHeaders, signal) {
98
101
  return withWrapper((wrappedRequestHeaders) => client.request({ document: CreateProjectDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), "CreateProject", "mutation", variables);
99
102
  },
@@ -135,7 +138,7 @@ function getSdk(client, withWrapper = defaultWrapper) {
135
138
  }
136
139
  };
137
140
  }
138
- var AiBlameInferenceType, FixQuestionInputType, Language, ManifestAction, Effort_To_Apply_Fix_Enum, Fix_Rating_Tag_Enum, Fix_Report_State_Enum, Fix_State_Enum, IssueLanguage_Enum, IssueType_Enum, Pr_Status_Enum, Project_Role_Type_Enum, Vulnerability_Report_Issue_Category_Enum, Vulnerability_Report_Issue_State_Enum, Vulnerability_Report_Issue_Tag_Enum, Vulnerability_Report_Vendor_Enum, Vulnerability_Severity_Enum, FixDetailsFragmentDoc, FixReportSummaryFieldsFragmentDoc, MeDocument, GetLastOrgAndNamedProjectDocument, GetLastOrgDocument, GetEncryptedApiTokenDocument, FixReportStateDocument, GetVulnerabilityReportPathsDocument, GetAnalysisSubscriptionDocument, GetAnalysisDocument, GetFixesDocument, GetVulByNodesMetadataDocument, GetFalsePositiveDocument, UpdateScmTokenDocument, UploadS3BucketInfoDocument, GetTracyDiffUploadUrlDocument, AnalyzeCommitForExtensionAiBlameDocument, GetAiBlameInferenceDocument, GetAiBlameAttributionPromptDocument, GetPromptSummaryDocument, UploadAiBlameInferencesInitDocument, FinalizeAiBlameInferencesUploadDocument, UploadTracyRecordsDocument, GetTracyRawDataUploadUrlDocument, DigestVulnerabilityReportDocument, SubmitVulnerabilityReportDocument, CreateCommunityUserDocument, CreateCliLoginDocument, PerformCliLoginDocument, CreateProjectDocument, ValidateRepoUrlDocument, GitReferenceDocument, AutoPrAnalysisDocument, GetFixReportsByRepoUrlDocument, GetReportFixesDocument, GetLatestReportByRepoUrlDocument, UpdateDownloadedFixDataDocument, GetUserMvsAutoFixDocument, StreamBlameAiAnalysisRequestsDocument, StreamCommitBlameRequestsDocument, ScanSkillDocument, SkillVerdictsByMd5Document, defaultWrapper;
141
+ var AiBlameInferenceType, FixQuestionInputType, Language, ManifestAction, Effort_To_Apply_Fix_Enum, Fix_Rating_Tag_Enum, Fix_Report_State_Enum, Fix_State_Enum, IssueLanguage_Enum, IssueType_Enum, Pr_Status_Enum, Project_Role_Type_Enum, Vulnerability_Report_Issue_Category_Enum, Vulnerability_Report_Issue_State_Enum, Vulnerability_Report_Issue_Tag_Enum, Vulnerability_Report_Vendor_Enum, Vulnerability_Severity_Enum, FixDetailsFragmentDoc, FixReportSummaryFieldsFragmentDoc, MeDocument, GetLastOrgAndNamedProjectDocument, GetLastOrgDocument, GetEncryptedApiTokenDocument, FixReportStateDocument, GetVulnerabilityReportPathsDocument, GetAnalysisSubscriptionDocument, GetAnalysisDocument, GetFixesDocument, GetVulByNodesMetadataDocument, GetFalsePositiveDocument, UpdateScmTokenDocument, UploadS3BucketInfoDocument, GetTracyDiffUploadUrlDocument, AnalyzeCommitForExtensionAiBlameDocument, GetAiBlameInferenceDocument, GetAiBlameAttributionPromptDocument, GetPromptSummaryDocument, UploadAiBlameInferencesInitDocument, FinalizeAiBlameInferencesUploadDocument, UploadTracyRecordsDocument, GetTracyRawDataUploadUrlDocument, DigestVulnerabilityReportDocument, SubmitVulnerabilityReportDocument, CreateCommunityUserDocument, CreateCliLoginDocument, PerformCliLoginDocument, SetQuarantineEnabledDocument, CreateProjectDocument, ValidateRepoUrlDocument, GitReferenceDocument, AutoPrAnalysisDocument, GetFixReportsByRepoUrlDocument, GetReportFixesDocument, GetLatestReportByRepoUrlDocument, UpdateDownloadedFixDataDocument, GetUserMvsAutoFixDocument, StreamBlameAiAnalysisRequestsDocument, StreamCommitBlameRequestsDocument, ScanSkillDocument, SkillVerdictsByMd5Document, defaultWrapper;
139
142
  var init_client_generates = __esm({
140
143
  "src/features/analysis/scm/generates/client_generates.ts"() {
141
144
  "use strict";
@@ -260,6 +263,7 @@ var init_client_generates = __esm({
260
263
  IssueType_Enum2["HttpParameterPollution"] = "HTTP_PARAMETER_POLLUTION";
261
264
  IssueType_Enum2["HttpResponseSplitting"] = "HTTP_RESPONSE_SPLITTING";
262
265
  IssueType_Enum2["IframeWithoutSandbox"] = "IFRAME_WITHOUT_SANDBOX";
266
+ IssueType_Enum2["ImproperCertificateValidation"] = "IMPROPER_CERTIFICATE_VALIDATION";
263
267
  IssueType_Enum2["ImproperExceptionHandling"] = "IMPROPER_EXCEPTION_HANDLING";
264
268
  IssueType_Enum2["ImproperResourceShutdownOrRelease"] = "IMPROPER_RESOURCE_SHUTDOWN_OR_RELEASE";
265
269
  IssueType_Enum2["ImproperStringFormatting"] = "IMPROPER_STRING_FORMATTING";
@@ -278,6 +282,7 @@ var init_client_generates = __esm({
278
282
  IssueType_Enum2["InsecureTmpFile"] = "INSECURE_TMP_FILE";
279
283
  IssueType_Enum2["InsecureUuidVersion"] = "INSECURE_UUID_VERSION";
280
284
  IssueType_Enum2["InsufficientLogging"] = "INSUFFICIENT_LOGGING";
285
+ IssueType_Enum2["J2EeGetConnection"] = "J2EE_GET_CONNECTION";
281
286
  IssueType_Enum2["JqueryDeprecatedSymbols"] = "JQUERY_DEPRECATED_SYMBOLS";
282
287
  IssueType_Enum2["LeftoverDebugCode"] = "LEFTOVER_DEBUG_CODE";
283
288
  IssueType_Enum2["LocaleDependentComparison"] = "LOCALE_DEPENDENT_COMPARISON";
@@ -941,6 +946,12 @@ var init_client_generates = __esm({
941
946
  level
942
947
  justification
943
948
  }
949
+ appliedSkills
950
+ mcpCalls {
951
+ mcpServer
952
+ mcpTool
953
+ callCount
954
+ }
944
955
  }
945
956
  }
946
957
  ... on PromptSummaryProcessing {
@@ -1092,6 +1103,13 @@ var init_client_generates = __esm({
1092
1103
  performCliLogin(loginId: $loginId) {
1093
1104
  status
1094
1105
  }
1106
+ }
1107
+ `;
1108
+ SetQuarantineEnabledDocument = `
1109
+ mutation SetQuarantineEnabled($enabled: Boolean!) {
1110
+ update_organization(where: {}, _set: {quarantineEnabled: $enabled}) {
1111
+ affected_rows
1112
+ }
1095
1113
  }
1096
1114
  `;
1097
1115
  CreateProjectDocument = `
@@ -1277,12 +1295,15 @@ var init_client_generates = __esm({
1277
1295
  SkillVerdictsByMd5Document = `
1278
1296
  query SkillVerdictsByMd5($md5s: [String!]!) {
1279
1297
  skillVerdictsByMd5(md5s: $md5s) {
1280
- md5
1281
- verdict
1282
- summary
1283
- scannerName
1284
- scannerVersion
1285
- scannedAt
1298
+ quarantineEnabled
1299
+ verdicts {
1300
+ md5
1301
+ verdict
1302
+ summary
1303
+ scannerName
1304
+ scannerVersion
1305
+ scannedAt
1306
+ }
1286
1307
  }
1287
1308
  }
1288
1309
  `;
@@ -1400,6 +1421,7 @@ var init_getIssueType = __esm({
1400
1421
  ["NO_EQUIVALENCE_METHOD" /* NoEquivalenceMethod */]: "Class Does Not Implement Equivalence Method",
1401
1422
  ["INFORMATION_EXPOSURE_VIA_HEADERS" /* InformationExposureViaHeaders */]: "Information Exposure via Headers",
1402
1423
  ["DEBUG_ENABLED" /* DebugEnabled */]: "Debug Enabled",
1424
+ ["J2EE_GET_CONNECTION" /* J2EeGetConnection */]: "J2EE Bad Practices: getConnection()",
1403
1425
  ["LEFTOVER_DEBUG_CODE" /* LeftoverDebugCode */]: "Leftover Debug Code",
1404
1426
  ["POOR_ERROR_HANDLING_EMPTY_CATCH_BLOCK" /* PoorErrorHandlingEmptyCatchBlock */]: "Poor Error Handling: Empty Catch Block",
1405
1427
  ["ERRONEOUS_STRING_COMPARE" /* ErroneousStringCompare */]: "Erroneous String Compare",
@@ -1474,7 +1496,8 @@ var init_getIssueType = __esm({
1474
1496
  ["TAINTED_NUMERIC_CAST" /* TaintedNumericCast */]: "Tainted Numeric Cast",
1475
1497
  ["MISSING_X_FRAME_OPTIONS" /* MissingXFrameOptions */]: "Missing X-Frame-Options Header",
1476
1498
  ["IMPROPER_VALIDATION_OF_ARRAY_INDEX" /* ImproperValidationOfArrayIndex */]: "Improper Validation of Array Index",
1477
- ["INCORRECT_INTEGER_CONVERSION" /* IncorrectIntegerConversion */]: "Incorrect Integer Conversion"
1499
+ ["INCORRECT_INTEGER_CONVERSION" /* IncorrectIntegerConversion */]: "Incorrect Integer Conversion",
1500
+ ["IMPROPER_CERTIFICATE_VALIDATION" /* ImproperCertificateValidation */]: "Improper Certificate Validation"
1478
1501
  };
1479
1502
  issueTypeZ = z.nativeEnum(IssueType_Enum);
1480
1503
  getIssueTypeFriendlyString = (issueType) => {
@@ -3601,8 +3624,8 @@ var init_FileUtils = __esm({
3601
3624
  const fullPath = path.join(dir, item);
3602
3625
  try {
3603
3626
  await fsPromises.access(fullPath, fs.constants.R_OK);
3604
- const stat4 = await fsPromises.stat(fullPath);
3605
- if (stat4.isDirectory()) {
3627
+ const stat5 = await fsPromises.stat(fullPath);
3628
+ if (stat5.isDirectory()) {
3606
3629
  if (isRootLevel && excludedRootDirectories.includes(item)) {
3607
3630
  continue;
3608
3631
  }
@@ -3614,7 +3637,7 @@ var init_FileUtils = __esm({
3614
3637
  name: item,
3615
3638
  fullPath,
3616
3639
  relativePath: path.relative(rootDir, fullPath),
3617
- time: stat4.mtime.getTime(),
3640
+ time: stat5.mtime.getTime(),
3618
3641
  isFile: true
3619
3642
  });
3620
3643
  }
@@ -4148,11 +4171,11 @@ ${rootContent}`;
4148
4171
  try {
4149
4172
  const gitRoot = await this.getGitRoot();
4150
4173
  const gitignorePath = path2.join(gitRoot, ".gitignore");
4151
- const exists = fs2.existsSync(gitignorePath);
4174
+ const exists2 = fs2.existsSync(gitignorePath);
4152
4175
  this.log("[GitService] .gitignore existence check complete", "debug", {
4153
- exists
4176
+ exists: exists2
4154
4177
  });
4155
- return exists;
4178
+ return exists2;
4156
4179
  } catch (error) {
4157
4180
  const errorMessage = `Failed to check .gitignore existence: ${error.message}`;
4158
4181
  this.log(`[GitService] ${errorMessage}`, "error", { error });
@@ -4568,6 +4591,7 @@ var fixDetailsData = {
4568
4591
  issueDescription: "A data member and a function have the same name which can be confusing to the developer.",
4569
4592
  fixInstructions: "Rename the data member to avoid confusion."
4570
4593
  },
4594
+ ["J2EE_GET_CONNECTION" /* J2EeGetConnection */]: void 0,
4571
4595
  ["LEFTOVER_DEBUG_CODE" /* LeftoverDebugCode */]: void 0,
4572
4596
  ["UNVALIDATED_PUBLIC_METHOD_ARGUMENT" /* UnvalidatedPublicMethodArgument */]: void 0,
4573
4597
  ["ERRONEOUS_STRING_COMPARE" /* ErroneousStringCompare */]: void 0,
@@ -4670,7 +4694,8 @@ var fixDetailsData = {
4670
4694
  ["TAINTED_NUMERIC_CAST" /* TaintedNumericCast */]: void 0,
4671
4695
  ["MISSING_X_FRAME_OPTIONS" /* MissingXFrameOptions */]: void 0,
4672
4696
  ["IMPROPER_VALIDATION_OF_ARRAY_INDEX" /* ImproperValidationOfArrayIndex */]: void 0,
4673
- ["INCORRECT_INTEGER_CONVERSION" /* IncorrectIntegerConversion */]: void 0
4697
+ ["INCORRECT_INTEGER_CONVERSION" /* IncorrectIntegerConversion */]: void 0,
4698
+ ["IMPROPER_CERTIFICATE_VALIDATION" /* ImproperCertificateValidation */]: void 0
4674
4699
  };
4675
4700
 
4676
4701
  // src/features/analysis/scm/shared/src/commitDescriptionMarkup.ts
@@ -4834,6 +4859,31 @@ var go_default = vulnerabilities3;
4834
4859
  // src/features/analysis/scm/shared/src/storedFixData/java/index.ts
4835
4860
  init_client_generates();
4836
4861
 
4862
+ // src/features/analysis/scm/shared/src/storedFixData/java/j2eeGetConnection.ts
4863
+ var j2eeGetConnection = {
4864
+ guidance: () => `This fix replaces direct \`DriverManager.getConnection(...)\` calls with a container-managed JNDI \`DataSource\` lookup. The new code expects the app server (Tomcat / WildFly / WebSphere / etc.) to expose a configured connection pool under the JNDI name you specified.
4865
+
4866
+
4867
+  
4868
+
4869
+ ***Make sure the resource pool exists before merging.*** The patched code will throw a \`NamingException\` at runtime if the JNDI name does not resolve. Configure it in your container's resource definition:
4870
+
4871
+ - **Tomcat**: declare a \`<Resource>\` element in \`context.xml\` (or per-app \`META-INF/context.xml\`) with the same JNDI name, plus \`url\`, \`username\`, \`password\`, \`driverClassName\`, and any pool sizing.
4872
+ - **Spring Boot (embedded Tomcat)**: configure via \`spring.datasource.jndi-name\` and matching \`<Resource>\`, or use \`@ConfigurationProperties\` to bind a \`DataSource\` bean.
4873
+ - **WildFly / JBoss EAP**: declare a \`<datasource>\` in the standalone/domain XML and reference its JNDI binding.
4874
+ - **WebSphere / WebLogic**: define the JDBC provider and data source through the admin console; bind it to the JNDI name.
4875
+
4876
+
4877
+ &nbsp;
4878
+
4879
+ Also add a matching \`<resource-ref>\` (or \`<data-source>\`) in your \`WEB-INF/web.xml\` if you use one. The original connection details (URL, user, password) move from the call site into the resource definition \u2014 remove them from any constants / properties files where they were duplicated.
4880
+
4881
+
4882
+ &nbsp;
4883
+
4884
+ This fix is mandated by the J2EE / Jakarta EE specification (CWE-245) \u2014 direct driver management bypasses the container's pooling, retry, and failover policies.`
4885
+ };
4886
+
4837
4887
  // src/features/analysis/scm/shared/src/storedFixData/java/sqlInjection.ts
4838
4888
  var sqlInjection = {
4839
4889
  guidance: ({
@@ -4861,6 +4911,7 @@ var systemInformationLeak = {
4861
4911
  // src/features/analysis/scm/shared/src/storedFixData/java/index.ts
4862
4912
  var vulnerabilities4 = {
4863
4913
  ["PASSWORD_IN_COMMENT" /* PasswordInComment */]: passwordInComment,
4914
+ ["J2EE_GET_CONNECTION" /* J2EeGetConnection */]: j2eeGetConnection,
4864
4915
  ["SQL_Injection" /* SqlInjection */]: sqlInjection,
4865
4916
  ["SYSTEM_INFORMATION_LEAK" /* SystemInformationLeak */]: systemInformationLeak
4866
4917
  };
@@ -4945,10 +4996,24 @@ See more information [here](https://jinja.palletsprojects.com/en/3.1.x/templates
4945
4996
  ***Note: make sure that none of the data you're marking as safe is coming from user input, as this can lead to XSS vulnerabilities!***`
4946
4997
  };
4947
4998
 
4999
+ // src/features/analysis/scm/shared/src/storedFixData/python/improperCertificateValidation.ts
5000
+ var improperCertificateValidation = {
5001
+ guidance: () => `This fix re-enables TLS certificate validation by changing \`verify=False\` to \`verify=True\` on the HTTP request. Any call that was deliberately reaching a server with a self-signed, expired, or otherwise untrusted certificate will start raising \`ssl.SSLError\` / \`requests.exceptions.SSLError\` after this change.
5002
+
5003
+ &nbsp;
5004
+
5005
+ ***Before merging, confirm that every endpoint reached by this call presents a certificate signed by a trusted CA.*** If the call must talk to an internal service that uses a private CA, prefer pointing \`verify\` at the CA bundle (\`verify="/path/to/ca.pem"\`) over disabling validation. If the certificate cannot be trusted at all, the safe fix is to terminate that connection at a properly configured proxy, not to keep it unvalidated.
5006
+
5007
+ &nbsp;
5008
+
5009
+ See the [\`requests\` SSL verification docs](https://requests.readthedocs.io/en/latest/user/advanced/#ssl-cert-verification) for the supported \`verify\` values.`
5010
+ };
5011
+
4948
5012
  // src/features/analysis/scm/shared/src/storedFixData/python/index.ts
4949
5013
  var vulnerabilities7 = {
4950
5014
  ["AUTO_ESCAPE_FALSE" /* AutoEscapeFalse */]: autoEscapeFalse,
4951
- ["CSRF" /* Csrf */]: csrf
5015
+ ["CSRF" /* Csrf */]: csrf,
5016
+ ["IMPROPER_CERTIFICATE_VALIDATION" /* ImproperCertificateValidation */]: improperCertificateValidation
4952
5017
  };
4953
5018
  var python_default = vulnerabilities7;
4954
5019
 
@@ -5484,6 +5549,15 @@ var insecureCookie2 = {
5484
5549
  }
5485
5550
  };
5486
5551
 
5552
+ // src/features/analysis/scm/shared/src/storedQuestionData/java/j2eeGetConnection.ts
5553
+ var j2eeGetConnection2 = {
5554
+ jndiResourceName: {
5555
+ content: () => "What JNDI name is the database connection pool registered under?",
5556
+ description: () => 'We need the JNDI name your app server uses to expose its container-managed `DataSource`. The fix performs `new InitialContext().lookup(<jndi-name>)` to retrieve the pool, so this value must exactly match the resource definition (e.g. `<Resource name="...">` in Tomcat `context.xml`, or the binding declared in WildFly / WebSphere / WebLogic). The default `java:comp/env/jdbc/myDataSource` is the canonical Tomcat / Spring convention; replace it with whatever your environment uses.',
5557
+ guidance: () => ""
5558
+ }
5559
+ };
5560
+
5487
5561
  // src/features/analysis/scm/shared/src/storedQuestionData/java/leftoverDebugCode.ts
5488
5562
  var leftoverDebugCode = {
5489
5563
  isCodeUsed: {
@@ -5812,6 +5886,7 @@ var vulnerabilities12 = {
5812
5886
  ["UNCHECKED_LOOP_CONDITION" /* UncheckedLoopCondition */]: uncheckedLoopCondition,
5813
5887
  ["INSECURE_COOKIE" /* InsecureCookie */]: insecureCookie2,
5814
5888
  ["TRUST_BOUNDARY_VIOLATION" /* TrustBoundaryViolation */]: trustBoundaryViolation2,
5889
+ ["J2EE_GET_CONNECTION" /* J2EeGetConnection */]: j2eeGetConnection2,
5815
5890
  ["LEFTOVER_DEBUG_CODE" /* LeftoverDebugCode */]: leftoverDebugCode,
5816
5891
  ["ERRONEOUS_STRING_COMPARE" /* ErroneousStringCompare */]: erroneousStringCompare,
5817
5892
  ["DUPLICATED_STRINGS" /* DuplicatedStrings */]: duplicatedStrings
@@ -7178,7 +7253,7 @@ async function getAdoSdk(params) {
7178
7253
  const url = new URL(repoUrl);
7179
7254
  const origin = url.origin.toLowerCase().endsWith(".visualstudio.com") ? DEFUALT_ADO_ORIGIN : url.origin.toLowerCase();
7180
7255
  const params2 = `path=/&versionDescriptor[versionOptions]=0&versionDescriptor[versionType]=commit&versionDescriptor[version]=${branch}&resolveLfs=true&$format=zip&api-version=5.0&download=true`;
7181
- const path35 = [
7256
+ const path37 = [
7182
7257
  prefixPath,
7183
7258
  owner,
7184
7259
  projectName,
@@ -7189,7 +7264,7 @@ async function getAdoSdk(params) {
7189
7264
  "items",
7190
7265
  "items"
7191
7266
  ].filter(Boolean).join("/");
7192
- return new URL(`${path35}?${params2}`, origin).toString();
7267
+ return new URL(`${path37}?${params2}`, origin).toString();
7193
7268
  },
7194
7269
  async getAdoBranchList({ repoUrl }) {
7195
7270
  try {
@@ -7278,8 +7353,8 @@ async function getAdoSdk(params) {
7278
7353
  const changeType = entry.changeType;
7279
7354
  return changeType !== 16 && entry.item?.path;
7280
7355
  }).map((entry) => {
7281
- const path35 = entry.item.path;
7282
- return path35.startsWith("/") ? path35.slice(1) : path35;
7356
+ const path37 = entry.item.path;
7357
+ return path37.startsWith("/") ? path37.slice(1) : path37;
7283
7358
  });
7284
7359
  },
7285
7360
  async searchAdoPullRequests({
@@ -13646,6 +13721,7 @@ var GQLClient = class {
13646
13721
  return await this._clientSdk.ScanSkill(variables);
13647
13722
  }
13648
13723
  // T-467 — batched verdict lookup for the client-side quarantine check.
13724
+ // T-493 — response is the envelope `{ quarantineEnabled, verdicts }`.
13649
13725
  async skillVerdictsByMd5(md5s) {
13650
13726
  return await this._clientSdk.SkillVerdictsByMd5({ md5s });
13651
13727
  }
@@ -14068,7 +14144,11 @@ async function sanitizeDataWithCounts(obj, options) {
14068
14144
  if (typeof data === "string") {
14069
14145
  return sanitizeString(data);
14070
14146
  } else if (Array.isArray(data)) {
14071
- return Promise.all(data.map((item) => sanitizeRecursive(item)));
14147
+ const results = [];
14148
+ for (const item of data) {
14149
+ results.push(await sanitizeRecursive(item));
14150
+ }
14151
+ return results;
14072
14152
  } else if (data instanceof Error) {
14073
14153
  return data;
14074
14154
  } else if (data instanceof Date) {
@@ -14499,22 +14579,25 @@ async function prepareAndSendTracyRecords(client, rawRecords, workingDir, option
14499
14579
  const serializedRawDataByIndex = /* @__PURE__ */ new Map();
14500
14580
  const records = await timedStep(
14501
14581
  `${shouldSanitize ? "sanitize" : "serialize"} ${rawRecords.length} records`,
14502
- () => Promise.all(
14503
- rawRecords.map(async (record, index) => {
14582
+ async () => {
14583
+ const results = [];
14584
+ for (let index = 0; index < rawRecords.length; index++) {
14585
+ const record = rawRecords[index];
14504
14586
  if (record.rawData != null && record.rawDataS3Key == null) {
14505
14587
  const serialized = shouldSanitize ? await sanitizeRawData(record.rawData) : JSON.stringify(record.rawData);
14506
14588
  serializedRawDataByIndex.set(index, serialized);
14507
14589
  }
14508
14590
  const { rawData: _rawData, ...rest } = record;
14509
- return {
14591
+ results.push({
14510
14592
  ...rest,
14511
14593
  repositoryUrl: record.repositoryUrl ?? defaultRepoUrl,
14512
14594
  computerName,
14513
14595
  userName,
14514
14596
  clientVersion: record.clientVersion ?? defaultClientVersion
14515
- };
14516
- })
14517
- )
14597
+ });
14598
+ }
14599
+ return results;
14600
+ }
14518
14601
  );
14519
14602
  const recordsWithRawData = rawRecords.map((r, i) => ({ recordId: r.recordId, index: i })).filter((entry) => serializedRawDataByIndex.has(entry.index));
14520
14603
  if (recordsWithRawData.length > 0) {
@@ -14555,32 +14638,39 @@ async function prepareAndSendTracyRecords(client, rawRecords, workingDir, option
14555
14638
  errors: ["[step:s3-url] Malformed uploadFieldsJSON from server"]
14556
14639
  };
14557
14640
  }
14641
+ const MAX_CONCURRENT_S3_UPLOADS = 5;
14558
14642
  debug10(
14559
- "[step:s3-upload] Uploading %d files to S3",
14560
- recordsWithRawData.length
14643
+ "[step:s3-upload] Uploading %d files to S3 (concurrency=%d)",
14644
+ recordsWithRawData.length,
14645
+ MAX_CONCURRENT_S3_UPLOADS
14561
14646
  );
14562
14647
  const s3Start = Date.now();
14563
- const uploadResults = await Promise.allSettled(
14564
- recordsWithRawData.map(async (entry) => {
14565
- const rawDataJson = serializedRawDataByIndex.get(entry.index);
14566
- if (!rawDataJson) {
14567
- debug10("No serialized rawData for recordId=%s", entry.recordId);
14568
- return;
14569
- }
14570
- const uploadKey = `${keyPrefix}${entry.recordId}.json`;
14571
- await withTimeout(
14572
- uploadFile({
14573
- file: Buffer.from(rawDataJson, "utf-8"),
14574
- url,
14575
- uploadKey,
14576
- uploadFields
14577
- }),
14578
- BATCH_TIMEOUT_MS,
14579
- `[step:s3-upload] uploadFile ${entry.recordId}`
14580
- );
14581
- records[entry.index].rawDataS3Key = uploadKey;
14582
- })
14583
- );
14648
+ const uploadResults = [];
14649
+ for (let i = 0; i < recordsWithRawData.length; i += MAX_CONCURRENT_S3_UPLOADS) {
14650
+ const chunk = recordsWithRawData.slice(i, i + MAX_CONCURRENT_S3_UPLOADS);
14651
+ const chunkResults = await Promise.allSettled(
14652
+ chunk.map(async (entry) => {
14653
+ const rawDataJson = serializedRawDataByIndex.get(entry.index);
14654
+ if (!rawDataJson) {
14655
+ debug10("No serialized rawData for recordId=%s", entry.recordId);
14656
+ return;
14657
+ }
14658
+ const uploadKey = `${keyPrefix}${entry.recordId}.json`;
14659
+ await withTimeout(
14660
+ uploadFile({
14661
+ file: Buffer.from(rawDataJson, "utf-8"),
14662
+ url,
14663
+ uploadKey,
14664
+ uploadFields
14665
+ }),
14666
+ BATCH_TIMEOUT_MS,
14667
+ `[step:s3-upload] uploadFile ${entry.recordId}`
14668
+ );
14669
+ records[entry.index].rawDataS3Key = uploadKey;
14670
+ })
14671
+ );
14672
+ uploadResults.push(...chunkResults);
14673
+ }
14584
14674
  debug10(
14585
14675
  "[perf] s3-upload %d files: %dms",
14586
14676
  recordsWithRawData.length,
@@ -15172,7 +15262,7 @@ async function postIssueComment(params) {
15172
15262
  fpDescription
15173
15263
  } = params;
15174
15264
  const {
15175
- path: path35,
15265
+ path: path37,
15176
15266
  startLine,
15177
15267
  vulnerabilityReportIssue: {
15178
15268
  vulnerabilityReportIssueTags,
@@ -15187,7 +15277,7 @@ async function postIssueComment(params) {
15187
15277
  Refresh the page in order to see the changes.`,
15188
15278
  pull_number: pullRequest,
15189
15279
  commit_id: commitSha,
15190
- path: path35,
15280
+ path: path37,
15191
15281
  line: startLine
15192
15282
  });
15193
15283
  const commentId = commentRes.data.id;
@@ -15221,7 +15311,7 @@ async function postFixComment(params) {
15221
15311
  scanner
15222
15312
  } = params;
15223
15313
  const {
15224
- path: path35,
15314
+ path: path37,
15225
15315
  startLine,
15226
15316
  vulnerabilityReportIssue: { fixId, vulnerabilityReportIssueTags, category },
15227
15317
  vulnerabilityReportIssueId
@@ -15239,7 +15329,7 @@ async function postFixComment(params) {
15239
15329
  Refresh the page in order to see the changes.`,
15240
15330
  pull_number: pullRequest,
15241
15331
  commit_id: commitSha,
15242
- path: path35,
15332
+ path: path37,
15243
15333
  line: startLine
15244
15334
  });
15245
15335
  const commentId = commentRes.data.id;
@@ -15843,8 +15933,8 @@ if (typeof __filename !== "undefined") {
15843
15933
  }
15844
15934
  var costumeRequire = createRequire(moduleUrl);
15845
15935
  var getCheckmarxPath = () => {
15846
- const os16 = type();
15847
- const cxFileName = os16 === "Windows_NT" ? "cx.exe" : "cx";
15936
+ const os17 = type();
15937
+ const cxFileName = os17 === "Windows_NT" ? "cx.exe" : "cx";
15848
15938
  try {
15849
15939
  return costumeRequire.resolve(`.bin/${cxFileName}`);
15850
15940
  } catch (e) {
@@ -16761,8 +16851,8 @@ async function resolveSkillScanInput(skillInput) {
16761
16851
  if (!fs11.existsSync(resolvedPath)) {
16762
16852
  return skillInput;
16763
16853
  }
16764
- const stat4 = fs11.statSync(resolvedPath);
16765
- if (!stat4.isDirectory()) {
16854
+ const stat5 = fs11.statSync(resolvedPath);
16855
+ if (!stat5.isDirectory()) {
16766
16856
  throw new CliError(
16767
16857
  "Local skill input must be a directory containing SKILL.md"
16768
16858
  );
@@ -17161,10 +17251,16 @@ import { spawn } from "child_process";
17161
17251
 
17162
17252
  // src/features/claude_code/daemon.ts
17163
17253
  import { readFileSync, writeFileSync as writeFileSync2 } from "fs";
17164
- import path22 from "path";
17254
+ import * as os8 from "os";
17255
+ import path24 from "path";
17165
17256
  import { setTimeout as sleep2 } from "timers/promises";
17166
17257
  import Configstore3 from "configstore";
17167
17258
 
17259
+ // src/features/analysis/skill_quarantine/runQuarantineCheck.ts
17260
+ import { stat as stat3 } from "fs/promises";
17261
+ import { homedir as homedir4 } from "os";
17262
+ import path19 from "path";
17263
+
17168
17264
  // src/features/analysis/skill_quarantine/constants.ts
17169
17265
  var HEARTBEAT_DEBOUNCE_MS = (() => {
17170
17266
  const raw = Number(process.env["MOBB_TRACY_SKILL_QUARANTINE_DEBOUNCE_MS"]);
@@ -17173,7 +17269,8 @@ var HEARTBEAT_DEBOUNCE_MS = (() => {
17173
17269
  })();
17174
17270
  var KILL_SWITCH_ENV = "MOBB_TRACY_SKILL_QUARANTINE_DISABLE";
17175
17271
  var MALICIOUS_VERDICT = "MALICIOUS";
17176
- var ORPHAN_SWEEP_GRACE_MS = 10 * 60 * 1e3;
17272
+ var PARTIAL_SWEEP_GRACE_MS = 10 * 60 * 1e3;
17273
+ var STUB_MARKER = "\u26D4 QUARANTINED BY TRACY";
17177
17274
 
17178
17275
  // src/features/analysis/skill_quarantine/enumerateInstalledSkills.ts
17179
17276
  import { homedir as homedir2 } from "os";
@@ -17239,51 +17336,86 @@ import { globby as globby2 } from "globby";
17239
17336
  import { parse as parseJsoncLib } from "jsonc-parser";
17240
17337
 
17241
17338
  // src/features/analysis/context_file_scan_paths.ts
17242
- var SKILL_CATEGORY = "skill";
17339
+ var CATEGORY = {
17340
+ RULE: "rule",
17341
+ MEMORY: "memory",
17342
+ SKILL: "skill",
17343
+ COMMAND: "command",
17344
+ PROMPT: "prompt",
17345
+ AGENT_CONFIG: "agent-config",
17346
+ CONFIG: "config",
17347
+ MCP_CONFIG: "mcp-config",
17348
+ IGNORE: "ignore"
17349
+ };
17350
+ var SKILL_CATEGORY = CATEGORY.SKILL;
17243
17351
  var SCAN_PATHS = {
17244
17352
  "claude-code": [
17245
- { glob: "CLAUDE.md", category: "rule", root: "workspace" },
17246
- { glob: "CLAUDE.local.md", category: "rule", root: "workspace" },
17247
- { glob: "INSIGHTS.md", category: "rule", root: "workspace" },
17248
- { glob: "AGENTS.md", category: "rule", root: "workspace" },
17249
- { glob: ".claude/rules/**/*.md", category: "rule", root: "workspace" },
17250
- { glob: ".claude/CLAUDE.md", category: "rule", root: "home" },
17251
- { glob: ".claude/INSIGHTS.md", category: "rule", root: "home" },
17252
- { glob: ".claude/rules/**/*.md", category: "rule", root: "home" },
17353
+ { glob: "CLAUDE.md", category: CATEGORY.RULE, root: "workspace" },
17354
+ { glob: "CLAUDE.local.md", category: CATEGORY.RULE, root: "workspace" },
17355
+ { glob: "INSIGHTS.md", category: CATEGORY.RULE, root: "workspace" },
17356
+ { glob: "AGENTS.md", category: CATEGORY.RULE, root: "workspace" },
17357
+ {
17358
+ glob: ".claude/rules/**/*.md",
17359
+ category: CATEGORY.RULE,
17360
+ root: "workspace"
17361
+ },
17362
+ { glob: ".claude/CLAUDE.md", category: CATEGORY.RULE, root: "home" },
17363
+ { glob: ".claude/INSIGHTS.md", category: CATEGORY.RULE, root: "home" },
17364
+ { glob: ".claude/rules/**/*.md", category: CATEGORY.RULE, root: "home" },
17253
17365
  {
17254
17366
  glob: ".claude/projects/*/memory/*.md",
17255
- category: "memory",
17367
+ category: CATEGORY.MEMORY,
17256
17368
  root: "home"
17257
17369
  },
17258
17370
  { kind: "skill-bundle", skillsRoot: ".claude/skills", root: "workspace" },
17259
- { glob: ".claude/commands/*.md", category: "skill", root: "workspace" },
17371
+ {
17372
+ glob: ".claude/commands/*.md",
17373
+ category: CATEGORY.COMMAND,
17374
+ root: "workspace"
17375
+ },
17260
17376
  {
17261
17377
  glob: ".claude/agents/*.md",
17262
- category: SKILL_CATEGORY,
17378
+ category: CATEGORY.SKILL,
17263
17379
  root: "workspace"
17264
17380
  },
17265
17381
  { kind: "skill-bundle", skillsRoot: ".claude/skills", root: "home" },
17266
- { glob: ".claude/commands/*.md", category: "skill", root: "home" },
17267
- { glob: ".claude/agents/*.md", category: SKILL_CATEGORY, root: "home" },
17268
- { glob: ".claude/settings.json", category: "config", root: "workspace" },
17382
+ { glob: ".claude/commands/*.md", category: CATEGORY.COMMAND, root: "home" },
17383
+ { glob: ".claude/agents/*.md", category: CATEGORY.SKILL, root: "home" },
17384
+ {
17385
+ glob: ".claude/settings.json",
17386
+ category: CATEGORY.CONFIG,
17387
+ root: "workspace"
17388
+ },
17269
17389
  {
17270
17390
  glob: ".claude/settings.local.json",
17271
- category: "config",
17391
+ category: CATEGORY.CONFIG,
17272
17392
  root: "workspace"
17273
17393
  },
17274
- { glob: ".mcp.json", category: "mcp-config", root: "workspace" },
17275
- { glob: ".claude/.mcp.json", category: "mcp-config", root: "workspace" },
17276
- { glob: ".claude/settings.json", category: "config", root: "home" },
17277
- { glob: ".claudeignore", category: "ignore", root: "workspace" }
17394
+ { glob: ".mcp.json", category: CATEGORY.MCP_CONFIG, root: "workspace" },
17395
+ {
17396
+ glob: ".claude/.mcp.json",
17397
+ category: CATEGORY.MCP_CONFIG,
17398
+ root: "workspace"
17399
+ },
17400
+ { glob: ".claude/settings.json", category: CATEGORY.CONFIG, root: "home" },
17401
+ { glob: ".claudeignore", category: CATEGORY.IGNORE, root: "workspace" }
17278
17402
  ],
17279
17403
  cursor: [
17280
17404
  // Legacy single-file rules
17281
- { glob: ".cursorrules", category: "rule", root: "workspace" },
17405
+ { glob: ".cursorrules", category: CATEGORY.RULE, root: "workspace" },
17282
17406
  // Project Rules — docs support both `.mdc` and `.md` inside .cursor/rules/
17283
- { glob: ".cursor/rules/**/*.mdc", category: "rule", root: "workspace" },
17284
- { glob: ".cursor/rules/**/*.md", category: "rule", root: "workspace" },
17407
+ {
17408
+ glob: ".cursor/rules/**/*.mdc",
17409
+ category: CATEGORY.RULE,
17410
+ root: "workspace"
17411
+ },
17412
+ {
17413
+ glob: ".cursor/rules/**/*.md",
17414
+ category: CATEGORY.RULE,
17415
+ root: "workspace"
17416
+ },
17285
17417
  // AGENTS.md — Cursor's documented alternative to .cursor/rules/
17286
- { glob: "AGENTS.md", category: "rule", root: "workspace" },
17418
+ { glob: "AGENTS.md", category: CATEGORY.RULE, root: "workspace" },
17287
17419
  // Agent skills — Cursor auto-loads from these dirs plus compat with
17288
17420
  // Claude / Codex / generic .agents/ per Cursor docs.
17289
17421
  { kind: "skill-bundle", skillsRoot: ".cursor/skills", root: "workspace" },
@@ -17291,15 +17423,19 @@ var SCAN_PATHS = {
17291
17423
  { kind: "skill-bundle", skillsRoot: ".claude/skills", root: "workspace" },
17292
17424
  { kind: "skill-bundle", skillsRoot: ".codex/skills", root: "workspace" },
17293
17425
  // MCP — project + global
17294
- { glob: ".cursor/mcp.json", category: "mcp-config", root: "workspace" },
17295
- { glob: ".cursor/mcp.json", category: "mcp-config", root: "home" },
17426
+ {
17427
+ glob: ".cursor/mcp.json",
17428
+ category: CATEGORY.MCP_CONFIG,
17429
+ root: "workspace"
17430
+ },
17431
+ { glob: ".cursor/mcp.json", category: CATEGORY.MCP_CONFIG, root: "home" },
17296
17432
  // Home skills (user-level cross-project skills)
17297
17433
  { kind: "skill-bundle", skillsRoot: ".cursor/skills", root: "home" },
17298
17434
  { kind: "skill-bundle", skillsRoot: ".agents/skills", root: "home" },
17299
17435
  { kind: "skill-bundle", skillsRoot: ".claude/skills", root: "home" },
17300
17436
  { kind: "skill-bundle", skillsRoot: ".codex/skills", root: "home" },
17301
17437
  // Exclusion
17302
- { glob: ".cursorignore", category: "ignore", root: "workspace" }
17438
+ { glob: ".cursorignore", category: CATEGORY.IGNORE, root: "workspace" }
17303
17439
  // Note: Cursor's global "Rules for AI" from Settings UI is stored in
17304
17440
  // Cursor's internal settings DB. The tracer_ext reads it via VS Code API
17305
17441
  // (vscode.workspace.getConfiguration) and includes it as a synthetic entry.
@@ -17308,42 +17444,46 @@ var SCAN_PATHS = {
17308
17444
  // Instructions — workspace
17309
17445
  {
17310
17446
  glob: ".github/copilot-instructions.md",
17311
- category: "rule",
17447
+ category: CATEGORY.RULE,
17312
17448
  root: "workspace"
17313
17449
  },
17314
17450
  {
17315
17451
  glob: ".github/instructions/**/*.instructions.md",
17316
- category: "rule",
17452
+ category: CATEGORY.RULE,
17317
17453
  root: "workspace"
17318
17454
  },
17319
17455
  // AGENTS.md / CLAUDE.md family (Copilot reads these via chat.useAgentsMdFile,
17320
17456
  // chat.useClaudeMdFile for cross-compat with Claude Code / other agents).
17321
- { glob: "AGENTS.md", category: "rule", root: "workspace" },
17322
- { glob: "CLAUDE.md", category: "rule", root: "workspace" },
17323
- { glob: "CLAUDE.local.md", category: "rule", root: "workspace" },
17324
- { glob: ".claude/CLAUDE.md", category: "rule", root: "workspace" },
17325
- { glob: ".claude/rules/**/*.md", category: "rule", root: "workspace" },
17457
+ { glob: "AGENTS.md", category: CATEGORY.RULE, root: "workspace" },
17458
+ { glob: "CLAUDE.md", category: CATEGORY.RULE, root: "workspace" },
17459
+ { glob: "CLAUDE.local.md", category: CATEGORY.RULE, root: "workspace" },
17460
+ { glob: ".claude/CLAUDE.md", category: CATEGORY.RULE, root: "workspace" },
17461
+ {
17462
+ glob: ".claude/rules/**/*.md",
17463
+ category: CATEGORY.RULE,
17464
+ root: "workspace"
17465
+ },
17326
17466
  // Prompts — workspace
17327
17467
  {
17328
17468
  glob: ".github/prompts/*.prompt.md",
17329
- category: SKILL_CATEGORY,
17469
+ category: CATEGORY.PROMPT,
17330
17470
  root: "workspace"
17331
17471
  },
17332
17472
  // Custom agents — `.agent.md` is the current format; `.chatmode.md` is the
17333
17473
  // legacy naming docs recommend renaming. We scan both for transition.
17334
17474
  {
17335
17475
  glob: ".github/agents/*.agent.md",
17336
- category: "agent-config",
17476
+ category: CATEGORY.AGENT_CONFIG,
17337
17477
  root: "workspace"
17338
17478
  },
17339
17479
  {
17340
17480
  glob: ".github/chatmodes/*.chatmode.md",
17341
- category: "agent-config",
17481
+ category: CATEGORY.AGENT_CONFIG,
17342
17482
  root: "workspace"
17343
17483
  },
17344
17484
  {
17345
17485
  glob: ".claude/agents/*.md",
17346
- category: SKILL_CATEGORY,
17486
+ category: CATEGORY.SKILL,
17347
17487
  root: "workspace"
17348
17488
  },
17349
17489
  // Agent skills — Copilot discovers skills in all three roots (VS Code docs:
@@ -17352,30 +17492,38 @@ var SCAN_PATHS = {
17352
17492
  { kind: "skill-bundle", skillsRoot: ".claude/skills", root: "workspace" },
17353
17493
  { kind: "skill-bundle", skillsRoot: ".agents/skills", root: "workspace" },
17354
17494
  // MCP — VS Code Copilot reads MCP servers from .vscode/mcp.json
17355
- { glob: ".vscode/mcp.json", category: "mcp-config", root: "workspace" },
17495
+ {
17496
+ glob: ".vscode/mcp.json",
17497
+ category: CATEGORY.MCP_CONFIG,
17498
+ root: "workspace"
17499
+ },
17356
17500
  // Global — home (JetBrains stores global instructions here)
17357
17501
  {
17358
17502
  glob: ".config/github-copilot/global-copilot-instructions.md",
17359
- category: "rule",
17503
+ category: CATEGORY.RULE,
17360
17504
  root: "home"
17361
17505
  },
17362
17506
  // User-level Copilot customizations (~/.copilot/)
17363
17507
  {
17364
17508
  glob: ".copilot/instructions/**/*.instructions.md",
17365
- category: "rule",
17509
+ category: CATEGORY.RULE,
17510
+ root: "home"
17511
+ },
17512
+ {
17513
+ glob: ".copilot/prompts/*.prompt.md",
17514
+ category: CATEGORY.PROMPT,
17366
17515
  root: "home"
17367
17516
  },
17368
- { glob: ".copilot/prompts/*.prompt.md", category: "skill", root: "home" },
17369
17517
  {
17370
17518
  glob: ".copilot/agents/*.agent.md",
17371
- category: "agent-config",
17519
+ category: CATEGORY.AGENT_CONFIG,
17372
17520
  root: "home"
17373
17521
  },
17374
17522
  { kind: "skill-bundle", skillsRoot: ".copilot/skills", root: "home" },
17375
17523
  // Cross-compat home paths (Copilot reads Claude / generic agent dirs too)
17376
- { glob: ".claude/CLAUDE.md", category: "rule", root: "home" },
17377
- { glob: ".claude/rules/**/*.md", category: "rule", root: "home" },
17378
- { glob: ".claude/agents/*.md", category: SKILL_CATEGORY, root: "home" },
17524
+ { glob: ".claude/CLAUDE.md", category: CATEGORY.RULE, root: "home" },
17525
+ { glob: ".claude/rules/**/*.md", category: CATEGORY.RULE, root: "home" },
17526
+ { glob: ".claude/agents/*.md", category: CATEGORY.SKILL, root: "home" },
17379
17527
  { kind: "skill-bundle", skillsRoot: ".claude/skills", root: "home" },
17380
17528
  { kind: "skill-bundle", skillsRoot: ".agents/skills", root: "home" }
17381
17529
  ]
@@ -17415,7 +17563,7 @@ var COPILOT_CUSTOM_LOCATION_SETTINGS = [
17415
17563
  {
17416
17564
  key: "chat.promptFilesLocations",
17417
17565
  kind: "glob",
17418
- category: "skill",
17566
+ category: "prompt",
17419
17567
  glob: "**/*.prompt.md"
17420
17568
  },
17421
17569
  {
@@ -17546,11 +17694,11 @@ async function readJsoncSettings(settingsPath) {
17546
17694
  putSettingsCache(settingsPath, { mtimeMs, parsed: payload });
17547
17695
  return payload;
17548
17696
  }
17549
- function putSettingsCache(path35, entry) {
17550
- if (!settingsCache.has(path35) && settingsCache.size >= MAX_SETTINGS_CACHE_SIZE) {
17697
+ function putSettingsCache(path37, entry) {
17698
+ if (!settingsCache.has(path37) && settingsCache.size >= MAX_SETTINGS_CACHE_SIZE) {
17551
17699
  settingsCache.delete(settingsCache.keys().next().value);
17552
17700
  }
17553
- settingsCache.set(path35, entry);
17701
+ settingsCache.set(path37, entry);
17554
17702
  }
17555
17703
  async function readCopilotCustomLocations(workspaceRoot) {
17556
17704
  const parsed = await readJsoncSettings(
@@ -17800,7 +17948,7 @@ async function enumerateGlob(pattern, cwd, category, isDynamic) {
17800
17948
  } catch {
17801
17949
  return [];
17802
17950
  }
17803
- return files.map((path35) => ({ path: path35, category }));
17951
+ return files.map((path37) => ({ path: path37, category }));
17804
17952
  }
17805
17953
  async function enumerateSkillBundle(baseDir, skillsRoot) {
17806
17954
  const skillsDir = path14.resolve(baseDir, skillsRoot);
@@ -17964,29 +18112,51 @@ var Metric = {
17964
18112
  CHECK_TRIGGERED: "skill_quarantine.check_triggered",
17965
18113
  /** The env-var kill switch skipped the run. */
17966
18114
  CHECK_DISABLED_ENV: "skill_quarantine.check_disabled_env",
18115
+ /**
18116
+ * T-493 — the per-org opt-in toggle was off for every org the caller
18117
+ * belongs to. Verdict query still ran (useful server-side telemetry);
18118
+ * on-disk enforcement was skipped.
18119
+ */
18120
+ CHECK_DISABLED_ORG: "skill_quarantine.check_disabled_org",
17967
18121
  /** Verdict-query call failed. Fail-open. */
17968
18122
  QUERY_ERROR: "skill_quarantine.query_error",
17969
- /** Count of skills enumerated in this run (histogram-ish). */
18123
+ /** Count of skills enumerated in this run. */
17970
18124
  SKILLS_CHECKED: "skill_quarantine.skills_checked",
17971
- /** A skill was freshly quarantined. Tagged with shape. */
18125
+ /** A skill was freshly quarantined. */
17972
18126
  QUARANTINED: "skill_quarantine.quarantined",
17973
- /** Presence check hit; skill already quarantined. */
18127
+ /** Presence check hit (`<md5>.zip` exists); skill already quarantined. */
17974
18128
  ALREADY_QUARANTINED: "skill_quarantine.already_quarantined",
17975
- /** Move step failed. Tagged with phase (stage | publish). */
17976
- MOVE_ERROR: "skill_quarantine.move_error",
17977
- /** Stub creation failed after the move succeeded. */
18129
+ /** Zip build or partial→tmp rename failed (phase 1). */
18130
+ ZIP_ERROR: "skill_quarantine.zip_error",
18131
+ /** Stub write failed (phase 2); tmp preserved for reconcile. */
17978
18132
  STUB_ERROR: "skill_quarantine.stub_error",
17979
- /** A stale staging dir was swept. */
17980
- ORPHAN_SWEPT: "skill_quarantine.orphan_swept",
18133
+ /** Tmp→published rename failed (phase 3); reconcile will retry. */
18134
+ PUBLISH_ERROR: "skill_quarantine.publish_error",
18135
+ /** Reconcile published a leftover tmp whose `<md5>.zip` was missing. */
18136
+ RECONCILED: "skill_quarantine.reconciled",
18137
+ /** Tmp removed because `<md5>.zip` already existed. */
18138
+ SWEPT_REDUNDANT_TMP: "skill_quarantine.swept_redundant_tmp",
18139
+ /** Stale partial zip swept (older than grace window). */
18140
+ SWEPT_PARTIAL: "skill_quarantine.swept_partial",
17981
18141
  /** Total run duration including I/O. */
17982
- DURATION_MS: "skill_quarantine.duration_ms"
18142
+ DURATION_MS: "skill_quarantine.duration_ms",
18143
+ /**
18144
+ * T-492 — Stub-md5 sentinel published successfully. The next heartbeat's
18145
+ * presence check will short-circuit re-quarantining our own stub.
18146
+ */
18147
+ STUB_PREREGISTERED: "skill_quarantine.stub_preregistered",
18148
+ /**
18149
+ * T-492 — Stub-md5 sentinel write failed. Layer 1 (the scanner LLM
18150
+ * recognising stubs) still protects against the loop; this layer is
18151
+ * defense in depth.
18152
+ */
18153
+ STUB_PREREGISTER_ERROR: "skill_quarantine.stub_preregister_error"
17983
18154
  };
17984
18155
 
17985
18156
  // src/features/analysis/skill_quarantine/quarantineSkill.ts
17986
18157
  import { randomUUID } from "crypto";
17987
- import { existsSync as existsSync2 } from "fs";
17988
18158
  import {
17989
- lstat as lstat2,
18159
+ access,
17990
18160
  mkdir,
17991
18161
  readdir as readdir2,
17992
18162
  readFile as readFile2,
@@ -17996,8 +18166,8 @@ import {
17996
18166
  unlink,
17997
18167
  writeFile
17998
18168
  } from "fs/promises";
17999
- import path17 from "path";
18000
- import { move } from "fs-extra";
18169
+ import path18 from "path";
18170
+ import AdmZip4 from "adm-zip";
18001
18171
 
18002
18172
  // src/features/analysis/skill_quarantine/paths.ts
18003
18173
  import { homedir as homedir3 } from "os";
@@ -18005,27 +18175,29 @@ import path16 from "path";
18005
18175
  function getQuarantineRoot() {
18006
18176
  return path16.join(homedir3(), ".tracy", "quarantine", "claude", "skills");
18007
18177
  }
18008
- function getQuarantinedHashDir(md5) {
18009
- return path16.join(getQuarantineRoot(), md5);
18010
- }
18011
- function getQuarantinedTargetPath(md5, origName) {
18012
- return path16.join(getQuarantinedHashDir(md5), origName);
18178
+ function getQuarantineZipPath(md5) {
18179
+ return path16.join(getQuarantineRoot(), `${md5}.zip`);
18013
18180
  }
18014
- function getStagingDir(md5, pid, uuid) {
18015
- return path16.join(getQuarantineRoot(), `${md5}_tmp_${pid}_${uuid}`);
18181
+ function getTmpZipPath(md5, uuid) {
18182
+ return path16.join(getQuarantineRoot(), `${md5}_tmp_${uuid}.zip`);
18016
18183
  }
18017
- var STAGING_DIR_REGEX = /^([0-9a-f]{32})_tmp_/;
18184
+ var TMP_ZIP_REGEX = /^([0-9a-f]{32})_tmp_[0-9a-f-]+\.zip$/;
18185
+ var COMMITTED_ZIP_REGEX = /^([0-9a-f]{32})\.zip$/;
18018
18186
 
18019
18187
  // src/features/analysis/skill_quarantine/stubTemplate.ts
18188
+ import path17 from "path";
18189
+ import { quote } from "shell-quote";
18020
18190
  var LEGACY_SUMMARY_FALLBACK = "not available (scan predates current schema)";
18021
18191
  function renderStub(params) {
18022
18192
  const folderOrFile = params.isFolder ? "skill folder" : "skill file";
18023
18193
  const reason = params.summary ?? LEGACY_SUMMARY_FALLBACK;
18024
- return `# \u26D4 QUARANTINED BY TRACY
18194
+ const extractParent = path17.dirname(params.origPath);
18195
+ const recoverCommand = `rm -rf ${quote([params.origPath])} && unzip -o ${quote([params.quarantinedZipPath])} -d ${quote([extractParent])}`;
18196
+ return `# ${STUB_MARKER}
18025
18197
 
18026
18198
  This skill was flagged **MALICIOUS** by the Mobb security scanner and has been
18027
- moved out of your skills folder. **Claude Code will not execute it** while this
18028
- stub is in place.
18199
+ archived out of your skills folder. **Claude Code will not execute it** while
18200
+ this stub is in place.
18029
18201
 
18030
18202
  ## Why this skill was flagged
18031
18203
 
@@ -18036,23 +18208,22 @@ stub is in place.
18036
18208
 
18037
18209
  ## Where the original is now
18038
18210
 
18039
- The original ${folderOrFile} has been moved to:
18211
+ The original ${folderOrFile} has been archived to:
18040
18212
 
18041
- ${params.quarantinedPath}
18213
+ ${params.quarantinedZipPath}
18042
18214
 
18043
- Nothing has been deleted. The contents are intact; only the location changed.
18215
+ Nothing has been deleted. The archive preserves the skill exactly as it was,
18216
+ including any secrets or local-only edits.
18044
18217
 
18045
18218
  ## If this is a false positive \u2014 how to recover
18046
18219
 
18047
18220
  If you're confident this skill is safe and want to restore it:
18048
18221
 
18049
- mv ${params.quarantinedPath} ${params.origPath}
18222
+ ${recoverCommand}
18050
18223
 
18051
- Tracy will not re-quarantine it as long as the directory
18052
- \`~/.tracy/quarantine/claude/skills/${params.md5}/\` still exists on your
18053
- machine (even if it's empty after you moved the contents out). If you delete
18054
- that directory entirely, the next heartbeat will re-evaluate the skill from
18055
- scratch.
18224
+ Tracy will not re-quarantine it as long as \`${params.md5}.zip\` remains in
18225
+ the quarantine folder. If you delete the archive, the next heartbeat will
18226
+ re-evaluate the skill from scratch.
18056
18227
 
18057
18228
  ## How to report a false positive
18058
18229
 
@@ -18069,130 +18240,56 @@ Your report helps tune the scanner for everyone.
18069
18240
  // src/features/analysis/skill_quarantine/quarantineSkill.ts
18070
18241
  async function quarantineSkill(params) {
18071
18242
  const { skillPath, isFolder, md5, origName, verdict, log: log2 } = params;
18072
- const hashDir = getQuarantinedHashDir(md5);
18073
- if (existsSync2(hashDir)) {
18243
+ const finalZip = getQuarantineZipPath(md5);
18244
+ if (await exists(finalZip)) {
18074
18245
  log2.debug(
18075
18246
  { md5, metric: Metric.ALREADY_QUARANTINED },
18076
- "skill_quarantine: already quarantined, skipping"
18247
+ "skill_quarantine: already quarantined"
18077
18248
  );
18078
18249
  return { status: "already_quarantined" };
18079
18250
  }
18080
- let isSymlink = false;
18251
+ const tmpZip = getTmpZipPath(md5, randomUUID());
18081
18252
  try {
18082
- const lst = await lstat2(skillPath);
18083
- isSymlink = lst.isSymbolicLink();
18084
- } catch {
18085
- }
18086
- if (isSymlink) {
18087
- const stubContent2 = renderStub({
18088
- md5,
18089
- isFolder,
18090
- // Symlinks have no quarantine archive; note that in the stub.
18091
- quarantinedPath: `(symlink at ${skillPath} replaced \u2014 original content was not moved)`,
18092
- origPath: skillPath,
18093
- summary: verdict.summary,
18094
- scannerName: verdict.scannerName,
18095
- scannerVersion: verdict.scannerVersion,
18096
- scannedAt: verdict.scannedAt
18097
- });
18098
- try {
18099
- await mkdir(hashDir, { recursive: true });
18100
- if (isFolder) {
18101
- const tmpDir = `${skillPath}.__tracy_tmp__`;
18102
- await mkdir(tmpDir, { recursive: true });
18103
- await writeFile(path17.join(tmpDir, "SKILL.md"), stubContent2, "utf8");
18104
- await unlink(skillPath);
18105
- await rename(tmpDir, skillPath);
18106
- } else {
18107
- const tmpFile = `${skillPath}.__tracy_tmp__`;
18108
- await writeFile(tmpFile, stubContent2, "utf8");
18109
- await unlink(skillPath);
18110
- await rename(tmpFile, skillPath);
18111
- }
18112
- } catch (err) {
18113
- log2.error(
18114
- { err, md5, skillPath, metric: Metric.STUB_ERROR },
18115
- "skill_quarantine: symlink stub write failed"
18116
- );
18117
- return { status: "stub_error", err };
18253
+ await mkdir(getQuarantineRoot(), { recursive: true });
18254
+ const zip = new AdmZip4();
18255
+ if (isFolder) {
18256
+ await addFolderAsync(zip, skillPath, origName);
18257
+ } else {
18258
+ zip.addFile(origName, await readFile2(skillPath));
18118
18259
  }
18119
- await preRegisterStubMd5(skillPath, isFolder, log2);
18120
- log2.info(
18121
- {
18122
- md5,
18123
- verdict: verdict.verdict,
18124
- shape: isFolder ? "folder" : "standalone",
18125
- scanner: verdict.scannerName,
18126
- scannerVersion: verdict.scannerVersion,
18127
- metric: Metric.QUARANTINED
18128
- },
18129
- "skill_quarantine: quarantined (symlink)"
18130
- );
18131
- return { status: "quarantined" };
18132
- }
18133
- const stagingDir = getStagingDir(md5, process.pid, randomUUID());
18134
- const stagingTarget = path17.join(stagingDir, origName);
18135
- const finalTarget = getQuarantinedTargetPath(md5, origName);
18136
- try {
18137
- await mkdir(stagingDir, { recursive: true });
18260
+ await writeFile(tmpZip, zip.toBuffer());
18138
18261
  } catch (err) {
18262
+ await unlink(tmpZip).catch(ignoreErr);
18139
18263
  log2.error(
18140
- { err, md5, metric: Metric.MOVE_ERROR, phase: "stage" },
18141
- "skill_quarantine: failed to create staging dir"
18264
+ { err, md5, metric: Metric.ZIP_ERROR },
18265
+ "skill_quarantine: phase-1 zip write failed"
18142
18266
  );
18143
- return { status: "move_error", phase: "stage", err };
18267
+ return { status: "zip_error", err };
18144
18268
  }
18145
18269
  try {
18146
- await move(skillPath, stagingTarget);
18270
+ await writeStub(params);
18147
18271
  } catch (err) {
18148
- await tryRm(stagingDir);
18149
18272
  log2.error(
18150
- { err, md5, metric: Metric.MOVE_ERROR, phase: "stage" },
18151
- "skill_quarantine: phase-1 move failed"
18273
+ { err, md5, skillPath, metric: Metric.STUB_ERROR },
18274
+ "skill_quarantine: stub write failed; tmp zip preserved for reconcile"
18152
18275
  );
18153
- return { status: "move_error", phase: "stage", err };
18276
+ return { status: "stub_error", err };
18154
18277
  }
18155
18278
  try {
18156
- await rename(stagingDir, hashDir);
18279
+ await rename(tmpZip, finalZip);
18157
18280
  } catch (err) {
18158
18281
  log2.error(
18159
- {
18160
- err,
18161
- md5,
18162
- stagingDir,
18163
- metric: Metric.MOVE_ERROR,
18164
- phase: "publish"
18165
- },
18166
- "skill_quarantine: phase-2 publish failed; staging dir preserved for manual recovery"
18282
+ { err, md5, tmpZip, metric: Metric.PUBLISH_ERROR },
18283
+ "skill_quarantine: phase-3 publish failed; reconcile will retry"
18167
18284
  );
18168
- return { status: "move_error", phase: "publish", err };
18285
+ return { status: "publish_error", err };
18169
18286
  }
18170
- const quarantinedPath = finalTarget;
18171
- const stubContent = renderStub({
18172
- md5,
18173
- isFolder,
18174
- quarantinedPath,
18175
- origPath: skillPath,
18176
- summary: verdict.summary,
18177
- scannerName: verdict.scannerName,
18178
- scannerVersion: verdict.scannerVersion,
18179
- scannedAt: verdict.scannedAt
18180
- });
18181
- try {
18182
- if (isFolder) {
18183
- await mkdir(skillPath, { recursive: true });
18184
- await writeFile(path17.join(skillPath, "SKILL.md"), stubContent, "utf8");
18185
- } else {
18186
- await writeFile(skillPath, stubContent, "utf8");
18187
- }
18188
- } catch (err) {
18189
- log2.error(
18190
- { err, md5, skillPath, metric: Metric.STUB_ERROR },
18191
- "skill_quarantine: stub write failed; quarantine is still in place"
18287
+ await preregisterStubMd5(params).catch((err) => {
18288
+ log2.warn(
18289
+ { err, md5, metric: Metric.STUB_PREREGISTER_ERROR },
18290
+ "skill_quarantine: stub-md5 pre-registration failed; LLM-side recognition remains"
18192
18291
  );
18193
- return { status: "stub_error", err };
18194
- }
18195
- await preRegisterStubMd5(skillPath, isFolder, log2);
18292
+ });
18196
18293
  log2.info(
18197
18294
  {
18198
18295
  md5,
@@ -18206,98 +18303,169 @@ async function quarantineSkill(params) {
18206
18303
  );
18207
18304
  return { status: "quarantined" };
18208
18305
  }
18209
- async function preRegisterStubMd5(skillPath, isFolder, log2) {
18306
+ async function preregisterStubMd5(params) {
18307
+ const { skillPath, isFolder, origName, log: log2 } = params;
18308
+ const stubFilePath = isFolder ? path18.join(skillPath, "SKILL.md") : skillPath;
18309
+ const stubEntryName = isFolder ? `${origName}/SKILL.md` : origName;
18310
+ const content = await readFile2(stubFilePath, "utf-8");
18311
+ const fileStat = await stat2(stubFilePath);
18312
+ const stubFile = {
18313
+ name: stubEntryName,
18314
+ path: stubFilePath,
18315
+ content,
18316
+ sizeBytes: fileStat.size,
18317
+ category: "skill",
18318
+ mtimeMs: fileStat.mtimeMs
18319
+ };
18320
+ const stubGroup = {
18321
+ name: isFolder ? origName : path18.basename(skillPath, path18.extname(skillPath)),
18322
+ root: "workspace",
18323
+ // unused by md5 computation
18324
+ skillPath,
18325
+ files: [stubFile],
18326
+ isFolder,
18327
+ maxMtimeMs: stubFile.mtimeMs,
18328
+ sessionKey: `stub-preregister:${skillPath}`
18329
+ };
18330
+ const { skills } = await processContextFiles([], [stubGroup]);
18331
+ const processed = skills[0];
18332
+ if (!processed) {
18333
+ return;
18334
+ }
18335
+ const { md5: stubMd5, zipBuffer } = processed;
18336
+ const sentinelPath = getQuarantineZipPath(stubMd5);
18337
+ if (await exists(sentinelPath)) {
18338
+ return;
18339
+ }
18340
+ const tmpSentinel = getTmpZipPath(stubMd5, randomUUID());
18210
18341
  try {
18211
- const stubEntries = await gatherStubEntries(skillPath, isFolder);
18212
- const stubGroup = {
18213
- name: path17.basename(skillPath).replace(/\.md$/i, ""),
18214
- root: "workspace",
18215
- skillPath,
18216
- files: stubEntries,
18217
- isFolder,
18218
- maxMtimeMs: Date.now(),
18219
- sessionKey: `quarantine-stub:${skillPath}`
18220
- };
18221
- const { skills } = await processContextFiles([], [stubGroup]);
18222
- if (skills.length === 0) return;
18223
- const stubMd5 = skills[0].md5;
18224
- await mkdir(getQuarantinedHashDir(stubMd5), { recursive: true });
18342
+ await writeFile(tmpSentinel, zipBuffer);
18343
+ await rename(tmpSentinel, sentinelPath);
18225
18344
  } catch (err) {
18226
- log2.warn(
18227
- { err, skillPath },
18228
- "skill_quarantine: failed to pre-register stub md5"
18229
- );
18345
+ await unlink(tmpSentinel).catch(ignoreErr);
18346
+ throw err;
18230
18347
  }
18348
+ log2.info(
18349
+ { stubMd5, metric: Metric.STUB_PREREGISTERED },
18350
+ "skill_quarantine: stub md5 pre-registered"
18351
+ );
18231
18352
  }
18232
- async function gatherStubEntries(skillPath, isFolder) {
18233
- const now = Date.now();
18234
- const target = isFolder ? path17.join(skillPath, "SKILL.md") : skillPath;
18235
- const [st, content] = await Promise.all([
18236
- stat2(target),
18237
- readFile2(target, "utf8")
18238
- ]);
18239
- return [
18240
- {
18241
- name: isFolder ? "SKILL.md" : path17.basename(skillPath),
18242
- path: target,
18243
- content,
18244
- sizeBytes: st.size,
18245
- category: "skill",
18246
- mtimeMs: now
18247
- }
18248
- ];
18353
+ async function writeStub(params) {
18354
+ const { skillPath, isFolder, md5, verdict } = params;
18355
+ const stubContent = renderStub({
18356
+ md5,
18357
+ isFolder,
18358
+ quarantinedZipPath: getQuarantineZipPath(md5),
18359
+ origPath: skillPath,
18360
+ summary: verdict.summary,
18361
+ scannerName: verdict.scannerName,
18362
+ scannerVersion: verdict.scannerVersion,
18363
+ scannedAt: verdict.scannedAt
18364
+ });
18365
+ if (isFolder) {
18366
+ await rm(skillPath, { recursive: true, force: true });
18367
+ await mkdir(skillPath, { recursive: true });
18368
+ await writeFile(path18.join(skillPath, "SKILL.md"), stubContent, "utf8");
18369
+ } else {
18370
+ await writeFile(skillPath, stubContent, "utf8");
18371
+ }
18249
18372
  }
18250
- async function sweepOrphanStagingDirs(log2) {
18373
+ async function reconcileAndSweep(log2) {
18251
18374
  const root = getQuarantineRoot();
18252
18375
  let entries;
18253
18376
  try {
18254
18377
  entries = await readdir2(root);
18255
18378
  } catch (err) {
18256
- if (err.code === "ENOENT") return 0;
18257
- log2.warn({ err, root }, "skill_quarantine: orphan sweep readdir failed");
18258
- return 0;
18379
+ if (err.code === "ENOENT") return;
18380
+ log2.warn({ err, root }, "skill_quarantine: reconcile readdir failed");
18381
+ return;
18259
18382
  }
18383
+ const committed = new Set(
18384
+ entries.map((e) => COMMITTED_ZIP_REGEX.exec(e)?.[1]).filter((m) => m !== void 0)
18385
+ );
18260
18386
  const now = Date.now();
18261
- let swept = 0;
18262
18387
  for (const entry of entries) {
18263
- if (!STAGING_DIR_REGEX.test(entry)) continue;
18264
- const full = path17.join(root, entry);
18265
- let mtimeMs;
18266
- try {
18267
- mtimeMs = (await stat2(full)).mtimeMs;
18268
- } catch {
18388
+ const md5 = TMP_ZIP_REGEX.exec(entry)?.[1];
18389
+ if (md5 === void 0) continue;
18390
+ const full = path18.join(root, entry);
18391
+ if (committed.has(md5)) {
18392
+ await unlink(full).catch(
18393
+ (err) => log2.warn(
18394
+ { err, path: full, md5 },
18395
+ "skill_quarantine: redundant tmp unlink failed"
18396
+ )
18397
+ );
18398
+ log2.info(
18399
+ { path: full, md5, metric: Metric.SWEPT_REDUNDANT_TMP },
18400
+ "skill_quarantine: swept redundant tmp"
18401
+ );
18269
18402
  continue;
18270
18403
  }
18271
- if (now - mtimeMs < ORPHAN_SWEEP_GRACE_MS) continue;
18404
+ let valid;
18272
18405
  try {
18273
- await rm(full, { recursive: true, force: true });
18274
- swept += 1;
18275
- log2.info(
18276
- { path: full, metric: Metric.ORPHAN_SWEPT },
18277
- "skill_quarantine: orphan swept"
18278
- );
18279
- } catch (err) {
18280
- log2.warn({ err, path: full }, "skill_quarantine: orphan sweep rm failed");
18406
+ new AdmZip4(full).getEntries();
18407
+ valid = true;
18408
+ } catch {
18409
+ valid = false;
18281
18410
  }
18411
+ if (valid) {
18412
+ try {
18413
+ await rename(full, getQuarantineZipPath(md5));
18414
+ committed.add(md5);
18415
+ log2.info(
18416
+ { path: full, md5, metric: Metric.RECONCILED },
18417
+ "skill_quarantine: reconciled tmp \u2192 published"
18418
+ );
18419
+ } catch (err) {
18420
+ log2.warn(
18421
+ { err, path: full, md5 },
18422
+ "skill_quarantine: reconcile rename failed"
18423
+ );
18424
+ }
18425
+ continue;
18426
+ }
18427
+ const { mtimeMs } = await stat2(full).catch(() => ({ mtimeMs: now }));
18428
+ if (now - mtimeMs < PARTIAL_SWEEP_GRACE_MS) continue;
18429
+ await unlink(full).catch(
18430
+ (err) => log2.warn({ err, path: full }, "skill_quarantine: partial unlink failed")
18431
+ );
18432
+ log2.info(
18433
+ { path: full, metric: Metric.SWEPT_PARTIAL },
18434
+ "skill_quarantine: swept broken tmp (partial write)"
18435
+ );
18282
18436
  }
18283
- return swept;
18284
18437
  }
18285
- async function tryRm(p) {
18286
- try {
18287
- await rm(p, { recursive: true, force: true });
18288
- } catch {
18438
+ function exists(p) {
18439
+ return access(p).then(
18440
+ () => true,
18441
+ () => false
18442
+ );
18443
+ }
18444
+ function ignoreErr() {
18445
+ }
18446
+ async function addFolderAsync(zip, root, prefix) {
18447
+ async function walk(dir, relPrefix) {
18448
+ const entries = await readdir2(dir, { withFileTypes: true });
18449
+ for (const e of entries) {
18450
+ const full = path18.join(dir, e.name);
18451
+ const rel = path18.posix.join(relPrefix, e.name);
18452
+ if (e.isDirectory()) await walk(full, rel);
18453
+ else if (e.isFile()) zip.addFile(rel, await readFile2(full));
18454
+ }
18289
18455
  }
18456
+ await walk(root, prefix);
18290
18457
  }
18291
18458
 
18292
18459
  // src/features/analysis/skill_quarantine/queryVerdicts.ts
18293
18460
  async function queryVerdicts(gqlClient, md5s, log2) {
18294
18461
  if (md5s.length === 0) {
18295
- return /* @__PURE__ */ new Map();
18462
+ return { verdicts: /* @__PURE__ */ new Map(), quarantineEnabled: false };
18296
18463
  }
18297
18464
  try {
18298
18465
  const res = await gqlClient.skillVerdictsByMd5(md5s);
18466
+ const envelope = res.skillVerdictsByMd5;
18299
18467
  const out = /* @__PURE__ */ new Map();
18300
- for (const row of res.skillVerdictsByMd5) {
18468
+ for (const row of envelope.verdicts) {
18301
18469
  out.set(row.md5, {
18302
18470
  md5: row.md5,
18303
18471
  verdict: row.verdict,
@@ -18307,18 +18475,41 @@ async function queryVerdicts(gqlClient, md5s, log2) {
18307
18475
  scannedAt: row.scannedAt
18308
18476
  });
18309
18477
  }
18310
- return out;
18478
+ return { verdicts: out, quarantineEnabled: envelope.quarantineEnabled };
18311
18479
  } catch (err) {
18312
18480
  log2.warn(
18313
18481
  { err, md5_count: md5s.length, metric: "skill_quarantine.query_error" },
18314
18482
  "skill_quarantine: verdict query failed, failing open"
18315
18483
  );
18316
- return /* @__PURE__ */ new Map();
18484
+ return { verdicts: /* @__PURE__ */ new Map(), quarantineEnabled: false };
18317
18485
  }
18318
18486
  }
18319
18487
 
18320
18488
  // src/features/analysis/skill_quarantine/runQuarantineCheck.ts
18489
+ var SKILL_PARENT_DIRS = [
18490
+ ".claude/skills",
18491
+ ".claude/commands",
18492
+ ".claude/agents"
18493
+ ];
18494
+ async function getSkillDirsMtimeMs(cwd) {
18495
+ const home = homedir4();
18496
+ const dirs = SKILL_PARENT_DIRS.flatMap((d) => [
18497
+ path19.join(cwd, d),
18498
+ path19.join(home, d)
18499
+ ]);
18500
+ let max = 0;
18501
+ for (const dir of dirs) {
18502
+ try {
18503
+ const s = await stat3(dir);
18504
+ if (s.mtimeMs > max) max = s.mtimeMs;
18505
+ } catch {
18506
+ }
18507
+ }
18508
+ return max;
18509
+ }
18321
18510
  var lastRunAt = /* @__PURE__ */ new Map();
18511
+ var lastDirsMtimeMs = /* @__PURE__ */ new Map();
18512
+ var seenSkillMd5s = /* @__PURE__ */ new Set();
18322
18513
  var killSwitchLogged = false;
18323
18514
  async function runQuarantineCheckIfNeeded(opts) {
18324
18515
  const { sessionId, cwd, gqlClient, log: log2 } = opts;
@@ -18334,18 +18525,25 @@ async function runQuarantineCheckIfNeeded(opts) {
18334
18525
  }
18335
18526
  const now = Date.now();
18336
18527
  const prev = lastRunAt.get(sessionId);
18337
- if (prev !== void 0 && now - prev < HEARTBEAT_DEBOUNCE_MS) {
18528
+ const withinDebounce = prev !== void 0 && now - prev < HEARTBEAT_DEBOUNCE_MS;
18529
+ lastRunAt.set(sessionId, now);
18530
+ const dirsMtime = await getSkillDirsMtimeMs(cwd);
18531
+ if (withinDebounce && dirsMtime <= (lastDirsMtimeMs.get(sessionId) ?? 0)) {
18338
18532
  return;
18339
18533
  }
18340
- lastRunAt.set(sessionId, now);
18534
+ const installed = await enumerateInstalledSkills(cwd);
18535
+ const hasNewSkills = installed.some((s) => !seenSkillMd5s.has(s.md5));
18536
+ if (!hasNewSkills && withinDebounce) {
18537
+ return;
18538
+ }
18539
+ lastDirsMtimeMs.set(sessionId, dirsMtime);
18341
18540
  log2.info(
18342
- { sessionId, metric: Metric.CHECK_TRIGGERED },
18541
+ { sessionId, metric: Metric.CHECK_TRIGGERED, hasNewSkills },
18343
18542
  "skill_quarantine: check start"
18344
18543
  );
18345
18544
  const t0 = Date.now();
18346
18545
  try {
18347
- await sweepOrphanStagingDirs(log2);
18348
- const installed = await enumerateInstalledSkills(cwd);
18546
+ await reconcileAndSweep(log2);
18349
18547
  log2.info(
18350
18548
  { sessionId, count: installed.length, metric: Metric.SKILLS_CHECKED },
18351
18549
  "skill_quarantine: skills enumerated"
@@ -18353,11 +18551,25 @@ async function runQuarantineCheckIfNeeded(opts) {
18353
18551
  if (installed.length === 0) {
18354
18552
  return;
18355
18553
  }
18356
- const verdicts = await queryVerdicts(
18554
+ const currentMd5s = new Set(installed.map((s) => s.md5));
18555
+ for (const md5 of seenSkillMd5s) {
18556
+ if (!currentMd5s.has(md5)) seenSkillMd5s.delete(md5);
18557
+ }
18558
+ for (const skill of installed) {
18559
+ seenSkillMd5s.add(skill.md5);
18560
+ }
18561
+ const { verdicts, quarantineEnabled } = await queryVerdicts(
18357
18562
  gqlClient,
18358
18563
  installed.map((s) => s.md5),
18359
18564
  log2
18360
18565
  );
18566
+ if (!quarantineEnabled) {
18567
+ log2.info(
18568
+ { sessionId, metric: Metric.CHECK_DISABLED_ORG },
18569
+ "skill_quarantine: opt-in not enabled for any org of caller; skipping enforcement"
18570
+ );
18571
+ return;
18572
+ }
18361
18573
  for (const skill of installed) {
18362
18574
  const verdict = verdicts.get(skill.md5);
18363
18575
  if (!verdict || verdict.verdict !== MALICIOUS_VERDICT) {
@@ -18394,7 +18606,7 @@ async function runQuarantineCheckIfNeeded(opts) {
18394
18606
  // src/features/claude_code/daemon_pid_file.ts
18395
18607
  import fs13 from "fs";
18396
18608
  import os4 from "os";
18397
- import path18 from "path";
18609
+ import path20 from "path";
18398
18610
 
18399
18611
  // src/features/claude_code/data_collector_constants.ts
18400
18612
  var CC_VERSION_CACHE_KEY = "claudeCode.detectedCCVersion";
@@ -18415,17 +18627,17 @@ var CONTEXT_SCAN_INTERVAL_MS = 5e3;
18415
18627
 
18416
18628
  // src/features/claude_code/daemon_pid_file.ts
18417
18629
  function getMobbdevDir() {
18418
- return path18.join(os4.homedir(), ".mobbdev");
18630
+ return path20.join(os4.homedir(), ".mobbdev");
18419
18631
  }
18420
18632
  function getDaemonCheckScriptPath() {
18421
- return path18.join(getMobbdevDir(), "daemon-check.js");
18633
+ return path20.join(getMobbdevDir(), "daemon-check.js");
18422
18634
  }
18423
18635
  var DaemonPidFile = class {
18424
18636
  constructor() {
18425
18637
  __publicField(this, "data", null);
18426
18638
  }
18427
18639
  get filePath() {
18428
- return path18.join(getMobbdevDir(), "daemon.pid");
18640
+ return path20.join(getMobbdevDir(), "daemon.pid");
18429
18641
  }
18430
18642
  /** Ensure ~/.mobbdev/ directory exists. */
18431
18643
  ensureDir() {
@@ -18487,8 +18699,8 @@ var DaemonPidFile = class {
18487
18699
  // src/features/claude_code/data_collector.ts
18488
18700
  import { execFile } from "child_process";
18489
18701
  import { createHash as createHash3 } from "crypto";
18490
- import { access, open as open4, readdir as readdir3, readFile as readFile3, unlink as unlink2 } from "fs/promises";
18491
- import path19 from "path";
18702
+ import { access as access2, open as open4, readdir as readdir3, readFile as readFile3, unlink as unlink2 } from "fs/promises";
18703
+ import path21 from "path";
18492
18704
  import { promisify } from "util";
18493
18705
 
18494
18706
  // src/features/analysis/context_file_uploader.ts
@@ -18753,8 +18965,8 @@ function createConfigstoreStream(store, opts) {
18753
18965
  heartbeatBuffer.length = 0;
18754
18966
  }
18755
18967
  }
18756
- function setScopePath(path35) {
18757
- scopePath = path35;
18968
+ function setScopePath(path37) {
18969
+ scopePath = path37;
18758
18970
  }
18759
18971
  return { writable, flush, setScopePath };
18760
18972
  }
@@ -18978,18 +19190,24 @@ function createLogger(config2) {
18978
19190
 
18979
19191
  // src/features/claude_code/hook_logger.ts
18980
19192
  var DD_RUM_TOKEN = true ? "pubf59c0182545bfb4c299175119f1abf9b" : "";
18981
- var CLI_VERSION = true ? "1.4.1" : "unknown";
19193
+ var CLI_VERSION = true ? "1.4.7" : "unknown";
18982
19194
  var NAMESPACE = "mobbdev-claude-code-hook-logs";
18983
19195
  var claudeCodeVersion;
18984
19196
  function buildDdTags() {
18985
- const tags = [`version:${CLI_VERSION}`];
19197
+ const tags = [
19198
+ `version:${CLI_VERSION}`,
19199
+ `platform:claude_code`,
19200
+ `os:${process.platform}`,
19201
+ `arch:${process.arch}`
19202
+ ];
18986
19203
  if (claudeCodeVersion) {
18987
19204
  tags.push(`cc_version:${claudeCodeVersion}`);
18988
19205
  }
18989
19206
  return tags.join(",");
18990
19207
  }
19208
+ var handlingDdError = false;
18991
19209
  function createHookLogger(opts) {
18992
- return createLogger({
19210
+ const created = createLogger({
18993
19211
  namespace: NAMESPACE,
18994
19212
  scopePath: opts?.scopePath,
18995
19213
  enableConfigstore: opts?.enableConfigstore,
@@ -18999,9 +19217,26 @@ function createHookLogger(opts) {
18999
19217
  service: "mobbdev-cli-hook",
19000
19218
  ddtags: buildDdTags(),
19001
19219
  hostnameMode: "hashed",
19002
- unrefTimer: true
19220
+ unrefTimer: true,
19221
+ onError: (error) => {
19222
+ if (handlingDdError) {
19223
+ process.stderr.write(`dd-ship-error: ${String(error)}
19224
+ `);
19225
+ return;
19226
+ }
19227
+ handlingDdError = true;
19228
+ try {
19229
+ created.warn(
19230
+ { err: String(error), source: "dd-log-shipping" },
19231
+ "Datadog log shipping failed"
19232
+ );
19233
+ } finally {
19234
+ handlingDdError = false;
19235
+ }
19236
+ }
19003
19237
  }
19004
19238
  });
19239
+ return created;
19005
19240
  }
19006
19241
  var logger = createHookLogger();
19007
19242
  var activeScopedLoggers = [];
@@ -19031,6 +19266,9 @@ function createScopedHookLog(scopePath, opts) {
19031
19266
  activeScopedLoggers.push(scoped);
19032
19267
  return scoped;
19033
19268
  }
19269
+ function getScopedLoggerCount() {
19270
+ return scopedLoggerCache.size;
19271
+ }
19034
19272
 
19035
19273
  // src/features/claude_code/data_collector.ts
19036
19274
  var execFileAsync = promisify(execFile);
@@ -19065,18 +19303,18 @@ function getCursorKey(transcriptPath) {
19065
19303
  }
19066
19304
  async function resolveTranscriptPath(transcriptPath, sessionId) {
19067
19305
  try {
19068
- await access(transcriptPath);
19306
+ await access2(transcriptPath);
19069
19307
  return transcriptPath;
19070
19308
  } catch {
19071
19309
  }
19072
- const filename = path19.basename(transcriptPath);
19073
- const dirName = path19.basename(path19.dirname(transcriptPath));
19074
- const projectsDir = path19.dirname(path19.dirname(transcriptPath));
19310
+ const filename = path21.basename(transcriptPath);
19311
+ const dirName = path21.basename(path21.dirname(transcriptPath));
19312
+ const projectsDir = path21.dirname(path21.dirname(transcriptPath));
19075
19313
  const baseDirName = dirName.replace(/[-.]claude-worktrees-.+$/, "");
19076
19314
  if (baseDirName !== dirName) {
19077
- const candidate = path19.join(projectsDir, baseDirName, filename);
19315
+ const candidate = path21.join(projectsDir, baseDirName, filename);
19078
19316
  try {
19079
- await access(candidate);
19317
+ await access2(candidate);
19080
19318
  hookLog.info(
19081
19319
  {
19082
19320
  data: {
@@ -19096,9 +19334,9 @@ async function resolveTranscriptPath(transcriptPath, sessionId) {
19096
19334
  const dirs = await readdir3(projectsDir);
19097
19335
  for (const dir of dirs) {
19098
19336
  if (dir === dirName) continue;
19099
- const candidate = path19.join(projectsDir, dir, filename);
19337
+ const candidate = path21.join(projectsDir, dir, filename);
19100
19338
  try {
19101
- await access(candidate);
19339
+ await access2(candidate);
19102
19340
  hookLog.info(
19103
19341
  {
19104
19342
  data: {
@@ -19125,21 +19363,33 @@ async function readNewTranscriptEntries(transcriptPath, sessionId, sessionStore,
19125
19363
  let fileSize;
19126
19364
  let lineIndexOffset;
19127
19365
  if (cursor?.byteOffset) {
19366
+ const MAX_TRANSCRIPT_READ_BYTES = 10 * 1024 * 1024;
19128
19367
  const fh = await open4(transcriptPath, "r");
19129
19368
  try {
19130
- const stat4 = await fh.stat();
19131
- fileSize = stat4.size;
19132
- if (cursor.byteOffset >= stat4.size) {
19369
+ const stat5 = await fh.stat();
19370
+ fileSize = stat5.size;
19371
+ if (cursor.byteOffset >= stat5.size) {
19133
19372
  hookLog.info({ data: { sessionId } }, "No new data in transcript file");
19134
19373
  return {
19135
19374
  entries: [],
19136
19375
  endByteOffset: fileSize,
19137
- resolvedTranscriptPath: transcriptPath
19376
+ resolvedTranscriptPath: transcriptPath,
19377
+ transcriptBytesRead: 0
19138
19378
  };
19139
19379
  }
19140
- const buf = Buffer.alloc(stat4.size - cursor.byteOffset);
19141
- await fh.read(buf, 0, buf.length, cursor.byteOffset);
19380
+ const bytesToRead = Math.min(
19381
+ stat5.size - cursor.byteOffset,
19382
+ MAX_TRANSCRIPT_READ_BYTES
19383
+ );
19384
+ const buf = Buffer.alloc(bytesToRead);
19385
+ await fh.read(buf, 0, bytesToRead, cursor.byteOffset);
19142
19386
  content = buf.toString("utf-8");
19387
+ if (bytesToRead < stat5.size - cursor.byteOffset && !content.endsWith("\n")) {
19388
+ const lastNewline = content.lastIndexOf("\n");
19389
+ if (lastNewline > 0) {
19390
+ content = content.substring(0, lastNewline + 1);
19391
+ }
19392
+ }
19143
19393
  } finally {
19144
19394
  await fh.close();
19145
19395
  }
@@ -19155,12 +19405,34 @@ async function readNewTranscriptEntries(transcriptPath, sessionId, sessionStore,
19155
19405
  "Read transcript file from offset"
19156
19406
  );
19157
19407
  } else {
19158
- content = await readFile3(transcriptPath, "utf-8");
19159
- fileSize = Buffer.byteLength(content, "utf-8");
19408
+ const MAX_CWD_READ_BYTES = 2 * 1024 * 1024;
19409
+ const fh = await open4(transcriptPath, "r");
19410
+ try {
19411
+ const stat5 = await fh.stat();
19412
+ fileSize = stat5.size;
19413
+ const bytesToRead = Math.min(stat5.size, MAX_CWD_READ_BYTES);
19414
+ const buf = Buffer.alloc(bytesToRead);
19415
+ await fh.read(buf, 0, bytesToRead, 0);
19416
+ content = buf.toString("utf-8");
19417
+ if (bytesToRead < stat5.size && !content.endsWith("\n")) {
19418
+ const lastNewline = content.lastIndexOf("\n");
19419
+ if (lastNewline > 0) {
19420
+ content = content.substring(0, lastNewline + 1);
19421
+ }
19422
+ }
19423
+ } finally {
19424
+ await fh.close();
19425
+ }
19160
19426
  lineIndexOffset = 0;
19161
19427
  hookLog.debug(
19162
- { data: { transcriptPath, totalBytes: fileSize } },
19163
- "Read full transcript file"
19428
+ {
19429
+ data: {
19430
+ transcriptPath,
19431
+ totalBytes: fileSize,
19432
+ cappedBytes: content.length
19433
+ }
19434
+ },
19435
+ "Read transcript file (first run)"
19164
19436
  );
19165
19437
  }
19166
19438
  const startOffset = cursor?.byteOffset ?? 0;
@@ -19225,7 +19497,8 @@ async function readNewTranscriptEntries(transcriptPath, sessionId, sessionStore,
19225
19497
  return {
19226
19498
  entries: parsed,
19227
19499
  endByteOffset,
19228
- resolvedTranscriptPath: transcriptPath
19500
+ resolvedTranscriptPath: transcriptPath,
19501
+ transcriptBytesRead: content.length
19229
19502
  };
19230
19503
  }
19231
19504
  var FILTERED_PROGRESS_SUBTYPES = /* @__PURE__ */ new Set([
@@ -19274,14 +19547,16 @@ async function cleanupStaleSessions(configDir) {
19274
19547
  if (lastCleanup && Date.now() - lastCleanup < CLEANUP_INTERVAL_MS) {
19275
19548
  return;
19276
19549
  }
19277
- const now = Date.now();
19550
+ const cleanupStart = Date.now();
19278
19551
  const prefix = getSessionFilePrefix();
19279
19552
  try {
19280
19553
  const files = await readdir3(configDir);
19554
+ const sessionFiles = files.filter(
19555
+ (f) => f.startsWith(prefix) && f.endsWith(".json")
19556
+ );
19281
19557
  let deletedCount = 0;
19282
- for (const file of files) {
19283
- if (!file.startsWith(prefix) || !file.endsWith(".json")) continue;
19284
- const filePath = path19.join(configDir, file);
19558
+ for (const file of sessionFiles) {
19559
+ const filePath = path21.join(configDir, file);
19285
19560
  try {
19286
19561
  const content = JSON.parse(await readFile3(filePath, "utf-8"));
19287
19562
  let newest = 0;
@@ -19292,25 +19567,34 @@ async function cleanupStaleSessions(configDir) {
19292
19567
  if (c?.updatedAt && c.updatedAt > newest) newest = c.updatedAt;
19293
19568
  }
19294
19569
  }
19295
- if (newest > 0 && now - newest > STALE_KEY_MAX_AGE_MS) {
19570
+ if (newest > 0 && cleanupStart - newest > STALE_KEY_MAX_AGE_MS) {
19296
19571
  await unlink2(filePath);
19297
19572
  deletedCount++;
19298
19573
  }
19299
19574
  } catch {
19300
19575
  }
19301
19576
  }
19302
- if (deletedCount > 0) {
19303
- hookLog.info({ data: { deletedCount } }, "Cleaned up stale session files");
19304
- }
19577
+ hookLog.info(
19578
+ {
19579
+ heartbeat: true,
19580
+ data: {
19581
+ sessionFileCount: sessionFiles.length,
19582
+ deletedCount,
19583
+ cleanupDurationMs: Date.now() - cleanupStart
19584
+ }
19585
+ },
19586
+ "Session cleanup"
19587
+ );
19305
19588
  } catch {
19306
19589
  }
19307
- configStore.set("claudeCode.lastCleanupAt", now);
19590
+ configStore.set("claudeCode.lastCleanupAt", cleanupStart);
19308
19591
  }
19309
19592
  async function processTranscript(input, sessionStore, log2, maxEntries = DAEMON_CHUNK_SIZE, gqlClientOverride) {
19310
19593
  const {
19311
19594
  entries: rawEntries,
19312
19595
  endByteOffset,
19313
- resolvedTranscriptPath
19596
+ resolvedTranscriptPath,
19597
+ transcriptBytesRead
19314
19598
  } = await log2.timed(
19315
19599
  "Read transcript",
19316
19600
  () => readNewTranscriptEntries(
@@ -19397,15 +19681,21 @@ async function processTranscript(input, sessionStore, log2, maxEntries = DAEMON_
19397
19681
  rawData: rawEntry
19398
19682
  };
19399
19683
  });
19400
- const totalRawDataBytes = records.reduce((sum, r) => {
19401
- return sum + (r.rawData ? JSON.stringify(r.rawData).length : 0);
19402
- }, 0);
19684
+ let totalRawDataBytes = 0;
19685
+ let maxRecordBytes = 0;
19686
+ for (const r of records) {
19687
+ const size = r.rawData ? JSON.stringify(r.rawData).length : 0;
19688
+ totalRawDataBytes += size;
19689
+ if (size > maxRecordBytes) maxRecordBytes = size;
19690
+ }
19403
19691
  log2.info(
19404
19692
  {
19405
19693
  data: {
19406
19694
  count: records.length,
19407
19695
  skipped: filteredOut,
19696
+ transcriptBytesRead,
19408
19697
  rawDataBytes: totalRawDataBytes,
19698
+ maxRecordBytes,
19409
19699
  firstRecordId: records[0]?.recordId,
19410
19700
  lastRecordId: records[records.length - 1]?.recordId
19411
19701
  }
@@ -19519,14 +19809,14 @@ async function uploadContextFilesIfNeeded(sessionId, cwd, gqlClient, log2) {
19519
19809
  import fs14 from "fs";
19520
19810
  import fsPromises4 from "fs/promises";
19521
19811
  import os6 from "os";
19522
- import path20 from "path";
19812
+ import path22 from "path";
19523
19813
  import chalk11 from "chalk";
19524
19814
 
19525
19815
  // src/features/claude_code/daemon-check-shim.tmpl.js
19526
19816
  var daemon_check_shim_tmpl_default = "// Mobb daemon shim \u2014 checks if daemon is alive, spawns if dead.\n// Auto-generated by mobbdev CLI. Do not edit.\nvar fs = require('fs')\nvar spawn = require('child_process').spawn\nvar path = require('path')\nvar os = require('os')\n\nvar pidFile = path.join(os.homedir(), '.mobbdev', 'daemon.pid')\nvar HEARTBEAT_STALE_MS = __HEARTBEAT_STALE_MS__\n\ntry {\n var data = JSON.parse(fs.readFileSync(pidFile, 'utf8'))\n if (Date.now() - data.heartbeat > HEARTBEAT_STALE_MS) throw new Error('stale')\n process.kill(data.pid, 0) // throws ESRCH if the process is gone\n} catch (e) {\n var localCli = process.env.MOBBDEV_LOCAL_CLI\n var child = localCli\n ? spawn('node', [localCli, 'claude-code-daemon'], { detached: true, stdio: 'ignore', windowsHide: true })\n : spawn('npx', ['--yes', 'mobbdev@latest', 'claude-code-daemon'], { detached: true, stdio: 'ignore', shell: true, windowsHide: true })\n child.unref()\n}\n";
19527
19817
 
19528
19818
  // src/features/claude_code/install_hook.ts
19529
- var CLAUDE_SETTINGS_PATH = path20.join(os6.homedir(), ".claude", "settings.json");
19819
+ var CLAUDE_SETTINGS_PATH = path22.join(os6.homedir(), ".claude", "settings.json");
19530
19820
  var RECOMMENDED_MATCHER = "*";
19531
19821
  async function claudeSettingsExists() {
19532
19822
  try {
@@ -19672,18 +19962,18 @@ async function installMobbHooks(options = {}) {
19672
19962
  }
19673
19963
 
19674
19964
  // src/features/claude_code/transcript_scanner.ts
19675
- import { open as open5, readdir as readdir4, stat as stat3 } from "fs/promises";
19965
+ import { open as open5, readdir as readdir4, stat as stat4 } from "fs/promises";
19676
19966
  import os7 from "os";
19677
- import path21 from "path";
19967
+ import path23 from "path";
19678
19968
  var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
19679
19969
  function getClaudeProjectsDirs() {
19680
19970
  const dirs = [];
19681
19971
  const configDir = process.env["CLAUDE_CONFIG_DIR"];
19682
19972
  if (configDir) {
19683
- dirs.push(path21.join(configDir, "projects"));
19973
+ dirs.push(path23.join(configDir, "projects"));
19684
19974
  }
19685
- dirs.push(path21.join(os7.homedir(), ".config", "claude", "projects"));
19686
- dirs.push(path21.join(os7.homedir(), ".claude", "projects"));
19975
+ dirs.push(path23.join(os7.homedir(), ".config", "claude", "projects"));
19976
+ dirs.push(path23.join(os7.homedir(), ".claude", "projects"));
19687
19977
  return dirs;
19688
19978
  }
19689
19979
  async function collectJsonlFiles(files, dir, projectDir, seen, now, results) {
@@ -19691,12 +19981,12 @@ async function collectJsonlFiles(files, dir, projectDir, seen, now, results) {
19691
19981
  if (!file.endsWith(".jsonl")) continue;
19692
19982
  const sessionId = file.replace(".jsonl", "");
19693
19983
  if (!UUID_RE.test(sessionId)) continue;
19694
- const filePath = path21.join(dir, file);
19984
+ const filePath = path23.join(dir, file);
19695
19985
  if (seen.has(filePath)) continue;
19696
19986
  seen.add(filePath);
19697
19987
  let fileStat;
19698
19988
  try {
19699
- fileStat = await stat3(filePath);
19989
+ fileStat = await stat4(filePath);
19700
19990
  } catch {
19701
19991
  continue;
19702
19992
  }
@@ -19722,10 +20012,10 @@ async function scanForTranscripts(projectsDirs = getClaudeProjectsDirs()) {
19722
20012
  continue;
19723
20013
  }
19724
20014
  for (const projName of projectDirs) {
19725
- const projPath = path21.join(projectsDir, projName);
20015
+ const projPath = path23.join(projectsDir, projName);
19726
20016
  let projStat;
19727
20017
  try {
19728
- projStat = await stat3(projPath);
20018
+ projStat = await stat4(projPath);
19729
20019
  } catch {
19730
20020
  continue;
19731
20021
  }
@@ -19739,9 +20029,9 @@ async function scanForTranscripts(projectsDirs = getClaudeProjectsDirs()) {
19739
20029
  await collectJsonlFiles(files, projPath, projPath, seen, now, results);
19740
20030
  for (const entry of files) {
19741
20031
  if (!UUID_RE.test(entry)) continue;
19742
- const subagentsDir = path21.join(projPath, entry, "subagents");
20032
+ const subagentsDir = path23.join(projPath, entry, "subagents");
19743
20033
  try {
19744
- const s = await stat3(subagentsDir);
20034
+ const s = await stat4(subagentsDir);
19745
20035
  if (!s.isDirectory()) continue;
19746
20036
  const subFiles = await readdir4(subagentsDir);
19747
20037
  await collectJsonlFiles(
@@ -19789,6 +20079,9 @@ async function extractCwdFromTranscript(filePath) {
19789
20079
  }
19790
20080
  return void 0;
19791
20081
  }
20082
+ function getCwdCacheSize() {
20083
+ return cwdCache.size;
20084
+ }
19792
20085
 
19793
20086
  // src/features/claude_code/daemon.ts
19794
20087
  async function startDaemon() {
@@ -19829,6 +20122,11 @@ async function startDaemon() {
19829
20122
  let cleanupConfigDir;
19830
20123
  const sessionCwdCache = /* @__PURE__ */ new Map();
19831
20124
  let lastContextScanMs = 0;
20125
+ const machineContext = {
20126
+ cpuCount: os8.cpus().length,
20127
+ totalMemGB: Math.round(os8.totalmem() / 1024 ** 3),
20128
+ nodeVersion: process.version
20129
+ };
19832
20130
  while (true) {
19833
20131
  if (shuttingDown) {
19834
20132
  await gracefulExit(0, "signal");
@@ -19837,19 +20135,44 @@ async function startDaemon() {
19837
20135
  await gracefulExit(0, "TTL reached");
19838
20136
  }
19839
20137
  pidFile.updateHeartbeat();
20138
+ const cpuBefore = process.cpuUsage();
20139
+ const heapBefore = process.memoryUsage().heapUsed;
20140
+ const cycleStart = Date.now();
20141
+ let changedCount = 0;
20142
+ let totalUploaded = 0;
20143
+ let totalErrors = 0;
19840
20144
  try {
19841
20145
  const changed = await detectChangedTranscripts(lastSeen);
20146
+ changedCount = changed.length;
19842
20147
  for (const transcript of changed) {
19843
20148
  const sessionStore = createSessionConfigStore(transcript.sessionId);
19844
20149
  if (!cleanupConfigDir) {
19845
- cleanupConfigDir = path22.dirname(sessionStore.path);
20150
+ cleanupConfigDir = path24.dirname(sessionStore.path);
19846
20151
  }
19847
- await drainTranscript(
20152
+ pidFile.updateHeartbeat();
20153
+ const drainStart = Date.now();
20154
+ const result = await drainTranscript(
19848
20155
  transcript,
19849
20156
  sessionStore,
19850
20157
  gqlClient,
19851
20158
  sessionCwdCache
19852
20159
  );
20160
+ totalUploaded += result.uploaded;
20161
+ totalErrors += result.errors;
20162
+ if (result.uploaded > 0 || result.errors > 0) {
20163
+ hookLog.info(
20164
+ {
20165
+ data: {
20166
+ sessionId: transcript.sessionId,
20167
+ drainDurationMs: Date.now() - drainStart,
20168
+ chunksProcessed: result.chunks,
20169
+ entriesUploaded: result.uploaded,
20170
+ errors: result.errors
20171
+ }
20172
+ },
20173
+ "Transcript drained"
20174
+ );
20175
+ }
19853
20176
  }
19854
20177
  if (lastSeen.size > 0) {
19855
20178
  for (const filePath of sessionCwdCache.keys()) {
@@ -19872,6 +20195,29 @@ async function startDaemon() {
19872
20195
  } catch (err) {
19873
20196
  hookLog.warn({ err }, "Unexpected error in daemon cycle");
19874
20197
  }
20198
+ const cpuDelta = process.cpuUsage(cpuBefore);
20199
+ const heapAfter = process.memoryUsage().heapUsed;
20200
+ hookLog.info(
20201
+ {
20202
+ heartbeat: true,
20203
+ data: {
20204
+ cycleDurationMs: Date.now() - cycleStart,
20205
+ cpuUserUs: cpuDelta.user,
20206
+ cpuSystemUs: cpuDelta.system,
20207
+ heapDeltaBytes: heapAfter - heapBefore,
20208
+ heapUsedBytes: heapAfter,
20209
+ daemonUptimeMs: Date.now() - startedAt,
20210
+ changedTranscripts: changedCount,
20211
+ activeTranscripts: lastSeen.size,
20212
+ entriesUploaded: totalUploaded,
20213
+ errors: totalErrors,
20214
+ cwdCacheSize: getCwdCacheSize(),
20215
+ scopedLoggerCount: getScopedLoggerCount(),
20216
+ ...machineContext
20217
+ }
20218
+ },
20219
+ "daemon poll cycle"
20220
+ );
19875
20221
  await sleep2(DAEMON_POLL_INTERVAL_MS);
19876
20222
  }
19877
20223
  }
@@ -19910,6 +20256,9 @@ async function drainTranscript(transcript, sessionStore, gqlClient, sessionCwdCa
19910
20256
  const log2 = createScopedHookLog(cwd ?? transcript.projectDir, {
19911
20257
  daemonMode: true
19912
20258
  });
20259
+ let totalUploaded = 0;
20260
+ let totalErrors = 0;
20261
+ let chunks = 0;
19913
20262
  if (cwd) {
19914
20263
  sessionCwdCache.set(transcript.filePath, {
19915
20264
  sessionId: transcript.sessionId,
@@ -19919,6 +20268,7 @@ async function drainTranscript(transcript, sessionStore, gqlClient, sessionCwdCa
19919
20268
  try {
19920
20269
  let hasMore = true;
19921
20270
  while (hasMore) {
20271
+ chunks++;
19922
20272
  const result = await processTranscript(
19923
20273
  {
19924
20274
  session_id: transcript.sessionId,
@@ -19930,8 +20280,10 @@ async function drainTranscript(transcript, sessionStore, gqlClient, sessionCwdCa
19930
20280
  DAEMON_CHUNK_SIZE,
19931
20281
  gqlClient
19932
20282
  );
20283
+ totalUploaded += result.entriesUploaded;
19933
20284
  hasMore = result.entriesUploaded + result.entriesSkipped >= DAEMON_CHUNK_SIZE;
19934
20285
  if (result.errors > 0) {
20286
+ totalErrors += result.errors;
19935
20287
  hookLog.warn(
19936
20288
  {
19937
20289
  data: {
@@ -19963,6 +20315,7 @@ async function drainTranscript(transcript, sessionStore, gqlClient, sessionCwdCa
19963
20315
  );
19964
20316
  });
19965
20317
  }
20318
+ return { uploaded: totalUploaded, errors: totalErrors, chunks };
19966
20319
  }
19967
20320
  async function detectChangedTranscripts(lastSeen) {
19968
20321
  const transcripts = await scanForTranscripts();
@@ -20139,8 +20492,8 @@ var WorkspaceService = class {
20139
20492
  * Sets a known workspace path that was discovered through successful validation
20140
20493
  * @param path The validated workspace path to store
20141
20494
  */
20142
- static setKnownWorkspacePath(path35) {
20143
- this.knownWorkspacePath = path35;
20495
+ static setKnownWorkspacePath(path37) {
20496
+ this.knownWorkspacePath = path37;
20144
20497
  }
20145
20498
  /**
20146
20499
  * Gets the known workspace path that was previously validated
@@ -21000,8 +21353,8 @@ async function createAuthenticatedMcpGQLClient({
21000
21353
  // src/mcp/services/McpUsageService/host.ts
21001
21354
  import { execSync as execSync2 } from "child_process";
21002
21355
  import fs15 from "fs";
21003
- import os8 from "os";
21004
- import path23 from "path";
21356
+ import os9 from "os";
21357
+ import path25 from "path";
21005
21358
  var IDEs = ["cursor", "windsurf", "webstorm", "vscode", "claude"];
21006
21359
  var runCommand = (cmd) => {
21007
21360
  try {
@@ -21015,8 +21368,8 @@ var gitInfo = {
21015
21368
  email: runCommand("git config user.email")
21016
21369
  };
21017
21370
  var getClaudeWorkspacePaths = () => {
21018
- const home = os8.homedir();
21019
- const claudeIdePath = path23.join(home, ".claude", "ide");
21371
+ const home = os9.homedir();
21372
+ const claudeIdePath = path25.join(home, ".claude", "ide");
21020
21373
  const workspacePaths = [];
21021
21374
  if (!fs15.existsSync(claudeIdePath)) {
21022
21375
  return workspacePaths;
@@ -21024,7 +21377,7 @@ var getClaudeWorkspacePaths = () => {
21024
21377
  try {
21025
21378
  const lockFiles = fs15.readdirSync(claudeIdePath).filter((file) => file.endsWith(".lock"));
21026
21379
  for (const lockFile of lockFiles) {
21027
- const lockFilePath = path23.join(claudeIdePath, lockFile);
21380
+ const lockFilePath = path25.join(claudeIdePath, lockFile);
21028
21381
  try {
21029
21382
  const lockContent = JSON.parse(fs15.readFileSync(lockFilePath, "utf8"));
21030
21383
  if (lockContent.workspaceFolders && Array.isArray(lockContent.workspaceFolders)) {
@@ -21044,29 +21397,29 @@ var getClaudeWorkspacePaths = () => {
21044
21397
  return workspacePaths;
21045
21398
  };
21046
21399
  var getMCPConfigPaths = (hostName) => {
21047
- const home = os8.homedir();
21400
+ const home = os9.homedir();
21048
21401
  const currentDir = process.env["WORKSPACE_FOLDER_PATHS"] || process.env["PWD"] || process.cwd();
21049
21402
  switch (hostName.toLowerCase()) {
21050
21403
  case "cursor":
21051
21404
  return [
21052
- path23.join(currentDir, ".cursor", "mcp.json"),
21405
+ path25.join(currentDir, ".cursor", "mcp.json"),
21053
21406
  // local first
21054
- path23.join(home, ".cursor", "mcp.json")
21407
+ path25.join(home, ".cursor", "mcp.json")
21055
21408
  ];
21056
21409
  case "windsurf":
21057
21410
  return [
21058
- path23.join(currentDir, ".codeium", "mcp_config.json"),
21411
+ path25.join(currentDir, ".codeium", "mcp_config.json"),
21059
21412
  // local first
21060
- path23.join(home, ".codeium", "windsurf", "mcp_config.json")
21413
+ path25.join(home, ".codeium", "windsurf", "mcp_config.json")
21061
21414
  ];
21062
21415
  case "webstorm":
21063
21416
  return [];
21064
21417
  case "visualstudiocode":
21065
21418
  case "vscode":
21066
21419
  return [
21067
- path23.join(currentDir, ".vscode", "mcp.json"),
21420
+ path25.join(currentDir, ".vscode", "mcp.json"),
21068
21421
  // local first
21069
- process.platform === "win32" ? path23.join(home, "AppData", "Roaming", "Code", "User", "mcp.json") : path23.join(
21422
+ process.platform === "win32" ? path25.join(home, "AppData", "Roaming", "Code", "User", "mcp.json") : path25.join(
21070
21423
  home,
21071
21424
  "Library",
21072
21425
  "Application Support",
@@ -21077,13 +21430,13 @@ var getMCPConfigPaths = (hostName) => {
21077
21430
  ];
21078
21431
  case "claude": {
21079
21432
  const claudePaths = [
21080
- path23.join(currentDir, ".claude.json"),
21433
+ path25.join(currentDir, ".claude.json"),
21081
21434
  // local first
21082
- path23.join(home, ".claude.json")
21435
+ path25.join(home, ".claude.json")
21083
21436
  ];
21084
21437
  const workspacePaths = getClaudeWorkspacePaths();
21085
21438
  for (const workspacePath of workspacePaths) {
21086
- claudePaths.push(path23.join(workspacePath, ".mcp.json"));
21439
+ claudePaths.push(path25.join(workspacePath, ".mcp.json"));
21087
21440
  }
21088
21441
  return claudePaths;
21089
21442
  }
@@ -21134,7 +21487,7 @@ var readMCPConfig = (hostName) => {
21134
21487
  };
21135
21488
  var getRunningProcesses = () => {
21136
21489
  try {
21137
- return os8.platform() === "win32" ? execSync2("tasklist", { encoding: "utf8" }) : execSync2("ps aux", { encoding: "utf8" });
21490
+ return os9.platform() === "win32" ? execSync2("tasklist", { encoding: "utf8" }) : execSync2("ps aux", { encoding: "utf8" });
21138
21491
  } catch {
21139
21492
  return "";
21140
21493
  }
@@ -21209,7 +21562,7 @@ var versionCommands = {
21209
21562
  }
21210
21563
  };
21211
21564
  var getProcessInfo = (pid) => {
21212
- const platform2 = os8.platform();
21565
+ const platform2 = os9.platform();
21213
21566
  try {
21214
21567
  if (platform2 === "linux" || platform2 === "darwin") {
21215
21568
  const output = execSync2(`ps -o pid=,ppid=,comm= -p ${pid}`, {
@@ -21244,10 +21597,10 @@ var getHostInfo = (additionalMcpList) => {
21244
21597
  const ideConfigPaths = /* @__PURE__ */ new Set();
21245
21598
  for (const ide of IDEs) {
21246
21599
  const configPaths = getMCPConfigPaths(ide);
21247
- configPaths.forEach((path35) => ideConfigPaths.add(path35));
21600
+ configPaths.forEach((path37) => ideConfigPaths.add(path37));
21248
21601
  }
21249
21602
  const uniqueAdditionalPaths = additionalMcpList.filter(
21250
- (path35) => !ideConfigPaths.has(path35)
21603
+ (path37) => !ideConfigPaths.has(path37)
21251
21604
  );
21252
21605
  for (const ide of IDEs) {
21253
21606
  const cfg = readMCPConfig(ide);
@@ -21328,7 +21681,7 @@ var getHostInfo = (additionalMcpList) => {
21328
21681
  const config2 = allConfigs[ide] || null;
21329
21682
  const ideName = ide.charAt(0).toUpperCase() + ide.slice(1) || "Unknown";
21330
21683
  let ideVersion = "Unknown";
21331
- const platform2 = os8.platform();
21684
+ const platform2 = os9.platform();
21332
21685
  const cmds = versionCommands[ideName]?.[platform2] ?? [];
21333
21686
  for (const cmd of cmds) {
21334
21687
  try {
@@ -21361,15 +21714,15 @@ var getHostInfo = (additionalMcpList) => {
21361
21714
 
21362
21715
  // src/mcp/services/McpUsageService/McpUsageService.ts
21363
21716
  import fetch6 from "node-fetch";
21364
- import os10 from "os";
21717
+ import os11 from "os";
21365
21718
  import { v4 as uuidv42, v5 as uuidv5 } from "uuid";
21366
21719
  init_configs();
21367
21720
 
21368
21721
  // src/mcp/services/McpUsageService/system.ts
21369
21722
  init_configs();
21370
21723
  import fs16 from "fs";
21371
- import os9 from "os";
21372
- import path24 from "path";
21724
+ import os10 from "os";
21725
+ import path26 from "path";
21373
21726
  var MAX_DEPTH = 2;
21374
21727
  var patterns = ["mcp", "claude"];
21375
21728
  var isFileMatch = (fileName) => {
@@ -21389,7 +21742,7 @@ var searchDir = async (dir, depth = 0) => {
21389
21742
  if (depth > MAX_DEPTH) return results;
21390
21743
  const entries = await fs16.promises.readdir(dir, { withFileTypes: true }).catch(() => []);
21391
21744
  for (const entry of entries) {
21392
- const fullPath = path24.join(dir, entry.name);
21745
+ const fullPath = path26.join(dir, entry.name);
21393
21746
  if (entry.isFile() && isFileMatch(entry.name)) {
21394
21747
  results.push(fullPath);
21395
21748
  } else if (entry.isDirectory()) {
@@ -21403,17 +21756,17 @@ var searchDir = async (dir, depth = 0) => {
21403
21756
  };
21404
21757
  var findSystemMCPConfigs = async () => {
21405
21758
  try {
21406
- const home = os9.homedir();
21407
- const platform2 = os9.platform();
21759
+ const home = os10.homedir();
21760
+ const platform2 = os10.platform();
21408
21761
  const knownDirs = platform2 === "win32" ? [
21409
- path24.join(home, ".cursor"),
21410
- path24.join(home, "Documents"),
21411
- path24.join(home, "Downloads")
21762
+ path26.join(home, ".cursor"),
21763
+ path26.join(home, "Documents"),
21764
+ path26.join(home, "Downloads")
21412
21765
  ] : [
21413
- path24.join(home, ".cursor"),
21414
- process.env["XDG_CONFIG_HOME"] || path24.join(home, ".config"),
21415
- path24.join(home, "Documents"),
21416
- path24.join(home, "Downloads")
21766
+ path26.join(home, ".cursor"),
21767
+ process.env["XDG_CONFIG_HOME"] || path26.join(home, ".config"),
21768
+ path26.join(home, "Documents"),
21769
+ path26.join(home, "Downloads")
21417
21770
  ];
21418
21771
  const timeoutPromise = new Promise(
21419
21772
  (resolve) => setTimeout(() => {
@@ -21476,7 +21829,7 @@ var McpUsageService = class {
21476
21829
  generateHostId() {
21477
21830
  const stored = configStore.get(this.configKey);
21478
21831
  if (stored?.mcpHostId) return stored.mcpHostId;
21479
- const interfaces = os10.networkInterfaces();
21832
+ const interfaces = os11.networkInterfaces();
21480
21833
  const macs = [];
21481
21834
  for (const iface of Object.values(interfaces)) {
21482
21835
  if (!iface) continue;
@@ -21484,7 +21837,7 @@ var McpUsageService = class {
21484
21837
  if (net.mac && net.mac !== "00:00:00:00:00:00") macs.push(net.mac);
21485
21838
  }
21486
21839
  }
21487
- const macString = macs.length ? macs.sort().join(",") : `${os10.hostname()}-${uuidv42()}`;
21840
+ const macString = macs.length ? macs.sort().join(",") : `${os11.hostname()}-${uuidv42()}`;
21488
21841
  const hostId = uuidv5(macString, uuidv5.DNS);
21489
21842
  logDebug("[UsageService] Generated new host ID", { hostId });
21490
21843
  return hostId;
@@ -21507,7 +21860,7 @@ var McpUsageService = class {
21507
21860
  mcpHostId,
21508
21861
  organizationId,
21509
21862
  mcpVersion: packageJson.version,
21510
- mcpOsName: os10.platform(),
21863
+ mcpOsName: os11.platform(),
21511
21864
  mcps: JSON.stringify(mcps),
21512
21865
  status,
21513
21866
  userName: user.name,
@@ -23828,30 +24181,30 @@ For a complete security audit workflow, use the \`full-security-audit\` prompt.
23828
24181
 
23829
24182
  // src/mcp/services/McpDetectionService/CursorMcpDetectionService.ts
23830
24183
  import * as fs19 from "fs";
23831
- import * as os12 from "os";
23832
- import * as path26 from "path";
24184
+ import * as os13 from "os";
24185
+ import * as path28 from "path";
23833
24186
 
23834
24187
  // src/mcp/services/McpDetectionService/BaseMcpDetectionService.ts
23835
24188
  init_configs();
23836
24189
  import * as fs18 from "fs";
23837
24190
  import fetch7 from "node-fetch";
23838
- import * as path25 from "path";
24191
+ import * as path27 from "path";
23839
24192
 
23840
24193
  // src/mcp/services/McpDetectionService/McpDetectionServiceUtils.ts
23841
24194
  import * as fs17 from "fs";
23842
- import * as os11 from "os";
24195
+ import * as os12 from "os";
23843
24196
 
23844
24197
  // src/mcp/services/McpDetectionService/VscodeMcpDetectionService.ts
23845
24198
  import * as fs20 from "fs";
23846
- import * as os13 from "os";
23847
- import * as path27 from "path";
24199
+ import * as os14 from "os";
24200
+ import * as path29 from "path";
23848
24201
 
23849
24202
  // src/mcp/tools/checkForNewAvailableFixes/CheckForNewAvailableFixesTool.ts
23850
24203
  import { z as z42 } from "zod";
23851
24204
 
23852
24205
  // src/mcp/services/PathValidation.ts
23853
24206
  import fs21 from "fs";
23854
- import path28 from "path";
24207
+ import path30 from "path";
23855
24208
  async function validatePath(inputPath) {
23856
24209
  logDebug("Validating MCP path", { inputPath });
23857
24210
  if (/^\/[a-zA-Z]:\//.test(inputPath)) {
@@ -23883,7 +24236,7 @@ async function validatePath(inputPath) {
23883
24236
  logError(error);
23884
24237
  return { isValid: false, error, path: inputPath };
23885
24238
  }
23886
- const normalizedPath = path28.normalize(inputPath);
24239
+ const normalizedPath = path30.normalize(inputPath);
23887
24240
  if (normalizedPath.includes("..")) {
23888
24241
  const error = `Normalized path contains path traversal patterns: ${inputPath}`;
23889
24242
  logError(error);
@@ -24535,7 +24888,7 @@ init_configs();
24535
24888
  import fs22 from "fs/promises";
24536
24889
  import nodePath from "path";
24537
24890
  var getLocalFiles = async ({
24538
- path: path35,
24891
+ path: path37,
24539
24892
  maxFileSize = MCP_MAX_FILE_SIZE,
24540
24893
  maxFiles,
24541
24894
  isAllFilesScan,
@@ -24543,17 +24896,17 @@ var getLocalFiles = async ({
24543
24896
  scanRecentlyChangedFiles
24544
24897
  }) => {
24545
24898
  logDebug(`[${scanContext}] Starting getLocalFiles`, {
24546
- path: path35,
24899
+ path: path37,
24547
24900
  maxFileSize,
24548
24901
  maxFiles,
24549
24902
  isAllFilesScan,
24550
24903
  scanRecentlyChangedFiles
24551
24904
  });
24552
24905
  try {
24553
- const resolvedRepoPath = await fs22.realpath(path35);
24906
+ const resolvedRepoPath = await fs22.realpath(path37);
24554
24907
  logDebug(`[${scanContext}] Resolved repository path`, {
24555
24908
  resolvedRepoPath,
24556
- originalPath: path35
24909
+ originalPath: path37
24557
24910
  });
24558
24911
  const gitService = new GitService(resolvedRepoPath, log);
24559
24912
  const gitValidation = await gitService.validateRepository();
@@ -24566,7 +24919,7 @@ var getLocalFiles = async ({
24566
24919
  if (!gitValidation.isValid || isAllFilesScan) {
24567
24920
  try {
24568
24921
  files = await FileUtils.getLastChangedFiles({
24569
- dir: path35,
24922
+ dir: path37,
24570
24923
  maxFileSize,
24571
24924
  maxFiles,
24572
24925
  isAllFilesScan
@@ -24658,7 +25011,7 @@ var getLocalFiles = async ({
24658
25011
  logError(`${scanContext}Unexpected error in getLocalFiles`, {
24659
25012
  error: error instanceof Error ? error.message : String(error),
24660
25013
  stack: error instanceof Error ? error.stack : void 0,
24661
- path: path35
25014
+ path: path37
24662
25015
  });
24663
25016
  throw error;
24664
25017
  }
@@ -24668,7 +25021,7 @@ var getLocalFiles = async ({
24668
25021
  init_client_generates();
24669
25022
  init_GitService();
24670
25023
  import fs23 from "fs";
24671
- import path29 from "path";
25024
+ import path31 from "path";
24672
25025
  import { z as z41 } from "zod";
24673
25026
  function extractPathFromPatch(patch) {
24674
25027
  const match = patch?.match(/diff --git a\/([^\s]+) b\//);
@@ -24754,7 +25107,7 @@ var LocalMobbFolderService = class {
24754
25107
  "[LocalMobbFolderService] Non-git repository detected, skipping .gitignore operations"
24755
25108
  );
24756
25109
  }
24757
- const mobbFolderPath = path29.join(
25110
+ const mobbFolderPath = path31.join(
24758
25111
  this.repoPath,
24759
25112
  this.defaultMobbFolderName
24760
25113
  );
@@ -24926,7 +25279,7 @@ var LocalMobbFolderService = class {
24926
25279
  mobbFolderPath,
24927
25280
  baseFileName
24928
25281
  );
24929
- const filePath = path29.join(mobbFolderPath, uniqueFileName);
25282
+ const filePath = path31.join(mobbFolderPath, uniqueFileName);
24930
25283
  await fs23.promises.writeFile(filePath, patch, "utf8");
24931
25284
  logInfo("[LocalMobbFolderService] Patch saved successfully", {
24932
25285
  filePath,
@@ -24984,11 +25337,11 @@ var LocalMobbFolderService = class {
24984
25337
  * @returns Unique filename that doesn't conflict with existing files
24985
25338
  */
24986
25339
  getUniqueFileName(folderPath, baseFileName) {
24987
- const baseName = path29.parse(baseFileName).name;
24988
- const extension = path29.parse(baseFileName).ext;
25340
+ const baseName = path31.parse(baseFileName).name;
25341
+ const extension = path31.parse(baseFileName).ext;
24989
25342
  let uniqueFileName = baseFileName;
24990
25343
  let index = 1;
24991
- while (fs23.existsSync(path29.join(folderPath, uniqueFileName))) {
25344
+ while (fs23.existsSync(path31.join(folderPath, uniqueFileName))) {
24992
25345
  uniqueFileName = `${baseName}-${index}${extension}`;
24993
25346
  index++;
24994
25347
  if (index > 1e3) {
@@ -25019,7 +25372,7 @@ var LocalMobbFolderService = class {
25019
25372
  logDebug("[LocalMobbFolderService] Logging patch info", { fixId: fix.id });
25020
25373
  try {
25021
25374
  const mobbFolderPath = await this.getFolder();
25022
- const patchInfoPath = path29.join(mobbFolderPath, "patchInfo.md");
25375
+ const patchInfoPath = path31.join(mobbFolderPath, "patchInfo.md");
25023
25376
  const markdownContent = this.generateFixMarkdown(fix, savedPatchFileName);
25024
25377
  let existingContent = "";
25025
25378
  if (fs23.existsSync(patchInfoPath)) {
@@ -25061,7 +25414,7 @@ var LocalMobbFolderService = class {
25061
25414
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
25062
25415
  const patch = this.extractPatchFromFix(fix);
25063
25416
  const relativePatchedFilePath = patch ? extractPathFromPatch(patch) : null;
25064
- const patchedFilePath = relativePatchedFilePath ? path29.resolve(this.repoPath, relativePatchedFilePath) : null;
25417
+ const patchedFilePath = relativePatchedFilePath ? path31.resolve(this.repoPath, relativePatchedFilePath) : null;
25065
25418
  const fixIdentifier = savedPatchFileName ? savedPatchFileName.replace(".patch", "") : fix.id;
25066
25419
  let markdown = `# Fix ${fixIdentifier}
25067
25420
 
@@ -25397,7 +25750,7 @@ var LocalMobbFolderService = class {
25397
25750
  // src/mcp/services/PatchApplicationService.ts
25398
25751
  init_configs();
25399
25752
  import {
25400
- existsSync as existsSync7,
25753
+ existsSync as existsSync6,
25401
25754
  mkdirSync,
25402
25755
  readFileSync as readFileSync4,
25403
25756
  unlinkSync,
@@ -25405,14 +25758,14 @@ import {
25405
25758
  } from "fs";
25406
25759
  import fs24 from "fs/promises";
25407
25760
  import parseDiff2 from "parse-diff";
25408
- import path30 from "path";
25761
+ import path32 from "path";
25409
25762
  var PatchApplicationService = class {
25410
25763
  /**
25411
25764
  * Gets the appropriate comment syntax for a file based on its extension
25412
25765
  */
25413
25766
  static getCommentSyntax(filePath) {
25414
- const ext = path30.extname(filePath).toLowerCase();
25415
- const basename2 = path30.basename(filePath);
25767
+ const ext = path32.extname(filePath).toLowerCase();
25768
+ const basename2 = path32.basename(filePath);
25416
25769
  const commentMap = {
25417
25770
  // C-style languages (single line comments)
25418
25771
  ".js": "//",
@@ -25620,7 +25973,7 @@ var PatchApplicationService = class {
25620
25973
  }
25621
25974
  );
25622
25975
  }
25623
- const dirPath = path30.dirname(normalizedFilePath);
25976
+ const dirPath = path32.dirname(normalizedFilePath);
25624
25977
  mkdirSync(dirPath, { recursive: true });
25625
25978
  writeFileSync3(normalizedFilePath, finalContent, "utf8");
25626
25979
  return normalizedFilePath;
@@ -25629,9 +25982,9 @@ var PatchApplicationService = class {
25629
25982
  repositoryPath,
25630
25983
  targetPath
25631
25984
  }) {
25632
- const repoRoot = path30.resolve(repositoryPath);
25633
- const normalizedPath = path30.resolve(repoRoot, targetPath);
25634
- const repoRootWithSep = repoRoot.endsWith(path30.sep) ? repoRoot : `${repoRoot}${path30.sep}`;
25985
+ const repoRoot = path32.resolve(repositoryPath);
25986
+ const normalizedPath = path32.resolve(repoRoot, targetPath);
25987
+ const repoRootWithSep = repoRoot.endsWith(path32.sep) ? repoRoot : `${repoRoot}${path32.sep}`;
25635
25988
  if (normalizedPath !== repoRoot && !normalizedPath.startsWith(repoRootWithSep)) {
25636
25989
  throw new Error(
25637
25990
  `Security violation: target path ${targetPath} resolves outside repository`
@@ -25640,7 +25993,7 @@ var PatchApplicationService = class {
25640
25993
  return {
25641
25994
  repoRoot,
25642
25995
  normalizedPath,
25643
- relativePath: path30.relative(repoRoot, normalizedPath)
25996
+ relativePath: path32.relative(repoRoot, normalizedPath)
25644
25997
  };
25645
25998
  }
25646
25999
  /**
@@ -25922,8 +26275,8 @@ var PatchApplicationService = class {
25922
26275
  continue;
25923
26276
  }
25924
26277
  try {
25925
- const absolutePath = path30.resolve(repositoryPath, targetFile);
25926
- if (existsSync7(absolutePath)) {
26278
+ const absolutePath = path32.resolve(repositoryPath, targetFile);
26279
+ if (existsSync6(absolutePath)) {
25927
26280
  const stats = await fs24.stat(absolutePath);
25928
26281
  const fileModTime = stats.mtime.getTime();
25929
26282
  if (fileModTime > scanStartTime) {
@@ -26124,7 +26477,7 @@ var PatchApplicationService = class {
26124
26477
  targetFile,
26125
26478
  absoluteFilePath,
26126
26479
  relativePath,
26127
- exists: existsSync7(absoluteFilePath)
26480
+ exists: existsSync6(absoluteFilePath)
26128
26481
  });
26129
26482
  return { absoluteFilePath, relativePath };
26130
26483
  }
@@ -26148,7 +26501,7 @@ var PatchApplicationService = class {
26148
26501
  fix,
26149
26502
  scanContext
26150
26503
  });
26151
- appliedFiles.push(path30.relative(repositoryPath, actualPath));
26504
+ appliedFiles.push(path32.relative(repositoryPath, actualPath));
26152
26505
  logDebug(`[${scanContext}] Created new file: ${relativePath}`);
26153
26506
  }
26154
26507
  /**
@@ -26160,7 +26513,7 @@ var PatchApplicationService = class {
26160
26513
  appliedFiles,
26161
26514
  scanContext
26162
26515
  }) {
26163
- if (existsSync7(absoluteFilePath)) {
26516
+ if (existsSync6(absoluteFilePath)) {
26164
26517
  unlinkSync(absoluteFilePath);
26165
26518
  appliedFiles.push(relativePath);
26166
26519
  logDebug(`[${scanContext}] Deleted file: ${relativePath}`);
@@ -26179,7 +26532,7 @@ var PatchApplicationService = class {
26179
26532
  appliedFiles,
26180
26533
  scanContext
26181
26534
  }) {
26182
- if (!existsSync7(absoluteFilePath)) {
26535
+ if (!existsSync6(absoluteFilePath)) {
26183
26536
  throw new Error(
26184
26537
  `Target file does not exist: ${targetFile} (resolved to: ${absoluteFilePath})`
26185
26538
  );
@@ -26197,7 +26550,7 @@ var PatchApplicationService = class {
26197
26550
  fix,
26198
26551
  scanContext
26199
26552
  });
26200
- appliedFiles.push(path30.relative(repositoryPath, actualPath));
26553
+ appliedFiles.push(path32.relative(repositoryPath, actualPath));
26201
26554
  logDebug(`[${scanContext}] Modified file: ${relativePath}`);
26202
26555
  }
26203
26556
  }
@@ -26394,8 +26747,8 @@ init_configs();
26394
26747
  // src/mcp/services/FileOperations.ts
26395
26748
  init_FileUtils();
26396
26749
  import fs25 from "fs";
26397
- import path31 from "path";
26398
- import AdmZip4 from "adm-zip";
26750
+ import path33 from "path";
26751
+ import AdmZip5 from "adm-zip";
26399
26752
  var FileOperations = class {
26400
26753
  /**
26401
26754
  * Creates a ZIP archive containing the specified source files
@@ -26410,14 +26763,14 @@ var FileOperations = class {
26410
26763
  maxFileSize
26411
26764
  }) {
26412
26765
  logDebug("[FileOperations] Packing files");
26413
- const zip = new AdmZip4();
26766
+ const zip = new AdmZip5();
26414
26767
  let packedFilesCount = 0;
26415
26768
  const packedFiles = [];
26416
26769
  const excludedFiles = [];
26417
- const resolvedRepoPath = path31.resolve(repositoryPath);
26770
+ const resolvedRepoPath = path33.resolve(repositoryPath);
26418
26771
  for (const filepath of fileList) {
26419
- const absoluteFilepath = path31.join(repositoryPath, filepath);
26420
- const resolvedFilePath = path31.resolve(absoluteFilepath);
26772
+ const absoluteFilepath = path33.join(repositoryPath, filepath);
26773
+ const resolvedFilePath = path33.resolve(absoluteFilepath);
26421
26774
  if (!resolvedFilePath.startsWith(resolvedRepoPath)) {
26422
26775
  const reason = "potential path traversal security risk";
26423
26776
  logDebug(`[FileOperations] Skipping ${filepath} due to ${reason}`);
@@ -26464,11 +26817,11 @@ var FileOperations = class {
26464
26817
  fileList,
26465
26818
  repositoryPath
26466
26819
  }) {
26467
- const resolvedRepoPath = path31.resolve(repositoryPath);
26820
+ const resolvedRepoPath = path33.resolve(repositoryPath);
26468
26821
  const validatedPaths = [];
26469
26822
  for (const filepath of fileList) {
26470
- const absoluteFilepath = path31.join(repositoryPath, filepath);
26471
- const resolvedFilePath = path31.resolve(absoluteFilepath);
26823
+ const absoluteFilepath = path33.join(repositoryPath, filepath);
26824
+ const resolvedFilePath = path33.resolve(absoluteFilepath);
26472
26825
  if (!resolvedFilePath.startsWith(resolvedRepoPath)) {
26473
26826
  logDebug(
26474
26827
  `[FileOperations] Rejecting ${filepath} - path traversal attempt detected`
@@ -26496,7 +26849,7 @@ var FileOperations = class {
26496
26849
  for (const absolutePath of filePaths) {
26497
26850
  try {
26498
26851
  const content = await fs25.promises.readFile(absolutePath);
26499
- const relativePath = path31.basename(absolutePath);
26852
+ const relativePath = path33.basename(absolutePath);
26500
26853
  fileDataArray.push({
26501
26854
  relativePath,
26502
26855
  absolutePath,
@@ -26808,14 +27161,14 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
26808
27161
  * since the last scan.
26809
27162
  */
26810
27163
  async scanForSecurityVulnerabilities({
26811
- path: path35,
27164
+ path: path37,
26812
27165
  isAllDetectionRulesScan,
26813
27166
  isAllFilesScan,
26814
27167
  scanContext
26815
27168
  }) {
26816
27169
  this.hasAuthenticationFailed = false;
26817
27170
  logDebug(`[${scanContext}] Scanning for new security vulnerabilities`, {
26818
- path: path35
27171
+ path: path37
26819
27172
  });
26820
27173
  if (!this.gqlClient) {
26821
27174
  logInfo(`[${scanContext}] No GQL client found, skipping scan`);
@@ -26831,11 +27184,11 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
26831
27184
  }
26832
27185
  logDebug(
26833
27186
  `[${scanContext}] Connected to the API, assembling list of files to scan`,
26834
- { path: path35 }
27187
+ { path: path37 }
26835
27188
  );
26836
27189
  const isBackgroundScan = scanContext === ScanContext.BACKGROUND_INITIAL || scanContext === ScanContext.BACKGROUND_PERIODIC;
26837
27190
  const files = await getLocalFiles({
26838
- path: path35,
27191
+ path: path37,
26839
27192
  isAllFilesScan,
26840
27193
  scanContext,
26841
27194
  scanRecentlyChangedFiles: !isBackgroundScan
@@ -26861,13 +27214,13 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
26861
27214
  });
26862
27215
  const { fixReportId, projectId } = await scanFiles({
26863
27216
  fileList: filesToScan.map((file) => file.relativePath),
26864
- repositoryPath: path35,
27217
+ repositoryPath: path37,
26865
27218
  gqlClient: this.gqlClient,
26866
27219
  isAllDetectionRulesScan,
26867
27220
  scanContext
26868
27221
  });
26869
27222
  logInfo(
26870
- `[${scanContext}] Security scan completed for ${path35} reportId: ${fixReportId} projectId: ${projectId}`
27223
+ `[${scanContext}] Security scan completed for ${path37} reportId: ${fixReportId} projectId: ${projectId}`
26871
27224
  );
26872
27225
  if (isAllFilesScan) {
26873
27226
  return;
@@ -27161,13 +27514,13 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
27161
27514
  });
27162
27515
  return scannedFiles.some((file) => file.relativePath === fixFile);
27163
27516
  }
27164
- async getFreshFixes({ path: path35 }) {
27517
+ async getFreshFixes({ path: path37 }) {
27165
27518
  const scanContext = ScanContext.USER_REQUEST;
27166
- logDebug(`[${scanContext}] Getting fresh fixes`, { path: path35 });
27167
- if (this.path !== path35) {
27168
- this.path = path35;
27519
+ logDebug(`[${scanContext}] Getting fresh fixes`, { path: path37 });
27520
+ if (this.path !== path37) {
27521
+ this.path = path37;
27169
27522
  this.reset();
27170
- logInfo(`[${scanContext}] Reset service state for new path`, { path: path35 });
27523
+ logInfo(`[${scanContext}] Reset service state for new path`, { path: path37 });
27171
27524
  }
27172
27525
  try {
27173
27526
  const loginContext = createMcpLoginContext("check_new_fixes");
@@ -27186,7 +27539,7 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
27186
27539
  }
27187
27540
  throw error;
27188
27541
  }
27189
- this.triggerScan({ path: path35, gqlClient: this.gqlClient });
27542
+ this.triggerScan({ path: path37, gqlClient: this.gqlClient });
27190
27543
  let isMvsAutoFixEnabled = null;
27191
27544
  try {
27192
27545
  isMvsAutoFixEnabled = await this.gqlClient.getMvsAutoFixSettings();
@@ -27220,33 +27573,33 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
27220
27573
  return noFreshFixesPrompt;
27221
27574
  }
27222
27575
  triggerScan({
27223
- path: path35,
27576
+ path: path37,
27224
27577
  gqlClient
27225
27578
  }) {
27226
- if (this.path !== path35) {
27227
- this.path = path35;
27579
+ if (this.path !== path37) {
27580
+ this.path = path37;
27228
27581
  this.reset();
27229
- logInfo(`Reset service state for new path in triggerScan`, { path: path35 });
27582
+ logInfo(`Reset service state for new path in triggerScan`, { path: path37 });
27230
27583
  }
27231
27584
  this.gqlClient = gqlClient;
27232
27585
  if (!this.intervalId) {
27233
- this.startPeriodicScanning(path35);
27234
- this.executeInitialScan(path35);
27235
- void this.executeInitialFullScan(path35);
27586
+ this.startPeriodicScanning(path37);
27587
+ this.executeInitialScan(path37);
27588
+ void this.executeInitialFullScan(path37);
27236
27589
  }
27237
27590
  }
27238
- startPeriodicScanning(path35) {
27591
+ startPeriodicScanning(path37) {
27239
27592
  const scanContext = ScanContext.BACKGROUND_PERIODIC;
27240
27593
  logDebug(
27241
27594
  `[${scanContext}] Starting periodic scan for new security vulnerabilities`,
27242
27595
  {
27243
- path: path35
27596
+ path: path37
27244
27597
  }
27245
27598
  );
27246
27599
  this.intervalId = setInterval(() => {
27247
- logDebug(`[${scanContext}] Triggering periodic security scan`, { path: path35 });
27600
+ logDebug(`[${scanContext}] Triggering periodic security scan`, { path: path37 });
27248
27601
  this.scanForSecurityVulnerabilities({
27249
- path: path35,
27602
+ path: path37,
27250
27603
  scanContext
27251
27604
  }).catch((error) => {
27252
27605
  logError(`[${scanContext}] Error during periodic security scan`, {
@@ -27255,45 +27608,45 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
27255
27608
  });
27256
27609
  }, MCP_PERIODIC_CHECK_INTERVAL);
27257
27610
  }
27258
- async executeInitialFullScan(path35) {
27611
+ async executeInitialFullScan(path37) {
27259
27612
  const scanContext = ScanContext.FULL_SCAN;
27260
- logDebug(`[${scanContext}] Triggering initial full security scan`, { path: path35 });
27613
+ logDebug(`[${scanContext}] Triggering initial full security scan`, { path: path37 });
27261
27614
  logDebug(`[${scanContext}] Full scan paths scanned`, {
27262
27615
  fullScanPathsScanned: this.fullScanPathsScanned
27263
27616
  });
27264
- if (this.fullScanPathsScanned.includes(path35)) {
27617
+ if (this.fullScanPathsScanned.includes(path37)) {
27265
27618
  logDebug(`[${scanContext}] Full scan already executed for this path`, {
27266
- path: path35
27619
+ path: path37
27267
27620
  });
27268
27621
  return;
27269
27622
  }
27270
27623
  configStore.set("fullScanPathsScanned", [
27271
27624
  ...this.fullScanPathsScanned,
27272
- path35
27625
+ path37
27273
27626
  ]);
27274
27627
  try {
27275
27628
  await this.scanForSecurityVulnerabilities({
27276
- path: path35,
27629
+ path: path37,
27277
27630
  isAllFilesScan: true,
27278
27631
  isAllDetectionRulesScan: true,
27279
27632
  scanContext: ScanContext.FULL_SCAN
27280
27633
  });
27281
- if (!this.fullScanPathsScanned.includes(path35)) {
27282
- this.fullScanPathsScanned.push(path35);
27634
+ if (!this.fullScanPathsScanned.includes(path37)) {
27635
+ this.fullScanPathsScanned.push(path37);
27283
27636
  configStore.set("fullScanPathsScanned", this.fullScanPathsScanned);
27284
27637
  }
27285
- logInfo(`[${scanContext}] Full scan completed`, { path: path35 });
27638
+ logInfo(`[${scanContext}] Full scan completed`, { path: path37 });
27286
27639
  } catch (error) {
27287
27640
  logError(`[${scanContext}] Error during initial full security scan`, {
27288
27641
  error
27289
27642
  });
27290
27643
  }
27291
27644
  }
27292
- executeInitialScan(path35) {
27645
+ executeInitialScan(path37) {
27293
27646
  const scanContext = ScanContext.BACKGROUND_INITIAL;
27294
- logDebug(`[${scanContext}] Triggering initial security scan`, { path: path35 });
27647
+ logDebug(`[${scanContext}] Triggering initial security scan`, { path: path37 });
27295
27648
  this.scanForSecurityVulnerabilities({
27296
- path: path35,
27649
+ path: path37,
27297
27650
  scanContext: ScanContext.BACKGROUND_INITIAL
27298
27651
  }).catch((error) => {
27299
27652
  logError(`[${scanContext}] Error during initial security scan`, { error });
@@ -27390,9 +27743,9 @@ Example payload:
27390
27743
  `Invalid path: potential security risk detected in path: ${pathValidationResult.error}`
27391
27744
  );
27392
27745
  }
27393
- const path35 = pathValidationResult.path;
27746
+ const path37 = pathValidationResult.path;
27394
27747
  const resultText = await this.newFixesService.getFreshFixes({
27395
- path: path35
27748
+ path: path37
27396
27749
  });
27397
27750
  logInfo("CheckForNewAvailableFixesTool execution completed", {
27398
27751
  resultText
@@ -27570,8 +27923,8 @@ Call this tool instead of ${MCP_TOOL_SCAN_AND_FIX_VULNERABILITIES} when you only
27570
27923
  `Invalid path: potential security risk detected in path: ${pathValidationResult.error}`
27571
27924
  );
27572
27925
  }
27573
- const path35 = pathValidationResult.path;
27574
- const gitService = new GitService(path35, log);
27926
+ const path37 = pathValidationResult.path;
27927
+ const gitService = new GitService(path37, log);
27575
27928
  const gitValidation = await gitService.validateRepository();
27576
27929
  if (!gitValidation.isValid) {
27577
27930
  throw new Error(`Invalid git repository: ${gitValidation.error}`);
@@ -27956,9 +28309,9 @@ Example payload:
27956
28309
  `Invalid path: potential security risk detected in path: ${pathValidationResult.error}`
27957
28310
  );
27958
28311
  }
27959
- const path35 = pathValidationResult.path;
28312
+ const path37 = pathValidationResult.path;
27960
28313
  const files = await getLocalFiles({
27961
- path: path35,
28314
+ path: path37,
27962
28315
  maxFileSize: MCP_MAX_FILE_SIZE,
27963
28316
  maxFiles: args.maxFiles,
27964
28317
  scanContext: ScanContext.USER_REQUEST,
@@ -27978,7 +28331,7 @@ Example payload:
27978
28331
  try {
27979
28332
  const fixResult = await this.vulnerabilityFixService.processVulnerabilities({
27980
28333
  fileList: files.map((file) => file.relativePath),
27981
- repositoryPath: path35,
28334
+ repositoryPath: path37,
27982
28335
  offset: args.offset,
27983
28336
  limit: args.limit,
27984
28337
  isRescan: args.rescan || !!args.maxFiles
@@ -28279,10 +28632,10 @@ init_client_generates();
28279
28632
  init_urlParser2();
28280
28633
 
28281
28634
  // src/features/codeium_intellij/codeium_language_server_grpc_client.ts
28282
- import path32 from "path";
28635
+ import path34 from "path";
28283
28636
  import * as grpc from "@grpc/grpc-js";
28284
28637
  import * as protoLoader from "@grpc/proto-loader";
28285
- var PROTO_PATH = path32.join(
28638
+ var PROTO_PATH = path34.join(
28286
28639
  getModuleRootDir(),
28287
28640
  "src/features/codeium_intellij/proto/exa/language_server_pb/language_server.proto"
28288
28641
  );
@@ -28294,7 +28647,7 @@ function loadProto() {
28294
28647
  defaults: true,
28295
28648
  oneofs: true,
28296
28649
  includeDirs: [
28297
- path32.join(getModuleRootDir(), "src/features/codeium_intellij/proto")
28650
+ path34.join(getModuleRootDir(), "src/features/codeium_intellij/proto")
28298
28651
  ]
28299
28652
  });
28300
28653
  return grpc.loadPackageDefinition(
@@ -28349,29 +28702,29 @@ async function getGrpcClient(port, csrf3) {
28349
28702
 
28350
28703
  // src/features/codeium_intellij/parse_intellij_logs.ts
28351
28704
  import fs27 from "fs";
28352
- import os14 from "os";
28353
- import path33 from "path";
28705
+ import os15 from "os";
28706
+ import path35 from "path";
28354
28707
  function getLogsDir() {
28355
28708
  if (process.platform === "darwin") {
28356
- return path33.join(os14.homedir(), "Library/Logs/JetBrains");
28709
+ return path35.join(os15.homedir(), "Library/Logs/JetBrains");
28357
28710
  } else if (process.platform === "win32") {
28358
- return path33.join(
28359
- process.env["LOCALAPPDATA"] || path33.join(os14.homedir(), "AppData/Local"),
28711
+ return path35.join(
28712
+ process.env["LOCALAPPDATA"] || path35.join(os15.homedir(), "AppData/Local"),
28360
28713
  "JetBrains"
28361
28714
  );
28362
28715
  } else {
28363
- return path33.join(os14.homedir(), ".cache/JetBrains");
28716
+ return path35.join(os15.homedir(), ".cache/JetBrains");
28364
28717
  }
28365
28718
  }
28366
28719
  function parseIdeLogDir(ideLogDir) {
28367
28720
  const logFiles = fs27.readdirSync(ideLogDir).filter((f) => /^idea(\.\d+)?\.log$/.test(f)).map((f) => ({
28368
28721
  name: f,
28369
- mtime: fs27.statSync(path33.join(ideLogDir, f)).mtimeMs
28722
+ mtime: fs27.statSync(path35.join(ideLogDir, f)).mtimeMs
28370
28723
  })).sort((a, b) => a.mtime - b.mtime).map((f) => f.name);
28371
28724
  let latestCsrf = null;
28372
28725
  let latestPort = null;
28373
28726
  for (const logFile of logFiles) {
28374
- const lines = fs27.readFileSync(path33.join(ideLogDir, logFile), "utf-8").split("\n");
28727
+ const lines = fs27.readFileSync(path35.join(ideLogDir, logFile), "utf-8").split("\n");
28375
28728
  for (const line of lines) {
28376
28729
  if (!line.includes(
28377
28730
  "com.codeium.intellij.language_server.LanguageServerProcessHandler"
@@ -28399,9 +28752,9 @@ function findRunningCodeiumLanguageServers() {
28399
28752
  const logsDir = getLogsDir();
28400
28753
  if (!fs27.existsSync(logsDir)) return results;
28401
28754
  for (const ide of fs27.readdirSync(logsDir)) {
28402
- let ideLogDir = path33.join(logsDir, ide);
28755
+ let ideLogDir = path35.join(logsDir, ide);
28403
28756
  if (process.platform !== "darwin") {
28404
- ideLogDir = path33.join(ideLogDir, "log");
28757
+ ideLogDir = path35.join(ideLogDir, "log");
28405
28758
  }
28406
28759
  if (!fs27.existsSync(ideLogDir) || !fs27.statSync(ideLogDir).isDirectory()) {
28407
28760
  continue;
@@ -28583,11 +28936,11 @@ function processChatStepCodeAction(step) {
28583
28936
 
28584
28937
  // src/features/codeium_intellij/install_hook.ts
28585
28938
  import fsPromises5 from "fs/promises";
28586
- import os15 from "os";
28587
- import path34 from "path";
28939
+ import os16 from "os";
28940
+ import path36 from "path";
28588
28941
  import chalk14 from "chalk";
28589
28942
  function getCodeiumHooksPath() {
28590
- return path34.join(os15.homedir(), ".codeium", "hooks.json");
28943
+ return path36.join(os16.homedir(), ".codeium", "hooks.json");
28591
28944
  }
28592
28945
  async function readCodeiumHooks() {
28593
28946
  const hooksPath = getCodeiumHooksPath();
@@ -28600,7 +28953,7 @@ async function readCodeiumHooks() {
28600
28953
  }
28601
28954
  async function writeCodeiumHooks(config2) {
28602
28955
  const hooksPath = getCodeiumHooksPath();
28603
- const dir = path34.dirname(hooksPath);
28956
+ const dir = path36.dirname(hooksPath);
28604
28957
  await fsPromises5.mkdir(dir, { recursive: true });
28605
28958
  await fsPromises5.writeFile(
28606
28959
  hooksPath,