ctxloom-pro 1.7.9 → 1.7.10

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.
@@ -2706,7 +2706,7 @@ var CallGraphIndex = class _CallGraphIndex {
2706
2706
  var TS_EXTENSIONS2 = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".vue"]);
2707
2707
  var PY_EXTENSIONS = /* @__PURE__ */ new Set([".py", ".ipynb"]);
2708
2708
  var AST_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".py", ".go", ".rs", ".java", ".cs", ".rb", ".kt", ".kts", ".swift", ".ipynb", ".php", ".dart"]);
2709
- var CTXLOOM_VERSION = "1.7.9".length > 0 ? "1.7.9" : "dev";
2709
+ var CTXLOOM_VERSION = "1.7.10".length > 0 ? "1.7.10" : "dev";
2710
2710
  var SNAPSHOT_SCHEMA_VERSION = 2;
2711
2711
  function compareCtxloomVersions(snapshotVer, currentVer) {
2712
2712
  if (snapshotVer === currentVer) return "same";
@@ -4219,8 +4219,8 @@ var CoChangeIndex = class _CoChangeIndex {
4219
4219
  if (event.isBulk || event.isMerge) return;
4220
4220
  const paths = event.files.map((f) => f.path);
4221
4221
  if (paths.length === 0) return;
4222
- for (const path38 of paths) {
4223
- this.nodeCounts.set(path38, (this.nodeCounts.get(path38) ?? 0) + 1);
4222
+ for (const path39 of paths) {
4223
+ this.nodeCounts.set(path39, (this.nodeCounts.get(path39) ?? 0) + 1);
4224
4224
  }
4225
4225
  for (let i = 0; i < paths.length; i++) {
4226
4226
  for (let j = i + 1; j < paths.length; j++) {
@@ -4367,8 +4367,8 @@ var ChurnIndex = class _ChurnIndex {
4367
4367
  */
4368
4368
  snapshot() {
4369
4369
  const nodes = {};
4370
- for (const [path38, raw] of this.nodes) {
4371
- nodes[path38] = {
4370
+ for (const [path39, raw] of this.nodes) {
4371
+ nodes[path39] = {
4372
4372
  commits: raw.commits,
4373
4373
  churnLines: raw.churnLines,
4374
4374
  bugCommits: raw.bugCommits,
@@ -4383,8 +4383,8 @@ var ChurnIndex = class _ChurnIndex {
4383
4383
  */
4384
4384
  static load(s) {
4385
4385
  const idx = new _ChurnIndex();
4386
- for (const [path38, raw] of Object.entries(s.nodes)) {
4387
- idx.nodes.set(path38, {
4386
+ for (const [path39, raw] of Object.entries(s.nodes)) {
4387
+ idx.nodes.set(path39, {
4388
4388
  commits: raw.commits,
4389
4389
  churnLines: raw.churnLines,
4390
4390
  bugCommits: raw.bugCommits,
@@ -4397,8 +4397,8 @@ var ChurnIndex = class _ChurnIndex {
4397
4397
  // -------------------------------------------------------------------------
4398
4398
  // Private helpers
4399
4399
  // -------------------------------------------------------------------------
4400
- getOrCreate(path38) {
4401
- const existing = this.nodes.get(path38);
4400
+ getOrCreate(path39) {
4401
+ const existing = this.nodes.get(path39);
4402
4402
  if (existing !== void 0) return existing;
4403
4403
  const fresh = {
4404
4404
  commits: 0,
@@ -4407,7 +4407,7 @@ var ChurnIndex = class _ChurnIndex {
4407
4407
  authorCounts: {},
4408
4408
  lastTouch: 0
4409
4409
  };
4410
- this.nodes.set(path38, fresh);
4410
+ this.nodes.set(path39, fresh);
4411
4411
  return fresh;
4412
4412
  }
4413
4413
  };
@@ -4488,12 +4488,12 @@ var OwnershipIndex = class _OwnershipIndex {
4488
4488
  */
4489
4489
  snapshot() {
4490
4490
  const nodes = {};
4491
- for (const [path38, raw] of this.nodes) {
4491
+ for (const [path39, raw] of this.nodes) {
4492
4492
  const authorWeights = {};
4493
4493
  for (const [email, entry] of Object.entries(raw.authorWeights)) {
4494
4494
  authorWeights[email] = { ...entry };
4495
4495
  }
4496
- nodes[path38] = { authorWeights, lastTouch: raw.lastTouch };
4496
+ nodes[path39] = { authorWeights, lastTouch: raw.lastTouch };
4497
4497
  }
4498
4498
  return { version: 1, nodes };
4499
4499
  }
@@ -4502,23 +4502,23 @@ var OwnershipIndex = class _OwnershipIndex {
4502
4502
  */
4503
4503
  static load(s) {
4504
4504
  const idx = new _OwnershipIndex();
4505
- for (const [path38, raw] of Object.entries(s.nodes)) {
4505
+ for (const [path39, raw] of Object.entries(s.nodes)) {
4506
4506
  const authorWeights = {};
4507
4507
  for (const [email, entry] of Object.entries(raw.authorWeights)) {
4508
4508
  authorWeights[email] = { ...entry };
4509
4509
  }
4510
- idx.nodes.set(path38, { authorWeights, lastTouch: raw.lastTouch });
4510
+ idx.nodes.set(path39, { authorWeights, lastTouch: raw.lastTouch });
4511
4511
  }
4512
4512
  return idx;
4513
4513
  }
4514
4514
  // -------------------------------------------------------------------------
4515
4515
  // Private helpers
4516
4516
  // -------------------------------------------------------------------------
4517
- getOrCreate(path38) {
4518
- const existing = this.nodes.get(path38);
4517
+ getOrCreate(path39) {
4518
+ const existing = this.nodes.get(path39);
4519
4519
  if (existing !== void 0) return existing;
4520
4520
  const fresh = { authorWeights: {}, lastTouch: 0 };
4521
- this.nodes.set(path38, fresh);
4521
+ this.nodes.set(path39, fresh);
4522
4522
  return fresh;
4523
4523
  }
4524
4524
  };
@@ -7094,7 +7094,8 @@ function registerBlastRadiusTool(registry, ctx) {
7094
7094
  return '<blast_radius changed_files="0">\n <!-- No changed files detected -->\n</blast_radius>';
7095
7095
  }
7096
7096
  const result = await computeBlastRadius({ changedFiles: files, depth, projectRoot: gitRoot, graph });
7097
- const report = getImpactRadius({ graph, overlay: ctx.overlay, changedFiles: files, depth });
7097
+ const overlay = await ctx.getOverlay(project_root) ?? void 0;
7098
+ const report = getImpactRadius({ graph, overlay, changedFiles: files, depth });
7098
7099
  return buildBlastRadiusXml(result, depth, detail_level, report.historicalCoupling);
7099
7100
  }
7100
7101
  );
@@ -8731,15 +8732,16 @@ function registerDetectChangesTool(registry, ctx) {
8731
8732
  if (files.length === 0) {
8732
8733
  return '<detect_changes count="0">\n <!-- No changed files detected -->\n</detect_changes>';
8733
8734
  }
8735
+ const overlay = await ctx.getOverlay(project_root) ?? void 0;
8734
8736
  const { changedFiles: scored, summary } = detectChanges({
8735
8737
  graph,
8736
- overlay: ctx.overlay,
8738
+ overlay,
8737
8739
  changedFiles: files
8738
8740
  });
8739
8741
  if (detail_level === "minimal") {
8740
8742
  return `<detect_changes count="${scored.length}" critical="${summary.critical}" high="${summary.high}" medium="${summary.medium}" low="${summary.low}" detail_level="minimal" />`;
8741
8743
  }
8742
- const hasOverlay = ctx.overlay !== void 0;
8744
+ const hasOverlay = overlay !== void 0;
8743
8745
  const xml = [
8744
8746
  `<detect_changes count="${scored.length}" critical="${summary.critical}" high="${summary.high}" medium="${summary.medium}" low="${summary.low}">`
8745
8747
  ];
@@ -9449,6 +9451,28 @@ function registerFindLargeFunctionsTool(registry, ctx) {
9449
9451
 
9450
9452
  // packages/core/src/tools/git-coupling.ts
9451
9453
  import { z as z31 } from "zod";
9454
+
9455
+ // packages/core/src/tools/overlayNote.ts
9456
+ import fs22 from "fs";
9457
+ import path24 from "path";
9458
+ async function overlayUnavailableNote(ctx, projectRoot) {
9459
+ let rootDir = "";
9460
+ try {
9461
+ const graph = await ctx.getGraph(projectRoot);
9462
+ rootDir = graph.getRootDir();
9463
+ } catch {
9464
+ }
9465
+ if (rootDir) {
9466
+ const overlayFile = path24.join(rootDir, ".ctxloom", "git-overlay.json");
9467
+ if (fs22.existsSync(overlayFile)) {
9468
+ return `Git overlay exists at ${overlayFile} but could not be loaded into the server. Restart the MCP server (or check its logs for a git/overlay error); a re-index is not required.`;
9469
+ }
9470
+ return `No git overlay for ${rootDir}. Build it by running \`ctxloom index\` in that repo (the git overlay is created automatically when git is enabled). If the server was started with --no-git, restart it without that flag.`;
9471
+ }
9472
+ return "Git overlay not available. Run `ctxloom index` with git enabled to build it.";
9473
+ }
9474
+
9475
+ // packages/core/src/tools/git-coupling.ts
9452
9476
  var Schema26 = z31.object({
9453
9477
  file: z31.string().describe("File path to look up co-changed siblings for"),
9454
9478
  limit: z31.number().int().min(1).max(50).default(10),
@@ -9456,11 +9480,11 @@ var Schema26 = z31.object({
9456
9480
  half_life_days: z31.number().int().min(1).max(3650).default(90),
9457
9481
  project_root: ProjectRootField
9458
9482
  });
9459
- function overlayUnavailableResponse(file) {
9483
+ function overlayUnavailableResponse(file, note) {
9460
9484
  return {
9461
9485
  file,
9462
9486
  coupledFiles: [],
9463
- note: "Git overlay not available. Re-index with --with-git to enable coupling data."
9487
+ note
9464
9488
  };
9465
9489
  }
9466
9490
  function buildExplanation(sharedCommits, lastSharedDaysAgo) {
@@ -9485,13 +9509,18 @@ function registerGitCouplingTool(registry, ctx) {
9485
9509
  }
9486
9510
  },
9487
9511
  async (args) => {
9488
- const { file, limit, min_confidence, half_life_days } = Schema26.parse(args);
9489
- if (!ctx.overlay) {
9490
- return JSON.stringify(overlayUnavailableResponse(file));
9512
+ const { file, limit, min_confidence, half_life_days, project_root } = Schema26.parse(args);
9513
+ const overlay = await ctx.getOverlay(project_root);
9514
+ if (!overlay) {
9515
+ return JSON.stringify(
9516
+ overlayUnavailableResponse(file, await overlayUnavailableNote(ctx, project_root))
9517
+ );
9491
9518
  }
9492
- const coChange = ctx.overlay.coChange;
9519
+ const coChange = overlay.coChange;
9493
9520
  if (coChange.size().pairs === 0) {
9494
- return JSON.stringify(overlayUnavailableResponse(file));
9521
+ return JSON.stringify(
9522
+ overlayUnavailableResponse(file, await overlayUnavailableNote(ctx, project_root))
9523
+ );
9495
9524
  }
9496
9525
  const nowSec = Math.floor(Date.now() / 1e3);
9497
9526
  const pairs = coChange.topFor({
@@ -9556,8 +9585,9 @@ function registerRiskOverlayTool(registry, ctx) {
9556
9585
  }
9557
9586
  },
9558
9587
  async (args) => {
9559
- const { nodes } = Schema27.parse(args);
9560
- if (!ctx.overlay) {
9588
+ const { nodes, project_root } = Schema27.parse(args);
9589
+ const overlay = await ctx.getOverlay(project_root);
9590
+ if (!overlay) {
9561
9591
  const response2 = {
9562
9592
  nodes: nodes.map((file) => ({
9563
9593
  file,
@@ -9571,11 +9601,11 @@ function registerRiskOverlayTool(registry, ctx) {
9571
9601
  note: "no git data"
9572
9602
  })),
9573
9603
  overallRiskScore: 0,
9574
- note: "Git overlay not available. Re-index with --with-git to enable risk data."
9604
+ note: await overlayUnavailableNote(ctx, project_root)
9575
9605
  };
9576
9606
  return JSON.stringify(response2);
9577
9607
  }
9578
- const { churn, ownership, coChange } = ctx.overlay;
9608
+ const { churn, ownership, coChange } = overlay;
9579
9609
  const nowSec = Math.floor(Date.now() / 1e3);
9580
9610
  const nodeEntries = nodes.map((file) => {
9581
9611
  const churnStats = churn.statsFor(file);
@@ -9640,8 +9670,8 @@ var RulesConfigError = class extends Error {
9640
9670
  };
9641
9671
 
9642
9672
  // packages/core/src/rules/loadConfig.ts
9643
- import fs22 from "fs/promises";
9644
- import path24 from "path";
9673
+ import fs23 from "fs/promises";
9674
+ import path25 from "path";
9645
9675
  import yaml from "js-yaml";
9646
9676
  import { z as z33 } from "zod";
9647
9677
  var RuleSchema = z33.object({
@@ -9656,10 +9686,10 @@ var RulesConfigSchema = z33.object({
9656
9686
  rules: z33.array(RuleSchema).default([])
9657
9687
  });
9658
9688
  async function loadRulesConfig(rootDir) {
9659
- const filePath = path24.join(rootDir, ".ctxloom", "rules.yml");
9689
+ const filePath = path25.join(rootDir, ".ctxloom", "rules.yml");
9660
9690
  let raw;
9661
9691
  try {
9662
- raw = await fs22.readFile(filePath, "utf-8");
9692
+ raw = await fs23.readFile(filePath, "utf-8");
9663
9693
  } catch (err) {
9664
9694
  if (err.code === "ENOENT") return null;
9665
9695
  throw new RulesConfigError(`Failed to read rules config: ${String(err)}`);
@@ -10072,11 +10102,11 @@ function readRecentChanges(projectRoot) {
10072
10102
  return lines.slice(0, 20).map((line) => {
10073
10103
  const x = line[0];
10074
10104
  const y = line[1];
10075
- const path38 = line.slice(3).trim();
10105
+ const path39 = line.slice(3).trim();
10076
10106
  let status = "?";
10077
10107
  const xy = x === " " ? y : x;
10078
10108
  if (xy === "M" || xy === "A" || xy === "D" || xy === "R") status = xy;
10079
- return { file: path38, status };
10109
+ return { file: path39, status };
10080
10110
  });
10081
10111
  } catch {
10082
10112
  return [];
@@ -10303,8 +10333,8 @@ function createToolRegistry(ctx) {
10303
10333
  }
10304
10334
 
10305
10335
  // packages/core/src/tools/ruleManager.ts
10306
- import fs23 from "fs";
10307
- import path25 from "path";
10336
+ import fs24 from "fs";
10337
+ import path26 from "path";
10308
10338
  var RULE_FILES = [
10309
10339
  ".cursorrules",
10310
10340
  "CLAUDE.md",
@@ -10328,30 +10358,30 @@ var RuleManager = class {
10328
10358
  if (this.cachedRules) return this.cachedRules;
10329
10359
  const rules = [];
10330
10360
  for (const ruleFile of RULE_FILES) {
10331
- const fullPath = path25.join(this.projectRoot, ruleFile);
10361
+ const fullPath = path26.join(this.projectRoot, ruleFile);
10332
10362
  try {
10333
10363
  this.pathValidator.validate(fullPath);
10334
- if (fs23.existsSync(fullPath)) {
10335
- const stat = fs23.statSync(fullPath);
10364
+ if (fs24.existsSync(fullPath)) {
10365
+ const stat = fs24.statSync(fullPath);
10336
10366
  if (stat.isFile()) {
10337
- const content = fs23.readFileSync(fullPath, "utf-8");
10367
+ const content = fs24.readFileSync(fullPath, "utf-8");
10338
10368
  rules.push({
10339
10369
  name: ruleFile,
10340
10370
  path: ruleFile,
10341
10371
  content
10342
10372
  });
10343
10373
  } else if (stat.isDirectory()) {
10344
- const dirEntries = fs23.readdirSync(fullPath);
10374
+ const dirEntries = fs24.readdirSync(fullPath);
10345
10375
  for (const entry of dirEntries) {
10346
- const entryPath = path25.join(fullPath, entry);
10376
+ const entryPath = path26.join(fullPath, entry);
10347
10377
  try {
10348
10378
  this.pathValidator.validate(entryPath);
10349
10379
  } catch {
10350
10380
  continue;
10351
10381
  }
10352
- const entryStat = fs23.statSync(entryPath);
10382
+ const entryStat = fs24.statSync(entryPath);
10353
10383
  if (entryStat.isFile()) {
10354
- const content = fs23.readFileSync(entryPath, "utf-8");
10384
+ const content = fs24.readFileSync(entryPath, "utf-8");
10355
10385
  rules.push({
10356
10386
  name: `${ruleFile}/${entry}`,
10357
10387
  path: `${ruleFile}/${entry}`,
@@ -10394,8 +10424,8 @@ var RuleManager = class {
10394
10424
  };
10395
10425
 
10396
10426
  // packages/core/src/review/AuthorResolver.ts
10397
- import fs24 from "fs/promises";
10398
- import path26 from "path";
10427
+ import fs25 from "fs/promises";
10428
+ import path27 from "path";
10399
10429
  import yaml2 from "js-yaml";
10400
10430
  var AuthorResolver = class {
10401
10431
  constructor(ctxloomDir) {
@@ -10420,8 +10450,8 @@ var AuthorResolver = class {
10420
10450
  /** Write a new mapping to the cache file. */
10421
10451
  async writeCache(email, handle) {
10422
10452
  this.cache = { ...this.cache, [email]: handle };
10423
- await fs24.writeFile(
10424
- path26.join(this.ctxloomDir, "authors-cache.json"),
10453
+ await fs25.writeFile(
10454
+ path27.join(this.ctxloomDir, "authors-cache.json"),
10425
10455
  JSON.stringify(this.cache, null, 2)
10426
10456
  );
10427
10457
  }
@@ -10430,9 +10460,9 @@ var AuthorResolver = class {
10430
10460
  return emails.filter((e) => this.resolve(e) === void 0);
10431
10461
  }
10432
10462
  async loadYml() {
10433
- const file = path26.join(this.ctxloomDir, "authors.yml");
10463
+ const file = path27.join(this.ctxloomDir, "authors.yml");
10434
10464
  try {
10435
- const raw = await fs24.readFile(file, "utf8");
10465
+ const raw = await fs25.readFile(file, "utf8");
10436
10466
  const parsed = yaml2.load(raw);
10437
10467
  if (!parsed) return;
10438
10468
  this.mappings = parsed.mappings ?? {};
@@ -10441,9 +10471,9 @@ var AuthorResolver = class {
10441
10471
  }
10442
10472
  }
10443
10473
  async loadCache() {
10444
- const file = path26.join(this.ctxloomDir, "authors-cache.json");
10474
+ const file = path27.join(this.ctxloomDir, "authors-cache.json");
10445
10475
  try {
10446
- const raw = await fs24.readFile(file, "utf8");
10476
+ const raw = await fs25.readFile(file, "utf8");
10447
10477
  this.cache = JSON.parse(raw);
10448
10478
  } catch {
10449
10479
  }
@@ -10468,8 +10498,8 @@ async function resolveViaGitHubApi(email, owner, repo, token) {
10468
10498
  }
10469
10499
 
10470
10500
  // packages/core/src/review/CodeownersWriter.ts
10471
- import fs25 from "fs/promises";
10472
- import path27 from "path";
10501
+ import fs26 from "fs/promises";
10502
+ import path28 from "path";
10473
10503
  var MARKER_START = "# <ctxloom:start> \u2014 managed by ctxloom review-suggest; do not edit between markers";
10474
10504
  var MARKER_START_DETECT = "# <ctxloom:start>";
10475
10505
  var MARKER_END = "# <ctxloom:end>";
@@ -10501,15 +10531,15 @@ ${block}
10501
10531
  async function generateCODEOWNERS(codeownersPath, rules) {
10502
10532
  let existing = "";
10503
10533
  try {
10504
- existing = await fs25.readFile(codeownersPath, "utf8");
10534
+ existing = await fs26.readFile(codeownersPath, "utf8");
10505
10535
  } catch {
10506
10536
  }
10507
10537
  const block = buildCodeownersBlock(rules);
10508
10538
  return mergeIntoFile(existing, block);
10509
10539
  }
10510
10540
  async function writeCODEOWNERS(codeownersPath, content) {
10511
- await fs25.mkdir(path27.dirname(codeownersPath), { recursive: true });
10512
- await fs25.writeFile(codeownersPath, content, "utf8");
10541
+ await fs26.mkdir(path28.dirname(codeownersPath), { recursive: true });
10542
+ await fs26.writeFile(codeownersPath, content, "utf8");
10513
10543
  }
10514
10544
 
10515
10545
  // packages/core/src/review/types.ts
@@ -10694,8 +10724,8 @@ function matchGlob(pattern, value) {
10694
10724
  }
10695
10725
 
10696
10726
  // packages/core/src/review/loadConfig.ts
10697
- import fs26 from "fs/promises";
10698
- import path28 from "path";
10727
+ import fs27 from "fs/promises";
10728
+ import path29 from "path";
10699
10729
  import yaml3 from "js-yaml";
10700
10730
  function freshDefaults() {
10701
10731
  return {
@@ -10706,9 +10736,9 @@ function freshDefaults() {
10706
10736
  };
10707
10737
  }
10708
10738
  async function loadReviewConfig(root) {
10709
- const file = path28.join(root, ".ctxloom", "review.yml");
10739
+ const file = path29.join(root, ".ctxloom", "review.yml");
10710
10740
  try {
10711
- const raw = await fs26.readFile(file, "utf8");
10741
+ const raw = await fs27.readFile(file, "utf8");
10712
10742
  const parsed = yaml3.load(raw);
10713
10743
  if (!parsed) return freshDefaults();
10714
10744
  return {
@@ -10723,13 +10753,13 @@ async function loadReviewConfig(root) {
10723
10753
  }
10724
10754
 
10725
10755
  // packages/core/src/security/PathValidator.ts
10726
- import path29 from "path";
10727
- import fs27 from "fs";
10756
+ import path30 from "path";
10757
+ import fs28 from "fs";
10728
10758
  var MAX_FILE_SIZE = 5 * 1024 * 1024;
10729
10759
  var PathValidator = class {
10730
10760
  canonicalRoot;
10731
10761
  constructor(projectRoot) {
10732
- this.canonicalRoot = fs27.realpathSync(path29.resolve(projectRoot));
10762
+ this.canonicalRoot = fs28.realpathSync(path30.resolve(projectRoot));
10733
10763
  }
10734
10764
  /**
10735
10765
  * Validates that the given input path resolves within the project root.
@@ -10739,14 +10769,14 @@ var PathValidator = class {
10739
10769
  * @throws Error if the path escapes the project root
10740
10770
  */
10741
10771
  validate(inputPath) {
10742
- const resolved = path29.resolve(this.canonicalRoot, inputPath);
10772
+ const resolved = path30.resolve(this.canonicalRoot, inputPath);
10743
10773
  let canonical;
10744
10774
  try {
10745
- canonical = fs27.realpathSync(resolved);
10775
+ canonical = fs28.realpathSync(resolved);
10746
10776
  } catch {
10747
10777
  canonical = resolved;
10748
10778
  }
10749
- if (!canonical.startsWith(this.canonicalRoot + path29.sep) && canonical !== this.canonicalRoot) {
10779
+ if (!canonical.startsWith(this.canonicalRoot + path30.sep) && canonical !== this.canonicalRoot) {
10750
10780
  throw new Error(
10751
10781
  `Path traversal blocked: "${inputPath}" resolves outside of the project root`
10752
10782
  );
@@ -10763,7 +10793,7 @@ var PathValidator = class {
10763
10793
  * Converts an absolute path to a relative path from the project root.
10764
10794
  */
10765
10795
  toRelative(absolutePath) {
10766
- return path29.relative(this.canonicalRoot, absolutePath);
10796
+ return path30.relative(this.canonicalRoot, absolutePath);
10767
10797
  }
10768
10798
  /**
10769
10799
  * Validates and reads a file, returning its content.
@@ -10771,11 +10801,11 @@ var PathValidator = class {
10771
10801
  */
10772
10802
  readFile(inputPath) {
10773
10803
  const absPath = this.validate(inputPath);
10774
- const stat = fs27.statSync(absPath);
10804
+ const stat = fs28.statSync(absPath);
10775
10805
  if (stat.size > MAX_FILE_SIZE) {
10776
10806
  throw new Error(`File too large: ${inputPath} (${Math.round(stat.size / 1024)}KB, max ${MAX_FILE_SIZE / 1024 / 1024}MB)`);
10777
10807
  }
10778
- return fs27.readFileSync(absPath, "utf-8");
10808
+ return fs28.readFileSync(absPath, "utf-8");
10779
10809
  }
10780
10810
  /**
10781
10811
  * Checks if a path exists and is within the project root.
@@ -10926,7 +10956,7 @@ var EmailAlreadyUsedError = class extends Error {
10926
10956
 
10927
10957
  // packages/core/src/license/LicenseStore.ts
10928
10958
  import { readFileSync, writeFileSync, unlinkSync, mkdirSync, chmodSync, existsSync } from "fs";
10929
- import path30 from "path";
10959
+ import path31 from "path";
10930
10960
 
10931
10961
  // packages/core/src/license/types.ts
10932
10962
  import { z as z37 } from "zod";
@@ -10947,7 +10977,7 @@ var LicenseFileSchema = z37.object({
10947
10977
 
10948
10978
  // packages/core/src/license/LicenseStore.ts
10949
10979
  function licenseFilePath(home) {
10950
- return path30.join(home, ".ctxloom", "license.json");
10980
+ return path31.join(home, ".ctxloom", "license.json");
10951
10981
  }
10952
10982
  var LicenseStore = class {
10953
10983
  filePath;
@@ -10970,7 +11000,7 @@ var LicenseStore = class {
10970
11000
  }
10971
11001
  }
10972
11002
  async write(license) {
10973
- mkdirSync(path30.dirname(this.filePath), { recursive: true });
11003
+ mkdirSync(path31.dirname(this.filePath), { recursive: true });
10974
11004
  writeFileSync(this.filePath, JSON.stringify(license, null, 2), {
10975
11005
  encoding: "utf8",
10976
11006
  mode: 384
@@ -11101,11 +11131,11 @@ import os5 from "os";
11101
11131
 
11102
11132
  // packages/core/src/license/DistinctIdStore.ts
11103
11133
  import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync2 } from "fs";
11104
- import path31 from "path";
11134
+ import path32 from "path";
11105
11135
  import os3 from "os";
11106
11136
  var UUID_V4_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
11107
11137
  function distinctIdPath(home) {
11108
- return path31.join(home ?? os3.homedir(), ".ctxloom", "distinct_id");
11138
+ return path32.join(home ?? os3.homedir(), ".ctxloom", "distinct_id");
11109
11139
  }
11110
11140
  function isValidV4(id) {
11111
11141
  return typeof id === "string" && UUID_V4_REGEX.test(id);
@@ -11126,7 +11156,7 @@ function getOrCreateDistinctId(home) {
11126
11156
  id: crypto.randomUUID(),
11127
11157
  alias_pending: os3.hostname()
11128
11158
  };
11129
- mkdirSync2(path31.dirname(filePath), { recursive: true });
11159
+ mkdirSync2(path32.dirname(filePath), { recursive: true });
11130
11160
  writeFileSync2(filePath, JSON.stringify(record), { mode: 384 });
11131
11161
  return record;
11132
11162
  }
@@ -11155,7 +11185,7 @@ var TELEMETRY_DISABLED = TELEMETRY_LEVEL === "off";
11155
11185
  function getTelemetryLevel() {
11156
11186
  return TELEMETRY_LEVEL;
11157
11187
  }
11158
- var CTXLOOM_VERSION2 = "1.7.9".length > 0 ? "1.7.9" : "dev";
11188
+ var CTXLOOM_VERSION2 = "1.7.10".length > 0 ? "1.7.10" : "dev";
11159
11189
  var POSTHOG_HOST = "https://eu.i.posthog.com";
11160
11190
  var POSTHOG_KEY = process.env["POSTHOG_API_KEY"] ?? (true ? "phc_CiDkmFLcZ2K6uCpcoSUQLmFrnnUvsyXGhSxopX5TVKE6" : "");
11161
11191
  var SENTRY_DSN = process.env["SENTRY_DSN"] ?? (true ? "https://81c94a0f04a8e242dee493ac1e17f733@o4508531702497280.ingest.de.sentry.io/4511256875368528" : "");
@@ -11288,17 +11318,17 @@ function parseStack(stack) {
11288
11318
 
11289
11319
  // packages/core/src/license/FunnelMilestones.ts
11290
11320
  import { existsSync as existsSync3, mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "fs";
11291
- import path32 from "path";
11321
+ import path33 from "path";
11292
11322
  import os4 from "os";
11293
11323
  var INSTALL_MARKER = "installed_at";
11294
11324
  var FIRST_REVIEW_MARKER = "first_review_at";
11295
11325
  function writeMarker(filePath) {
11296
- mkdirSync3(path32.dirname(filePath), { recursive: true });
11326
+ mkdirSync3(path33.dirname(filePath), { recursive: true });
11297
11327
  writeFileSync3(filePath, (/* @__PURE__ */ new Date()).toISOString(), { mode: 384 });
11298
11328
  }
11299
11329
  function shouldEmitInstallCompleted(home) {
11300
11330
  const root = home ?? os4.homedir();
11301
- const marker = path32.join(root, ".ctxloom", INSTALL_MARKER);
11331
+ const marker = path33.join(root, ".ctxloom", INSTALL_MARKER);
11302
11332
  if (existsSync3(marker)) return false;
11303
11333
  try {
11304
11334
  writeMarker(marker);
@@ -11307,7 +11337,7 @@ function shouldEmitInstallCompleted(home) {
11307
11337
  return true;
11308
11338
  }
11309
11339
  function shouldEmitFirstReviewRun(projectRoot) {
11310
- const marker = path32.join(projectRoot, ".ctxloom", FIRST_REVIEW_MARKER);
11340
+ const marker = path33.join(projectRoot, ".ctxloom", FIRST_REVIEW_MARKER);
11311
11341
  if (existsSync3(marker)) return false;
11312
11342
  try {
11313
11343
  writeMarker(marker);
@@ -11425,16 +11455,16 @@ async function startTrial(email, opts = {}) {
11425
11455
 
11426
11456
  // packages/core/src/license/TelemetryNotice.ts
11427
11457
  import { existsSync as existsSync4, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "fs";
11428
- import path33 from "path";
11458
+ import path34 from "path";
11429
11459
  import os6 from "os";
11430
11460
  function noticePath(home) {
11431
- return path33.join(home ?? os6.homedir(), ".ctxloom", "telemetry_notice_shown");
11461
+ return path34.join(home ?? os6.homedir(), ".ctxloom", "telemetry_notice_shown");
11432
11462
  }
11433
11463
  function shouldShowTelemetryNotice(home) {
11434
11464
  const filePath = noticePath(home);
11435
11465
  if (existsSync4(filePath)) return false;
11436
11466
  try {
11437
- mkdirSync4(path33.dirname(filePath), { recursive: true });
11467
+ mkdirSync4(path34.dirname(filePath), { recursive: true });
11438
11468
  writeFileSync4(filePath, (/* @__PURE__ */ new Date()).toISOString(), { mode: 384 });
11439
11469
  } catch {
11440
11470
  }
@@ -11442,13 +11472,13 @@ function shouldShowTelemetryNotice(home) {
11442
11472
  }
11443
11473
 
11444
11474
  // packages/core/src/server/ProjectState.ts
11445
- import path35 from "path";
11475
+ import path36 from "path";
11446
11476
 
11447
11477
  // packages/core/src/server/projectId.ts
11448
11478
  import crypto5 from "crypto";
11449
- import path34 from "path";
11479
+ import path35 from "path";
11450
11480
  function hashProjectRoot(absPath) {
11451
- const canonical = path34.resolve(absPath);
11481
+ const canonical = path35.resolve(absPath);
11452
11482
  return crypto5.createHash("sha256").update(canonical).digest("hex").slice(0, 16);
11453
11483
  }
11454
11484
 
@@ -11456,7 +11486,7 @@ function hashProjectRoot(absPath) {
11456
11486
  function createProjectState(projectRoot, opts = {}) {
11457
11487
  return {
11458
11488
  projectRoot,
11459
- dbPath: path35.join(projectRoot, ".ctxloom", "vectors.lancedb"),
11489
+ dbPath: path36.join(projectRoot, ".ctxloom", "vectors.lancedb"),
11460
11490
  pinned: opts.pinned ?? false,
11461
11491
  lastTouchedAt: Date.now(),
11462
11492
  vectorsInitialized: false,
@@ -11467,6 +11497,7 @@ function createProjectState(projectRoot, opts = {}) {
11467
11497
  skeletonizerPromise: null,
11468
11498
  ruleManager: null,
11469
11499
  overlay: null,
11500
+ overlayPromise: null,
11470
11501
  watcher: null,
11471
11502
  pathValidator: null
11472
11503
  };
@@ -11507,6 +11538,7 @@ async function disposeProjectState(state) {
11507
11538
  state.skeletonizerPromise = null;
11508
11539
  state.ruleManager = null;
11509
11540
  state.overlay = null;
11541
+ state.overlayPromise = null;
11510
11542
  state.pathValidator = null;
11511
11543
  state.graphInitialized = false;
11512
11544
  state.vectorsInitialized = false;
@@ -11613,8 +11645,8 @@ var ProjectStateManager = class {
11613
11645
  };
11614
11646
 
11615
11647
  // packages/core/src/server/resolveProjectRoot.ts
11616
- import fs28 from "fs";
11617
- import path36 from "path";
11648
+ import fs29 from "fs";
11649
+ import path37 from "path";
11618
11650
  var PATH_SEPARATOR_PATTERN = /[/\\~]|^[A-Za-z]:/;
11619
11651
  function looksLikePath(value) {
11620
11652
  return PATH_SEPARATOR_PATTERN.test(value);
@@ -11639,13 +11671,13 @@ function resolvePathSafely(p, cwd) {
11639
11671
  let expanded = p;
11640
11672
  if (p === "~" || p.startsWith("~/")) {
11641
11673
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
11642
- expanded = p === "~" ? home : path36.join(home, p.slice(2));
11674
+ expanded = p === "~" ? home : path37.join(home, p.slice(2));
11643
11675
  }
11644
- return path36.isAbsolute(expanded) ? path36.resolve(expanded) : path36.resolve(cwd, expanded);
11676
+ return path37.isAbsolute(expanded) ? path37.resolve(expanded) : path37.resolve(cwd, expanded);
11645
11677
  }
11646
11678
  function realpathOrSame(p) {
11647
11679
  try {
11648
- return fs28.realpathSync(p);
11680
+ return fs29.realpathSync(p);
11649
11681
  } catch {
11650
11682
  return p;
11651
11683
  }
@@ -11670,7 +11702,7 @@ function resolveProjectRoot(input) {
11670
11702
  };
11671
11703
  }
11672
11704
  const resolved2 = resolvePathSafely(arg, cwd);
11673
- if (!fs28.existsSync(resolved2)) {
11705
+ if (!fs29.existsSync(resolved2)) {
11674
11706
  return {
11675
11707
  kind: "project_root_not_found",
11676
11708
  attemptedPath: resolved2,
@@ -11681,7 +11713,7 @@ function resolveProjectRoot(input) {
11681
11713
  }
11682
11714
  if (env !== void 0 && env !== "") {
11683
11715
  const resolved2 = resolvePathSafely(env, cwd);
11684
- if (!fs28.existsSync(resolved2)) {
11716
+ if (!fs29.existsSync(resolved2)) {
11685
11717
  return {
11686
11718
  kind: "project_root_not_found",
11687
11719
  attemptedPath: resolved2,
@@ -11708,12 +11740,12 @@ var FILESYSTEM_ROOTS = /* @__PURE__ */ new Set(["/", "C:\\", "D:\\", "E:\\", "F:
11708
11740
  function validateDefaultRoot(candidate) {
11709
11741
  if (FILESYSTEM_ROOTS.has(candidate)) return false;
11710
11742
  try {
11711
- const stat = fs28.statSync(candidate);
11743
+ const stat = fs29.statSync(candidate);
11712
11744
  if (!stat.isDirectory()) return false;
11713
11745
  } catch {
11714
11746
  return false;
11715
11747
  }
11716
- return PROJECT_MARKERS.some((m) => fs28.existsSync(path36.join(candidate, m)));
11748
+ return PROJECT_MARKERS.some((m) => fs29.existsSync(path37.join(candidate, m)));
11717
11749
  }
11718
11750
 
11719
11751
  // packages/core/src/server/structuredErrors.ts
@@ -11785,8 +11817,8 @@ var EmittedOnceTracker = class {
11785
11817
  };
11786
11818
 
11787
11819
  // packages/core/src/install/installer.ts
11788
- import fs29 from "fs";
11789
- import path37 from "path";
11820
+ import fs30 from "fs";
11821
+ import path38 from "path";
11790
11822
 
11791
11823
  // packages/core/src/install/templates.ts
11792
11824
  var RULES_BLOCK_NAME = "CTXLOOM-RULES";
@@ -12491,8 +12523,8 @@ function skillFilePath(name) {
12491
12523
  // packages/core/src/install/installer.ts
12492
12524
  function installHarness(opts = {}) {
12493
12525
  const cwd = opts.cwd ?? process.cwd();
12494
- const projectRoot = path37.resolve(cwd);
12495
- const stat = fs29.statSync(projectRoot);
12526
+ const projectRoot = path38.resolve(cwd);
12527
+ const stat = fs30.statSync(projectRoot);
12496
12528
  if (!stat.isDirectory()) {
12497
12529
  throw new Error(`installHarness: ${projectRoot} is not a directory`);
12498
12530
  }
@@ -12540,20 +12572,20 @@ function resolveExtraHosts(ids, warnings) {
12540
12572
  return HOST_ADAPTERS.filter((a) => requested.has(a.id));
12541
12573
  }
12542
12574
  function safeJoin(root, name) {
12543
- const target = path37.resolve(root, name);
12544
- const rootResolved = path37.resolve(root);
12575
+ const target = path38.resolve(root, name);
12576
+ const rootResolved = path38.resolve(root);
12545
12577
  const caseFold = process.platform === "darwin" || process.platform === "win32";
12546
12578
  const t = caseFold ? target.toLowerCase() : target;
12547
12579
  const r = caseFold ? rootResolved.toLowerCase() : rootResolved;
12548
- if (!t.startsWith(r + path37.sep) && t !== r) {
12580
+ if (!t.startsWith(r + path38.sep) && t !== r) {
12549
12581
  throw new Error(`installHarness: refusing to write outside project root: ${target}`);
12550
12582
  }
12551
12583
  return target;
12552
12584
  }
12553
12585
  function writeRulesBlock(projectRoot, filename, opts) {
12554
12586
  const filePath = safeJoin(projectRoot, filename);
12555
- const existed = fs29.existsSync(filePath);
12556
- const existing = existed ? fs29.readFileSync(filePath, "utf-8") : "";
12587
+ const existed = fs30.existsSync(filePath);
12588
+ const existing = existed ? fs30.readFileSync(filePath, "utf-8") : "";
12557
12589
  if (existed) {
12558
12590
  const block = extractBlock(existing, RULES_BLOCK_NAME);
12559
12591
  if (block) {
@@ -12571,7 +12603,7 @@ function writeRulesBlock(projectRoot, filename, opts) {
12571
12603
  }
12572
12604
  const next = upsertBlock(existing, RULES_BLOCK_NAME, RULES_BLOCK_CONTENT);
12573
12605
  if (!opts.dryRun) {
12574
- fs29.writeFileSync(filePath, next, "utf-8");
12606
+ fs30.writeFileSync(filePath, next, "utf-8");
12575
12607
  }
12576
12608
  return {
12577
12609
  path: filePath,
@@ -12584,15 +12616,15 @@ function writeRulesBlock(projectRoot, filename, opts) {
12584
12616
  function writeHooksJson(projectRoot, opts) {
12585
12617
  const dir = safeJoin(projectRoot, ".claude");
12586
12618
  const filePath = safeJoin(projectRoot, ".claude/hooks.json");
12587
- const existed = fs29.existsSync(filePath);
12619
+ const existed = fs30.existsSync(filePath);
12588
12620
  let current = {};
12589
12621
  if (existed) {
12590
12622
  try {
12591
- const text = fs29.readFileSync(filePath, "utf-8");
12623
+ const text = fs30.readFileSync(filePath, "utf-8");
12592
12624
  current = JSON.parse(text);
12593
12625
  } catch (err) {
12594
12626
  opts.warnings.push(
12595
- `Could not parse existing ${path37.relative(projectRoot, filePath)}; treating as empty. (${err instanceof Error ? err.message : String(err)})`
12627
+ `Could not parse existing ${path38.relative(projectRoot, filePath)}; treating as empty. (${err instanceof Error ? err.message : String(err)})`
12596
12628
  );
12597
12629
  current = {};
12598
12630
  }
@@ -12609,12 +12641,12 @@ function writeHooksJson(projectRoot, opts) {
12609
12641
  const nextJson = JSON.stringify(merged, null, 2) + "\n";
12610
12642
  let alreadyCorrect = false;
12611
12643
  if (existed) {
12612
- const currentText = fs29.readFileSync(filePath, "utf-8");
12644
+ const currentText = fs30.readFileSync(filePath, "utf-8");
12613
12645
  if (currentText === nextJson) alreadyCorrect = true;
12614
12646
  }
12615
12647
  if (!opts.dryRun && !alreadyCorrect) {
12616
- fs29.mkdirSync(dir, { recursive: true });
12617
- fs29.writeFileSync(filePath, nextJson, "utf-8");
12648
+ fs30.mkdirSync(dir, { recursive: true });
12649
+ fs30.writeFileSync(filePath, nextJson, "utf-8");
12618
12650
  }
12619
12651
  return {
12620
12652
  path: filePath,
@@ -12636,19 +12668,19 @@ function isCtxloomEntry(entry, expectedMatcher) {
12636
12668
  }
12637
12669
  function writeHostAdapter(projectRoot, adapter, opts) {
12638
12670
  const filePath = safeJoin(projectRoot, adapter.path);
12639
- const dir = path37.dirname(filePath);
12640
- const existed = fs29.existsSync(filePath);
12671
+ const dir = path38.dirname(filePath);
12672
+ const existed = fs30.existsSync(filePath);
12641
12673
  const rendered = adapter.render();
12642
12674
  let alreadyCorrect = false;
12643
12675
  if (existed) {
12644
- const current = fs29.readFileSync(filePath, "utf-8");
12676
+ const current = fs30.readFileSync(filePath, "utf-8");
12645
12677
  if (adapter.isCanonical(current)) {
12646
12678
  alreadyCorrect = true;
12647
12679
  }
12648
12680
  }
12649
12681
  if (!opts.dryRun && !alreadyCorrect) {
12650
- fs29.mkdirSync(dir, { recursive: true });
12651
- fs29.writeFileSync(filePath, rendered, "utf-8");
12682
+ fs30.mkdirSync(dir, { recursive: true });
12683
+ fs30.writeFileSync(filePath, rendered, "utf-8");
12652
12684
  }
12653
12685
  return {
12654
12686
  path: filePath,
@@ -12661,16 +12693,16 @@ function writeHostAdapter(projectRoot, adapter, opts) {
12661
12693
  function writeSkill(projectRoot, skill, opts) {
12662
12694
  const dir = safeJoin(projectRoot, `.claude/skills/${skill.name}`);
12663
12695
  const filePath = safeJoin(projectRoot, skillFilePath(skill.name));
12664
- const existed = fs29.existsSync(filePath);
12696
+ const existed = fs30.existsSync(filePath);
12665
12697
  let alreadyCorrect = false;
12666
12698
  if (existed) {
12667
- if (fs29.readFileSync(filePath, "utf-8") === skill.content) {
12699
+ if (fs30.readFileSync(filePath, "utf-8") === skill.content) {
12668
12700
  alreadyCorrect = true;
12669
12701
  }
12670
12702
  }
12671
12703
  if (!opts.dryRun && !alreadyCorrect) {
12672
- fs29.mkdirSync(dir, { recursive: true });
12673
- fs29.writeFileSync(filePath, skill.content, "utf-8");
12704
+ fs30.mkdirSync(dir, { recursive: true });
12705
+ fs30.writeFileSync(filePath, skill.content, "utf-8");
12674
12706
  }
12675
12707
  return {
12676
12708
  path: filePath,
@@ -12683,17 +12715,17 @@ function writeSkill(projectRoot, skill, opts) {
12683
12715
  function writeSessionStartScript(projectRoot, opts) {
12684
12716
  const dir = safeJoin(projectRoot, ".claude/hooks");
12685
12717
  const filePath = safeJoin(projectRoot, ".claude/hooks/session-start.sh");
12686
- const existed = fs29.existsSync(filePath);
12718
+ const existed = fs30.existsSync(filePath);
12687
12719
  let alreadyCorrect = false;
12688
12720
  if (existed) {
12689
- const current = fs29.readFileSync(filePath, "utf-8");
12721
+ const current = fs30.readFileSync(filePath, "utf-8");
12690
12722
  if (current === SESSION_START_FULL) alreadyCorrect = true;
12691
12723
  }
12692
12724
  if (!opts.dryRun && !alreadyCorrect) {
12693
- fs29.mkdirSync(dir, { recursive: true });
12694
- fs29.writeFileSync(filePath, SESSION_START_FULL, "utf-8");
12725
+ fs30.mkdirSync(dir, { recursive: true });
12726
+ fs30.writeFileSync(filePath, SESSION_START_FULL, "utf-8");
12695
12727
  try {
12696
- fs29.chmodSync(filePath, 493);
12728
+ fs30.chmodSync(filePath, 493);
12697
12729
  } catch {
12698
12730
  }
12699
12731
  }
@@ -12841,4 +12873,4 @@ export {
12841
12873
  skillFilePath,
12842
12874
  installHarness
12843
12875
  };
12844
- //# sourceMappingURL=chunk-73ZZ6O4B.js.map
12876
+ //# sourceMappingURL=chunk-J3NVYR6J.js.map