mobbdev 1.3.7 → 1.4.0
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 +20 -1
- package/dist/index.mjs +1224 -389
- package/package.json +4 -1
package/dist/index.mjs
CHANGED
|
@@ -129,10 +129,13 @@ function getSdk(client, withWrapper = defaultWrapper) {
|
|
|
129
129
|
},
|
|
130
130
|
ScanSkill(variables, requestHeaders, signal) {
|
|
131
131
|
return withWrapper((wrappedRequestHeaders) => client.request({ document: ScanSkillDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), "ScanSkill", "mutation", variables);
|
|
132
|
+
},
|
|
133
|
+
SkillVerdictsByMd5(variables, requestHeaders, signal) {
|
|
134
|
+
return withWrapper((wrappedRequestHeaders) => client.request({ document: SkillVerdictsByMd5Document, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), "SkillVerdictsByMd5", "query", variables);
|
|
132
135
|
}
|
|
133
136
|
};
|
|
134
137
|
}
|
|
135
|
-
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, defaultWrapper;
|
|
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;
|
|
136
139
|
var init_client_generates = __esm({
|
|
137
140
|
"src/features/analysis/scm/generates/client_generates.ts"() {
|
|
138
141
|
"use strict";
|
|
@@ -1265,6 +1268,18 @@ var init_client_generates = __esm({
|
|
|
1265
1268
|
cached
|
|
1266
1269
|
summary
|
|
1267
1270
|
}
|
|
1271
|
+
}
|
|
1272
|
+
`;
|
|
1273
|
+
SkillVerdictsByMd5Document = `
|
|
1274
|
+
query SkillVerdictsByMd5($md5s: [String!]!) {
|
|
1275
|
+
skillVerdictsByMd5(md5s: $md5s) {
|
|
1276
|
+
md5
|
|
1277
|
+
verdict
|
|
1278
|
+
summary
|
|
1279
|
+
scannerName
|
|
1280
|
+
scannerVersion
|
|
1281
|
+
scannedAt
|
|
1282
|
+
}
|
|
1268
1283
|
}
|
|
1269
1284
|
`;
|
|
1270
1285
|
defaultWrapper = (action, _operationName, _operationType, _variables) => action();
|
|
@@ -3579,8 +3594,8 @@ var init_FileUtils = __esm({
|
|
|
3579
3594
|
const fullPath = path.join(dir, item);
|
|
3580
3595
|
try {
|
|
3581
3596
|
await fsPromises.access(fullPath, fs.constants.R_OK);
|
|
3582
|
-
const
|
|
3583
|
-
if (
|
|
3597
|
+
const stat4 = await fsPromises.stat(fullPath);
|
|
3598
|
+
if (stat4.isDirectory()) {
|
|
3584
3599
|
if (isRootLevel && excludedRootDirectories.includes(item)) {
|
|
3585
3600
|
continue;
|
|
3586
3601
|
}
|
|
@@ -3592,7 +3607,7 @@ var init_FileUtils = __esm({
|
|
|
3592
3607
|
name: item,
|
|
3593
3608
|
fullPath,
|
|
3594
3609
|
relativePath: path.relative(rootDir, fullPath),
|
|
3595
|
-
time:
|
|
3610
|
+
time: stat4.mtime.getTime(),
|
|
3596
3611
|
isFile: true
|
|
3597
3612
|
});
|
|
3598
3613
|
}
|
|
@@ -7132,7 +7147,7 @@ async function getAdoSdk(params) {
|
|
|
7132
7147
|
const url = new URL(repoUrl);
|
|
7133
7148
|
const origin = url.origin.toLowerCase().endsWith(".visualstudio.com") ? DEFUALT_ADO_ORIGIN : url.origin.toLowerCase();
|
|
7134
7149
|
const params2 = `path=/&versionDescriptor[versionOptions]=0&versionDescriptor[versionType]=commit&versionDescriptor[version]=${branch}&resolveLfs=true&$format=zip&api-version=5.0&download=true`;
|
|
7135
|
-
const
|
|
7150
|
+
const path34 = [
|
|
7136
7151
|
prefixPath,
|
|
7137
7152
|
owner,
|
|
7138
7153
|
projectName,
|
|
@@ -7143,7 +7158,7 @@ async function getAdoSdk(params) {
|
|
|
7143
7158
|
"items",
|
|
7144
7159
|
"items"
|
|
7145
7160
|
].filter(Boolean).join("/");
|
|
7146
|
-
return new URL(`${
|
|
7161
|
+
return new URL(`${path34}?${params2}`, origin).toString();
|
|
7147
7162
|
},
|
|
7148
7163
|
async getAdoBranchList({ repoUrl }) {
|
|
7149
7164
|
try {
|
|
@@ -7232,8 +7247,8 @@ async function getAdoSdk(params) {
|
|
|
7232
7247
|
const changeType = entry.changeType;
|
|
7233
7248
|
return changeType !== 16 && entry.item?.path;
|
|
7234
7249
|
}).map((entry) => {
|
|
7235
|
-
const
|
|
7236
|
-
return
|
|
7250
|
+
const path34 = entry.item.path;
|
|
7251
|
+
return path34.startsWith("/") ? path34.slice(1) : path34;
|
|
7237
7252
|
});
|
|
7238
7253
|
},
|
|
7239
7254
|
async searchAdoPullRequests({
|
|
@@ -13539,6 +13554,10 @@ var GQLClient = class {
|
|
|
13539
13554
|
async scanSkill(variables) {
|
|
13540
13555
|
return await this._clientSdk.ScanSkill(variables);
|
|
13541
13556
|
}
|
|
13557
|
+
// T-467 — batched verdict lookup for the client-side quarantine check.
|
|
13558
|
+
async skillVerdictsByMd5(md5s) {
|
|
13559
|
+
return await this._clientSdk.SkillVerdictsByMd5({ md5s });
|
|
13560
|
+
}
|
|
13542
13561
|
};
|
|
13543
13562
|
|
|
13544
13563
|
// src/features/analysis/graphql/tracy-batch-upload.ts
|
|
@@ -15061,7 +15080,7 @@ async function postIssueComment(params) {
|
|
|
15061
15080
|
fpDescription
|
|
15062
15081
|
} = params;
|
|
15063
15082
|
const {
|
|
15064
|
-
path:
|
|
15083
|
+
path: path34,
|
|
15065
15084
|
startLine,
|
|
15066
15085
|
vulnerabilityReportIssue: {
|
|
15067
15086
|
vulnerabilityReportIssueTags,
|
|
@@ -15076,7 +15095,7 @@ async function postIssueComment(params) {
|
|
|
15076
15095
|
Refresh the page in order to see the changes.`,
|
|
15077
15096
|
pull_number: pullRequest,
|
|
15078
15097
|
commit_id: commitSha,
|
|
15079
|
-
path:
|
|
15098
|
+
path: path34,
|
|
15080
15099
|
line: startLine
|
|
15081
15100
|
});
|
|
15082
15101
|
const commentId = commentRes.data.id;
|
|
@@ -15110,7 +15129,7 @@ async function postFixComment(params) {
|
|
|
15110
15129
|
scanner
|
|
15111
15130
|
} = params;
|
|
15112
15131
|
const {
|
|
15113
|
-
path:
|
|
15132
|
+
path: path34,
|
|
15114
15133
|
startLine,
|
|
15115
15134
|
vulnerabilityReportIssue: { fixId, vulnerabilityReportIssueTags, category },
|
|
15116
15135
|
vulnerabilityReportIssueId
|
|
@@ -15128,7 +15147,7 @@ async function postFixComment(params) {
|
|
|
15128
15147
|
Refresh the page in order to see the changes.`,
|
|
15129
15148
|
pull_number: pullRequest,
|
|
15130
15149
|
commit_id: commitSha,
|
|
15131
|
-
path:
|
|
15150
|
+
path: path34,
|
|
15132
15151
|
line: startLine
|
|
15133
15152
|
});
|
|
15134
15153
|
const commentId = commentRes.data.id;
|
|
@@ -16650,8 +16669,8 @@ async function resolveSkillScanInput(skillInput) {
|
|
|
16650
16669
|
if (!fs11.existsSync(resolvedPath)) {
|
|
16651
16670
|
return skillInput;
|
|
16652
16671
|
}
|
|
16653
|
-
const
|
|
16654
|
-
if (!
|
|
16672
|
+
const stat4 = fs11.statSync(resolvedPath);
|
|
16673
|
+
if (!stat4.isDirectory()) {
|
|
16655
16674
|
throw new CliError(
|
|
16656
16675
|
"Local skill input must be a directory containing SKILL.md"
|
|
16657
16676
|
);
|
|
@@ -17050,108 +17069,23 @@ import { spawn } from "child_process";
|
|
|
17050
17069
|
|
|
17051
17070
|
// src/features/claude_code/daemon.ts
|
|
17052
17071
|
import { readFileSync, writeFileSync as writeFileSync2 } from "fs";
|
|
17053
|
-
import
|
|
17072
|
+
import path21 from "path";
|
|
17054
17073
|
import { setTimeout as sleep2 } from "timers/promises";
|
|
17055
17074
|
import Configstore3 from "configstore";
|
|
17056
17075
|
|
|
17057
|
-
// src/features/
|
|
17058
|
-
|
|
17059
|
-
|
|
17060
|
-
|
|
17061
|
-
|
|
17062
|
-
|
|
17063
|
-
var
|
|
17064
|
-
var
|
|
17065
|
-
var
|
|
17066
|
-
var STALE_KEY_MAX_AGE_MS = 14 * 24 * 60 * 60 * 1e3;
|
|
17067
|
-
var CLEANUP_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
17068
|
-
var DAEMON_TTL_MS = 30 * 60 * 1e3;
|
|
17069
|
-
var DAEMON_POLL_INTERVAL_MS = 1e4;
|
|
17070
|
-
var HEARTBEAT_STALE_MS = 3e4;
|
|
17071
|
-
var TRANSCRIPT_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
17072
|
-
var DAEMON_CHUNK_SIZE = 50;
|
|
17073
|
-
|
|
17074
|
-
// src/features/claude_code/daemon_pid_file.ts
|
|
17075
|
-
function getMobbdevDir() {
|
|
17076
|
-
return path13.join(os4.homedir(), ".mobbdev");
|
|
17077
|
-
}
|
|
17078
|
-
function getDaemonCheckScriptPath() {
|
|
17079
|
-
return path13.join(getMobbdevDir(), "daemon-check.js");
|
|
17080
|
-
}
|
|
17081
|
-
var DaemonPidFile = class {
|
|
17082
|
-
constructor() {
|
|
17083
|
-
__publicField(this, "data", null);
|
|
17084
|
-
}
|
|
17085
|
-
get filePath() {
|
|
17086
|
-
return path13.join(getMobbdevDir(), "daemon.pid");
|
|
17087
|
-
}
|
|
17088
|
-
/** Ensure ~/.mobbdev/ directory exists. */
|
|
17089
|
-
ensureDir() {
|
|
17090
|
-
fs13.mkdirSync(getMobbdevDir(), { recursive: true });
|
|
17091
|
-
}
|
|
17092
|
-
/** Read the PID file from disk. Returns the parsed data or null. */
|
|
17093
|
-
read() {
|
|
17094
|
-
try {
|
|
17095
|
-
const raw = fs13.readFileSync(this.filePath, "utf8");
|
|
17096
|
-
const parsed = JSON.parse(raw);
|
|
17097
|
-
if (typeof parsed.pid !== "number" || typeof parsed.startedAt !== "number" || typeof parsed.heartbeat !== "number") {
|
|
17098
|
-
this.data = null;
|
|
17099
|
-
} else {
|
|
17100
|
-
this.data = parsed;
|
|
17101
|
-
}
|
|
17102
|
-
} catch {
|
|
17103
|
-
this.data = null;
|
|
17104
|
-
}
|
|
17105
|
-
return this.data;
|
|
17106
|
-
}
|
|
17107
|
-
/** Write a new PID file for the given process id. */
|
|
17108
|
-
write(pid, version) {
|
|
17109
|
-
this.data = {
|
|
17110
|
-
pid,
|
|
17111
|
-
startedAt: Date.now(),
|
|
17112
|
-
heartbeat: Date.now(),
|
|
17113
|
-
version
|
|
17114
|
-
};
|
|
17115
|
-
fs13.writeFileSync(this.filePath, JSON.stringify(this.data), "utf8");
|
|
17116
|
-
}
|
|
17117
|
-
/** Update the heartbeat timestamp of the current PID file. */
|
|
17118
|
-
updateHeartbeat() {
|
|
17119
|
-
if (!this.data) this.read();
|
|
17120
|
-
if (!this.data) return;
|
|
17121
|
-
this.data.heartbeat = Date.now();
|
|
17122
|
-
fs13.writeFileSync(this.filePath, JSON.stringify(this.data), "utf8");
|
|
17123
|
-
}
|
|
17124
|
-
/** Check whether the previously read PID data represents a live daemon. */
|
|
17125
|
-
isAlive() {
|
|
17126
|
-
if (!this.data) return false;
|
|
17127
|
-
if (Date.now() - this.data.heartbeat > HEARTBEAT_STALE_MS) return false;
|
|
17128
|
-
try {
|
|
17129
|
-
process.kill(this.data.pid, 0);
|
|
17130
|
-
return true;
|
|
17131
|
-
} catch {
|
|
17132
|
-
return false;
|
|
17133
|
-
}
|
|
17134
|
-
}
|
|
17135
|
-
/** Remove the PID file from disk (best-effort). */
|
|
17136
|
-
remove() {
|
|
17137
|
-
this.data = null;
|
|
17138
|
-
try {
|
|
17139
|
-
fs13.unlinkSync(this.filePath);
|
|
17140
|
-
} catch {
|
|
17141
|
-
}
|
|
17142
|
-
}
|
|
17143
|
-
};
|
|
17144
|
-
|
|
17145
|
-
// src/features/claude_code/data_collector.ts
|
|
17146
|
-
import { execFile } from "child_process";
|
|
17147
|
-
import { createHash as createHash3 } from "crypto";
|
|
17148
|
-
import { access, open as open4, readdir, readFile as readFile2, unlink } from "fs/promises";
|
|
17149
|
-
import path16 from "path";
|
|
17150
|
-
import { promisify } from "util";
|
|
17076
|
+
// src/features/analysis/skill_quarantine/constants.ts
|
|
17077
|
+
var HEARTBEAT_DEBOUNCE_MS = (() => {
|
|
17078
|
+
const raw = Number(process.env["MOBB_TRACY_SKILL_QUARANTINE_DEBOUNCE_MS"]);
|
|
17079
|
+
if (!Number.isFinite(raw) || raw < 0) return 3e4;
|
|
17080
|
+
return Math.min(raw, 3e5);
|
|
17081
|
+
})();
|
|
17082
|
+
var KILL_SWITCH_ENV = "MOBB_TRACY_SKILL_QUARANTINE_DISABLE";
|
|
17083
|
+
var MALICIOUS_VERDICT = "MALICIOUS";
|
|
17084
|
+
var ORPHAN_SWEEP_GRACE_MS = 10 * 60 * 1e3;
|
|
17151
17085
|
|
|
17152
17086
|
// src/features/analysis/context_file_processor.ts
|
|
17153
17087
|
import { createHash } from "crypto";
|
|
17154
|
-
import
|
|
17088
|
+
import path13 from "path";
|
|
17155
17089
|
import AdmZip3 from "adm-zip";
|
|
17156
17090
|
import pLimit6 from "p-limit";
|
|
17157
17091
|
var SANITIZE_CONCURRENCY = 5;
|
|
@@ -17185,8 +17119,12 @@ async function processContextFiles(regularFiles, skillGroups) {
|
|
|
17185
17119
|
);
|
|
17186
17120
|
for (const file of sortedFiles) {
|
|
17187
17121
|
const sanitizedContent = await sanitizeFileContent(file.content);
|
|
17188
|
-
const zipEntryName = group.isFolder ?
|
|
17122
|
+
const zipEntryName = group.isFolder ? path13.relative(group.skillPath, file.path).replace(/\\/g, "/") : path13.basename(file.path);
|
|
17189
17123
|
zip.addFile(zipEntryName, Buffer.from(sanitizedContent, "utf-8"));
|
|
17124
|
+
const entry = zip.getEntry(zipEntryName);
|
|
17125
|
+
if (entry) {
|
|
17126
|
+
entry.header.time = /* @__PURE__ */ new Date(0);
|
|
17127
|
+
}
|
|
17190
17128
|
}
|
|
17191
17129
|
const zipBuffer = zip.toBuffer();
|
|
17192
17130
|
const md5 = md5Hex(zipBuffer);
|
|
@@ -17198,30 +17136,14 @@ async function processContextFiles(regularFiles, skillGroups) {
|
|
|
17198
17136
|
}
|
|
17199
17137
|
|
|
17200
17138
|
// src/features/analysis/context_file_scanner.ts
|
|
17201
|
-
import { readFile, stat } from "fs/promises";
|
|
17139
|
+
import { lstat, readFile, stat } from "fs/promises";
|
|
17202
17140
|
import { homedir } from "os";
|
|
17203
|
-
import
|
|
17141
|
+
import path14 from "path";
|
|
17204
17142
|
import { globby as globby2 } from "globby";
|
|
17205
|
-
|
|
17143
|
+
import { parse as parseJsoncLib } from "jsonc-parser";
|
|
17144
|
+
|
|
17145
|
+
// src/features/analysis/context_file_scan_paths.ts
|
|
17206
17146
|
var SKILL_CATEGORY = "skill";
|
|
17207
|
-
var SESSION_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
17208
|
-
var sessionMtimes = /* @__PURE__ */ new Map();
|
|
17209
|
-
function markContextFilesUploaded(sessionId, files, skills) {
|
|
17210
|
-
let entry = sessionMtimes.get(sessionId);
|
|
17211
|
-
if (!entry) {
|
|
17212
|
-
entry = { files: /* @__PURE__ */ new Map(), skills: /* @__PURE__ */ new Map(), lastUpdatedAt: Date.now() };
|
|
17213
|
-
sessionMtimes.set(sessionId, entry);
|
|
17214
|
-
}
|
|
17215
|
-
for (const f of files) {
|
|
17216
|
-
entry.files.set(f.path, f.mtimeMs);
|
|
17217
|
-
}
|
|
17218
|
-
if (skills) {
|
|
17219
|
-
for (const sg of skills) {
|
|
17220
|
-
entry.skills.set(sg.sessionKey, sg.maxMtimeMs);
|
|
17221
|
-
}
|
|
17222
|
-
}
|
|
17223
|
-
entry.lastUpdatedAt = Date.now();
|
|
17224
|
-
}
|
|
17225
17147
|
var SCAN_PATHS = {
|
|
17226
17148
|
"claude-code": [
|
|
17227
17149
|
{ glob: "CLAUDE.md", category: "rule", root: "workspace" },
|
|
@@ -17237,18 +17159,14 @@ var SCAN_PATHS = {
|
|
|
17237
17159
|
category: "memory",
|
|
17238
17160
|
root: "home"
|
|
17239
17161
|
},
|
|
17240
|
-
{
|
|
17241
|
-
glob: ".claude/skills/**/*",
|
|
17242
|
-
category: SKILL_CATEGORY,
|
|
17243
|
-
root: "workspace"
|
|
17244
|
-
},
|
|
17162
|
+
{ kind: "skill-bundle", skillsRoot: ".claude/skills", root: "workspace" },
|
|
17245
17163
|
{ glob: ".claude/commands/*.md", category: "command", root: "workspace" },
|
|
17246
17164
|
{
|
|
17247
17165
|
glob: ".claude/agents/*.md",
|
|
17248
17166
|
category: "agent-config",
|
|
17249
17167
|
root: "workspace"
|
|
17250
17168
|
},
|
|
17251
|
-
{
|
|
17169
|
+
{ kind: "skill-bundle", skillsRoot: ".claude/skills", root: "home" },
|
|
17252
17170
|
{ glob: ".claude/commands/*.md", category: "command", root: "home" },
|
|
17253
17171
|
{ glob: ".claude/agents/*.md", category: "agent-config", root: "home" },
|
|
17254
17172
|
{ glob: ".claude/settings.json", category: "config", root: "workspace" },
|
|
@@ -17263,126 +17181,452 @@ var SCAN_PATHS = {
|
|
|
17263
17181
|
{ glob: ".claudeignore", category: "ignore", root: "workspace" }
|
|
17264
17182
|
],
|
|
17265
17183
|
cursor: [
|
|
17184
|
+
// Legacy single-file rules
|
|
17266
17185
|
{ glob: ".cursorrules", category: "rule", root: "workspace" },
|
|
17186
|
+
// Project Rules — docs support both `.mdc` and `.md` inside .cursor/rules/
|
|
17267
17187
|
{ glob: ".cursor/rules/**/*.mdc", category: "rule", root: "workspace" },
|
|
17188
|
+
{ glob: ".cursor/rules/**/*.md", category: "rule", root: "workspace" },
|
|
17189
|
+
// AGENTS.md — Cursor's documented alternative to .cursor/rules/
|
|
17190
|
+
{ glob: "AGENTS.md", category: "rule", root: "workspace" },
|
|
17191
|
+
// Agent skills — Cursor auto-loads from these dirs plus compat with
|
|
17192
|
+
// Claude / Codex / generic .agents/ per Cursor docs.
|
|
17193
|
+
{ kind: "skill-bundle", skillsRoot: ".cursor/skills", root: "workspace" },
|
|
17194
|
+
{ kind: "skill-bundle", skillsRoot: ".agents/skills", root: "workspace" },
|
|
17195
|
+
{ kind: "skill-bundle", skillsRoot: ".claude/skills", root: "workspace" },
|
|
17196
|
+
{ kind: "skill-bundle", skillsRoot: ".codex/skills", root: "workspace" },
|
|
17197
|
+
// MCP — project + global
|
|
17268
17198
|
{ glob: ".cursor/mcp.json", category: "mcp-config", root: "workspace" },
|
|
17269
17199
|
{ glob: ".cursor/mcp.json", category: "mcp-config", root: "home" },
|
|
17200
|
+
// Home skills (user-level cross-project skills)
|
|
17201
|
+
{ kind: "skill-bundle", skillsRoot: ".cursor/skills", root: "home" },
|
|
17202
|
+
{ kind: "skill-bundle", skillsRoot: ".agents/skills", root: "home" },
|
|
17203
|
+
{ kind: "skill-bundle", skillsRoot: ".claude/skills", root: "home" },
|
|
17204
|
+
{ kind: "skill-bundle", skillsRoot: ".codex/skills", root: "home" },
|
|
17205
|
+
// Exclusion
|
|
17270
17206
|
{ glob: ".cursorignore", category: "ignore", root: "workspace" }
|
|
17207
|
+
// Note: Cursor's global "Rules for AI" from Settings UI is stored in
|
|
17208
|
+
// Cursor's internal settings DB. The tracer_ext reads it via VS Code API
|
|
17209
|
+
// (vscode.workspace.getConfiguration) and includes it as a synthetic entry.
|
|
17271
17210
|
],
|
|
17272
17211
|
copilot: [
|
|
17212
|
+
// Instructions — workspace
|
|
17273
17213
|
{
|
|
17274
17214
|
glob: ".github/copilot-instructions.md",
|
|
17275
17215
|
category: "rule",
|
|
17276
17216
|
root: "workspace"
|
|
17277
17217
|
},
|
|
17278
17218
|
{
|
|
17279
|
-
glob: ".github/instructions
|
|
17219
|
+
glob: ".github/instructions/**/*.instructions.md",
|
|
17280
17220
|
category: "rule",
|
|
17281
17221
|
root: "workspace"
|
|
17282
17222
|
},
|
|
17223
|
+
// AGENTS.md / CLAUDE.md family (Copilot reads these via chat.useAgentsMdFile,
|
|
17224
|
+
// chat.useClaudeMdFile for cross-compat with Claude Code / other agents).
|
|
17225
|
+
{ glob: "AGENTS.md", category: "rule", root: "workspace" },
|
|
17226
|
+
{ glob: "CLAUDE.md", category: "rule", root: "workspace" },
|
|
17227
|
+
{ glob: "CLAUDE.local.md", category: "rule", root: "workspace" },
|
|
17228
|
+
{ glob: ".claude/CLAUDE.md", category: "rule", root: "workspace" },
|
|
17229
|
+
{ glob: ".claude/rules/**/*.md", category: "rule", root: "workspace" },
|
|
17230
|
+
// Prompts — workspace
|
|
17283
17231
|
{
|
|
17284
17232
|
glob: ".github/prompts/*.prompt.md",
|
|
17285
17233
|
category: SKILL_CATEGORY,
|
|
17286
17234
|
root: "workspace"
|
|
17287
17235
|
},
|
|
17236
|
+
// Custom agents — `.agent.md` is the current format; `.chatmode.md` is the
|
|
17237
|
+
// legacy naming docs recommend renaming. We scan both for transition.
|
|
17238
|
+
{
|
|
17239
|
+
glob: ".github/agents/*.agent.md",
|
|
17240
|
+
category: "agent-config",
|
|
17241
|
+
root: "workspace"
|
|
17242
|
+
},
|
|
17288
17243
|
{
|
|
17289
17244
|
glob: ".github/chatmodes/*.chatmode.md",
|
|
17290
|
-
category:
|
|
17245
|
+
category: "agent-config",
|
|
17246
|
+
root: "workspace"
|
|
17247
|
+
},
|
|
17248
|
+
{
|
|
17249
|
+
glob: ".claude/agents/*.md",
|
|
17250
|
+
category: "agent-config",
|
|
17291
17251
|
root: "workspace"
|
|
17292
17252
|
},
|
|
17253
|
+
// Agent skills — Copilot discovers skills in all three roots (VS Code docs:
|
|
17254
|
+
// Agent Skills). Each skill is a directory with SKILL.md plus sibling files.
|
|
17255
|
+
{ kind: "skill-bundle", skillsRoot: ".github/skills", root: "workspace" },
|
|
17256
|
+
{ kind: "skill-bundle", skillsRoot: ".claude/skills", root: "workspace" },
|
|
17257
|
+
{ kind: "skill-bundle", skillsRoot: ".agents/skills", root: "workspace" },
|
|
17258
|
+
// MCP — VS Code Copilot reads MCP servers from .vscode/mcp.json
|
|
17259
|
+
{ glob: ".vscode/mcp.json", category: "mcp-config", root: "workspace" },
|
|
17260
|
+
// Global — home (JetBrains stores global instructions here)
|
|
17293
17261
|
{
|
|
17294
17262
|
glob: ".config/github-copilot/global-copilot-instructions.md",
|
|
17295
17263
|
category: "rule",
|
|
17296
17264
|
root: "home"
|
|
17297
|
-
}
|
|
17265
|
+
},
|
|
17266
|
+
// User-level Copilot customizations (~/.copilot/)
|
|
17267
|
+
{
|
|
17268
|
+
glob: ".copilot/instructions/**/*.instructions.md",
|
|
17269
|
+
category: "rule",
|
|
17270
|
+
root: "home"
|
|
17271
|
+
},
|
|
17272
|
+
{ glob: ".copilot/prompts/*.prompt.md", category: "skill", root: "home" },
|
|
17273
|
+
{
|
|
17274
|
+
glob: ".copilot/agents/*.agent.md",
|
|
17275
|
+
category: "agent-config",
|
|
17276
|
+
root: "home"
|
|
17277
|
+
},
|
|
17278
|
+
{ kind: "skill-bundle", skillsRoot: ".copilot/skills", root: "home" },
|
|
17279
|
+
// Cross-compat home paths (Copilot reads Claude / generic agent dirs too)
|
|
17280
|
+
{ glob: ".claude/CLAUDE.md", category: "rule", root: "home" },
|
|
17281
|
+
{ glob: ".claude/rules/**/*.md", category: "rule", root: "home" },
|
|
17282
|
+
{ glob: ".claude/agents/*.md", category: "agent-config", root: "home" },
|
|
17283
|
+
{ kind: "skill-bundle", skillsRoot: ".claude/skills", root: "home" },
|
|
17284
|
+
{ kind: "skill-bundle", skillsRoot: ".agents/skills", root: "home" }
|
|
17298
17285
|
]
|
|
17299
17286
|
};
|
|
17300
|
-
|
|
17301
|
-
|
|
17302
|
-
|
|
17303
|
-
|
|
17304
|
-
|
|
17305
|
-
|
|
17306
|
-
|
|
17307
|
-
|
|
17308
|
-
|
|
17309
|
-
|
|
17310
|
-
|
|
17311
|
-
|
|
17312
|
-
|
|
17313
|
-
|
|
17314
|
-
|
|
17315
|
-
|
|
17316
|
-
|
|
17317
|
-
const folderName = relFromSkills.slice(0, slashIdx);
|
|
17318
|
-
if (!folderMap.has(folderName)) {
|
|
17319
|
-
folderMap.set(folderName, []);
|
|
17320
|
-
}
|
|
17321
|
-
folderMap.get(folderName).push(f);
|
|
17287
|
+
|
|
17288
|
+
// src/features/analysis/context_file_scanner.ts
|
|
17289
|
+
var MAX_CONTEXT_FILE_SIZE = 20 * 1024 * 1024;
|
|
17290
|
+
var SESSION_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
17291
|
+
var sessionMtimes = /* @__PURE__ */ new Map();
|
|
17292
|
+
function markContextFilesUploaded(sessionId, files, skills) {
|
|
17293
|
+
let entry = sessionMtimes.get(sessionId);
|
|
17294
|
+
if (!entry) {
|
|
17295
|
+
entry = { files: /* @__PURE__ */ new Map(), skills: /* @__PURE__ */ new Map(), lastUpdatedAt: Date.now() };
|
|
17296
|
+
sessionMtimes.set(sessionId, entry);
|
|
17297
|
+
}
|
|
17298
|
+
for (const f of files) {
|
|
17299
|
+
entry.files.set(f.path, f.mtimeMs);
|
|
17300
|
+
}
|
|
17301
|
+
if (skills) {
|
|
17302
|
+
for (const sg of skills) {
|
|
17303
|
+
entry.skills.set(sg.sessionKey, sg.maxMtimeMs);
|
|
17322
17304
|
}
|
|
17323
17305
|
}
|
|
17324
|
-
|
|
17325
|
-
|
|
17326
|
-
|
|
17327
|
-
|
|
17328
|
-
|
|
17329
|
-
|
|
17330
|
-
|
|
17331
|
-
|
|
17332
|
-
|
|
17333
|
-
|
|
17334
|
-
|
|
17335
|
-
|
|
17336
|
-
|
|
17306
|
+
entry.lastUpdatedAt = Date.now();
|
|
17307
|
+
}
|
|
17308
|
+
var COPILOT_CUSTOM_LOCATION_SETTINGS = [
|
|
17309
|
+
{
|
|
17310
|
+
key: "chat.agentSkillsLocations",
|
|
17311
|
+
kind: "skill-bundle"
|
|
17312
|
+
},
|
|
17313
|
+
{
|
|
17314
|
+
key: "chat.instructionsFilesLocations",
|
|
17315
|
+
kind: "glob",
|
|
17316
|
+
category: "rule",
|
|
17317
|
+
glob: "**/*.instructions.md"
|
|
17318
|
+
},
|
|
17319
|
+
{
|
|
17320
|
+
key: "chat.promptFilesLocations",
|
|
17321
|
+
kind: "glob",
|
|
17322
|
+
category: "skill",
|
|
17323
|
+
glob: "**/*.prompt.md"
|
|
17324
|
+
},
|
|
17325
|
+
{
|
|
17326
|
+
key: "chat.agentFilesLocations",
|
|
17327
|
+
kind: "glob",
|
|
17328
|
+
category: "agent-config",
|
|
17329
|
+
glob: "**/*.agent.md"
|
|
17337
17330
|
}
|
|
17338
|
-
|
|
17339
|
-
|
|
17340
|
-
|
|
17341
|
-
|
|
17342
|
-
|
|
17343
|
-
|
|
17344
|
-
|
|
17345
|
-
|
|
17331
|
+
];
|
|
17332
|
+
var CLAUDE_CODE_CUSTOM_LOCATION_SETTINGS = [
|
|
17333
|
+
{
|
|
17334
|
+
key: "autoMemoryDirectory",
|
|
17335
|
+
category: "memory",
|
|
17336
|
+
glob: "*/memory/*.md"
|
|
17337
|
+
}
|
|
17338
|
+
];
|
|
17339
|
+
function parseJsonc(text) {
|
|
17340
|
+
const errors = [];
|
|
17341
|
+
const parsed = parseJsoncLib(text, errors, {
|
|
17342
|
+
allowTrailingComma: true,
|
|
17343
|
+
disallowComments: false
|
|
17344
|
+
});
|
|
17345
|
+
return parsed ?? null;
|
|
17346
|
+
}
|
|
17347
|
+
function extractCustomLocations(value) {
|
|
17348
|
+
if (Array.isArray(value)) {
|
|
17349
|
+
return value.filter(
|
|
17350
|
+
(v) => typeof v === "string" && v.length > 0
|
|
17346
17351
|
);
|
|
17347
|
-
const skillPath = path15.join(baseDir, skillRelPath);
|
|
17348
|
-
const sessionKey = `skill:${root}:${folderName}`;
|
|
17349
|
-
groups.push({
|
|
17350
|
-
name: folderName,
|
|
17351
|
-
root,
|
|
17352
|
-
skillPath,
|
|
17353
|
-
files: folderFiles,
|
|
17354
|
-
isFolder: true,
|
|
17355
|
-
maxMtimeMs,
|
|
17356
|
-
sessionKey
|
|
17357
|
-
});
|
|
17358
17352
|
}
|
|
17359
|
-
|
|
17353
|
+
if (value && typeof value === "object") {
|
|
17354
|
+
return Object.entries(value).filter(([, v]) => v === true).map(([k]) => k).filter((k) => k.length > 0);
|
|
17355
|
+
}
|
|
17356
|
+
return [];
|
|
17360
17357
|
}
|
|
17361
|
-
|
|
17362
|
-
|
|
17363
|
-
|
|
17364
|
-
|
|
17358
|
+
var SENSITIVE_HOME_SUBDIRS = [
|
|
17359
|
+
".ssh",
|
|
17360
|
+
".aws",
|
|
17361
|
+
".gnupg",
|
|
17362
|
+
".config",
|
|
17363
|
+
".kube",
|
|
17364
|
+
".docker",
|
|
17365
|
+
".gcloud",
|
|
17366
|
+
".npmrc",
|
|
17367
|
+
".netrc",
|
|
17368
|
+
".git-credentials",
|
|
17369
|
+
".m2",
|
|
17370
|
+
".pypirc",
|
|
17371
|
+
".pgpass",
|
|
17372
|
+
".boto",
|
|
17373
|
+
".password-store"
|
|
17374
|
+
];
|
|
17375
|
+
function resolveCustomLocationPath(raw, workspaceRoot, home) {
|
|
17376
|
+
let s = raw.replace(/\$\{workspaceFolder\}/g, workspaceRoot);
|
|
17377
|
+
if (/\$\{[^}]+\}/.test(s)) {
|
|
17378
|
+
return null;
|
|
17365
17379
|
}
|
|
17366
|
-
|
|
17367
|
-
|
|
17368
|
-
if (now - entry.lastUpdatedAt > SESSION_TTL_MS) {
|
|
17369
|
-
sessionMtimes.delete(sid);
|
|
17370
|
-
}
|
|
17380
|
+
if (/^~[^/]/.test(s)) {
|
|
17381
|
+
return null;
|
|
17371
17382
|
}
|
|
17372
|
-
|
|
17373
|
-
|
|
17374
|
-
|
|
17375
|
-
|
|
17376
|
-
|
|
17377
|
-
|
|
17378
|
-
|
|
17379
|
-
|
|
17380
|
-
|
|
17381
|
-
|
|
17382
|
-
|
|
17383
|
-
|
|
17383
|
+
if (s.startsWith("~/") || s === "~") {
|
|
17384
|
+
s = path14.join(home, s.slice(1));
|
|
17385
|
+
}
|
|
17386
|
+
if (!path14.isAbsolute(s)) {
|
|
17387
|
+
s = path14.resolve(workspaceRoot, s);
|
|
17388
|
+
}
|
|
17389
|
+
const resolved = path14.normalize(s);
|
|
17390
|
+
if (resolved === "/" || resolved === home) {
|
|
17391
|
+
return null;
|
|
17392
|
+
}
|
|
17393
|
+
for (const sub of SENSITIVE_HOME_SUBDIRS) {
|
|
17394
|
+
const sensitive = path14.join(home, sub);
|
|
17395
|
+
if (resolved === sensitive || resolved.startsWith(`${sensitive}${path14.sep}`)) {
|
|
17396
|
+
return null;
|
|
17397
|
+
}
|
|
17398
|
+
}
|
|
17399
|
+
const relWorkspace = path14.relative(workspaceRoot, resolved);
|
|
17400
|
+
const relHome = path14.relative(home, resolved);
|
|
17401
|
+
const escapesWorkspace = relWorkspace.startsWith("..") || path14.isAbsolute(relWorkspace);
|
|
17402
|
+
const escapesHome = relHome.startsWith("..") || path14.isAbsolute(relHome);
|
|
17403
|
+
if (escapesWorkspace && escapesHome) {
|
|
17404
|
+
return null;
|
|
17405
|
+
}
|
|
17406
|
+
return resolved;
|
|
17407
|
+
}
|
|
17408
|
+
var MAX_SETTINGS_CACHE_SIZE = 50;
|
|
17409
|
+
var settingsCache = /* @__PURE__ */ new Map();
|
|
17410
|
+
async function readJsoncSettings(settingsPath) {
|
|
17411
|
+
try {
|
|
17412
|
+
const lst = await lstat(settingsPath);
|
|
17413
|
+
if (lst.isSymbolicLink()) {
|
|
17414
|
+
return null;
|
|
17415
|
+
}
|
|
17416
|
+
} catch {
|
|
17417
|
+
putSettingsCache(settingsPath, { mtimeMs: null, parsed: null });
|
|
17418
|
+
return null;
|
|
17419
|
+
}
|
|
17420
|
+
let mtimeMs = null;
|
|
17421
|
+
try {
|
|
17422
|
+
const st = await stat(settingsPath);
|
|
17423
|
+
if (!st.isFile()) {
|
|
17424
|
+
putSettingsCache(settingsPath, { mtimeMs: null, parsed: null });
|
|
17425
|
+
return null;
|
|
17426
|
+
}
|
|
17427
|
+
mtimeMs = st.mtimeMs;
|
|
17428
|
+
} catch (err) {
|
|
17429
|
+
if (err.code === "ENOENT") {
|
|
17430
|
+
putSettingsCache(settingsPath, { mtimeMs: null, parsed: null });
|
|
17431
|
+
}
|
|
17432
|
+
return null;
|
|
17433
|
+
}
|
|
17434
|
+
const cached = settingsCache.get(settingsPath);
|
|
17435
|
+
if (cached && cached.mtimeMs === mtimeMs) {
|
|
17436
|
+
return cached.parsed;
|
|
17437
|
+
}
|
|
17438
|
+
let text;
|
|
17439
|
+
try {
|
|
17440
|
+
text = await readFile(settingsPath, "utf-8");
|
|
17441
|
+
} catch {
|
|
17442
|
+
return null;
|
|
17443
|
+
}
|
|
17444
|
+
const parsed = parseJsonc(text);
|
|
17445
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
17446
|
+
putSettingsCache(settingsPath, { mtimeMs, parsed: null });
|
|
17447
|
+
return null;
|
|
17448
|
+
}
|
|
17449
|
+
const payload = parsed;
|
|
17450
|
+
putSettingsCache(settingsPath, { mtimeMs, parsed: payload });
|
|
17451
|
+
return payload;
|
|
17452
|
+
}
|
|
17453
|
+
function putSettingsCache(path34, entry) {
|
|
17454
|
+
if (!settingsCache.has(path34) && settingsCache.size >= MAX_SETTINGS_CACHE_SIZE) {
|
|
17455
|
+
settingsCache.delete(settingsCache.keys().next().value);
|
|
17456
|
+
}
|
|
17457
|
+
settingsCache.set(path34, entry);
|
|
17458
|
+
}
|
|
17459
|
+
async function readCopilotCustomLocations(workspaceRoot) {
|
|
17460
|
+
const parsed = await readJsoncSettings(
|
|
17461
|
+
path14.join(workspaceRoot, ".vscode", "settings.json")
|
|
17462
|
+
);
|
|
17463
|
+
if (!parsed) {
|
|
17464
|
+
return [];
|
|
17465
|
+
}
|
|
17466
|
+
const home = homedir();
|
|
17467
|
+
const dynamic = [];
|
|
17468
|
+
const seen = /* @__PURE__ */ new Set();
|
|
17469
|
+
for (const setting of COPILOT_CUSTOM_LOCATION_SETTINGS) {
|
|
17470
|
+
for (const loc of extractCustomLocations(parsed[setting.key])) {
|
|
17471
|
+
const abs = resolveCustomLocationPath(loc, workspaceRoot, home);
|
|
17472
|
+
if (!abs) {
|
|
17473
|
+
continue;
|
|
17474
|
+
}
|
|
17475
|
+
if (setting.kind === "skill-bundle") {
|
|
17476
|
+
const dedupKey = `skill-bundle:${abs}`;
|
|
17477
|
+
if (seen.has(dedupKey)) {
|
|
17478
|
+
continue;
|
|
17479
|
+
}
|
|
17480
|
+
seen.add(dedupKey);
|
|
17481
|
+
dynamic.push({
|
|
17482
|
+
kind: "skill-bundle",
|
|
17483
|
+
skillsRoot: ".",
|
|
17484
|
+
root: "absolute",
|
|
17485
|
+
absoluteBase: abs
|
|
17486
|
+
});
|
|
17487
|
+
} else {
|
|
17488
|
+
const dedupKey = `${setting.category}:${abs}`;
|
|
17489
|
+
if (seen.has(dedupKey)) {
|
|
17490
|
+
continue;
|
|
17491
|
+
}
|
|
17492
|
+
seen.add(dedupKey);
|
|
17493
|
+
dynamic.push({
|
|
17494
|
+
kind: "glob",
|
|
17495
|
+
glob: setting.glob,
|
|
17496
|
+
category: setting.category,
|
|
17497
|
+
root: "absolute",
|
|
17498
|
+
absoluteBase: abs
|
|
17499
|
+
});
|
|
17500
|
+
}
|
|
17501
|
+
}
|
|
17502
|
+
}
|
|
17503
|
+
return dynamic;
|
|
17504
|
+
}
|
|
17505
|
+
async function readClaudeCodeCustomLocations() {
|
|
17506
|
+
const home = homedir();
|
|
17507
|
+
const parsed = await readJsoncSettings(
|
|
17508
|
+
path14.join(home, ".claude", "settings.json")
|
|
17509
|
+
);
|
|
17510
|
+
if (!parsed) {
|
|
17511
|
+
return [];
|
|
17512
|
+
}
|
|
17513
|
+
const dynamic = [];
|
|
17514
|
+
for (const { key, category, glob } of CLAUDE_CODE_CUSTOM_LOCATION_SETTINGS) {
|
|
17515
|
+
const raw = parsed[key];
|
|
17516
|
+
if (typeof raw !== "string" || raw.length === 0) {
|
|
17517
|
+
continue;
|
|
17518
|
+
}
|
|
17519
|
+
if (/\$\{workspaceFolder\}/.test(raw)) {
|
|
17520
|
+
continue;
|
|
17521
|
+
}
|
|
17522
|
+
const abs = resolveCustomLocationPath(raw, home, home);
|
|
17523
|
+
if (!abs) {
|
|
17524
|
+
continue;
|
|
17525
|
+
}
|
|
17526
|
+
dynamic.push({
|
|
17527
|
+
kind: "glob",
|
|
17528
|
+
glob,
|
|
17529
|
+
category,
|
|
17530
|
+
root: "absolute",
|
|
17531
|
+
absoluteBase: abs
|
|
17532
|
+
});
|
|
17533
|
+
}
|
|
17534
|
+
return dynamic;
|
|
17535
|
+
}
|
|
17536
|
+
async function readCustomLocations(workspaceRoot, platform2) {
|
|
17537
|
+
if (platform2 === "copilot") {
|
|
17538
|
+
return readCopilotCustomLocations(workspaceRoot);
|
|
17539
|
+
}
|
|
17540
|
+
if (platform2 === "claude-code") {
|
|
17541
|
+
return readClaudeCodeCustomLocations();
|
|
17542
|
+
}
|
|
17543
|
+
return [];
|
|
17544
|
+
}
|
|
17545
|
+
function groupSkills(files, root, baseDir) {
|
|
17546
|
+
const skillFiles = files.filter((f) => f.category === SKILL_CATEGORY);
|
|
17547
|
+
const folderMap = /* @__PURE__ */ new Map();
|
|
17548
|
+
const standalone = [];
|
|
17549
|
+
for (const f of skillFiles) {
|
|
17550
|
+
const rel = path14.relative(baseDir, f.path).replace(/\\/g, "/");
|
|
17551
|
+
const skillsMarker = "skills/";
|
|
17552
|
+
const skillsIdx = rel.indexOf(skillsMarker);
|
|
17553
|
+
if (skillsIdx === -1) {
|
|
17554
|
+
standalone.push(f);
|
|
17555
|
+
continue;
|
|
17556
|
+
}
|
|
17557
|
+
const relFromSkills = rel.slice(skillsIdx + skillsMarker.length);
|
|
17558
|
+
const slashIdx = relFromSkills.indexOf("/");
|
|
17559
|
+
if (slashIdx === -1) {
|
|
17560
|
+
standalone.push(f);
|
|
17561
|
+
} else {
|
|
17562
|
+
const folderName = relFromSkills.slice(0, slashIdx);
|
|
17563
|
+
if (!folderMap.has(folderName)) {
|
|
17564
|
+
folderMap.set(folderName, []);
|
|
17565
|
+
}
|
|
17566
|
+
folderMap.get(folderName).push(f);
|
|
17567
|
+
}
|
|
17568
|
+
}
|
|
17569
|
+
const groups = [];
|
|
17570
|
+
for (const f of standalone) {
|
|
17571
|
+
const name = path14.basename(f.path, path14.extname(f.path));
|
|
17572
|
+
const sessionKey = `skill:${root}:${name}`;
|
|
17573
|
+
groups.push({
|
|
17574
|
+
name,
|
|
17575
|
+
root,
|
|
17576
|
+
skillPath: f.path,
|
|
17577
|
+
files: [f],
|
|
17578
|
+
isFolder: false,
|
|
17579
|
+
maxMtimeMs: f.mtimeMs,
|
|
17580
|
+
sessionKey
|
|
17581
|
+
});
|
|
17582
|
+
}
|
|
17583
|
+
for (const [folderName, folderFiles] of folderMap) {
|
|
17584
|
+
const maxMtimeMs = Math.max(...folderFiles.map((f) => f.mtimeMs));
|
|
17585
|
+
const anyFile = folderFiles[0];
|
|
17586
|
+
const rel = path14.relative(baseDir, anyFile.path).replace(/\\/g, "/");
|
|
17587
|
+
const skillsIdx = rel.indexOf("skills/");
|
|
17588
|
+
const skillRelPath = rel.slice(
|
|
17589
|
+
0,
|
|
17590
|
+
skillsIdx + "skills/".length + folderName.length
|
|
17591
|
+
);
|
|
17592
|
+
const skillPath = path14.join(baseDir, skillRelPath);
|
|
17593
|
+
const sessionKey = `skill:${root}:${folderName}`;
|
|
17594
|
+
groups.push({
|
|
17595
|
+
name: folderName,
|
|
17596
|
+
root,
|
|
17597
|
+
skillPath,
|
|
17598
|
+
files: folderFiles,
|
|
17599
|
+
isFolder: true,
|
|
17600
|
+
maxMtimeMs,
|
|
17601
|
+
sessionKey
|
|
17384
17602
|
});
|
|
17385
|
-
|
|
17603
|
+
}
|
|
17604
|
+
return groups;
|
|
17605
|
+
}
|
|
17606
|
+
async function scanContextFiles(workspaceRoot, platform2, sessionId) {
|
|
17607
|
+
const staticEntries = SCAN_PATHS[platform2] ?? [];
|
|
17608
|
+
const dynamicEntries = await readCustomLocations(workspaceRoot, platform2);
|
|
17609
|
+
const entries = [...staticEntries, ...dynamicEntries];
|
|
17610
|
+
if (entries.length === 0) {
|
|
17611
|
+
return { regularFiles: [], skillGroups: [] };
|
|
17612
|
+
}
|
|
17613
|
+
const now = Date.now();
|
|
17614
|
+
for (const [sid, entry] of sessionMtimes) {
|
|
17615
|
+
if (now - entry.lastUpdatedAt > SESSION_TTL_MS) {
|
|
17616
|
+
sessionMtimes.delete(sid);
|
|
17617
|
+
}
|
|
17618
|
+
}
|
|
17619
|
+
const home = homedir();
|
|
17620
|
+
const sessionEntry = sessionId ? sessionMtimes.get(sessionId) : void 0;
|
|
17621
|
+
const allFiles = [];
|
|
17622
|
+
const skillBatches = /* @__PURE__ */ new Map();
|
|
17623
|
+
const seenPaths = /* @__PURE__ */ new Set();
|
|
17624
|
+
for (const entry of entries) {
|
|
17625
|
+
const baseDir = resolveBaseDir(entry, workspaceRoot, home);
|
|
17626
|
+
const scope = scopeForRoot(entry.root);
|
|
17627
|
+
const isDynamic = entry.root === "absolute";
|
|
17628
|
+
const matches = entry.kind === "skill-bundle" ? await enumerateSkillBundle(baseDir, entry.skillsRoot) : await enumerateGlob(entry.glob, baseDir, entry.category, isDynamic);
|
|
17629
|
+
for (const { path: filePath, category } of matches) {
|
|
17386
17630
|
if (seenPaths.has(filePath)) {
|
|
17387
17631
|
continue;
|
|
17388
17632
|
}
|
|
@@ -17400,16 +17644,21 @@ async function scanContextFiles(workspaceRoot, platform2, sessionId) {
|
|
|
17400
17644
|
path: filePath,
|
|
17401
17645
|
content,
|
|
17402
17646
|
sizeBytes,
|
|
17403
|
-
category
|
|
17647
|
+
category,
|
|
17404
17648
|
mtimeMs: fileStat.mtimeMs
|
|
17405
17649
|
};
|
|
17406
|
-
if (
|
|
17407
|
-
|
|
17408
|
-
|
|
17409
|
-
|
|
17410
|
-
|
|
17650
|
+
if (scope) {
|
|
17651
|
+
fileEntry.scope = scope;
|
|
17652
|
+
}
|
|
17653
|
+
if (category === SKILL_CATEGORY) {
|
|
17654
|
+
const effectiveRoot = entry.root === "home" ? "home" : "workspace";
|
|
17655
|
+
const batchKey = `${effectiveRoot}:${baseDir}`;
|
|
17656
|
+
let batch = skillBatches.get(batchKey);
|
|
17657
|
+
if (!batch) {
|
|
17658
|
+
batch = { root: effectiveRoot, baseDir, files: [] };
|
|
17659
|
+
skillBatches.set(batchKey, batch);
|
|
17411
17660
|
}
|
|
17412
|
-
|
|
17661
|
+
batch.files.push(fileEntry);
|
|
17413
17662
|
} else {
|
|
17414
17663
|
const prevMtime = sessionEntry?.files.get(filePath);
|
|
17415
17664
|
if (prevMtime !== void 0 && fileStat.mtimeMs <= prevMtime) {
|
|
@@ -17417,13 +17666,12 @@ async function scanContextFiles(workspaceRoot, platform2, sessionId) {
|
|
|
17417
17666
|
}
|
|
17418
17667
|
allFiles.push(fileEntry);
|
|
17419
17668
|
}
|
|
17420
|
-
} catch
|
|
17669
|
+
} catch {
|
|
17421
17670
|
}
|
|
17422
17671
|
}
|
|
17423
17672
|
}
|
|
17424
17673
|
const allSkillGroups = [];
|
|
17425
|
-
for (const
|
|
17426
|
-
const baseDir = root === "home" ? home : workspaceRoot;
|
|
17674
|
+
for (const { root, baseDir, files } of skillBatches.values()) {
|
|
17427
17675
|
const groups = groupSkills(files, root, baseDir);
|
|
17428
17676
|
for (const group of groups) {
|
|
17429
17677
|
if (sessionEntry) {
|
|
@@ -17437,13 +17685,587 @@ async function scanContextFiles(workspaceRoot, platform2, sessionId) {
|
|
|
17437
17685
|
}
|
|
17438
17686
|
return { regularFiles: allFiles, skillGroups: allSkillGroups };
|
|
17439
17687
|
}
|
|
17688
|
+
async function enumerateGlob(pattern, cwd, category, isDynamic) {
|
|
17689
|
+
let files;
|
|
17690
|
+
try {
|
|
17691
|
+
files = await globby2(pattern, {
|
|
17692
|
+
cwd,
|
|
17693
|
+
absolute: true,
|
|
17694
|
+
onlyFiles: true,
|
|
17695
|
+
dot: true,
|
|
17696
|
+
// Never follow symlinks — a malicious committed repo can place a
|
|
17697
|
+
// symlink at a scanned path (e.g. .github/copilot-instructions.md →
|
|
17698
|
+
// ~/.aws/credentials) and the scanner would read and upload the target.
|
|
17699
|
+
followSymbolicLinks: false,
|
|
17700
|
+
// Dynamic absolute bases additionally cap traversal depth so a
|
|
17701
|
+
// misconfigured setting can't enumerate the whole filesystem.
|
|
17702
|
+
...isDynamic ? { deep: DYNAMIC_SCAN_MAX_DEPTH } : {}
|
|
17703
|
+
});
|
|
17704
|
+
} catch {
|
|
17705
|
+
return [];
|
|
17706
|
+
}
|
|
17707
|
+
return files.map((path34) => ({ path: path34, category }));
|
|
17708
|
+
}
|
|
17709
|
+
async function enumerateSkillBundle(baseDir, skillsRoot) {
|
|
17710
|
+
const skillsDir = path14.resolve(baseDir, skillsRoot);
|
|
17711
|
+
let manifests;
|
|
17712
|
+
try {
|
|
17713
|
+
manifests = await globby2("*/SKILL.md", {
|
|
17714
|
+
cwd: skillsDir,
|
|
17715
|
+
absolute: true,
|
|
17716
|
+
onlyFiles: true,
|
|
17717
|
+
dot: true,
|
|
17718
|
+
followSymbolicLinks: false,
|
|
17719
|
+
deep: SKILL_MANIFEST_SCAN_DEPTH
|
|
17720
|
+
});
|
|
17721
|
+
} catch {
|
|
17722
|
+
return [];
|
|
17723
|
+
}
|
|
17724
|
+
const perSkillResults = await Promise.all(
|
|
17725
|
+
manifests.map(async (manifest) => {
|
|
17726
|
+
const skillDir = path14.dirname(manifest);
|
|
17727
|
+
try {
|
|
17728
|
+
return await globby2("**/*", {
|
|
17729
|
+
cwd: skillDir,
|
|
17730
|
+
absolute: true,
|
|
17731
|
+
onlyFiles: true,
|
|
17732
|
+
dot: true,
|
|
17733
|
+
followSymbolicLinks: false,
|
|
17734
|
+
deep: SKILL_BUNDLE_MAX_DEPTH
|
|
17735
|
+
});
|
|
17736
|
+
} catch {
|
|
17737
|
+
return [];
|
|
17738
|
+
}
|
|
17739
|
+
})
|
|
17740
|
+
);
|
|
17741
|
+
return perSkillResults.flat().map((p) => ({ path: p, category: "skill" }));
|
|
17742
|
+
}
|
|
17743
|
+
var DYNAMIC_SCAN_MAX_DEPTH = 6;
|
|
17744
|
+
var SKILL_MANIFEST_SCAN_DEPTH = 2;
|
|
17745
|
+
var SKILL_BUNDLE_MAX_DEPTH = 5;
|
|
17746
|
+
function resolveBaseDir(entry, workspaceRoot, home) {
|
|
17747
|
+
switch (entry.root) {
|
|
17748
|
+
case "home":
|
|
17749
|
+
return home;
|
|
17750
|
+
case "absolute":
|
|
17751
|
+
return entry.absoluteBase;
|
|
17752
|
+
default:
|
|
17753
|
+
return workspaceRoot;
|
|
17754
|
+
}
|
|
17755
|
+
}
|
|
17756
|
+
function scopeForRoot(root) {
|
|
17757
|
+
if (root === "home" || root === "absolute") {
|
|
17758
|
+
return "user-global";
|
|
17759
|
+
}
|
|
17760
|
+
return void 0;
|
|
17761
|
+
}
|
|
17440
17762
|
function deriveIdentifier(filePath, baseDir) {
|
|
17441
|
-
const relative2 =
|
|
17763
|
+
const relative2 = path14.relative(baseDir, filePath);
|
|
17442
17764
|
if (!relative2.startsWith("..")) {
|
|
17443
17765
|
return relative2.replace(/\\/g, "/");
|
|
17444
17766
|
}
|
|
17445
|
-
return
|
|
17767
|
+
return path14.basename(filePath);
|
|
17768
|
+
}
|
|
17769
|
+
|
|
17770
|
+
// src/features/analysis/skill_quarantine/enumerateInstalledSkills.ts
|
|
17771
|
+
async function enumerateInstalledSkills(workspaceRoot) {
|
|
17772
|
+
const { skillGroups } = await scanContextFiles(
|
|
17773
|
+
workspaceRoot,
|
|
17774
|
+
"claude-code",
|
|
17775
|
+
void 0
|
|
17776
|
+
);
|
|
17777
|
+
if (skillGroups.length === 0) {
|
|
17778
|
+
return [];
|
|
17779
|
+
}
|
|
17780
|
+
const { skills } = await processContextFiles([], skillGroups);
|
|
17781
|
+
return skills.map((s) => {
|
|
17782
|
+
const parts = s.group.skillPath.split(/[\\/]/);
|
|
17783
|
+
const origName = parts[parts.length - 1] || s.group.name;
|
|
17784
|
+
return {
|
|
17785
|
+
skillPath: s.group.skillPath,
|
|
17786
|
+
md5: s.md5,
|
|
17787
|
+
origName,
|
|
17788
|
+
isFolder: s.group.isFolder
|
|
17789
|
+
};
|
|
17790
|
+
});
|
|
17791
|
+
}
|
|
17792
|
+
|
|
17793
|
+
// src/features/analysis/skill_quarantine/metrics.ts
|
|
17794
|
+
var Metric = {
|
|
17795
|
+
/** A heartbeat triggered the quarantine check (after debounce). */
|
|
17796
|
+
CHECK_TRIGGERED: "skill_quarantine.check_triggered",
|
|
17797
|
+
/** The env-var kill switch skipped the run. */
|
|
17798
|
+
CHECK_DISABLED_ENV: "skill_quarantine.check_disabled_env",
|
|
17799
|
+
/** Verdict-query call failed. Fail-open. */
|
|
17800
|
+
QUERY_ERROR: "skill_quarantine.query_error",
|
|
17801
|
+
/** Count of skills enumerated in this run (histogram-ish). */
|
|
17802
|
+
SKILLS_CHECKED: "skill_quarantine.skills_checked",
|
|
17803
|
+
/** A skill was freshly quarantined. Tagged with shape. */
|
|
17804
|
+
QUARANTINED: "skill_quarantine.quarantined",
|
|
17805
|
+
/** Presence check hit; skill already quarantined. */
|
|
17806
|
+
ALREADY_QUARANTINED: "skill_quarantine.already_quarantined",
|
|
17807
|
+
/** Move step failed. Tagged with phase (stage | publish). */
|
|
17808
|
+
MOVE_ERROR: "skill_quarantine.move_error",
|
|
17809
|
+
/** Stub creation failed after the move succeeded. */
|
|
17810
|
+
STUB_ERROR: "skill_quarantine.stub_error",
|
|
17811
|
+
/** A stale staging dir was swept. */
|
|
17812
|
+
ORPHAN_SWEPT: "skill_quarantine.orphan_swept",
|
|
17813
|
+
/** Total run duration including I/O. */
|
|
17814
|
+
DURATION_MS: "skill_quarantine.duration_ms"
|
|
17815
|
+
};
|
|
17816
|
+
|
|
17817
|
+
// src/features/analysis/skill_quarantine/quarantineSkill.ts
|
|
17818
|
+
import { randomUUID } from "crypto";
|
|
17819
|
+
import { existsSync as existsSync2 } from "fs";
|
|
17820
|
+
import {
|
|
17821
|
+
mkdir,
|
|
17822
|
+
readdir,
|
|
17823
|
+
readFile as readFile2,
|
|
17824
|
+
rename,
|
|
17825
|
+
rm,
|
|
17826
|
+
stat as stat2,
|
|
17827
|
+
writeFile
|
|
17828
|
+
} from "fs/promises";
|
|
17829
|
+
import path16 from "path";
|
|
17830
|
+
import { move } from "fs-extra";
|
|
17831
|
+
|
|
17832
|
+
// src/features/analysis/skill_quarantine/paths.ts
|
|
17833
|
+
import { homedir as homedir2 } from "os";
|
|
17834
|
+
import path15 from "path";
|
|
17835
|
+
function getQuarantineRoot() {
|
|
17836
|
+
return path15.join(homedir2(), ".tracy", "quarantine", "claude", "skills");
|
|
17837
|
+
}
|
|
17838
|
+
function getQuarantinedHashDir(md5) {
|
|
17839
|
+
return path15.join(getQuarantineRoot(), md5);
|
|
17840
|
+
}
|
|
17841
|
+
function getQuarantinedTargetPath(md5, origName) {
|
|
17842
|
+
return path15.join(getQuarantinedHashDir(md5), origName);
|
|
17843
|
+
}
|
|
17844
|
+
function getStagingDir(md5, pid, uuid) {
|
|
17845
|
+
return path15.join(getQuarantineRoot(), `${md5}_tmp_${pid}_${uuid}`);
|
|
17846
|
+
}
|
|
17847
|
+
var STAGING_DIR_REGEX = /^([0-9a-f]{32})_tmp_/;
|
|
17848
|
+
|
|
17849
|
+
// src/features/analysis/skill_quarantine/stubTemplate.ts
|
|
17850
|
+
var LEGACY_SUMMARY_FALLBACK = "not available (scan predates current schema)";
|
|
17851
|
+
function renderStub(params) {
|
|
17852
|
+
const folderOrFile = params.isFolder ? "skill folder" : "skill file";
|
|
17853
|
+
const reason = params.summary ?? LEGACY_SUMMARY_FALLBACK;
|
|
17854
|
+
return `# \u26D4 QUARANTINED BY TRACY
|
|
17855
|
+
|
|
17856
|
+
This skill was flagged **MALICIOUS** by the Mobb security scanner and has been
|
|
17857
|
+
moved out of your skills folder. **Claude Code will not execute it** while this
|
|
17858
|
+
stub is in place.
|
|
17859
|
+
|
|
17860
|
+
## Why this skill was flagged
|
|
17861
|
+
|
|
17862
|
+
- **Reason:** ${reason}
|
|
17863
|
+
- **Scanner:** ${params.scannerName} @ ${params.scannerVersion}
|
|
17864
|
+
- **Scanned at:** ${params.scannedAt}
|
|
17865
|
+
- **Content hash (MD5):** \`${params.md5}\`
|
|
17866
|
+
|
|
17867
|
+
## Where the original is now
|
|
17868
|
+
|
|
17869
|
+
The original ${folderOrFile} has been moved to:
|
|
17870
|
+
|
|
17871
|
+
${params.quarantinedPath}
|
|
17872
|
+
|
|
17873
|
+
Nothing has been deleted. The contents are intact; only the location changed.
|
|
17874
|
+
|
|
17875
|
+
## If this is a false positive \u2014 how to recover
|
|
17876
|
+
|
|
17877
|
+
If you're confident this skill is safe and want to restore it:
|
|
17878
|
+
|
|
17879
|
+
mv ${params.quarantinedPath} ${params.origPath}
|
|
17880
|
+
|
|
17881
|
+
Tracy will not re-quarantine it as long as the directory
|
|
17882
|
+
\`~/.tracy/quarantine/claude/skills/${params.md5}/\` still exists on your
|
|
17883
|
+
machine (even if it's empty after you moved the contents out). If you delete
|
|
17884
|
+
that directory entirely, the next heartbeat will re-evaluate the skill from
|
|
17885
|
+
scratch.
|
|
17886
|
+
|
|
17887
|
+
## How to report a false positive
|
|
17888
|
+
|
|
17889
|
+
Please email **security@mobb.ai** with:
|
|
17890
|
+
|
|
17891
|
+
- The MD5 above
|
|
17892
|
+
- A short description of why you believe the flag was wrong
|
|
17893
|
+
- (Optional) the skill folder contents
|
|
17894
|
+
|
|
17895
|
+
Your report helps tune the scanner for everyone.
|
|
17896
|
+
`;
|
|
17897
|
+
}
|
|
17898
|
+
|
|
17899
|
+
// src/features/analysis/skill_quarantine/quarantineSkill.ts
|
|
17900
|
+
async function quarantineSkill(params) {
|
|
17901
|
+
const { skillPath, isFolder, md5, origName, verdict, log: log2 } = params;
|
|
17902
|
+
const hashDir = getQuarantinedHashDir(md5);
|
|
17903
|
+
if (existsSync2(hashDir)) {
|
|
17904
|
+
log2.debug(
|
|
17905
|
+
{ md5, metric: Metric.ALREADY_QUARANTINED },
|
|
17906
|
+
"skill_quarantine: already quarantined, skipping"
|
|
17907
|
+
);
|
|
17908
|
+
return { status: "already_quarantined" };
|
|
17909
|
+
}
|
|
17910
|
+
const stagingDir = getStagingDir(md5, process.pid, randomUUID());
|
|
17911
|
+
const stagingTarget = path16.join(stagingDir, origName);
|
|
17912
|
+
const finalTarget = getQuarantinedTargetPath(md5, origName);
|
|
17913
|
+
try {
|
|
17914
|
+
await mkdir(stagingDir, { recursive: true });
|
|
17915
|
+
} catch (err) {
|
|
17916
|
+
log2.error(
|
|
17917
|
+
{ err, md5, metric: Metric.MOVE_ERROR, phase: "stage" },
|
|
17918
|
+
"skill_quarantine: failed to create staging dir"
|
|
17919
|
+
);
|
|
17920
|
+
return { status: "move_error", phase: "stage", err };
|
|
17921
|
+
}
|
|
17922
|
+
try {
|
|
17923
|
+
await move(skillPath, stagingTarget);
|
|
17924
|
+
} catch (err) {
|
|
17925
|
+
await tryRm(stagingDir);
|
|
17926
|
+
log2.error(
|
|
17927
|
+
{ err, md5, metric: Metric.MOVE_ERROR, phase: "stage" },
|
|
17928
|
+
"skill_quarantine: phase-1 move failed"
|
|
17929
|
+
);
|
|
17930
|
+
return { status: "move_error", phase: "stage", err };
|
|
17931
|
+
}
|
|
17932
|
+
try {
|
|
17933
|
+
await rename(stagingDir, hashDir);
|
|
17934
|
+
} catch (err) {
|
|
17935
|
+
log2.error(
|
|
17936
|
+
{
|
|
17937
|
+
err,
|
|
17938
|
+
md5,
|
|
17939
|
+
stagingDir,
|
|
17940
|
+
metric: Metric.MOVE_ERROR,
|
|
17941
|
+
phase: "publish"
|
|
17942
|
+
},
|
|
17943
|
+
"skill_quarantine: phase-2 publish failed; staging dir preserved for manual recovery"
|
|
17944
|
+
);
|
|
17945
|
+
return { status: "move_error", phase: "publish", err };
|
|
17946
|
+
}
|
|
17947
|
+
const quarantinedPath = finalTarget;
|
|
17948
|
+
const stubContent = renderStub({
|
|
17949
|
+
md5,
|
|
17950
|
+
isFolder,
|
|
17951
|
+
quarantinedPath,
|
|
17952
|
+
origPath: skillPath,
|
|
17953
|
+
summary: verdict.summary,
|
|
17954
|
+
scannerName: verdict.scannerName,
|
|
17955
|
+
scannerVersion: verdict.scannerVersion,
|
|
17956
|
+
scannedAt: verdict.scannedAt
|
|
17957
|
+
});
|
|
17958
|
+
try {
|
|
17959
|
+
if (isFolder) {
|
|
17960
|
+
await mkdir(skillPath, { recursive: true });
|
|
17961
|
+
await writeFile(path16.join(skillPath, "SKILL.md"), stubContent, "utf8");
|
|
17962
|
+
} else {
|
|
17963
|
+
await writeFile(skillPath, stubContent, "utf8");
|
|
17964
|
+
}
|
|
17965
|
+
} catch (err) {
|
|
17966
|
+
log2.error(
|
|
17967
|
+
{ err, md5, skillPath, metric: Metric.STUB_ERROR },
|
|
17968
|
+
"skill_quarantine: stub write failed; quarantine is still in place"
|
|
17969
|
+
);
|
|
17970
|
+
return { status: "stub_error", err };
|
|
17971
|
+
}
|
|
17972
|
+
await preRegisterStubMd5(skillPath, isFolder, log2);
|
|
17973
|
+
log2.info(
|
|
17974
|
+
{
|
|
17975
|
+
md5,
|
|
17976
|
+
verdict: verdict.verdict,
|
|
17977
|
+
shape: isFolder ? "folder" : "standalone",
|
|
17978
|
+
scanner: verdict.scannerName,
|
|
17979
|
+
scannerVersion: verdict.scannerVersion,
|
|
17980
|
+
metric: Metric.QUARANTINED
|
|
17981
|
+
},
|
|
17982
|
+
"skill_quarantine: quarantined"
|
|
17983
|
+
);
|
|
17984
|
+
return { status: "quarantined" };
|
|
17985
|
+
}
|
|
17986
|
+
async function preRegisterStubMd5(skillPath, isFolder, log2) {
|
|
17987
|
+
try {
|
|
17988
|
+
const stubEntries = await gatherStubEntries(skillPath, isFolder);
|
|
17989
|
+
const stubGroup = {
|
|
17990
|
+
name: path16.basename(skillPath).replace(/\.md$/i, ""),
|
|
17991
|
+
root: "workspace",
|
|
17992
|
+
skillPath,
|
|
17993
|
+
files: stubEntries,
|
|
17994
|
+
isFolder,
|
|
17995
|
+
maxMtimeMs: Date.now(),
|
|
17996
|
+
sessionKey: `quarantine-stub:${skillPath}`
|
|
17997
|
+
};
|
|
17998
|
+
const { skills } = await processContextFiles([], [stubGroup]);
|
|
17999
|
+
if (skills.length === 0) return;
|
|
18000
|
+
const stubMd5 = skills[0].md5;
|
|
18001
|
+
await mkdir(getQuarantinedHashDir(stubMd5), { recursive: true });
|
|
18002
|
+
} catch (err) {
|
|
18003
|
+
log2.warn(
|
|
18004
|
+
{ err, skillPath },
|
|
18005
|
+
"skill_quarantine: failed to pre-register stub md5"
|
|
18006
|
+
);
|
|
18007
|
+
}
|
|
18008
|
+
}
|
|
18009
|
+
async function gatherStubEntries(skillPath, isFolder) {
|
|
18010
|
+
const now = Date.now();
|
|
18011
|
+
const target = isFolder ? path16.join(skillPath, "SKILL.md") : skillPath;
|
|
18012
|
+
const [st, content] = await Promise.all([
|
|
18013
|
+
stat2(target),
|
|
18014
|
+
readFile2(target, "utf8")
|
|
18015
|
+
]);
|
|
18016
|
+
return [
|
|
18017
|
+
{
|
|
18018
|
+
name: isFolder ? "SKILL.md" : path16.basename(skillPath),
|
|
18019
|
+
path: target,
|
|
18020
|
+
content,
|
|
18021
|
+
sizeBytes: st.size,
|
|
18022
|
+
category: "skill",
|
|
18023
|
+
mtimeMs: now
|
|
18024
|
+
}
|
|
18025
|
+
];
|
|
18026
|
+
}
|
|
18027
|
+
async function sweepOrphanStagingDirs(log2) {
|
|
18028
|
+
const root = getQuarantineRoot();
|
|
18029
|
+
let entries;
|
|
18030
|
+
try {
|
|
18031
|
+
entries = await readdir(root);
|
|
18032
|
+
} catch (err) {
|
|
18033
|
+
if (err.code === "ENOENT") return 0;
|
|
18034
|
+
log2.warn({ err, root }, "skill_quarantine: orphan sweep readdir failed");
|
|
18035
|
+
return 0;
|
|
18036
|
+
}
|
|
18037
|
+
const now = Date.now();
|
|
18038
|
+
let swept = 0;
|
|
18039
|
+
for (const entry of entries) {
|
|
18040
|
+
if (!STAGING_DIR_REGEX.test(entry)) continue;
|
|
18041
|
+
const full = path16.join(root, entry);
|
|
18042
|
+
let mtimeMs;
|
|
18043
|
+
try {
|
|
18044
|
+
mtimeMs = (await stat2(full)).mtimeMs;
|
|
18045
|
+
} catch {
|
|
18046
|
+
continue;
|
|
18047
|
+
}
|
|
18048
|
+
if (now - mtimeMs < ORPHAN_SWEEP_GRACE_MS) continue;
|
|
18049
|
+
try {
|
|
18050
|
+
await rm(full, { recursive: true, force: true });
|
|
18051
|
+
swept += 1;
|
|
18052
|
+
log2.info(
|
|
18053
|
+
{ path: full, metric: Metric.ORPHAN_SWEPT },
|
|
18054
|
+
"skill_quarantine: orphan swept"
|
|
18055
|
+
);
|
|
18056
|
+
} catch (err) {
|
|
18057
|
+
log2.warn({ err, path: full }, "skill_quarantine: orphan sweep rm failed");
|
|
18058
|
+
}
|
|
18059
|
+
}
|
|
18060
|
+
return swept;
|
|
18061
|
+
}
|
|
18062
|
+
async function tryRm(p) {
|
|
18063
|
+
try {
|
|
18064
|
+
await rm(p, { recursive: true, force: true });
|
|
18065
|
+
} catch {
|
|
18066
|
+
}
|
|
18067
|
+
}
|
|
18068
|
+
|
|
18069
|
+
// src/features/analysis/skill_quarantine/queryVerdicts.ts
|
|
18070
|
+
async function queryVerdicts(gqlClient, md5s, log2) {
|
|
18071
|
+
if (md5s.length === 0) {
|
|
18072
|
+
return /* @__PURE__ */ new Map();
|
|
18073
|
+
}
|
|
18074
|
+
try {
|
|
18075
|
+
const res = await gqlClient.skillVerdictsByMd5(md5s);
|
|
18076
|
+
const out = /* @__PURE__ */ new Map();
|
|
18077
|
+
for (const row of res.skillVerdictsByMd5) {
|
|
18078
|
+
out.set(row.md5, {
|
|
18079
|
+
md5: row.md5,
|
|
18080
|
+
verdict: row.verdict,
|
|
18081
|
+
summary: row.summary ?? null,
|
|
18082
|
+
scannerName: row.scannerName,
|
|
18083
|
+
scannerVersion: row.scannerVersion,
|
|
18084
|
+
scannedAt: row.scannedAt
|
|
18085
|
+
});
|
|
18086
|
+
}
|
|
18087
|
+
return out;
|
|
18088
|
+
} catch (err) {
|
|
18089
|
+
log2.warn(
|
|
18090
|
+
{ err, md5_count: md5s.length, metric: "skill_quarantine.query_error" },
|
|
18091
|
+
"skill_quarantine: verdict query failed, failing open"
|
|
18092
|
+
);
|
|
18093
|
+
return /* @__PURE__ */ new Map();
|
|
18094
|
+
}
|
|
18095
|
+
}
|
|
18096
|
+
|
|
18097
|
+
// src/features/analysis/skill_quarantine/runQuarantineCheck.ts
|
|
18098
|
+
var lastRunAt = /* @__PURE__ */ new Map();
|
|
18099
|
+
var killSwitchLogged = false;
|
|
18100
|
+
async function runQuarantineCheckIfNeeded(opts) {
|
|
18101
|
+
const { sessionId, cwd, gqlClient, log: log2 } = opts;
|
|
18102
|
+
if (process.env[KILL_SWITCH_ENV] === "1") {
|
|
18103
|
+
if (!killSwitchLogged) {
|
|
18104
|
+
log2.warn(
|
|
18105
|
+
{ metric: Metric.CHECK_DISABLED_ENV },
|
|
18106
|
+
`skill_quarantine: disabled by ${KILL_SWITCH_ENV}=1`
|
|
18107
|
+
);
|
|
18108
|
+
killSwitchLogged = true;
|
|
18109
|
+
}
|
|
18110
|
+
return;
|
|
18111
|
+
}
|
|
18112
|
+
const now = Date.now();
|
|
18113
|
+
const prev = lastRunAt.get(sessionId);
|
|
18114
|
+
if (prev !== void 0 && now - prev < HEARTBEAT_DEBOUNCE_MS) {
|
|
18115
|
+
return;
|
|
18116
|
+
}
|
|
18117
|
+
lastRunAt.set(sessionId, now);
|
|
18118
|
+
log2.info(
|
|
18119
|
+
{ sessionId, metric: Metric.CHECK_TRIGGERED },
|
|
18120
|
+
"skill_quarantine: check start"
|
|
18121
|
+
);
|
|
18122
|
+
const t0 = Date.now();
|
|
18123
|
+
try {
|
|
18124
|
+
await sweepOrphanStagingDirs(log2);
|
|
18125
|
+
const installed = await enumerateInstalledSkills(cwd);
|
|
18126
|
+
log2.info(
|
|
18127
|
+
{ sessionId, count: installed.length, metric: Metric.SKILLS_CHECKED },
|
|
18128
|
+
"skill_quarantine: skills enumerated"
|
|
18129
|
+
);
|
|
18130
|
+
if (installed.length === 0) {
|
|
18131
|
+
return;
|
|
18132
|
+
}
|
|
18133
|
+
const verdicts = await queryVerdicts(
|
|
18134
|
+
gqlClient,
|
|
18135
|
+
installed.map((s) => s.md5),
|
|
18136
|
+
log2
|
|
18137
|
+
);
|
|
18138
|
+
for (const skill of installed) {
|
|
18139
|
+
const verdict = verdicts.get(skill.md5);
|
|
18140
|
+
if (!verdict || verdict.verdict !== MALICIOUS_VERDICT) {
|
|
18141
|
+
continue;
|
|
18142
|
+
}
|
|
18143
|
+
try {
|
|
18144
|
+
await quarantineSkill({
|
|
18145
|
+
skillPath: skill.skillPath,
|
|
18146
|
+
isFolder: skill.isFolder,
|
|
18147
|
+
md5: skill.md5,
|
|
18148
|
+
origName: skill.origName,
|
|
18149
|
+
verdict,
|
|
18150
|
+
log: log2
|
|
18151
|
+
});
|
|
18152
|
+
} catch (err) {
|
|
18153
|
+
log2.error(
|
|
18154
|
+
{ err, md5: skill.md5, skillPath: skill.skillPath },
|
|
18155
|
+
"skill_quarantine: unexpected error during quarantine"
|
|
18156
|
+
);
|
|
18157
|
+
}
|
|
18158
|
+
}
|
|
18159
|
+
} finally {
|
|
18160
|
+
log2.info(
|
|
18161
|
+
{
|
|
18162
|
+
sessionId,
|
|
18163
|
+
duration_ms: Date.now() - t0,
|
|
18164
|
+
metric: Metric.DURATION_MS
|
|
18165
|
+
},
|
|
18166
|
+
"skill_quarantine: check done"
|
|
18167
|
+
);
|
|
18168
|
+
}
|
|
18169
|
+
}
|
|
18170
|
+
|
|
18171
|
+
// src/features/claude_code/daemon_pid_file.ts
|
|
18172
|
+
import fs13 from "fs";
|
|
18173
|
+
import os4 from "os";
|
|
18174
|
+
import path17 from "path";
|
|
18175
|
+
|
|
18176
|
+
// src/features/claude_code/data_collector_constants.ts
|
|
18177
|
+
var CC_VERSION_CACHE_KEY = "claudeCode.detectedCCVersion";
|
|
18178
|
+
var CC_VERSION_CLI_KEY = "claudeCode.detectedCCVersionCli";
|
|
18179
|
+
var GQL_AUTH_TIMEOUT_MS = 15e3;
|
|
18180
|
+
var STALE_KEY_MAX_AGE_MS = 14 * 24 * 60 * 60 * 1e3;
|
|
18181
|
+
var CLEANUP_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
18182
|
+
var DAEMON_TTL_MS = 30 * 60 * 1e3;
|
|
18183
|
+
var DAEMON_POLL_INTERVAL_MS = (() => {
|
|
18184
|
+
const raw = Number(process.env["MOBB_DAEMON_POLL_INTERVAL_MS"]);
|
|
18185
|
+
if (!Number.isFinite(raw) || raw <= 0) return 1e4;
|
|
18186
|
+
return Math.min(Math.max(raw, 100), 6e4);
|
|
18187
|
+
})();
|
|
18188
|
+
var HEARTBEAT_STALE_MS = 3e4;
|
|
18189
|
+
var TRANSCRIPT_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
18190
|
+
var DAEMON_CHUNK_SIZE = 50;
|
|
18191
|
+
|
|
18192
|
+
// src/features/claude_code/daemon_pid_file.ts
|
|
18193
|
+
function getMobbdevDir() {
|
|
18194
|
+
return path17.join(os4.homedir(), ".mobbdev");
|
|
18195
|
+
}
|
|
18196
|
+
function getDaemonCheckScriptPath() {
|
|
18197
|
+
return path17.join(getMobbdevDir(), "daemon-check.js");
|
|
17446
18198
|
}
|
|
18199
|
+
var DaemonPidFile = class {
|
|
18200
|
+
constructor() {
|
|
18201
|
+
__publicField(this, "data", null);
|
|
18202
|
+
}
|
|
18203
|
+
get filePath() {
|
|
18204
|
+
return path17.join(getMobbdevDir(), "daemon.pid");
|
|
18205
|
+
}
|
|
18206
|
+
/** Ensure ~/.mobbdev/ directory exists. */
|
|
18207
|
+
ensureDir() {
|
|
18208
|
+
fs13.mkdirSync(getMobbdevDir(), { recursive: true });
|
|
18209
|
+
}
|
|
18210
|
+
/** Read the PID file from disk. Returns the parsed data or null. */
|
|
18211
|
+
read() {
|
|
18212
|
+
try {
|
|
18213
|
+
const raw = fs13.readFileSync(this.filePath, "utf8");
|
|
18214
|
+
const parsed = JSON.parse(raw);
|
|
18215
|
+
if (typeof parsed.pid !== "number" || typeof parsed.startedAt !== "number" || typeof parsed.heartbeat !== "number") {
|
|
18216
|
+
this.data = null;
|
|
18217
|
+
} else {
|
|
18218
|
+
this.data = parsed;
|
|
18219
|
+
}
|
|
18220
|
+
} catch {
|
|
18221
|
+
this.data = null;
|
|
18222
|
+
}
|
|
18223
|
+
return this.data;
|
|
18224
|
+
}
|
|
18225
|
+
/** Write a new PID file for the given process id. */
|
|
18226
|
+
write(pid, version) {
|
|
18227
|
+
this.data = {
|
|
18228
|
+
pid,
|
|
18229
|
+
startedAt: Date.now(),
|
|
18230
|
+
heartbeat: Date.now(),
|
|
18231
|
+
version
|
|
18232
|
+
};
|
|
18233
|
+
fs13.writeFileSync(this.filePath, JSON.stringify(this.data), "utf8");
|
|
18234
|
+
}
|
|
18235
|
+
/** Update the heartbeat timestamp of the current PID file. */
|
|
18236
|
+
updateHeartbeat() {
|
|
18237
|
+
if (!this.data) this.read();
|
|
18238
|
+
if (!this.data) return;
|
|
18239
|
+
this.data.heartbeat = Date.now();
|
|
18240
|
+
fs13.writeFileSync(this.filePath, JSON.stringify(this.data), "utf8");
|
|
18241
|
+
}
|
|
18242
|
+
/** Check whether the previously read PID data represents a live daemon. */
|
|
18243
|
+
isAlive() {
|
|
18244
|
+
if (!this.data) return false;
|
|
18245
|
+
if (Date.now() - this.data.heartbeat > HEARTBEAT_STALE_MS) return false;
|
|
18246
|
+
try {
|
|
18247
|
+
process.kill(this.data.pid, 0);
|
|
18248
|
+
return true;
|
|
18249
|
+
} catch {
|
|
18250
|
+
return false;
|
|
18251
|
+
}
|
|
18252
|
+
}
|
|
18253
|
+
/** Remove the PID file from disk (best-effort). */
|
|
18254
|
+
remove() {
|
|
18255
|
+
this.data = null;
|
|
18256
|
+
try {
|
|
18257
|
+
fs13.unlinkSync(this.filePath);
|
|
18258
|
+
} catch {
|
|
18259
|
+
}
|
|
18260
|
+
}
|
|
18261
|
+
};
|
|
18262
|
+
|
|
18263
|
+
// src/features/claude_code/data_collector.ts
|
|
18264
|
+
import { execFile } from "child_process";
|
|
18265
|
+
import { createHash as createHash3 } from "crypto";
|
|
18266
|
+
import { access, open as open4, readdir as readdir2, readFile as readFile3, unlink } from "fs/promises";
|
|
18267
|
+
import path18 from "path";
|
|
18268
|
+
import { promisify } from "util";
|
|
17447
18269
|
|
|
17448
18270
|
// src/features/analysis/context_file_uploader.ts
|
|
17449
18271
|
import pLimit7 from "p-limit";
|
|
@@ -17707,8 +18529,8 @@ function createConfigstoreStream(store, opts) {
|
|
|
17707
18529
|
heartbeatBuffer.length = 0;
|
|
17708
18530
|
}
|
|
17709
18531
|
}
|
|
17710
|
-
function setScopePath(
|
|
17711
|
-
scopePath =
|
|
18532
|
+
function setScopePath(path34) {
|
|
18533
|
+
scopePath = path34;
|
|
17712
18534
|
}
|
|
17713
18535
|
return { writable, flush, setScopePath };
|
|
17714
18536
|
}
|
|
@@ -17932,7 +18754,7 @@ function createLogger(config2) {
|
|
|
17932
18754
|
|
|
17933
18755
|
// src/features/claude_code/hook_logger.ts
|
|
17934
18756
|
var DD_RUM_TOKEN = true ? "pubf59c0182545bfb4c299175119f1abf9b" : "";
|
|
17935
|
-
var CLI_VERSION = true ? "1.
|
|
18757
|
+
var CLI_VERSION = true ? "1.4.0" : "unknown";
|
|
17936
18758
|
var NAMESPACE = "mobbdev-claude-code-hook-logs";
|
|
17937
18759
|
var claudeCodeVersion;
|
|
17938
18760
|
function buildDdTags() {
|
|
@@ -18023,12 +18845,12 @@ async function resolveTranscriptPath(transcriptPath, sessionId) {
|
|
|
18023
18845
|
return transcriptPath;
|
|
18024
18846
|
} catch {
|
|
18025
18847
|
}
|
|
18026
|
-
const filename =
|
|
18027
|
-
const dirName =
|
|
18028
|
-
const projectsDir =
|
|
18848
|
+
const filename = path18.basename(transcriptPath);
|
|
18849
|
+
const dirName = path18.basename(path18.dirname(transcriptPath));
|
|
18850
|
+
const projectsDir = path18.dirname(path18.dirname(transcriptPath));
|
|
18029
18851
|
const baseDirName = dirName.replace(/[-.]claude-worktrees-.+$/, "");
|
|
18030
18852
|
if (baseDirName !== dirName) {
|
|
18031
|
-
const candidate =
|
|
18853
|
+
const candidate = path18.join(projectsDir, baseDirName, filename);
|
|
18032
18854
|
try {
|
|
18033
18855
|
await access(candidate);
|
|
18034
18856
|
hookLog.info(
|
|
@@ -18047,10 +18869,10 @@ async function resolveTranscriptPath(transcriptPath, sessionId) {
|
|
|
18047
18869
|
}
|
|
18048
18870
|
}
|
|
18049
18871
|
try {
|
|
18050
|
-
const dirs = await
|
|
18872
|
+
const dirs = await readdir2(projectsDir);
|
|
18051
18873
|
for (const dir of dirs) {
|
|
18052
18874
|
if (dir === dirName) continue;
|
|
18053
|
-
const candidate =
|
|
18875
|
+
const candidate = path18.join(projectsDir, dir, filename);
|
|
18054
18876
|
try {
|
|
18055
18877
|
await access(candidate);
|
|
18056
18878
|
hookLog.info(
|
|
@@ -18081,9 +18903,9 @@ async function readNewTranscriptEntries(transcriptPath, sessionId, sessionStore,
|
|
|
18081
18903
|
if (cursor?.byteOffset) {
|
|
18082
18904
|
const fh = await open4(transcriptPath, "r");
|
|
18083
18905
|
try {
|
|
18084
|
-
const
|
|
18085
|
-
fileSize =
|
|
18086
|
-
if (cursor.byteOffset >=
|
|
18906
|
+
const stat4 = await fh.stat();
|
|
18907
|
+
fileSize = stat4.size;
|
|
18908
|
+
if (cursor.byteOffset >= stat4.size) {
|
|
18087
18909
|
hookLog.info({ data: { sessionId } }, "No new data in transcript file");
|
|
18088
18910
|
return {
|
|
18089
18911
|
entries: [],
|
|
@@ -18091,7 +18913,7 @@ async function readNewTranscriptEntries(transcriptPath, sessionId, sessionStore,
|
|
|
18091
18913
|
resolvedTranscriptPath: transcriptPath
|
|
18092
18914
|
};
|
|
18093
18915
|
}
|
|
18094
|
-
const buf = Buffer.alloc(
|
|
18916
|
+
const buf = Buffer.alloc(stat4.size - cursor.byteOffset);
|
|
18095
18917
|
await fh.read(buf, 0, buf.length, cursor.byteOffset);
|
|
18096
18918
|
content = buf.toString("utf-8");
|
|
18097
18919
|
} finally {
|
|
@@ -18109,7 +18931,7 @@ async function readNewTranscriptEntries(transcriptPath, sessionId, sessionStore,
|
|
|
18109
18931
|
"Read transcript file from offset"
|
|
18110
18932
|
);
|
|
18111
18933
|
} else {
|
|
18112
|
-
content = await
|
|
18934
|
+
content = await readFile3(transcriptPath, "utf-8");
|
|
18113
18935
|
fileSize = Buffer.byteLength(content, "utf-8");
|
|
18114
18936
|
lineIndexOffset = 0;
|
|
18115
18937
|
hookLog.debug(
|
|
@@ -18231,13 +19053,13 @@ async function cleanupStaleSessions(configDir) {
|
|
|
18231
19053
|
const now = Date.now();
|
|
18232
19054
|
const prefix = getSessionFilePrefix();
|
|
18233
19055
|
try {
|
|
18234
|
-
const files = await
|
|
19056
|
+
const files = await readdir2(configDir);
|
|
18235
19057
|
let deletedCount = 0;
|
|
18236
19058
|
for (const file of files) {
|
|
18237
19059
|
if (!file.startsWith(prefix) || !file.endsWith(".json")) continue;
|
|
18238
|
-
const filePath =
|
|
19060
|
+
const filePath = path18.join(configDir, file);
|
|
18239
19061
|
try {
|
|
18240
|
-
const content = JSON.parse(await
|
|
19062
|
+
const content = JSON.parse(await readFile3(filePath, "utf-8"));
|
|
18241
19063
|
let newest = 0;
|
|
18242
19064
|
const cursors = content["cursor"];
|
|
18243
19065
|
if (cursors && typeof cursors === "object") {
|
|
@@ -18483,14 +19305,14 @@ async function uploadContextFilesIfNeeded(sessionId, cwd, gqlClient, log2) {
|
|
|
18483
19305
|
import fs14 from "fs";
|
|
18484
19306
|
import fsPromises4 from "fs/promises";
|
|
18485
19307
|
import os6 from "os";
|
|
18486
|
-
import
|
|
19308
|
+
import path19 from "path";
|
|
18487
19309
|
import chalk11 from "chalk";
|
|
18488
19310
|
|
|
18489
19311
|
// src/features/claude_code/daemon-check-shim.tmpl.js
|
|
18490
19312
|
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";
|
|
18491
19313
|
|
|
18492
19314
|
// src/features/claude_code/install_hook.ts
|
|
18493
|
-
var CLAUDE_SETTINGS_PATH =
|
|
19315
|
+
var CLAUDE_SETTINGS_PATH = path19.join(os6.homedir(), ".claude", "settings.json");
|
|
18494
19316
|
var RECOMMENDED_MATCHER = "*";
|
|
18495
19317
|
async function claudeSettingsExists() {
|
|
18496
19318
|
try {
|
|
@@ -18636,18 +19458,18 @@ async function installMobbHooks(options = {}) {
|
|
|
18636
19458
|
}
|
|
18637
19459
|
|
|
18638
19460
|
// src/features/claude_code/transcript_scanner.ts
|
|
18639
|
-
import { open as open5, readdir as
|
|
19461
|
+
import { open as open5, readdir as readdir3, stat as stat3 } from "fs/promises";
|
|
18640
19462
|
import os7 from "os";
|
|
18641
|
-
import
|
|
19463
|
+
import path20 from "path";
|
|
18642
19464
|
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
18643
19465
|
function getClaudeProjectsDirs() {
|
|
18644
19466
|
const dirs = [];
|
|
18645
19467
|
const configDir = process.env["CLAUDE_CONFIG_DIR"];
|
|
18646
19468
|
if (configDir) {
|
|
18647
|
-
dirs.push(
|
|
19469
|
+
dirs.push(path20.join(configDir, "projects"));
|
|
18648
19470
|
}
|
|
18649
|
-
dirs.push(
|
|
18650
|
-
dirs.push(
|
|
19471
|
+
dirs.push(path20.join(os7.homedir(), ".config", "claude", "projects"));
|
|
19472
|
+
dirs.push(path20.join(os7.homedir(), ".claude", "projects"));
|
|
18651
19473
|
return dirs;
|
|
18652
19474
|
}
|
|
18653
19475
|
async function collectJsonlFiles(files, dir, projectDir, seen, now, results) {
|
|
@@ -18655,12 +19477,12 @@ async function collectJsonlFiles(files, dir, projectDir, seen, now, results) {
|
|
|
18655
19477
|
if (!file.endsWith(".jsonl")) continue;
|
|
18656
19478
|
const sessionId = file.replace(".jsonl", "");
|
|
18657
19479
|
if (!UUID_RE.test(sessionId)) continue;
|
|
18658
|
-
const filePath =
|
|
19480
|
+
const filePath = path20.join(dir, file);
|
|
18659
19481
|
if (seen.has(filePath)) continue;
|
|
18660
19482
|
seen.add(filePath);
|
|
18661
19483
|
let fileStat;
|
|
18662
19484
|
try {
|
|
18663
|
-
fileStat = await
|
|
19485
|
+
fileStat = await stat3(filePath);
|
|
18664
19486
|
} catch {
|
|
18665
19487
|
continue;
|
|
18666
19488
|
}
|
|
@@ -18681,33 +19503,33 @@ async function scanForTranscripts(projectsDirs = getClaudeProjectsDirs()) {
|
|
|
18681
19503
|
for (const projectsDir of projectsDirs) {
|
|
18682
19504
|
let projectDirs;
|
|
18683
19505
|
try {
|
|
18684
|
-
projectDirs = await
|
|
19506
|
+
projectDirs = await readdir3(projectsDir);
|
|
18685
19507
|
} catch {
|
|
18686
19508
|
continue;
|
|
18687
19509
|
}
|
|
18688
19510
|
for (const projName of projectDirs) {
|
|
18689
|
-
const projPath =
|
|
19511
|
+
const projPath = path20.join(projectsDir, projName);
|
|
18690
19512
|
let projStat;
|
|
18691
19513
|
try {
|
|
18692
|
-
projStat = await
|
|
19514
|
+
projStat = await stat3(projPath);
|
|
18693
19515
|
} catch {
|
|
18694
19516
|
continue;
|
|
18695
19517
|
}
|
|
18696
19518
|
if (!projStat.isDirectory()) continue;
|
|
18697
19519
|
let files;
|
|
18698
19520
|
try {
|
|
18699
|
-
files = await
|
|
19521
|
+
files = await readdir3(projPath);
|
|
18700
19522
|
} catch {
|
|
18701
19523
|
continue;
|
|
18702
19524
|
}
|
|
18703
19525
|
await collectJsonlFiles(files, projPath, projPath, seen, now, results);
|
|
18704
19526
|
for (const entry of files) {
|
|
18705
19527
|
if (!UUID_RE.test(entry)) continue;
|
|
18706
|
-
const subagentsDir =
|
|
19528
|
+
const subagentsDir = path20.join(projPath, entry, "subagents");
|
|
18707
19529
|
try {
|
|
18708
|
-
const s = await
|
|
19530
|
+
const s = await stat3(subagentsDir);
|
|
18709
19531
|
if (!s.isDirectory()) continue;
|
|
18710
|
-
const subFiles = await
|
|
19532
|
+
const subFiles = await readdir3(subagentsDir);
|
|
18711
19533
|
await collectJsonlFiles(
|
|
18712
19534
|
subFiles,
|
|
18713
19535
|
subagentsDir,
|
|
@@ -18804,7 +19626,7 @@ async function startDaemon() {
|
|
|
18804
19626
|
for (const transcript of changed) {
|
|
18805
19627
|
const sessionStore = createSessionConfigStore(transcript.sessionId);
|
|
18806
19628
|
if (!cleanupConfigDir) {
|
|
18807
|
-
cleanupConfigDir =
|
|
19629
|
+
cleanupConfigDir = path21.dirname(sessionStore.path);
|
|
18808
19630
|
}
|
|
18809
19631
|
await drainTranscript(transcript, sessionStore, gqlClient);
|
|
18810
19632
|
}
|
|
@@ -18886,6 +19708,19 @@ async function drainTranscript(transcript, sessionStore, gqlClient) {
|
|
|
18886
19708
|
"Error processing transcript \u2014 skipping"
|
|
18887
19709
|
);
|
|
18888
19710
|
}
|
|
19711
|
+
if (cwd) {
|
|
19712
|
+
runQuarantineCheckIfNeeded({
|
|
19713
|
+
sessionId: transcript.sessionId,
|
|
19714
|
+
cwd,
|
|
19715
|
+
gqlClient,
|
|
19716
|
+
log: log2
|
|
19717
|
+
}).catch((err) => {
|
|
19718
|
+
hookLog.warn(
|
|
19719
|
+
{ err, data: { sessionId: transcript.sessionId } },
|
|
19720
|
+
"runQuarantineCheckIfNeeded failed"
|
|
19721
|
+
);
|
|
19722
|
+
});
|
|
19723
|
+
}
|
|
18889
19724
|
}
|
|
18890
19725
|
async function detectChangedTranscripts(lastSeen) {
|
|
18891
19726
|
const transcripts = await scanForTranscripts();
|
|
@@ -19062,8 +19897,8 @@ var WorkspaceService = class {
|
|
|
19062
19897
|
* Sets a known workspace path that was discovered through successful validation
|
|
19063
19898
|
* @param path The validated workspace path to store
|
|
19064
19899
|
*/
|
|
19065
|
-
static setKnownWorkspacePath(
|
|
19066
|
-
this.knownWorkspacePath =
|
|
19900
|
+
static setKnownWorkspacePath(path34) {
|
|
19901
|
+
this.knownWorkspacePath = path34;
|
|
19067
19902
|
}
|
|
19068
19903
|
/**
|
|
19069
19904
|
* Gets the known workspace path that was previously validated
|
|
@@ -19924,7 +20759,7 @@ async function createAuthenticatedMcpGQLClient({
|
|
|
19924
20759
|
import { execSync as execSync2 } from "child_process";
|
|
19925
20760
|
import fs15 from "fs";
|
|
19926
20761
|
import os8 from "os";
|
|
19927
|
-
import
|
|
20762
|
+
import path22 from "path";
|
|
19928
20763
|
var IDEs = ["cursor", "windsurf", "webstorm", "vscode", "claude"];
|
|
19929
20764
|
var runCommand = (cmd) => {
|
|
19930
20765
|
try {
|
|
@@ -19939,7 +20774,7 @@ var gitInfo = {
|
|
|
19939
20774
|
};
|
|
19940
20775
|
var getClaudeWorkspacePaths = () => {
|
|
19941
20776
|
const home = os8.homedir();
|
|
19942
|
-
const claudeIdePath =
|
|
20777
|
+
const claudeIdePath = path22.join(home, ".claude", "ide");
|
|
19943
20778
|
const workspacePaths = [];
|
|
19944
20779
|
if (!fs15.existsSync(claudeIdePath)) {
|
|
19945
20780
|
return workspacePaths;
|
|
@@ -19947,7 +20782,7 @@ var getClaudeWorkspacePaths = () => {
|
|
|
19947
20782
|
try {
|
|
19948
20783
|
const lockFiles = fs15.readdirSync(claudeIdePath).filter((file) => file.endsWith(".lock"));
|
|
19949
20784
|
for (const lockFile of lockFiles) {
|
|
19950
|
-
const lockFilePath =
|
|
20785
|
+
const lockFilePath = path22.join(claudeIdePath, lockFile);
|
|
19951
20786
|
try {
|
|
19952
20787
|
const lockContent = JSON.parse(fs15.readFileSync(lockFilePath, "utf8"));
|
|
19953
20788
|
if (lockContent.workspaceFolders && Array.isArray(lockContent.workspaceFolders)) {
|
|
@@ -19972,24 +20807,24 @@ var getMCPConfigPaths = (hostName) => {
|
|
|
19972
20807
|
switch (hostName.toLowerCase()) {
|
|
19973
20808
|
case "cursor":
|
|
19974
20809
|
return [
|
|
19975
|
-
|
|
20810
|
+
path22.join(currentDir, ".cursor", "mcp.json"),
|
|
19976
20811
|
// local first
|
|
19977
|
-
|
|
20812
|
+
path22.join(home, ".cursor", "mcp.json")
|
|
19978
20813
|
];
|
|
19979
20814
|
case "windsurf":
|
|
19980
20815
|
return [
|
|
19981
|
-
|
|
20816
|
+
path22.join(currentDir, ".codeium", "mcp_config.json"),
|
|
19982
20817
|
// local first
|
|
19983
|
-
|
|
20818
|
+
path22.join(home, ".codeium", "windsurf", "mcp_config.json")
|
|
19984
20819
|
];
|
|
19985
20820
|
case "webstorm":
|
|
19986
20821
|
return [];
|
|
19987
20822
|
case "visualstudiocode":
|
|
19988
20823
|
case "vscode":
|
|
19989
20824
|
return [
|
|
19990
|
-
|
|
20825
|
+
path22.join(currentDir, ".vscode", "mcp.json"),
|
|
19991
20826
|
// local first
|
|
19992
|
-
process.platform === "win32" ?
|
|
20827
|
+
process.platform === "win32" ? path22.join(home, "AppData", "Roaming", "Code", "User", "mcp.json") : path22.join(
|
|
19993
20828
|
home,
|
|
19994
20829
|
"Library",
|
|
19995
20830
|
"Application Support",
|
|
@@ -20000,13 +20835,13 @@ var getMCPConfigPaths = (hostName) => {
|
|
|
20000
20835
|
];
|
|
20001
20836
|
case "claude": {
|
|
20002
20837
|
const claudePaths = [
|
|
20003
|
-
|
|
20838
|
+
path22.join(currentDir, ".claude.json"),
|
|
20004
20839
|
// local first
|
|
20005
|
-
|
|
20840
|
+
path22.join(home, ".claude.json")
|
|
20006
20841
|
];
|
|
20007
20842
|
const workspacePaths = getClaudeWorkspacePaths();
|
|
20008
20843
|
for (const workspacePath of workspacePaths) {
|
|
20009
|
-
claudePaths.push(
|
|
20844
|
+
claudePaths.push(path22.join(workspacePath, ".mcp.json"));
|
|
20010
20845
|
}
|
|
20011
20846
|
return claudePaths;
|
|
20012
20847
|
}
|
|
@@ -20167,10 +21002,10 @@ var getHostInfo = (additionalMcpList) => {
|
|
|
20167
21002
|
const ideConfigPaths = /* @__PURE__ */ new Set();
|
|
20168
21003
|
for (const ide of IDEs) {
|
|
20169
21004
|
const configPaths = getMCPConfigPaths(ide);
|
|
20170
|
-
configPaths.forEach((
|
|
21005
|
+
configPaths.forEach((path34) => ideConfigPaths.add(path34));
|
|
20171
21006
|
}
|
|
20172
21007
|
const uniqueAdditionalPaths = additionalMcpList.filter(
|
|
20173
|
-
(
|
|
21008
|
+
(path34) => !ideConfigPaths.has(path34)
|
|
20174
21009
|
);
|
|
20175
21010
|
for (const ide of IDEs) {
|
|
20176
21011
|
const cfg = readMCPConfig(ide);
|
|
@@ -20292,7 +21127,7 @@ init_configs();
|
|
|
20292
21127
|
init_configs();
|
|
20293
21128
|
import fs16 from "fs";
|
|
20294
21129
|
import os9 from "os";
|
|
20295
|
-
import
|
|
21130
|
+
import path23 from "path";
|
|
20296
21131
|
var MAX_DEPTH = 2;
|
|
20297
21132
|
var patterns = ["mcp", "claude"];
|
|
20298
21133
|
var isFileMatch = (fileName) => {
|
|
@@ -20312,7 +21147,7 @@ var searchDir = async (dir, depth = 0) => {
|
|
|
20312
21147
|
if (depth > MAX_DEPTH) return results;
|
|
20313
21148
|
const entries = await fs16.promises.readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
20314
21149
|
for (const entry of entries) {
|
|
20315
|
-
const fullPath =
|
|
21150
|
+
const fullPath = path23.join(dir, entry.name);
|
|
20316
21151
|
if (entry.isFile() && isFileMatch(entry.name)) {
|
|
20317
21152
|
results.push(fullPath);
|
|
20318
21153
|
} else if (entry.isDirectory()) {
|
|
@@ -20329,14 +21164,14 @@ var findSystemMCPConfigs = async () => {
|
|
|
20329
21164
|
const home = os9.homedir();
|
|
20330
21165
|
const platform2 = os9.platform();
|
|
20331
21166
|
const knownDirs = platform2 === "win32" ? [
|
|
20332
|
-
|
|
20333
|
-
|
|
20334
|
-
|
|
21167
|
+
path23.join(home, ".cursor"),
|
|
21168
|
+
path23.join(home, "Documents"),
|
|
21169
|
+
path23.join(home, "Downloads")
|
|
20335
21170
|
] : [
|
|
20336
|
-
|
|
20337
|
-
process.env["XDG_CONFIG_HOME"] ||
|
|
20338
|
-
|
|
20339
|
-
|
|
21171
|
+
path23.join(home, ".cursor"),
|
|
21172
|
+
process.env["XDG_CONFIG_HOME"] || path23.join(home, ".config"),
|
|
21173
|
+
path23.join(home, "Documents"),
|
|
21174
|
+
path23.join(home, "Downloads")
|
|
20340
21175
|
];
|
|
20341
21176
|
const timeoutPromise = new Promise(
|
|
20342
21177
|
(resolve) => setTimeout(() => {
|
|
@@ -22752,13 +23587,13 @@ For a complete security audit workflow, use the \`full-security-audit\` prompt.
|
|
|
22752
23587
|
// src/mcp/services/McpDetectionService/CursorMcpDetectionService.ts
|
|
22753
23588
|
import * as fs19 from "fs";
|
|
22754
23589
|
import * as os12 from "os";
|
|
22755
|
-
import * as
|
|
23590
|
+
import * as path25 from "path";
|
|
22756
23591
|
|
|
22757
23592
|
// src/mcp/services/McpDetectionService/BaseMcpDetectionService.ts
|
|
22758
23593
|
init_configs();
|
|
22759
23594
|
import * as fs18 from "fs";
|
|
22760
23595
|
import fetch7 from "node-fetch";
|
|
22761
|
-
import * as
|
|
23596
|
+
import * as path24 from "path";
|
|
22762
23597
|
|
|
22763
23598
|
// src/mcp/services/McpDetectionService/McpDetectionServiceUtils.ts
|
|
22764
23599
|
import * as fs17 from "fs";
|
|
@@ -22767,14 +23602,14 @@ import * as os11 from "os";
|
|
|
22767
23602
|
// src/mcp/services/McpDetectionService/VscodeMcpDetectionService.ts
|
|
22768
23603
|
import * as fs20 from "fs";
|
|
22769
23604
|
import * as os13 from "os";
|
|
22770
|
-
import * as
|
|
23605
|
+
import * as path26 from "path";
|
|
22771
23606
|
|
|
22772
23607
|
// src/mcp/tools/checkForNewAvailableFixes/CheckForNewAvailableFixesTool.ts
|
|
22773
23608
|
import { z as z42 } from "zod";
|
|
22774
23609
|
|
|
22775
23610
|
// src/mcp/services/PathValidation.ts
|
|
22776
23611
|
import fs21 from "fs";
|
|
22777
|
-
import
|
|
23612
|
+
import path27 from "path";
|
|
22778
23613
|
async function validatePath(inputPath) {
|
|
22779
23614
|
logDebug("Validating MCP path", { inputPath });
|
|
22780
23615
|
if (/^\/[a-zA-Z]:\//.test(inputPath)) {
|
|
@@ -22806,7 +23641,7 @@ async function validatePath(inputPath) {
|
|
|
22806
23641
|
logError(error);
|
|
22807
23642
|
return { isValid: false, error, path: inputPath };
|
|
22808
23643
|
}
|
|
22809
|
-
const normalizedPath =
|
|
23644
|
+
const normalizedPath = path27.normalize(inputPath);
|
|
22810
23645
|
if (normalizedPath.includes("..")) {
|
|
22811
23646
|
const error = `Normalized path contains path traversal patterns: ${inputPath}`;
|
|
22812
23647
|
logError(error);
|
|
@@ -23458,7 +24293,7 @@ init_configs();
|
|
|
23458
24293
|
import fs22 from "fs/promises";
|
|
23459
24294
|
import nodePath from "path";
|
|
23460
24295
|
var getLocalFiles = async ({
|
|
23461
|
-
path:
|
|
24296
|
+
path: path34,
|
|
23462
24297
|
maxFileSize = MCP_MAX_FILE_SIZE,
|
|
23463
24298
|
maxFiles,
|
|
23464
24299
|
isAllFilesScan,
|
|
@@ -23466,17 +24301,17 @@ var getLocalFiles = async ({
|
|
|
23466
24301
|
scanRecentlyChangedFiles
|
|
23467
24302
|
}) => {
|
|
23468
24303
|
logDebug(`[${scanContext}] Starting getLocalFiles`, {
|
|
23469
|
-
path:
|
|
24304
|
+
path: path34,
|
|
23470
24305
|
maxFileSize,
|
|
23471
24306
|
maxFiles,
|
|
23472
24307
|
isAllFilesScan,
|
|
23473
24308
|
scanRecentlyChangedFiles
|
|
23474
24309
|
});
|
|
23475
24310
|
try {
|
|
23476
|
-
const resolvedRepoPath = await fs22.realpath(
|
|
24311
|
+
const resolvedRepoPath = await fs22.realpath(path34);
|
|
23477
24312
|
logDebug(`[${scanContext}] Resolved repository path`, {
|
|
23478
24313
|
resolvedRepoPath,
|
|
23479
|
-
originalPath:
|
|
24314
|
+
originalPath: path34
|
|
23480
24315
|
});
|
|
23481
24316
|
const gitService = new GitService(resolvedRepoPath, log);
|
|
23482
24317
|
const gitValidation = await gitService.validateRepository();
|
|
@@ -23489,7 +24324,7 @@ var getLocalFiles = async ({
|
|
|
23489
24324
|
if (!gitValidation.isValid || isAllFilesScan) {
|
|
23490
24325
|
try {
|
|
23491
24326
|
files = await FileUtils.getLastChangedFiles({
|
|
23492
|
-
dir:
|
|
24327
|
+
dir: path34,
|
|
23493
24328
|
maxFileSize,
|
|
23494
24329
|
maxFiles,
|
|
23495
24330
|
isAllFilesScan
|
|
@@ -23581,7 +24416,7 @@ var getLocalFiles = async ({
|
|
|
23581
24416
|
logError(`${scanContext}Unexpected error in getLocalFiles`, {
|
|
23582
24417
|
error: error instanceof Error ? error.message : String(error),
|
|
23583
24418
|
stack: error instanceof Error ? error.stack : void 0,
|
|
23584
|
-
path:
|
|
24419
|
+
path: path34
|
|
23585
24420
|
});
|
|
23586
24421
|
throw error;
|
|
23587
24422
|
}
|
|
@@ -23591,7 +24426,7 @@ var getLocalFiles = async ({
|
|
|
23591
24426
|
init_client_generates();
|
|
23592
24427
|
init_GitService();
|
|
23593
24428
|
import fs23 from "fs";
|
|
23594
|
-
import
|
|
24429
|
+
import path28 from "path";
|
|
23595
24430
|
import { z as z41 } from "zod";
|
|
23596
24431
|
function extractPathFromPatch(patch) {
|
|
23597
24432
|
const match = patch?.match(/diff --git a\/([^\s]+) b\//);
|
|
@@ -23677,7 +24512,7 @@ var LocalMobbFolderService = class {
|
|
|
23677
24512
|
"[LocalMobbFolderService] Non-git repository detected, skipping .gitignore operations"
|
|
23678
24513
|
);
|
|
23679
24514
|
}
|
|
23680
|
-
const mobbFolderPath =
|
|
24515
|
+
const mobbFolderPath = path28.join(
|
|
23681
24516
|
this.repoPath,
|
|
23682
24517
|
this.defaultMobbFolderName
|
|
23683
24518
|
);
|
|
@@ -23849,7 +24684,7 @@ var LocalMobbFolderService = class {
|
|
|
23849
24684
|
mobbFolderPath,
|
|
23850
24685
|
baseFileName
|
|
23851
24686
|
);
|
|
23852
|
-
const filePath =
|
|
24687
|
+
const filePath = path28.join(mobbFolderPath, uniqueFileName);
|
|
23853
24688
|
await fs23.promises.writeFile(filePath, patch, "utf8");
|
|
23854
24689
|
logInfo("[LocalMobbFolderService] Patch saved successfully", {
|
|
23855
24690
|
filePath,
|
|
@@ -23907,11 +24742,11 @@ var LocalMobbFolderService = class {
|
|
|
23907
24742
|
* @returns Unique filename that doesn't conflict with existing files
|
|
23908
24743
|
*/
|
|
23909
24744
|
getUniqueFileName(folderPath, baseFileName) {
|
|
23910
|
-
const baseName =
|
|
23911
|
-
const extension =
|
|
24745
|
+
const baseName = path28.parse(baseFileName).name;
|
|
24746
|
+
const extension = path28.parse(baseFileName).ext;
|
|
23912
24747
|
let uniqueFileName = baseFileName;
|
|
23913
24748
|
let index = 1;
|
|
23914
|
-
while (fs23.existsSync(
|
|
24749
|
+
while (fs23.existsSync(path28.join(folderPath, uniqueFileName))) {
|
|
23915
24750
|
uniqueFileName = `${baseName}-${index}${extension}`;
|
|
23916
24751
|
index++;
|
|
23917
24752
|
if (index > 1e3) {
|
|
@@ -23942,7 +24777,7 @@ var LocalMobbFolderService = class {
|
|
|
23942
24777
|
logDebug("[LocalMobbFolderService] Logging patch info", { fixId: fix.id });
|
|
23943
24778
|
try {
|
|
23944
24779
|
const mobbFolderPath = await this.getFolder();
|
|
23945
|
-
const patchInfoPath =
|
|
24780
|
+
const patchInfoPath = path28.join(mobbFolderPath, "patchInfo.md");
|
|
23946
24781
|
const markdownContent = this.generateFixMarkdown(fix, savedPatchFileName);
|
|
23947
24782
|
let existingContent = "";
|
|
23948
24783
|
if (fs23.existsSync(patchInfoPath)) {
|
|
@@ -23984,7 +24819,7 @@ var LocalMobbFolderService = class {
|
|
|
23984
24819
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
23985
24820
|
const patch = this.extractPatchFromFix(fix);
|
|
23986
24821
|
const relativePatchedFilePath = patch ? extractPathFromPatch(patch) : null;
|
|
23987
|
-
const patchedFilePath = relativePatchedFilePath ?
|
|
24822
|
+
const patchedFilePath = relativePatchedFilePath ? path28.resolve(this.repoPath, relativePatchedFilePath) : null;
|
|
23988
24823
|
const fixIdentifier = savedPatchFileName ? savedPatchFileName.replace(".patch", "") : fix.id;
|
|
23989
24824
|
let markdown = `# Fix ${fixIdentifier}
|
|
23990
24825
|
|
|
@@ -24320,7 +25155,7 @@ var LocalMobbFolderService = class {
|
|
|
24320
25155
|
// src/mcp/services/PatchApplicationService.ts
|
|
24321
25156
|
init_configs();
|
|
24322
25157
|
import {
|
|
24323
|
-
existsSync as
|
|
25158
|
+
existsSync as existsSync7,
|
|
24324
25159
|
mkdirSync,
|
|
24325
25160
|
readFileSync as readFileSync4,
|
|
24326
25161
|
unlinkSync,
|
|
@@ -24328,14 +25163,14 @@ import {
|
|
|
24328
25163
|
} from "fs";
|
|
24329
25164
|
import fs24 from "fs/promises";
|
|
24330
25165
|
import parseDiff2 from "parse-diff";
|
|
24331
|
-
import
|
|
25166
|
+
import path29 from "path";
|
|
24332
25167
|
var PatchApplicationService = class {
|
|
24333
25168
|
/**
|
|
24334
25169
|
* Gets the appropriate comment syntax for a file based on its extension
|
|
24335
25170
|
*/
|
|
24336
25171
|
static getCommentSyntax(filePath) {
|
|
24337
|
-
const ext =
|
|
24338
|
-
const basename2 =
|
|
25172
|
+
const ext = path29.extname(filePath).toLowerCase();
|
|
25173
|
+
const basename2 = path29.basename(filePath);
|
|
24339
25174
|
const commentMap = {
|
|
24340
25175
|
// C-style languages (single line comments)
|
|
24341
25176
|
".js": "//",
|
|
@@ -24543,7 +25378,7 @@ var PatchApplicationService = class {
|
|
|
24543
25378
|
}
|
|
24544
25379
|
);
|
|
24545
25380
|
}
|
|
24546
|
-
const dirPath =
|
|
25381
|
+
const dirPath = path29.dirname(normalizedFilePath);
|
|
24547
25382
|
mkdirSync(dirPath, { recursive: true });
|
|
24548
25383
|
writeFileSync3(normalizedFilePath, finalContent, "utf8");
|
|
24549
25384
|
return normalizedFilePath;
|
|
@@ -24552,9 +25387,9 @@ var PatchApplicationService = class {
|
|
|
24552
25387
|
repositoryPath,
|
|
24553
25388
|
targetPath
|
|
24554
25389
|
}) {
|
|
24555
|
-
const repoRoot =
|
|
24556
|
-
const normalizedPath =
|
|
24557
|
-
const repoRootWithSep = repoRoot.endsWith(
|
|
25390
|
+
const repoRoot = path29.resolve(repositoryPath);
|
|
25391
|
+
const normalizedPath = path29.resolve(repoRoot, targetPath);
|
|
25392
|
+
const repoRootWithSep = repoRoot.endsWith(path29.sep) ? repoRoot : `${repoRoot}${path29.sep}`;
|
|
24558
25393
|
if (normalizedPath !== repoRoot && !normalizedPath.startsWith(repoRootWithSep)) {
|
|
24559
25394
|
throw new Error(
|
|
24560
25395
|
`Security violation: target path ${targetPath} resolves outside repository`
|
|
@@ -24563,7 +25398,7 @@ var PatchApplicationService = class {
|
|
|
24563
25398
|
return {
|
|
24564
25399
|
repoRoot,
|
|
24565
25400
|
normalizedPath,
|
|
24566
|
-
relativePath:
|
|
25401
|
+
relativePath: path29.relative(repoRoot, normalizedPath)
|
|
24567
25402
|
};
|
|
24568
25403
|
}
|
|
24569
25404
|
/**
|
|
@@ -24845,8 +25680,8 @@ var PatchApplicationService = class {
|
|
|
24845
25680
|
continue;
|
|
24846
25681
|
}
|
|
24847
25682
|
try {
|
|
24848
|
-
const absolutePath =
|
|
24849
|
-
if (
|
|
25683
|
+
const absolutePath = path29.resolve(repositoryPath, targetFile);
|
|
25684
|
+
if (existsSync7(absolutePath)) {
|
|
24850
25685
|
const stats = await fs24.stat(absolutePath);
|
|
24851
25686
|
const fileModTime = stats.mtime.getTime();
|
|
24852
25687
|
if (fileModTime > scanStartTime) {
|
|
@@ -25047,7 +25882,7 @@ var PatchApplicationService = class {
|
|
|
25047
25882
|
targetFile,
|
|
25048
25883
|
absoluteFilePath,
|
|
25049
25884
|
relativePath,
|
|
25050
|
-
exists:
|
|
25885
|
+
exists: existsSync7(absoluteFilePath)
|
|
25051
25886
|
});
|
|
25052
25887
|
return { absoluteFilePath, relativePath };
|
|
25053
25888
|
}
|
|
@@ -25071,7 +25906,7 @@ var PatchApplicationService = class {
|
|
|
25071
25906
|
fix,
|
|
25072
25907
|
scanContext
|
|
25073
25908
|
});
|
|
25074
|
-
appliedFiles.push(
|
|
25909
|
+
appliedFiles.push(path29.relative(repositoryPath, actualPath));
|
|
25075
25910
|
logDebug(`[${scanContext}] Created new file: ${relativePath}`);
|
|
25076
25911
|
}
|
|
25077
25912
|
/**
|
|
@@ -25083,7 +25918,7 @@ var PatchApplicationService = class {
|
|
|
25083
25918
|
appliedFiles,
|
|
25084
25919
|
scanContext
|
|
25085
25920
|
}) {
|
|
25086
|
-
if (
|
|
25921
|
+
if (existsSync7(absoluteFilePath)) {
|
|
25087
25922
|
unlinkSync(absoluteFilePath);
|
|
25088
25923
|
appliedFiles.push(relativePath);
|
|
25089
25924
|
logDebug(`[${scanContext}] Deleted file: ${relativePath}`);
|
|
@@ -25102,7 +25937,7 @@ var PatchApplicationService = class {
|
|
|
25102
25937
|
appliedFiles,
|
|
25103
25938
|
scanContext
|
|
25104
25939
|
}) {
|
|
25105
|
-
if (!
|
|
25940
|
+
if (!existsSync7(absoluteFilePath)) {
|
|
25106
25941
|
throw new Error(
|
|
25107
25942
|
`Target file does not exist: ${targetFile} (resolved to: ${absoluteFilePath})`
|
|
25108
25943
|
);
|
|
@@ -25120,7 +25955,7 @@ var PatchApplicationService = class {
|
|
|
25120
25955
|
fix,
|
|
25121
25956
|
scanContext
|
|
25122
25957
|
});
|
|
25123
|
-
appliedFiles.push(
|
|
25958
|
+
appliedFiles.push(path29.relative(repositoryPath, actualPath));
|
|
25124
25959
|
logDebug(`[${scanContext}] Modified file: ${relativePath}`);
|
|
25125
25960
|
}
|
|
25126
25961
|
}
|
|
@@ -25317,7 +26152,7 @@ init_configs();
|
|
|
25317
26152
|
// src/mcp/services/FileOperations.ts
|
|
25318
26153
|
init_FileUtils();
|
|
25319
26154
|
import fs25 from "fs";
|
|
25320
|
-
import
|
|
26155
|
+
import path30 from "path";
|
|
25321
26156
|
import AdmZip4 from "adm-zip";
|
|
25322
26157
|
var FileOperations = class {
|
|
25323
26158
|
/**
|
|
@@ -25337,10 +26172,10 @@ var FileOperations = class {
|
|
|
25337
26172
|
let packedFilesCount = 0;
|
|
25338
26173
|
const packedFiles = [];
|
|
25339
26174
|
const excludedFiles = [];
|
|
25340
|
-
const resolvedRepoPath =
|
|
26175
|
+
const resolvedRepoPath = path30.resolve(repositoryPath);
|
|
25341
26176
|
for (const filepath of fileList) {
|
|
25342
|
-
const absoluteFilepath =
|
|
25343
|
-
const resolvedFilePath =
|
|
26177
|
+
const absoluteFilepath = path30.join(repositoryPath, filepath);
|
|
26178
|
+
const resolvedFilePath = path30.resolve(absoluteFilepath);
|
|
25344
26179
|
if (!resolvedFilePath.startsWith(resolvedRepoPath)) {
|
|
25345
26180
|
const reason = "potential path traversal security risk";
|
|
25346
26181
|
logDebug(`[FileOperations] Skipping ${filepath} due to ${reason}`);
|
|
@@ -25387,11 +26222,11 @@ var FileOperations = class {
|
|
|
25387
26222
|
fileList,
|
|
25388
26223
|
repositoryPath
|
|
25389
26224
|
}) {
|
|
25390
|
-
const resolvedRepoPath =
|
|
26225
|
+
const resolvedRepoPath = path30.resolve(repositoryPath);
|
|
25391
26226
|
const validatedPaths = [];
|
|
25392
26227
|
for (const filepath of fileList) {
|
|
25393
|
-
const absoluteFilepath =
|
|
25394
|
-
const resolvedFilePath =
|
|
26228
|
+
const absoluteFilepath = path30.join(repositoryPath, filepath);
|
|
26229
|
+
const resolvedFilePath = path30.resolve(absoluteFilepath);
|
|
25395
26230
|
if (!resolvedFilePath.startsWith(resolvedRepoPath)) {
|
|
25396
26231
|
logDebug(
|
|
25397
26232
|
`[FileOperations] Rejecting ${filepath} - path traversal attempt detected`
|
|
@@ -25419,7 +26254,7 @@ var FileOperations = class {
|
|
|
25419
26254
|
for (const absolutePath of filePaths) {
|
|
25420
26255
|
try {
|
|
25421
26256
|
const content = await fs25.promises.readFile(absolutePath);
|
|
25422
|
-
const relativePath =
|
|
26257
|
+
const relativePath = path30.basename(absolutePath);
|
|
25423
26258
|
fileDataArray.push({
|
|
25424
26259
|
relativePath,
|
|
25425
26260
|
absolutePath,
|
|
@@ -25731,14 +26566,14 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
|
|
|
25731
26566
|
* since the last scan.
|
|
25732
26567
|
*/
|
|
25733
26568
|
async scanForSecurityVulnerabilities({
|
|
25734
|
-
path:
|
|
26569
|
+
path: path34,
|
|
25735
26570
|
isAllDetectionRulesScan,
|
|
25736
26571
|
isAllFilesScan,
|
|
25737
26572
|
scanContext
|
|
25738
26573
|
}) {
|
|
25739
26574
|
this.hasAuthenticationFailed = false;
|
|
25740
26575
|
logDebug(`[${scanContext}] Scanning for new security vulnerabilities`, {
|
|
25741
|
-
path:
|
|
26576
|
+
path: path34
|
|
25742
26577
|
});
|
|
25743
26578
|
if (!this.gqlClient) {
|
|
25744
26579
|
logInfo(`[${scanContext}] No GQL client found, skipping scan`);
|
|
@@ -25754,11 +26589,11 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
|
|
|
25754
26589
|
}
|
|
25755
26590
|
logDebug(
|
|
25756
26591
|
`[${scanContext}] Connected to the API, assembling list of files to scan`,
|
|
25757
|
-
{ path:
|
|
26592
|
+
{ path: path34 }
|
|
25758
26593
|
);
|
|
25759
26594
|
const isBackgroundScan = scanContext === ScanContext.BACKGROUND_INITIAL || scanContext === ScanContext.BACKGROUND_PERIODIC;
|
|
25760
26595
|
const files = await getLocalFiles({
|
|
25761
|
-
path:
|
|
26596
|
+
path: path34,
|
|
25762
26597
|
isAllFilesScan,
|
|
25763
26598
|
scanContext,
|
|
25764
26599
|
scanRecentlyChangedFiles: !isBackgroundScan
|
|
@@ -25784,13 +26619,13 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
|
|
|
25784
26619
|
});
|
|
25785
26620
|
const { fixReportId, projectId } = await scanFiles({
|
|
25786
26621
|
fileList: filesToScan.map((file) => file.relativePath),
|
|
25787
|
-
repositoryPath:
|
|
26622
|
+
repositoryPath: path34,
|
|
25788
26623
|
gqlClient: this.gqlClient,
|
|
25789
26624
|
isAllDetectionRulesScan,
|
|
25790
26625
|
scanContext
|
|
25791
26626
|
});
|
|
25792
26627
|
logInfo(
|
|
25793
|
-
`[${scanContext}] Security scan completed for ${
|
|
26628
|
+
`[${scanContext}] Security scan completed for ${path34} reportId: ${fixReportId} projectId: ${projectId}`
|
|
25794
26629
|
);
|
|
25795
26630
|
if (isAllFilesScan) {
|
|
25796
26631
|
return;
|
|
@@ -26084,13 +26919,13 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
|
|
|
26084
26919
|
});
|
|
26085
26920
|
return scannedFiles.some((file) => file.relativePath === fixFile);
|
|
26086
26921
|
}
|
|
26087
|
-
async getFreshFixes({ path:
|
|
26922
|
+
async getFreshFixes({ path: path34 }) {
|
|
26088
26923
|
const scanContext = ScanContext.USER_REQUEST;
|
|
26089
|
-
logDebug(`[${scanContext}] Getting fresh fixes`, { path:
|
|
26090
|
-
if (this.path !==
|
|
26091
|
-
this.path =
|
|
26924
|
+
logDebug(`[${scanContext}] Getting fresh fixes`, { path: path34 });
|
|
26925
|
+
if (this.path !== path34) {
|
|
26926
|
+
this.path = path34;
|
|
26092
26927
|
this.reset();
|
|
26093
|
-
logInfo(`[${scanContext}] Reset service state for new path`, { path:
|
|
26928
|
+
logInfo(`[${scanContext}] Reset service state for new path`, { path: path34 });
|
|
26094
26929
|
}
|
|
26095
26930
|
try {
|
|
26096
26931
|
const loginContext = createMcpLoginContext("check_new_fixes");
|
|
@@ -26109,7 +26944,7 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
|
|
|
26109
26944
|
}
|
|
26110
26945
|
throw error;
|
|
26111
26946
|
}
|
|
26112
|
-
this.triggerScan({ path:
|
|
26947
|
+
this.triggerScan({ path: path34, gqlClient: this.gqlClient });
|
|
26113
26948
|
let isMvsAutoFixEnabled = null;
|
|
26114
26949
|
try {
|
|
26115
26950
|
isMvsAutoFixEnabled = await this.gqlClient.getMvsAutoFixSettings();
|
|
@@ -26143,33 +26978,33 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
|
|
|
26143
26978
|
return noFreshFixesPrompt;
|
|
26144
26979
|
}
|
|
26145
26980
|
triggerScan({
|
|
26146
|
-
path:
|
|
26981
|
+
path: path34,
|
|
26147
26982
|
gqlClient
|
|
26148
26983
|
}) {
|
|
26149
|
-
if (this.path !==
|
|
26150
|
-
this.path =
|
|
26984
|
+
if (this.path !== path34) {
|
|
26985
|
+
this.path = path34;
|
|
26151
26986
|
this.reset();
|
|
26152
|
-
logInfo(`Reset service state for new path in triggerScan`, { path:
|
|
26987
|
+
logInfo(`Reset service state for new path in triggerScan`, { path: path34 });
|
|
26153
26988
|
}
|
|
26154
26989
|
this.gqlClient = gqlClient;
|
|
26155
26990
|
if (!this.intervalId) {
|
|
26156
|
-
this.startPeriodicScanning(
|
|
26157
|
-
this.executeInitialScan(
|
|
26158
|
-
void this.executeInitialFullScan(
|
|
26991
|
+
this.startPeriodicScanning(path34);
|
|
26992
|
+
this.executeInitialScan(path34);
|
|
26993
|
+
void this.executeInitialFullScan(path34);
|
|
26159
26994
|
}
|
|
26160
26995
|
}
|
|
26161
|
-
startPeriodicScanning(
|
|
26996
|
+
startPeriodicScanning(path34) {
|
|
26162
26997
|
const scanContext = ScanContext.BACKGROUND_PERIODIC;
|
|
26163
26998
|
logDebug(
|
|
26164
26999
|
`[${scanContext}] Starting periodic scan for new security vulnerabilities`,
|
|
26165
27000
|
{
|
|
26166
|
-
path:
|
|
27001
|
+
path: path34
|
|
26167
27002
|
}
|
|
26168
27003
|
);
|
|
26169
27004
|
this.intervalId = setInterval(() => {
|
|
26170
|
-
logDebug(`[${scanContext}] Triggering periodic security scan`, { path:
|
|
27005
|
+
logDebug(`[${scanContext}] Triggering periodic security scan`, { path: path34 });
|
|
26171
27006
|
this.scanForSecurityVulnerabilities({
|
|
26172
|
-
path:
|
|
27007
|
+
path: path34,
|
|
26173
27008
|
scanContext
|
|
26174
27009
|
}).catch((error) => {
|
|
26175
27010
|
logError(`[${scanContext}] Error during periodic security scan`, {
|
|
@@ -26178,45 +27013,45 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
|
|
|
26178
27013
|
});
|
|
26179
27014
|
}, MCP_PERIODIC_CHECK_INTERVAL);
|
|
26180
27015
|
}
|
|
26181
|
-
async executeInitialFullScan(
|
|
27016
|
+
async executeInitialFullScan(path34) {
|
|
26182
27017
|
const scanContext = ScanContext.FULL_SCAN;
|
|
26183
|
-
logDebug(`[${scanContext}] Triggering initial full security scan`, { path:
|
|
27018
|
+
logDebug(`[${scanContext}] Triggering initial full security scan`, { path: path34 });
|
|
26184
27019
|
logDebug(`[${scanContext}] Full scan paths scanned`, {
|
|
26185
27020
|
fullScanPathsScanned: this.fullScanPathsScanned
|
|
26186
27021
|
});
|
|
26187
|
-
if (this.fullScanPathsScanned.includes(
|
|
27022
|
+
if (this.fullScanPathsScanned.includes(path34)) {
|
|
26188
27023
|
logDebug(`[${scanContext}] Full scan already executed for this path`, {
|
|
26189
|
-
path:
|
|
27024
|
+
path: path34
|
|
26190
27025
|
});
|
|
26191
27026
|
return;
|
|
26192
27027
|
}
|
|
26193
27028
|
configStore.set("fullScanPathsScanned", [
|
|
26194
27029
|
...this.fullScanPathsScanned,
|
|
26195
|
-
|
|
27030
|
+
path34
|
|
26196
27031
|
]);
|
|
26197
27032
|
try {
|
|
26198
27033
|
await this.scanForSecurityVulnerabilities({
|
|
26199
|
-
path:
|
|
27034
|
+
path: path34,
|
|
26200
27035
|
isAllFilesScan: true,
|
|
26201
27036
|
isAllDetectionRulesScan: true,
|
|
26202
27037
|
scanContext: ScanContext.FULL_SCAN
|
|
26203
27038
|
});
|
|
26204
|
-
if (!this.fullScanPathsScanned.includes(
|
|
26205
|
-
this.fullScanPathsScanned.push(
|
|
27039
|
+
if (!this.fullScanPathsScanned.includes(path34)) {
|
|
27040
|
+
this.fullScanPathsScanned.push(path34);
|
|
26206
27041
|
configStore.set("fullScanPathsScanned", this.fullScanPathsScanned);
|
|
26207
27042
|
}
|
|
26208
|
-
logInfo(`[${scanContext}] Full scan completed`, { path:
|
|
27043
|
+
logInfo(`[${scanContext}] Full scan completed`, { path: path34 });
|
|
26209
27044
|
} catch (error) {
|
|
26210
27045
|
logError(`[${scanContext}] Error during initial full security scan`, {
|
|
26211
27046
|
error
|
|
26212
27047
|
});
|
|
26213
27048
|
}
|
|
26214
27049
|
}
|
|
26215
|
-
executeInitialScan(
|
|
27050
|
+
executeInitialScan(path34) {
|
|
26216
27051
|
const scanContext = ScanContext.BACKGROUND_INITIAL;
|
|
26217
|
-
logDebug(`[${scanContext}] Triggering initial security scan`, { path:
|
|
27052
|
+
logDebug(`[${scanContext}] Triggering initial security scan`, { path: path34 });
|
|
26218
27053
|
this.scanForSecurityVulnerabilities({
|
|
26219
|
-
path:
|
|
27054
|
+
path: path34,
|
|
26220
27055
|
scanContext: ScanContext.BACKGROUND_INITIAL
|
|
26221
27056
|
}).catch((error) => {
|
|
26222
27057
|
logError(`[${scanContext}] Error during initial security scan`, { error });
|
|
@@ -26313,9 +27148,9 @@ Example payload:
|
|
|
26313
27148
|
`Invalid path: potential security risk detected in path: ${pathValidationResult.error}`
|
|
26314
27149
|
);
|
|
26315
27150
|
}
|
|
26316
|
-
const
|
|
27151
|
+
const path34 = pathValidationResult.path;
|
|
26317
27152
|
const resultText = await this.newFixesService.getFreshFixes({
|
|
26318
|
-
path:
|
|
27153
|
+
path: path34
|
|
26319
27154
|
});
|
|
26320
27155
|
logInfo("CheckForNewAvailableFixesTool execution completed", {
|
|
26321
27156
|
resultText
|
|
@@ -26493,8 +27328,8 @@ Call this tool instead of ${MCP_TOOL_SCAN_AND_FIX_VULNERABILITIES} when you only
|
|
|
26493
27328
|
`Invalid path: potential security risk detected in path: ${pathValidationResult.error}`
|
|
26494
27329
|
);
|
|
26495
27330
|
}
|
|
26496
|
-
const
|
|
26497
|
-
const gitService = new GitService(
|
|
27331
|
+
const path34 = pathValidationResult.path;
|
|
27332
|
+
const gitService = new GitService(path34, log);
|
|
26498
27333
|
const gitValidation = await gitService.validateRepository();
|
|
26499
27334
|
if (!gitValidation.isValid) {
|
|
26500
27335
|
throw new Error(`Invalid git repository: ${gitValidation.error}`);
|
|
@@ -26879,9 +27714,9 @@ Example payload:
|
|
|
26879
27714
|
`Invalid path: potential security risk detected in path: ${pathValidationResult.error}`
|
|
26880
27715
|
);
|
|
26881
27716
|
}
|
|
26882
|
-
const
|
|
27717
|
+
const path34 = pathValidationResult.path;
|
|
26883
27718
|
const files = await getLocalFiles({
|
|
26884
|
-
path:
|
|
27719
|
+
path: path34,
|
|
26885
27720
|
maxFileSize: MCP_MAX_FILE_SIZE,
|
|
26886
27721
|
maxFiles: args.maxFiles,
|
|
26887
27722
|
scanContext: ScanContext.USER_REQUEST,
|
|
@@ -26901,7 +27736,7 @@ Example payload:
|
|
|
26901
27736
|
try {
|
|
26902
27737
|
const fixResult = await this.vulnerabilityFixService.processVulnerabilities({
|
|
26903
27738
|
fileList: files.map((file) => file.relativePath),
|
|
26904
|
-
repositoryPath:
|
|
27739
|
+
repositoryPath: path34,
|
|
26905
27740
|
offset: args.offset,
|
|
26906
27741
|
limit: args.limit,
|
|
26907
27742
|
isRescan: args.rescan || !!args.maxFiles
|
|
@@ -27202,10 +28037,10 @@ init_client_generates();
|
|
|
27202
28037
|
init_urlParser2();
|
|
27203
28038
|
|
|
27204
28039
|
// src/features/codeium_intellij/codeium_language_server_grpc_client.ts
|
|
27205
|
-
import
|
|
28040
|
+
import path31 from "path";
|
|
27206
28041
|
import * as grpc from "@grpc/grpc-js";
|
|
27207
28042
|
import * as protoLoader from "@grpc/proto-loader";
|
|
27208
|
-
var PROTO_PATH =
|
|
28043
|
+
var PROTO_PATH = path31.join(
|
|
27209
28044
|
getModuleRootDir(),
|
|
27210
28045
|
"src/features/codeium_intellij/proto/exa/language_server_pb/language_server.proto"
|
|
27211
28046
|
);
|
|
@@ -27217,7 +28052,7 @@ function loadProto() {
|
|
|
27217
28052
|
defaults: true,
|
|
27218
28053
|
oneofs: true,
|
|
27219
28054
|
includeDirs: [
|
|
27220
|
-
|
|
28055
|
+
path31.join(getModuleRootDir(), "src/features/codeium_intellij/proto")
|
|
27221
28056
|
]
|
|
27222
28057
|
});
|
|
27223
28058
|
return grpc.loadPackageDefinition(
|
|
@@ -27273,28 +28108,28 @@ async function getGrpcClient(port, csrf3) {
|
|
|
27273
28108
|
// src/features/codeium_intellij/parse_intellij_logs.ts
|
|
27274
28109
|
import fs27 from "fs";
|
|
27275
28110
|
import os14 from "os";
|
|
27276
|
-
import
|
|
28111
|
+
import path32 from "path";
|
|
27277
28112
|
function getLogsDir() {
|
|
27278
28113
|
if (process.platform === "darwin") {
|
|
27279
|
-
return
|
|
28114
|
+
return path32.join(os14.homedir(), "Library/Logs/JetBrains");
|
|
27280
28115
|
} else if (process.platform === "win32") {
|
|
27281
|
-
return
|
|
27282
|
-
process.env["LOCALAPPDATA"] ||
|
|
28116
|
+
return path32.join(
|
|
28117
|
+
process.env["LOCALAPPDATA"] || path32.join(os14.homedir(), "AppData/Local"),
|
|
27283
28118
|
"JetBrains"
|
|
27284
28119
|
);
|
|
27285
28120
|
} else {
|
|
27286
|
-
return
|
|
28121
|
+
return path32.join(os14.homedir(), ".cache/JetBrains");
|
|
27287
28122
|
}
|
|
27288
28123
|
}
|
|
27289
28124
|
function parseIdeLogDir(ideLogDir) {
|
|
27290
28125
|
const logFiles = fs27.readdirSync(ideLogDir).filter((f) => /^idea(\.\d+)?\.log$/.test(f)).map((f) => ({
|
|
27291
28126
|
name: f,
|
|
27292
|
-
mtime: fs27.statSync(
|
|
28127
|
+
mtime: fs27.statSync(path32.join(ideLogDir, f)).mtimeMs
|
|
27293
28128
|
})).sort((a, b) => a.mtime - b.mtime).map((f) => f.name);
|
|
27294
28129
|
let latestCsrf = null;
|
|
27295
28130
|
let latestPort = null;
|
|
27296
28131
|
for (const logFile of logFiles) {
|
|
27297
|
-
const lines = fs27.readFileSync(
|
|
28132
|
+
const lines = fs27.readFileSync(path32.join(ideLogDir, logFile), "utf-8").split("\n");
|
|
27298
28133
|
for (const line of lines) {
|
|
27299
28134
|
if (!line.includes(
|
|
27300
28135
|
"com.codeium.intellij.language_server.LanguageServerProcessHandler"
|
|
@@ -27322,9 +28157,9 @@ function findRunningCodeiumLanguageServers() {
|
|
|
27322
28157
|
const logsDir = getLogsDir();
|
|
27323
28158
|
if (!fs27.existsSync(logsDir)) return results;
|
|
27324
28159
|
for (const ide of fs27.readdirSync(logsDir)) {
|
|
27325
|
-
let ideLogDir =
|
|
28160
|
+
let ideLogDir = path32.join(logsDir, ide);
|
|
27326
28161
|
if (process.platform !== "darwin") {
|
|
27327
|
-
ideLogDir =
|
|
28162
|
+
ideLogDir = path32.join(ideLogDir, "log");
|
|
27328
28163
|
}
|
|
27329
28164
|
if (!fs27.existsSync(ideLogDir) || !fs27.statSync(ideLogDir).isDirectory()) {
|
|
27330
28165
|
continue;
|
|
@@ -27507,10 +28342,10 @@ function processChatStepCodeAction(step) {
|
|
|
27507
28342
|
// src/features/codeium_intellij/install_hook.ts
|
|
27508
28343
|
import fsPromises5 from "fs/promises";
|
|
27509
28344
|
import os15 from "os";
|
|
27510
|
-
import
|
|
28345
|
+
import path33 from "path";
|
|
27511
28346
|
import chalk14 from "chalk";
|
|
27512
28347
|
function getCodeiumHooksPath() {
|
|
27513
|
-
return
|
|
28348
|
+
return path33.join(os15.homedir(), ".codeium", "hooks.json");
|
|
27514
28349
|
}
|
|
27515
28350
|
async function readCodeiumHooks() {
|
|
27516
28351
|
const hooksPath = getCodeiumHooksPath();
|
|
@@ -27523,7 +28358,7 @@ async function readCodeiumHooks() {
|
|
|
27523
28358
|
}
|
|
27524
28359
|
async function writeCodeiumHooks(config2) {
|
|
27525
28360
|
const hooksPath = getCodeiumHooksPath();
|
|
27526
|
-
const dir =
|
|
28361
|
+
const dir = path33.dirname(hooksPath);
|
|
27527
28362
|
await fsPromises5.mkdir(dir, { recursive: true });
|
|
27528
28363
|
await fsPromises5.writeFile(
|
|
27529
28364
|
hooksPath,
|