@liendev/lien 0.28.1 → 0.29.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
@@ -8745,6 +8745,91 @@ function wrapToolHandler(schema, handler) {
8745
8745
  };
8746
8746
  }
8747
8747
 
8748
+ // src/mcp/utils/metadata-shaper.ts
8749
+ var FIELD_ALLOWLISTS = {
8750
+ semantic_search: /* @__PURE__ */ new Set([
8751
+ "language",
8752
+ "type",
8753
+ "symbolName",
8754
+ "symbolType",
8755
+ "signature",
8756
+ "parentClass",
8757
+ "parameters",
8758
+ "exports",
8759
+ "repoId"
8760
+ ]),
8761
+ find_similar: /* @__PURE__ */ new Set([
8762
+ "language",
8763
+ "type",
8764
+ "symbolName",
8765
+ "symbolType",
8766
+ "signature",
8767
+ "parentClass",
8768
+ "parameters",
8769
+ "exports"
8770
+ ]),
8771
+ get_files_context: /* @__PURE__ */ new Set([
8772
+ "language",
8773
+ "type",
8774
+ "symbolName",
8775
+ "symbolType",
8776
+ "signature",
8777
+ "parentClass",
8778
+ "parameters",
8779
+ "exports",
8780
+ "imports",
8781
+ "importedSymbols",
8782
+ "callSites",
8783
+ "symbols"
8784
+ ]),
8785
+ list_functions: /* @__PURE__ */ new Set([
8786
+ "language",
8787
+ "type",
8788
+ "symbolName",
8789
+ "symbolType",
8790
+ "signature",
8791
+ "parentClass",
8792
+ "parameters",
8793
+ "exports",
8794
+ "symbols"
8795
+ ])
8796
+ };
8797
+ function deduplicateResults(results) {
8798
+ const seen = /* @__PURE__ */ new Set();
8799
+ return results.filter((r) => {
8800
+ const key = JSON.stringify([r.metadata.repoId ?? "", r.metadata.file, r.metadata.startLine, r.metadata.endLine]);
8801
+ if (seen.has(key)) return false;
8802
+ seen.add(key);
8803
+ return true;
8804
+ });
8805
+ }
8806
+ function pickMetadata(metadata, allowlist) {
8807
+ const result = {
8808
+ file: metadata.file,
8809
+ startLine: metadata.startLine,
8810
+ endLine: metadata.endLine
8811
+ };
8812
+ const out = result;
8813
+ for (const key of allowlist) {
8814
+ if (key === "file" || key === "startLine" || key === "endLine") continue;
8815
+ if (metadata[key] !== void 0) {
8816
+ out[key] = metadata[key];
8817
+ }
8818
+ }
8819
+ return result;
8820
+ }
8821
+ function shapeResultMetadata(result, tool) {
8822
+ return {
8823
+ content: result.content,
8824
+ metadata: pickMetadata(result.metadata, FIELD_ALLOWLISTS[tool]),
8825
+ score: result.score,
8826
+ relevance: result.relevance
8827
+ };
8828
+ }
8829
+ function shapeResults(results, tool) {
8830
+ return results.map((r) => shapeResultMetadata(r, tool));
8831
+ }
8832
+
8748
8833
  // src/mcp/handlers/semantic-search.ts
8749
8834
  import { QdrantDB } from "@liendev/core";
8750
8835
  function groupResultsByRepo(results) {
@@ -8780,15 +8865,31 @@ async function handleSemanticSearch(args, ctx) {
8780
8865
  results = await vectorDB.search(queryEmbedding, limit, query);
8781
8866
  log(`Found ${results.length} results`);
8782
8867
  }
8868
+ results = deduplicateResults(results);
8869
+ const notes = [];
8870
+ if (crossRepoFallback) {
8871
+ notes.push("Cross-repo search requires Qdrant backend. Fell back to single-repo search.");
8872
+ }
8873
+ if (results.length > 0 && results.every((r) => r.relevance === "not_relevant")) {
8874
+ notes.push("No relevant matches found.");
8875
+ log("Returning 0 results (all not_relevant)");
8876
+ return {
8877
+ indexInfo: getIndexMetadata(),
8878
+ results: [],
8879
+ note: notes.join(" ")
8880
+ };
8881
+ }
8882
+ log(`Returning ${results.length} results`);
8883
+ const shaped = shapeResults(results, "semantic_search");
8783
8884
  const response = {
8784
8885
  indexInfo: getIndexMetadata(),
8785
- results
8886
+ results: shaped
8786
8887
  };
8787
8888
  if (crossRepo && vectorDB instanceof QdrantDB) {
8788
- response.groupedByRepo = groupResultsByRepo(results);
8889
+ response.groupedByRepo = groupResultsByRepo(shaped);
8789
8890
  }
8790
- if (crossRepoFallback) {
8791
- response.note = "Cross-repo search requires Qdrant backend. Fell back to single-repo search.";
8891
+ if (notes.length > 0) {
8892
+ response.note = notes.join(" ");
8792
8893
  }
8793
8894
  return response;
8794
8895
  }
@@ -8820,6 +8921,12 @@ async function handleFindSimilar(args, ctx) {
8820
8921
  const limit = validatedArgs.limit ?? 5;
8821
8922
  const extraLimit = limit + 10;
8822
8923
  let results = await vectorDB.search(codeEmbedding, extraLimit, validatedArgs.code);
8924
+ results = deduplicateResults(results);
8925
+ const inputCode = validatedArgs.code.trim();
8926
+ results = results.filter((r) => {
8927
+ if (r.score >= 0.1) return true;
8928
+ return r.content.trim() !== inputCode;
8929
+ });
8823
8930
  const filtersApplied = { prunedLowRelevance: 0 };
8824
8931
  if (validatedArgs.language) {
8825
8932
  filtersApplied.language = validatedArgs.language;
@@ -8836,7 +8943,7 @@ async function handleFindSimilar(args, ctx) {
8836
8943
  const hasFilters = filtersApplied.language || filtersApplied.pathHint || filtersApplied.prunedLowRelevance > 0;
8837
8944
  return {
8838
8945
  indexInfo: getIndexMetadata(),
8839
- results: finalResults,
8946
+ results: shapeResults(finalResults, "find_similar"),
8840
8947
  ...hasFilters && { filtersApplied }
8841
8948
  };
8842
8949
  }
@@ -9044,18 +9151,26 @@ function buildScanLimitNote(hitScanLimit) {
9044
9151
  return hitScanLimit ? "Scanned 10,000 chunks (limit reached). Test associations may be incomplete for large codebases." : void 0;
9045
9152
  }
9046
9153
  function buildSingleFileResponse(filepath, filesData, indexInfo, note) {
9154
+ const data = filesData[filepath];
9047
9155
  return {
9048
9156
  indexInfo,
9049
9157
  file: filepath,
9050
- chunks: filesData[filepath].chunks,
9051
- testAssociations: filesData[filepath].testAssociations,
9158
+ chunks: shapeResults(data.chunks, "get_files_context"),
9159
+ testAssociations: data.testAssociations,
9052
9160
  ...note && { note }
9053
9161
  };
9054
9162
  }
9055
9163
  function buildMultiFileResponse(filesData, indexInfo, note) {
9164
+ const shaped = {};
9165
+ for (const [filepath, data] of Object.entries(filesData)) {
9166
+ shaped[filepath] = {
9167
+ chunks: shapeResults(data.chunks, "get_files_context"),
9168
+ testAssociations: data.testAssociations
9169
+ };
9170
+ }
9056
9171
  return {
9057
9172
  indexInfo,
9058
- files: filesData,
9173
+ files: shaped,
9059
9174
  ...note && { note }
9060
9175
  };
9061
9176
  }
@@ -9160,11 +9275,12 @@ async function handleListFunctions(args, ctx) {
9160
9275
  log(`Symbol query failed: ${error}`);
9161
9276
  queryResult = await performContentScan(vectorDB, validatedArgs, log);
9162
9277
  }
9163
- log(`Found ${queryResult.results.length} matches using ${queryResult.method} method`);
9278
+ const dedupedResults = deduplicateResults(queryResult.results);
9279
+ log(`Found ${dedupedResults.length} matches using ${queryResult.method} method`);
9164
9280
  return {
9165
9281
  indexInfo: getIndexMetadata(),
9166
9282
  method: queryResult.method,
9167
- results: queryResult.results,
9283
+ results: shapeResults(dedupedResults, "list_functions"),
9168
9284
  note: queryResult.method === "content" ? 'Using content search. Run "lien reindex" to enable faster symbol-based queries.' : void 0
9169
9285
  };
9170
9286
  }