@liendev/lien 0.14.0 → 0.15.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -161,7 +161,7 @@ function migrateConfig(oldConfig) {
161
161
  path: ".",
162
162
  enabled: true,
163
163
  config: {
164
- include: oldConfig.indexing.include ?? ["**/*.{ts,tsx,js,jsx,py,go,rs,java,c,cpp,cs}"],
164
+ include: oldConfig.indexing.include ?? ["**/*.{ts,tsx,js,jsx,py,php,go,rs,java,c,cpp,cs}"],
165
165
  exclude: oldConfig.indexing.exclude ?? [
166
166
  "**/node_modules/**",
167
167
  "**/dist/**",
@@ -181,7 +181,7 @@ function migrateConfig(oldConfig) {
181
181
  path: ".",
182
182
  enabled: true,
183
183
  config: {
184
- include: ["**/*.{ts,tsx,js,jsx,py,go,rs,java,c,cpp,cs}"],
184
+ include: ["**/*.{ts,tsx,js,jsx,py,php,go,rs,java,c,cpp,cs}"],
185
185
  exclude: [
186
186
  "**/node_modules/**",
187
187
  "**/dist/**",
@@ -1030,7 +1030,7 @@ async function scanCodebase(options) {
1030
1030
  ".lien/**",
1031
1031
  ...excludePatterns
1032
1032
  ]);
1033
- const patterns = includePatterns.length > 0 ? includePatterns : ["**/*.{ts,tsx,js,jsx,py,go,rs,java,cpp,c,h,md,mdx}"];
1033
+ const patterns = includePatterns.length > 0 ? includePatterns : ["**/*.{ts,tsx,js,jsx,py,php,go,rs,java,cpp,c,cs,h,md,mdx}"];
1034
1034
  const allFiles = [];
1035
1035
  for (const pattern of patterns) {
1036
1036
  const files = await glob(pattern, {
@@ -1388,6 +1388,7 @@ import Parser from "tree-sitter";
1388
1388
  import TypeScript from "tree-sitter-typescript";
1389
1389
  import JavaScript from "tree-sitter-javascript";
1390
1390
  import PHPParser from "tree-sitter-php";
1391
+ import Python from "tree-sitter-python";
1391
1392
  import { extname } from "path";
1392
1393
  function getParser(language) {
1393
1394
  if (!parserCache.has(language)) {
@@ -1414,6 +1415,8 @@ function detectLanguage2(filePath) {
1414
1415
  return "javascript";
1415
1416
  case "php":
1416
1417
  return "php";
1418
+ case "py":
1419
+ return "python";
1417
1420
  default:
1418
1421
  return null;
1419
1422
  }
@@ -1447,8 +1450,9 @@ var init_parser = __esm({
1447
1450
  languageConfig = {
1448
1451
  typescript: TypeScript.typescript,
1449
1452
  javascript: JavaScript,
1450
- php: PHPParser.php
1453
+ php: PHPParser.php,
1451
1454
  // Note: tree-sitter-php exports both 'php' (mixed HTML/PHP) and 'php_only'
1455
+ python: Python
1452
1456
  };
1453
1457
  }
1454
1458
  });
@@ -1524,7 +1528,35 @@ function extractInterfaceInfo(node, _content, _parentClass) {
1524
1528
  signature: `interface ${nameNode.text}`
1525
1529
  };
1526
1530
  }
1527
- function extractSymbolInfo(node, content, parentClass) {
1531
+ function extractPythonFunctionInfo(node, content, parentClass) {
1532
+ const nameNode = node.childForFieldName("name");
1533
+ if (!nameNode) return null;
1534
+ return {
1535
+ name: nameNode.text,
1536
+ type: parentClass ? "method" : "function",
1537
+ startLine: node.startPosition.row + 1,
1538
+ endLine: node.endPosition.row + 1,
1539
+ parentClass,
1540
+ signature: extractSignature(node, content),
1541
+ parameters: extractParameters(node, content),
1542
+ complexity: calculateComplexity(node)
1543
+ };
1544
+ }
1545
+ function extractPythonClassInfo(node, _content, _parentClass) {
1546
+ const nameNode = node.childForFieldName("name");
1547
+ if (!nameNode) return null;
1548
+ return {
1549
+ name: nameNode.text,
1550
+ type: "class",
1551
+ startLine: node.startPosition.row + 1,
1552
+ endLine: node.endPosition.row + 1,
1553
+ signature: `class ${nameNode.text}`
1554
+ };
1555
+ }
1556
+ function extractSymbolInfo(node, content, parentClass, language) {
1557
+ if (node.type === "function_definition" && language === "python") {
1558
+ return extractPythonFunctionInfo(node, content, parentClass);
1559
+ }
1528
1560
  const extractor = symbolExtractors[node.type];
1529
1561
  return extractor ? extractor(node, content, parentClass) : null;
1530
1562
  }
@@ -1563,23 +1595,39 @@ function extractReturnType(node, _content) {
1563
1595
  function calculateComplexity(node) {
1564
1596
  let complexity = 1;
1565
1597
  const decisionPoints = [
1566
- // TypeScript/JavaScript
1598
+ // Common across languages (TypeScript/JavaScript/Python/PHP)
1567
1599
  "if_statement",
1600
+ // if conditions
1568
1601
  "while_statement",
1569
- "do_statement",
1570
- // do...while loops
1602
+ // while loops
1571
1603
  "for_statement",
1572
- "for_in_statement",
1573
- "for_of_statement",
1574
- // for...of loops
1604
+ // for loops
1575
1605
  "switch_case",
1606
+ // switch/case statements
1576
1607
  "catch_clause",
1608
+ // try/catch error handling
1577
1609
  "ternary_expression",
1610
+ // Ternary operator (a ? b : c)
1578
1611
  "binary_expression",
1579
- // For && and ||
1580
- // PHP
1581
- "foreach_statement"
1612
+ // For && and || logical operators
1613
+ // TypeScript/JavaScript specific
1614
+ "do_statement",
1615
+ // do...while loops
1616
+ "for_in_statement",
1617
+ // for...in loops
1618
+ "for_of_statement",
1619
+ // for...of loops
1620
+ // PHP specific
1621
+ "foreach_statement",
1582
1622
  // PHP foreach loops
1623
+ // Python specific
1624
+ "elif_clause",
1625
+ // Python elif (adds decision point)
1626
+ // Note: 'else_clause' is NOT a decision point (it's the default path)
1627
+ "except_clause",
1628
+ // Python except (try/except)
1629
+ "conditional_expression"
1630
+ // Python ternary (x if cond else y)
1583
1631
  ];
1584
1632
  function traverse(n) {
1585
1633
  if (decisionPoints.includes(n.type)) {
@@ -1608,7 +1656,13 @@ function extractImports(rootNode) {
1608
1656
  if (sourceNode) {
1609
1657
  const importPath = sourceNode.text.replace(/['"]/g, "");
1610
1658
  imports.push(importPath);
1659
+ } else {
1660
+ const importText = node.text.split("\n")[0];
1661
+ imports.push(importText);
1611
1662
  }
1663
+ } else if (node.type === "import_from_statement") {
1664
+ const importText = node.text.split("\n")[0];
1665
+ imports.push(importText);
1612
1666
  }
1613
1667
  if (node === rootNode) {
1614
1668
  for (let i = 0; i < node.namedChildCount; i++) {
@@ -1635,9 +1689,16 @@ var init_symbols = __esm({
1635
1689
  "interface_declaration": extractInterfaceInfo,
1636
1690
  // PHP
1637
1691
  "function_definition": extractFunctionInfo,
1638
- // PHP functions
1639
- "method_declaration": extractMethodInfo
1692
+ // PHP functions (Python handled via language check in extractSymbolInfo)
1693
+ "method_declaration": extractMethodInfo,
1640
1694
  // PHP methods
1695
+ // Python
1696
+ "async_function_definition": extractPythonFunctionInfo,
1697
+ // Python async functions
1698
+ "class_definition": extractPythonClassInfo
1699
+ // Python classes
1700
+ // Note: Python regular functions use 'function_definition' (same as PHP)
1701
+ // They are dispatched to extractPythonFunctionInfo via language check in extractSymbolInfo()
1641
1702
  };
1642
1703
  }
1643
1704
  });
@@ -1795,6 +1856,69 @@ var init_php = __esm({
1795
1856
  }
1796
1857
  });
1797
1858
 
1859
+ // src/indexer/ast/traversers/python.ts
1860
+ var PythonTraverser;
1861
+ var init_python = __esm({
1862
+ "src/indexer/ast/traversers/python.ts"() {
1863
+ "use strict";
1864
+ PythonTraverser = class {
1865
+ targetNodeTypes = [
1866
+ "function_definition",
1867
+ "async_function_definition"
1868
+ ];
1869
+ containerTypes = [
1870
+ "class_definition"
1871
+ // We extract methods, not the class itself
1872
+ ];
1873
+ declarationTypes = [
1874
+ // Python doesn't have const/let/var declarations like JS/TS
1875
+ // Functions are always defined with 'def' or 'async def'
1876
+ ];
1877
+ functionTypes = [
1878
+ "function_definition",
1879
+ "async_function_definition"
1880
+ ];
1881
+ shouldExtractChildren(node) {
1882
+ return this.containerTypes.includes(node.type);
1883
+ }
1884
+ isDeclarationWithFunction(_node) {
1885
+ return false;
1886
+ }
1887
+ getContainerBody(node) {
1888
+ if (node.type === "class_definition") {
1889
+ return node.childForFieldName("body");
1890
+ }
1891
+ return null;
1892
+ }
1893
+ shouldTraverseChildren(node) {
1894
+ return node.type === "module" || // Top-level Python file
1895
+ node.type === "block";
1896
+ }
1897
+ findParentContainerName(node) {
1898
+ let current = node.parent;
1899
+ while (current) {
1900
+ if (current.type === "class_definition") {
1901
+ const nameNode = current.childForFieldName("name");
1902
+ return nameNode?.text;
1903
+ }
1904
+ current = current.parent;
1905
+ }
1906
+ return void 0;
1907
+ }
1908
+ /**
1909
+ * Python doesn't have this pattern (const x = () => {})
1910
+ * Functions are always defined with 'def' or 'async def'
1911
+ */
1912
+ findFunctionInDeclaration(_node) {
1913
+ return {
1914
+ hasFunction: false,
1915
+ functionNode: null
1916
+ };
1917
+ }
1918
+ };
1919
+ }
1920
+ });
1921
+
1798
1922
  // src/indexer/ast/traversers/index.ts
1799
1923
  function getTraverser(language) {
1800
1924
  const traverser = traverserRegistry[language];
@@ -1809,10 +1933,12 @@ var init_traversers = __esm({
1809
1933
  "use strict";
1810
1934
  init_typescript();
1811
1935
  init_php();
1936
+ init_python();
1812
1937
  traverserRegistry = {
1813
1938
  typescript: new TypeScriptTraverser(),
1814
1939
  javascript: new JavaScriptTraverser(),
1815
- php: new PHPTraverser()
1940
+ php: new PHPTraverser(),
1941
+ python: new PythonTraverser()
1816
1942
  };
1817
1943
  }
1818
1944
  });
@@ -1843,7 +1969,7 @@ function chunkByAST(filepath, content, options = {}) {
1843
1969
  }
1844
1970
  }
1845
1971
  const parentClassName = traverser.findParentContainerName(actualNode);
1846
- const symbolInfo = extractSymbolInfo(actualNode, content, parentClassName);
1972
+ const symbolInfo = extractSymbolInfo(actualNode, content, parentClassName, language);
1847
1973
  const nodeContent = getNodeContent(node, lines);
1848
1974
  chunks.push(createChunk(filepath, node, nodeContent, symbolInfo, fileImports, language));
1849
1975
  }
@@ -4926,7 +5052,7 @@ async function createNewConfig(rootDir, options) {
4926
5052
  path: ".",
4927
5053
  enabled: true,
4928
5054
  config: {
4929
- include: ["**/*.{ts,tsx,js,jsx,py,go,rs,java,c,cpp,cs}"],
5055
+ include: ["**/*.{ts,tsx,js,jsx,py,php,go,rs,java,c,cpp,cs}"],
4930
5056
  exclude: [
4931
5057
  "**/node_modules/**",
4932
5058
  "**/dist/**",
@@ -5304,22 +5430,48 @@ var tools = [
5304
5430
  toMCPToolSchema(
5305
5431
  SemanticSearchSchema,
5306
5432
  "semantic_search",
5307
- "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."
5433
+ `Search codebase by MEANING, not text. USE THIS INSTEAD OF grep/ripgrep for finding implementations, features, or understanding how code works.
5434
+
5435
+ Examples:
5436
+ - "Where is authentication handled?" \u2192 semantic_search({ query: "handles user authentication" })
5437
+ - "How does payment work?" \u2192 semantic_search({ query: "processes payment transactions" })
5438
+
5439
+ Use natural language describing what the code DOES, not function names. For exact string matching, use grep instead.
5440
+
5441
+ Results include a relevance category (highly_relevant, relevant, loosely_related, not_relevant) for each match.`
5308
5442
  ),
5309
5443
  toMCPToolSchema(
5310
5444
  FindSimilarSchema,
5311
5445
  "find_similar",
5312
- "Find code similar to a given code snippet. Results include a relevance category (highly_relevant, relevant, loosely_related, not_relevant) based on semantic similarity."
5446
+ `Find code structurally similar to a given snippet. Use for:
5447
+ - Ensuring consistency when adding new code
5448
+ - Finding duplicate implementations
5449
+ - Refactoring similar patterns together
5450
+
5451
+ Provide at least 10 characters of code to match against. Results include a relevance category for each match.`
5313
5452
  ),
5314
5453
  toMCPToolSchema(
5315
5454
  GetFileContextSchema,
5316
5455
  "get_file_context",
5317
- "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."
5456
+ `Get full context for a file including related code and dependencies.
5457
+
5458
+ IMPORTANT: Call this BEFORE editing any file to understand:
5459
+ - What the file does
5460
+ - What depends on it
5461
+ - Related test files (via testAssociations)
5462
+
5463
+ Results include a relevance category for each related chunk. Typical flow: semantic_search \u2192 find file \u2192 get_file_context \u2192 make changes.`
5318
5464
  ),
5319
5465
  toMCPToolSchema(
5320
5466
  ListFunctionsSchema,
5321
5467
  "list_functions",
5322
- "List functions, classes, and interfaces by name pattern and language"
5468
+ `Fast symbol lookup by naming pattern. Use when searching by NAME, not behavior.
5469
+
5470
+ Examples:
5471
+ - "Show all controllers" \u2192 list_functions({ pattern: ".*Controller.*" })
5472
+ - "Find service classes" \u2192 list_functions({ pattern: ".*Service$" })
5473
+
5474
+ 10x faster than semantic_search for structural/architectural queries. Use semantic_search instead when searching by what code DOES.`
5323
5475
  )
5324
5476
  ];
5325
5477