@liendev/lien 0.29.0 → 0.30.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
@@ -8524,7 +8524,8 @@ var ListFunctionsSchema = external_exports.object({
8524
8524
  ),
8525
8525
  language: external_exports.string().optional().describe(
8526
8526
  "Filter by programming language.\n\nExamples: 'typescript', 'python', 'javascript', 'php'\n\nIf omitted, searches all languages."
8527
- )
8527
+ ),
8528
+ symbolType: external_exports.enum(["function", "method", "class", "interface"]).optional().describe("Filter by symbol type. If omitted, returns all types.")
8528
8529
  });
8529
8530
 
8530
8531
  // src/mcp/schemas/dependents.schema.ts
@@ -8647,6 +8648,10 @@ Batch calls are more efficient than multiple single-file calls.`
8647
8648
  Examples:
8648
8649
  - "Show all controllers" \u2192 list_functions({ pattern: ".*Controller.*" })
8649
8650
  - "Find service classes" \u2192 list_functions({ pattern: ".*Service$" })
8651
+ - "List all class methods" \u2192 list_functions({ symbolType: "method" })
8652
+ - "Find standalone functions" \u2192 list_functions({ symbolType: "function" })
8653
+
8654
+ Filter by symbol type (function, method, class, interface) to narrow results.
8650
8655
 
8651
8656
  10x faster than semantic_search for structural/architectural queries. Use semantic_search instead when searching by what code DOES.`
8652
8657
  ),
@@ -8803,6 +8808,25 @@ function deduplicateResults(results) {
8803
8808
  return true;
8804
8809
  });
8805
8810
  }
8811
+ function cleanMetadataValue(key, value) {
8812
+ if (value === void 0 || value === "") return null;
8813
+ if (Array.isArray(value)) {
8814
+ const filtered = value.filter((v) => v !== "");
8815
+ return filtered.length > 0 ? filtered : null;
8816
+ }
8817
+ if (key === "symbols" && typeof value === "object" && value !== null) {
8818
+ const symbols = value;
8819
+ const filterArr = (arr) => Array.isArray(arr) ? arr.filter((s) => s !== "") : [];
8820
+ const filtered = {
8821
+ functions: filterArr(symbols.functions),
8822
+ classes: filterArr(symbols.classes),
8823
+ interfaces: filterArr(symbols.interfaces)
8824
+ };
8825
+ const hasAny = filtered.functions.length > 0 || filtered.classes.length > 0 || filtered.interfaces.length > 0;
8826
+ return hasAny ? filtered : null;
8827
+ }
8828
+ return value;
8829
+ }
8806
8830
  function pickMetadata(metadata, allowlist) {
8807
8831
  const result = {
8808
8832
  file: metadata.file,
@@ -8812,8 +8836,9 @@ function pickMetadata(metadata, allowlist) {
8812
8836
  const out = result;
8813
8837
  for (const key of allowlist) {
8814
8838
  if (key === "file" || key === "startLine" || key === "endLine") continue;
8815
- if (metadata[key] !== void 0) {
8816
- out[key] = metadata[key];
8839
+ const cleaned = cleanMetadataValue(key, metadata[key]);
8840
+ if (cleaned !== null) {
8841
+ out[key] = cleaned;
8817
8842
  }
8818
8843
  }
8819
8844
  return result;
@@ -8881,17 +8906,12 @@ async function handleSemanticSearch(args, ctx) {
8881
8906
  }
8882
8907
  log(`Returning ${results.length} results`);
8883
8908
  const shaped = shapeResults(results, "semantic_search");
8884
- const response = {
8909
+ return {
8885
8910
  indexInfo: getIndexMetadata(),
8886
- results: shaped
8911
+ results: shaped,
8912
+ ...crossRepo && vectorDB instanceof QdrantDB && { groupedByRepo: groupResultsByRepo(shaped) },
8913
+ ...notes.length > 0 && { note: notes.join(" ") }
8887
8914
  };
8888
- if (crossRepo && vectorDB instanceof QdrantDB) {
8889
- response.groupedByRepo = groupResultsByRepo(shaped);
8890
- }
8891
- if (notes.length > 0) {
8892
- response.note = notes.join(" ");
8893
- }
8894
- return response;
8895
8915
  }
8896
8916
  )(args);
8897
8917
  }
@@ -9086,7 +9106,9 @@ async function findRelatedChunks(filepaths, fileChunksMap, ctx) {
9086
9106
  const targetCanonical = getCanonicalPath(filepath, workspaceRoot);
9087
9107
  relatedChunksMap[index] = related.filter((r) => {
9088
9108
  const chunkCanonical = getCanonicalPath(r.metadata.file, workspaceRoot);
9089
- return chunkCanonical !== targetCanonical;
9109
+ if (chunkCanonical === targetCanonical) return false;
9110
+ if (r.metadata.language === "markdown") return false;
9111
+ return true;
9090
9112
  });
9091
9113
  });
9092
9114
  return relatedChunksMap;
@@ -9122,23 +9144,15 @@ function findTestAssociations(filepaths, allChunks, ctx) {
9122
9144
  return Array.from(testFiles);
9123
9145
  });
9124
9146
  }
9125
- function deduplicateChunks(fileChunks, relatedChunks, workspaceRoot) {
9126
- const seenChunks = /* @__PURE__ */ new Set();
9127
- return [...fileChunks, ...relatedChunks].filter((chunk) => {
9128
- const canonicalFile = getCanonicalPath(chunk.metadata.file, workspaceRoot);
9129
- const chunkId = `${canonicalFile}:${chunk.metadata.startLine}-${chunk.metadata.endLine}`;
9130
- if (seenChunks.has(chunkId)) return false;
9131
- seenChunks.add(chunkId);
9132
- return true;
9133
- });
9147
+ function deduplicateChunks(fileChunks, relatedChunks) {
9148
+ return deduplicateResults([...fileChunks, ...relatedChunks]);
9134
9149
  }
9135
- function buildFilesData(filepaths, fileChunksMap, relatedChunksMap, testAssociationsMap, workspaceRoot) {
9150
+ function buildFilesData(filepaths, fileChunksMap, relatedChunksMap, testAssociationsMap) {
9136
9151
  const filesData = {};
9137
9152
  filepaths.forEach((filepath, i) => {
9138
9153
  const dedupedChunks = deduplicateChunks(
9139
9154
  fileChunksMap[i],
9140
- relatedChunksMap[i] || [],
9141
- workspaceRoot
9155
+ relatedChunksMap[i] || []
9142
9156
  );
9143
9157
  filesData[filepath] = {
9144
9158
  chunks: dedupedChunks,
@@ -9216,8 +9230,7 @@ async function handleGetFilesContext(args, ctx) {
9216
9230
  filepaths,
9217
9231
  fileChunksMap,
9218
9232
  relatedChunksMap,
9219
- testAssociationsMap,
9220
- workspaceRoot
9233
+ testAssociationsMap
9221
9234
  );
9222
9235
  const totalChunks = Object.values(filesData).reduce(
9223
9236
  (sum, f) => sum + f.chunks.length,
@@ -9232,6 +9245,7 @@ async function handleGetFilesContext(args, ctx) {
9232
9245
  }
9233
9246
 
9234
9247
  // src/mcp/handlers/list-functions.ts
9248
+ import { SYMBOL_TYPE_MATCHES } from "@liendev/core";
9235
9249
  async function performContentScan(vectorDB, args, log) {
9236
9250
  log("Falling back to content scan...");
9237
9251
  let results = await vectorDB.scanWithFilter({
@@ -9246,6 +9260,13 @@ async function performContentScan(vectorDB, args, log) {
9246
9260
  return symbolName && regex.test(symbolName);
9247
9261
  });
9248
9262
  }
9263
+ if (args.symbolType) {
9264
+ const allowedTypes = SYMBOL_TYPE_MATCHES[args.symbolType];
9265
+ results = results.filter((r) => {
9266
+ const recordType2 = r.metadata?.symbolType;
9267
+ return recordType2 && allowedTypes?.has(recordType2);
9268
+ });
9269
+ }
9249
9270
  return {
9250
9271
  results: results.slice(0, 50),
9251
9272
  method: "content"
@@ -9263,9 +9284,10 @@ async function handleListFunctions(args, ctx) {
9263
9284
  const results = await vectorDB.querySymbols({
9264
9285
  language: validatedArgs.language,
9265
9286
  pattern: validatedArgs.pattern,
9287
+ symbolType: validatedArgs.symbolType,
9266
9288
  limit: 50
9267
9289
  });
9268
- if (results.length === 0 && (validatedArgs.language || validatedArgs.pattern)) {
9290
+ if (results.length === 0 && (validatedArgs.language || validatedArgs.pattern || validatedArgs.symbolType)) {
9269
9291
  log("No symbol results, falling back to content scan...");
9270
9292
  queryResult = await performContentScan(vectorDB, validatedArgs, log);
9271
9293
  } else {
@@ -9796,7 +9818,11 @@ async function handleGetComplexity(args, ctx) {
9796
9818
  threshold,
9797
9819
  top ?? 10
9798
9820
  );
9799
- const response = {
9821
+ const note = buildCrossRepoFallbackNote(fallback);
9822
+ if (note) {
9823
+ log("Warning: crossRepo=true requires Qdrant backend. Falling back to single-repo analysis.", "warning");
9824
+ }
9825
+ return {
9800
9826
  indexInfo: getIndexMetadata(),
9801
9827
  summary: {
9802
9828
  filesAnalyzed: report.summary.filesAnalyzed,
@@ -9805,17 +9831,12 @@ async function handleGetComplexity(args, ctx) {
9805
9831
  violationCount: violations.length,
9806
9832
  bySeverity
9807
9833
  },
9808
- violations: topViolations
9834
+ violations: topViolations,
9835
+ ...crossRepo && !fallback && allChunks.length > 0 && {
9836
+ groupedByRepo: groupViolationsByRepo(topViolations, allChunks)
9837
+ },
9838
+ ...note && { note }
9809
9839
  };
9810
- if (crossRepo && !fallback && allChunks.length > 0) {
9811
- response.groupedByRepo = groupViolationsByRepo(topViolations, allChunks);
9812
- }
9813
- const note = buildCrossRepoFallbackNote(fallback);
9814
- if (note) {
9815
- log("Warning: crossRepo=true requires Qdrant backend. Falling back to single-repo analysis.", "warning");
9816
- response.note = note;
9817
- }
9818
- return response;
9819
9840
  }
9820
9841
  )(args);
9821
9842
  }