@liendev/lien 0.11.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,14 @@ var init_migration = __esm({
207
252
  "src/config/migration.ts"() {
208
253
  "use strict";
209
254
  init_schema();
255
+ init_constants();
256
+ }
257
+ });
258
+
259
+ // src/errors/codes.ts
260
+ var init_codes = __esm({
261
+ "src/errors/codes.ts"() {
262
+ "use strict";
210
263
  }
211
264
  });
212
265
 
@@ -216,7 +269,7 @@ function wrapError(error, context, additionalContext) {
216
269
  const stack = error instanceof Error ? error.stack : void 0;
217
270
  const wrappedError = new LienError(
218
271
  `${context}: ${message}`,
219
- "WRAPPED_ERROR",
272
+ "INTERNAL_ERROR" /* INTERNAL_ERROR */,
220
273
  additionalContext
221
274
  );
222
275
  if (stack) {
@@ -231,32 +284,61 @@ var LienError, ConfigError, EmbeddingError, DatabaseError;
231
284
  var init_errors = __esm({
232
285
  "src/errors/index.ts"() {
233
286
  "use strict";
287
+ init_codes();
288
+ init_codes();
234
289
  LienError = class extends Error {
235
- constructor(message, code, context) {
290
+ constructor(message, code, context, severity = "medium", recoverable = true, retryable = false) {
236
291
  super(message);
237
292
  this.code = code;
238
293
  this.context = context;
294
+ this.severity = severity;
295
+ this.recoverable = recoverable;
296
+ this.retryable = retryable;
239
297
  this.name = "LienError";
240
298
  if (Error.captureStackTrace) {
241
299
  Error.captureStackTrace(this, this.constructor);
242
300
  }
243
301
  }
302
+ /**
303
+ * Serialize error to JSON for MCP responses
304
+ */
305
+ toJSON() {
306
+ return {
307
+ error: this.message,
308
+ code: this.code,
309
+ severity: this.severity,
310
+ recoverable: this.recoverable,
311
+ context: this.context
312
+ };
313
+ }
314
+ /**
315
+ * Check if this error is retryable
316
+ */
317
+ isRetryable() {
318
+ return this.retryable;
319
+ }
320
+ /**
321
+ * Check if this error is recoverable
322
+ */
323
+ isRecoverable() {
324
+ return this.recoverable;
325
+ }
244
326
  };
245
327
  ConfigError = class extends LienError {
246
328
  constructor(message, context) {
247
- super(message, "CONFIG_ERROR", context);
329
+ super(message, "CONFIG_INVALID" /* CONFIG_INVALID */, context, "medium", true, false);
248
330
  this.name = "ConfigError";
249
331
  }
250
332
  };
251
333
  EmbeddingError = class extends LienError {
252
334
  constructor(message, context) {
253
- super(message, "EMBEDDING_ERROR", context);
335
+ super(message, "EMBEDDING_GENERATION_FAILED" /* EMBEDDING_GENERATION_FAILED */, context, "high", true, true);
254
336
  this.name = "EmbeddingError";
255
337
  }
256
338
  };
257
339
  DatabaseError = class extends LienError {
258
340
  constructor(message, context) {
259
- super(message, "DATABASE_ERROR", context);
341
+ super(message, "INTERNAL_ERROR" /* INTERNAL_ERROR */, context, "high", true, true);
260
342
  this.name = "DatabaseError";
261
343
  }
262
344
  };
@@ -839,7 +921,7 @@ async function readVersionFile(indexPath) {
839
921
  }
840
922
  }
841
923
  var VERSION_FILE;
842
- var init_version = __esm({
924
+ var init_version2 = __esm({
843
925
  "src/vectordb/version.ts"() {
844
926
  "use strict";
845
927
  VERSION_FILE = ".lien-index-version";
@@ -1269,9 +1351,501 @@ var init_symbol_extractor = __esm({
1269
1351
  }
1270
1352
  });
1271
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
+
1272
1831
  // src/indexer/chunker.ts
1273
1832
  function chunkFile(filepath, content, options = {}) {
1274
- 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) {
1275
1849
  const lines = content.split("\n");
1276
1850
  const chunks = [];
1277
1851
  const language = detectLanguage(filepath);
@@ -1304,11 +1878,12 @@ function chunkFile(filepath, content, options = {}) {
1304
1878
  }
1305
1879
  return chunks;
1306
1880
  }
1307
- var init_chunker = __esm({
1881
+ var init_chunker2 = __esm({
1308
1882
  "src/indexer/chunker.ts"() {
1309
1883
  "use strict";
1310
1884
  init_scanner();
1311
1885
  init_symbol_extractor();
1886
+ init_chunker();
1312
1887
  }
1313
1888
  });
1314
1889
 
@@ -1570,7 +2145,7 @@ var init_lancedb = __esm({
1570
2145
  "src/vectordb/lancedb.ts"() {
1571
2146
  "use strict";
1572
2147
  init_types();
1573
- init_version();
2148
+ init_version2();
1574
2149
  init_errors();
1575
2150
  init_relevance();
1576
2151
  init_intent_classifier();
@@ -1655,7 +2230,15 @@ var init_lancedb = __esm({
1655
2230
  // Ensure arrays have at least empty string for Arrow type inference
1656
2231
  functionNames: batch.metadatas[i].symbols?.functions && batch.metadatas[i].symbols.functions.length > 0 ? batch.metadatas[i].symbols.functions : [""],
1657
2232
  classNames: batch.metadatas[i].symbols?.classes && batch.metadatas[i].symbols.classes.length > 0 ? batch.metadatas[i].symbols.classes : [""],
1658
- 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 : [""]
1659
2242
  }));
1660
2243
  if (!this.table) {
1661
2244
  this.table = await this.db.createTable(this.tableName, records);
@@ -1710,7 +2293,15 @@ var init_lancedb = __esm({
1710
2293
  startLine: r.startLine,
1711
2294
  endLine: r.endLine,
1712
2295
  type: r.type,
1713
- 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
1714
2305
  },
1715
2306
  score: boostedScore,
1716
2307
  relevance: calculateRelevance(boostedScore)
@@ -1783,7 +2374,15 @@ var init_lancedb = __esm({
1783
2374
  startLine: r.startLine,
1784
2375
  endLine: r.endLine,
1785
2376
  type: r.type,
1786
- 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
1787
2386
  },
1788
2387
  score,
1789
2388
  relevance: calculateRelevance(score)
@@ -1813,12 +2412,43 @@ var init_lancedb = __esm({
1813
2412
  return false;
1814
2413
  }
1815
2414
  const symbols = symbolType === "function" ? r.functionNames || [] : symbolType === "class" ? r.classNames || [] : symbolType === "interface" ? r.interfaceNames || [] : [...r.functionNames || [], ...r.classNames || [], ...r.interfaceNames || []];
1816
- if (symbols.length === 0) {
2415
+ const astSymbolName = r.symbolName || "";
2416
+ if (symbols.length === 0 && !astSymbolName) {
1817
2417
  return false;
1818
2418
  }
1819
2419
  if (pattern) {
1820
2420
  const regex = new RegExp(pattern, "i");
1821
- 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 !== "");
1822
2452
  }
1823
2453
  return true;
1824
2454
  });
@@ -1836,7 +2466,15 @@ var init_lancedb = __esm({
1836
2466
  functions: r.functionNames || [],
1837
2467
  classes: r.classNames || [],
1838
2468
  interfaces: r.interfaceNames || []
1839
- }
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
1840
2478
  },
1841
2479
  score,
1842
2480
  relevance: calculateRelevance(score)
@@ -1987,33 +2625,6 @@ var init_lancedb = __esm({
1987
2625
  }
1988
2626
  });
1989
2627
 
1990
- // src/utils/version.ts
1991
- import { createRequire as createRequire2 } from "module";
1992
- import { fileURLToPath as fileURLToPath3 } from "url";
1993
- import { dirname as dirname2, join as join2 } from "path";
1994
- function getPackageVersion() {
1995
- return packageJson2.version;
1996
- }
1997
- var __filename3, __dirname3, require3, packageJson2;
1998
- var init_version2 = __esm({
1999
- "src/utils/version.ts"() {
2000
- "use strict";
2001
- __filename3 = fileURLToPath3(import.meta.url);
2002
- __dirname3 = dirname2(__filename3);
2003
- require3 = createRequire2(import.meta.url);
2004
- try {
2005
- packageJson2 = require3(join2(__dirname3, "../package.json"));
2006
- } catch {
2007
- try {
2008
- packageJson2 = require3(join2(__dirname3, "../../package.json"));
2009
- } catch {
2010
- console.warn("[Lien] Warning: Could not load package.json, using fallback version");
2011
- packageJson2 = { version: "0.0.0-unknown" };
2012
- }
2013
- }
2014
- }
2015
- });
2016
-
2017
2628
  // src/indexer/manifest.ts
2018
2629
  var manifest_exports = {};
2019
2630
  __export(manifest_exports, {
@@ -2026,7 +2637,7 @@ var init_manifest = __esm({
2026
2637
  "src/indexer/manifest.ts"() {
2027
2638
  "use strict";
2028
2639
  init_constants();
2029
- init_version2();
2640
+ init_version();
2030
2641
  MANIFEST_FILE = "manifest.json";
2031
2642
  ManifestManager = class {
2032
2643
  manifestPath;
@@ -2586,9 +3197,13 @@ import fs14 from "fs/promises";
2586
3197
  async function processFileContent(filepath, content, embeddings, config, verbose) {
2587
3198
  const chunkSize = isModernConfig(config) ? config.core.chunkSize : isLegacyConfig(config) ? config.indexing.chunkSize : 75;
2588
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";
2589
3202
  const chunks = chunkFile(filepath, content, {
2590
3203
  chunkSize,
2591
- chunkOverlap
3204
+ chunkOverlap,
3205
+ useAST,
3206
+ astFallback
2592
3207
  });
2593
3208
  if (chunks.length === 0) {
2594
3209
  if (verbose) {
@@ -2739,7 +3354,7 @@ async function indexMultipleFiles(filepaths, vectorDB, embeddings, config, optio
2739
3354
  var init_incremental = __esm({
2740
3355
  "src/indexer/incremental.ts"() {
2741
3356
  "use strict";
2742
- init_chunker();
3357
+ init_chunker2();
2743
3358
  init_schema();
2744
3359
  init_manifest();
2745
3360
  init_constants();
@@ -3001,9 +3616,13 @@ async function indexCodebase(options = {}) {
3001
3616
  const content = await fs15.readFile(file, "utf-8");
3002
3617
  const chunkSize = isModernConfig(config) ? config.core.chunkSize : 75;
3003
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";
3004
3621
  const chunks = chunkFile(file, content, {
3005
3622
  chunkSize,
3006
- chunkOverlap
3623
+ chunkOverlap,
3624
+ useAST,
3625
+ astFallback
3007
3626
  });
3008
3627
  if (chunks.length === 0) {
3009
3628
  processedFiles++;
@@ -3081,11 +3700,11 @@ var init_indexer = __esm({
3081
3700
  "src/indexer/index.ts"() {
3082
3701
  "use strict";
3083
3702
  init_scanner();
3084
- init_chunker();
3703
+ init_chunker2();
3085
3704
  init_local();
3086
3705
  init_lancedb();
3087
3706
  init_service();
3088
- init_version();
3707
+ init_version2();
3089
3708
  init_schema();
3090
3709
  init_manifest();
3091
3710
  init_change_detector();
@@ -3106,27 +3725,27 @@ init_schema();
3106
3725
  init_merge();
3107
3726
  import fs5 from "fs/promises";
3108
3727
  import path5 from "path";
3109
- import { fileURLToPath as fileURLToPath2 } from "url";
3728
+ import { fileURLToPath as fileURLToPath3 } from "url";
3110
3729
  import chalk2 from "chalk";
3111
3730
  import inquirer from "inquirer";
3112
3731
 
3113
3732
  // src/utils/banner.ts
3114
3733
  import figlet from "figlet";
3115
3734
  import chalk from "chalk";
3116
- import { createRequire } from "module";
3117
- import { fileURLToPath } from "url";
3118
- import { dirname, join } from "path";
3119
- var __filename = fileURLToPath(import.meta.url);
3120
- var __dirname = dirname(__filename);
3121
- var require2 = createRequire(import.meta.url);
3122
- 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;
3123
3742
  try {
3124
- packageJson = require2(join(__dirname, "../package.json"));
3743
+ packageJson2 = require3(join2(__dirname2, "../package.json"));
3125
3744
  } catch {
3126
- packageJson = require2(join(__dirname, "../../package.json"));
3745
+ packageJson2 = require3(join2(__dirname2, "../../package.json"));
3127
3746
  }
3128
- var PACKAGE_NAME = packageJson.name;
3129
- var VERSION = packageJson.version;
3747
+ var PACKAGE_NAME = packageJson2.name;
3748
+ var VERSION = packageJson2.version;
3130
3749
  function wrapInBox(text, footer, padding = 1) {
3131
3750
  const lines = text.split("\n").filter((line) => line.trim().length > 0);
3132
3751
  const maxLength = Math.max(...lines.map((line) => line.length));
@@ -3675,8 +4294,9 @@ async function scanSubdirectories(rootDir, relativePath, results, visited, depth
3675
4294
  }
3676
4295
 
3677
4296
  // src/cli/init.ts
3678
- var __filename2 = fileURLToPath2(import.meta.url);
3679
- var __dirname2 = path5.dirname(__filename2);
4297
+ init_constants();
4298
+ var __filename3 = fileURLToPath3(import.meta.url);
4299
+ var __dirname3 = path5.dirname(__filename3);
3680
4300
  async function initCommand(options = {}) {
3681
4301
  const rootDir = options.path || process.cwd();
3682
4302
  const configPath = path5.join(rootDir, ".lien.config.json");
@@ -3821,7 +4441,7 @@ async function createNewConfig(rootDir, options) {
3821
4441
  try {
3822
4442
  const cursorRulesDir = path5.join(rootDir, ".cursor");
3823
4443
  await fs5.mkdir(cursorRulesDir, { recursive: true });
3824
- const templatePath = path5.join(__dirname2, "../CURSOR_RULES_TEMPLATE.md");
4444
+ const templatePath = path5.join(__dirname3, "../CURSOR_RULES_TEMPLATE.md");
3825
4445
  const rulesPath = path5.join(cursorRulesDir, "rules");
3826
4446
  let targetPath;
3827
4447
  let isDirectory = false;
@@ -3909,23 +4529,28 @@ Customizing ${frameworkName} settings:`));
3909
4529
  }
3910
4530
  async function upgradeConfig(configPath) {
3911
4531
  try {
3912
- const backupPath = `${configPath}.backup`;
3913
- await fs5.copyFile(configPath, backupPath);
3914
4532
  const existingContent = await fs5.readFile(configPath, "utf-8");
3915
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);
3916
4544
  let upgradedConfig;
3917
4545
  let migrated = false;
3918
- if (needsMigration(existingConfig)) {
3919
- 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}...`));
3920
4548
  upgradedConfig = migrateConfig(existingConfig);
3921
4549
  migrated = true;
3922
4550
  } else {
3923
- const newFields = detectNewFields(existingConfig, defaultConfig);
3924
4551
  upgradedConfig = deepMergeConfig(defaultConfig, existingConfig);
3925
- if (newFields.length > 0) {
3926
- console.log(chalk2.dim("\nNew options added:"));
3927
- newFields.forEach((field) => console.log(chalk2.dim(" \u2022"), chalk2.bold(field)));
3928
- }
4552
+ console.log(chalk2.dim("\nNew options added:"));
4553
+ newFields.forEach((field) => console.log(chalk2.dim(" \u2022"), chalk2.bold(field)));
3929
4554
  }
3930
4555
  await fs5.writeFile(
3931
4556
  configPath,
@@ -3946,7 +4571,7 @@ async function upgradeConfig(configPath) {
3946
4571
  // src/cli/status.ts
3947
4572
  init_service();
3948
4573
  init_utils();
3949
- init_version();
4574
+ init_version2();
3950
4575
  import chalk3 from "chalk";
3951
4576
  import fs9 from "fs/promises";
3952
4577
  import path9 from "path";
@@ -4082,82 +4707,85 @@ import { createRequire as createRequire3 } from "module";
4082
4707
  import { fileURLToPath as fileURLToPath4 } from "url";
4083
4708
  import { dirname as dirname3, join as join3 } from "path";
4084
4709
 
4710
+ // src/mcp/utils/zod-to-json-schema.ts
4711
+ import { zodToJsonSchema } from "zod-to-json-schema";
4712
+ function toMCPToolSchema(zodSchema, name, description) {
4713
+ return {
4714
+ name,
4715
+ description,
4716
+ inputSchema: zodToJsonSchema(zodSchema, {
4717
+ target: "jsonSchema7",
4718
+ $refStrategy: "none"
4719
+ })
4720
+ };
4721
+ }
4722
+
4723
+ // src/mcp/schemas/search.schema.ts
4724
+ import { z } from "zod";
4725
+ var SemanticSearchSchema = z.object({
4726
+ query: z.string().min(3, "Query must be at least 3 characters").max(500, "Query too long (max 500 characters)").describe(
4727
+ "Natural language description of what you're looking for.\n\nUse full sentences describing functionality, not exact names.\n\nGood examples:\n - 'handles user authentication'\n - 'validates email format'\n - 'processes payment transactions'\n\nBad examples:\n - 'auth' (too vague)\n - 'validateEmail' (use grep for exact names)"
4728
+ ),
4729
+ limit: z.number().int().min(1, "Limit must be at least 1").max(50, "Limit cannot exceed 50").default(5).describe(
4730
+ "Number of results to return.\n\nDefault: 5\nIncrease to 10-15 for broad exploration."
4731
+ )
4732
+ });
4733
+
4734
+ // src/mcp/schemas/similarity.schema.ts
4735
+ import { z as z2 } from "zod";
4736
+ var FindSimilarSchema = z2.object({
4737
+ code: z2.string().min(10, "Code snippet must be at least 10 characters").describe(
4738
+ "Code snippet to find similar implementations for.\n\nProvide a representative code sample that demonstrates the pattern you want to find similar examples of in the codebase."
4739
+ ),
4740
+ limit: z2.number().int().min(1, "Limit must be at least 1").max(20, "Limit cannot exceed 20").default(5).describe(
4741
+ "Number of similar code blocks to return.\n\nDefault: 5"
4742
+ )
4743
+ });
4744
+
4745
+ // src/mcp/schemas/file.schema.ts
4746
+ import { z as z3 } from "zod";
4747
+ var GetFileContextSchema = z3.object({
4748
+ filepath: z3.string().min(1, "Filepath cannot be empty").describe(
4749
+ "Relative path to file from workspace root.\n\nExample: 'src/components/Button.tsx'"
4750
+ ),
4751
+ includeRelated: z3.boolean().default(true).describe(
4752
+ "Include semantically related chunks from nearby code.\n\nDefault: true\n\nWhen enabled, also returns related code from other files that are semantically similar to the target file's contents."
4753
+ )
4754
+ });
4755
+
4756
+ // src/mcp/schemas/symbols.schema.ts
4757
+ import { z as z4 } from "zod";
4758
+ var ListFunctionsSchema = z4.object({
4759
+ pattern: z4.string().optional().describe(
4760
+ "Regex pattern to match symbol names.\n\nExamples:\n - '.*Controller.*' to find all Controllers\n - 'handle.*' to find handlers\n - '.*Service$' to find Services\n\nIf omitted, returns all symbols."
4761
+ ),
4762
+ language: z4.string().optional().describe(
4763
+ "Filter by programming language.\n\nExamples: 'typescript', 'python', 'javascript', 'php'\n\nIf omitted, searches all languages."
4764
+ )
4765
+ });
4766
+
4085
4767
  // src/mcp/tools.ts
4086
4768
  var tools = [
4087
- {
4088
- name: "semantic_search",
4089
- description: "Search the codebase semantically for relevant code using natural language. Results include a relevance category (highly_relevant, relevant, loosely_related, not_relevant) based on semantic similarity.",
4090
- inputSchema: {
4091
- type: "object",
4092
- properties: {
4093
- query: {
4094
- type: "string",
4095
- description: 'Natural language search query (e.g., "authentication logic", "database connection handling")'
4096
- },
4097
- limit: {
4098
- type: "number",
4099
- description: "Maximum number of results to return",
4100
- default: 5
4101
- }
4102
- },
4103
- required: ["query"]
4104
- }
4105
- },
4106
- {
4107
- name: "find_similar",
4108
- description: "Find code similar to a given code snippet. Results include a relevance category (highly_relevant, relevant, loosely_related, not_relevant) based on semantic similarity.",
4109
- inputSchema: {
4110
- type: "object",
4111
- properties: {
4112
- code: {
4113
- type: "string",
4114
- description: "Code snippet to find similar implementations"
4115
- },
4116
- limit: {
4117
- type: "number",
4118
- description: "Maximum number of results to return",
4119
- default: 5
4120
- }
4121
- },
4122
- required: ["code"]
4123
- }
4124
- },
4125
- {
4126
- name: "get_file_context",
4127
- description: "Get all chunks and related context for a specific file. Results include a relevance category (highly_relevant, relevant, loosely_related, not_relevant) based on semantic similarity.",
4128
- inputSchema: {
4129
- type: "object",
4130
- properties: {
4131
- filepath: {
4132
- type: "string",
4133
- description: "Path to the file (relative to project root)"
4134
- },
4135
- includeRelated: {
4136
- type: "boolean",
4137
- description: "Include semantically related chunks from other files",
4138
- default: true
4139
- }
4140
- },
4141
- required: ["filepath"]
4142
- }
4143
- },
4144
- {
4145
- name: "list_functions",
4146
- description: "List functions, classes, and interfaces by name pattern and language",
4147
- inputSchema: {
4148
- type: "object",
4149
- properties: {
4150
- pattern: {
4151
- type: "string",
4152
- description: 'Regex pattern to match symbol names (e.g., ".*Service$", "handle.*")'
4153
- },
4154
- language: {
4155
- type: "string",
4156
- description: 'Language filter (e.g., "typescript", "python", "php")'
4157
- }
4158
- }
4159
- }
4160
- }
4769
+ toMCPToolSchema(
4770
+ SemanticSearchSchema,
4771
+ "semantic_search",
4772
+ "Search the codebase semantically for relevant code using natural language. Results include a relevance category (highly_relevant, relevant, loosely_related, not_relevant) based on semantic similarity."
4773
+ ),
4774
+ toMCPToolSchema(
4775
+ FindSimilarSchema,
4776
+ "find_similar",
4777
+ "Find code similar to a given code snippet. Results include a relevance category (highly_relevant, relevant, loosely_related, not_relevant) based on semantic similarity."
4778
+ ),
4779
+ toMCPToolSchema(
4780
+ GetFileContextSchema,
4781
+ "get_file_context",
4782
+ "Get all chunks and related context for a specific file. Results include a relevance category (highly_relevant, relevant, loosely_related, not_relevant) based on semantic similarity."
4783
+ ),
4784
+ toMCPToolSchema(
4785
+ ListFunctionsSchema,
4786
+ "list_functions",
4787
+ "List functions, classes, and interfaces by name pattern and language"
4788
+ )
4161
4789
  ];
4162
4790
 
4163
4791
  // src/mcp/server.ts
@@ -4300,6 +4928,64 @@ var FileWatcher = class {
4300
4928
 
4301
4929
  // src/mcp/server.ts
4302
4930
  init_constants();
4931
+
4932
+ // src/mcp/utils/tool-wrapper.ts
4933
+ init_errors();
4934
+ import { ZodError } from "zod";
4935
+ function wrapToolHandler(schema, handler) {
4936
+ return async (args) => {
4937
+ try {
4938
+ const validated = schema.parse(args);
4939
+ const result = await handler(validated);
4940
+ return {
4941
+ content: [{
4942
+ type: "text",
4943
+ text: JSON.stringify(result, null, 2)
4944
+ }]
4945
+ };
4946
+ } catch (error) {
4947
+ if (error instanceof ZodError) {
4948
+ return {
4949
+ isError: true,
4950
+ content: [{
4951
+ type: "text",
4952
+ text: JSON.stringify({
4953
+ error: "Invalid parameters",
4954
+ code: "INVALID_INPUT" /* INVALID_INPUT */,
4955
+ details: error.errors.map((e) => ({
4956
+ field: e.path.join("."),
4957
+ message: e.message
4958
+ }))
4959
+ }, null, 2)
4960
+ }]
4961
+ };
4962
+ }
4963
+ if (error instanceof LienError) {
4964
+ return {
4965
+ isError: true,
4966
+ content: [{
4967
+ type: "text",
4968
+ text: JSON.stringify(error.toJSON(), null, 2)
4969
+ }]
4970
+ };
4971
+ }
4972
+ console.error("Unexpected error in tool handler:", error);
4973
+ return {
4974
+ isError: true,
4975
+ content: [{
4976
+ type: "text",
4977
+ text: JSON.stringify({
4978
+ error: error instanceof Error ? error.message : "Unknown error",
4979
+ code: "INTERNAL_ERROR" /* INTERNAL_ERROR */
4980
+ }, null, 2)
4981
+ }]
4982
+ };
4983
+ }
4984
+ };
4985
+ }
4986
+
4987
+ // src/mcp/server.ts
4988
+ init_errors();
4303
4989
  var __filename4 = fileURLToPath4(import.meta.url);
4304
4990
  var __dirname4 = dirname3(__filename4);
4305
4991
  var require4 = createRequire3(import.meta.url);
@@ -4364,148 +5050,139 @@ async function startMCPServer(options) {
4364
5050
  }, VERSION_CHECK_INTERVAL_MS);
4365
5051
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
4366
5052
  const { name, arguments: args } = request.params;
5053
+ log(`Handling tool call: ${name}`);
4367
5054
  try {
4368
- log(`Handling tool call: ${name}`);
4369
5055
  switch (name) {
4370
- case "semantic_search": {
4371
- const query = args?.query;
4372
- const limit = args?.limit || 5;
4373
- log(`Searching for: "${query}"`);
4374
- await checkAndReconnect();
4375
- const queryEmbedding = await embeddings.embed(query);
4376
- const results = await vectorDB.search(queryEmbedding, limit, query);
4377
- log(`Found ${results.length} results`);
4378
- const response = {
4379
- indexInfo: getIndexMetadata(),
4380
- results
4381
- };
4382
- return {
4383
- content: [
4384
- {
4385
- type: "text",
4386
- text: JSON.stringify(response, null, 2)
4387
- }
4388
- ]
4389
- };
4390
- }
4391
- case "find_similar": {
4392
- const code = args?.code;
4393
- const limit = args?.limit || 5;
4394
- log(`Finding similar code...`);
4395
- await checkAndReconnect();
4396
- const codeEmbedding = await embeddings.embed(code);
4397
- const results = await vectorDB.search(codeEmbedding, limit, code);
4398
- log(`Found ${results.length} similar chunks`);
4399
- const response = {
4400
- indexInfo: getIndexMetadata(),
4401
- results
4402
- };
4403
- return {
4404
- content: [
4405
- {
4406
- type: "text",
4407
- text: JSON.stringify(response, null, 2)
4408
- }
4409
- ]
4410
- };
4411
- }
4412
- case "get_file_context": {
4413
- const filepath = args?.filepath;
4414
- const includeRelated = args?.includeRelated ?? true;
4415
- log(`Getting context for: ${filepath}`);
4416
- await checkAndReconnect();
4417
- const fileEmbedding = await embeddings.embed(filepath);
4418
- const allResults = await vectorDB.search(fileEmbedding, 50, filepath);
4419
- const fileChunks = allResults.filter(
4420
- (r) => r.metadata.file.includes(filepath) || filepath.includes(r.metadata.file)
4421
- );
4422
- let results = fileChunks;
4423
- if (includeRelated && fileChunks.length > 0) {
4424
- const relatedEmbedding = await embeddings.embed(fileChunks[0].content);
4425
- const related = await vectorDB.search(relatedEmbedding, 5, fileChunks[0].content);
4426
- const relatedOtherFiles = related.filter(
4427
- (r) => !r.metadata.file.includes(filepath) && !filepath.includes(r.metadata.file)
4428
- );
4429
- results = [...fileChunks, ...relatedOtherFiles];
4430
- }
4431
- log(`Found ${results.length} chunks`);
4432
- const response = {
4433
- indexInfo: getIndexMetadata(),
4434
- file: filepath,
4435
- chunks: results
4436
- };
4437
- return {
4438
- content: [
4439
- {
4440
- type: "text",
4441
- text: JSON.stringify(response, null, 2)
5056
+ case "semantic_search":
5057
+ return await wrapToolHandler(
5058
+ SemanticSearchSchema,
5059
+ async (validatedArgs) => {
5060
+ log(`Searching for: "${validatedArgs.query}"`);
5061
+ await checkAndReconnect();
5062
+ const queryEmbedding = await embeddings.embed(validatedArgs.query);
5063
+ const results = await vectorDB.search(queryEmbedding, validatedArgs.limit, validatedArgs.query);
5064
+ log(`Found ${results.length} results`);
5065
+ return {
5066
+ indexInfo: getIndexMetadata(),
5067
+ results
5068
+ };
5069
+ }
5070
+ )(args);
5071
+ case "find_similar":
5072
+ return await wrapToolHandler(
5073
+ FindSimilarSchema,
5074
+ async (validatedArgs) => {
5075
+ log(`Finding similar code...`);
5076
+ await checkAndReconnect();
5077
+ const codeEmbedding = await embeddings.embed(validatedArgs.code);
5078
+ const results = await vectorDB.search(codeEmbedding, validatedArgs.limit, validatedArgs.code);
5079
+ log(`Found ${results.length} similar chunks`);
5080
+ return {
5081
+ indexInfo: getIndexMetadata(),
5082
+ results
5083
+ };
5084
+ }
5085
+ )(args);
5086
+ case "get_file_context":
5087
+ return await wrapToolHandler(
5088
+ GetFileContextSchema,
5089
+ async (validatedArgs) => {
5090
+ log(`Getting context for: ${validatedArgs.filepath}`);
5091
+ await checkAndReconnect();
5092
+ const fileEmbedding = await embeddings.embed(validatedArgs.filepath);
5093
+ const allResults = await vectorDB.search(fileEmbedding, 50, validatedArgs.filepath);
5094
+ const fileChunks = allResults.filter(
5095
+ (r) => r.metadata.file.includes(validatedArgs.filepath) || validatedArgs.filepath.includes(r.metadata.file)
5096
+ );
5097
+ let results = fileChunks;
5098
+ if (validatedArgs.includeRelated && fileChunks.length > 0) {
5099
+ const relatedEmbedding = await embeddings.embed(fileChunks[0].content);
5100
+ const related = await vectorDB.search(relatedEmbedding, 5, fileChunks[0].content);
5101
+ const relatedOtherFiles = related.filter(
5102
+ (r) => !r.metadata.file.includes(validatedArgs.filepath) && !validatedArgs.filepath.includes(r.metadata.file)
5103
+ );
5104
+ results = [...fileChunks, ...relatedOtherFiles];
4442
5105
  }
4443
- ]
4444
- };
4445
- }
4446
- case "list_functions": {
4447
- const pattern = args?.pattern;
4448
- const language = args?.language;
4449
- log("Listing functions with symbol metadata...");
4450
- await checkAndReconnect();
4451
- let results;
4452
- let usedMethod = "symbols";
4453
- try {
4454
- results = await vectorDB.querySymbols({
4455
- language,
4456
- pattern,
4457
- limit: 50
4458
- });
4459
- if (results.length === 0 && (language || pattern)) {
4460
- log("No symbol results, falling back to content scan...");
4461
- results = await vectorDB.scanWithFilter({
4462
- language,
4463
- pattern,
4464
- limit: 50
4465
- });
4466
- usedMethod = "content";
5106
+ log(`Found ${results.length} chunks`);
5107
+ return {
5108
+ indexInfo: getIndexMetadata(),
5109
+ file: validatedArgs.filepath,
5110
+ chunks: results
5111
+ };
4467
5112
  }
4468
- } catch (error) {
4469
- log(`Symbol query failed, falling back to content scan: ${error}`);
4470
- results = await vectorDB.scanWithFilter({
4471
- language,
4472
- pattern,
4473
- limit: 50
4474
- });
4475
- usedMethod = "content";
4476
- }
4477
- log(`Found ${results.length} matches using ${usedMethod} method`);
4478
- const response = {
4479
- indexInfo: getIndexMetadata(),
4480
- method: usedMethod,
4481
- results,
4482
- note: usedMethod === "content" ? 'Using content search. Run "lien reindex" to enable faster symbol-based queries.' : void 0
4483
- };
4484
- return {
4485
- content: [
4486
- {
4487
- type: "text",
4488
- text: JSON.stringify(response, null, 2)
5113
+ )(args);
5114
+ case "list_functions":
5115
+ return await wrapToolHandler(
5116
+ ListFunctionsSchema,
5117
+ async (validatedArgs) => {
5118
+ log("Listing functions with symbol metadata...");
5119
+ await checkAndReconnect();
5120
+ let results;
5121
+ let usedMethod = "symbols";
5122
+ try {
5123
+ results = await vectorDB.querySymbols({
5124
+ language: validatedArgs.language,
5125
+ pattern: validatedArgs.pattern,
5126
+ limit: 50
5127
+ });
5128
+ if (results.length === 0 && (validatedArgs.language || validatedArgs.pattern)) {
5129
+ log("No symbol results, falling back to content scan...");
5130
+ results = await vectorDB.scanWithFilter({
5131
+ language: validatedArgs.language,
5132
+ pattern: validatedArgs.pattern,
5133
+ limit: 50
5134
+ });
5135
+ usedMethod = "content";
5136
+ }
5137
+ } catch (error) {
5138
+ log(`Symbol query failed, falling back to content scan: ${error}`);
5139
+ results = await vectorDB.scanWithFilter({
5140
+ language: validatedArgs.language,
5141
+ pattern: validatedArgs.pattern,
5142
+ limit: 50
5143
+ });
5144
+ usedMethod = "content";
4489
5145
  }
4490
- ]
4491
- };
4492
- }
5146
+ log(`Found ${results.length} matches using ${usedMethod} method`);
5147
+ return {
5148
+ indexInfo: getIndexMetadata(),
5149
+ method: usedMethod,
5150
+ results,
5151
+ note: usedMethod === "content" ? 'Using content search. Run "lien reindex" to enable faster symbol-based queries.' : void 0
5152
+ };
5153
+ }
5154
+ )(args);
4493
5155
  default:
4494
- throw new Error(`Unknown tool: ${name}`);
5156
+ throw new LienError(
5157
+ `Unknown tool: ${name}`,
5158
+ "INVALID_INPUT" /* INVALID_INPUT */,
5159
+ { requestedTool: name, availableTools: tools.map((t) => t.name) },
5160
+ "medium",
5161
+ false,
5162
+ false
5163
+ );
4495
5164
  }
4496
5165
  } catch (error) {
4497
- console.error(`Error handling tool call ${name}:`, error);
4498
- return {
4499
- content: [
4500
- {
5166
+ if (error instanceof LienError) {
5167
+ return {
5168
+ isError: true,
5169
+ content: [{
4501
5170
  type: "text",
4502
- text: JSON.stringify({
4503
- error: String(error),
4504
- tool: name
4505
- })
4506
- }
4507
- ],
4508
- isError: true
5171
+ text: JSON.stringify(error.toJSON(), null, 2)
5172
+ }]
5173
+ };
5174
+ }
5175
+ console.error(`Unexpected error handling tool call ${name}:`, error);
5176
+ return {
5177
+ isError: true,
5178
+ content: [{
5179
+ type: "text",
5180
+ text: JSON.stringify({
5181
+ error: error instanceof Error ? error.message : "Unknown error",
5182
+ code: "INTERNAL_ERROR" /* INTERNAL_ERROR */,
5183
+ tool: name
5184
+ }, null, 2)
5185
+ }]
4509
5186
  };
4510
5187
  }
4511
5188
  });