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/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 stat3 = await fsPromises.stat(fullPath);
3583
- if (stat3.isDirectory()) {
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: stat3.mtime.getTime(),
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 path32 = [
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(`${path32}?${params2}`, origin).toString();
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 path32 = entry.item.path;
7236
- return path32.startsWith("/") ? path32.slice(1) : path32;
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: path32,
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: path32,
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: path32,
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: path32,
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 stat3 = fs11.statSync(resolvedPath);
16654
- if (!stat3.isDirectory()) {
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 path19 from "path";
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/claude_code/daemon_pid_file.ts
17058
- import fs13 from "fs";
17059
- import os4 from "os";
17060
- import path13 from "path";
17061
-
17062
- // src/features/claude_code/data_collector_constants.ts
17063
- var CC_VERSION_CACHE_KEY = "claudeCode.detectedCCVersion";
17064
- var CC_VERSION_CLI_KEY = "claudeCode.detectedCCVersionCli";
17065
- var GQL_AUTH_TIMEOUT_MS = 15e3;
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 path14 from "path";
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 ? path14.relative(group.skillPath, file.path).replace(/\\/g, "/") : path14.basename(file.path);
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 path15 from "path";
17141
+ import path14 from "path";
17204
17142
  import { globby as globby2 } from "globby";
17205
- var MAX_CONTEXT_FILE_SIZE = 20 * 1024 * 1024;
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
- { glob: ".claude/skills/**/*", category: SKILL_CATEGORY, root: "home" },
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/*.instructions.md",
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: SKILL_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
- function groupSkills(files, root, baseDir) {
17301
- const skillFiles = files.filter((f) => f.category === SKILL_CATEGORY);
17302
- const folderMap = /* @__PURE__ */ new Map();
17303
- const standalone = [];
17304
- for (const f of skillFiles) {
17305
- const rel = path15.relative(baseDir, f.path).replace(/\\/g, "/");
17306
- const skillsMarker = "skills/";
17307
- const skillsIdx = rel.indexOf(skillsMarker);
17308
- if (skillsIdx === -1) {
17309
- standalone.push(f);
17310
- continue;
17311
- }
17312
- const relFromSkills = rel.slice(skillsIdx + skillsMarker.length);
17313
- const slashIdx = relFromSkills.indexOf("/");
17314
- if (slashIdx === -1) {
17315
- standalone.push(f);
17316
- } else {
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
- const groups = [];
17325
- for (const f of standalone) {
17326
- const name = path15.basename(f.path, path15.extname(f.path));
17327
- const sessionKey = `skill:${root}:${name}`;
17328
- groups.push({
17329
- name,
17330
- root,
17331
- skillPath: f.path,
17332
- files: [f],
17333
- isFolder: false,
17334
- maxMtimeMs: f.mtimeMs,
17335
- sessionKey
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
- for (const [folderName, folderFiles] of folderMap) {
17339
- const maxMtimeMs = Math.max(...folderFiles.map((f) => f.mtimeMs));
17340
- const anyFile = folderFiles[0];
17341
- const rel = path15.relative(baseDir, anyFile.path).replace(/\\/g, "/");
17342
- const skillsIdx = rel.indexOf("skills/");
17343
- const skillRelPath = rel.slice(
17344
- 0,
17345
- skillsIdx + "skills/".length + folderName.length
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
- return groups;
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
- async function scanContextFiles(workspaceRoot, platform2, sessionId) {
17362
- const entries = SCAN_PATHS[platform2];
17363
- if (!entries || entries.length === 0) {
17364
- return { regularFiles: [], skillGroups: [] };
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
- const now = Date.now();
17367
- for (const [sid, entry] of sessionMtimes) {
17368
- if (now - entry.lastUpdatedAt > SESSION_TTL_MS) {
17369
- sessionMtimes.delete(sid);
17370
- }
17380
+ if (/^~[^/]/.test(s)) {
17381
+ return null;
17371
17382
  }
17372
- const home = homedir();
17373
- const sessionEntry = sessionId ? sessionMtimes.get(sessionId) : void 0;
17374
- const allFiles = [];
17375
- const skillFilesByRoot = /* @__PURE__ */ new Map();
17376
- const seenPaths = /* @__PURE__ */ new Set();
17377
- for (const entry of entries) {
17378
- const baseDir = entry.root === "home" ? home : workspaceRoot;
17379
- const matchedFiles = await globby2(entry.glob, {
17380
- cwd: baseDir,
17381
- absolute: true,
17382
- onlyFiles: true,
17383
- dot: true
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
- for (const filePath of matchedFiles) {
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: entry.category,
17647
+ category,
17404
17648
  mtimeMs: fileStat.mtimeMs
17405
17649
  };
17406
- if (entry.category === SKILL_CATEGORY) {
17407
- let rootFiles = skillFilesByRoot.get(entry.root);
17408
- if (!rootFiles) {
17409
- rootFiles = [];
17410
- skillFilesByRoot.set(entry.root, rootFiles);
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
- rootFiles.push(fileEntry);
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 (_err) {
17669
+ } catch {
17421
17670
  }
17422
17671
  }
17423
17672
  }
17424
17673
  const allSkillGroups = [];
17425
- for (const [root, files] of skillFilesByRoot) {
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 = path15.relative(baseDir, filePath);
17763
+ const relative2 = path14.relative(baseDir, filePath);
17442
17764
  if (!relative2.startsWith("..")) {
17443
17765
  return relative2.replace(/\\/g, "/");
17444
17766
  }
17445
- return path15.basename(filePath);
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(path32) {
17711
- scopePath = path32;
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.3.7" : "unknown";
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 = path16.basename(transcriptPath);
18027
- const dirName = path16.basename(path16.dirname(transcriptPath));
18028
- const projectsDir = path16.dirname(path16.dirname(transcriptPath));
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 = path16.join(projectsDir, baseDirName, filename);
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 readdir(projectsDir);
18872
+ const dirs = await readdir2(projectsDir);
18051
18873
  for (const dir of dirs) {
18052
18874
  if (dir === dirName) continue;
18053
- const candidate = path16.join(projectsDir, dir, filename);
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 stat3 = await fh.stat();
18085
- fileSize = stat3.size;
18086
- if (cursor.byteOffset >= stat3.size) {
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(stat3.size - cursor.byteOffset);
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 readFile2(transcriptPath, "utf-8");
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 readdir(configDir);
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 = path16.join(configDir, file);
19060
+ const filePath = path18.join(configDir, file);
18239
19061
  try {
18240
- const content = JSON.parse(await readFile2(filePath, "utf-8"));
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 path17 from "path";
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 = path17.join(os6.homedir(), ".claude", "settings.json");
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 readdir2, stat as stat2 } from "fs/promises";
19461
+ import { open as open5, readdir as readdir3, stat as stat3 } from "fs/promises";
18640
19462
  import os7 from "os";
18641
- import path18 from "path";
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(path18.join(configDir, "projects"));
19469
+ dirs.push(path20.join(configDir, "projects"));
18648
19470
  }
18649
- dirs.push(path18.join(os7.homedir(), ".config", "claude", "projects"));
18650
- dirs.push(path18.join(os7.homedir(), ".claude", "projects"));
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 = path18.join(dir, file);
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 stat2(filePath);
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 readdir2(projectsDir);
19506
+ projectDirs = await readdir3(projectsDir);
18685
19507
  } catch {
18686
19508
  continue;
18687
19509
  }
18688
19510
  for (const projName of projectDirs) {
18689
- const projPath = path18.join(projectsDir, projName);
19511
+ const projPath = path20.join(projectsDir, projName);
18690
19512
  let projStat;
18691
19513
  try {
18692
- projStat = await stat2(projPath);
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 readdir2(projPath);
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 = path18.join(projPath, entry, "subagents");
19528
+ const subagentsDir = path20.join(projPath, entry, "subagents");
18707
19529
  try {
18708
- const s = await stat2(subagentsDir);
19530
+ const s = await stat3(subagentsDir);
18709
19531
  if (!s.isDirectory()) continue;
18710
- const subFiles = await readdir2(subagentsDir);
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 = path19.dirname(sessionStore.path);
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(path32) {
19066
- this.knownWorkspacePath = path32;
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 path20 from "path";
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 = path20.join(home, ".claude", "ide");
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 = path20.join(claudeIdePath, lockFile);
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
- path20.join(currentDir, ".cursor", "mcp.json"),
20810
+ path22.join(currentDir, ".cursor", "mcp.json"),
19976
20811
  // local first
19977
- path20.join(home, ".cursor", "mcp.json")
20812
+ path22.join(home, ".cursor", "mcp.json")
19978
20813
  ];
19979
20814
  case "windsurf":
19980
20815
  return [
19981
- path20.join(currentDir, ".codeium", "mcp_config.json"),
20816
+ path22.join(currentDir, ".codeium", "mcp_config.json"),
19982
20817
  // local first
19983
- path20.join(home, ".codeium", "windsurf", "mcp_config.json")
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
- path20.join(currentDir, ".vscode", "mcp.json"),
20825
+ path22.join(currentDir, ".vscode", "mcp.json"),
19991
20826
  // local first
19992
- process.platform === "win32" ? path20.join(home, "AppData", "Roaming", "Code", "User", "mcp.json") : path20.join(
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
- path20.join(currentDir, ".claude.json"),
20838
+ path22.join(currentDir, ".claude.json"),
20004
20839
  // local first
20005
- path20.join(home, ".claude.json")
20840
+ path22.join(home, ".claude.json")
20006
20841
  ];
20007
20842
  const workspacePaths = getClaudeWorkspacePaths();
20008
20843
  for (const workspacePath of workspacePaths) {
20009
- claudePaths.push(path20.join(workspacePath, ".mcp.json"));
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((path32) => ideConfigPaths.add(path32));
21005
+ configPaths.forEach((path34) => ideConfigPaths.add(path34));
20171
21006
  }
20172
21007
  const uniqueAdditionalPaths = additionalMcpList.filter(
20173
- (path32) => !ideConfigPaths.has(path32)
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 path21 from "path";
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 = path21.join(dir, entry.name);
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
- path21.join(home, ".cursor"),
20333
- path21.join(home, "Documents"),
20334
- path21.join(home, "Downloads")
21167
+ path23.join(home, ".cursor"),
21168
+ path23.join(home, "Documents"),
21169
+ path23.join(home, "Downloads")
20335
21170
  ] : [
20336
- path21.join(home, ".cursor"),
20337
- process.env["XDG_CONFIG_HOME"] || path21.join(home, ".config"),
20338
- path21.join(home, "Documents"),
20339
- path21.join(home, "Downloads")
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 path23 from "path";
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 path22 from "path";
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 path24 from "path";
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 path25 from "path";
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 = path25.normalize(inputPath);
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: path32,
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: path32,
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(path32);
24311
+ const resolvedRepoPath = await fs22.realpath(path34);
23477
24312
  logDebug(`[${scanContext}] Resolved repository path`, {
23478
24313
  resolvedRepoPath,
23479
- originalPath: path32
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: path32,
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: path32
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 path26 from "path";
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 = path26.join(
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 = path26.join(mobbFolderPath, uniqueFileName);
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 = path26.parse(baseFileName).name;
23911
- const extension = path26.parse(baseFileName).ext;
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(path26.join(folderPath, uniqueFileName))) {
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 = path26.join(mobbFolderPath, "patchInfo.md");
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 ? path26.resolve(this.repoPath, relativePatchedFilePath) : null;
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 existsSync6,
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 path27 from "path";
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 = path27.extname(filePath).toLowerCase();
24338
- const basename2 = path27.basename(filePath);
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 = path27.dirname(normalizedFilePath);
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 = path27.resolve(repositoryPath);
24556
- const normalizedPath = path27.resolve(repoRoot, targetPath);
24557
- const repoRootWithSep = repoRoot.endsWith(path27.sep) ? repoRoot : `${repoRoot}${path27.sep}`;
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: path27.relative(repoRoot, normalizedPath)
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 = path27.resolve(repositoryPath, targetFile);
24849
- if (existsSync6(absolutePath)) {
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: existsSync6(absoluteFilePath)
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(path27.relative(repositoryPath, actualPath));
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 (existsSync6(absoluteFilePath)) {
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 (!existsSync6(absoluteFilePath)) {
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(path27.relative(repositoryPath, actualPath));
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 path28 from "path";
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 = path28.resolve(repositoryPath);
26175
+ const resolvedRepoPath = path30.resolve(repositoryPath);
25341
26176
  for (const filepath of fileList) {
25342
- const absoluteFilepath = path28.join(repositoryPath, filepath);
25343
- const resolvedFilePath = path28.resolve(absoluteFilepath);
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 = path28.resolve(repositoryPath);
26225
+ const resolvedRepoPath = path30.resolve(repositoryPath);
25391
26226
  const validatedPaths = [];
25392
26227
  for (const filepath of fileList) {
25393
- const absoluteFilepath = path28.join(repositoryPath, filepath);
25394
- const resolvedFilePath = path28.resolve(absoluteFilepath);
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 = path28.basename(absolutePath);
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: path32,
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: path32
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: path32 }
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: path32,
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: path32,
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 ${path32} reportId: ${fixReportId} projectId: ${projectId}`
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: path32 }) {
26922
+ async getFreshFixes({ path: path34 }) {
26088
26923
  const scanContext = ScanContext.USER_REQUEST;
26089
- logDebug(`[${scanContext}] Getting fresh fixes`, { path: path32 });
26090
- if (this.path !== path32) {
26091
- this.path = path32;
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: path32 });
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: path32, gqlClient: this.gqlClient });
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: path32,
26981
+ path: path34,
26147
26982
  gqlClient
26148
26983
  }) {
26149
- if (this.path !== path32) {
26150
- this.path = path32;
26984
+ if (this.path !== path34) {
26985
+ this.path = path34;
26151
26986
  this.reset();
26152
- logInfo(`Reset service state for new path in triggerScan`, { path: path32 });
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(path32);
26157
- this.executeInitialScan(path32);
26158
- void this.executeInitialFullScan(path32);
26991
+ this.startPeriodicScanning(path34);
26992
+ this.executeInitialScan(path34);
26993
+ void this.executeInitialFullScan(path34);
26159
26994
  }
26160
26995
  }
26161
- startPeriodicScanning(path32) {
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: path32
27001
+ path: path34
26167
27002
  }
26168
27003
  );
26169
27004
  this.intervalId = setInterval(() => {
26170
- logDebug(`[${scanContext}] Triggering periodic security scan`, { path: path32 });
27005
+ logDebug(`[${scanContext}] Triggering periodic security scan`, { path: path34 });
26171
27006
  this.scanForSecurityVulnerabilities({
26172
- path: path32,
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(path32) {
27016
+ async executeInitialFullScan(path34) {
26182
27017
  const scanContext = ScanContext.FULL_SCAN;
26183
- logDebug(`[${scanContext}] Triggering initial full security scan`, { path: path32 });
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(path32)) {
27022
+ if (this.fullScanPathsScanned.includes(path34)) {
26188
27023
  logDebug(`[${scanContext}] Full scan already executed for this path`, {
26189
- path: path32
27024
+ path: path34
26190
27025
  });
26191
27026
  return;
26192
27027
  }
26193
27028
  configStore.set("fullScanPathsScanned", [
26194
27029
  ...this.fullScanPathsScanned,
26195
- path32
27030
+ path34
26196
27031
  ]);
26197
27032
  try {
26198
27033
  await this.scanForSecurityVulnerabilities({
26199
- path: path32,
27034
+ path: path34,
26200
27035
  isAllFilesScan: true,
26201
27036
  isAllDetectionRulesScan: true,
26202
27037
  scanContext: ScanContext.FULL_SCAN
26203
27038
  });
26204
- if (!this.fullScanPathsScanned.includes(path32)) {
26205
- this.fullScanPathsScanned.push(path32);
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: path32 });
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(path32) {
27050
+ executeInitialScan(path34) {
26216
27051
  const scanContext = ScanContext.BACKGROUND_INITIAL;
26217
- logDebug(`[${scanContext}] Triggering initial security scan`, { path: path32 });
27052
+ logDebug(`[${scanContext}] Triggering initial security scan`, { path: path34 });
26218
27053
  this.scanForSecurityVulnerabilities({
26219
- path: path32,
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 path32 = pathValidationResult.path;
27151
+ const path34 = pathValidationResult.path;
26317
27152
  const resultText = await this.newFixesService.getFreshFixes({
26318
- path: path32
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 path32 = pathValidationResult.path;
26497
- const gitService = new GitService(path32, log);
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 path32 = pathValidationResult.path;
27717
+ const path34 = pathValidationResult.path;
26883
27718
  const files = await getLocalFiles({
26884
- path: path32,
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: path32,
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 path29 from "path";
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 = path29.join(
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
- path29.join(getModuleRootDir(), "src/features/codeium_intellij/proto")
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 path30 from "path";
28111
+ import path32 from "path";
27277
28112
  function getLogsDir() {
27278
28113
  if (process.platform === "darwin") {
27279
- return path30.join(os14.homedir(), "Library/Logs/JetBrains");
28114
+ return path32.join(os14.homedir(), "Library/Logs/JetBrains");
27280
28115
  } else if (process.platform === "win32") {
27281
- return path30.join(
27282
- process.env["LOCALAPPDATA"] || path30.join(os14.homedir(), "AppData/Local"),
28116
+ return path32.join(
28117
+ process.env["LOCALAPPDATA"] || path32.join(os14.homedir(), "AppData/Local"),
27283
28118
  "JetBrains"
27284
28119
  );
27285
28120
  } else {
27286
- return path30.join(os14.homedir(), ".cache/JetBrains");
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(path30.join(ideLogDir, f)).mtimeMs
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(path30.join(ideLogDir, logFile), "utf-8").split("\n");
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 = path30.join(logsDir, ide);
28160
+ let ideLogDir = path32.join(logsDir, ide);
27326
28161
  if (process.platform !== "darwin") {
27327
- ideLogDir = path30.join(ideLogDir, "log");
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 path31 from "path";
28345
+ import path33 from "path";
27511
28346
  import chalk14 from "chalk";
27512
28347
  function getCodeiumHooksPath() {
27513
- return path31.join(os15.homedir(), ".codeium", "hooks.json");
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 = path31.dirname(hooksPath);
28361
+ const dir = path33.dirname(hooksPath);
27527
28362
  await fsPromises5.mkdir(dir, { recursive: true });
27528
28363
  await fsPromises5.writeFile(
27529
28364
  hooksPath,