ctxloom-pro 1.1.5 → 1.2.1

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/README.md CHANGED
@@ -630,6 +630,19 @@ Token counts use the standard 4 chars/token approximation. Per-repo range (57–
630
630
  | `CTXLOOM_GRAMMAR_CDN` | CDN base URL for grammar downloads (air-gapped environments) | Built-in |
631
631
  | `CTXLOOM_MAX_PROJECTS` | LRU cache cap for multi-project state (v1.1.0+) | `5` |
632
632
  | `CTXLOOM_DISABLE_MULTIPROJECT` | Set to `1` to revert to v1.0.31 single-project mode (v1.1.0+) | (unset) |
633
+ | `CTXLOOM_NO_TELEMETRY` | Set to `1` to disable anonymous telemetry entirely (v1.2.0+) | (unset) |
634
+ | `CTXLOOM_TELEMETRY_LEVEL` | `all` / `error` / `off` — granular telemetry scope (v1.2.0+) | `all` |
635
+ | `DO_NOT_TRACK` | Universal cross-tool opt-out — equivalent to `CTXLOOM_NO_TELEMETRY=1` | (unset) |
636
+
637
+ ---
638
+
639
+ ## Telemetry
640
+
641
+ ctxloom collects **anonymous, opt-out telemetry** to understand which features are used and to catch crashes. **No file contents, paths, project names, or aliases are ever transmitted.** Project identifiers are SHA-256 hashes of the absolute path. The `distinct_id` is a random UUID at `~/.ctxloom/distinct_id`.
642
+
643
+ Disable with `CTXLOOM_NO_TELEMETRY=1` or the cross-tool `DO_NOT_TRACK=1`. For a granular middle ground (crash reports yes, usage analytics no) use `CTXLOOM_TELEMETRY_LEVEL=error`.
644
+
645
+ The complete list of events, properties, what is *never* collected, and how project paths are anonymized is documented in **[docs/TELEMETRY.md](docs/TELEMETRY.md)**.
633
646
 
634
647
  ---
635
648
 
@@ -77,7 +77,6 @@ var init_logger = __esm({
77
77
  });
78
78
 
79
79
  // ../../packages/core/src/indexer/embedder.ts
80
- import { pipeline } from "@huggingface/transformers";
81
80
  import fs3 from "fs";
82
81
  import path3 from "path";
83
82
  function collectFiles(dir, results = []) {
@@ -155,8 +154,6 @@ var init_embedder = __esm({
155
154
  });
156
155
 
157
156
  // ../../packages/core/src/db/VectorStore.ts
158
- import lancedb from "@lancedb/lancedb";
159
- import { makeArrowTable } from "@lancedb/lancedb";
160
157
  import path15 from "path";
161
158
  import fs15 from "fs";
162
159
  var init_VectorStore = __esm({
@@ -169,19 +166,18 @@ var init_VectorStore = __esm({
169
166
  // server/index.ts
170
167
  import express from "express";
171
168
  import cors from "cors";
172
- import path42 from "path";
169
+ import path43 from "path";
173
170
  import fs32 from "fs";
174
171
  import { fileURLToPath as fileURLToPath2 } from "url";
175
172
 
176
173
  // server/loader.ts
177
- import path36 from "path";
174
+ import path37 from "path";
178
175
 
179
176
  // ../../packages/core/src/graph/DependencyGraph.ts
180
177
  import fs7 from "fs";
181
178
  import path7 from "path";
182
179
 
183
180
  // ../../packages/core/src/ast/ASTParser.ts
184
- import * as TreeSitter from "web-tree-sitter";
185
181
  import fs2 from "fs";
186
182
  import path2 from "path";
187
183
  import { fileURLToPath } from "url";
@@ -425,6 +421,11 @@ function extractNotebookPythonSource(content) {
425
421
  }
426
422
 
427
423
  // ../../packages/core/src/ast/ASTParser.ts
424
+ var _ts = null;
425
+ async function loadTreeSitter() {
426
+ if (_ts === null) _ts = await import("web-tree-sitter");
427
+ return _ts;
428
+ }
428
429
  var __dirname = path2.dirname(fileURLToPath(import.meta.url));
429
430
  function findWasmDir() {
430
431
  const candidates = [
@@ -480,7 +481,7 @@ var ASTParser = class {
480
481
  getParser(language) {
481
482
  let parser = this.parserCache.get(language);
482
483
  if (parser) return parser;
483
- parser = new TreeSitter.Parser();
484
+ parser = new _ts.Parser();
484
485
  parser.setLanguage(language);
485
486
  this.parserCache.set(language, parser);
486
487
  return parser;
@@ -488,7 +489,8 @@ var ASTParser = class {
488
489
  dartLang = null;
489
490
  grammarLoader = new GrammarLoader();
490
491
  async init() {
491
- await TreeSitter.Parser.init({
492
+ const TS = await loadTreeSitter();
493
+ await TS.Parser.init({
492
494
  locateFile: () => path2.join(WASM_DIR, "tree-sitter.wasm")
493
495
  });
494
496
  const grammarCandidates = [
@@ -510,7 +512,7 @@ var ASTParser = class {
510
512
  if (!grammarPath) {
511
513
  throw new Error("Could not locate tree-sitter-typescript.wasm grammar file");
512
514
  }
513
- this.tsLang = await TreeSitter.Language.load(grammarPath);
515
+ this.tsLang = await _ts.Language.load(grammarPath);
514
516
  }
515
517
  /**
516
518
  * Load Python grammar on demand. Downloads and caches WASM if needed.
@@ -519,7 +521,7 @@ var ASTParser = class {
519
521
  if (this.pyLang) return;
520
522
  try {
521
523
  const wasmPath = await this.grammarLoader.ensureGrammar("python");
522
- this.pyLang = await TreeSitter.Language.load(wasmPath);
524
+ this.pyLang = await _ts.Language.load(wasmPath);
523
525
  } catch (err) {
524
526
  const { logger: logger2 } = await Promise.resolve().then(() => (init_logger(), logger_exports));
525
527
  logger2.warn("Python grammar unavailable", { detail: err instanceof Error ? err.message : String(err) });
@@ -532,7 +534,7 @@ var ASTParser = class {
532
534
  if (this.goLang) return;
533
535
  try {
534
536
  const wasmPath = await this.grammarLoader.ensureGrammar("go");
535
- this.goLang = await TreeSitter.Language.load(wasmPath);
537
+ this.goLang = await _ts.Language.load(wasmPath);
536
538
  } catch (err) {
537
539
  const { logger: logger2 } = await Promise.resolve().then(() => (init_logger(), logger_exports));
538
540
  logger2.warn("Go grammar unavailable", { detail: err instanceof Error ? err.message : String(err) });
@@ -545,7 +547,7 @@ var ASTParser = class {
545
547
  if (this.rustLang) return;
546
548
  try {
547
549
  const wasmPath = await this.grammarLoader.ensureGrammar("rust");
548
- this.rustLang = await TreeSitter.Language.load(wasmPath);
550
+ this.rustLang = await _ts.Language.load(wasmPath);
549
551
  } catch (err) {
550
552
  const { logger: logger2 } = await Promise.resolve().then(() => (init_logger(), logger_exports));
551
553
  logger2.warn("Rust grammar unavailable", { detail: err instanceof Error ? err.message : String(err) });
@@ -558,7 +560,7 @@ var ASTParser = class {
558
560
  if (this.javaLang) return;
559
561
  try {
560
562
  const wasmPath = await this.grammarLoader.ensureGrammar("java");
561
- this.javaLang = await TreeSitter.Language.load(wasmPath);
563
+ this.javaLang = await _ts.Language.load(wasmPath);
562
564
  } catch (err) {
563
565
  const { logger: logger2 } = await Promise.resolve().then(() => (init_logger(), logger_exports));
564
566
  logger2.warn("Java grammar unavailable", { detail: err instanceof Error ? err.message : String(err) });
@@ -568,7 +570,7 @@ var ASTParser = class {
568
570
  if (this.csLang) return;
569
571
  try {
570
572
  const wasmPath = await this.grammarLoader.ensureGrammar("csharp");
571
- this.csLang = await TreeSitter.Language.load(wasmPath);
573
+ this.csLang = await _ts.Language.load(wasmPath);
572
574
  } catch (err) {
573
575
  const { logger: logger2 } = await Promise.resolve().then(() => (init_logger(), logger_exports));
574
576
  logger2.warn("C# grammar unavailable", { detail: err instanceof Error ? err.message : String(err) });
@@ -578,7 +580,7 @@ var ASTParser = class {
578
580
  if (this.rubyLang) return;
579
581
  try {
580
582
  const wasmPath = await this.grammarLoader.ensureGrammar("ruby");
581
- this.rubyLang = await TreeSitter.Language.load(wasmPath);
583
+ this.rubyLang = await _ts.Language.load(wasmPath);
582
584
  } catch (err) {
583
585
  const { logger: logger2 } = await Promise.resolve().then(() => (init_logger(), logger_exports));
584
586
  logger2.warn("Ruby grammar unavailable", { detail: err instanceof Error ? err.message : String(err) });
@@ -588,7 +590,7 @@ var ASTParser = class {
588
590
  if (this.kotlinLang) return;
589
591
  try {
590
592
  const wasmPath = await this.grammarLoader.ensureGrammar("kotlin");
591
- this.kotlinLang = await TreeSitter.Language.load(wasmPath);
593
+ this.kotlinLang = await _ts.Language.load(wasmPath);
592
594
  } catch (err) {
593
595
  const { logger: logger2 } = await Promise.resolve().then(() => (init_logger(), logger_exports));
594
596
  logger2.warn("Kotlin grammar unavailable", { detail: err instanceof Error ? err.message : String(err) });
@@ -598,7 +600,7 @@ var ASTParser = class {
598
600
  if (this.swiftLang) return;
599
601
  try {
600
602
  const wasmPath = await this.grammarLoader.ensureGrammar("swift");
601
- this.swiftLang = await TreeSitter.Language.load(wasmPath);
603
+ this.swiftLang = await _ts.Language.load(wasmPath);
602
604
  } catch (err) {
603
605
  const { logger: logger2 } = await Promise.resolve().then(() => (init_logger(), logger_exports));
604
606
  logger2.warn("Swift grammar unavailable", { detail: err instanceof Error ? err.message : String(err) });
@@ -608,7 +610,7 @@ var ASTParser = class {
608
610
  if (this.phpLang) return;
609
611
  try {
610
612
  const wasmPath = await this.grammarLoader.ensureGrammar("php");
611
- this.phpLang = await TreeSitter.Language.load(wasmPath);
613
+ this.phpLang = await _ts.Language.load(wasmPath);
612
614
  } catch (err) {
613
615
  const { logger: logger2 } = await Promise.resolve().then(() => (init_logger(), logger_exports));
614
616
  logger2.warn("PHP grammar unavailable", { detail: err instanceof Error ? err.message : String(err) });
@@ -618,7 +620,7 @@ var ASTParser = class {
618
620
  if (this.dartLang) return;
619
621
  try {
620
622
  const wasmPath = await this.grammarLoader.ensureGrammar("dart");
621
- this.dartLang = await TreeSitter.Language.load(wasmPath);
623
+ this.dartLang = await _ts.Language.load(wasmPath);
622
624
  } catch (err) {
623
625
  const { logger: logger2 } = await Promise.resolve().then(() => (init_logger(), logger_exports));
624
626
  logger2.warn("Dart grammar unavailable", { detail: err instanceof Error ? err.message : String(err) });
@@ -3133,8 +3135,8 @@ var CoChangeIndex = class _CoChangeIndex {
3133
3135
  if (event.isBulk || event.isMerge) return;
3134
3136
  const paths = event.files.map((f) => f.path);
3135
3137
  if (paths.length === 0) return;
3136
- for (const path43 of paths) {
3137
- this.nodeCounts.set(path43, (this.nodeCounts.get(path43) ?? 0) + 1);
3138
+ for (const path44 of paths) {
3139
+ this.nodeCounts.set(path44, (this.nodeCounts.get(path44) ?? 0) + 1);
3138
3140
  }
3139
3141
  for (let i = 0; i < paths.length; i++) {
3140
3142
  for (let j = i + 1; j < paths.length; j++) {
@@ -3281,8 +3283,8 @@ var ChurnIndex = class _ChurnIndex {
3281
3283
  */
3282
3284
  snapshot() {
3283
3285
  const nodes = {};
3284
- for (const [path43, raw] of this.nodes) {
3285
- nodes[path43] = {
3286
+ for (const [path44, raw] of this.nodes) {
3287
+ nodes[path44] = {
3286
3288
  commits: raw.commits,
3287
3289
  churnLines: raw.churnLines,
3288
3290
  bugCommits: raw.bugCommits,
@@ -3297,8 +3299,8 @@ var ChurnIndex = class _ChurnIndex {
3297
3299
  */
3298
3300
  static load(s) {
3299
3301
  const idx = new _ChurnIndex();
3300
- for (const [path43, raw] of Object.entries(s.nodes)) {
3301
- idx.nodes.set(path43, {
3302
+ for (const [path44, raw] of Object.entries(s.nodes)) {
3303
+ idx.nodes.set(path44, {
3302
3304
  commits: raw.commits,
3303
3305
  churnLines: raw.churnLines,
3304
3306
  bugCommits: raw.bugCommits,
@@ -3311,8 +3313,8 @@ var ChurnIndex = class _ChurnIndex {
3311
3313
  // -------------------------------------------------------------------------
3312
3314
  // Private helpers
3313
3315
  // -------------------------------------------------------------------------
3314
- getOrCreate(path43) {
3315
- const existing = this.nodes.get(path43);
3316
+ getOrCreate(path44) {
3317
+ const existing = this.nodes.get(path44);
3316
3318
  if (existing !== void 0) return existing;
3317
3319
  const fresh = {
3318
3320
  commits: 0,
@@ -3321,7 +3323,7 @@ var ChurnIndex = class _ChurnIndex {
3321
3323
  authorCounts: {},
3322
3324
  lastTouch: 0
3323
3325
  };
3324
- this.nodes.set(path43, fresh);
3326
+ this.nodes.set(path44, fresh);
3325
3327
  return fresh;
3326
3328
  }
3327
3329
  };
@@ -3402,12 +3404,12 @@ var OwnershipIndex = class _OwnershipIndex {
3402
3404
  */
3403
3405
  snapshot() {
3404
3406
  const nodes = {};
3405
- for (const [path43, raw] of this.nodes) {
3407
+ for (const [path44, raw] of this.nodes) {
3406
3408
  const authorWeights = {};
3407
3409
  for (const [email, entry] of Object.entries(raw.authorWeights)) {
3408
3410
  authorWeights[email] = { ...entry };
3409
3411
  }
3410
- nodes[path43] = { authorWeights, lastTouch: raw.lastTouch };
3412
+ nodes[path44] = { authorWeights, lastTouch: raw.lastTouch };
3411
3413
  }
3412
3414
  return { version: 1, nodes };
3413
3415
  }
@@ -3416,23 +3418,23 @@ var OwnershipIndex = class _OwnershipIndex {
3416
3418
  */
3417
3419
  static load(s) {
3418
3420
  const idx = new _OwnershipIndex();
3419
- for (const [path43, raw] of Object.entries(s.nodes)) {
3421
+ for (const [path44, raw] of Object.entries(s.nodes)) {
3420
3422
  const authorWeights = {};
3421
3423
  for (const [email, entry] of Object.entries(raw.authorWeights)) {
3422
3424
  authorWeights[email] = { ...entry };
3423
3425
  }
3424
- idx.nodes.set(path43, { authorWeights, lastTouch: raw.lastTouch });
3426
+ idx.nodes.set(path44, { authorWeights, lastTouch: raw.lastTouch });
3425
3427
  }
3426
3428
  return idx;
3427
3429
  }
3428
3430
  // -------------------------------------------------------------------------
3429
3431
  // Private helpers
3430
3432
  // -------------------------------------------------------------------------
3431
- getOrCreate(path43) {
3432
- const existing = this.nodes.get(path43);
3433
+ getOrCreate(path44) {
3434
+ const existing = this.nodes.get(path44);
3433
3435
  if (existing !== void 0) return existing;
3434
3436
  const fresh = { authorWeights: {}, lastTouch: 0 };
3435
- this.nodes.set(path43, fresh);
3437
+ this.nodes.set(path44, fresh);
3436
3438
  return fresh;
3437
3439
  }
3438
3440
  };
@@ -4670,8 +4672,8 @@ function getErrorMap() {
4670
4672
 
4671
4673
  // ../../node_modules/zod/v3/helpers/parseUtil.js
4672
4674
  var makeIssue = (params) => {
4673
- const { data, path: path43, errorMaps, issueData } = params;
4674
- const fullPath = [...path43, ...issueData.path || []];
4675
+ const { data, path: path44, errorMaps, issueData } = params;
4676
+ const fullPath = [...path44, ...issueData.path || []];
4675
4677
  const fullIssue = {
4676
4678
  ...issueData,
4677
4679
  path: fullPath
@@ -4787,11 +4789,11 @@ var errorUtil;
4787
4789
 
4788
4790
  // ../../node_modules/zod/v3/types.js
4789
4791
  var ParseInputLazyPath = class {
4790
- constructor(parent, value, path43, key) {
4792
+ constructor(parent, value, path44, key) {
4791
4793
  this._cachedPath = [];
4792
4794
  this.parent = parent;
4793
4795
  this.data = value;
4794
- this._path = path43;
4796
+ this._path = path44;
4795
4797
  this._key = key;
4796
4798
  }
4797
4799
  get path() {
@@ -11311,8 +11313,17 @@ function markAliasSent(home) {
11311
11313
  }
11312
11314
 
11313
11315
  // ../../packages/core/src/license/telemetry.ts
11314
- var TELEMETRY_DISABLED = process.env["CTXLOOM_NO_TELEMETRY"] === "1" || process.env["DO_NOT_TRACK"] === "1";
11315
- var CTXLOOM_VERSION = "1.1.5".length > 0 ? "1.1.5" : "dev";
11316
+ function resolveTelemetryLevel() {
11317
+ if (process.env["CTXLOOM_NO_TELEMETRY"] === "1" || process.env["DO_NOT_TRACK"] === "1") {
11318
+ return "off";
11319
+ }
11320
+ const raw = process.env["CTXLOOM_TELEMETRY_LEVEL"]?.toLowerCase();
11321
+ if (raw === "off" || raw === "error" || raw === "all") return raw;
11322
+ return "all";
11323
+ }
11324
+ var TELEMETRY_LEVEL = resolveTelemetryLevel();
11325
+ var TELEMETRY_DISABLED = TELEMETRY_LEVEL === "off";
11326
+ var CTXLOOM_VERSION = "1.2.1".length > 0 ? "1.2.1" : "dev";
11316
11327
  var POSTHOG_HOST = "https://eu.i.posthog.com";
11317
11328
  var POSTHOG_KEY = process.env["POSTHOG_API_KEY"] ?? (true ? "phc_CiDkmFLcZ2K6uCpcoSUQLmFrnnUvsyXGhSxopX5TVKE6" : "");
11318
11329
  var SENTRY_DSN = process.env["SENTRY_DSN"] ?? (true ? "https://81c94a0f04a8e242dee493ac1e17f733@o4508531702497280.ingest.de.sentry.io/4511256875368528" : "");
@@ -11327,7 +11338,7 @@ function resolveDistinctId() {
11327
11338
  }
11328
11339
  }
11329
11340
  function track(event, props = {}) {
11330
- if (TELEMETRY_DISABLED || !POSTHOG_KEY) return;
11341
+ if (TELEMETRY_LEVEL !== "all" || !POSTHOG_KEY) return;
11331
11342
  const record = resolveDistinctId();
11332
11343
  if (!record) return;
11333
11344
  void sendPostHog(event, record.id, props);
@@ -11443,23 +11454,28 @@ function parseStack(stack) {
11443
11454
  }).filter((f) => f !== null).slice(0, 20);
11444
11455
  }
11445
11456
 
11457
+ // ../../packages/core/src/license/TelemetryNotice.ts
11458
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "fs";
11459
+ import path33 from "path";
11460
+ import os5 from "os";
11461
+
11446
11462
  // ../../packages/core/src/server/ProjectState.ts
11447
- import path34 from "path";
11463
+ import path35 from "path";
11448
11464
 
11449
11465
  // ../../packages/core/src/server/projectId.ts
11450
11466
  import crypto5 from "crypto";
11451
- import path33 from "path";
11467
+ import path34 from "path";
11452
11468
 
11453
11469
  // ../../packages/core/src/server/ProjectStateManager.ts
11454
11470
  init_logger();
11455
11471
 
11456
11472
  // ../../packages/core/src/server/resolveProjectRoot.ts
11457
11473
  import fs29 from "fs";
11458
- import path35 from "path";
11474
+ import path36 from "path";
11459
11475
 
11460
11476
  // server/loader.ts
11461
11477
  async function loadContext(root) {
11462
- const absRoot = path36.resolve(root);
11478
+ const absRoot = path37.resolve(root);
11463
11479
  const overlay = new GitOverlayStore(absRoot);
11464
11480
  const gitEnabled = await overlay.loadSnapshot();
11465
11481
  const graph = new DependencyGraph();
@@ -11713,20 +11729,20 @@ function buildOwnershipRouter(ctx) {
11713
11729
  // server/routes/file.ts
11714
11730
  import { Router as Router7 } from "express";
11715
11731
  import fs30 from "fs/promises";
11716
- import path37 from "path";
11732
+ import path38 from "path";
11717
11733
  function buildFileRouter(ctx) {
11718
11734
  const router = Router7();
11719
11735
  router.get("/", async (req, res) => {
11720
11736
  const rel = req.query.path;
11721
11737
  if (!rel) return res.status(400).json({ error: "missing path" });
11722
- const abs = path37.resolve(ctx.root, rel);
11723
- const rootBoundary = ctx.root.endsWith(path37.sep) ? ctx.root : ctx.root + path37.sep;
11738
+ const abs = path38.resolve(ctx.root, rel);
11739
+ const rootBoundary = ctx.root.endsWith(path38.sep) ? ctx.root : ctx.root + path38.sep;
11724
11740
  if (abs !== ctx.root && !abs.startsWith(rootBoundary)) {
11725
11741
  return res.status(403).json({ error: "forbidden" });
11726
11742
  }
11727
11743
  try {
11728
11744
  const content = await fs30.readFile(abs, "utf-8");
11729
- const ext = path37.extname(abs).slice(1);
11745
+ const ext = path38.extname(abs).slice(1);
11730
11746
  res.json({ content, lines: content.split("\n").length, ext });
11731
11747
  } catch {
11732
11748
  res.status(404).json({ error: "not found" });
@@ -11738,7 +11754,7 @@ function buildFileRouter(ctx) {
11738
11754
  // server/routes/open.ts
11739
11755
  import { Router as Router8 } from "express";
11740
11756
  import { execFile as execFile2 } from "child_process";
11741
- import path38 from "path";
11757
+ import path39 from "path";
11742
11758
  function tryOpen(bin, abs) {
11743
11759
  return new Promise((resolve) => {
11744
11760
  execFile2(bin, [abs], { timeout: 5e3 }, (err) => resolve(!err));
@@ -11749,8 +11765,8 @@ function buildOpenRouter(ctx) {
11749
11765
  router.post("/", async (req, res) => {
11750
11766
  const rel = req.body?.path;
11751
11767
  if (!rel || typeof rel !== "string") return res.status(400).json({ error: "missing path" });
11752
- const abs = path38.resolve(ctx.root, rel);
11753
- const rootBoundary = ctx.root.endsWith(path38.sep) ? ctx.root : ctx.root + path38.sep;
11768
+ const abs = path39.resolve(ctx.root, rel);
11769
+ const rootBoundary = ctx.root.endsWith(path39.sep) ? ctx.root : ctx.root + path39.sep;
11754
11770
  if (abs !== ctx.root && !abs.startsWith(rootBoundary)) {
11755
11771
  return res.status(403).json({ error: "forbidden" });
11756
11772
  }
@@ -11762,7 +11778,7 @@ function buildOpenRouter(ctx) {
11762
11778
 
11763
11779
  // server/routes/tokens.ts
11764
11780
  import { Router as Router9 } from "express";
11765
- import path39 from "path";
11781
+ import path40 from "path";
11766
11782
  import fs31 from "fs";
11767
11783
  var CHARS_PER_TOKEN = 4;
11768
11784
  var cache = null;
@@ -11778,7 +11794,7 @@ function buildTokensRouter(ctx) {
11778
11794
  let fullChars = 0;
11779
11795
  let skeletonChars = 0;
11780
11796
  for (const file of files) {
11781
- const absPath = path39.join(ctx.root, file);
11797
+ const absPath = path40.join(ctx.root, file);
11782
11798
  try {
11783
11799
  const content = fs31.readFileSync(absPath, "utf-8");
11784
11800
  fullChars += content.length;
@@ -11881,21 +11897,21 @@ function buildFileTrendsRouter(ctx) {
11881
11897
 
11882
11898
  // server/routes/projects.ts
11883
11899
  import { Router as Router12 } from "express";
11884
- import path41 from "path";
11900
+ import path42 from "path";
11885
11901
 
11886
11902
  // server/projects.ts
11887
- import { existsSync as existsSync3, readFileSync as readFileSync4 } from "fs";
11888
- import os5 from "os";
11889
- import path40 from "path";
11903
+ import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
11904
+ import os6 from "os";
11905
+ import path41 from "path";
11890
11906
  import crypto6 from "crypto";
11891
- var HOME = os5.homedir();
11892
- var REGISTRY_PATH = path40.join(HOME, ".ctxloom", "repos.json");
11907
+ var HOME = os6.homedir();
11908
+ var REGISTRY_PATH = path41.join(HOME, ".ctxloom", "repos.json");
11893
11909
  function slugFor(root) {
11894
- const abs = path40.resolve(root);
11910
+ const abs = path41.resolve(root);
11895
11911
  return crypto6.createHash("sha1").update(abs).digest("hex").slice(0, 12);
11896
11912
  }
11897
11913
  function readRegistry() {
11898
- if (!existsSync3(REGISTRY_PATH)) return [];
11914
+ if (!existsSync4(REGISTRY_PATH)) return [];
11899
11915
  try {
11900
11916
  const raw = readFileSync4(REGISTRY_PATH, "utf-8");
11901
11917
  const parsed = JSON.parse(raw);
@@ -11906,27 +11922,27 @@ function readRegistry() {
11906
11922
  }
11907
11923
  }
11908
11924
  function listProjects(defaultRoot) {
11909
- const absDefault = path40.resolve(defaultRoot);
11925
+ const absDefault = path41.resolve(defaultRoot);
11910
11926
  const out = [
11911
11927
  {
11912
11928
  slug: slugFor(absDefault),
11913
- name: path40.basename(absDefault) || absDefault,
11929
+ name: path41.basename(absDefault) || absDefault,
11914
11930
  root: absDefault,
11915
11931
  isDefault: true,
11916
- hasSnapshot: existsSync3(path40.join(absDefault, ".ctxloom"))
11932
+ hasSnapshot: existsSync4(path41.join(absDefault, ".ctxloom"))
11917
11933
  }
11918
11934
  ];
11919
11935
  const seen = /* @__PURE__ */ new Set([absDefault]);
11920
11936
  for (const entry of readRegistry()) {
11921
- const abs = path40.resolve(entry.root);
11937
+ const abs = path41.resolve(entry.root);
11922
11938
  if (seen.has(abs)) continue;
11923
11939
  seen.add(abs);
11924
11940
  const item = {
11925
11941
  slug: slugFor(abs),
11926
- name: entry.name ?? (path40.basename(abs) || abs),
11942
+ name: entry.name ?? (path41.basename(abs) || abs),
11927
11943
  root: abs,
11928
11944
  isDefault: false,
11929
- hasSnapshot: existsSync3(path40.join(abs, ".ctxloom"))
11945
+ hasSnapshot: existsSync4(path41.join(abs, ".ctxloom"))
11930
11946
  };
11931
11947
  if (entry.alias !== void 0) item.alias = entry.alias;
11932
11948
  out.push(item);
@@ -11979,7 +11995,7 @@ function buildProjectsRouter(deps) {
11979
11995
  } catch (err) {
11980
11996
  const detail = err instanceof Error ? err.message : String(err);
11981
11997
  res.status(500).json({
11982
- error: `failed to switch to ${path41.basename(target.root)}: ${detail}`
11998
+ error: `failed to switch to ${path42.basename(target.root)}: ${detail}`
11983
11999
  });
11984
12000
  }
11985
12001
  });
@@ -12036,7 +12052,7 @@ function buildTelemetryRouter() {
12036
12052
  }
12037
12053
 
12038
12054
  // server/index.ts
12039
- var __dirname2 = path42.dirname(fileURLToPath2(import.meta.url));
12055
+ var __dirname2 = path43.dirname(fileURLToPath2(import.meta.url));
12040
12056
  async function startDashboard(options) {
12041
12057
  const { root, port, open } = options;
12042
12058
  console.log(`ctxloom dashboard \u2014 loading context from ${root}...`);
@@ -12095,7 +12111,7 @@ async function startDashboard(options) {
12095
12111
  }
12096
12112
  activeWatcher = null;
12097
12113
  }
12098
- const snapshotDir = path42.join(targetRoot, ".ctxloom");
12114
+ const snapshotDir = path43.join(targetRoot, ".ctxloom");
12099
12115
  try {
12100
12116
  activeWatcher = fs32.watch(snapshotDir, (_event, filename) => {
12101
12117
  if (!filename || !filename.includes("snapshot")) return;
@@ -12126,12 +12142,12 @@ async function startDashboard(options) {
12126
12142
  attachSnapshotWatcher(newRoot);
12127
12143
  }
12128
12144
  }));
12129
- const clientDist = path42.join(__dirname2, "../dashboard/client");
12130
- const clientDistExists = fs32.existsSync(path42.join(clientDist, "index.html"));
12145
+ const clientDist = path43.join(__dirname2, "../dashboard/client");
12146
+ const clientDistExists = fs32.existsSync(path43.join(clientDist, "index.html"));
12131
12147
  if (clientDistExists) {
12132
12148
  app.use(express.static(clientDist, { dotfiles: "allow" }));
12133
12149
  app.get(/.*/, (_req, res) => {
12134
- res.sendFile(path42.join(clientDist, "index.html"), { dotfiles: "allow" });
12150
+ res.sendFile(path43.join(clientDist, "index.html"), { dotfiles: "allow" });
12135
12151
  });
12136
12152
  } else {
12137
12153
  app.get(/^\/(?!api\/).*/, (_req, res) => {
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  VectorStore
3
- } from "./chunk-NEHYSE2Y.js";
3
+ } from "./chunk-DVI2RWJR.js";
4
4
  import "./chunk-TYDMSHV7.js";
5
5
  export {
6
6
  VectorStore
7
7
  };
8
- //# sourceMappingURL=VectorStore-HOSUSLV7.js.map
8
+ //# sourceMappingURL=VectorStore-XYLGD37W.js.map
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  VectorStore
3
- } from "./chunk-NEHYSE2Y.js";
3
+ } from "./chunk-DVI2RWJR.js";
4
4
  import {
5
5
  collectFiles,
6
6
  generateEmbedding
7
- } from "./chunk-VR6PNQYH.js";
7
+ } from "./chunk-NMXQC5CG.js";
8
8
  import {
9
9
  logger
10
10
  } from "./chunk-TYDMSHV7.js";
@@ -14,7 +14,6 @@ import fs6 from "fs";
14
14
  import path6 from "path";
15
15
 
16
16
  // packages/core/src/ast/ASTParser.ts
17
- import * as TreeSitter from "web-tree-sitter";
18
17
  import fs2 from "fs";
19
18
  import path2 from "path";
20
19
  import { fileURLToPath } from "url";
@@ -264,6 +263,11 @@ function extractNotebookLanguage(content) {
264
263
  }
265
264
 
266
265
  // packages/core/src/ast/ASTParser.ts
266
+ var _ts = null;
267
+ async function loadTreeSitter() {
268
+ if (_ts === null) _ts = await import("web-tree-sitter");
269
+ return _ts;
270
+ }
267
271
  var __dirname = path2.dirname(fileURLToPath(import.meta.url));
268
272
  function findWasmDir() {
269
273
  const candidates = [
@@ -319,7 +323,7 @@ var ASTParser = class {
319
323
  getParser(language) {
320
324
  let parser = this.parserCache.get(language);
321
325
  if (parser) return parser;
322
- parser = new TreeSitter.Parser();
326
+ parser = new _ts.Parser();
323
327
  parser.setLanguage(language);
324
328
  this.parserCache.set(language, parser);
325
329
  return parser;
@@ -327,7 +331,8 @@ var ASTParser = class {
327
331
  dartLang = null;
328
332
  grammarLoader = new GrammarLoader();
329
333
  async init() {
330
- await TreeSitter.Parser.init({
334
+ const TS = await loadTreeSitter();
335
+ await TS.Parser.init({
331
336
  locateFile: () => path2.join(WASM_DIR, "tree-sitter.wasm")
332
337
  });
333
338
  const grammarCandidates = [
@@ -349,7 +354,7 @@ var ASTParser = class {
349
354
  if (!grammarPath) {
350
355
  throw new Error("Could not locate tree-sitter-typescript.wasm grammar file");
351
356
  }
352
- this.tsLang = await TreeSitter.Language.load(grammarPath);
357
+ this.tsLang = await _ts.Language.load(grammarPath);
353
358
  }
354
359
  /**
355
360
  * Load Python grammar on demand. Downloads and caches WASM if needed.
@@ -358,7 +363,7 @@ var ASTParser = class {
358
363
  if (this.pyLang) return;
359
364
  try {
360
365
  const wasmPath = await this.grammarLoader.ensureGrammar("python");
361
- this.pyLang = await TreeSitter.Language.load(wasmPath);
366
+ this.pyLang = await _ts.Language.load(wasmPath);
362
367
  } catch (err) {
363
368
  const { logger: logger2 } = await import("./logger-PDXPCKJ6.js");
364
369
  logger2.warn("Python grammar unavailable", { detail: err instanceof Error ? err.message : String(err) });
@@ -371,7 +376,7 @@ var ASTParser = class {
371
376
  if (this.goLang) return;
372
377
  try {
373
378
  const wasmPath = await this.grammarLoader.ensureGrammar("go");
374
- this.goLang = await TreeSitter.Language.load(wasmPath);
379
+ this.goLang = await _ts.Language.load(wasmPath);
375
380
  } catch (err) {
376
381
  const { logger: logger2 } = await import("./logger-PDXPCKJ6.js");
377
382
  logger2.warn("Go grammar unavailable", { detail: err instanceof Error ? err.message : String(err) });
@@ -384,7 +389,7 @@ var ASTParser = class {
384
389
  if (this.rustLang) return;
385
390
  try {
386
391
  const wasmPath = await this.grammarLoader.ensureGrammar("rust");
387
- this.rustLang = await TreeSitter.Language.load(wasmPath);
392
+ this.rustLang = await _ts.Language.load(wasmPath);
388
393
  } catch (err) {
389
394
  const { logger: logger2 } = await import("./logger-PDXPCKJ6.js");
390
395
  logger2.warn("Rust grammar unavailable", { detail: err instanceof Error ? err.message : String(err) });
@@ -397,7 +402,7 @@ var ASTParser = class {
397
402
  if (this.javaLang) return;
398
403
  try {
399
404
  const wasmPath = await this.grammarLoader.ensureGrammar("java");
400
- this.javaLang = await TreeSitter.Language.load(wasmPath);
405
+ this.javaLang = await _ts.Language.load(wasmPath);
401
406
  } catch (err) {
402
407
  const { logger: logger2 } = await import("./logger-PDXPCKJ6.js");
403
408
  logger2.warn("Java grammar unavailable", { detail: err instanceof Error ? err.message : String(err) });
@@ -407,7 +412,7 @@ var ASTParser = class {
407
412
  if (this.csLang) return;
408
413
  try {
409
414
  const wasmPath = await this.grammarLoader.ensureGrammar("csharp");
410
- this.csLang = await TreeSitter.Language.load(wasmPath);
415
+ this.csLang = await _ts.Language.load(wasmPath);
411
416
  } catch (err) {
412
417
  const { logger: logger2 } = await import("./logger-PDXPCKJ6.js");
413
418
  logger2.warn("C# grammar unavailable", { detail: err instanceof Error ? err.message : String(err) });
@@ -417,7 +422,7 @@ var ASTParser = class {
417
422
  if (this.rubyLang) return;
418
423
  try {
419
424
  const wasmPath = await this.grammarLoader.ensureGrammar("ruby");
420
- this.rubyLang = await TreeSitter.Language.load(wasmPath);
425
+ this.rubyLang = await _ts.Language.load(wasmPath);
421
426
  } catch (err) {
422
427
  const { logger: logger2 } = await import("./logger-PDXPCKJ6.js");
423
428
  logger2.warn("Ruby grammar unavailable", { detail: err instanceof Error ? err.message : String(err) });
@@ -427,7 +432,7 @@ var ASTParser = class {
427
432
  if (this.kotlinLang) return;
428
433
  try {
429
434
  const wasmPath = await this.grammarLoader.ensureGrammar("kotlin");
430
- this.kotlinLang = await TreeSitter.Language.load(wasmPath);
435
+ this.kotlinLang = await _ts.Language.load(wasmPath);
431
436
  } catch (err) {
432
437
  const { logger: logger2 } = await import("./logger-PDXPCKJ6.js");
433
438
  logger2.warn("Kotlin grammar unavailable", { detail: err instanceof Error ? err.message : String(err) });
@@ -437,7 +442,7 @@ var ASTParser = class {
437
442
  if (this.swiftLang) return;
438
443
  try {
439
444
  const wasmPath = await this.grammarLoader.ensureGrammar("swift");
440
- this.swiftLang = await TreeSitter.Language.load(wasmPath);
445
+ this.swiftLang = await _ts.Language.load(wasmPath);
441
446
  } catch (err) {
442
447
  const { logger: logger2 } = await import("./logger-PDXPCKJ6.js");
443
448
  logger2.warn("Swift grammar unavailable", { detail: err instanceof Error ? err.message : String(err) });
@@ -447,7 +452,7 @@ var ASTParser = class {
447
452
  if (this.phpLang) return;
448
453
  try {
449
454
  const wasmPath = await this.grammarLoader.ensureGrammar("php");
450
- this.phpLang = await TreeSitter.Language.load(wasmPath);
455
+ this.phpLang = await _ts.Language.load(wasmPath);
451
456
  } catch (err) {
452
457
  const { logger: logger2 } = await import("./logger-PDXPCKJ6.js");
453
458
  logger2.warn("PHP grammar unavailable", { detail: err instanceof Error ? err.message : String(err) });
@@ -457,7 +462,7 @@ var ASTParser = class {
457
462
  if (this.dartLang) return;
458
463
  try {
459
464
  const wasmPath = await this.grammarLoader.ensureGrammar("dart");
460
- this.dartLang = await TreeSitter.Language.load(wasmPath);
465
+ this.dartLang = await _ts.Language.load(wasmPath);
461
466
  } catch (err) {
462
467
  const { logger: logger2 } = await import("./logger-PDXPCKJ6.js");
463
468
  logger2.warn("Dart grammar unavailable", { detail: err instanceof Error ? err.message : String(err) });
@@ -3406,8 +3411,8 @@ var CoChangeIndex = class _CoChangeIndex {
3406
3411
  if (event.isBulk || event.isMerge) return;
3407
3412
  const paths = event.files.map((f) => f.path);
3408
3413
  if (paths.length === 0) return;
3409
- for (const path34 of paths) {
3410
- this.nodeCounts.set(path34, (this.nodeCounts.get(path34) ?? 0) + 1);
3414
+ for (const path35 of paths) {
3415
+ this.nodeCounts.set(path35, (this.nodeCounts.get(path35) ?? 0) + 1);
3411
3416
  }
3412
3417
  for (let i = 0; i < paths.length; i++) {
3413
3418
  for (let j = i + 1; j < paths.length; j++) {
@@ -3554,8 +3559,8 @@ var ChurnIndex = class _ChurnIndex {
3554
3559
  */
3555
3560
  snapshot() {
3556
3561
  const nodes = {};
3557
- for (const [path34, raw] of this.nodes) {
3558
- nodes[path34] = {
3562
+ for (const [path35, raw] of this.nodes) {
3563
+ nodes[path35] = {
3559
3564
  commits: raw.commits,
3560
3565
  churnLines: raw.churnLines,
3561
3566
  bugCommits: raw.bugCommits,
@@ -3570,8 +3575,8 @@ var ChurnIndex = class _ChurnIndex {
3570
3575
  */
3571
3576
  static load(s) {
3572
3577
  const idx = new _ChurnIndex();
3573
- for (const [path34, raw] of Object.entries(s.nodes)) {
3574
- idx.nodes.set(path34, {
3578
+ for (const [path35, raw] of Object.entries(s.nodes)) {
3579
+ idx.nodes.set(path35, {
3575
3580
  commits: raw.commits,
3576
3581
  churnLines: raw.churnLines,
3577
3582
  bugCommits: raw.bugCommits,
@@ -3584,8 +3589,8 @@ var ChurnIndex = class _ChurnIndex {
3584
3589
  // -------------------------------------------------------------------------
3585
3590
  // Private helpers
3586
3591
  // -------------------------------------------------------------------------
3587
- getOrCreate(path34) {
3588
- const existing = this.nodes.get(path34);
3592
+ getOrCreate(path35) {
3593
+ const existing = this.nodes.get(path35);
3589
3594
  if (existing !== void 0) return existing;
3590
3595
  const fresh = {
3591
3596
  commits: 0,
@@ -3594,7 +3599,7 @@ var ChurnIndex = class _ChurnIndex {
3594
3599
  authorCounts: {},
3595
3600
  lastTouch: 0
3596
3601
  };
3597
- this.nodes.set(path34, fresh);
3602
+ this.nodes.set(path35, fresh);
3598
3603
  return fresh;
3599
3604
  }
3600
3605
  };
@@ -3675,12 +3680,12 @@ var OwnershipIndex = class _OwnershipIndex {
3675
3680
  */
3676
3681
  snapshot() {
3677
3682
  const nodes = {};
3678
- for (const [path34, raw] of this.nodes) {
3683
+ for (const [path35, raw] of this.nodes) {
3679
3684
  const authorWeights = {};
3680
3685
  for (const [email, entry] of Object.entries(raw.authorWeights)) {
3681
3686
  authorWeights[email] = { ...entry };
3682
3687
  }
3683
- nodes[path34] = { authorWeights, lastTouch: raw.lastTouch };
3688
+ nodes[path35] = { authorWeights, lastTouch: raw.lastTouch };
3684
3689
  }
3685
3690
  return { version: 1, nodes };
3686
3691
  }
@@ -3689,23 +3694,23 @@ var OwnershipIndex = class _OwnershipIndex {
3689
3694
  */
3690
3695
  static load(s) {
3691
3696
  const idx = new _OwnershipIndex();
3692
- for (const [path34, raw] of Object.entries(s.nodes)) {
3697
+ for (const [path35, raw] of Object.entries(s.nodes)) {
3693
3698
  const authorWeights = {};
3694
3699
  for (const [email, entry] of Object.entries(raw.authorWeights)) {
3695
3700
  authorWeights[email] = { ...entry };
3696
3701
  }
3697
- idx.nodes.set(path34, { authorWeights, lastTouch: raw.lastTouch });
3702
+ idx.nodes.set(path35, { authorWeights, lastTouch: raw.lastTouch });
3698
3703
  }
3699
3704
  return idx;
3700
3705
  }
3701
3706
  // -------------------------------------------------------------------------
3702
3707
  // Private helpers
3703
3708
  // -------------------------------------------------------------------------
3704
- getOrCreate(path34) {
3705
- const existing = this.nodes.get(path34);
3709
+ getOrCreate(path35) {
3710
+ const existing = this.nodes.get(path35);
3706
3711
  if (existing !== void 0) return existing;
3707
3712
  const fresh = { authorWeights: {}, lastTouch: 0 };
3708
- this.nodes.set(path34, fresh);
3713
+ this.nodes.set(path35, fresh);
3709
3714
  return fresh;
3710
3715
  }
3711
3716
  };
@@ -6781,7 +6786,7 @@ function registerFullTextSearchTool(registry, ctx) {
6781
6786
  const { query, mode, case_sensitive, limit, context_lines, project_root } = Schema23.parse(args);
6782
6787
  if (mode === "semantic") {
6783
6788
  try {
6784
- const { generateEmbedding: generateEmbedding2 } = await import("./embedder-MPDEA6P7.js");
6789
+ const { generateEmbedding: generateEmbedding2 } = await import("./embedder-5LMEYY4M.js");
6785
6790
  const store = await ctx.getStore(project_root);
6786
6791
  const embedding = await generateEmbedding2(query);
6787
6792
  const results = await store.search(embedding, limit);
@@ -6818,7 +6823,7 @@ function registerFullTextSearchTool(registry, ctx) {
6818
6823
  let merged = keywordResults.slice(0, limit);
6819
6824
  if (mode === "hybrid") {
6820
6825
  try {
6821
- const { generateEmbedding: generateEmbedding2 } = await import("./embedder-MPDEA6P7.js");
6826
+ const { generateEmbedding: generateEmbedding2 } = await import("./embedder-5LMEYY4M.js");
6822
6827
  const store = await ctx.getStore(project_root);
6823
6828
  const embedding = await generateEmbedding2(query);
6824
6829
  const vectorResults = await store.search(embedding, Math.ceil(limit / 2));
@@ -8837,8 +8842,20 @@ function markAliasSent(home) {
8837
8842
  }
8838
8843
 
8839
8844
  // packages/core/src/license/telemetry.ts
8840
- var TELEMETRY_DISABLED = process.env["CTXLOOM_NO_TELEMETRY"] === "1" || process.env["DO_NOT_TRACK"] === "1";
8841
- var CTXLOOM_VERSION = "1.1.5".length > 0 ? "1.1.5" : "dev";
8845
+ function resolveTelemetryLevel() {
8846
+ if (process.env["CTXLOOM_NO_TELEMETRY"] === "1" || process.env["DO_NOT_TRACK"] === "1") {
8847
+ return "off";
8848
+ }
8849
+ const raw = process.env["CTXLOOM_TELEMETRY_LEVEL"]?.toLowerCase();
8850
+ if (raw === "off" || raw === "error" || raw === "all") return raw;
8851
+ return "all";
8852
+ }
8853
+ var TELEMETRY_LEVEL = resolveTelemetryLevel();
8854
+ var TELEMETRY_DISABLED = TELEMETRY_LEVEL === "off";
8855
+ function getTelemetryLevel() {
8856
+ return TELEMETRY_LEVEL;
8857
+ }
8858
+ var CTXLOOM_VERSION = "1.2.1".length > 0 ? "1.2.1" : "dev";
8842
8859
  var POSTHOG_HOST = "https://eu.i.posthog.com";
8843
8860
  var POSTHOG_KEY = process.env["POSTHOG_API_KEY"] ?? (true ? "phc_CiDkmFLcZ2K6uCpcoSUQLmFrnnUvsyXGhSxopX5TVKE6" : "");
8844
8861
  var SENTRY_DSN = process.env["SENTRY_DSN"] ?? (true ? "https://81c94a0f04a8e242dee493ac1e17f733@o4508531702497280.ingest.de.sentry.io/4511256875368528" : "");
@@ -8853,7 +8870,7 @@ function resolveDistinctId() {
8853
8870
  }
8854
8871
  }
8855
8872
  function track(event, props = {}) {
8856
- if (TELEMETRY_DISABLED || !POSTHOG_KEY) return;
8873
+ if (TELEMETRY_LEVEL !== "all" || !POSTHOG_KEY) return;
8857
8874
  const record = resolveDistinctId();
8858
8875
  if (!record) return;
8859
8876
  void sendPostHog(event, record.id, props);
@@ -8969,14 +8986,32 @@ function parseStack(stack) {
8969
8986
  }).filter((f) => f !== null).slice(0, 20);
8970
8987
  }
8971
8988
 
8989
+ // packages/core/src/license/TelemetryNotice.ts
8990
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "fs";
8991
+ import path31 from "path";
8992
+ import os5 from "os";
8993
+ function noticePath(home) {
8994
+ return path31.join(home ?? os5.homedir(), ".ctxloom", "telemetry_notice_shown");
8995
+ }
8996
+ function shouldShowTelemetryNotice(home) {
8997
+ const filePath = noticePath(home);
8998
+ if (existsSync3(filePath)) return false;
8999
+ try {
9000
+ mkdirSync3(path31.dirname(filePath), { recursive: true });
9001
+ writeFileSync3(filePath, (/* @__PURE__ */ new Date()).toISOString(), { mode: 384 });
9002
+ } catch {
9003
+ }
9004
+ return true;
9005
+ }
9006
+
8972
9007
  // packages/core/src/server/ProjectState.ts
8973
- import path32 from "path";
9008
+ import path33 from "path";
8974
9009
 
8975
9010
  // packages/core/src/server/projectId.ts
8976
9011
  import crypto5 from "crypto";
8977
- import path31 from "path";
9012
+ import path32 from "path";
8978
9013
  function hashProjectRoot(absPath) {
8979
- const canonical = path31.resolve(absPath);
9014
+ const canonical = path32.resolve(absPath);
8980
9015
  return crypto5.createHash("sha256").update(canonical).digest("hex").slice(0, 16);
8981
9016
  }
8982
9017
 
@@ -8984,7 +9019,7 @@ function hashProjectRoot(absPath) {
8984
9019
  function createProjectState(projectRoot, opts = {}) {
8985
9020
  return {
8986
9021
  projectRoot,
8987
- dbPath: path32.join(projectRoot, ".ctxloom", "vectors.lancedb"),
9022
+ dbPath: path33.join(projectRoot, ".ctxloom", "vectors.lancedb"),
8988
9023
  pinned: opts.pinned ?? false,
8989
9024
  lastTouchedAt: Date.now(),
8990
9025
  vectorsInitialized: false,
@@ -9137,7 +9172,7 @@ var ProjectStateManager = class {
9137
9172
 
9138
9173
  // packages/core/src/server/resolveProjectRoot.ts
9139
9174
  import fs27 from "fs";
9140
- import path33 from "path";
9175
+ import path34 from "path";
9141
9176
  var PATH_SEPARATOR_PATTERN = /[/\\~]|^[A-Za-z]:/;
9142
9177
  function looksLikePath(value) {
9143
9178
  return PATH_SEPARATOR_PATTERN.test(value);
@@ -9162,9 +9197,9 @@ function resolvePathSafely(p, cwd) {
9162
9197
  let expanded = p;
9163
9198
  if (p === "~" || p.startsWith("~/")) {
9164
9199
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
9165
- expanded = p === "~" ? home : path33.join(home, p.slice(2));
9200
+ expanded = p === "~" ? home : path34.join(home, p.slice(2));
9166
9201
  }
9167
- return path33.isAbsolute(expanded) ? path33.resolve(expanded) : path33.resolve(cwd, expanded);
9202
+ return path34.isAbsolute(expanded) ? path34.resolve(expanded) : path34.resolve(cwd, expanded);
9168
9203
  }
9169
9204
  function realpathOrSame(p) {
9170
9205
  try {
@@ -9236,7 +9271,7 @@ function validateDefaultRoot(candidate) {
9236
9271
  } catch {
9237
9272
  return false;
9238
9273
  }
9239
- return PROJECT_MARKERS.some((m) => fs27.existsSync(path33.join(candidate, m)));
9274
+ return PROJECT_MARKERS.some((m) => fs27.existsSync(path34.join(candidate, m)));
9240
9275
  }
9241
9276
 
9242
9277
  // packages/core/src/server/structuredErrors.ts
@@ -9391,8 +9426,10 @@ export {
9391
9426
  startTrial,
9392
9427
  getOrCreateDistinctId,
9393
9428
  markAliasSent,
9429
+ getTelemetryLevel,
9394
9430
  track,
9395
9431
  captureError,
9432
+ shouldShowTelemetryNotice,
9396
9433
  hashProjectRoot,
9397
9434
  createProjectState,
9398
9435
  ensureVectorsInitialized,
@@ -9409,4 +9446,4 @@ export {
9409
9446
  FirstTouchTracker,
9410
9447
  EmittedOnceTracker
9411
9448
  };
9412
- //# sourceMappingURL=chunk-7BJDA4UT.js.map
9449
+ //# sourceMappingURL=chunk-3MA5KZYI.js.map
@@ -3,8 +3,6 @@ import {
3
3
  } from "./chunk-TYDMSHV7.js";
4
4
 
5
5
  // packages/core/src/db/VectorStore.ts
6
- import lancedb from "@lancedb/lancedb";
7
- import { makeArrowTable } from "@lancedb/lancedb";
8
6
  import path from "path";
9
7
  import fs from "fs";
10
8
  function sanitizeFilterPath(filePath) {
@@ -24,7 +22,9 @@ var VectorStore = class {
24
22
  if (!fs.existsSync(dir)) {
25
23
  fs.mkdirSync(dir, { recursive: true });
26
24
  }
27
- this.db = await lancedb.connect(this.dbPath);
25
+ const lancedb = await import("@lancedb/lancedb");
26
+ const { makeArrowTable } = lancedb;
27
+ this.db = await lancedb.default.connect(this.dbPath);
28
28
  const existingTables = await this.db.tableNames();
29
29
  if (existingTables.includes("code_embeddings")) {
30
30
  this.table = await this.db.openTable("code_embeddings");
@@ -141,4 +141,4 @@ var VectorStore = class {
141
141
  export {
142
142
  VectorStore
143
143
  };
144
- //# sourceMappingURL=chunk-NEHYSE2Y.js.map
144
+ //# sourceMappingURL=chunk-DVI2RWJR.js.map
@@ -3,7 +3,6 @@ import {
3
3
  } from "./chunk-TYDMSHV7.js";
4
4
 
5
5
  // packages/core/src/indexer/embedder.ts
6
- import { pipeline } from "@huggingface/transformers";
7
6
  import fs from "fs";
8
7
  import path from "path";
9
8
  var EMBEDDING_DIMENSION = 384;
@@ -13,6 +12,7 @@ var MIN_MODEL_BYTES = 80 * 1024 * 1024;
13
12
  var embedder = null;
14
13
  var embedderInitInFlight = null;
15
14
  async function loadEmbedder() {
15
+ const { pipeline } = await import("@huggingface/transformers");
16
16
  return await pipeline("feature-extraction", MODEL_ID, {
17
17
  dtype: "fp32"
18
18
  });
@@ -158,7 +158,7 @@ function collectFiles(dir, results = []) {
158
158
  return results;
159
159
  }
160
160
  async function indexDirectory(rootDir, onProgress) {
161
- const { VectorStore } = await import("./VectorStore-HOSUSLV7.js");
161
+ const { VectorStore } = await import("./VectorStore-XYLGD37W.js");
162
162
  const store = new VectorStore(path.join(rootDir, ".ctxloom", "vectors.lancedb"));
163
163
  await store.init();
164
164
  const files = collectFiles(rootDir);
@@ -211,4 +211,4 @@ export {
211
211
  collectFiles,
212
212
  indexDirectory
213
213
  };
214
- //# sourceMappingURL=chunk-VR6PNQYH.js.map
214
+ //# sourceMappingURL=chunk-NMXQC5CG.js.map
@@ -3,7 +3,7 @@ import {
3
3
  collectFiles,
4
4
  generateEmbedding,
5
5
  indexDirectory
6
- } from "./chunk-VR6PNQYH.js";
6
+ } from "./chunk-NMXQC5CG.js";
7
7
  import "./chunk-TYDMSHV7.js";
8
8
  export {
9
9
  EMBEDDING_DIMENSION,
@@ -11,4 +11,4 @@ export {
11
11
  generateEmbedding,
12
12
  indexDirectory
13
13
  };
14
- //# sourceMappingURL=embedder-MPDEA6P7.js.map
14
+ //# sourceMappingURL=embedder-5LMEYY4M.js.map
package/dist/index.js CHANGED
@@ -31,6 +31,7 @@ import {
31
31
  ensureVectorsInitialized,
32
32
  generateCODEOWNERS,
33
33
  getLicenseInfo,
34
+ getTelemetryLevel,
34
35
  hashProjectRoot,
35
36
  isActive,
36
37
  loadReviewConfig,
@@ -40,19 +41,20 @@ import {
40
41
  resolveProjectRoot,
41
42
  resolveViaGitHubApi,
42
43
  scoreReviewers,
44
+ shouldShowTelemetryNotice,
43
45
  startTrial,
44
46
  track,
45
47
  validateDefaultRoot,
46
48
  wrapWithIndexingEnvelope,
47
49
  writeCODEOWNERS
48
- } from "./chunk-7BJDA4UT.js";
50
+ } from "./chunk-3MA5KZYI.js";
49
51
  import {
50
52
  VectorStore
51
- } from "./chunk-NEHYSE2Y.js";
53
+ } from "./chunk-DVI2RWJR.js";
52
54
  import {
53
55
  generateEmbedding,
54
56
  indexDirectory
55
- } from "./chunk-VR6PNQYH.js";
57
+ } from "./chunk-NMXQC5CG.js";
56
58
  import {
57
59
  logger
58
60
  } from "./chunk-TYDMSHV7.js";
@@ -892,7 +894,7 @@ try {
892
894
  } catch {
893
895
  }
894
896
  var args = process.argv.slice(2);
895
- var ctxloomVersion = "1.1.5".length > 0 ? "1.1.5" : "dev";
897
+ var ctxloomVersion = "1.2.1".length > 0 ? "1.2.1" : "dev";
896
898
  if (args.includes("--version") || args.includes("-v")) {
897
899
  process.stdout.write(`ctxloom ${ctxloomVersion}
898
900
  `);
@@ -965,7 +967,7 @@ async function checkLicense() {
965
967
  if (command !== void 0 && LICENSE_GATE_BYPASS_COMMANDS.has(command)) return;
966
968
  const ciKey = process.env["CTXLOOM_LICENSE_KEY"];
967
969
  if (ciKey) {
968
- const { ApiClient } = await import("./src-WDWBHNN6.js");
970
+ const { ApiClient } = await import("./src-Z4U6JJBW.js");
969
971
  const client = new ApiClient(process.env["CTXLOOM_API_BASE"]);
970
972
  try {
971
973
  const result = await client.validate(ciKey, "ci-ephemeral");
@@ -1169,7 +1171,26 @@ async function runStatus() {
1169
1171
  ]));
1170
1172
  process.stdout.write("\n");
1171
1173
  }
1174
+ function maybePrintTelemetryNotice() {
1175
+ if (command === void 0) return;
1176
+ if (getTelemetryLevel() === "off") return;
1177
+ if (!shouldShowTelemetryNotice()) return;
1178
+ process.stderr.write(
1179
+ `
1180
+ ${style.dim("\u2500".repeat(60))}
1181
+ ${style.bold("ctxloom collects anonymous usage telemetry")} to improve the tool.
1182
+ No file contents, paths, or aliases are ever transmitted.
1183
+
1184
+ Disable with: ${style.highlight("CTXLOOM_NO_TELEMETRY=1")}
1185
+ Errors only: ${style.highlight("CTXLOOM_TELEMETRY_LEVEL=error")}
1186
+ Details: ${style.highlight("https://github.com/kodiii/ctxloom/blob/main/docs/TELEMETRY.md")}
1187
+ ${style.dim("\u2500".repeat(60))}
1188
+
1189
+ `
1190
+ );
1191
+ }
1172
1192
  async function main() {
1193
+ maybePrintTelemetryNotice();
1173
1194
  await checkLicense();
1174
1195
  switch (command) {
1175
1196
  case "trial": {
@@ -1318,7 +1339,7 @@ async function main() {
1318
1339
  process.exit(1);
1319
1340
  }
1320
1341
  if (alias !== void 0) {
1321
- const { validateAlias } = await import("./src-WDWBHNN6.js");
1342
+ const { validateAlias } = await import("./src-Z4U6JJBW.js");
1322
1343
  const v = validateAlias(alias);
1323
1344
  if (!v.ok) {
1324
1345
  console.error(`[ctxloom] Invalid alias: ${v.reason}`);
@@ -1562,7 +1583,7 @@ Suggested reviewers for ${files.length} file(s):`);
1562
1583
  process.stderr.write("[ctxloom] --limit must be a non-negative integer (0 for unlimited)\n");
1563
1584
  process.exit(2);
1564
1585
  }
1565
- const { loadRulesConfig, RulesChecker, formatText, formatJson, RulesConfigError } = await import("./src-WDWBHNN6.js");
1586
+ const { loadRulesConfig, RulesChecker, formatText, formatJson, RulesConfigError } = await import("./src-Z4U6JJBW.js");
1566
1587
  let config;
1567
1588
  try {
1568
1589
  config = await loadRulesConfig(root);
@@ -1586,7 +1607,7 @@ Suggested reviewers for ${files.length} file(s):`);
1586
1607
  }
1587
1608
  let graph;
1588
1609
  if (useSnapshot) {
1589
- const { DependencyGraph: DG } = await import("./src-WDWBHNN6.js");
1610
+ const { DependencyGraph: DG } = await import("./src-Z4U6JJBW.js");
1590
1611
  graph = new DG();
1591
1612
  const loaded = await graph.loadSnapshotOnly(root);
1592
1613
  if (!loaded) {
@@ -1595,7 +1616,7 @@ Suggested reviewers for ${files.length} file(s):`);
1595
1616
  }
1596
1617
  } else {
1597
1618
  process.stderr.write("[ctxloom] Building dependency graph...\n");
1598
- const { ASTParser: ASTParser2, DependencyGraph: DependencyGraph2 } = await import("./src-WDWBHNN6.js");
1619
+ const { ASTParser: ASTParser2, DependencyGraph: DependencyGraph2 } = await import("./src-Z4U6JJBW.js");
1599
1620
  let parser;
1600
1621
  try {
1601
1622
  parser = new ASTParser2();
@@ -68,6 +68,7 @@ import {
68
68
  getImpactRadius,
69
69
  getLicenseInfo,
70
70
  getOrCreateDistinctId,
71
+ getTelemetryLevel,
71
72
  hashProjectRoot,
72
73
  isActive,
73
74
  isSiloed,
@@ -93,22 +94,23 @@ import {
93
94
  scoreAll,
94
95
  scoreFromBreakdown,
95
96
  scoreReviewers,
97
+ shouldShowTelemetryNotice,
96
98
  startTrial,
97
99
  track,
98
100
  validateAlias,
99
101
  validateDefaultRoot,
100
102
  wrapWithIndexingEnvelope,
101
103
  writeCODEOWNERS
102
- } from "./chunk-7BJDA4UT.js";
104
+ } from "./chunk-3MA5KZYI.js";
103
105
  import {
104
106
  VectorStore
105
- } from "./chunk-NEHYSE2Y.js";
107
+ } from "./chunk-DVI2RWJR.js";
106
108
  import {
107
109
  EMBEDDING_DIMENSION,
108
110
  collectFiles,
109
111
  generateEmbedding,
110
112
  indexDirectory
111
- } from "./chunk-VR6PNQYH.js";
113
+ } from "./chunk-NMXQC5CG.js";
112
114
  import {
113
115
  logger
114
116
  } from "./chunk-TYDMSHV7.js";
@@ -186,6 +188,7 @@ export {
186
188
  getImpactRadius,
187
189
  getLicenseInfo,
188
190
  getOrCreateDistinctId,
191
+ getTelemetryLevel,
189
192
  hashProjectRoot,
190
193
  indexDirectory,
191
194
  isActive,
@@ -213,6 +216,7 @@ export {
213
216
  scoreAll,
214
217
  scoreFromBreakdown,
215
218
  scoreReviewers,
219
+ shouldShowTelemetryNotice,
216
220
  startTrial,
217
221
  track,
218
222
  validateAlias,
@@ -220,4 +224,4 @@ export {
220
224
  wrapWithIndexingEnvelope,
221
225
  writeCODEOWNERS
222
226
  };
223
- //# sourceMappingURL=src-WDWBHNN6.js.map
227
+ //# sourceMappingURL=src-Z4U6JJBW.js.map
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  VectorStore
3
- } from "../chunk-NEHYSE2Y.js";
3
+ } from "../chunk-DVI2RWJR.js";
4
4
  import {
5
5
  generateEmbedding
6
- } from "../chunk-VR6PNQYH.js";
6
+ } from "../chunk-NMXQC5CG.js";
7
7
  import "../chunk-TYDMSHV7.js";
8
8
 
9
9
  // packages/core/src/workers/indexerWorker.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ctxloom-pro",
3
- "version": "1.1.5",
3
+ "version": "1.2.1",
4
4
  "description": "ctxloom — The Universal Code Context Engine. A local-first MCP server providing intelligent code context via hybrid Vector + AST + Graph search with Skeletonization (92% token reduction).",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",