ctxloom-pro 1.0.17 → 1.0.19

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
@@ -70,19 +70,39 @@ Set `CTXLOOM_LICENSE_KEY` in your CI secrets. The key is validated on every run
70
70
 
71
71
  ### Manual MCP Configuration
72
72
 
73
+ This is what `ctxloom setup` writes for you. Match it by hand if you prefer:
74
+
73
75
  ```jsonc
74
- // ~/.claude/claude_desktop_config.json (or equivalent)
76
+ // Claude Code: ~/.claude.json
77
+ // Cursor: ~/.cursor/mcp.json
78
+ // Codex CLI: ~/.codex/mcp.json
79
+ // Kimi: ~/.kimi/mcp.json
80
+ // Claude Desktop: ~/Library/Application Support/Claude/claude_desktop_config.json
75
81
  {
76
82
  "mcpServers": {
77
83
  "ctxloom": {
78
- "command": "npx",
79
- "args": ["-y", "ctxloom"]
84
+ "command": "ctxloom",
85
+ "args": []
80
86
  }
81
87
  }
82
88
  }
83
89
  ```
84
90
 
85
- > If installed globally: `"command": "ctxloom"` with `"args": []`.
91
+ The MCP server inherits cwd from the host. Claude Code, Cursor, Codex, and Kimi all spawn it with the open project as cwd, so the right project is indexed automatically — no `CTXLOOM_ROOT` needed.
92
+
93
+ For hosts without a project concept (Claude Desktop, CI), set the root explicitly:
94
+
95
+ ```jsonc
96
+ {
97
+ "mcpServers": {
98
+ "ctxloom": {
99
+ "command": "ctxloom",
100
+ "args": [],
101
+ "env": { "CTXLOOM_ROOT": "/path/to/project" }
102
+ }
103
+ }
104
+ }
105
+ ```
86
106
  >
87
107
  > Pricing: **Pro** €9.90/mo or €99/yr (1 machine) · **Team** €19.90/mo or €199/yr (3 machines) · [ctxloom.com/pricing](https://ctxloom.com/pricing)
88
108
 
@@ -355,7 +375,7 @@ The tool reads `.ctxloom/rules.yml` and the live dependency graph on every call
355
375
 
356
376
  ---
357
377
 
358
- ## Tools — 32 total
378
+ ## Tools — 33 total
359
379
 
360
380
  ### Search & Context
361
381
 
@@ -388,6 +408,7 @@ The tool reads `.ctxloom/rules.yml` and the live dependency graph on every call
388
408
  | `ctx_get_call_graph` | Bidirectional call graph traversal with configurable depth |
389
409
  | `ctx_get_definition` | Symbol definition lookup via AST index |
390
410
  | `ctx_execution_flow` | DFS call graph traversal from entry point with cycle detection |
411
+ | `ctx_get_affected_flows` | Which flows are affected by changed files? Traces back to root callers, then forward — auto-detects from `git diff HEAD~1` |
391
412
  | `ctx_refactor_preview` | Read-only symbol rename diff preview — see every change before applying |
392
413
  | `ctx_apply_refactor` | Write symbol renames to disk atomically (supports dry_run) |
393
414
 
@@ -487,10 +508,10 @@ ctxloom --help Show help
487
508
  | Ruby | ✅ Relative paths | ✅ | ✅ |
488
509
  | Kotlin | ✅ Package imports | ✅ | ✅ |
489
510
  | Swift | ✅ Module imports | ✅ | ✅ |
490
- | PHP | ✅ PSR-4 + require_once | ✅ | |
491
- | Dart | ✅ Relative imports | ✅ | |
492
- | Vue SFC | ✅ Script block | ✅ | |
493
- | Jupyter Notebook | ✅ Python cell imports | ✅ | |
511
+ | PHP | ✅ PSR-4 + require_once | ✅ | |
512
+ | Dart | ✅ Relative imports | ✅ | |
513
+ | Vue SFC | ✅ Script block | ✅ | |
514
+ | Jupyter Notebook | ✅ Python cell imports | ✅ | |
494
515
 
495
516
  ---
496
517
 
@@ -501,7 +522,7 @@ ctxloom --help Show help
501
522
  │ MCP Interface │
502
523
  │ (Stdio transport) │
503
524
  ├──────────────────────────────────────────────────────────┤
504
- 32 Tools (ToolRegistry) │
525
+ 33 Tools (ToolRegistry) │
505
526
  │ Search · Graph Intelligence · Navigation · Review │
506
527
  ├──────────────────────────────────────────────────────────┤
507
528
  │ Context Engine │
@@ -543,18 +564,18 @@ See [`benchmarks/README.md`](benchmarks/README.md) for methodology and how to re
543
564
 
544
565
  ## Token reduction benchmarks
545
566
 
546
- Measured on real open-source repos with realistic review scenarios (skeletonization applies to JS/TS files; Python and Rust show graph indexing metrics only):
567
+ Full-source skeletonization on real open-source frameworks every TS/JS file (skipping tests, `.d.ts`, build output, minified vendor bundles).
547
568
 
548
- | Repository | Language | Files | Raw tokens | Skeleton tokens | Reduction |
549
- |---|---|---|---|---|---|
550
- | expressjs/express | JavaScript | 141 | ~4,646 | ~390 | **92%** |
551
- | sindresorhus/got | TypeScript | 71 | ~10,807 | ~742 | **93%** |
552
- | pallets/flask | Python | 83 | n/a | n/a | n/a |
553
- | SergioBenitez/Rocket | Rust | 495 | ~1,281 | ~90 | **93%** |
554
- | fastify/fastify | JavaScript | 258 | ~2,136 | ~202 | **91%** |
555
- | **Average (JS/TS/RS)** | | | | | **92%** |
569
+ | Repository | Files | Raw tokens | Skeleton tokens | Reduction |
570
+ |---|---:|---:|---:|---:|
571
+ | vercel/next.js | 2,742 | ~12.2M | ~584k | **95%** |
572
+ | honojs/hono | 200 | ~185k | ~30k | **84%** |
573
+ | vitejs/vite | 1,032 | ~459k | ~105k | **77%** |
574
+ | withastro/astro | 875 | ~805k | ~191k | **76%** |
575
+ | nestjs/nest | 1,305 | ~409k | ~177k | **57%** |
576
+ | **Weighted average · 6,154 files** | | **~14.1M** | **~1.1M** | **92%** |
556
577
 
557
- Token counts use the standard 4 chars/token approximation. Results saved in [`benchmarks/public-repos-results.json`](benchmarks/public-repos-results.json). Run `npm run bench:repos` to reproduce.
578
+ Token counts use the standard 4 chars/token approximation. Per-repo range (57–95%) reflects file-shape sensitivity: codebases with lots of tiny re-export shims compress less than ones with meatier source. Results saved in [`benchmarks/large-repos-results.json`](benchmarks/large-repos-results.json). Run `npm run bench:repos` to reproduce.
558
579
 
559
580
  ---
560
581
 
@@ -455,6 +455,27 @@ var ASTParser = class {
455
455
  kotlinLang = null;
456
456
  swiftLang = null;
457
457
  phpLang = null;
458
+ /**
459
+ * Per-language parser cache. Each TreeSitter.Parser is a
460
+ * WASM-allocated object; instantiating one per file (the previous
461
+ * behaviour) leaked memory faster than V8 could reclaim it — OOM
462
+ * at ~1100 files even with --max-old-space-size=8192. Reusing one
463
+ * parser per language for the lifetime of the ASTParser instance
464
+ * is exactly what the tree-sitter docs recommend.
465
+ *
466
+ * Map key is the resolved Language; the same Language object is
467
+ * cached per-language above (tsLang, pyLang, …) so the WeakMap is
468
+ * stable across calls.
469
+ */
470
+ parserCache = /* @__PURE__ */ new WeakMap();
471
+ getParser(language) {
472
+ let parser = this.parserCache.get(language);
473
+ if (parser) return parser;
474
+ parser = new TreeSitter.Parser();
475
+ parser.setLanguage(language);
476
+ this.parserCache.set(language, parser);
477
+ return parser;
478
+ }
458
479
  dartLang = null;
459
480
  grammarLoader = new GrammarLoader();
460
481
  async init() {
@@ -611,12 +632,15 @@ var ASTParser = class {
611
632
  if (ext === ".php") return this.parsePhp(filePath);
612
633
  if (ext === ".dart") return this.parseDart(filePath);
613
634
  if (ext === ".vue") return this.parseVue(filePath);
614
- const parser = new TreeSitter.Parser();
615
- parser.setLanguage(this.tsLang);
635
+ const parser = this.getParser(this.tsLang);
616
636
  const source = fs2.readFileSync(filePath, "utf-8");
617
637
  const tree = parser.parse(source);
618
638
  if (!tree) return [];
619
- return this.extractTSNodes(tree.rootNode, filePath, source.split("\n"));
639
+ try {
640
+ return this.extractTSNodes(tree.rootNode, filePath, source.split("\n"));
641
+ } finally {
642
+ tree.delete();
643
+ }
620
644
  }
621
645
  extractTSNodes(rootNode, _filePath, lines) {
622
646
  const nodes = [];
@@ -793,21 +817,27 @@ var ASTParser = class {
793
817
  if (!match?.[1]?.trim()) return [];
794
818
  const scriptContent = match[1];
795
819
  if (!this.tsLang) return [];
796
- const parser = new TreeSitter.Parser();
797
- parser.setLanguage(this.tsLang);
820
+ const parser = this.getParser(this.tsLang);
798
821
  const tree = parser.parse(scriptContent);
799
822
  if (!tree) return [];
800
- return this.extractTSNodes(tree.rootNode, filePath, scriptContent.split("\n"));
823
+ try {
824
+ return this.extractTSNodes(tree.rootNode, filePath, scriptContent.split("\n"));
825
+ } finally {
826
+ tree.delete();
827
+ }
801
828
  }
802
829
  async parsePython(filePath) {
803
830
  if (!this.pyLang) await this.loadPython();
804
831
  if (!this.pyLang) return [];
805
- const parser = new TreeSitter.Parser();
806
- parser.setLanguage(this.pyLang);
832
+ const parser = this.getParser(this.pyLang);
807
833
  const source = fs2.readFileSync(filePath, "utf-8");
808
834
  const tree = parser.parse(source);
809
835
  if (!tree) return [];
810
- return this.extractPythonNodes(tree.rootNode, filePath, source);
836
+ try {
837
+ return this.extractPythonNodes(tree.rootNode, filePath, source);
838
+ } finally {
839
+ tree.delete();
840
+ }
811
841
  }
812
842
  async parseNotebook(filePath) {
813
843
  if (!this.pyLang) await this.loadPython();
@@ -816,11 +846,14 @@ var ASTParser = class {
816
846
  const raw = fs2.readFileSync(filePath, "utf-8");
817
847
  const pythonSource = extractNotebookPythonSource(raw);
818
848
  if (!pythonSource.trim()) return [];
819
- const parser = new TreeSitter.Parser();
820
- parser.setLanguage(this.pyLang);
849
+ const parser = this.getParser(this.pyLang);
821
850
  const tree = parser.parse(pythonSource);
822
851
  if (!tree) return [];
823
- return this.extractPythonNodes(tree.rootNode, filePath, pythonSource);
852
+ try {
853
+ return this.extractPythonNodes(tree.rootNode, filePath, pythonSource);
854
+ } finally {
855
+ tree.delete();
856
+ }
824
857
  } catch {
825
858
  return [];
826
859
  }
@@ -903,8 +936,7 @@ var ASTParser = class {
903
936
  async parseGo(filePath) {
904
937
  if (!this.goLang) await this.loadGo();
905
938
  if (!this.goLang) return [];
906
- const parser = new TreeSitter.Parser();
907
- parser.setLanguage(this.goLang);
939
+ const parser = this.getParser(this.goLang);
908
940
  const source = fs2.readFileSync(filePath, "utf-8");
909
941
  const tree = parser.parse(source);
910
942
  if (!tree) return [];
@@ -984,14 +1016,17 @@ var ASTParser = class {
984
1016
  if (child) walk(child);
985
1017
  }
986
1018
  };
987
- walk(tree.rootNode);
988
- return nodes;
1019
+ try {
1020
+ walk(tree.rootNode);
1021
+ return nodes;
1022
+ } finally {
1023
+ tree.delete();
1024
+ }
989
1025
  }
990
1026
  async parseRust(filePath) {
991
1027
  if (!this.rustLang) await this.loadRust();
992
1028
  if (!this.rustLang) return [];
993
- const parser = new TreeSitter.Parser();
994
- parser.setLanguage(this.rustLang);
1029
+ const parser = this.getParser(this.rustLang);
995
1030
  const source = fs2.readFileSync(filePath, "utf-8");
996
1031
  const tree = parser.parse(source);
997
1032
  if (!tree) return [];
@@ -1093,14 +1128,17 @@ var ASTParser = class {
1093
1128
  if (child) walk(child);
1094
1129
  }
1095
1130
  };
1096
- walk(tree.rootNode);
1097
- return nodes;
1131
+ try {
1132
+ walk(tree.rootNode);
1133
+ return nodes;
1134
+ } finally {
1135
+ tree.delete();
1136
+ }
1098
1137
  }
1099
1138
  async parseJava(filePath) {
1100
1139
  if (!this.javaLang) await this.loadJava();
1101
1140
  if (!this.javaLang) return [];
1102
- const parser = new TreeSitter.Parser();
1103
- parser.setLanguage(this.javaLang);
1141
+ const parser = this.getParser(this.javaLang);
1104
1142
  const source = fs2.readFileSync(filePath, "utf-8");
1105
1143
  const tree = parser.parse(source);
1106
1144
  if (!tree) return [];
@@ -1193,14 +1231,17 @@ var ASTParser = class {
1193
1231
  if (child) walk(child);
1194
1232
  }
1195
1233
  };
1196
- walk(tree.rootNode);
1197
- return nodes;
1234
+ try {
1235
+ walk(tree.rootNode);
1236
+ return nodes;
1237
+ } finally {
1238
+ tree.delete();
1239
+ }
1198
1240
  }
1199
1241
  async parseCSharp(filePath) {
1200
1242
  if (!this.csLang) await this.loadCSharp();
1201
1243
  if (!this.csLang) return [];
1202
- const parser = new TreeSitter.Parser();
1203
- parser.setLanguage(this.csLang);
1244
+ const parser = this.getParser(this.csLang);
1204
1245
  const source = fs2.readFileSync(filePath, "utf-8");
1205
1246
  const tree = parser.parse(source);
1206
1247
  if (!tree) return [];
@@ -1271,14 +1312,17 @@ var ASTParser = class {
1271
1312
  if (child) walk(child);
1272
1313
  }
1273
1314
  };
1274
- walk(tree.rootNode);
1275
- return nodes;
1315
+ try {
1316
+ walk(tree.rootNode);
1317
+ return nodes;
1318
+ } finally {
1319
+ tree.delete();
1320
+ }
1276
1321
  }
1277
1322
  async parseRuby(filePath) {
1278
1323
  if (!this.rubyLang) await this.loadRuby();
1279
1324
  if (!this.rubyLang) return [];
1280
- const parser = new TreeSitter.Parser();
1281
- parser.setLanguage(this.rubyLang);
1325
+ const parser = this.getParser(this.rubyLang);
1282
1326
  const source = fs2.readFileSync(filePath, "utf-8");
1283
1327
  const tree = parser.parse(source);
1284
1328
  if (!tree) return [];
@@ -1334,14 +1378,17 @@ var ASTParser = class {
1334
1378
  if (child) walk(child);
1335
1379
  }
1336
1380
  };
1337
- walk(tree.rootNode);
1338
- return nodes;
1381
+ try {
1382
+ walk(tree.rootNode);
1383
+ return nodes;
1384
+ } finally {
1385
+ tree.delete();
1386
+ }
1339
1387
  }
1340
1388
  async parseKotlin(filePath) {
1341
1389
  if (!this.kotlinLang) await this.loadKotlin();
1342
1390
  if (!this.kotlinLang) return [];
1343
- const parser = new TreeSitter.Parser();
1344
- parser.setLanguage(this.kotlinLang);
1391
+ const parser = this.getParser(this.kotlinLang);
1345
1392
  const source = fs2.readFileSync(filePath, "utf-8");
1346
1393
  const tree = parser.parse(source);
1347
1394
  if (!tree) return [];
@@ -1394,14 +1441,17 @@ var ASTParser = class {
1394
1441
  if (child) walk(child);
1395
1442
  }
1396
1443
  };
1397
- walk(tree.rootNode);
1398
- return nodes;
1444
+ try {
1445
+ walk(tree.rootNode);
1446
+ return nodes;
1447
+ } finally {
1448
+ tree.delete();
1449
+ }
1399
1450
  }
1400
1451
  async parseSwift(filePath) {
1401
1452
  if (!this.swiftLang) await this.loadSwift();
1402
1453
  if (!this.swiftLang) return [];
1403
- const parser = new TreeSitter.Parser();
1404
- parser.setLanguage(this.swiftLang);
1454
+ const parser = this.getParser(this.swiftLang);
1405
1455
  const source = fs2.readFileSync(filePath, "utf-8");
1406
1456
  const tree = parser.parse(source);
1407
1457
  if (!tree) return [];
@@ -1466,14 +1516,17 @@ var ASTParser = class {
1466
1516
  if (child) walk(child);
1467
1517
  }
1468
1518
  };
1469
- walk(tree.rootNode);
1470
- return nodes;
1519
+ try {
1520
+ walk(tree.rootNode);
1521
+ return nodes;
1522
+ } finally {
1523
+ tree.delete();
1524
+ }
1471
1525
  }
1472
1526
  async parsePhp(filePath) {
1473
1527
  if (!this.phpLang) await this.loadPhp();
1474
1528
  if (!this.phpLang) return [];
1475
- const parser = new TreeSitter.Parser();
1476
- parser.setLanguage(this.phpLang);
1529
+ const parser = this.getParser(this.phpLang);
1477
1530
  const source = fs2.readFileSync(filePath, "utf-8");
1478
1531
  const tree = parser.parse(source);
1479
1532
  if (!tree) return [];
@@ -1545,14 +1598,17 @@ var ASTParser = class {
1545
1598
  if (child) walk(child);
1546
1599
  }
1547
1600
  };
1548
- walk(tree.rootNode);
1549
- return nodes;
1601
+ try {
1602
+ walk(tree.rootNode);
1603
+ return nodes;
1604
+ } finally {
1605
+ tree.delete();
1606
+ }
1550
1607
  }
1551
1608
  async parseDart(filePath) {
1552
1609
  if (!this.dartLang) await this.loadDart();
1553
1610
  if (!this.dartLang) return [];
1554
- const parser = new TreeSitter.Parser();
1555
- parser.setLanguage(this.dartLang);
1611
+ const parser = this.getParser(this.dartLang);
1556
1612
  const source = fs2.readFileSync(filePath, "utf-8");
1557
1613
  const tree = parser.parse(source);
1558
1614
  if (!tree) return [];
@@ -1607,16 +1663,19 @@ var ASTParser = class {
1607
1663
  if (child) walk(child);
1608
1664
  }
1609
1665
  };
1610
- walk(tree.rootNode);
1611
- return nodes;
1666
+ try {
1667
+ walk(tree.rootNode);
1668
+ return nodes;
1669
+ } finally {
1670
+ tree.delete();
1671
+ }
1612
1672
  }
1613
1673
  /**
1614
1674
  * Find all call sites of a symbol in a file.
1615
1675
  */
1616
1676
  async findCallSites(filePath, symbolName) {
1617
1677
  if (!this.tsLang) throw new Error("ASTParser not initialized. Call init() first.");
1618
- const parser = new TreeSitter.Parser();
1619
- parser.setLanguage(this.tsLang);
1678
+ const parser = this.getParser(this.tsLang);
1620
1679
  const source = fs2.readFileSync(filePath, "utf-8");
1621
1680
  const tree = parser.parse(source);
1622
1681
  if (!tree) return [];
@@ -1641,8 +1700,12 @@ var ASTParser = class {
1641
1700
  if (child) walk(child);
1642
1701
  }
1643
1702
  };
1644
- walk(tree.rootNode);
1645
- return results;
1703
+ try {
1704
+ walk(tree.rootNode);
1705
+ return results;
1706
+ } finally {
1707
+ tree.delete();
1708
+ }
1646
1709
  }
1647
1710
  /**
1648
1711
  * Extract all call edges in a TypeScript/TSX file.
@@ -1651,8 +1714,7 @@ var ASTParser = class {
1651
1714
  */
1652
1715
  async parseAllCallEdges(filePath) {
1653
1716
  if (!this.tsLang) throw new Error("ASTParser not initialized. Call init() first.");
1654
- const parser = new TreeSitter.Parser();
1655
- parser.setLanguage(this.tsLang);
1717
+ const parser = this.getParser(this.tsLang);
1656
1718
  const source = fs2.readFileSync(filePath, "utf-8");
1657
1719
  const tree = parser.parse(source);
1658
1720
  if (!tree) return [];
@@ -1683,8 +1745,12 @@ var ASTParser = class {
1683
1745
  if (child) walk(child, newStack);
1684
1746
  }
1685
1747
  };
1686
- walk(tree.rootNode, []);
1687
- return results;
1748
+ try {
1749
+ walk(tree.rootNode, []);
1750
+ return results;
1751
+ } finally {
1752
+ tree.delete();
1753
+ }
1688
1754
  }
1689
1755
  };
1690
1756
 
@@ -3771,27 +3837,68 @@ var Skeletonizer = class {
3771
3837
  }
3772
3838
  /**
3773
3839
  * Produce a plain-text skeleton of the file.
3840
+ *
3841
+ * Three layers of pathological-input defense — without them, a single
3842
+ * minified bundled file (e.g. Next.js ships entire compiled vendor
3843
+ * packages as 466KB-on-16-lines `.cjs` files) explodes into hundreds
3844
+ * of MB of output. The `import` and `interface` cases call
3845
+ * `readLines(node.startLine, node.endLine)`; on minified one-line
3846
+ * source where every node spans the whole file, that returns the
3847
+ * entire file each time, and many such nodes repeat the duplication.
3848
+ * Observed: 466KB → 211MB (452× expansion) on @vercel/blob's compiled
3849
+ * bundle in next.js. Defenses, in order of cheapest:
3850
+ *
3851
+ * 1. `MAX_INPUT_BYTES` — skip files larger than 256KB. AI context
3852
+ * tools shouldn't be skeletonizing pre-built vendor bundles
3853
+ * anyway; they're not human-authored source.
3854
+ * 2. Average-line-length heuristic — if mean line >1KB, treat as
3855
+ * minified and skip. Catches the .cjs/.min.js pattern that
3856
+ * slips under the 256KB ceiling.
3857
+ * 3. `MAX_OUTPUT_BYTES` running cap — belt-and-suspenders against
3858
+ * anything the first two miss. Stop appending and return what
3859
+ * we have rather than OOM.
3860
+ *
3861
+ * Skipped files return a single comment so callers can distinguish
3862
+ * "deliberately skipped" from "skeletonized to empty".
3774
3863
  */
3775
3864
  async skeletonize(filePath) {
3865
+ const fileSource = fs13.readFileSync(filePath, "utf-8");
3866
+ const fileLines = fileSource.split("\n");
3867
+ const MAX_INPUT_BYTES = 256 * 1024;
3868
+ if (fileSource.length > MAX_INPUT_BYTES) {
3869
+ return `// Source: ${filePath}
3870
+ // (skipped: ${fileSource.length} bytes exceeds ${MAX_INPUT_BYTES} byte limit; likely a bundled vendor file)`;
3871
+ }
3872
+ const meanLineBytes = fileSource.length / Math.max(fileLines.length, 1);
3873
+ if (meanLineBytes > 1024) {
3874
+ return `// Source: ${filePath}
3875
+ // (skipped: appears minified \u2014 ${fileLines.length} lines for ${fileSource.length} bytes)`;
3876
+ }
3776
3877
  const nodes = await this.parser.parse(filePath);
3777
- const fileLines = fs13.readFileSync(filePath, "utf-8").split("\n");
3778
3878
  const lines = [`// Source: ${filePath}`];
3879
+ const MAX_OUTPUT_BYTES = 1024 * 1024;
3880
+ let outputBytes = lines[0].length;
3779
3881
  for (const node of nodes) {
3882
+ if (outputBytes > MAX_OUTPUT_BYTES) {
3883
+ lines.push(`// (output truncated at ${MAX_OUTPUT_BYTES} bytes)`);
3884
+ break;
3885
+ }
3886
+ let chunk = "";
3780
3887
  switch (node.type) {
3781
3888
  case "import":
3782
- lines.push(this.readLines(fileLines, node.startLine, node.endLine));
3889
+ chunk = this.readLines(fileLines, node.startLine, node.endLine);
3783
3890
  break;
3784
3891
  case "interface":
3785
- lines.push(this.readLines(fileLines, node.startLine, node.endLine));
3892
+ chunk = this.readLines(fileLines, node.startLine, node.endLine);
3786
3893
  break;
3787
3894
  case "function":
3788
- lines.push(`${node.signature};`);
3895
+ chunk = `${node.signature};`;
3789
3896
  break;
3790
3897
  case "arrow_function":
3791
- lines.push(`${node.signature};`);
3898
+ chunk = `${node.signature};`;
3792
3899
  break;
3793
3900
  case "export_default":
3794
- lines.push(`${node.signature};`);
3901
+ chunk = `${node.signature};`;
3795
3902
  break;
3796
3903
  case "class": {
3797
3904
  const methodLines = (node.methodRanges ?? []).map((mr) => {
@@ -3801,12 +3908,16 @@ var Skeletonizer = class {
3801
3908
  if (methodLines.length === 0 && node.methods) {
3802
3909
  node.methods.forEach((m) => methodLines.push(` ${m}(...): unknown;`));
3803
3910
  }
3804
- lines.push(`class ${node.name} {
3911
+ chunk = `class ${node.name} {
3805
3912
  ${methodLines.join("\n")}
3806
- }`);
3913
+ }`;
3807
3914
  break;
3808
3915
  }
3809
3916
  }
3917
+ if (chunk) {
3918
+ lines.push(chunk);
3919
+ outputBytes += chunk.length + 1;
3920
+ }
3810
3921
  }
3811
3922
  return lines.join("\n");
3812
3923
  }
@@ -11460,9 +11571,9 @@ async function startDashboard(options) {
11460
11571
  }
11461
11572
  }));
11462
11573
  const clientDist = path37.join(__dirname2, "../dashboard/client");
11463
- app.use(express.static(clientDist));
11574
+ app.use(express.static(clientDist, { dotfiles: "allow" }));
11464
11575
  app.get(/.*/, (_req, res) => {
11465
- res.sendFile(path37.join(clientDist, "index.html"));
11576
+ res.sendFile(path37.join(clientDist, "index.html"), { dotfiles: "allow" });
11466
11577
  });
11467
11578
  app.listen(port, () => {
11468
11579
  const url = `http://localhost:${port}`;
@@ -303,6 +303,27 @@ var ASTParser = class {
303
303
  kotlinLang = null;
304
304
  swiftLang = null;
305
305
  phpLang = null;
306
+ /**
307
+ * Per-language parser cache. Each TreeSitter.Parser is a
308
+ * WASM-allocated object; instantiating one per file (the previous
309
+ * behaviour) leaked memory faster than V8 could reclaim it — OOM
310
+ * at ~1100 files even with --max-old-space-size=8192. Reusing one
311
+ * parser per language for the lifetime of the ASTParser instance
312
+ * is exactly what the tree-sitter docs recommend.
313
+ *
314
+ * Map key is the resolved Language; the same Language object is
315
+ * cached per-language above (tsLang, pyLang, …) so the WeakMap is
316
+ * stable across calls.
317
+ */
318
+ parserCache = /* @__PURE__ */ new WeakMap();
319
+ getParser(language) {
320
+ let parser = this.parserCache.get(language);
321
+ if (parser) return parser;
322
+ parser = new TreeSitter.Parser();
323
+ parser.setLanguage(language);
324
+ this.parserCache.set(language, parser);
325
+ return parser;
326
+ }
306
327
  dartLang = null;
307
328
  grammarLoader = new GrammarLoader();
308
329
  async init() {
@@ -459,12 +480,15 @@ var ASTParser = class {
459
480
  if (ext === ".php") return this.parsePhp(filePath);
460
481
  if (ext === ".dart") return this.parseDart(filePath);
461
482
  if (ext === ".vue") return this.parseVue(filePath);
462
- const parser = new TreeSitter.Parser();
463
- parser.setLanguage(this.tsLang);
483
+ const parser = this.getParser(this.tsLang);
464
484
  const source = fs2.readFileSync(filePath, "utf-8");
465
485
  const tree = parser.parse(source);
466
486
  if (!tree) return [];
467
- return this.extractTSNodes(tree.rootNode, filePath, source.split("\n"));
487
+ try {
488
+ return this.extractTSNodes(tree.rootNode, filePath, source.split("\n"));
489
+ } finally {
490
+ tree.delete();
491
+ }
468
492
  }
469
493
  extractTSNodes(rootNode, _filePath, lines) {
470
494
  const nodes = [];
@@ -641,21 +665,27 @@ var ASTParser = class {
641
665
  if (!match?.[1]?.trim()) return [];
642
666
  const scriptContent = match[1];
643
667
  if (!this.tsLang) return [];
644
- const parser = new TreeSitter.Parser();
645
- parser.setLanguage(this.tsLang);
668
+ const parser = this.getParser(this.tsLang);
646
669
  const tree = parser.parse(scriptContent);
647
670
  if (!tree) return [];
648
- return this.extractTSNodes(tree.rootNode, filePath, scriptContent.split("\n"));
671
+ try {
672
+ return this.extractTSNodes(tree.rootNode, filePath, scriptContent.split("\n"));
673
+ } finally {
674
+ tree.delete();
675
+ }
649
676
  }
650
677
  async parsePython(filePath) {
651
678
  if (!this.pyLang) await this.loadPython();
652
679
  if (!this.pyLang) return [];
653
- const parser = new TreeSitter.Parser();
654
- parser.setLanguage(this.pyLang);
680
+ const parser = this.getParser(this.pyLang);
655
681
  const source = fs2.readFileSync(filePath, "utf-8");
656
682
  const tree = parser.parse(source);
657
683
  if (!tree) return [];
658
- return this.extractPythonNodes(tree.rootNode, filePath, source);
684
+ try {
685
+ return this.extractPythonNodes(tree.rootNode, filePath, source);
686
+ } finally {
687
+ tree.delete();
688
+ }
659
689
  }
660
690
  async parseNotebook(filePath) {
661
691
  if (!this.pyLang) await this.loadPython();
@@ -664,11 +694,14 @@ var ASTParser = class {
664
694
  const raw = fs2.readFileSync(filePath, "utf-8");
665
695
  const pythonSource = extractNotebookPythonSource(raw);
666
696
  if (!pythonSource.trim()) return [];
667
- const parser = new TreeSitter.Parser();
668
- parser.setLanguage(this.pyLang);
697
+ const parser = this.getParser(this.pyLang);
669
698
  const tree = parser.parse(pythonSource);
670
699
  if (!tree) return [];
671
- return this.extractPythonNodes(tree.rootNode, filePath, pythonSource);
700
+ try {
701
+ return this.extractPythonNodes(tree.rootNode, filePath, pythonSource);
702
+ } finally {
703
+ tree.delete();
704
+ }
672
705
  } catch {
673
706
  return [];
674
707
  }
@@ -751,8 +784,7 @@ var ASTParser = class {
751
784
  async parseGo(filePath) {
752
785
  if (!this.goLang) await this.loadGo();
753
786
  if (!this.goLang) return [];
754
- const parser = new TreeSitter.Parser();
755
- parser.setLanguage(this.goLang);
787
+ const parser = this.getParser(this.goLang);
756
788
  const source = fs2.readFileSync(filePath, "utf-8");
757
789
  const tree = parser.parse(source);
758
790
  if (!tree) return [];
@@ -832,14 +864,17 @@ var ASTParser = class {
832
864
  if (child) walk(child);
833
865
  }
834
866
  };
835
- walk(tree.rootNode);
836
- return nodes;
867
+ try {
868
+ walk(tree.rootNode);
869
+ return nodes;
870
+ } finally {
871
+ tree.delete();
872
+ }
837
873
  }
838
874
  async parseRust(filePath) {
839
875
  if (!this.rustLang) await this.loadRust();
840
876
  if (!this.rustLang) return [];
841
- const parser = new TreeSitter.Parser();
842
- parser.setLanguage(this.rustLang);
877
+ const parser = this.getParser(this.rustLang);
843
878
  const source = fs2.readFileSync(filePath, "utf-8");
844
879
  const tree = parser.parse(source);
845
880
  if (!tree) return [];
@@ -941,14 +976,17 @@ var ASTParser = class {
941
976
  if (child) walk(child);
942
977
  }
943
978
  };
944
- walk(tree.rootNode);
945
- return nodes;
979
+ try {
980
+ walk(tree.rootNode);
981
+ return nodes;
982
+ } finally {
983
+ tree.delete();
984
+ }
946
985
  }
947
986
  async parseJava(filePath) {
948
987
  if (!this.javaLang) await this.loadJava();
949
988
  if (!this.javaLang) return [];
950
- const parser = new TreeSitter.Parser();
951
- parser.setLanguage(this.javaLang);
989
+ const parser = this.getParser(this.javaLang);
952
990
  const source = fs2.readFileSync(filePath, "utf-8");
953
991
  const tree = parser.parse(source);
954
992
  if (!tree) return [];
@@ -1041,14 +1079,17 @@ var ASTParser = class {
1041
1079
  if (child) walk(child);
1042
1080
  }
1043
1081
  };
1044
- walk(tree.rootNode);
1045
- return nodes;
1082
+ try {
1083
+ walk(tree.rootNode);
1084
+ return nodes;
1085
+ } finally {
1086
+ tree.delete();
1087
+ }
1046
1088
  }
1047
1089
  async parseCSharp(filePath) {
1048
1090
  if (!this.csLang) await this.loadCSharp();
1049
1091
  if (!this.csLang) return [];
1050
- const parser = new TreeSitter.Parser();
1051
- parser.setLanguage(this.csLang);
1092
+ const parser = this.getParser(this.csLang);
1052
1093
  const source = fs2.readFileSync(filePath, "utf-8");
1053
1094
  const tree = parser.parse(source);
1054
1095
  if (!tree) return [];
@@ -1119,14 +1160,17 @@ var ASTParser = class {
1119
1160
  if (child) walk(child);
1120
1161
  }
1121
1162
  };
1122
- walk(tree.rootNode);
1123
- return nodes;
1163
+ try {
1164
+ walk(tree.rootNode);
1165
+ return nodes;
1166
+ } finally {
1167
+ tree.delete();
1168
+ }
1124
1169
  }
1125
1170
  async parseRuby(filePath) {
1126
1171
  if (!this.rubyLang) await this.loadRuby();
1127
1172
  if (!this.rubyLang) return [];
1128
- const parser = new TreeSitter.Parser();
1129
- parser.setLanguage(this.rubyLang);
1173
+ const parser = this.getParser(this.rubyLang);
1130
1174
  const source = fs2.readFileSync(filePath, "utf-8");
1131
1175
  const tree = parser.parse(source);
1132
1176
  if (!tree) return [];
@@ -1182,14 +1226,17 @@ var ASTParser = class {
1182
1226
  if (child) walk(child);
1183
1227
  }
1184
1228
  };
1185
- walk(tree.rootNode);
1186
- return nodes;
1229
+ try {
1230
+ walk(tree.rootNode);
1231
+ return nodes;
1232
+ } finally {
1233
+ tree.delete();
1234
+ }
1187
1235
  }
1188
1236
  async parseKotlin(filePath) {
1189
1237
  if (!this.kotlinLang) await this.loadKotlin();
1190
1238
  if (!this.kotlinLang) return [];
1191
- const parser = new TreeSitter.Parser();
1192
- parser.setLanguage(this.kotlinLang);
1239
+ const parser = this.getParser(this.kotlinLang);
1193
1240
  const source = fs2.readFileSync(filePath, "utf-8");
1194
1241
  const tree = parser.parse(source);
1195
1242
  if (!tree) return [];
@@ -1242,14 +1289,17 @@ var ASTParser = class {
1242
1289
  if (child) walk(child);
1243
1290
  }
1244
1291
  };
1245
- walk(tree.rootNode);
1246
- return nodes;
1292
+ try {
1293
+ walk(tree.rootNode);
1294
+ return nodes;
1295
+ } finally {
1296
+ tree.delete();
1297
+ }
1247
1298
  }
1248
1299
  async parseSwift(filePath) {
1249
1300
  if (!this.swiftLang) await this.loadSwift();
1250
1301
  if (!this.swiftLang) return [];
1251
- const parser = new TreeSitter.Parser();
1252
- parser.setLanguage(this.swiftLang);
1302
+ const parser = this.getParser(this.swiftLang);
1253
1303
  const source = fs2.readFileSync(filePath, "utf-8");
1254
1304
  const tree = parser.parse(source);
1255
1305
  if (!tree) return [];
@@ -1314,14 +1364,17 @@ var ASTParser = class {
1314
1364
  if (child) walk(child);
1315
1365
  }
1316
1366
  };
1317
- walk(tree.rootNode);
1318
- return nodes;
1367
+ try {
1368
+ walk(tree.rootNode);
1369
+ return nodes;
1370
+ } finally {
1371
+ tree.delete();
1372
+ }
1319
1373
  }
1320
1374
  async parsePhp(filePath) {
1321
1375
  if (!this.phpLang) await this.loadPhp();
1322
1376
  if (!this.phpLang) return [];
1323
- const parser = new TreeSitter.Parser();
1324
- parser.setLanguage(this.phpLang);
1377
+ const parser = this.getParser(this.phpLang);
1325
1378
  const source = fs2.readFileSync(filePath, "utf-8");
1326
1379
  const tree = parser.parse(source);
1327
1380
  if (!tree) return [];
@@ -1393,14 +1446,17 @@ var ASTParser = class {
1393
1446
  if (child) walk(child);
1394
1447
  }
1395
1448
  };
1396
- walk(tree.rootNode);
1397
- return nodes;
1449
+ try {
1450
+ walk(tree.rootNode);
1451
+ return nodes;
1452
+ } finally {
1453
+ tree.delete();
1454
+ }
1398
1455
  }
1399
1456
  async parseDart(filePath) {
1400
1457
  if (!this.dartLang) await this.loadDart();
1401
1458
  if (!this.dartLang) return [];
1402
- const parser = new TreeSitter.Parser();
1403
- parser.setLanguage(this.dartLang);
1459
+ const parser = this.getParser(this.dartLang);
1404
1460
  const source = fs2.readFileSync(filePath, "utf-8");
1405
1461
  const tree = parser.parse(source);
1406
1462
  if (!tree) return [];
@@ -1455,16 +1511,19 @@ var ASTParser = class {
1455
1511
  if (child) walk(child);
1456
1512
  }
1457
1513
  };
1458
- walk(tree.rootNode);
1459
- return nodes;
1514
+ try {
1515
+ walk(tree.rootNode);
1516
+ return nodes;
1517
+ } finally {
1518
+ tree.delete();
1519
+ }
1460
1520
  }
1461
1521
  /**
1462
1522
  * Find all call sites of a symbol in a file.
1463
1523
  */
1464
1524
  async findCallSites(filePath, symbolName) {
1465
1525
  if (!this.tsLang) throw new Error("ASTParser not initialized. Call init() first.");
1466
- const parser = new TreeSitter.Parser();
1467
- parser.setLanguage(this.tsLang);
1526
+ const parser = this.getParser(this.tsLang);
1468
1527
  const source = fs2.readFileSync(filePath, "utf-8");
1469
1528
  const tree = parser.parse(source);
1470
1529
  if (!tree) return [];
@@ -1489,8 +1548,12 @@ var ASTParser = class {
1489
1548
  if (child) walk(child);
1490
1549
  }
1491
1550
  };
1492
- walk(tree.rootNode);
1493
- return results;
1551
+ try {
1552
+ walk(tree.rootNode);
1553
+ return results;
1554
+ } finally {
1555
+ tree.delete();
1556
+ }
1494
1557
  }
1495
1558
  /**
1496
1559
  * Extract all call edges in a TypeScript/TSX file.
@@ -1499,8 +1562,7 @@ var ASTParser = class {
1499
1562
  */
1500
1563
  async parseAllCallEdges(filePath) {
1501
1564
  if (!this.tsLang) throw new Error("ASTParser not initialized. Call init() first.");
1502
- const parser = new TreeSitter.Parser();
1503
- parser.setLanguage(this.tsLang);
1565
+ const parser = this.getParser(this.tsLang);
1504
1566
  const source = fs2.readFileSync(filePath, "utf-8");
1505
1567
  const tree = parser.parse(source);
1506
1568
  if (!tree) return [];
@@ -1531,8 +1593,12 @@ var ASTParser = class {
1531
1593
  if (child) walk(child, newStack);
1532
1594
  }
1533
1595
  };
1534
- walk(tree.rootNode, []);
1535
- return results;
1596
+ try {
1597
+ walk(tree.rootNode, []);
1598
+ return results;
1599
+ } finally {
1600
+ tree.delete();
1601
+ }
1536
1602
  }
1537
1603
  };
1538
1604
 
@@ -4051,27 +4117,68 @@ var Skeletonizer = class {
4051
4117
  }
4052
4118
  /**
4053
4119
  * Produce a plain-text skeleton of the file.
4120
+ *
4121
+ * Three layers of pathological-input defense — without them, a single
4122
+ * minified bundled file (e.g. Next.js ships entire compiled vendor
4123
+ * packages as 466KB-on-16-lines `.cjs` files) explodes into hundreds
4124
+ * of MB of output. The `import` and `interface` cases call
4125
+ * `readLines(node.startLine, node.endLine)`; on minified one-line
4126
+ * source where every node spans the whole file, that returns the
4127
+ * entire file each time, and many such nodes repeat the duplication.
4128
+ * Observed: 466KB → 211MB (452× expansion) on @vercel/blob's compiled
4129
+ * bundle in next.js. Defenses, in order of cheapest:
4130
+ *
4131
+ * 1. `MAX_INPUT_BYTES` — skip files larger than 256KB. AI context
4132
+ * tools shouldn't be skeletonizing pre-built vendor bundles
4133
+ * anyway; they're not human-authored source.
4134
+ * 2. Average-line-length heuristic — if mean line >1KB, treat as
4135
+ * minified and skip. Catches the .cjs/.min.js pattern that
4136
+ * slips under the 256KB ceiling.
4137
+ * 3. `MAX_OUTPUT_BYTES` running cap — belt-and-suspenders against
4138
+ * anything the first two miss. Stop appending and return what
4139
+ * we have rather than OOM.
4140
+ *
4141
+ * Skipped files return a single comment so callers can distinguish
4142
+ * "deliberately skipped" from "skeletonized to empty".
4054
4143
  */
4055
4144
  async skeletonize(filePath) {
4145
+ const fileSource = fs12.readFileSync(filePath, "utf-8");
4146
+ const fileLines = fileSource.split("\n");
4147
+ const MAX_INPUT_BYTES = 256 * 1024;
4148
+ if (fileSource.length > MAX_INPUT_BYTES) {
4149
+ return `// Source: ${filePath}
4150
+ // (skipped: ${fileSource.length} bytes exceeds ${MAX_INPUT_BYTES} byte limit; likely a bundled vendor file)`;
4151
+ }
4152
+ const meanLineBytes = fileSource.length / Math.max(fileLines.length, 1);
4153
+ if (meanLineBytes > 1024) {
4154
+ return `// Source: ${filePath}
4155
+ // (skipped: appears minified \u2014 ${fileLines.length} lines for ${fileSource.length} bytes)`;
4156
+ }
4056
4157
  const nodes = await this.parser.parse(filePath);
4057
- const fileLines = fs12.readFileSync(filePath, "utf-8").split("\n");
4058
4158
  const lines = [`// Source: ${filePath}`];
4159
+ const MAX_OUTPUT_BYTES = 1024 * 1024;
4160
+ let outputBytes = lines[0].length;
4059
4161
  for (const node of nodes) {
4162
+ if (outputBytes > MAX_OUTPUT_BYTES) {
4163
+ lines.push(`// (output truncated at ${MAX_OUTPUT_BYTES} bytes)`);
4164
+ break;
4165
+ }
4166
+ let chunk = "";
4060
4167
  switch (node.type) {
4061
4168
  case "import":
4062
- lines.push(this.readLines(fileLines, node.startLine, node.endLine));
4169
+ chunk = this.readLines(fileLines, node.startLine, node.endLine);
4063
4170
  break;
4064
4171
  case "interface":
4065
- lines.push(this.readLines(fileLines, node.startLine, node.endLine));
4172
+ chunk = this.readLines(fileLines, node.startLine, node.endLine);
4066
4173
  break;
4067
4174
  case "function":
4068
- lines.push(`${node.signature};`);
4175
+ chunk = `${node.signature};`;
4069
4176
  break;
4070
4177
  case "arrow_function":
4071
- lines.push(`${node.signature};`);
4178
+ chunk = `${node.signature};`;
4072
4179
  break;
4073
4180
  case "export_default":
4074
- lines.push(`${node.signature};`);
4181
+ chunk = `${node.signature};`;
4075
4182
  break;
4076
4183
  case "class": {
4077
4184
  const methodLines = (node.methodRanges ?? []).map((mr) => {
@@ -4081,12 +4188,16 @@ var Skeletonizer = class {
4081
4188
  if (methodLines.length === 0 && node.methods) {
4082
4189
  node.methods.forEach((m) => methodLines.push(` ${m}(...): unknown;`));
4083
4190
  }
4084
- lines.push(`class ${node.name} {
4191
+ chunk = `class ${node.name} {
4085
4192
  ${methodLines.join("\n")}
4086
- }`);
4193
+ }`;
4087
4194
  break;
4088
4195
  }
4089
4196
  }
4197
+ if (chunk) {
4198
+ lines.push(chunk);
4199
+ outputBytes += chunk.length + 1;
4200
+ }
4090
4201
  }
4091
4202
  return lines.join("\n");
4092
4203
  }
@@ -8420,4 +8531,4 @@ export {
8420
8531
  track,
8421
8532
  captureError
8422
8533
  };
8423
- //# sourceMappingURL=chunk-7D275UON.js.map
8534
+ //# sourceMappingURL=chunk-EHFVPRTN.js.map
package/dist/index.js CHANGED
@@ -34,7 +34,7 @@ import {
34
34
  startTrial,
35
35
  track,
36
36
  writeCODEOWNERS
37
- } from "./chunk-7D275UON.js";
37
+ } from "./chunk-EHFVPRTN.js";
38
38
  import {
39
39
  VectorStore
40
40
  } from "./chunk-NEHYSE2Y.js";
@@ -512,7 +512,7 @@ try {
512
512
  } catch {
513
513
  }
514
514
  var args = process.argv.slice(2);
515
- var ctxloomVersion = "1.0.17".length > 0 ? "1.0.17" : "dev";
515
+ var ctxloomVersion = "1.0.19".length > 0 ? "1.0.19" : "dev";
516
516
  if (args.includes("--version") || args.includes("-v")) {
517
517
  process.stdout.write(`ctxloom ${ctxloomVersion}
518
518
  `);
@@ -585,7 +585,7 @@ async function checkLicense() {
585
585
  if (command !== void 0 && LICENSE_GATE_BYPASS_COMMANDS.has(command)) return;
586
586
  const ciKey = process.env["CTXLOOM_LICENSE_KEY"];
587
587
  if (ciKey) {
588
- const { ApiClient } = await import("./src-WB7EQTRV.js");
588
+ const { ApiClient } = await import("./src-IHPQZOZE.js");
589
589
  const client = new ApiClient(process.env["CTXLOOM_API_BASE"]);
590
590
  try {
591
591
  const result = await client.validate(ciKey, "ci-ephemeral");
@@ -1112,7 +1112,7 @@ Suggested reviewers for ${files.length} file(s):`);
1112
1112
  process.stderr.write("[ctxloom] --limit must be a non-negative integer (0 for unlimited)\n");
1113
1113
  process.exit(2);
1114
1114
  }
1115
- const { loadRulesConfig, RulesChecker, formatText, formatJson, RulesConfigError } = await import("./src-WB7EQTRV.js");
1115
+ const { loadRulesConfig, RulesChecker, formatText, formatJson, RulesConfigError } = await import("./src-IHPQZOZE.js");
1116
1116
  let config;
1117
1117
  try {
1118
1118
  config = await loadRulesConfig(root);
@@ -1136,7 +1136,7 @@ Suggested reviewers for ${files.length} file(s):`);
1136
1136
  }
1137
1137
  let graph;
1138
1138
  if (useSnapshot) {
1139
- const { DependencyGraph: DG } = await import("./src-WB7EQTRV.js");
1139
+ const { DependencyGraph: DG } = await import("./src-IHPQZOZE.js");
1140
1140
  graph = new DG();
1141
1141
  const loaded = await graph.loadSnapshotOnly(root);
1142
1142
  if (!loaded) {
@@ -1145,7 +1145,7 @@ Suggested reviewers for ${files.length} file(s):`);
1145
1145
  }
1146
1146
  } else {
1147
1147
  process.stderr.write("[ctxloom] Building dependency graph...\n");
1148
- const { ASTParser: ASTParser2, DependencyGraph: DependencyGraph2 } = await import("./src-WB7EQTRV.js");
1148
+ const { ASTParser: ASTParser2, DependencyGraph: DependencyGraph2 } = await import("./src-IHPQZOZE.js");
1149
1149
  let parser;
1150
1150
  try {
1151
1151
  parser = new ASTParser2();
@@ -69,7 +69,7 @@ import {
69
69
  startTrial,
70
70
  track,
71
71
  writeCODEOWNERS
72
- } from "./chunk-7D275UON.js";
72
+ } from "./chunk-EHFVPRTN.js";
73
73
  import {
74
74
  VectorStore
75
75
  } from "./chunk-NEHYSE2Y.js";
@@ -160,4 +160,4 @@ export {
160
160
  track,
161
161
  writeCODEOWNERS
162
162
  };
163
- //# sourceMappingURL=src-WB7EQTRV.js.map
163
+ //# sourceMappingURL=src-IHPQZOZE.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ctxloom-pro",
3
- "version": "1.0.17",
3
+ "version": "1.0.19",
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",