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/args/commands/upload_ai_blame.mjs +91 -11
- package/dist/index.mjs +874 -521
- package/package.json +3 -1
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
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
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
|
|
3605
|
-
if (
|
|
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:
|
|
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
|
|
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
|
|
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
|
+
|
|
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
|
+
|
|
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
|
+
|
|
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
|
+
|
|
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
|
|
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(`${
|
|
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
|
|
7282
|
-
return
|
|
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
|
-
|
|
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
|
-
() =>
|
|
14503
|
-
|
|
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
|
-
|
|
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 =
|
|
14564
|
-
|
|
14565
|
-
|
|
14566
|
-
|
|
14567
|
-
|
|
14568
|
-
|
|
14569
|
-
|
|
14570
|
-
|
|
14571
|
-
|
|
14572
|
-
|
|
14573
|
-
|
|
14574
|
-
|
|
14575
|
-
|
|
14576
|
-
|
|
14577
|
-
|
|
14578
|
-
|
|
14579
|
-
|
|
14580
|
-
|
|
14581
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
15847
|
-
const cxFileName =
|
|
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
|
|
16765
|
-
if (!
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
17246
|
-
{ glob: "CLAUDE.local.md", category:
|
|
17247
|
-
{ glob: "INSIGHTS.md", category:
|
|
17248
|
-
{ glob: "AGENTS.md", category:
|
|
17249
|
-
{
|
|
17250
|
-
|
|
17251
|
-
|
|
17252
|
-
|
|
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:
|
|
17367
|
+
category: CATEGORY.MEMORY,
|
|
17256
17368
|
root: "home"
|
|
17257
17369
|
},
|
|
17258
17370
|
{ kind: "skill-bundle", skillsRoot: ".claude/skills", root: "workspace" },
|
|
17259
|
-
{
|
|
17371
|
+
{
|
|
17372
|
+
glob: ".claude/commands/*.md",
|
|
17373
|
+
category: CATEGORY.COMMAND,
|
|
17374
|
+
root: "workspace"
|
|
17375
|
+
},
|
|
17260
17376
|
{
|
|
17261
17377
|
glob: ".claude/agents/*.md",
|
|
17262
|
-
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:
|
|
17267
|
-
{ glob: ".claude/agents/*.md", category:
|
|
17268
|
-
{
|
|
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:
|
|
17391
|
+
category: CATEGORY.CONFIG,
|
|
17272
17392
|
root: "workspace"
|
|
17273
17393
|
},
|
|
17274
|
-
{ glob: ".mcp.json", category:
|
|
17275
|
-
{
|
|
17276
|
-
|
|
17277
|
-
|
|
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:
|
|
17405
|
+
{ glob: ".cursorrules", category: CATEGORY.RULE, root: "workspace" },
|
|
17282
17406
|
// Project Rules — docs support both `.mdc` and `.md` inside .cursor/rules/
|
|
17283
|
-
{
|
|
17284
|
-
|
|
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:
|
|
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
|
-
{
|
|
17295
|
-
|
|
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:
|
|
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:
|
|
17447
|
+
category: CATEGORY.RULE,
|
|
17312
17448
|
root: "workspace"
|
|
17313
17449
|
},
|
|
17314
17450
|
{
|
|
17315
17451
|
glob: ".github/instructions/**/*.instructions.md",
|
|
17316
|
-
category:
|
|
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:
|
|
17322
|
-
{ glob: "CLAUDE.md", category:
|
|
17323
|
-
{ glob: "CLAUDE.local.md", category:
|
|
17324
|
-
{ glob: ".claude/CLAUDE.md", category:
|
|
17325
|
-
{
|
|
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:
|
|
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:
|
|
17476
|
+
category: CATEGORY.AGENT_CONFIG,
|
|
17337
17477
|
root: "workspace"
|
|
17338
17478
|
},
|
|
17339
17479
|
{
|
|
17340
17480
|
glob: ".github/chatmodes/*.chatmode.md",
|
|
17341
|
-
category:
|
|
17481
|
+
category: CATEGORY.AGENT_CONFIG,
|
|
17342
17482
|
root: "workspace"
|
|
17343
17483
|
},
|
|
17344
17484
|
{
|
|
17345
17485
|
glob: ".claude/agents/*.md",
|
|
17346
|
-
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
|
-
{
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
17377
|
-
{ glob: ".claude/rules/**/*.md", category:
|
|
17378
|
-
{ glob: ".claude/agents/*.md", category:
|
|
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: "
|
|
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(
|
|
17550
|
-
if (!settingsCache.has(
|
|
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(
|
|
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((
|
|
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
|
|
18123
|
+
/** Count of skills enumerated in this run. */
|
|
17970
18124
|
SKILLS_CHECKED: "skill_quarantine.skills_checked",
|
|
17971
|
-
/** A skill was freshly quarantined.
|
|
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
|
-
/**
|
|
17976
|
-
|
|
17977
|
-
/** Stub
|
|
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
|
-
/**
|
|
17980
|
-
|
|
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
|
-
|
|
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
|
|
18000
|
-
import
|
|
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
|
|
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
|
|
18015
|
-
return path16.join(getQuarantineRoot(), `${md5}_tmp_${
|
|
18181
|
+
function getTmpZipPath(md5, uuid) {
|
|
18182
|
+
return path16.join(getQuarantineRoot(), `${md5}_tmp_${uuid}.zip`);
|
|
18016
18183
|
}
|
|
18017
|
-
var
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
18211
|
+
The original ${folderOrFile} has been archived to:
|
|
18040
18212
|
|
|
18041
|
-
${params.
|
|
18213
|
+
${params.quarantinedZipPath}
|
|
18042
18214
|
|
|
18043
|
-
Nothing has been deleted. The
|
|
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
|
-
|
|
18222
|
+
${recoverCommand}
|
|
18050
18223
|
|
|
18051
|
-
Tracy will not re-quarantine it as long as
|
|
18052
|
-
|
|
18053
|
-
|
|
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
|
|
18073
|
-
if (
|
|
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
|
|
18247
|
+
"skill_quarantine: already quarantined"
|
|
18077
18248
|
);
|
|
18078
18249
|
return { status: "already_quarantined" };
|
|
18079
18250
|
}
|
|
18080
|
-
|
|
18251
|
+
const tmpZip = getTmpZipPath(md5, randomUUID());
|
|
18081
18252
|
try {
|
|
18082
|
-
|
|
18083
|
-
|
|
18084
|
-
|
|
18085
|
-
|
|
18086
|
-
|
|
18087
|
-
|
|
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
|
|
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.
|
|
18141
|
-
"skill_quarantine:
|
|
18264
|
+
{ err, md5, metric: Metric.ZIP_ERROR },
|
|
18265
|
+
"skill_quarantine: phase-1 zip write failed"
|
|
18142
18266
|
);
|
|
18143
|
-
return { status: "
|
|
18267
|
+
return { status: "zip_error", err };
|
|
18144
18268
|
}
|
|
18145
18269
|
try {
|
|
18146
|
-
await
|
|
18270
|
+
await writeStub(params);
|
|
18147
18271
|
} catch (err) {
|
|
18148
|
-
await tryRm(stagingDir);
|
|
18149
18272
|
log2.error(
|
|
18150
|
-
{ err, md5, metric: Metric.
|
|
18151
|
-
"skill_quarantine:
|
|
18273
|
+
{ err, md5, skillPath, metric: Metric.STUB_ERROR },
|
|
18274
|
+
"skill_quarantine: stub write failed; tmp zip preserved for reconcile"
|
|
18152
18275
|
);
|
|
18153
|
-
return { status: "
|
|
18276
|
+
return { status: "stub_error", err };
|
|
18154
18277
|
}
|
|
18155
18278
|
try {
|
|
18156
|
-
await rename(
|
|
18279
|
+
await rename(tmpZip, finalZip);
|
|
18157
18280
|
} catch (err) {
|
|
18158
18281
|
log2.error(
|
|
18159
|
-
{
|
|
18160
|
-
|
|
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: "
|
|
18285
|
+
return { status: "publish_error", err };
|
|
18169
18286
|
}
|
|
18170
|
-
|
|
18171
|
-
|
|
18172
|
-
|
|
18173
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
18212
|
-
|
|
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
|
-
|
|
18227
|
-
|
|
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
|
|
18233
|
-
const
|
|
18234
|
-
const
|
|
18235
|
-
|
|
18236
|
-
|
|
18237
|
-
|
|
18238
|
-
|
|
18239
|
-
|
|
18240
|
-
|
|
18241
|
-
|
|
18242
|
-
|
|
18243
|
-
|
|
18244
|
-
|
|
18245
|
-
|
|
18246
|
-
|
|
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
|
|
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
|
|
18257
|
-
log2.warn({ err, root }, "skill_quarantine:
|
|
18258
|
-
return
|
|
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
|
-
|
|
18264
|
-
|
|
18265
|
-
|
|
18266
|
-
|
|
18267
|
-
|
|
18268
|
-
|
|
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
|
-
|
|
18404
|
+
let valid;
|
|
18272
18405
|
try {
|
|
18273
|
-
|
|
18274
|
-
|
|
18275
|
-
|
|
18276
|
-
|
|
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
|
-
|
|
18286
|
-
|
|
18287
|
-
|
|
18288
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
18630
|
+
return path20.join(os4.homedir(), ".mobbdev");
|
|
18419
18631
|
}
|
|
18420
18632
|
function getDaemonCheckScriptPath() {
|
|
18421
|
-
return
|
|
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
|
|
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
|
|
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(
|
|
18757
|
-
scopePath =
|
|
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.
|
|
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 = [
|
|
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
|
-
|
|
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
|
|
19306
|
+
await access2(transcriptPath);
|
|
19069
19307
|
return transcriptPath;
|
|
19070
19308
|
} catch {
|
|
19071
19309
|
}
|
|
19072
|
-
const filename =
|
|
19073
|
-
const dirName =
|
|
19074
|
-
const projectsDir =
|
|
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 =
|
|
19315
|
+
const candidate = path21.join(projectsDir, baseDirName, filename);
|
|
19078
19316
|
try {
|
|
19079
|
-
await
|
|
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 =
|
|
19337
|
+
const candidate = path21.join(projectsDir, dir, filename);
|
|
19100
19338
|
try {
|
|
19101
|
-
await
|
|
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
|
|
19131
|
-
fileSize =
|
|
19132
|
-
if (cursor.byteOffset >=
|
|
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
|
|
19141
|
-
|
|
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
|
-
|
|
19159
|
-
|
|
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
|
-
{
|
|
19163
|
-
|
|
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
|
|
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
|
|
19283
|
-
|
|
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 &&
|
|
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
|
-
|
|
19303
|
-
|
|
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",
|
|
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
|
-
|
|
19401
|
-
|
|
19402
|
-
|
|
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
|
|
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 =
|
|
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
|
|
19965
|
+
import { open as open5, readdir as readdir4, stat as stat4 } from "fs/promises";
|
|
19676
19966
|
import os7 from "os";
|
|
19677
|
-
import
|
|
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(
|
|
19973
|
+
dirs.push(path23.join(configDir, "projects"));
|
|
19684
19974
|
}
|
|
19685
|
-
dirs.push(
|
|
19686
|
-
dirs.push(
|
|
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 =
|
|
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
|
|
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 =
|
|
20015
|
+
const projPath = path23.join(projectsDir, projName);
|
|
19726
20016
|
let projStat;
|
|
19727
20017
|
try {
|
|
19728
|
-
projStat = await
|
|
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 =
|
|
20032
|
+
const subagentsDir = path23.join(projPath, entry, "subagents");
|
|
19743
20033
|
try {
|
|
19744
|
-
const s = await
|
|
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 =
|
|
20150
|
+
cleanupConfigDir = path24.dirname(sessionStore.path);
|
|
19846
20151
|
}
|
|
19847
|
-
|
|
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(
|
|
20143
|
-
this.knownWorkspacePath =
|
|
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
|
|
21004
|
-
import
|
|
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 =
|
|
21019
|
-
const claudeIdePath =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
21405
|
+
path25.join(currentDir, ".cursor", "mcp.json"),
|
|
21053
21406
|
// local first
|
|
21054
|
-
|
|
21407
|
+
path25.join(home, ".cursor", "mcp.json")
|
|
21055
21408
|
];
|
|
21056
21409
|
case "windsurf":
|
|
21057
21410
|
return [
|
|
21058
|
-
|
|
21411
|
+
path25.join(currentDir, ".codeium", "mcp_config.json"),
|
|
21059
21412
|
// local first
|
|
21060
|
-
|
|
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
|
-
|
|
21420
|
+
path25.join(currentDir, ".vscode", "mcp.json"),
|
|
21068
21421
|
// local first
|
|
21069
|
-
process.platform === "win32" ?
|
|
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
|
-
|
|
21433
|
+
path25.join(currentDir, ".claude.json"),
|
|
21081
21434
|
// local first
|
|
21082
|
-
|
|
21435
|
+
path25.join(home, ".claude.json")
|
|
21083
21436
|
];
|
|
21084
21437
|
const workspacePaths = getClaudeWorkspacePaths();
|
|
21085
21438
|
for (const workspacePath of workspacePaths) {
|
|
21086
|
-
claudePaths.push(
|
|
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
|
|
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 =
|
|
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((
|
|
21600
|
+
configPaths.forEach((path37) => ideConfigPaths.add(path37));
|
|
21248
21601
|
}
|
|
21249
21602
|
const uniqueAdditionalPaths = additionalMcpList.filter(
|
|
21250
|
-
(
|
|
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 =
|
|
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
|
|
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
|
|
21372
|
-
import
|
|
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 =
|
|
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 =
|
|
21407
|
-
const platform2 =
|
|
21759
|
+
const home = os10.homedir();
|
|
21760
|
+
const platform2 = os10.platform();
|
|
21408
21761
|
const knownDirs = platform2 === "win32" ? [
|
|
21409
|
-
|
|
21410
|
-
|
|
21411
|
-
|
|
21762
|
+
path26.join(home, ".cursor"),
|
|
21763
|
+
path26.join(home, "Documents"),
|
|
21764
|
+
path26.join(home, "Downloads")
|
|
21412
21765
|
] : [
|
|
21413
|
-
|
|
21414
|
-
process.env["XDG_CONFIG_HOME"] ||
|
|
21415
|
-
|
|
21416
|
-
|
|
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 =
|
|
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(",") : `${
|
|
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:
|
|
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
|
|
23832
|
-
import * as
|
|
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
|
|
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
|
|
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
|
|
23847
|
-
import * as
|
|
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
|
|
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 =
|
|
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:
|
|
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:
|
|
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(
|
|
24906
|
+
const resolvedRepoPath = await fs22.realpath(path37);
|
|
24554
24907
|
logDebug(`[${scanContext}] Resolved repository path`, {
|
|
24555
24908
|
resolvedRepoPath,
|
|
24556
|
-
originalPath:
|
|
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:
|
|
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:
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
24988
|
-
const extension =
|
|
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(
|
|
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 =
|
|
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 ?
|
|
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
|
|
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
|
|
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 =
|
|
25415
|
-
const basename2 =
|
|
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 =
|
|
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 =
|
|
25633
|
-
const normalizedPath =
|
|
25634
|
-
const repoRootWithSep = repoRoot.endsWith(
|
|
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:
|
|
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 =
|
|
25926
|
-
if (
|
|
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:
|
|
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(
|
|
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 (
|
|
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 (!
|
|
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(
|
|
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
|
|
26398
|
-
import
|
|
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
|
|
26766
|
+
const zip = new AdmZip5();
|
|
26414
26767
|
let packedFilesCount = 0;
|
|
26415
26768
|
const packedFiles = [];
|
|
26416
26769
|
const excludedFiles = [];
|
|
26417
|
-
const resolvedRepoPath =
|
|
26770
|
+
const resolvedRepoPath = path33.resolve(repositoryPath);
|
|
26418
26771
|
for (const filepath of fileList) {
|
|
26419
|
-
const absoluteFilepath =
|
|
26420
|
-
const resolvedFilePath =
|
|
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 =
|
|
26820
|
+
const resolvedRepoPath = path33.resolve(repositoryPath);
|
|
26468
26821
|
const validatedPaths = [];
|
|
26469
26822
|
for (const filepath of fileList) {
|
|
26470
|
-
const absoluteFilepath =
|
|
26471
|
-
const resolvedFilePath =
|
|
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 =
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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 ${
|
|
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:
|
|
27517
|
+
async getFreshFixes({ path: path37 }) {
|
|
27165
27518
|
const scanContext = ScanContext.USER_REQUEST;
|
|
27166
|
-
logDebug(`[${scanContext}] Getting fresh fixes`, { path:
|
|
27167
|
-
if (this.path !==
|
|
27168
|
-
this.path =
|
|
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:
|
|
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:
|
|
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:
|
|
27576
|
+
path: path37,
|
|
27224
27577
|
gqlClient
|
|
27225
27578
|
}) {
|
|
27226
|
-
if (this.path !==
|
|
27227
|
-
this.path =
|
|
27579
|
+
if (this.path !== path37) {
|
|
27580
|
+
this.path = path37;
|
|
27228
27581
|
this.reset();
|
|
27229
|
-
logInfo(`Reset service state for new path in triggerScan`, { path:
|
|
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(
|
|
27234
|
-
this.executeInitialScan(
|
|
27235
|
-
void this.executeInitialFullScan(
|
|
27586
|
+
this.startPeriodicScanning(path37);
|
|
27587
|
+
this.executeInitialScan(path37);
|
|
27588
|
+
void this.executeInitialFullScan(path37);
|
|
27236
27589
|
}
|
|
27237
27590
|
}
|
|
27238
|
-
startPeriodicScanning(
|
|
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:
|
|
27596
|
+
path: path37
|
|
27244
27597
|
}
|
|
27245
27598
|
);
|
|
27246
27599
|
this.intervalId = setInterval(() => {
|
|
27247
|
-
logDebug(`[${scanContext}] Triggering periodic security scan`, { path:
|
|
27600
|
+
logDebug(`[${scanContext}] Triggering periodic security scan`, { path: path37 });
|
|
27248
27601
|
this.scanForSecurityVulnerabilities({
|
|
27249
|
-
path:
|
|
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(
|
|
27611
|
+
async executeInitialFullScan(path37) {
|
|
27259
27612
|
const scanContext = ScanContext.FULL_SCAN;
|
|
27260
|
-
logDebug(`[${scanContext}] Triggering initial full security scan`, { path:
|
|
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(
|
|
27617
|
+
if (this.fullScanPathsScanned.includes(path37)) {
|
|
27265
27618
|
logDebug(`[${scanContext}] Full scan already executed for this path`, {
|
|
27266
|
-
path:
|
|
27619
|
+
path: path37
|
|
27267
27620
|
});
|
|
27268
27621
|
return;
|
|
27269
27622
|
}
|
|
27270
27623
|
configStore.set("fullScanPathsScanned", [
|
|
27271
27624
|
...this.fullScanPathsScanned,
|
|
27272
|
-
|
|
27625
|
+
path37
|
|
27273
27626
|
]);
|
|
27274
27627
|
try {
|
|
27275
27628
|
await this.scanForSecurityVulnerabilities({
|
|
27276
|
-
path:
|
|
27629
|
+
path: path37,
|
|
27277
27630
|
isAllFilesScan: true,
|
|
27278
27631
|
isAllDetectionRulesScan: true,
|
|
27279
27632
|
scanContext: ScanContext.FULL_SCAN
|
|
27280
27633
|
});
|
|
27281
|
-
if (!this.fullScanPathsScanned.includes(
|
|
27282
|
-
this.fullScanPathsScanned.push(
|
|
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:
|
|
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(
|
|
27645
|
+
executeInitialScan(path37) {
|
|
27293
27646
|
const scanContext = ScanContext.BACKGROUND_INITIAL;
|
|
27294
|
-
logDebug(`[${scanContext}] Triggering initial security scan`, { path:
|
|
27647
|
+
logDebug(`[${scanContext}] Triggering initial security scan`, { path: path37 });
|
|
27295
27648
|
this.scanForSecurityVulnerabilities({
|
|
27296
|
-
path:
|
|
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
|
|
27746
|
+
const path37 = pathValidationResult.path;
|
|
27394
27747
|
const resultText = await this.newFixesService.getFreshFixes({
|
|
27395
|
-
path:
|
|
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
|
|
27574
|
-
const gitService = new GitService(
|
|
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
|
|
28312
|
+
const path37 = pathValidationResult.path;
|
|
27960
28313
|
const files = await getLocalFiles({
|
|
27961
|
-
path:
|
|
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:
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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
|
|
28353
|
-
import
|
|
28705
|
+
import os15 from "os";
|
|
28706
|
+
import path35 from "path";
|
|
28354
28707
|
function getLogsDir() {
|
|
28355
28708
|
if (process.platform === "darwin") {
|
|
28356
|
-
return
|
|
28709
|
+
return path35.join(os15.homedir(), "Library/Logs/JetBrains");
|
|
28357
28710
|
} else if (process.platform === "win32") {
|
|
28358
|
-
return
|
|
28359
|
-
process.env["LOCALAPPDATA"] ||
|
|
28711
|
+
return path35.join(
|
|
28712
|
+
process.env["LOCALAPPDATA"] || path35.join(os15.homedir(), "AppData/Local"),
|
|
28360
28713
|
"JetBrains"
|
|
28361
28714
|
);
|
|
28362
28715
|
} else {
|
|
28363
|
-
return
|
|
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(
|
|
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(
|
|
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 =
|
|
28755
|
+
let ideLogDir = path35.join(logsDir, ide);
|
|
28403
28756
|
if (process.platform !== "darwin") {
|
|
28404
|
-
ideLogDir =
|
|
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
|
|
28587
|
-
import
|
|
28939
|
+
import os16 from "os";
|
|
28940
|
+
import path36 from "path";
|
|
28588
28941
|
import chalk14 from "chalk";
|
|
28589
28942
|
function getCodeiumHooksPath() {
|
|
28590
|
-
return
|
|
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 =
|
|
28956
|
+
const dir = path36.dirname(hooksPath);
|
|
28604
28957
|
await fsPromises5.mkdir(dir, { recursive: true });
|
|
28605
28958
|
await fsPromises5.writeFile(
|
|
28606
28959
|
hooksPath,
|