mobbdev 1.4.0 → 1.4.2

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
@@ -263,10 +263,12 @@ var init_client_generates = __esm({
263
263
  IssueType_Enum2["ImproperExceptionHandling"] = "IMPROPER_EXCEPTION_HANDLING";
264
264
  IssueType_Enum2["ImproperResourceShutdownOrRelease"] = "IMPROPER_RESOURCE_SHUTDOWN_OR_RELEASE";
265
265
  IssueType_Enum2["ImproperStringFormatting"] = "IMPROPER_STRING_FORMATTING";
266
+ IssueType_Enum2["ImproperValidationOfArrayIndex"] = "IMPROPER_VALIDATION_OF_ARRAY_INDEX";
266
267
  IssueType_Enum2["IncompleteHostnameRegex"] = "INCOMPLETE_HOSTNAME_REGEX";
267
268
  IssueType_Enum2["IncompleteSanitization"] = "INCOMPLETE_SANITIZATION";
268
269
  IssueType_Enum2["IncompleteUrlSanitization"] = "INCOMPLETE_URL_SANITIZATION";
269
270
  IssueType_Enum2["IncompleteUrlSchemeCheck"] = "INCOMPLETE_URL_SCHEME_CHECK";
271
+ IssueType_Enum2["IncorrectIntegerConversion"] = "INCORRECT_INTEGER_CONVERSION";
270
272
  IssueType_Enum2["IncorrectSqlApiUsage"] = "INCORRECT_SQL_API_USAGE";
271
273
  IssueType_Enum2["InformationExposureViaHeaders"] = "INFORMATION_EXPOSURE_VIA_HEADERS";
272
274
  IssueType_Enum2["InsecureBinderConfiguration"] = "INSECURE_BINDER_CONFIGURATION";
@@ -291,6 +293,7 @@ var init_client_generates = __esm({
291
293
  IssueType_Enum2["MissingUser"] = "MISSING_USER";
292
294
  IssueType_Enum2["MissingWhitespace"] = "MISSING_WHITESPACE";
293
295
  IssueType_Enum2["MissingWorkflowPermissions"] = "MISSING_WORKFLOW_PERMISSIONS";
296
+ IssueType_Enum2["MissingXFrameOptions"] = "MISSING_X_FRAME_OPTIONS";
294
297
  IssueType_Enum2["ModifiedDefaultParam"] = "MODIFIED_DEFAULT_PARAM";
295
298
  IssueType_Enum2["NonFinalPublicStaticField"] = "NON_FINAL_PUBLIC_STATIC_FIELD";
296
299
  IssueType_Enum2["NonReadonlyField"] = "NON_READONLY_FIELD";
@@ -408,6 +411,7 @@ var init_client_generates = __esm({
408
411
  return Vulnerability_Report_Issue_Tag_Enum3;
409
412
  })(Vulnerability_Report_Issue_Tag_Enum || {});
410
413
  Vulnerability_Report_Vendor_Enum = /* @__PURE__ */ ((Vulnerability_Report_Vendor_Enum3) => {
414
+ Vulnerability_Report_Vendor_Enum3["BlackDuck"] = "blackDuck";
411
415
  Vulnerability_Report_Vendor_Enum3["Checkmarx"] = "checkmarx";
412
416
  Vulnerability_Report_Vendor_Enum3["CheckmarxXml"] = "checkmarxXml";
413
417
  Vulnerability_Report_Vendor_Enum3["Codeql"] = "codeql";
@@ -1467,7 +1471,10 @@ var init_getIssueType = __esm({
1467
1471
  ["REDUNDANT_NIL_ERROR_CHECK" /* RedundantNilErrorCheck */]: "Redundant Nil Error Check",
1468
1472
  ["MISSING_WORKFLOW_PERMISSIONS" /* MissingWorkflowPermissions */]: "Missing Workflow Permissions",
1469
1473
  ["EXCESSIVE_SECRETS_EXPOSURE" /* ExcessiveSecretsExposure */]: "Excessive Secrets Exposure",
1470
- ["TAINTED_NUMERIC_CAST" /* TaintedNumericCast */]: "Tainted Numeric Cast"
1474
+ ["TAINTED_NUMERIC_CAST" /* TaintedNumericCast */]: "Tainted Numeric Cast",
1475
+ ["MISSING_X_FRAME_OPTIONS" /* MissingXFrameOptions */]: "Missing X-Frame-Options Header",
1476
+ ["IMPROPER_VALIDATION_OF_ARRAY_INDEX" /* ImproperValidationOfArrayIndex */]: "Improper Validation of Array Index",
1477
+ ["INCORRECT_INTEGER_CONVERSION" /* IncorrectIntegerConversion */]: "Incorrect Integer Conversion"
1471
1478
  };
1472
1479
  issueTypeZ = z.nativeEnum(IssueType_Enum);
1473
1480
  getIssueTypeFriendlyString = (issueType) => {
@@ -4141,11 +4148,11 @@ ${rootContent}`;
4141
4148
  try {
4142
4149
  const gitRoot = await this.getGitRoot();
4143
4150
  const gitignorePath = path2.join(gitRoot, ".gitignore");
4144
- const exists = fs2.existsSync(gitignorePath);
4151
+ const exists2 = fs2.existsSync(gitignorePath);
4145
4152
  this.log("[GitService] .gitignore existence check complete", "debug", {
4146
- exists
4153
+ exists: exists2
4147
4154
  });
4148
- return exists;
4155
+ return exists2;
4149
4156
  } catch (error) {
4150
4157
  const errorMessage = `Failed to check .gitignore existence: ${error.message}`;
4151
4158
  this.log(`[GitService] ${errorMessage}`, "error", { error });
@@ -4660,7 +4667,10 @@ var fixDetailsData = {
4660
4667
  ["REDUNDANT_NIL_ERROR_CHECK" /* RedundantNilErrorCheck */]: void 0,
4661
4668
  ["MISSING_WORKFLOW_PERMISSIONS" /* MissingWorkflowPermissions */]: void 0,
4662
4669
  ["EXCESSIVE_SECRETS_EXPOSURE" /* ExcessiveSecretsExposure */]: void 0,
4663
- ["TAINTED_NUMERIC_CAST" /* TaintedNumericCast */]: void 0
4670
+ ["TAINTED_NUMERIC_CAST" /* TaintedNumericCast */]: void 0,
4671
+ ["MISSING_X_FRAME_OPTIONS" /* MissingXFrameOptions */]: void 0,
4672
+ ["IMPROPER_VALIDATION_OF_ARRAY_INDEX" /* ImproperValidationOfArrayIndex */]: void 0,
4673
+ ["INCORRECT_INTEGER_CONVERSION" /* IncorrectIntegerConversion */]: void 0
4664
4674
  };
4665
4675
 
4666
4676
  // src/features/analysis/scm/shared/src/commitDescriptionMarkup.ts
@@ -5997,6 +6007,19 @@ var headerMaxAge = {
5997
6007
  }
5998
6008
  };
5999
6009
 
6010
+ // src/features/analysis/scm/shared/src/storedQuestionData/js/missingXFrameOptions.ts
6011
+ var xFrameOptionsValue = {
6012
+ xFrameOptionsValue: {
6013
+ content: () => "Please provide the value for the X-Frame-Options header",
6014
+ description: () => `The \`X-Frame-Options\` HTTP response header tells the browser whether the page is allowed to be rendered inside a \`<frame>\`, \`<iframe>\`, \`<embed>\` or \`<object>\`. Without it, attackers can embed your application in an invisible iframe and trick users into clicking on it \u2014 a class of attacks known as clickjacking (UI redressing).
6015
+ &nbsp;
6016
+ &nbsp; **Allowed values:**
6017
+ &nbsp; - \`DENY\` \u2014 the page cannot be framed by any site, including your own. Recommended default for any page that does not need to be embedded.
6018
+ &nbsp; - \`SAMEORIGIN\` \u2014 the page can only be framed by pages served from the same origin. Use this only if your own application legitimately embeds this page in an iframe.`,
6019
+ guidance: () => ``
6020
+ }
6021
+ };
6022
+
6000
6023
  // src/features/analysis/scm/shared/src/storedQuestionData/js/noLimitsOrThrottling.ts
6001
6024
  var noLimitsOrThrottling2 = {
6002
6025
  setGlobalLimiter: {
@@ -6141,6 +6164,7 @@ var vulnerabilities13 = {
6141
6164
  ["UNCHECKED_LOOP_CONDITION" /* UncheckedLoopCondition */]: uncheckedLoopCondition2,
6142
6165
  ["NO_LIMITS_OR_THROTTLING" /* NoLimitsOrThrottling */]: noLimitsOrThrottling2,
6143
6166
  ["MISSING_CSP_HEADER" /* MissingCspHeader */]: cspHeaderValue,
6167
+ ["MISSING_X_FRAME_OPTIONS" /* MissingXFrameOptions */]: xFrameOptionsValue,
6144
6168
  ["HARDCODED_DOMAIN_IN_HTML" /* HardcodedDomainInHtml */]: hardcodedDomainInHtml,
6145
6169
  ["CSRF" /* Csrf */]: csrf2
6146
6170
  };
@@ -6461,6 +6485,13 @@ var ReferenceType = /* @__PURE__ */ ((ReferenceType2) => {
6461
6485
  ReferenceType2["TAG"] = "TAG";
6462
6486
  return ReferenceType2;
6463
6487
  })(ReferenceType || {});
6488
+ var GithubFullShaZ = z13.string().regex(/^[a-f0-9]{40}$/);
6489
+ var MergedPrSurvivalMetadataZ = z13.object({
6490
+ mergeCommitShas: z13.array(GithubFullShaZ).min(1).refine((shas) => new Set(shas).size === shas.length, {
6491
+ message: "mergeCommitShas must contain unique SHAs"
6492
+ }),
6493
+ targetBranch: z13.string().min(1)
6494
+ });
6464
6495
  var ScmLibScmType = /* @__PURE__ */ ((ScmLibScmType2) => {
6465
6496
  ScmLibScmType2["GITHUB"] = "GITHUB";
6466
6497
  ScmLibScmType2["GITLAB"] = "GITLAB";
@@ -7147,7 +7178,7 @@ async function getAdoSdk(params) {
7147
7178
  const url = new URL(repoUrl);
7148
7179
  const origin = url.origin.toLowerCase().endsWith(".visualstudio.com") ? DEFUALT_ADO_ORIGIN : url.origin.toLowerCase();
7149
7180
  const params2 = `path=/&versionDescriptor[versionOptions]=0&versionDescriptor[versionType]=commit&versionDescriptor[version]=${branch}&resolveLfs=true&$format=zip&api-version=5.0&download=true`;
7150
- const path34 = [
7181
+ const path36 = [
7151
7182
  prefixPath,
7152
7183
  owner,
7153
7184
  projectName,
@@ -7158,7 +7189,7 @@ async function getAdoSdk(params) {
7158
7189
  "items",
7159
7190
  "items"
7160
7191
  ].filter(Boolean).join("/");
7161
- return new URL(`${path34}?${params2}`, origin).toString();
7192
+ return new URL(`${path36}?${params2}`, origin).toString();
7162
7193
  },
7163
7194
  async getAdoBranchList({ repoUrl }) {
7164
7195
  try {
@@ -7247,8 +7278,8 @@ async function getAdoSdk(params) {
7247
7278
  const changeType = entry.changeType;
7248
7279
  return changeType !== 16 && entry.item?.path;
7249
7280
  }).map((entry) => {
7250
- const path34 = entry.item.path;
7251
- return path34.startsWith("/") ? path34.slice(1) : path34;
7281
+ const path36 = entry.item.path;
7282
+ return path36.startsWith("/") ? path36.slice(1) : path36;
7252
7283
  });
7253
7284
  },
7254
7285
  async searchAdoPullRequests({
@@ -7649,6 +7680,12 @@ var SCMLib = class {
7649
7680
  async getPrDataBatch(_repoUrl, _prNumbers) {
7650
7681
  throw new Error("getPrDataBatch not implemented for this SCM provider");
7651
7682
  }
7683
+ /**
7684
+ * GitHub: merge detection for main-branch survival. Other providers return null.
7685
+ */
7686
+ async getMergedPrSurvivalMetadata(_prNumber) {
7687
+ return null;
7688
+ }
7652
7689
  getAccessToken() {
7653
7690
  return this.accessToken || "";
7654
7691
  }
@@ -8910,6 +8947,24 @@ async function encryptSecret(secret, key) {
8910
8947
  return sodium.to_base64(encBytes, sodium.base64_variants.ORIGINAL);
8911
8948
  }
8912
8949
 
8950
+ // src/features/analysis/scm/github/utils/mergeCommitShas.ts
8951
+ async function commitShasBetweenBaseAndMerge(githubSdk, args) {
8952
+ let compare;
8953
+ try {
8954
+ compare = await githubSdk.compareCommitsBasehead({
8955
+ owner: args.owner,
8956
+ repo: args.repo,
8957
+ basehead: `${args.baseSha}...${args.mergeCommitSha}`
8958
+ });
8959
+ } catch (err) {
8960
+ throw new Error(
8961
+ `Failed to compare commits ${args.baseSha}...${args.mergeCommitSha}: ${err instanceof Error ? err.message : String(err)}`
8962
+ );
8963
+ }
8964
+ const shas = compare.data.commits.map((c) => c.sha);
8965
+ return shas.length > 0 ? shas : [args.mergeCommitSha];
8966
+ }
8967
+
8913
8968
  // src/features/analysis/scm/github/utils/utils.ts
8914
8969
  import { Octokit } from "octokit";
8915
8970
  import { fetch as fetch2, ProxyAgent } from "undici";
@@ -9642,6 +9697,12 @@ function getGithubSdk(params = {}) {
9642
9697
  );
9643
9698
  return res;
9644
9699
  },
9700
+ async listPullRequestCommits(params2) {
9701
+ return octokit.rest.pulls.listCommits(params2);
9702
+ },
9703
+ async compareCommitsBasehead(params2) {
9704
+ return octokit.rest.repos.compareCommitsWithBasehead(params2);
9705
+ },
9645
9706
  /**
9646
9707
  * List PRs using GitHub's REST `/repos/{owner}/{repo}/pulls` endpoint.
9647
9708
  * https://docs.github.com/en/rest/pulls/pulls?apiVersion=2022-11-28#list-pull-requests
@@ -10456,6 +10517,34 @@ var GithubSCMLib = class extends SCMLib {
10456
10517
  commentIds
10457
10518
  };
10458
10519
  }
10520
+ /**
10521
+ * Detect merge strategy and SHAs on the target branch for main-branch survival (GitHub only).
10522
+ */
10523
+ async getMergedPrSurvivalMetadata(prNumber) {
10524
+ this._validateAccessTokenAndUrl();
10525
+ const { owner, repo } = parseGithubOwnerAndRepo(this.url);
10526
+ const pr = await this.githubSdk.getPr({
10527
+ owner,
10528
+ repo,
10529
+ pull_number: prNumber
10530
+ });
10531
+ if (pr.data.merged !== true || !pr.data.merge_commit_sha) {
10532
+ return null;
10533
+ }
10534
+ const mergeCommitSha = pr.data.merge_commit_sha;
10535
+ const targetBranch = pr.data.base.ref;
10536
+ const baseSha = pr.data.base.sha;
10537
+ const mergeCommitShas = await commitShasBetweenBaseAndMerge(
10538
+ this.githubSdk,
10539
+ {
10540
+ owner,
10541
+ repo,
10542
+ baseSha,
10543
+ mergeCommitSha
10544
+ }
10545
+ );
10546
+ return { mergeCommitShas, targetBranch };
10547
+ }
10459
10548
  };
10460
10549
 
10461
10550
  // src/features/analysis/scm/gitlab/gitlab.ts
@@ -12462,7 +12551,8 @@ var SCANNERS = {
12462
12551
  Snyk: "snyk",
12463
12552
  Sonarqube: "sonarqube",
12464
12553
  Semgrep: "semgrep",
12465
- Datadog: "datadog"
12554
+ Datadog: "datadog",
12555
+ BlackDuck: "blackduck"
12466
12556
  };
12467
12557
  var scannerToVulnerabilityReportVendorEnum = {
12468
12558
  [SCANNERS.Checkmarx]: "checkmarx" /* Checkmarx */,
@@ -12471,7 +12561,8 @@ var scannerToVulnerabilityReportVendorEnum = {
12471
12561
  [SCANNERS.Codeql]: "codeql" /* Codeql */,
12472
12562
  [SCANNERS.Fortify]: "fortify" /* Fortify */,
12473
12563
  [SCANNERS.Semgrep]: "semgrep" /* Semgrep */,
12474
- [SCANNERS.Datadog]: "datadog" /* Datadog */
12564
+ [SCANNERS.Datadog]: "datadog" /* Datadog */,
12565
+ [SCANNERS.BlackDuck]: "blackDuck" /* BlackDuck */
12475
12566
  };
12476
12567
  var SupportedScannersZ = z25.enum([SCANNERS.Checkmarx, SCANNERS.Snyk]);
12477
12568
  var envVariablesSchema = z25.object({
@@ -14890,7 +14981,8 @@ var scannerToFriendlyString = {
14890
14981
  snyk: "Snyk",
14891
14982
  sonarqube: "Sonarqube",
14892
14983
  semgrep: "Semgrep",
14893
- datadog: "Datadog"
14984
+ datadog: "Datadog",
14985
+ blackduck: "Black Duck"
14894
14986
  };
14895
14987
 
14896
14988
  // src/features/analysis/add_fix_comments_for_pr/utils/buildCommentBody.ts
@@ -15080,7 +15172,7 @@ async function postIssueComment(params) {
15080
15172
  fpDescription
15081
15173
  } = params;
15082
15174
  const {
15083
- path: path34,
15175
+ path: path36,
15084
15176
  startLine,
15085
15177
  vulnerabilityReportIssue: {
15086
15178
  vulnerabilityReportIssueTags,
@@ -15095,7 +15187,7 @@ async function postIssueComment(params) {
15095
15187
  Refresh the page in order to see the changes.`,
15096
15188
  pull_number: pullRequest,
15097
15189
  commit_id: commitSha,
15098
- path: path34,
15190
+ path: path36,
15099
15191
  line: startLine
15100
15192
  });
15101
15193
  const commentId = commentRes.data.id;
@@ -15129,7 +15221,7 @@ async function postFixComment(params) {
15129
15221
  scanner
15130
15222
  } = params;
15131
15223
  const {
15132
- path: path34,
15224
+ path: path36,
15133
15225
  startLine,
15134
15226
  vulnerabilityReportIssue: { fixId, vulnerabilityReportIssueTags, category },
15135
15227
  vulnerabilityReportIssueId
@@ -15147,7 +15239,7 @@ async function postFixComment(params) {
15147
15239
  Refresh the page in order to see the changes.`,
15148
15240
  pull_number: pullRequest,
15149
15241
  commit_id: commitSha,
15150
- path: path34,
15242
+ path: path36,
15151
15243
  line: startLine
15152
15244
  });
15153
15245
  const commentId = commentRes.data.id;
@@ -16998,7 +17090,7 @@ function analyzeBuilder(yargs2) {
16998
17090
  alias: "scan-file",
16999
17091
  type: "string",
17000
17092
  describe: chalk10.bold(
17001
- "Select the vulnerability report to analyze (Checkmarx, Snyk, Fortify, CodeQL, Sonarqube, Semgrep, Datadog)"
17093
+ "Select the vulnerability report to analyze (Checkmarx, Snyk, Fortify, CodeQL, Sonarqube, Semgrep, Datadog, Black Duck)"
17002
17094
  )
17003
17095
  }).option("repo", repoOption).option("p", {
17004
17096
  alias: "src-path",
@@ -17069,7 +17161,7 @@ import { spawn } from "child_process";
17069
17161
 
17070
17162
  // src/features/claude_code/daemon.ts
17071
17163
  import { readFileSync, writeFileSync as writeFileSync2 } from "fs";
17072
- import path21 from "path";
17164
+ import path23 from "path";
17073
17165
  import { setTimeout as sleep2 } from "timers/promises";
17074
17166
  import Configstore3 from "configstore";
17075
17167
 
@@ -17081,7 +17173,12 @@ var HEARTBEAT_DEBOUNCE_MS = (() => {
17081
17173
  })();
17082
17174
  var KILL_SWITCH_ENV = "MOBB_TRACY_SKILL_QUARANTINE_DISABLE";
17083
17175
  var MALICIOUS_VERDICT = "MALICIOUS";
17084
- var ORPHAN_SWEEP_GRACE_MS = 10 * 60 * 1e3;
17176
+ var PARTIAL_SWEEP_GRACE_MS = 10 * 60 * 1e3;
17177
+ var STUB_MARKER = "\u26D4 QUARANTINED BY TRACY";
17178
+
17179
+ // src/features/analysis/skill_quarantine/enumerateInstalledSkills.ts
17180
+ import { homedir as homedir2 } from "os";
17181
+ import path15 from "path";
17085
17182
 
17086
17183
  // src/features/analysis/context_file_processor.ts
17087
17184
  import { createHash } from "crypto";
@@ -17136,7 +17233,7 @@ async function processContextFiles(regularFiles, skillGroups) {
17136
17233
  }
17137
17234
 
17138
17235
  // src/features/analysis/context_file_scanner.ts
17139
- import { lstat, readFile, stat } from "fs/promises";
17236
+ import { lstat, readdir, readFile, realpath, stat } from "fs/promises";
17140
17237
  import { homedir } from "os";
17141
17238
  import path14 from "path";
17142
17239
  import { globby as globby2 } from "globby";
@@ -17160,15 +17257,15 @@ var SCAN_PATHS = {
17160
17257
  root: "home"
17161
17258
  },
17162
17259
  { kind: "skill-bundle", skillsRoot: ".claude/skills", root: "workspace" },
17163
- { glob: ".claude/commands/*.md", category: "command", root: "workspace" },
17260
+ { glob: ".claude/commands/*.md", category: "skill", root: "workspace" },
17164
17261
  {
17165
17262
  glob: ".claude/agents/*.md",
17166
- category: "agent-config",
17263
+ category: SKILL_CATEGORY,
17167
17264
  root: "workspace"
17168
17265
  },
17169
17266
  { kind: "skill-bundle", skillsRoot: ".claude/skills", root: "home" },
17170
- { glob: ".claude/commands/*.md", category: "command", root: "home" },
17171
- { glob: ".claude/agents/*.md", category: "agent-config", root: "home" },
17267
+ { glob: ".claude/commands/*.md", category: "skill", root: "home" },
17268
+ { glob: ".claude/agents/*.md", category: SKILL_CATEGORY, root: "home" },
17172
17269
  { glob: ".claude/settings.json", category: "config", root: "workspace" },
17173
17270
  {
17174
17271
  glob: ".claude/settings.local.json",
@@ -17247,7 +17344,7 @@ var SCAN_PATHS = {
17247
17344
  },
17248
17345
  {
17249
17346
  glob: ".claude/agents/*.md",
17250
- category: "agent-config",
17347
+ category: SKILL_CATEGORY,
17251
17348
  root: "workspace"
17252
17349
  },
17253
17350
  // Agent skills — Copilot discovers skills in all three roots (VS Code docs:
@@ -17279,7 +17376,7 @@ var SCAN_PATHS = {
17279
17376
  // Cross-compat home paths (Copilot reads Claude / generic agent dirs too)
17280
17377
  { glob: ".claude/CLAUDE.md", category: "rule", root: "home" },
17281
17378
  { glob: ".claude/rules/**/*.md", category: "rule", root: "home" },
17282
- { glob: ".claude/agents/*.md", category: "agent-config", root: "home" },
17379
+ { glob: ".claude/agents/*.md", category: SKILL_CATEGORY, root: "home" },
17283
17380
  { kind: "skill-bundle", skillsRoot: ".claude/skills", root: "home" },
17284
17381
  { kind: "skill-bundle", skillsRoot: ".agents/skills", root: "home" }
17285
17382
  ]
@@ -17450,11 +17547,11 @@ async function readJsoncSettings(settingsPath) {
17450
17547
  putSettingsCache(settingsPath, { mtimeMs, parsed: payload });
17451
17548
  return payload;
17452
17549
  }
17453
- function putSettingsCache(path34, entry) {
17454
- if (!settingsCache.has(path34) && settingsCache.size >= MAX_SETTINGS_CACHE_SIZE) {
17550
+ function putSettingsCache(path36, entry) {
17551
+ if (!settingsCache.has(path36) && settingsCache.size >= MAX_SETTINGS_CACHE_SIZE) {
17455
17552
  settingsCache.delete(settingsCache.keys().next().value);
17456
17553
  }
17457
- settingsCache.set(path34, entry);
17554
+ settingsCache.set(path36, entry);
17458
17555
  }
17459
17556
  async function readCopilotCustomLocations(workspaceRoot) {
17460
17557
  const parsed = await readJsoncSettings(
@@ -17704,7 +17801,7 @@ async function enumerateGlob(pattern, cwd, category, isDynamic) {
17704
17801
  } catch {
17705
17802
  return [];
17706
17803
  }
17707
- return files.map((path34) => ({ path: path34, category }));
17804
+ return files.map((path36) => ({ path: path36, category }));
17708
17805
  }
17709
17806
  async function enumerateSkillBundle(baseDir, skillsRoot) {
17710
17807
  const skillsDir = path14.resolve(baseDir, skillsRoot);
@@ -17738,7 +17835,68 @@ async function enumerateSkillBundle(baseDir, skillsRoot) {
17738
17835
  }
17739
17836
  })
17740
17837
  );
17741
- return perSkillResults.flat().map((p) => ({ path: p, category: "skill" }));
17838
+ const standaloneFiles = await enumerateStandaloneSkills(skillsDir);
17839
+ const symlinkResults = await enumerateSymlinkedSkills(skillsDir);
17840
+ return [
17841
+ ...perSkillResults.flat().map((p) => ({ path: p, category: "skill" })),
17842
+ ...standaloneFiles.map((p) => ({ path: p, category: "skill" })),
17843
+ ...symlinkResults
17844
+ ];
17845
+ }
17846
+ async function enumerateStandaloneSkills(skillsDir) {
17847
+ try {
17848
+ return await globby2("*.md", {
17849
+ cwd: skillsDir,
17850
+ absolute: true,
17851
+ onlyFiles: true,
17852
+ dot: false,
17853
+ followSymbolicLinks: false
17854
+ });
17855
+ } catch {
17856
+ return [];
17857
+ }
17858
+ }
17859
+ async function enumerateSymlinkedSkills(skillsDir) {
17860
+ let dirEntries;
17861
+ try {
17862
+ dirEntries = await readdir(skillsDir, {
17863
+ withFileTypes: true,
17864
+ encoding: "utf8"
17865
+ });
17866
+ } catch {
17867
+ return [];
17868
+ }
17869
+ const results = [];
17870
+ for (const entry of dirEntries) {
17871
+ if (!entry.isSymbolicLink()) continue;
17872
+ const entryPath = path14.join(skillsDir, entry.name);
17873
+ try {
17874
+ const st = await stat(entryPath);
17875
+ if (st.isDirectory()) {
17876
+ const hasManifest = await stat(path14.join(entryPath, "SKILL.md")).then(() => true).catch(() => false);
17877
+ if (!hasManifest) continue;
17878
+ const realDir = await realpath(entryPath);
17879
+ const realFiles = await globby2("**/*", {
17880
+ cwd: realDir,
17881
+ absolute: true,
17882
+ onlyFiles: true,
17883
+ dot: true,
17884
+ followSymbolicLinks: false,
17885
+ deep: SKILL_BUNDLE_MAX_DEPTH
17886
+ }).catch(() => []);
17887
+ for (const f of realFiles) {
17888
+ results.push({
17889
+ path: path14.join(entryPath, path14.relative(realDir, f)),
17890
+ category: "skill"
17891
+ });
17892
+ }
17893
+ } else if (st.isFile() && entry.name.endsWith(".md")) {
17894
+ results.push({ path: entryPath, category: "skill" });
17895
+ }
17896
+ } catch {
17897
+ }
17898
+ }
17899
+ return results;
17742
17900
  }
17743
17901
  var DYNAMIC_SCAN_MAX_DEPTH = 6;
17744
17902
  var SKILL_MANIFEST_SCAN_DEPTH = 2;
@@ -17769,15 +17927,26 @@ function deriveIdentifier(filePath, baseDir) {
17769
17927
 
17770
17928
  // src/features/analysis/skill_quarantine/enumerateInstalledSkills.ts
17771
17929
  async function enumerateInstalledSkills(workspaceRoot) {
17772
- const { skillGroups } = await scanContextFiles(
17930
+ const { skillGroups, regularFiles } = await scanContextFiles(
17773
17931
  workspaceRoot,
17774
17932
  "claude-code",
17775
17933
  void 0
17776
17934
  );
17777
- if (skillGroups.length === 0) {
17935
+ const home = homedir2();
17936
+ const agentGroups = regularFiles.filter((f) => f.category === "agent-config").map((f) => ({
17937
+ name: path15.basename(f.path, path15.extname(f.path)),
17938
+ root: f.path.startsWith(home + path15.sep) ? "home" : "workspace",
17939
+ skillPath: f.path,
17940
+ files: [f],
17941
+ isFolder: false,
17942
+ maxMtimeMs: f.mtimeMs,
17943
+ sessionKey: `agent-config:${f.path}`
17944
+ }));
17945
+ const allGroups = [...skillGroups, ...agentGroups];
17946
+ if (allGroups.length === 0) {
17778
17947
  return [];
17779
17948
  }
17780
- const { skills } = await processContextFiles([], skillGroups);
17949
+ const { skills } = await processContextFiles([], allGroups);
17781
17950
  return skills.map((s) => {
17782
17951
  const parts = s.group.skillPath.split(/[\\/]/);
17783
17952
  const origName = parts[parts.length - 1] || s.group.name;
@@ -17798,64 +17967,73 @@ var Metric = {
17798
17967
  CHECK_DISABLED_ENV: "skill_quarantine.check_disabled_env",
17799
17968
  /** Verdict-query call failed. Fail-open. */
17800
17969
  QUERY_ERROR: "skill_quarantine.query_error",
17801
- /** Count of skills enumerated in this run (histogram-ish). */
17970
+ /** Count of skills enumerated in this run. */
17802
17971
  SKILLS_CHECKED: "skill_quarantine.skills_checked",
17803
- /** A skill was freshly quarantined. Tagged with shape. */
17972
+ /** A skill was freshly quarantined. */
17804
17973
  QUARANTINED: "skill_quarantine.quarantined",
17805
- /** Presence check hit; skill already quarantined. */
17974
+ /** Presence check hit (`<md5>.zip` exists); skill already quarantined. */
17806
17975
  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. */
17976
+ /** Zip build or partial→tmp rename failed (phase 1). */
17977
+ ZIP_ERROR: "skill_quarantine.zip_error",
17978
+ /** Stub write failed (phase 2); tmp preserved for reconcile. */
17810
17979
  STUB_ERROR: "skill_quarantine.stub_error",
17811
- /** A stale staging dir was swept. */
17812
- ORPHAN_SWEPT: "skill_quarantine.orphan_swept",
17980
+ /** Tmp→published rename failed (phase 3); reconcile will retry. */
17981
+ PUBLISH_ERROR: "skill_quarantine.publish_error",
17982
+ /** Reconcile published a leftover tmp whose `<md5>.zip` was missing. */
17983
+ RECONCILED: "skill_quarantine.reconciled",
17984
+ /** Tmp removed because `<md5>.zip` already existed. */
17985
+ SWEPT_REDUNDANT_TMP: "skill_quarantine.swept_redundant_tmp",
17986
+ /** Stale partial zip swept (older than grace window). */
17987
+ SWEPT_PARTIAL: "skill_quarantine.swept_partial",
17813
17988
  /** Total run duration including I/O. */
17814
17989
  DURATION_MS: "skill_quarantine.duration_ms"
17815
17990
  };
17816
17991
 
17817
17992
  // src/features/analysis/skill_quarantine/quarantineSkill.ts
17818
17993
  import { randomUUID } from "crypto";
17819
- import { existsSync as existsSync2 } from "fs";
17820
17994
  import {
17995
+ access,
17821
17996
  mkdir,
17822
- readdir,
17997
+ readdir as readdir2,
17823
17998
  readFile as readFile2,
17824
17999
  rename,
17825
18000
  rm,
17826
18001
  stat as stat2,
18002
+ unlink,
17827
18003
  writeFile
17828
18004
  } from "fs/promises";
17829
- import path16 from "path";
17830
- import { move } from "fs-extra";
18005
+ import path18 from "path";
18006
+ import AdmZip4 from "adm-zip";
17831
18007
 
17832
18008
  // src/features/analysis/skill_quarantine/paths.ts
17833
- import { homedir as homedir2 } from "os";
17834
- import path15 from "path";
18009
+ import { homedir as homedir3 } from "os";
18010
+ import path16 from "path";
17835
18011
  function getQuarantineRoot() {
17836
- return path15.join(homedir2(), ".tracy", "quarantine", "claude", "skills");
17837
- }
17838
- function getQuarantinedHashDir(md5) {
17839
- return path15.join(getQuarantineRoot(), md5);
18012
+ return path16.join(homedir3(), ".tracy", "quarantine", "claude", "skills");
17840
18013
  }
17841
- function getQuarantinedTargetPath(md5, origName) {
17842
- return path15.join(getQuarantinedHashDir(md5), origName);
18014
+ function getQuarantineZipPath(md5) {
18015
+ return path16.join(getQuarantineRoot(), `${md5}.zip`);
17843
18016
  }
17844
- function getStagingDir(md5, pid, uuid) {
17845
- return path15.join(getQuarantineRoot(), `${md5}_tmp_${pid}_${uuid}`);
18017
+ function getTmpZipPath(md5, uuid) {
18018
+ return path16.join(getQuarantineRoot(), `${md5}_tmp_${uuid}.zip`);
17846
18019
  }
17847
- var STAGING_DIR_REGEX = /^([0-9a-f]{32})_tmp_/;
18020
+ var TMP_ZIP_REGEX = /^([0-9a-f]{32})_tmp_[0-9a-f-]+\.zip$/;
18021
+ var COMMITTED_ZIP_REGEX = /^([0-9a-f]{32})\.zip$/;
17848
18022
 
17849
18023
  // src/features/analysis/skill_quarantine/stubTemplate.ts
18024
+ import path17 from "path";
18025
+ import { quote } from "shell-quote";
17850
18026
  var LEGACY_SUMMARY_FALLBACK = "not available (scan predates current schema)";
17851
18027
  function renderStub(params) {
17852
18028
  const folderOrFile = params.isFolder ? "skill folder" : "skill file";
17853
18029
  const reason = params.summary ?? LEGACY_SUMMARY_FALLBACK;
17854
- return `# \u26D4 QUARANTINED BY TRACY
18030
+ const extractParent = path17.dirname(params.origPath);
18031
+ const recoverCommand = `rm -rf ${quote([params.origPath])} && unzip -o ${quote([params.quarantinedZipPath])} -d ${quote([extractParent])}`;
18032
+ return `# ${STUB_MARKER}
17855
18033
 
17856
18034
  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.
18035
+ archived out of your skills folder. **Claude Code will not execute it** while
18036
+ this stub is in place.
17859
18037
 
17860
18038
  ## Why this skill was flagged
17861
18039
 
@@ -17866,23 +18044,22 @@ stub is in place.
17866
18044
 
17867
18045
  ## Where the original is now
17868
18046
 
17869
- The original ${folderOrFile} has been moved to:
18047
+ The original ${folderOrFile} has been archived to:
17870
18048
 
17871
- ${params.quarantinedPath}
18049
+ ${params.quarantinedZipPath}
17872
18050
 
17873
- Nothing has been deleted. The contents are intact; only the location changed.
18051
+ Nothing has been deleted. The archive preserves the skill exactly as it was,
18052
+ including any secrets or local-only edits.
17874
18053
 
17875
18054
  ## If this is a false positive \u2014 how to recover
17876
18055
 
17877
18056
  If you're confident this skill is safe and want to restore it:
17878
18057
 
17879
- mv ${params.quarantinedPath} ${params.origPath}
18058
+ ${recoverCommand}
17880
18059
 
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.
18060
+ Tracy will not re-quarantine it as long as \`${params.md5}.zip\` remains in
18061
+ the quarantine folder. If you delete the archive, the next heartbeat will
18062
+ re-evaluate the skill from scratch.
17886
18063
 
17887
18064
  ## How to report a false positive
17888
18065
 
@@ -17899,77 +18076,50 @@ Your report helps tune the scanner for everyone.
17899
18076
  // src/features/analysis/skill_quarantine/quarantineSkill.ts
17900
18077
  async function quarantineSkill(params) {
17901
18078
  const { skillPath, isFolder, md5, origName, verdict, log: log2 } = params;
17902
- const hashDir = getQuarantinedHashDir(md5);
17903
- if (existsSync2(hashDir)) {
18079
+ const finalZip = getQuarantineZipPath(md5);
18080
+ if (await exists(finalZip)) {
17904
18081
  log2.debug(
17905
18082
  { md5, metric: Metric.ALREADY_QUARANTINED },
17906
- "skill_quarantine: already quarantined, skipping"
18083
+ "skill_quarantine: already quarantined"
17907
18084
  );
17908
18085
  return { status: "already_quarantined" };
17909
18086
  }
17910
- const stagingDir = getStagingDir(md5, process.pid, randomUUID());
17911
- const stagingTarget = path16.join(stagingDir, origName);
17912
- const finalTarget = getQuarantinedTargetPath(md5, origName);
18087
+ const tmpZip = getTmpZipPath(md5, randomUUID());
17913
18088
  try {
17914
- await mkdir(stagingDir, { recursive: true });
18089
+ await mkdir(getQuarantineRoot(), { recursive: true });
18090
+ const zip = new AdmZip4();
18091
+ if (isFolder) {
18092
+ await addFolderAsync(zip, skillPath, origName);
18093
+ } else {
18094
+ zip.addFile(origName, await readFile2(skillPath));
18095
+ }
18096
+ await writeFile(tmpZip, zip.toBuffer());
17915
18097
  } catch (err) {
18098
+ await unlink(tmpZip).catch(ignoreErr);
17916
18099
  log2.error(
17917
- { err, md5, metric: Metric.MOVE_ERROR, phase: "stage" },
17918
- "skill_quarantine: failed to create staging dir"
18100
+ { err, md5, metric: Metric.ZIP_ERROR },
18101
+ "skill_quarantine: phase-1 zip write failed"
17919
18102
  );
17920
- return { status: "move_error", phase: "stage", err };
18103
+ return { status: "zip_error", err };
17921
18104
  }
17922
18105
  try {
17923
- await move(skillPath, stagingTarget);
18106
+ await writeStub(params);
17924
18107
  } catch (err) {
17925
- await tryRm(stagingDir);
17926
18108
  log2.error(
17927
- { err, md5, metric: Metric.MOVE_ERROR, phase: "stage" },
17928
- "skill_quarantine: phase-1 move failed"
18109
+ { err, md5, skillPath, metric: Metric.STUB_ERROR },
18110
+ "skill_quarantine: stub write failed; tmp zip preserved for reconcile"
17929
18111
  );
17930
- return { status: "move_error", phase: "stage", err };
18112
+ return { status: "stub_error", err };
17931
18113
  }
17932
18114
  try {
17933
- await rename(stagingDir, hashDir);
18115
+ await rename(tmpZip, finalZip);
17934
18116
  } catch (err) {
17935
18117
  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"
18118
+ { err, md5, tmpZip, metric: Metric.PUBLISH_ERROR },
18119
+ "skill_quarantine: phase-3 publish failed; reconcile will retry"
17944
18120
  );
17945
- return { status: "move_error", phase: "publish", err };
18121
+ return { status: "publish_error", err };
17946
18122
  }
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
18123
  log2.info(
17974
18124
  {
17975
18125
  md5,
@@ -17983,87 +18133,110 @@ async function quarantineSkill(params) {
17983
18133
  );
17984
18134
  return { status: "quarantined" };
17985
18135
  }
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
- );
18136
+ async function writeStub(params) {
18137
+ const { skillPath, isFolder, md5, verdict } = params;
18138
+ const stubContent = renderStub({
18139
+ md5,
18140
+ isFolder,
18141
+ quarantinedZipPath: getQuarantineZipPath(md5),
18142
+ origPath: skillPath,
18143
+ summary: verdict.summary,
18144
+ scannerName: verdict.scannerName,
18145
+ scannerVersion: verdict.scannerVersion,
18146
+ scannedAt: verdict.scannedAt
18147
+ });
18148
+ if (isFolder) {
18149
+ await rm(skillPath, { recursive: true, force: true });
18150
+ await mkdir(skillPath, { recursive: true });
18151
+ await writeFile(path18.join(skillPath, "SKILL.md"), stubContent, "utf8");
18152
+ } else {
18153
+ await writeFile(skillPath, stubContent, "utf8");
18007
18154
  }
18008
18155
  }
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) {
18156
+ async function reconcileAndSweep(log2) {
18028
18157
  const root = getQuarantineRoot();
18029
18158
  let entries;
18030
18159
  try {
18031
- entries = await readdir(root);
18160
+ entries = await readdir2(root);
18032
18161
  } catch (err) {
18033
- if (err.code === "ENOENT") return 0;
18034
- log2.warn({ err, root }, "skill_quarantine: orphan sweep readdir failed");
18035
- return 0;
18162
+ if (err.code === "ENOENT") return;
18163
+ log2.warn({ err, root }, "skill_quarantine: reconcile readdir failed");
18164
+ return;
18036
18165
  }
18166
+ const committed = new Set(
18167
+ entries.map((e) => COMMITTED_ZIP_REGEX.exec(e)?.[1]).filter((m) => m !== void 0)
18168
+ );
18037
18169
  const now = Date.now();
18038
- let swept = 0;
18039
18170
  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 {
18171
+ const md5 = TMP_ZIP_REGEX.exec(entry)?.[1];
18172
+ if (md5 === void 0) continue;
18173
+ const full = path18.join(root, entry);
18174
+ if (committed.has(md5)) {
18175
+ await unlink(full).catch(
18176
+ (err) => log2.warn(
18177
+ { err, path: full, md5 },
18178
+ "skill_quarantine: redundant tmp unlink failed"
18179
+ )
18180
+ );
18181
+ log2.info(
18182
+ { path: full, md5, metric: Metric.SWEPT_REDUNDANT_TMP },
18183
+ "skill_quarantine: swept redundant tmp"
18184
+ );
18046
18185
  continue;
18047
18186
  }
18048
- if (now - mtimeMs < ORPHAN_SWEEP_GRACE_MS) continue;
18187
+ let valid;
18049
18188
  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");
18189
+ new AdmZip4(full).getEntries();
18190
+ valid = true;
18191
+ } catch {
18192
+ valid = false;
18058
18193
  }
18194
+ if (valid) {
18195
+ try {
18196
+ await rename(full, getQuarantineZipPath(md5));
18197
+ committed.add(md5);
18198
+ log2.info(
18199
+ { path: full, md5, metric: Metric.RECONCILED },
18200
+ "skill_quarantine: reconciled tmp \u2192 published"
18201
+ );
18202
+ } catch (err) {
18203
+ log2.warn(
18204
+ { err, path: full, md5 },
18205
+ "skill_quarantine: reconcile rename failed"
18206
+ );
18207
+ }
18208
+ continue;
18209
+ }
18210
+ const { mtimeMs } = await stat2(full).catch(() => ({ mtimeMs: now }));
18211
+ if (now - mtimeMs < PARTIAL_SWEEP_GRACE_MS) continue;
18212
+ await unlink(full).catch(
18213
+ (err) => log2.warn({ err, path: full }, "skill_quarantine: partial unlink failed")
18214
+ );
18215
+ log2.info(
18216
+ { path: full, metric: Metric.SWEPT_PARTIAL },
18217
+ "skill_quarantine: swept broken tmp (partial write)"
18218
+ );
18059
18219
  }
18060
- return swept;
18061
18220
  }
18062
- async function tryRm(p) {
18063
- try {
18064
- await rm(p, { recursive: true, force: true });
18065
- } catch {
18221
+ function exists(p) {
18222
+ return access(p).then(
18223
+ () => true,
18224
+ () => false
18225
+ );
18226
+ }
18227
+ function ignoreErr() {
18228
+ }
18229
+ async function addFolderAsync(zip, root, prefix) {
18230
+ async function walk(dir, relPrefix) {
18231
+ const entries = await readdir2(dir, { withFileTypes: true });
18232
+ for (const e of entries) {
18233
+ const full = path18.join(dir, e.name);
18234
+ const rel = path18.posix.join(relPrefix, e.name);
18235
+ if (e.isDirectory()) await walk(full, rel);
18236
+ else if (e.isFile()) zip.addFile(rel, await readFile2(full));
18237
+ }
18066
18238
  }
18239
+ await walk(root, prefix);
18067
18240
  }
18068
18241
 
18069
18242
  // src/features/analysis/skill_quarantine/queryVerdicts.ts
@@ -18121,7 +18294,7 @@ async function runQuarantineCheckIfNeeded(opts) {
18121
18294
  );
18122
18295
  const t0 = Date.now();
18123
18296
  try {
18124
- await sweepOrphanStagingDirs(log2);
18297
+ await reconcileAndSweep(log2);
18125
18298
  const installed = await enumerateInstalledSkills(cwd);
18126
18299
  log2.info(
18127
18300
  { sessionId, count: installed.length, metric: Metric.SKILLS_CHECKED },
@@ -18171,7 +18344,7 @@ async function runQuarantineCheckIfNeeded(opts) {
18171
18344
  // src/features/claude_code/daemon_pid_file.ts
18172
18345
  import fs13 from "fs";
18173
18346
  import os4 from "os";
18174
- import path17 from "path";
18347
+ import path19 from "path";
18175
18348
 
18176
18349
  // src/features/claude_code/data_collector_constants.ts
18177
18350
  var CC_VERSION_CACHE_KEY = "claudeCode.detectedCCVersion";
@@ -18188,20 +18361,21 @@ var DAEMON_POLL_INTERVAL_MS = (() => {
18188
18361
  var HEARTBEAT_STALE_MS = 3e4;
18189
18362
  var TRANSCRIPT_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
18190
18363
  var DAEMON_CHUNK_SIZE = 50;
18364
+ var CONTEXT_SCAN_INTERVAL_MS = 5e3;
18191
18365
 
18192
18366
  // src/features/claude_code/daemon_pid_file.ts
18193
18367
  function getMobbdevDir() {
18194
- return path17.join(os4.homedir(), ".mobbdev");
18368
+ return path19.join(os4.homedir(), ".mobbdev");
18195
18369
  }
18196
18370
  function getDaemonCheckScriptPath() {
18197
- return path17.join(getMobbdevDir(), "daemon-check.js");
18371
+ return path19.join(getMobbdevDir(), "daemon-check.js");
18198
18372
  }
18199
18373
  var DaemonPidFile = class {
18200
18374
  constructor() {
18201
18375
  __publicField(this, "data", null);
18202
18376
  }
18203
18377
  get filePath() {
18204
- return path17.join(getMobbdevDir(), "daemon.pid");
18378
+ return path19.join(getMobbdevDir(), "daemon.pid");
18205
18379
  }
18206
18380
  /** Ensure ~/.mobbdev/ directory exists. */
18207
18381
  ensureDir() {
@@ -18263,8 +18437,8 @@ var DaemonPidFile = class {
18263
18437
  // src/features/claude_code/data_collector.ts
18264
18438
  import { execFile } from "child_process";
18265
18439
  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";
18440
+ import { access as access2, open as open4, readdir as readdir3, readFile as readFile3, unlink as unlink2 } from "fs/promises";
18441
+ import path20 from "path";
18268
18442
  import { promisify } from "util";
18269
18443
 
18270
18444
  // src/features/analysis/context_file_uploader.ts
@@ -18529,8 +18703,8 @@ function createConfigstoreStream(store, opts) {
18529
18703
  heartbeatBuffer.length = 0;
18530
18704
  }
18531
18705
  }
18532
- function setScopePath(path34) {
18533
- scopePath = path34;
18706
+ function setScopePath(path36) {
18707
+ scopePath = path36;
18534
18708
  }
18535
18709
  return { writable, flush, setScopePath };
18536
18710
  }
@@ -18754,7 +18928,7 @@ function createLogger(config2) {
18754
18928
 
18755
18929
  // src/features/claude_code/hook_logger.ts
18756
18930
  var DD_RUM_TOKEN = true ? "pubf59c0182545bfb4c299175119f1abf9b" : "";
18757
- var CLI_VERSION = true ? "1.4.0" : "unknown";
18931
+ var CLI_VERSION = true ? "1.4.2" : "unknown";
18758
18932
  var NAMESPACE = "mobbdev-claude-code-hook-logs";
18759
18933
  var claudeCodeVersion;
18760
18934
  function buildDdTags() {
@@ -18841,18 +19015,18 @@ function getCursorKey(transcriptPath) {
18841
19015
  }
18842
19016
  async function resolveTranscriptPath(transcriptPath, sessionId) {
18843
19017
  try {
18844
- await access(transcriptPath);
19018
+ await access2(transcriptPath);
18845
19019
  return transcriptPath;
18846
19020
  } catch {
18847
19021
  }
18848
- const filename = path18.basename(transcriptPath);
18849
- const dirName = path18.basename(path18.dirname(transcriptPath));
18850
- const projectsDir = path18.dirname(path18.dirname(transcriptPath));
19022
+ const filename = path20.basename(transcriptPath);
19023
+ const dirName = path20.basename(path20.dirname(transcriptPath));
19024
+ const projectsDir = path20.dirname(path20.dirname(transcriptPath));
18851
19025
  const baseDirName = dirName.replace(/[-.]claude-worktrees-.+$/, "");
18852
19026
  if (baseDirName !== dirName) {
18853
- const candidate = path18.join(projectsDir, baseDirName, filename);
19027
+ const candidate = path20.join(projectsDir, baseDirName, filename);
18854
19028
  try {
18855
- await access(candidate);
19029
+ await access2(candidate);
18856
19030
  hookLog.info(
18857
19031
  {
18858
19032
  data: {
@@ -18869,12 +19043,12 @@ async function resolveTranscriptPath(transcriptPath, sessionId) {
18869
19043
  }
18870
19044
  }
18871
19045
  try {
18872
- const dirs = await readdir2(projectsDir);
19046
+ const dirs = await readdir3(projectsDir);
18873
19047
  for (const dir of dirs) {
18874
19048
  if (dir === dirName) continue;
18875
- const candidate = path18.join(projectsDir, dir, filename);
19049
+ const candidate = path20.join(projectsDir, dir, filename);
18876
19050
  try {
18877
- await access(candidate);
19051
+ await access2(candidate);
18878
19052
  hookLog.info(
18879
19053
  {
18880
19054
  data: {
@@ -19053,11 +19227,11 @@ async function cleanupStaleSessions(configDir) {
19053
19227
  const now = Date.now();
19054
19228
  const prefix = getSessionFilePrefix();
19055
19229
  try {
19056
- const files = await readdir2(configDir);
19230
+ const files = await readdir3(configDir);
19057
19231
  let deletedCount = 0;
19058
19232
  for (const file of files) {
19059
19233
  if (!file.startsWith(prefix) || !file.endsWith(".json")) continue;
19060
- const filePath = path18.join(configDir, file);
19234
+ const filePath = path20.join(configDir, file);
19061
19235
  try {
19062
19236
  const content = JSON.parse(await readFile3(filePath, "utf-8"));
19063
19237
  let newest = 0;
@@ -19069,7 +19243,7 @@ async function cleanupStaleSessions(configDir) {
19069
19243
  }
19070
19244
  }
19071
19245
  if (newest > 0 && now - newest > STALE_KEY_MAX_AGE_MS) {
19072
- await unlink(filePath);
19246
+ await unlink2(filePath);
19073
19247
  deletedCount++;
19074
19248
  }
19075
19249
  } catch {
@@ -19209,16 +19383,6 @@ async function processTranscript(input, sessionStore, log2, maxEntries = DAEMON_
19209
19383
  entriesSkipped: filteredOut,
19210
19384
  claudeCodeVersion: getClaudeCodeVersion()
19211
19385
  });
19212
- if (input.cwd) {
19213
- uploadContextFilesIfNeeded(
19214
- input.session_id,
19215
- input.cwd,
19216
- gqlClient,
19217
- log2
19218
- ).catch((err) => {
19219
- log2.error({ data: { err } }, "uploadContextFilesIfNeeded failed");
19220
- });
19221
- }
19222
19386
  return {
19223
19387
  entriesUploaded: entries.length,
19224
19388
  entriesSkipped: filteredOut,
@@ -19305,14 +19469,14 @@ async function uploadContextFilesIfNeeded(sessionId, cwd, gqlClient, log2) {
19305
19469
  import fs14 from "fs";
19306
19470
  import fsPromises4 from "fs/promises";
19307
19471
  import os6 from "os";
19308
- import path19 from "path";
19472
+ import path21 from "path";
19309
19473
  import chalk11 from "chalk";
19310
19474
 
19311
19475
  // src/features/claude_code/daemon-check-shim.tmpl.js
19312
19476
  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";
19313
19477
 
19314
19478
  // src/features/claude_code/install_hook.ts
19315
- var CLAUDE_SETTINGS_PATH = path19.join(os6.homedir(), ".claude", "settings.json");
19479
+ var CLAUDE_SETTINGS_PATH = path21.join(os6.homedir(), ".claude", "settings.json");
19316
19480
  var RECOMMENDED_MATCHER = "*";
19317
19481
  async function claudeSettingsExists() {
19318
19482
  try {
@@ -19458,18 +19622,18 @@ async function installMobbHooks(options = {}) {
19458
19622
  }
19459
19623
 
19460
19624
  // src/features/claude_code/transcript_scanner.ts
19461
- import { open as open5, readdir as readdir3, stat as stat3 } from "fs/promises";
19625
+ import { open as open5, readdir as readdir4, stat as stat3 } from "fs/promises";
19462
19626
  import os7 from "os";
19463
- import path20 from "path";
19627
+ import path22 from "path";
19464
19628
  var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
19465
19629
  function getClaudeProjectsDirs() {
19466
19630
  const dirs = [];
19467
19631
  const configDir = process.env["CLAUDE_CONFIG_DIR"];
19468
19632
  if (configDir) {
19469
- dirs.push(path20.join(configDir, "projects"));
19633
+ dirs.push(path22.join(configDir, "projects"));
19470
19634
  }
19471
- dirs.push(path20.join(os7.homedir(), ".config", "claude", "projects"));
19472
- dirs.push(path20.join(os7.homedir(), ".claude", "projects"));
19635
+ dirs.push(path22.join(os7.homedir(), ".config", "claude", "projects"));
19636
+ dirs.push(path22.join(os7.homedir(), ".claude", "projects"));
19473
19637
  return dirs;
19474
19638
  }
19475
19639
  async function collectJsonlFiles(files, dir, projectDir, seen, now, results) {
@@ -19477,7 +19641,7 @@ async function collectJsonlFiles(files, dir, projectDir, seen, now, results) {
19477
19641
  if (!file.endsWith(".jsonl")) continue;
19478
19642
  const sessionId = file.replace(".jsonl", "");
19479
19643
  if (!UUID_RE.test(sessionId)) continue;
19480
- const filePath = path20.join(dir, file);
19644
+ const filePath = path22.join(dir, file);
19481
19645
  if (seen.has(filePath)) continue;
19482
19646
  seen.add(filePath);
19483
19647
  let fileStat;
@@ -19503,12 +19667,12 @@ async function scanForTranscripts(projectsDirs = getClaudeProjectsDirs()) {
19503
19667
  for (const projectsDir of projectsDirs) {
19504
19668
  let projectDirs;
19505
19669
  try {
19506
- projectDirs = await readdir3(projectsDir);
19670
+ projectDirs = await readdir4(projectsDir);
19507
19671
  } catch {
19508
19672
  continue;
19509
19673
  }
19510
19674
  for (const projName of projectDirs) {
19511
- const projPath = path20.join(projectsDir, projName);
19675
+ const projPath = path22.join(projectsDir, projName);
19512
19676
  let projStat;
19513
19677
  try {
19514
19678
  projStat = await stat3(projPath);
@@ -19518,18 +19682,18 @@ async function scanForTranscripts(projectsDirs = getClaudeProjectsDirs()) {
19518
19682
  if (!projStat.isDirectory()) continue;
19519
19683
  let files;
19520
19684
  try {
19521
- files = await readdir3(projPath);
19685
+ files = await readdir4(projPath);
19522
19686
  } catch {
19523
19687
  continue;
19524
19688
  }
19525
19689
  await collectJsonlFiles(files, projPath, projPath, seen, now, results);
19526
19690
  for (const entry of files) {
19527
19691
  if (!UUID_RE.test(entry)) continue;
19528
- const subagentsDir = path20.join(projPath, entry, "subagents");
19692
+ const subagentsDir = path22.join(projPath, entry, "subagents");
19529
19693
  try {
19530
19694
  const s = await stat3(subagentsDir);
19531
19695
  if (!s.isDirectory()) continue;
19532
- const subFiles = await readdir3(subagentsDir);
19696
+ const subFiles = await readdir4(subagentsDir);
19533
19697
  await collectJsonlFiles(
19534
19698
  subFiles,
19535
19699
  subagentsDir,
@@ -19613,6 +19777,8 @@ async function startDaemon() {
19613
19777
  const startedAt = Date.now();
19614
19778
  const lastSeen = /* @__PURE__ */ new Map();
19615
19779
  let cleanupConfigDir;
19780
+ const sessionCwdCache = /* @__PURE__ */ new Map();
19781
+ let lastContextScanMs = 0;
19616
19782
  while (true) {
19617
19783
  if (shuttingDown) {
19618
19784
  await gracefulExit(0, "signal");
@@ -19626,9 +19792,29 @@ async function startDaemon() {
19626
19792
  for (const transcript of changed) {
19627
19793
  const sessionStore = createSessionConfigStore(transcript.sessionId);
19628
19794
  if (!cleanupConfigDir) {
19629
- cleanupConfigDir = path21.dirname(sessionStore.path);
19795
+ cleanupConfigDir = path23.dirname(sessionStore.path);
19796
+ }
19797
+ await drainTranscript(
19798
+ transcript,
19799
+ sessionStore,
19800
+ gqlClient,
19801
+ sessionCwdCache
19802
+ );
19803
+ }
19804
+ if (lastSeen.size > 0) {
19805
+ for (const filePath of sessionCwdCache.keys()) {
19806
+ if (!lastSeen.has(filePath)) sessionCwdCache.delete(filePath);
19807
+ }
19808
+ }
19809
+ const now = Date.now();
19810
+ if (now - lastContextScanMs >= CONTEXT_SCAN_INTERVAL_MS) {
19811
+ lastContextScanMs = now;
19812
+ for (const { sessionId, cwd } of sessionCwdCache.values()) {
19813
+ const log2 = createScopedHookLog(cwd, { daemonMode: true });
19814
+ uploadContextFilesIfNeeded(sessionId, cwd, gqlClient, log2).catch(
19815
+ (err) => log2.warn({ err }, "Context file scan failed")
19816
+ );
19630
19817
  }
19631
- await drainTranscript(transcript, sessionStore, gqlClient);
19632
19818
  }
19633
19819
  if (cleanupConfigDir) {
19634
19820
  await cleanupStaleSessions(cleanupConfigDir);
@@ -19669,11 +19855,17 @@ async function authenticateOrExit(exit) {
19669
19855
  return exit(1, "auth failed");
19670
19856
  }
19671
19857
  }
19672
- async function drainTranscript(transcript, sessionStore, gqlClient) {
19858
+ async function drainTranscript(transcript, sessionStore, gqlClient, sessionCwdCache) {
19673
19859
  const cwd = await extractCwdFromTranscript(transcript.filePath);
19674
19860
  const log2 = createScopedHookLog(cwd ?? transcript.projectDir, {
19675
19861
  daemonMode: true
19676
19862
  });
19863
+ if (cwd) {
19864
+ sessionCwdCache.set(transcript.filePath, {
19865
+ sessionId: transcript.sessionId,
19866
+ cwd
19867
+ });
19868
+ }
19677
19869
  try {
19678
19870
  let hasMore = true;
19679
19871
  while (hasMore) {
@@ -19897,8 +20089,8 @@ var WorkspaceService = class {
19897
20089
  * Sets a known workspace path that was discovered through successful validation
19898
20090
  * @param path The validated workspace path to store
19899
20091
  */
19900
- static setKnownWorkspacePath(path34) {
19901
- this.knownWorkspacePath = path34;
20092
+ static setKnownWorkspacePath(path36) {
20093
+ this.knownWorkspacePath = path36;
19902
20094
  }
19903
20095
  /**
19904
20096
  * Gets the known workspace path that was previously validated
@@ -20759,7 +20951,7 @@ async function createAuthenticatedMcpGQLClient({
20759
20951
  import { execSync as execSync2 } from "child_process";
20760
20952
  import fs15 from "fs";
20761
20953
  import os8 from "os";
20762
- import path22 from "path";
20954
+ import path24 from "path";
20763
20955
  var IDEs = ["cursor", "windsurf", "webstorm", "vscode", "claude"];
20764
20956
  var runCommand = (cmd) => {
20765
20957
  try {
@@ -20774,7 +20966,7 @@ var gitInfo = {
20774
20966
  };
20775
20967
  var getClaudeWorkspacePaths = () => {
20776
20968
  const home = os8.homedir();
20777
- const claudeIdePath = path22.join(home, ".claude", "ide");
20969
+ const claudeIdePath = path24.join(home, ".claude", "ide");
20778
20970
  const workspacePaths = [];
20779
20971
  if (!fs15.existsSync(claudeIdePath)) {
20780
20972
  return workspacePaths;
@@ -20782,7 +20974,7 @@ var getClaudeWorkspacePaths = () => {
20782
20974
  try {
20783
20975
  const lockFiles = fs15.readdirSync(claudeIdePath).filter((file) => file.endsWith(".lock"));
20784
20976
  for (const lockFile of lockFiles) {
20785
- const lockFilePath = path22.join(claudeIdePath, lockFile);
20977
+ const lockFilePath = path24.join(claudeIdePath, lockFile);
20786
20978
  try {
20787
20979
  const lockContent = JSON.parse(fs15.readFileSync(lockFilePath, "utf8"));
20788
20980
  if (lockContent.workspaceFolders && Array.isArray(lockContent.workspaceFolders)) {
@@ -20807,24 +20999,24 @@ var getMCPConfigPaths = (hostName) => {
20807
20999
  switch (hostName.toLowerCase()) {
20808
21000
  case "cursor":
20809
21001
  return [
20810
- path22.join(currentDir, ".cursor", "mcp.json"),
21002
+ path24.join(currentDir, ".cursor", "mcp.json"),
20811
21003
  // local first
20812
- path22.join(home, ".cursor", "mcp.json")
21004
+ path24.join(home, ".cursor", "mcp.json")
20813
21005
  ];
20814
21006
  case "windsurf":
20815
21007
  return [
20816
- path22.join(currentDir, ".codeium", "mcp_config.json"),
21008
+ path24.join(currentDir, ".codeium", "mcp_config.json"),
20817
21009
  // local first
20818
- path22.join(home, ".codeium", "windsurf", "mcp_config.json")
21010
+ path24.join(home, ".codeium", "windsurf", "mcp_config.json")
20819
21011
  ];
20820
21012
  case "webstorm":
20821
21013
  return [];
20822
21014
  case "visualstudiocode":
20823
21015
  case "vscode":
20824
21016
  return [
20825
- path22.join(currentDir, ".vscode", "mcp.json"),
21017
+ path24.join(currentDir, ".vscode", "mcp.json"),
20826
21018
  // local first
20827
- process.platform === "win32" ? path22.join(home, "AppData", "Roaming", "Code", "User", "mcp.json") : path22.join(
21019
+ process.platform === "win32" ? path24.join(home, "AppData", "Roaming", "Code", "User", "mcp.json") : path24.join(
20828
21020
  home,
20829
21021
  "Library",
20830
21022
  "Application Support",
@@ -20835,13 +21027,13 @@ var getMCPConfigPaths = (hostName) => {
20835
21027
  ];
20836
21028
  case "claude": {
20837
21029
  const claudePaths = [
20838
- path22.join(currentDir, ".claude.json"),
21030
+ path24.join(currentDir, ".claude.json"),
20839
21031
  // local first
20840
- path22.join(home, ".claude.json")
21032
+ path24.join(home, ".claude.json")
20841
21033
  ];
20842
21034
  const workspacePaths = getClaudeWorkspacePaths();
20843
21035
  for (const workspacePath of workspacePaths) {
20844
- claudePaths.push(path22.join(workspacePath, ".mcp.json"));
21036
+ claudePaths.push(path24.join(workspacePath, ".mcp.json"));
20845
21037
  }
20846
21038
  return claudePaths;
20847
21039
  }
@@ -21002,10 +21194,10 @@ var getHostInfo = (additionalMcpList) => {
21002
21194
  const ideConfigPaths = /* @__PURE__ */ new Set();
21003
21195
  for (const ide of IDEs) {
21004
21196
  const configPaths = getMCPConfigPaths(ide);
21005
- configPaths.forEach((path34) => ideConfigPaths.add(path34));
21197
+ configPaths.forEach((path36) => ideConfigPaths.add(path36));
21006
21198
  }
21007
21199
  const uniqueAdditionalPaths = additionalMcpList.filter(
21008
- (path34) => !ideConfigPaths.has(path34)
21200
+ (path36) => !ideConfigPaths.has(path36)
21009
21201
  );
21010
21202
  for (const ide of IDEs) {
21011
21203
  const cfg = readMCPConfig(ide);
@@ -21127,7 +21319,7 @@ init_configs();
21127
21319
  init_configs();
21128
21320
  import fs16 from "fs";
21129
21321
  import os9 from "os";
21130
- import path23 from "path";
21322
+ import path25 from "path";
21131
21323
  var MAX_DEPTH = 2;
21132
21324
  var patterns = ["mcp", "claude"];
21133
21325
  var isFileMatch = (fileName) => {
@@ -21147,7 +21339,7 @@ var searchDir = async (dir, depth = 0) => {
21147
21339
  if (depth > MAX_DEPTH) return results;
21148
21340
  const entries = await fs16.promises.readdir(dir, { withFileTypes: true }).catch(() => []);
21149
21341
  for (const entry of entries) {
21150
- const fullPath = path23.join(dir, entry.name);
21342
+ const fullPath = path25.join(dir, entry.name);
21151
21343
  if (entry.isFile() && isFileMatch(entry.name)) {
21152
21344
  results.push(fullPath);
21153
21345
  } else if (entry.isDirectory()) {
@@ -21164,14 +21356,14 @@ var findSystemMCPConfigs = async () => {
21164
21356
  const home = os9.homedir();
21165
21357
  const platform2 = os9.platform();
21166
21358
  const knownDirs = platform2 === "win32" ? [
21167
- path23.join(home, ".cursor"),
21168
- path23.join(home, "Documents"),
21169
- path23.join(home, "Downloads")
21359
+ path25.join(home, ".cursor"),
21360
+ path25.join(home, "Documents"),
21361
+ path25.join(home, "Downloads")
21170
21362
  ] : [
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")
21363
+ path25.join(home, ".cursor"),
21364
+ process.env["XDG_CONFIG_HOME"] || path25.join(home, ".config"),
21365
+ path25.join(home, "Documents"),
21366
+ path25.join(home, "Downloads")
21175
21367
  ];
21176
21368
  const timeoutPromise = new Promise(
21177
21369
  (resolve) => setTimeout(() => {
@@ -23587,13 +23779,13 @@ For a complete security audit workflow, use the \`full-security-audit\` prompt.
23587
23779
  // src/mcp/services/McpDetectionService/CursorMcpDetectionService.ts
23588
23780
  import * as fs19 from "fs";
23589
23781
  import * as os12 from "os";
23590
- import * as path25 from "path";
23782
+ import * as path27 from "path";
23591
23783
 
23592
23784
  // src/mcp/services/McpDetectionService/BaseMcpDetectionService.ts
23593
23785
  init_configs();
23594
23786
  import * as fs18 from "fs";
23595
23787
  import fetch7 from "node-fetch";
23596
- import * as path24 from "path";
23788
+ import * as path26 from "path";
23597
23789
 
23598
23790
  // src/mcp/services/McpDetectionService/McpDetectionServiceUtils.ts
23599
23791
  import * as fs17 from "fs";
@@ -23602,14 +23794,14 @@ import * as os11 from "os";
23602
23794
  // src/mcp/services/McpDetectionService/VscodeMcpDetectionService.ts
23603
23795
  import * as fs20 from "fs";
23604
23796
  import * as os13 from "os";
23605
- import * as path26 from "path";
23797
+ import * as path28 from "path";
23606
23798
 
23607
23799
  // src/mcp/tools/checkForNewAvailableFixes/CheckForNewAvailableFixesTool.ts
23608
23800
  import { z as z42 } from "zod";
23609
23801
 
23610
23802
  // src/mcp/services/PathValidation.ts
23611
23803
  import fs21 from "fs";
23612
- import path27 from "path";
23804
+ import path29 from "path";
23613
23805
  async function validatePath(inputPath) {
23614
23806
  logDebug("Validating MCP path", { inputPath });
23615
23807
  if (/^\/[a-zA-Z]:\//.test(inputPath)) {
@@ -23641,7 +23833,7 @@ async function validatePath(inputPath) {
23641
23833
  logError(error);
23642
23834
  return { isValid: false, error, path: inputPath };
23643
23835
  }
23644
- const normalizedPath = path27.normalize(inputPath);
23836
+ const normalizedPath = path29.normalize(inputPath);
23645
23837
  if (normalizedPath.includes("..")) {
23646
23838
  const error = `Normalized path contains path traversal patterns: ${inputPath}`;
23647
23839
  logError(error);
@@ -24293,7 +24485,7 @@ init_configs();
24293
24485
  import fs22 from "fs/promises";
24294
24486
  import nodePath from "path";
24295
24487
  var getLocalFiles = async ({
24296
- path: path34,
24488
+ path: path36,
24297
24489
  maxFileSize = MCP_MAX_FILE_SIZE,
24298
24490
  maxFiles,
24299
24491
  isAllFilesScan,
@@ -24301,17 +24493,17 @@ var getLocalFiles = async ({
24301
24493
  scanRecentlyChangedFiles
24302
24494
  }) => {
24303
24495
  logDebug(`[${scanContext}] Starting getLocalFiles`, {
24304
- path: path34,
24496
+ path: path36,
24305
24497
  maxFileSize,
24306
24498
  maxFiles,
24307
24499
  isAllFilesScan,
24308
24500
  scanRecentlyChangedFiles
24309
24501
  });
24310
24502
  try {
24311
- const resolvedRepoPath = await fs22.realpath(path34);
24503
+ const resolvedRepoPath = await fs22.realpath(path36);
24312
24504
  logDebug(`[${scanContext}] Resolved repository path`, {
24313
24505
  resolvedRepoPath,
24314
- originalPath: path34
24506
+ originalPath: path36
24315
24507
  });
24316
24508
  const gitService = new GitService(resolvedRepoPath, log);
24317
24509
  const gitValidation = await gitService.validateRepository();
@@ -24324,7 +24516,7 @@ var getLocalFiles = async ({
24324
24516
  if (!gitValidation.isValid || isAllFilesScan) {
24325
24517
  try {
24326
24518
  files = await FileUtils.getLastChangedFiles({
24327
- dir: path34,
24519
+ dir: path36,
24328
24520
  maxFileSize,
24329
24521
  maxFiles,
24330
24522
  isAllFilesScan
@@ -24416,7 +24608,7 @@ var getLocalFiles = async ({
24416
24608
  logError(`${scanContext}Unexpected error in getLocalFiles`, {
24417
24609
  error: error instanceof Error ? error.message : String(error),
24418
24610
  stack: error instanceof Error ? error.stack : void 0,
24419
- path: path34
24611
+ path: path36
24420
24612
  });
24421
24613
  throw error;
24422
24614
  }
@@ -24426,7 +24618,7 @@ var getLocalFiles = async ({
24426
24618
  init_client_generates();
24427
24619
  init_GitService();
24428
24620
  import fs23 from "fs";
24429
- import path28 from "path";
24621
+ import path30 from "path";
24430
24622
  import { z as z41 } from "zod";
24431
24623
  function extractPathFromPatch(patch) {
24432
24624
  const match = patch?.match(/diff --git a\/([^\s]+) b\//);
@@ -24512,7 +24704,7 @@ var LocalMobbFolderService = class {
24512
24704
  "[LocalMobbFolderService] Non-git repository detected, skipping .gitignore operations"
24513
24705
  );
24514
24706
  }
24515
- const mobbFolderPath = path28.join(
24707
+ const mobbFolderPath = path30.join(
24516
24708
  this.repoPath,
24517
24709
  this.defaultMobbFolderName
24518
24710
  );
@@ -24684,7 +24876,7 @@ var LocalMobbFolderService = class {
24684
24876
  mobbFolderPath,
24685
24877
  baseFileName
24686
24878
  );
24687
- const filePath = path28.join(mobbFolderPath, uniqueFileName);
24879
+ const filePath = path30.join(mobbFolderPath, uniqueFileName);
24688
24880
  await fs23.promises.writeFile(filePath, patch, "utf8");
24689
24881
  logInfo("[LocalMobbFolderService] Patch saved successfully", {
24690
24882
  filePath,
@@ -24742,11 +24934,11 @@ var LocalMobbFolderService = class {
24742
24934
  * @returns Unique filename that doesn't conflict with existing files
24743
24935
  */
24744
24936
  getUniqueFileName(folderPath, baseFileName) {
24745
- const baseName = path28.parse(baseFileName).name;
24746
- const extension = path28.parse(baseFileName).ext;
24937
+ const baseName = path30.parse(baseFileName).name;
24938
+ const extension = path30.parse(baseFileName).ext;
24747
24939
  let uniqueFileName = baseFileName;
24748
24940
  let index = 1;
24749
- while (fs23.existsSync(path28.join(folderPath, uniqueFileName))) {
24941
+ while (fs23.existsSync(path30.join(folderPath, uniqueFileName))) {
24750
24942
  uniqueFileName = `${baseName}-${index}${extension}`;
24751
24943
  index++;
24752
24944
  if (index > 1e3) {
@@ -24777,7 +24969,7 @@ var LocalMobbFolderService = class {
24777
24969
  logDebug("[LocalMobbFolderService] Logging patch info", { fixId: fix.id });
24778
24970
  try {
24779
24971
  const mobbFolderPath = await this.getFolder();
24780
- const patchInfoPath = path28.join(mobbFolderPath, "patchInfo.md");
24972
+ const patchInfoPath = path30.join(mobbFolderPath, "patchInfo.md");
24781
24973
  const markdownContent = this.generateFixMarkdown(fix, savedPatchFileName);
24782
24974
  let existingContent = "";
24783
24975
  if (fs23.existsSync(patchInfoPath)) {
@@ -24819,7 +25011,7 @@ var LocalMobbFolderService = class {
24819
25011
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
24820
25012
  const patch = this.extractPatchFromFix(fix);
24821
25013
  const relativePatchedFilePath = patch ? extractPathFromPatch(patch) : null;
24822
- const patchedFilePath = relativePatchedFilePath ? path28.resolve(this.repoPath, relativePatchedFilePath) : null;
25014
+ const patchedFilePath = relativePatchedFilePath ? path30.resolve(this.repoPath, relativePatchedFilePath) : null;
24823
25015
  const fixIdentifier = savedPatchFileName ? savedPatchFileName.replace(".patch", "") : fix.id;
24824
25016
  let markdown = `# Fix ${fixIdentifier}
24825
25017
 
@@ -25155,7 +25347,7 @@ var LocalMobbFolderService = class {
25155
25347
  // src/mcp/services/PatchApplicationService.ts
25156
25348
  init_configs();
25157
25349
  import {
25158
- existsSync as existsSync7,
25350
+ existsSync as existsSync6,
25159
25351
  mkdirSync,
25160
25352
  readFileSync as readFileSync4,
25161
25353
  unlinkSync,
@@ -25163,14 +25355,14 @@ import {
25163
25355
  } from "fs";
25164
25356
  import fs24 from "fs/promises";
25165
25357
  import parseDiff2 from "parse-diff";
25166
- import path29 from "path";
25358
+ import path31 from "path";
25167
25359
  var PatchApplicationService = class {
25168
25360
  /**
25169
25361
  * Gets the appropriate comment syntax for a file based on its extension
25170
25362
  */
25171
25363
  static getCommentSyntax(filePath) {
25172
- const ext = path29.extname(filePath).toLowerCase();
25173
- const basename2 = path29.basename(filePath);
25364
+ const ext = path31.extname(filePath).toLowerCase();
25365
+ const basename2 = path31.basename(filePath);
25174
25366
  const commentMap = {
25175
25367
  // C-style languages (single line comments)
25176
25368
  ".js": "//",
@@ -25378,7 +25570,7 @@ var PatchApplicationService = class {
25378
25570
  }
25379
25571
  );
25380
25572
  }
25381
- const dirPath = path29.dirname(normalizedFilePath);
25573
+ const dirPath = path31.dirname(normalizedFilePath);
25382
25574
  mkdirSync(dirPath, { recursive: true });
25383
25575
  writeFileSync3(normalizedFilePath, finalContent, "utf8");
25384
25576
  return normalizedFilePath;
@@ -25387,9 +25579,9 @@ var PatchApplicationService = class {
25387
25579
  repositoryPath,
25388
25580
  targetPath
25389
25581
  }) {
25390
- const repoRoot = path29.resolve(repositoryPath);
25391
- const normalizedPath = path29.resolve(repoRoot, targetPath);
25392
- const repoRootWithSep = repoRoot.endsWith(path29.sep) ? repoRoot : `${repoRoot}${path29.sep}`;
25582
+ const repoRoot = path31.resolve(repositoryPath);
25583
+ const normalizedPath = path31.resolve(repoRoot, targetPath);
25584
+ const repoRootWithSep = repoRoot.endsWith(path31.sep) ? repoRoot : `${repoRoot}${path31.sep}`;
25393
25585
  if (normalizedPath !== repoRoot && !normalizedPath.startsWith(repoRootWithSep)) {
25394
25586
  throw new Error(
25395
25587
  `Security violation: target path ${targetPath} resolves outside repository`
@@ -25398,7 +25590,7 @@ var PatchApplicationService = class {
25398
25590
  return {
25399
25591
  repoRoot,
25400
25592
  normalizedPath,
25401
- relativePath: path29.relative(repoRoot, normalizedPath)
25593
+ relativePath: path31.relative(repoRoot, normalizedPath)
25402
25594
  };
25403
25595
  }
25404
25596
  /**
@@ -25680,8 +25872,8 @@ var PatchApplicationService = class {
25680
25872
  continue;
25681
25873
  }
25682
25874
  try {
25683
- const absolutePath = path29.resolve(repositoryPath, targetFile);
25684
- if (existsSync7(absolutePath)) {
25875
+ const absolutePath = path31.resolve(repositoryPath, targetFile);
25876
+ if (existsSync6(absolutePath)) {
25685
25877
  const stats = await fs24.stat(absolutePath);
25686
25878
  const fileModTime = stats.mtime.getTime();
25687
25879
  if (fileModTime > scanStartTime) {
@@ -25882,7 +26074,7 @@ var PatchApplicationService = class {
25882
26074
  targetFile,
25883
26075
  absoluteFilePath,
25884
26076
  relativePath,
25885
- exists: existsSync7(absoluteFilePath)
26077
+ exists: existsSync6(absoluteFilePath)
25886
26078
  });
25887
26079
  return { absoluteFilePath, relativePath };
25888
26080
  }
@@ -25906,7 +26098,7 @@ var PatchApplicationService = class {
25906
26098
  fix,
25907
26099
  scanContext
25908
26100
  });
25909
- appliedFiles.push(path29.relative(repositoryPath, actualPath));
26101
+ appliedFiles.push(path31.relative(repositoryPath, actualPath));
25910
26102
  logDebug(`[${scanContext}] Created new file: ${relativePath}`);
25911
26103
  }
25912
26104
  /**
@@ -25918,7 +26110,7 @@ var PatchApplicationService = class {
25918
26110
  appliedFiles,
25919
26111
  scanContext
25920
26112
  }) {
25921
- if (existsSync7(absoluteFilePath)) {
26113
+ if (existsSync6(absoluteFilePath)) {
25922
26114
  unlinkSync(absoluteFilePath);
25923
26115
  appliedFiles.push(relativePath);
25924
26116
  logDebug(`[${scanContext}] Deleted file: ${relativePath}`);
@@ -25937,7 +26129,7 @@ var PatchApplicationService = class {
25937
26129
  appliedFiles,
25938
26130
  scanContext
25939
26131
  }) {
25940
- if (!existsSync7(absoluteFilePath)) {
26132
+ if (!existsSync6(absoluteFilePath)) {
25941
26133
  throw new Error(
25942
26134
  `Target file does not exist: ${targetFile} (resolved to: ${absoluteFilePath})`
25943
26135
  );
@@ -25955,7 +26147,7 @@ var PatchApplicationService = class {
25955
26147
  fix,
25956
26148
  scanContext
25957
26149
  });
25958
- appliedFiles.push(path29.relative(repositoryPath, actualPath));
26150
+ appliedFiles.push(path31.relative(repositoryPath, actualPath));
25959
26151
  logDebug(`[${scanContext}] Modified file: ${relativePath}`);
25960
26152
  }
25961
26153
  }
@@ -26152,8 +26344,8 @@ init_configs();
26152
26344
  // src/mcp/services/FileOperations.ts
26153
26345
  init_FileUtils();
26154
26346
  import fs25 from "fs";
26155
- import path30 from "path";
26156
- import AdmZip4 from "adm-zip";
26347
+ import path32 from "path";
26348
+ import AdmZip5 from "adm-zip";
26157
26349
  var FileOperations = class {
26158
26350
  /**
26159
26351
  * Creates a ZIP archive containing the specified source files
@@ -26168,14 +26360,14 @@ var FileOperations = class {
26168
26360
  maxFileSize
26169
26361
  }) {
26170
26362
  logDebug("[FileOperations] Packing files");
26171
- const zip = new AdmZip4();
26363
+ const zip = new AdmZip5();
26172
26364
  let packedFilesCount = 0;
26173
26365
  const packedFiles = [];
26174
26366
  const excludedFiles = [];
26175
- const resolvedRepoPath = path30.resolve(repositoryPath);
26367
+ const resolvedRepoPath = path32.resolve(repositoryPath);
26176
26368
  for (const filepath of fileList) {
26177
- const absoluteFilepath = path30.join(repositoryPath, filepath);
26178
- const resolvedFilePath = path30.resolve(absoluteFilepath);
26369
+ const absoluteFilepath = path32.join(repositoryPath, filepath);
26370
+ const resolvedFilePath = path32.resolve(absoluteFilepath);
26179
26371
  if (!resolvedFilePath.startsWith(resolvedRepoPath)) {
26180
26372
  const reason = "potential path traversal security risk";
26181
26373
  logDebug(`[FileOperations] Skipping ${filepath} due to ${reason}`);
@@ -26222,11 +26414,11 @@ var FileOperations = class {
26222
26414
  fileList,
26223
26415
  repositoryPath
26224
26416
  }) {
26225
- const resolvedRepoPath = path30.resolve(repositoryPath);
26417
+ const resolvedRepoPath = path32.resolve(repositoryPath);
26226
26418
  const validatedPaths = [];
26227
26419
  for (const filepath of fileList) {
26228
- const absoluteFilepath = path30.join(repositoryPath, filepath);
26229
- const resolvedFilePath = path30.resolve(absoluteFilepath);
26420
+ const absoluteFilepath = path32.join(repositoryPath, filepath);
26421
+ const resolvedFilePath = path32.resolve(absoluteFilepath);
26230
26422
  if (!resolvedFilePath.startsWith(resolvedRepoPath)) {
26231
26423
  logDebug(
26232
26424
  `[FileOperations] Rejecting ${filepath} - path traversal attempt detected`
@@ -26254,7 +26446,7 @@ var FileOperations = class {
26254
26446
  for (const absolutePath of filePaths) {
26255
26447
  try {
26256
26448
  const content = await fs25.promises.readFile(absolutePath);
26257
- const relativePath = path30.basename(absolutePath);
26449
+ const relativePath = path32.basename(absolutePath);
26258
26450
  fileDataArray.push({
26259
26451
  relativePath,
26260
26452
  absolutePath,
@@ -26566,14 +26758,14 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
26566
26758
  * since the last scan.
26567
26759
  */
26568
26760
  async scanForSecurityVulnerabilities({
26569
- path: path34,
26761
+ path: path36,
26570
26762
  isAllDetectionRulesScan,
26571
26763
  isAllFilesScan,
26572
26764
  scanContext
26573
26765
  }) {
26574
26766
  this.hasAuthenticationFailed = false;
26575
26767
  logDebug(`[${scanContext}] Scanning for new security vulnerabilities`, {
26576
- path: path34
26768
+ path: path36
26577
26769
  });
26578
26770
  if (!this.gqlClient) {
26579
26771
  logInfo(`[${scanContext}] No GQL client found, skipping scan`);
@@ -26589,11 +26781,11 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
26589
26781
  }
26590
26782
  logDebug(
26591
26783
  `[${scanContext}] Connected to the API, assembling list of files to scan`,
26592
- { path: path34 }
26784
+ { path: path36 }
26593
26785
  );
26594
26786
  const isBackgroundScan = scanContext === ScanContext.BACKGROUND_INITIAL || scanContext === ScanContext.BACKGROUND_PERIODIC;
26595
26787
  const files = await getLocalFiles({
26596
- path: path34,
26788
+ path: path36,
26597
26789
  isAllFilesScan,
26598
26790
  scanContext,
26599
26791
  scanRecentlyChangedFiles: !isBackgroundScan
@@ -26619,13 +26811,13 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
26619
26811
  });
26620
26812
  const { fixReportId, projectId } = await scanFiles({
26621
26813
  fileList: filesToScan.map((file) => file.relativePath),
26622
- repositoryPath: path34,
26814
+ repositoryPath: path36,
26623
26815
  gqlClient: this.gqlClient,
26624
26816
  isAllDetectionRulesScan,
26625
26817
  scanContext
26626
26818
  });
26627
26819
  logInfo(
26628
- `[${scanContext}] Security scan completed for ${path34} reportId: ${fixReportId} projectId: ${projectId}`
26820
+ `[${scanContext}] Security scan completed for ${path36} reportId: ${fixReportId} projectId: ${projectId}`
26629
26821
  );
26630
26822
  if (isAllFilesScan) {
26631
26823
  return;
@@ -26919,13 +27111,13 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
26919
27111
  });
26920
27112
  return scannedFiles.some((file) => file.relativePath === fixFile);
26921
27113
  }
26922
- async getFreshFixes({ path: path34 }) {
27114
+ async getFreshFixes({ path: path36 }) {
26923
27115
  const scanContext = ScanContext.USER_REQUEST;
26924
- logDebug(`[${scanContext}] Getting fresh fixes`, { path: path34 });
26925
- if (this.path !== path34) {
26926
- this.path = path34;
27116
+ logDebug(`[${scanContext}] Getting fresh fixes`, { path: path36 });
27117
+ if (this.path !== path36) {
27118
+ this.path = path36;
26927
27119
  this.reset();
26928
- logInfo(`[${scanContext}] Reset service state for new path`, { path: path34 });
27120
+ logInfo(`[${scanContext}] Reset service state for new path`, { path: path36 });
26929
27121
  }
26930
27122
  try {
26931
27123
  const loginContext = createMcpLoginContext("check_new_fixes");
@@ -26944,7 +27136,7 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
26944
27136
  }
26945
27137
  throw error;
26946
27138
  }
26947
- this.triggerScan({ path: path34, gqlClient: this.gqlClient });
27139
+ this.triggerScan({ path: path36, gqlClient: this.gqlClient });
26948
27140
  let isMvsAutoFixEnabled = null;
26949
27141
  try {
26950
27142
  isMvsAutoFixEnabled = await this.gqlClient.getMvsAutoFixSettings();
@@ -26978,33 +27170,33 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
26978
27170
  return noFreshFixesPrompt;
26979
27171
  }
26980
27172
  triggerScan({
26981
- path: path34,
27173
+ path: path36,
26982
27174
  gqlClient
26983
27175
  }) {
26984
- if (this.path !== path34) {
26985
- this.path = path34;
27176
+ if (this.path !== path36) {
27177
+ this.path = path36;
26986
27178
  this.reset();
26987
- logInfo(`Reset service state for new path in triggerScan`, { path: path34 });
27179
+ logInfo(`Reset service state for new path in triggerScan`, { path: path36 });
26988
27180
  }
26989
27181
  this.gqlClient = gqlClient;
26990
27182
  if (!this.intervalId) {
26991
- this.startPeriodicScanning(path34);
26992
- this.executeInitialScan(path34);
26993
- void this.executeInitialFullScan(path34);
27183
+ this.startPeriodicScanning(path36);
27184
+ this.executeInitialScan(path36);
27185
+ void this.executeInitialFullScan(path36);
26994
27186
  }
26995
27187
  }
26996
- startPeriodicScanning(path34) {
27188
+ startPeriodicScanning(path36) {
26997
27189
  const scanContext = ScanContext.BACKGROUND_PERIODIC;
26998
27190
  logDebug(
26999
27191
  `[${scanContext}] Starting periodic scan for new security vulnerabilities`,
27000
27192
  {
27001
- path: path34
27193
+ path: path36
27002
27194
  }
27003
27195
  );
27004
27196
  this.intervalId = setInterval(() => {
27005
- logDebug(`[${scanContext}] Triggering periodic security scan`, { path: path34 });
27197
+ logDebug(`[${scanContext}] Triggering periodic security scan`, { path: path36 });
27006
27198
  this.scanForSecurityVulnerabilities({
27007
- path: path34,
27199
+ path: path36,
27008
27200
  scanContext
27009
27201
  }).catch((error) => {
27010
27202
  logError(`[${scanContext}] Error during periodic security scan`, {
@@ -27013,45 +27205,45 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
27013
27205
  });
27014
27206
  }, MCP_PERIODIC_CHECK_INTERVAL);
27015
27207
  }
27016
- async executeInitialFullScan(path34) {
27208
+ async executeInitialFullScan(path36) {
27017
27209
  const scanContext = ScanContext.FULL_SCAN;
27018
- logDebug(`[${scanContext}] Triggering initial full security scan`, { path: path34 });
27210
+ logDebug(`[${scanContext}] Triggering initial full security scan`, { path: path36 });
27019
27211
  logDebug(`[${scanContext}] Full scan paths scanned`, {
27020
27212
  fullScanPathsScanned: this.fullScanPathsScanned
27021
27213
  });
27022
- if (this.fullScanPathsScanned.includes(path34)) {
27214
+ if (this.fullScanPathsScanned.includes(path36)) {
27023
27215
  logDebug(`[${scanContext}] Full scan already executed for this path`, {
27024
- path: path34
27216
+ path: path36
27025
27217
  });
27026
27218
  return;
27027
27219
  }
27028
27220
  configStore.set("fullScanPathsScanned", [
27029
27221
  ...this.fullScanPathsScanned,
27030
- path34
27222
+ path36
27031
27223
  ]);
27032
27224
  try {
27033
27225
  await this.scanForSecurityVulnerabilities({
27034
- path: path34,
27226
+ path: path36,
27035
27227
  isAllFilesScan: true,
27036
27228
  isAllDetectionRulesScan: true,
27037
27229
  scanContext: ScanContext.FULL_SCAN
27038
27230
  });
27039
- if (!this.fullScanPathsScanned.includes(path34)) {
27040
- this.fullScanPathsScanned.push(path34);
27231
+ if (!this.fullScanPathsScanned.includes(path36)) {
27232
+ this.fullScanPathsScanned.push(path36);
27041
27233
  configStore.set("fullScanPathsScanned", this.fullScanPathsScanned);
27042
27234
  }
27043
- logInfo(`[${scanContext}] Full scan completed`, { path: path34 });
27235
+ logInfo(`[${scanContext}] Full scan completed`, { path: path36 });
27044
27236
  } catch (error) {
27045
27237
  logError(`[${scanContext}] Error during initial full security scan`, {
27046
27238
  error
27047
27239
  });
27048
27240
  }
27049
27241
  }
27050
- executeInitialScan(path34) {
27242
+ executeInitialScan(path36) {
27051
27243
  const scanContext = ScanContext.BACKGROUND_INITIAL;
27052
- logDebug(`[${scanContext}] Triggering initial security scan`, { path: path34 });
27244
+ logDebug(`[${scanContext}] Triggering initial security scan`, { path: path36 });
27053
27245
  this.scanForSecurityVulnerabilities({
27054
- path: path34,
27246
+ path: path36,
27055
27247
  scanContext: ScanContext.BACKGROUND_INITIAL
27056
27248
  }).catch((error) => {
27057
27249
  logError(`[${scanContext}] Error during initial security scan`, { error });
@@ -27148,9 +27340,9 @@ Example payload:
27148
27340
  `Invalid path: potential security risk detected in path: ${pathValidationResult.error}`
27149
27341
  );
27150
27342
  }
27151
- const path34 = pathValidationResult.path;
27343
+ const path36 = pathValidationResult.path;
27152
27344
  const resultText = await this.newFixesService.getFreshFixes({
27153
- path: path34
27345
+ path: path36
27154
27346
  });
27155
27347
  logInfo("CheckForNewAvailableFixesTool execution completed", {
27156
27348
  resultText
@@ -27328,8 +27520,8 @@ Call this tool instead of ${MCP_TOOL_SCAN_AND_FIX_VULNERABILITIES} when you only
27328
27520
  `Invalid path: potential security risk detected in path: ${pathValidationResult.error}`
27329
27521
  );
27330
27522
  }
27331
- const path34 = pathValidationResult.path;
27332
- const gitService = new GitService(path34, log);
27523
+ const path36 = pathValidationResult.path;
27524
+ const gitService = new GitService(path36, log);
27333
27525
  const gitValidation = await gitService.validateRepository();
27334
27526
  if (!gitValidation.isValid) {
27335
27527
  throw new Error(`Invalid git repository: ${gitValidation.error}`);
@@ -27714,9 +27906,9 @@ Example payload:
27714
27906
  `Invalid path: potential security risk detected in path: ${pathValidationResult.error}`
27715
27907
  );
27716
27908
  }
27717
- const path34 = pathValidationResult.path;
27909
+ const path36 = pathValidationResult.path;
27718
27910
  const files = await getLocalFiles({
27719
- path: path34,
27911
+ path: path36,
27720
27912
  maxFileSize: MCP_MAX_FILE_SIZE,
27721
27913
  maxFiles: args.maxFiles,
27722
27914
  scanContext: ScanContext.USER_REQUEST,
@@ -27736,7 +27928,7 @@ Example payload:
27736
27928
  try {
27737
27929
  const fixResult = await this.vulnerabilityFixService.processVulnerabilities({
27738
27930
  fileList: files.map((file) => file.relativePath),
27739
- repositoryPath: path34,
27931
+ repositoryPath: path36,
27740
27932
  offset: args.offset,
27741
27933
  limit: args.limit,
27742
27934
  isRescan: args.rescan || !!args.maxFiles
@@ -28037,10 +28229,10 @@ init_client_generates();
28037
28229
  init_urlParser2();
28038
28230
 
28039
28231
  // src/features/codeium_intellij/codeium_language_server_grpc_client.ts
28040
- import path31 from "path";
28232
+ import path33 from "path";
28041
28233
  import * as grpc from "@grpc/grpc-js";
28042
28234
  import * as protoLoader from "@grpc/proto-loader";
28043
- var PROTO_PATH = path31.join(
28235
+ var PROTO_PATH = path33.join(
28044
28236
  getModuleRootDir(),
28045
28237
  "src/features/codeium_intellij/proto/exa/language_server_pb/language_server.proto"
28046
28238
  );
@@ -28052,7 +28244,7 @@ function loadProto() {
28052
28244
  defaults: true,
28053
28245
  oneofs: true,
28054
28246
  includeDirs: [
28055
- path31.join(getModuleRootDir(), "src/features/codeium_intellij/proto")
28247
+ path33.join(getModuleRootDir(), "src/features/codeium_intellij/proto")
28056
28248
  ]
28057
28249
  });
28058
28250
  return grpc.loadPackageDefinition(
@@ -28108,28 +28300,28 @@ async function getGrpcClient(port, csrf3) {
28108
28300
  // src/features/codeium_intellij/parse_intellij_logs.ts
28109
28301
  import fs27 from "fs";
28110
28302
  import os14 from "os";
28111
- import path32 from "path";
28303
+ import path34 from "path";
28112
28304
  function getLogsDir() {
28113
28305
  if (process.platform === "darwin") {
28114
- return path32.join(os14.homedir(), "Library/Logs/JetBrains");
28306
+ return path34.join(os14.homedir(), "Library/Logs/JetBrains");
28115
28307
  } else if (process.platform === "win32") {
28116
- return path32.join(
28117
- process.env["LOCALAPPDATA"] || path32.join(os14.homedir(), "AppData/Local"),
28308
+ return path34.join(
28309
+ process.env["LOCALAPPDATA"] || path34.join(os14.homedir(), "AppData/Local"),
28118
28310
  "JetBrains"
28119
28311
  );
28120
28312
  } else {
28121
- return path32.join(os14.homedir(), ".cache/JetBrains");
28313
+ return path34.join(os14.homedir(), ".cache/JetBrains");
28122
28314
  }
28123
28315
  }
28124
28316
  function parseIdeLogDir(ideLogDir) {
28125
28317
  const logFiles = fs27.readdirSync(ideLogDir).filter((f) => /^idea(\.\d+)?\.log$/.test(f)).map((f) => ({
28126
28318
  name: f,
28127
- mtime: fs27.statSync(path32.join(ideLogDir, f)).mtimeMs
28319
+ mtime: fs27.statSync(path34.join(ideLogDir, f)).mtimeMs
28128
28320
  })).sort((a, b) => a.mtime - b.mtime).map((f) => f.name);
28129
28321
  let latestCsrf = null;
28130
28322
  let latestPort = null;
28131
28323
  for (const logFile of logFiles) {
28132
- const lines = fs27.readFileSync(path32.join(ideLogDir, logFile), "utf-8").split("\n");
28324
+ const lines = fs27.readFileSync(path34.join(ideLogDir, logFile), "utf-8").split("\n");
28133
28325
  for (const line of lines) {
28134
28326
  if (!line.includes(
28135
28327
  "com.codeium.intellij.language_server.LanguageServerProcessHandler"
@@ -28157,9 +28349,9 @@ function findRunningCodeiumLanguageServers() {
28157
28349
  const logsDir = getLogsDir();
28158
28350
  if (!fs27.existsSync(logsDir)) return results;
28159
28351
  for (const ide of fs27.readdirSync(logsDir)) {
28160
- let ideLogDir = path32.join(logsDir, ide);
28352
+ let ideLogDir = path34.join(logsDir, ide);
28161
28353
  if (process.platform !== "darwin") {
28162
- ideLogDir = path32.join(ideLogDir, "log");
28354
+ ideLogDir = path34.join(ideLogDir, "log");
28163
28355
  }
28164
28356
  if (!fs27.existsSync(ideLogDir) || !fs27.statSync(ideLogDir).isDirectory()) {
28165
28357
  continue;
@@ -28342,10 +28534,10 @@ function processChatStepCodeAction(step) {
28342
28534
  // src/features/codeium_intellij/install_hook.ts
28343
28535
  import fsPromises5 from "fs/promises";
28344
28536
  import os15 from "os";
28345
- import path33 from "path";
28537
+ import path35 from "path";
28346
28538
  import chalk14 from "chalk";
28347
28539
  function getCodeiumHooksPath() {
28348
- return path33.join(os15.homedir(), ".codeium", "hooks.json");
28540
+ return path35.join(os15.homedir(), ".codeium", "hooks.json");
28349
28541
  }
28350
28542
  async function readCodeiumHooks() {
28351
28543
  const hooksPath = getCodeiumHooksPath();
@@ -28358,7 +28550,7 @@ async function readCodeiumHooks() {
28358
28550
  }
28359
28551
  async function writeCodeiumHooks(config2) {
28360
28552
  const hooksPath = getCodeiumHooksPath();
28361
- const dir = path33.dirname(hooksPath);
28553
+ const dir = path35.dirname(hooksPath);
28362
28554
  await fsPromises5.mkdir(dir, { recursive: true });
28363
28555
  await fsPromises5.writeFile(
28364
28556
  hooksPath,