@liendev/lien 0.12.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -9,11 +9,39 @@ var __export = (target, all) => {
9
9
  __defProp(target, name, { get: all[name], enumerable: true });
10
10
  };
11
11
 
12
+ // src/utils/version.ts
13
+ import { createRequire } from "module";
14
+ import { fileURLToPath } from "url";
15
+ import { dirname, join } from "path";
16
+ function getPackageVersion() {
17
+ return packageJson.version;
18
+ }
19
+ var __filename, __dirname, require2, packageJson;
20
+ var init_version = __esm({
21
+ "src/utils/version.ts"() {
22
+ "use strict";
23
+ __filename = fileURLToPath(import.meta.url);
24
+ __dirname = dirname(__filename);
25
+ require2 = createRequire(import.meta.url);
26
+ try {
27
+ packageJson = require2(join(__dirname, "../package.json"));
28
+ } catch {
29
+ try {
30
+ packageJson = require2(join(__dirname, "../../package.json"));
31
+ } catch {
32
+ console.warn("[Lien] Warning: Could not load package.json, using fallback version");
33
+ packageJson = { version: "0.0.0-unknown" };
34
+ }
35
+ }
36
+ }
37
+ });
38
+
12
39
  // src/constants.ts
13
40
  var DEFAULT_CHUNK_SIZE, DEFAULT_CHUNK_OVERLAP, DEFAULT_CONCURRENCY, DEFAULT_EMBEDDING_BATCH_SIZE, EMBEDDING_MICRO_BATCH_SIZE, VECTOR_DB_MAX_BATCH_SIZE, VECTOR_DB_MIN_BATCH_SIZE, EMBEDDING_DIMENSIONS, DEFAULT_EMBEDDING_MODEL, DEFAULT_PORT, VERSION_CHECK_INTERVAL_MS, DEFAULT_GIT_POLL_INTERVAL_MS, DEFAULT_DEBOUNCE_MS, CURRENT_CONFIG_VERSION, INDEX_FORMAT_VERSION;
14
41
  var init_constants = __esm({
15
42
  "src/constants.ts"() {
16
43
  "use strict";
44
+ init_version();
17
45
  DEFAULT_CHUNK_SIZE = 75;
18
46
  DEFAULT_CHUNK_OVERLAP = 10;
19
47
  DEFAULT_CONCURRENCY = 4;
@@ -27,8 +55,8 @@ var init_constants = __esm({
27
55
  VERSION_CHECK_INTERVAL_MS = 2e3;
28
56
  DEFAULT_GIT_POLL_INTERVAL_MS = 1e4;
29
57
  DEFAULT_DEBOUNCE_MS = 1e3;
30
- CURRENT_CONFIG_VERSION = "0.3.0";
31
- INDEX_FORMAT_VERSION = 1;
58
+ CURRENT_CONFIG_VERSION = getPackageVersion();
59
+ INDEX_FORMAT_VERSION = 2;
32
60
  }
33
61
  });
34
62
 
@@ -52,6 +80,12 @@ var init_schema = __esm({
52
80
  concurrency: DEFAULT_CONCURRENCY,
53
81
  embeddingBatchSize: DEFAULT_EMBEDDING_BATCH_SIZE
54
82
  },
83
+ chunking: {
84
+ useAST: true,
85
+ // AST-based chunking enabled by default (v0.13.0)
86
+ astFallback: "line-based"
87
+ // Fallback to line-based on errors
88
+ },
55
89
  mcp: {
56
90
  port: DEFAULT_PORT,
57
91
  transport: "stdio",
@@ -80,6 +114,10 @@ function deepMergeConfig(defaults, user) {
80
114
  ...defaults.core,
81
115
  ...user.core
82
116
  },
117
+ chunking: {
118
+ ...defaults.chunking,
119
+ ...user.chunking
120
+ },
83
121
  mcp: {
84
122
  ...defaults.mcp,
85
123
  ...user.mcp
@@ -125,7 +163,10 @@ function needsMigration(config) {
125
163
  if (!config) {
126
164
  return false;
127
165
  }
128
- if (config.frameworks !== void 0) {
166
+ if (config.frameworks !== void 0 && !config.chunking) {
167
+ return true;
168
+ }
169
+ if (config.frameworks !== void 0 && config.chunking !== void 0) {
129
170
  return false;
130
171
  }
131
172
  if (config.indexing !== void 0) {
@@ -138,12 +179,16 @@ function needsMigration(config) {
138
179
  }
139
180
  function migrateConfig(oldConfig) {
140
181
  const newConfig = {
141
- version: "0.3.0",
182
+ version: CURRENT_CONFIG_VERSION,
142
183
  core: {
143
- chunkSize: oldConfig.indexing?.chunkSize ?? defaultConfig.core.chunkSize,
144
- chunkOverlap: oldConfig.indexing?.chunkOverlap ?? defaultConfig.core.chunkOverlap,
145
- concurrency: oldConfig.indexing?.concurrency ?? defaultConfig.core.concurrency,
146
- embeddingBatchSize: oldConfig.indexing?.embeddingBatchSize ?? defaultConfig.core.embeddingBatchSize
184
+ chunkSize: oldConfig.indexing?.chunkSize ?? oldConfig.core?.chunkSize ?? defaultConfig.core.chunkSize,
185
+ chunkOverlap: oldConfig.indexing?.chunkOverlap ?? oldConfig.core?.chunkOverlap ?? defaultConfig.core.chunkOverlap,
186
+ concurrency: oldConfig.indexing?.concurrency ?? oldConfig.core?.concurrency ?? defaultConfig.core.concurrency,
187
+ embeddingBatchSize: oldConfig.indexing?.embeddingBatchSize ?? oldConfig.core?.embeddingBatchSize ?? defaultConfig.core.embeddingBatchSize
188
+ },
189
+ chunking: {
190
+ useAST: oldConfig.chunking?.useAST ?? defaultConfig.chunking.useAST,
191
+ astFallback: oldConfig.chunking?.astFallback ?? defaultConfig.chunking.astFallback
147
192
  },
148
193
  mcp: {
149
194
  port: oldConfig.mcp?.port ?? defaultConfig.mcp.port,
@@ -158,9 +203,9 @@ function migrateConfig(oldConfig) {
158
203
  enabled: oldConfig.fileWatching?.enabled ?? defaultConfig.fileWatching.enabled,
159
204
  debounceMs: oldConfig.fileWatching?.debounceMs ?? defaultConfig.fileWatching.debounceMs
160
205
  },
161
- frameworks: []
206
+ frameworks: oldConfig.frameworks ?? []
162
207
  };
163
- if (oldConfig.indexing) {
208
+ if (oldConfig.indexing && newConfig.frameworks.length === 0) {
164
209
  const genericFramework = {
165
210
  name: "generic",
166
211
  path: ".",
@@ -180,7 +225,7 @@ function migrateConfig(oldConfig) {
180
225
  }
181
226
  };
182
227
  newConfig.frameworks.push(genericFramework);
183
- } else {
228
+ } else if (newConfig.frameworks.length === 0) {
184
229
  const genericFramework = {
185
230
  name: "generic",
186
231
  path: ".",
@@ -207,6 +252,7 @@ var init_migration = __esm({
207
252
  "src/config/migration.ts"() {
208
253
  "use strict";
209
254
  init_schema();
255
+ init_constants();
210
256
  }
211
257
  });
212
258
 
@@ -875,7 +921,7 @@ async function readVersionFile(indexPath) {
875
921
  }
876
922
  }
877
923
  var VERSION_FILE;
878
- var init_version = __esm({
924
+ var init_version2 = __esm({
879
925
  "src/vectordb/version.ts"() {
880
926
  "use strict";
881
927
  VERSION_FILE = ".lien-index-version";
@@ -1305,9 +1351,501 @@ var init_symbol_extractor = __esm({
1305
1351
  }
1306
1352
  });
1307
1353
 
1354
+ // src/indexer/ast/parser.ts
1355
+ import Parser from "tree-sitter";
1356
+ import TypeScript from "tree-sitter-typescript";
1357
+ import JavaScript from "tree-sitter-javascript";
1358
+ import { extname } from "path";
1359
+ function getParser(language) {
1360
+ if (!parserCache.has(language)) {
1361
+ const parser = new Parser();
1362
+ const grammar = languageConfig[language];
1363
+ if (!grammar) {
1364
+ throw new Error(`No grammar available for language: ${language}`);
1365
+ }
1366
+ parser.setLanguage(grammar);
1367
+ parserCache.set(language, parser);
1368
+ }
1369
+ return parserCache.get(language);
1370
+ }
1371
+ function detectLanguage2(filePath) {
1372
+ const ext = extname(filePath).slice(1).toLowerCase();
1373
+ switch (ext) {
1374
+ case "ts":
1375
+ case "tsx":
1376
+ return "typescript";
1377
+ case "js":
1378
+ case "jsx":
1379
+ case "mjs":
1380
+ case "cjs":
1381
+ return "javascript";
1382
+ default:
1383
+ return null;
1384
+ }
1385
+ }
1386
+ function isASTSupported(filePath) {
1387
+ return detectLanguage2(filePath) !== null;
1388
+ }
1389
+ function parseAST(content, language) {
1390
+ try {
1391
+ const parser = getParser(language);
1392
+ const tree = parser.parse(content);
1393
+ if (tree.rootNode.hasError) {
1394
+ return {
1395
+ tree,
1396
+ error: "Parse completed with errors"
1397
+ };
1398
+ }
1399
+ return { tree };
1400
+ } catch (error) {
1401
+ return {
1402
+ tree: null,
1403
+ error: error instanceof Error ? error.message : "Unknown parse error"
1404
+ };
1405
+ }
1406
+ }
1407
+ var parserCache, languageConfig;
1408
+ var init_parser = __esm({
1409
+ "src/indexer/ast/parser.ts"() {
1410
+ "use strict";
1411
+ parserCache = /* @__PURE__ */ new Map();
1412
+ languageConfig = {
1413
+ typescript: TypeScript.typescript,
1414
+ javascript: JavaScript
1415
+ // Use proper JavaScript parser
1416
+ };
1417
+ }
1418
+ });
1419
+
1420
+ // src/indexer/ast/symbols.ts
1421
+ function extractSymbolInfo(node, content, parentClass) {
1422
+ const type = node.type;
1423
+ if (type === "function_declaration" || type === "function") {
1424
+ const nameNode = node.childForFieldName("name");
1425
+ if (!nameNode) return null;
1426
+ return {
1427
+ name: nameNode.text,
1428
+ type: parentClass ? "method" : "function",
1429
+ startLine: node.startPosition.row + 1,
1430
+ endLine: node.endPosition.row + 1,
1431
+ parentClass,
1432
+ signature: extractSignature(node, content),
1433
+ parameters: extractParameters(node, content),
1434
+ returnType: extractReturnType(node, content),
1435
+ complexity: calculateComplexity(node)
1436
+ };
1437
+ }
1438
+ if (type === "arrow_function" || type === "function_expression") {
1439
+ const parent = node.parent;
1440
+ let name = "anonymous";
1441
+ if (parent?.type === "variable_declarator") {
1442
+ const nameNode = parent.childForFieldName("name");
1443
+ name = nameNode?.text || "anonymous";
1444
+ }
1445
+ return {
1446
+ name,
1447
+ type: parentClass ? "method" : "function",
1448
+ startLine: node.startPosition.row + 1,
1449
+ endLine: node.endPosition.row + 1,
1450
+ parentClass,
1451
+ signature: extractSignature(node, content),
1452
+ parameters: extractParameters(node, content),
1453
+ complexity: calculateComplexity(node)
1454
+ };
1455
+ }
1456
+ if (type === "method_definition") {
1457
+ const nameNode = node.childForFieldName("name");
1458
+ if (!nameNode) return null;
1459
+ return {
1460
+ name: nameNode.text,
1461
+ type: "method",
1462
+ startLine: node.startPosition.row + 1,
1463
+ endLine: node.endPosition.row + 1,
1464
+ parentClass,
1465
+ signature: extractSignature(node, content),
1466
+ parameters: extractParameters(node, content),
1467
+ returnType: extractReturnType(node, content),
1468
+ complexity: calculateComplexity(node)
1469
+ };
1470
+ }
1471
+ if (type === "class_declaration") {
1472
+ const nameNode = node.childForFieldName("name");
1473
+ if (!nameNode) return null;
1474
+ return {
1475
+ name: nameNode.text,
1476
+ type: "class",
1477
+ startLine: node.startPosition.row + 1,
1478
+ endLine: node.endPosition.row + 1,
1479
+ signature: `class ${nameNode.text}`
1480
+ };
1481
+ }
1482
+ if (type === "interface_declaration") {
1483
+ const nameNode = node.childForFieldName("name");
1484
+ if (!nameNode) return null;
1485
+ return {
1486
+ name: nameNode.text,
1487
+ type: "interface",
1488
+ startLine: node.startPosition.row + 1,
1489
+ endLine: node.endPosition.row + 1,
1490
+ signature: `interface ${nameNode.text}`
1491
+ };
1492
+ }
1493
+ return null;
1494
+ }
1495
+ function extractSignature(node, content) {
1496
+ const startLine = node.startPosition.row;
1497
+ const lines = content.split("\n");
1498
+ let signature = lines[startLine] || "";
1499
+ let currentLine = startLine;
1500
+ while (currentLine < node.endPosition.row && !signature.includes("{") && !signature.includes("=>")) {
1501
+ currentLine++;
1502
+ signature += " " + (lines[currentLine] || "");
1503
+ }
1504
+ signature = signature.split("{")[0].split("=>")[0].trim();
1505
+ if (signature.length > 200) {
1506
+ signature = signature.substring(0, 197) + "...";
1507
+ }
1508
+ return signature;
1509
+ }
1510
+ function extractParameters(node, _content) {
1511
+ const parameters = [];
1512
+ const paramsNode = node.childForFieldName("parameters");
1513
+ if (!paramsNode) return parameters;
1514
+ for (let i = 0; i < paramsNode.namedChildCount; i++) {
1515
+ const param = paramsNode.namedChild(i);
1516
+ if (param) {
1517
+ parameters.push(param.text);
1518
+ }
1519
+ }
1520
+ return parameters;
1521
+ }
1522
+ function extractReturnType(node, _content) {
1523
+ const returnTypeNode = node.childForFieldName("return_type");
1524
+ if (!returnTypeNode) return void 0;
1525
+ return returnTypeNode.text;
1526
+ }
1527
+ function calculateComplexity(node) {
1528
+ let complexity = 1;
1529
+ const decisionPoints = [
1530
+ "if_statement",
1531
+ "while_statement",
1532
+ "do_statement",
1533
+ // do...while loops
1534
+ "for_statement",
1535
+ "for_in_statement",
1536
+ "for_of_statement",
1537
+ // for...of loops
1538
+ "switch_case",
1539
+ "catch_clause",
1540
+ "ternary_expression",
1541
+ "binary_expression"
1542
+ // For && and ||
1543
+ ];
1544
+ function traverse(n) {
1545
+ if (decisionPoints.includes(n.type)) {
1546
+ if (n.type === "binary_expression") {
1547
+ const operator = n.childForFieldName("operator");
1548
+ if (operator && (operator.text === "&&" || operator.text === "||")) {
1549
+ complexity++;
1550
+ }
1551
+ } else {
1552
+ complexity++;
1553
+ }
1554
+ }
1555
+ for (let i = 0; i < n.namedChildCount; i++) {
1556
+ const child = n.namedChild(i);
1557
+ if (child) traverse(child);
1558
+ }
1559
+ }
1560
+ traverse(node);
1561
+ return complexity;
1562
+ }
1563
+ function extractImports(rootNode) {
1564
+ const imports = [];
1565
+ function traverse(node) {
1566
+ if (node.type === "import_statement") {
1567
+ const sourceNode = node.childForFieldName("source");
1568
+ if (sourceNode) {
1569
+ const importPath = sourceNode.text.replace(/['"]/g, "");
1570
+ imports.push(importPath);
1571
+ }
1572
+ }
1573
+ if (node === rootNode) {
1574
+ for (let i = 0; i < node.namedChildCount; i++) {
1575
+ const child = node.namedChild(i);
1576
+ if (child) traverse(child);
1577
+ }
1578
+ }
1579
+ }
1580
+ traverse(rootNode);
1581
+ return imports;
1582
+ }
1583
+ var init_symbols = __esm({
1584
+ "src/indexer/ast/symbols.ts"() {
1585
+ "use strict";
1586
+ }
1587
+ });
1588
+
1589
+ // src/indexer/ast/chunker.ts
1590
+ function chunkByAST(filepath, content, options = {}) {
1591
+ const { minChunkSize = 5 } = options;
1592
+ const language = detectLanguage2(filepath);
1593
+ if (!language) {
1594
+ throw new Error(`Unsupported language for file: ${filepath}`);
1595
+ }
1596
+ const parseResult = parseAST(content, language);
1597
+ if (!parseResult.tree) {
1598
+ throw new Error(`Failed to parse ${filepath}: ${parseResult.error}`);
1599
+ }
1600
+ const chunks = [];
1601
+ const lines = content.split("\n");
1602
+ const rootNode = parseResult.tree.rootNode;
1603
+ const fileImports = extractImports(rootNode);
1604
+ const topLevelNodes = findTopLevelNodes(rootNode);
1605
+ for (const node of topLevelNodes) {
1606
+ let actualNode = node;
1607
+ if (node.type === "lexical_declaration" || node.type === "variable_declaration") {
1608
+ const funcNode = findActualFunctionNode(node);
1609
+ if (funcNode) {
1610
+ actualNode = funcNode;
1611
+ }
1612
+ }
1613
+ let parentClassName;
1614
+ if (actualNode.type === "method_definition") {
1615
+ parentClassName = findParentClassName(actualNode);
1616
+ }
1617
+ const symbolInfo = extractSymbolInfo(actualNode, content, parentClassName);
1618
+ const nodeContent = getNodeContent(node, lines);
1619
+ chunks.push(createChunk(filepath, node, nodeContent, symbolInfo, fileImports, language));
1620
+ }
1621
+ const coveredRanges = topLevelNodes.map((n) => ({
1622
+ start: n.startPosition.row,
1623
+ end: n.endPosition.row
1624
+ }));
1625
+ const uncoveredChunks = extractUncoveredCode(
1626
+ lines,
1627
+ coveredRanges,
1628
+ filepath,
1629
+ minChunkSize,
1630
+ fileImports,
1631
+ language
1632
+ );
1633
+ chunks.push(...uncoveredChunks);
1634
+ chunks.sort((a, b) => a.metadata.startLine - b.metadata.startLine);
1635
+ return chunks;
1636
+ }
1637
+ function findParentClassName(methodNode) {
1638
+ let current = methodNode.parent;
1639
+ while (current) {
1640
+ if (current.type === "class_declaration") {
1641
+ const nameNode = current.childForFieldName("name");
1642
+ return nameNode?.text;
1643
+ }
1644
+ current = current.parent;
1645
+ }
1646
+ return void 0;
1647
+ }
1648
+ function findTopLevelNodes(rootNode) {
1649
+ const nodes = [];
1650
+ const targetTypes = [
1651
+ "function_declaration",
1652
+ "function",
1653
+ // Note: 'class_declaration' is NOT included here - we extract methods individually
1654
+ "interface_declaration",
1655
+ "method_definition",
1656
+ "lexical_declaration",
1657
+ // For const/let with arrow functions
1658
+ "variable_declaration"
1659
+ // For var with functions
1660
+ ];
1661
+ function traverse(node, depth) {
1662
+ if ((node.type === "lexical_declaration" || node.type === "variable_declaration") && depth === 0) {
1663
+ const hasFunction = findFunctionInDeclaration(node);
1664
+ if (hasFunction) {
1665
+ nodes.push(node);
1666
+ return;
1667
+ }
1668
+ }
1669
+ if (depth <= 1 && targetTypes.includes(node.type)) {
1670
+ nodes.push(node);
1671
+ return;
1672
+ }
1673
+ if (node.type === "class_body") {
1674
+ for (let i = 0; i < node.namedChildCount; i++) {
1675
+ const child = node.namedChild(i);
1676
+ if (child) traverse(child, depth);
1677
+ }
1678
+ return;
1679
+ }
1680
+ if (node.type === "class_declaration") {
1681
+ const body = node.childForFieldName("body");
1682
+ if (body) {
1683
+ traverse(body, depth + 1);
1684
+ }
1685
+ return;
1686
+ }
1687
+ if (node.type === "program" || node.type === "export_statement") {
1688
+ for (let i = 0; i < node.namedChildCount; i++) {
1689
+ const child = node.namedChild(i);
1690
+ if (child) traverse(child, depth);
1691
+ }
1692
+ }
1693
+ }
1694
+ traverse(rootNode, 0);
1695
+ return nodes;
1696
+ }
1697
+ function findFunctionInDeclaration(node) {
1698
+ const functionTypes = ["arrow_function", "function_expression", "function"];
1699
+ function search(n, depth) {
1700
+ if (depth > 3) return false;
1701
+ if (functionTypes.includes(n.type)) {
1702
+ return true;
1703
+ }
1704
+ for (let i = 0; i < n.childCount; i++) {
1705
+ const child = n.child(i);
1706
+ if (child && search(child, depth + 1)) {
1707
+ return true;
1708
+ }
1709
+ }
1710
+ return false;
1711
+ }
1712
+ return search(node, 0);
1713
+ }
1714
+ function findActualFunctionNode(node) {
1715
+ const functionTypes = ["arrow_function", "function_expression", "function"];
1716
+ function search(n, depth) {
1717
+ if (depth > 3) return null;
1718
+ if (functionTypes.includes(n.type)) {
1719
+ return n;
1720
+ }
1721
+ for (let i = 0; i < n.childCount; i++) {
1722
+ const child = n.child(i);
1723
+ if (child) {
1724
+ const result = search(child, depth + 1);
1725
+ if (result) return result;
1726
+ }
1727
+ }
1728
+ return null;
1729
+ }
1730
+ return search(node, 0);
1731
+ }
1732
+ function getNodeContent(node, lines) {
1733
+ const startLine = node.startPosition.row;
1734
+ const endLine = node.endPosition.row;
1735
+ return lines.slice(startLine, endLine + 1).join("\n");
1736
+ }
1737
+ function createChunk(filepath, node, content, symbolInfo, imports, language) {
1738
+ const symbols = {
1739
+ functions: [],
1740
+ classes: [],
1741
+ interfaces: []
1742
+ };
1743
+ if (symbolInfo?.name) {
1744
+ if (symbolInfo.type === "function" || symbolInfo.type === "method") {
1745
+ symbols.functions.push(symbolInfo.name);
1746
+ } else if (symbolInfo.type === "class") {
1747
+ symbols.classes.push(symbolInfo.name);
1748
+ } else if (symbolInfo.type === "interface") {
1749
+ symbols.interfaces.push(symbolInfo.name);
1750
+ }
1751
+ }
1752
+ return {
1753
+ content,
1754
+ metadata: {
1755
+ file: filepath,
1756
+ startLine: node.startPosition.row + 1,
1757
+ endLine: node.endPosition.row + 1,
1758
+ type: symbolInfo == null ? "block" : symbolInfo.type === "class" ? "class" : "function",
1759
+ language,
1760
+ // Legacy symbols field for backward compatibility
1761
+ symbols,
1762
+ // New AST-derived metadata
1763
+ symbolName: symbolInfo?.name,
1764
+ symbolType: symbolInfo?.type,
1765
+ parentClass: symbolInfo?.parentClass,
1766
+ complexity: symbolInfo?.complexity,
1767
+ parameters: symbolInfo?.parameters,
1768
+ signature: symbolInfo?.signature,
1769
+ imports
1770
+ }
1771
+ };
1772
+ }
1773
+ function extractUncoveredCode(lines, coveredRanges, filepath, minChunkSize, imports, language) {
1774
+ const chunks = [];
1775
+ let currentStart = 0;
1776
+ coveredRanges.sort((a, b) => a.start - b.start);
1777
+ for (const range of coveredRanges) {
1778
+ if (currentStart < range.start) {
1779
+ const uncoveredLines = lines.slice(currentStart, range.start);
1780
+ const content = uncoveredLines.join("\n").trim();
1781
+ if (content.length > 0 && uncoveredLines.length >= minChunkSize) {
1782
+ chunks.push({
1783
+ content,
1784
+ metadata: {
1785
+ file: filepath,
1786
+ startLine: currentStart + 1,
1787
+ endLine: range.start,
1788
+ type: "block",
1789
+ language,
1790
+ // Empty symbols for uncovered code (imports, exports, etc.)
1791
+ symbols: { functions: [], classes: [], interfaces: [] },
1792
+ imports
1793
+ }
1794
+ });
1795
+ }
1796
+ }
1797
+ currentStart = range.end + 1;
1798
+ }
1799
+ if (currentStart < lines.length) {
1800
+ const uncoveredLines = lines.slice(currentStart);
1801
+ const content = uncoveredLines.join("\n").trim();
1802
+ if (content.length > 0 && uncoveredLines.length >= minChunkSize) {
1803
+ chunks.push({
1804
+ content,
1805
+ metadata: {
1806
+ file: filepath,
1807
+ startLine: currentStart + 1,
1808
+ endLine: lines.length,
1809
+ type: "block",
1810
+ language,
1811
+ // Empty symbols for uncovered code (imports, exports, etc.)
1812
+ symbols: { functions: [], classes: [], interfaces: [] },
1813
+ imports
1814
+ }
1815
+ });
1816
+ }
1817
+ }
1818
+ return chunks;
1819
+ }
1820
+ function shouldUseAST(filepath) {
1821
+ return isASTSupported(filepath);
1822
+ }
1823
+ var init_chunker = __esm({
1824
+ "src/indexer/ast/chunker.ts"() {
1825
+ "use strict";
1826
+ init_parser();
1827
+ init_symbols();
1828
+ }
1829
+ });
1830
+
1308
1831
  // src/indexer/chunker.ts
1309
1832
  function chunkFile(filepath, content, options = {}) {
1310
- const { chunkSize = 75, chunkOverlap = 10 } = options;
1833
+ const { chunkSize = 75, chunkOverlap = 10, useAST = true, astFallback = "line-based" } = options;
1834
+ if (useAST && shouldUseAST(filepath)) {
1835
+ try {
1836
+ return chunkByAST(filepath, content, {
1837
+ minChunkSize: Math.floor(chunkSize / 10)
1838
+ });
1839
+ } catch (error) {
1840
+ if (astFallback === "error") {
1841
+ throw new Error(`AST chunking failed for ${filepath}: ${error instanceof Error ? error.message : String(error)}`);
1842
+ }
1843
+ console.warn(`AST chunking failed for ${filepath}, falling back to line-based:`, error);
1844
+ }
1845
+ }
1846
+ return chunkByLines(filepath, content, chunkSize, chunkOverlap);
1847
+ }
1848
+ function chunkByLines(filepath, content, chunkSize, chunkOverlap) {
1311
1849
  const lines = content.split("\n");
1312
1850
  const chunks = [];
1313
1851
  const language = detectLanguage(filepath);
@@ -1340,11 +1878,12 @@ function chunkFile(filepath, content, options = {}) {
1340
1878
  }
1341
1879
  return chunks;
1342
1880
  }
1343
- var init_chunker = __esm({
1881
+ var init_chunker2 = __esm({
1344
1882
  "src/indexer/chunker.ts"() {
1345
1883
  "use strict";
1346
1884
  init_scanner();
1347
1885
  init_symbol_extractor();
1886
+ init_chunker();
1348
1887
  }
1349
1888
  });
1350
1889
 
@@ -1606,7 +2145,7 @@ var init_lancedb = __esm({
1606
2145
  "src/vectordb/lancedb.ts"() {
1607
2146
  "use strict";
1608
2147
  init_types();
1609
- init_version();
2148
+ init_version2();
1610
2149
  init_errors();
1611
2150
  init_relevance();
1612
2151
  init_intent_classifier();
@@ -1691,7 +2230,15 @@ var init_lancedb = __esm({
1691
2230
  // Ensure arrays have at least empty string for Arrow type inference
1692
2231
  functionNames: batch.metadatas[i].symbols?.functions && batch.metadatas[i].symbols.functions.length > 0 ? batch.metadatas[i].symbols.functions : [""],
1693
2232
  classNames: batch.metadatas[i].symbols?.classes && batch.metadatas[i].symbols.classes.length > 0 ? batch.metadatas[i].symbols.classes : [""],
1694
- interfaceNames: batch.metadatas[i].symbols?.interfaces && batch.metadatas[i].symbols.interfaces.length > 0 ? batch.metadatas[i].symbols.interfaces : [""]
2233
+ interfaceNames: batch.metadatas[i].symbols?.interfaces && batch.metadatas[i].symbols.interfaces.length > 0 ? batch.metadatas[i].symbols.interfaces : [""],
2234
+ // AST-derived metadata (v0.13.0)
2235
+ symbolName: batch.metadatas[i].symbolName || "",
2236
+ symbolType: batch.metadatas[i].symbolType || "",
2237
+ parentClass: batch.metadatas[i].parentClass || "",
2238
+ complexity: batch.metadatas[i].complexity || 0,
2239
+ parameters: batch.metadatas[i].parameters && batch.metadatas[i].parameters.length > 0 ? batch.metadatas[i].parameters : [""],
2240
+ signature: batch.metadatas[i].signature || "",
2241
+ imports: batch.metadatas[i].imports && batch.metadatas[i].imports.length > 0 ? batch.metadatas[i].imports : [""]
1695
2242
  }));
1696
2243
  if (!this.table) {
1697
2244
  this.table = await this.db.createTable(this.tableName, records);
@@ -1746,7 +2293,15 @@ var init_lancedb = __esm({
1746
2293
  startLine: r.startLine,
1747
2294
  endLine: r.endLine,
1748
2295
  type: r.type,
1749
- language: r.language
2296
+ language: r.language,
2297
+ // AST-derived metadata (v0.13.0)
2298
+ symbolName: r.symbolName || void 0,
2299
+ symbolType: r.symbolType,
2300
+ parentClass: r.parentClass || void 0,
2301
+ complexity: r.complexity || void 0,
2302
+ parameters: r.parameters && r.parameters.length > 0 && r.parameters[0] !== "" ? r.parameters : void 0,
2303
+ signature: r.signature || void 0,
2304
+ imports: r.imports && r.imports.length > 0 && r.imports[0] !== "" ? r.imports : void 0
1750
2305
  },
1751
2306
  score: boostedScore,
1752
2307
  relevance: calculateRelevance(boostedScore)
@@ -1819,7 +2374,15 @@ var init_lancedb = __esm({
1819
2374
  startLine: r.startLine,
1820
2375
  endLine: r.endLine,
1821
2376
  type: r.type,
1822
- language: r.language
2377
+ language: r.language,
2378
+ // AST-derived metadata (v0.13.0)
2379
+ symbolName: r.symbolName || void 0,
2380
+ symbolType: r.symbolType,
2381
+ parentClass: r.parentClass || void 0,
2382
+ complexity: r.complexity || void 0,
2383
+ parameters: r.parameters && r.parameters.length > 0 && r.parameters[0] !== "" ? r.parameters : void 0,
2384
+ signature: r.signature || void 0,
2385
+ imports: r.imports && r.imports.length > 0 && r.imports[0] !== "" ? r.imports : void 0
1823
2386
  },
1824
2387
  score,
1825
2388
  relevance: calculateRelevance(score)
@@ -1849,12 +2412,43 @@ var init_lancedb = __esm({
1849
2412
  return false;
1850
2413
  }
1851
2414
  const symbols = symbolType === "function" ? r.functionNames || [] : symbolType === "class" ? r.classNames || [] : symbolType === "interface" ? r.interfaceNames || [] : [...r.functionNames || [], ...r.classNames || [], ...r.interfaceNames || []];
1852
- if (symbols.length === 0) {
2415
+ const astSymbolName = r.symbolName || "";
2416
+ if (symbols.length === 0 && !astSymbolName) {
1853
2417
  return false;
1854
2418
  }
1855
2419
  if (pattern) {
1856
2420
  const regex = new RegExp(pattern, "i");
1857
- return symbols.some((s) => regex.test(s));
2421
+ const matchesOldSymbols = symbols.some((s) => regex.test(s));
2422
+ const matchesASTSymbol = regex.test(astSymbolName);
2423
+ const nameMatches = matchesOldSymbols || matchesASTSymbol;
2424
+ if (!nameMatches) return false;
2425
+ if (symbolType) {
2426
+ if (r.symbolType) {
2427
+ if (symbolType === "function") {
2428
+ return r.symbolType === "function" || r.symbolType === "method";
2429
+ } else if (symbolType === "class") {
2430
+ return r.symbolType === "class";
2431
+ } else if (symbolType === "interface") {
2432
+ return r.symbolType === "interface";
2433
+ }
2434
+ return false;
2435
+ }
2436
+ return nameMatches;
2437
+ }
2438
+ return nameMatches;
2439
+ }
2440
+ if (symbolType) {
2441
+ if (r.symbolType) {
2442
+ if (symbolType === "function") {
2443
+ return r.symbolType === "function" || r.symbolType === "method";
2444
+ } else if (symbolType === "class") {
2445
+ return r.symbolType === "class";
2446
+ } else if (symbolType === "interface") {
2447
+ return r.symbolType === "interface";
2448
+ }
2449
+ return false;
2450
+ }
2451
+ return symbols.length > 0 && symbols.some((s) => s.length > 0 && s !== "");
1858
2452
  }
1859
2453
  return true;
1860
2454
  });
@@ -1872,7 +2466,15 @@ var init_lancedb = __esm({
1872
2466
  functions: r.functionNames || [],
1873
2467
  classes: r.classNames || [],
1874
2468
  interfaces: r.interfaceNames || []
1875
- }
2469
+ },
2470
+ // AST-derived metadata (v0.13.0)
2471
+ symbolName: r.symbolName || void 0,
2472
+ symbolType: r.symbolType,
2473
+ parentClass: r.parentClass || void 0,
2474
+ complexity: r.complexity || void 0,
2475
+ parameters: r.parameters && r.parameters.length > 0 && r.parameters[0] !== "" ? r.parameters : void 0,
2476
+ signature: r.signature || void 0,
2477
+ imports: r.imports && r.imports.length > 0 && r.imports[0] !== "" ? r.imports : void 0
1876
2478
  },
1877
2479
  score,
1878
2480
  relevance: calculateRelevance(score)
@@ -2023,33 +2625,6 @@ var init_lancedb = __esm({
2023
2625
  }
2024
2626
  });
2025
2627
 
2026
- // src/utils/version.ts
2027
- import { createRequire as createRequire2 } from "module";
2028
- import { fileURLToPath as fileURLToPath3 } from "url";
2029
- import { dirname as dirname2, join as join2 } from "path";
2030
- function getPackageVersion() {
2031
- return packageJson2.version;
2032
- }
2033
- var __filename3, __dirname3, require3, packageJson2;
2034
- var init_version2 = __esm({
2035
- "src/utils/version.ts"() {
2036
- "use strict";
2037
- __filename3 = fileURLToPath3(import.meta.url);
2038
- __dirname3 = dirname2(__filename3);
2039
- require3 = createRequire2(import.meta.url);
2040
- try {
2041
- packageJson2 = require3(join2(__dirname3, "../package.json"));
2042
- } catch {
2043
- try {
2044
- packageJson2 = require3(join2(__dirname3, "../../package.json"));
2045
- } catch {
2046
- console.warn("[Lien] Warning: Could not load package.json, using fallback version");
2047
- packageJson2 = { version: "0.0.0-unknown" };
2048
- }
2049
- }
2050
- }
2051
- });
2052
-
2053
2628
  // src/indexer/manifest.ts
2054
2629
  var manifest_exports = {};
2055
2630
  __export(manifest_exports, {
@@ -2062,7 +2637,7 @@ var init_manifest = __esm({
2062
2637
  "src/indexer/manifest.ts"() {
2063
2638
  "use strict";
2064
2639
  init_constants();
2065
- init_version2();
2640
+ init_version();
2066
2641
  MANIFEST_FILE = "manifest.json";
2067
2642
  ManifestManager = class {
2068
2643
  manifestPath;
@@ -2622,9 +3197,13 @@ import fs14 from "fs/promises";
2622
3197
  async function processFileContent(filepath, content, embeddings, config, verbose) {
2623
3198
  const chunkSize = isModernConfig(config) ? config.core.chunkSize : isLegacyConfig(config) ? config.indexing.chunkSize : 75;
2624
3199
  const chunkOverlap = isModernConfig(config) ? config.core.chunkOverlap : isLegacyConfig(config) ? config.indexing.chunkOverlap : 10;
3200
+ const useAST = isModernConfig(config) ? config.chunking.useAST : true;
3201
+ const astFallback = isModernConfig(config) ? config.chunking.astFallback : "line-based";
2625
3202
  const chunks = chunkFile(filepath, content, {
2626
3203
  chunkSize,
2627
- chunkOverlap
3204
+ chunkOverlap,
3205
+ useAST,
3206
+ astFallback
2628
3207
  });
2629
3208
  if (chunks.length === 0) {
2630
3209
  if (verbose) {
@@ -2775,7 +3354,7 @@ async function indexMultipleFiles(filepaths, vectorDB, embeddings, config, optio
2775
3354
  var init_incremental = __esm({
2776
3355
  "src/indexer/incremental.ts"() {
2777
3356
  "use strict";
2778
- init_chunker();
3357
+ init_chunker2();
2779
3358
  init_schema();
2780
3359
  init_manifest();
2781
3360
  init_constants();
@@ -3037,9 +3616,13 @@ async function indexCodebase(options = {}) {
3037
3616
  const content = await fs15.readFile(file, "utf-8");
3038
3617
  const chunkSize = isModernConfig(config) ? config.core.chunkSize : 75;
3039
3618
  const chunkOverlap = isModernConfig(config) ? config.core.chunkOverlap : 10;
3619
+ const useAST = isModernConfig(config) ? config.chunking.useAST : true;
3620
+ const astFallback = isModernConfig(config) ? config.chunking.astFallback : "line-based";
3040
3621
  const chunks = chunkFile(file, content, {
3041
3622
  chunkSize,
3042
- chunkOverlap
3623
+ chunkOverlap,
3624
+ useAST,
3625
+ astFallback
3043
3626
  });
3044
3627
  if (chunks.length === 0) {
3045
3628
  processedFiles++;
@@ -3117,11 +3700,11 @@ var init_indexer = __esm({
3117
3700
  "src/indexer/index.ts"() {
3118
3701
  "use strict";
3119
3702
  init_scanner();
3120
- init_chunker();
3703
+ init_chunker2();
3121
3704
  init_local();
3122
3705
  init_lancedb();
3123
3706
  init_service();
3124
- init_version();
3707
+ init_version2();
3125
3708
  init_schema();
3126
3709
  init_manifest();
3127
3710
  init_change_detector();
@@ -3142,27 +3725,27 @@ init_schema();
3142
3725
  init_merge();
3143
3726
  import fs5 from "fs/promises";
3144
3727
  import path5 from "path";
3145
- import { fileURLToPath as fileURLToPath2 } from "url";
3728
+ import { fileURLToPath as fileURLToPath3 } from "url";
3146
3729
  import chalk2 from "chalk";
3147
3730
  import inquirer from "inquirer";
3148
3731
 
3149
3732
  // src/utils/banner.ts
3150
3733
  import figlet from "figlet";
3151
3734
  import chalk from "chalk";
3152
- import { createRequire } from "module";
3153
- import { fileURLToPath } from "url";
3154
- import { dirname, join } from "path";
3155
- var __filename = fileURLToPath(import.meta.url);
3156
- var __dirname = dirname(__filename);
3157
- var require2 = createRequire(import.meta.url);
3158
- var packageJson;
3735
+ import { createRequire as createRequire2 } from "module";
3736
+ import { fileURLToPath as fileURLToPath2 } from "url";
3737
+ import { dirname as dirname2, join as join2 } from "path";
3738
+ var __filename2 = fileURLToPath2(import.meta.url);
3739
+ var __dirname2 = dirname2(__filename2);
3740
+ var require3 = createRequire2(import.meta.url);
3741
+ var packageJson2;
3159
3742
  try {
3160
- packageJson = require2(join(__dirname, "../package.json"));
3743
+ packageJson2 = require3(join2(__dirname2, "../package.json"));
3161
3744
  } catch {
3162
- packageJson = require2(join(__dirname, "../../package.json"));
3745
+ packageJson2 = require3(join2(__dirname2, "../../package.json"));
3163
3746
  }
3164
- var PACKAGE_NAME = packageJson.name;
3165
- var VERSION = packageJson.version;
3747
+ var PACKAGE_NAME = packageJson2.name;
3748
+ var VERSION = packageJson2.version;
3166
3749
  function wrapInBox(text, footer, padding = 1) {
3167
3750
  const lines = text.split("\n").filter((line) => line.trim().length > 0);
3168
3751
  const maxLength = Math.max(...lines.map((line) => line.length));
@@ -3711,8 +4294,9 @@ async function scanSubdirectories(rootDir, relativePath, results, visited, depth
3711
4294
  }
3712
4295
 
3713
4296
  // src/cli/init.ts
3714
- var __filename2 = fileURLToPath2(import.meta.url);
3715
- var __dirname2 = path5.dirname(__filename2);
4297
+ init_constants();
4298
+ var __filename3 = fileURLToPath3(import.meta.url);
4299
+ var __dirname3 = path5.dirname(__filename3);
3716
4300
  async function initCommand(options = {}) {
3717
4301
  const rootDir = options.path || process.cwd();
3718
4302
  const configPath = path5.join(rootDir, ".lien.config.json");
@@ -3857,7 +4441,7 @@ async function createNewConfig(rootDir, options) {
3857
4441
  try {
3858
4442
  const cursorRulesDir = path5.join(rootDir, ".cursor");
3859
4443
  await fs5.mkdir(cursorRulesDir, { recursive: true });
3860
- const templatePath = path5.join(__dirname2, "../CURSOR_RULES_TEMPLATE.md");
4444
+ const templatePath = path5.join(__dirname3, "../CURSOR_RULES_TEMPLATE.md");
3861
4445
  const rulesPath = path5.join(cursorRulesDir, "rules");
3862
4446
  let targetPath;
3863
4447
  let isDirectory = false;
@@ -3945,23 +4529,28 @@ Customizing ${frameworkName} settings:`));
3945
4529
  }
3946
4530
  async function upgradeConfig(configPath) {
3947
4531
  try {
3948
- const backupPath = `${configPath}.backup`;
3949
- await fs5.copyFile(configPath, backupPath);
3950
4532
  const existingContent = await fs5.readFile(configPath, "utf-8");
3951
4533
  const existingConfig = JSON.parse(existingContent);
4534
+ const migrationNeeded = needsMigration(existingConfig);
4535
+ const newFields = migrationNeeded ? [] : detectNewFields(existingConfig, defaultConfig);
4536
+ const hasChanges = migrationNeeded || newFields.length > 0;
4537
+ if (!hasChanges) {
4538
+ console.log(chalk2.green("\u2713 Config is already up to date"));
4539
+ console.log(chalk2.dim("No changes needed"));
4540
+ return;
4541
+ }
4542
+ const backupPath = `${configPath}.backup`;
4543
+ await fs5.copyFile(configPath, backupPath);
3952
4544
  let upgradedConfig;
3953
4545
  let migrated = false;
3954
- if (needsMigration(existingConfig)) {
3955
- console.log(chalk2.blue("\u{1F504} Migrating config from v0.2.0 to v0.3.0..."));
4546
+ if (migrationNeeded) {
4547
+ console.log(chalk2.blue(`\u{1F504} Migrating config from v0.2.0 to v${CURRENT_CONFIG_VERSION}...`));
3956
4548
  upgradedConfig = migrateConfig(existingConfig);
3957
4549
  migrated = true;
3958
4550
  } else {
3959
- const newFields = detectNewFields(existingConfig, defaultConfig);
3960
4551
  upgradedConfig = deepMergeConfig(defaultConfig, existingConfig);
3961
- if (newFields.length > 0) {
3962
- console.log(chalk2.dim("\nNew options added:"));
3963
- newFields.forEach((field) => console.log(chalk2.dim(" \u2022"), chalk2.bold(field)));
3964
- }
4552
+ console.log(chalk2.dim("\nNew options added:"));
4553
+ newFields.forEach((field) => console.log(chalk2.dim(" \u2022"), chalk2.bold(field)));
3965
4554
  }
3966
4555
  await fs5.writeFile(
3967
4556
  configPath,
@@ -3982,7 +4571,7 @@ async function upgradeConfig(configPath) {
3982
4571
  // src/cli/status.ts
3983
4572
  init_service();
3984
4573
  init_utils();
3985
- init_version();
4574
+ init_version2();
3986
4575
  import chalk3 from "chalk";
3987
4576
  import fs9 from "fs/promises";
3988
4577
  import path9 from "path";