@liendev/lien 0.28.1 → 0.29.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 +164 -41
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -8745,6 +8745,111 @@ 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 cleanMetadataValue(key, value) {
|
|
8807
|
+
if (value === void 0 || value === "") return null;
|
|
8808
|
+
if (Array.isArray(value)) {
|
|
8809
|
+
const filtered = value.filter((v) => v !== "");
|
|
8810
|
+
return filtered.length > 0 ? filtered : null;
|
|
8811
|
+
}
|
|
8812
|
+
if (key === "symbols" && typeof value === "object" && value !== null) {
|
|
8813
|
+
const symbols = value;
|
|
8814
|
+
const filterArr = (arr) => Array.isArray(arr) ? arr.filter((s) => s !== "") : [];
|
|
8815
|
+
const filtered = {
|
|
8816
|
+
functions: filterArr(symbols.functions),
|
|
8817
|
+
classes: filterArr(symbols.classes),
|
|
8818
|
+
interfaces: filterArr(symbols.interfaces)
|
|
8819
|
+
};
|
|
8820
|
+
const hasAny = filtered.functions.length > 0 || filtered.classes.length > 0 || filtered.interfaces.length > 0;
|
|
8821
|
+
return hasAny ? filtered : null;
|
|
8822
|
+
}
|
|
8823
|
+
return value;
|
|
8824
|
+
}
|
|
8825
|
+
function pickMetadata(metadata, allowlist) {
|
|
8826
|
+
const result = {
|
|
8827
|
+
file: metadata.file,
|
|
8828
|
+
startLine: metadata.startLine,
|
|
8829
|
+
endLine: metadata.endLine
|
|
8830
|
+
};
|
|
8831
|
+
const out = result;
|
|
8832
|
+
for (const key of allowlist) {
|
|
8833
|
+
if (key === "file" || key === "startLine" || key === "endLine") continue;
|
|
8834
|
+
const cleaned = cleanMetadataValue(key, metadata[key]);
|
|
8835
|
+
if (cleaned !== null) {
|
|
8836
|
+
out[key] = cleaned;
|
|
8837
|
+
}
|
|
8838
|
+
}
|
|
8839
|
+
return result;
|
|
8840
|
+
}
|
|
8841
|
+
function shapeResultMetadata(result, tool) {
|
|
8842
|
+
return {
|
|
8843
|
+
content: result.content,
|
|
8844
|
+
metadata: pickMetadata(result.metadata, FIELD_ALLOWLISTS[tool]),
|
|
8845
|
+
score: result.score,
|
|
8846
|
+
relevance: result.relevance
|
|
8847
|
+
};
|
|
8848
|
+
}
|
|
8849
|
+
function shapeResults(results, tool) {
|
|
8850
|
+
return results.map((r) => shapeResultMetadata(r, tool));
|
|
8851
|
+
}
|
|
8852
|
+
|
|
8748
8853
|
// src/mcp/handlers/semantic-search.ts
|
|
8749
8854
|
import { QdrantDB } from "@liendev/core";
|
|
8750
8855
|
function groupResultsByRepo(results) {
|
|
@@ -8780,17 +8885,28 @@ async function handleSemanticSearch(args, ctx) {
|
|
|
8780
8885
|
results = await vectorDB.search(queryEmbedding, limit, query);
|
|
8781
8886
|
log(`Found ${results.length} results`);
|
|
8782
8887
|
}
|
|
8783
|
-
|
|
8784
|
-
|
|
8785
|
-
results
|
|
8786
|
-
};
|
|
8787
|
-
if (crossRepo && vectorDB instanceof QdrantDB) {
|
|
8788
|
-
response.groupedByRepo = groupResultsByRepo(results);
|
|
8789
|
-
}
|
|
8888
|
+
results = deduplicateResults(results);
|
|
8889
|
+
const notes = [];
|
|
8790
8890
|
if (crossRepoFallback) {
|
|
8791
|
-
|
|
8891
|
+
notes.push("Cross-repo search requires Qdrant backend. Fell back to single-repo search.");
|
|
8792
8892
|
}
|
|
8793
|
-
|
|
8893
|
+
if (results.length > 0 && results.every((r) => r.relevance === "not_relevant")) {
|
|
8894
|
+
notes.push("No relevant matches found.");
|
|
8895
|
+
log("Returning 0 results (all not_relevant)");
|
|
8896
|
+
return {
|
|
8897
|
+
indexInfo: getIndexMetadata(),
|
|
8898
|
+
results: [],
|
|
8899
|
+
note: notes.join(" ")
|
|
8900
|
+
};
|
|
8901
|
+
}
|
|
8902
|
+
log(`Returning ${results.length} results`);
|
|
8903
|
+
const shaped = shapeResults(results, "semantic_search");
|
|
8904
|
+
return {
|
|
8905
|
+
indexInfo: getIndexMetadata(),
|
|
8906
|
+
results: shaped,
|
|
8907
|
+
...crossRepo && vectorDB instanceof QdrantDB && { groupedByRepo: groupResultsByRepo(shaped) },
|
|
8908
|
+
...notes.length > 0 && { note: notes.join(" ") }
|
|
8909
|
+
};
|
|
8794
8910
|
}
|
|
8795
8911
|
)(args);
|
|
8796
8912
|
}
|
|
@@ -8820,6 +8936,12 @@ async function handleFindSimilar(args, ctx) {
|
|
|
8820
8936
|
const limit = validatedArgs.limit ?? 5;
|
|
8821
8937
|
const extraLimit = limit + 10;
|
|
8822
8938
|
let results = await vectorDB.search(codeEmbedding, extraLimit, validatedArgs.code);
|
|
8939
|
+
results = deduplicateResults(results);
|
|
8940
|
+
const inputCode = validatedArgs.code.trim();
|
|
8941
|
+
results = results.filter((r) => {
|
|
8942
|
+
if (r.score >= 0.1) return true;
|
|
8943
|
+
return r.content.trim() !== inputCode;
|
|
8944
|
+
});
|
|
8823
8945
|
const filtersApplied = { prunedLowRelevance: 0 };
|
|
8824
8946
|
if (validatedArgs.language) {
|
|
8825
8947
|
filtersApplied.language = validatedArgs.language;
|
|
@@ -8836,7 +8958,7 @@ async function handleFindSimilar(args, ctx) {
|
|
|
8836
8958
|
const hasFilters = filtersApplied.language || filtersApplied.pathHint || filtersApplied.prunedLowRelevance > 0;
|
|
8837
8959
|
return {
|
|
8838
8960
|
indexInfo: getIndexMetadata(),
|
|
8839
|
-
results: finalResults,
|
|
8961
|
+
results: shapeResults(finalResults, "find_similar"),
|
|
8840
8962
|
...hasFilters && { filtersApplied }
|
|
8841
8963
|
};
|
|
8842
8964
|
}
|
|
@@ -8979,7 +9101,9 @@ async function findRelatedChunks(filepaths, fileChunksMap, ctx) {
|
|
|
8979
9101
|
const targetCanonical = getCanonicalPath(filepath, workspaceRoot);
|
|
8980
9102
|
relatedChunksMap[index] = related.filter((r) => {
|
|
8981
9103
|
const chunkCanonical = getCanonicalPath(r.metadata.file, workspaceRoot);
|
|
8982
|
-
|
|
9104
|
+
if (chunkCanonical === targetCanonical) return false;
|
|
9105
|
+
if (r.metadata.language === "markdown") return false;
|
|
9106
|
+
return true;
|
|
8983
9107
|
});
|
|
8984
9108
|
});
|
|
8985
9109
|
return relatedChunksMap;
|
|
@@ -9015,23 +9139,15 @@ function findTestAssociations(filepaths, allChunks, ctx) {
|
|
|
9015
9139
|
return Array.from(testFiles);
|
|
9016
9140
|
});
|
|
9017
9141
|
}
|
|
9018
|
-
function deduplicateChunks(fileChunks, relatedChunks
|
|
9019
|
-
|
|
9020
|
-
return [...fileChunks, ...relatedChunks].filter((chunk) => {
|
|
9021
|
-
const canonicalFile = getCanonicalPath(chunk.metadata.file, workspaceRoot);
|
|
9022
|
-
const chunkId = `${canonicalFile}:${chunk.metadata.startLine}-${chunk.metadata.endLine}`;
|
|
9023
|
-
if (seenChunks.has(chunkId)) return false;
|
|
9024
|
-
seenChunks.add(chunkId);
|
|
9025
|
-
return true;
|
|
9026
|
-
});
|
|
9142
|
+
function deduplicateChunks(fileChunks, relatedChunks) {
|
|
9143
|
+
return deduplicateResults([...fileChunks, ...relatedChunks]);
|
|
9027
9144
|
}
|
|
9028
|
-
function buildFilesData(filepaths, fileChunksMap, relatedChunksMap, testAssociationsMap
|
|
9145
|
+
function buildFilesData(filepaths, fileChunksMap, relatedChunksMap, testAssociationsMap) {
|
|
9029
9146
|
const filesData = {};
|
|
9030
9147
|
filepaths.forEach((filepath, i) => {
|
|
9031
9148
|
const dedupedChunks = deduplicateChunks(
|
|
9032
9149
|
fileChunksMap[i],
|
|
9033
|
-
relatedChunksMap[i] || []
|
|
9034
|
-
workspaceRoot
|
|
9150
|
+
relatedChunksMap[i] || []
|
|
9035
9151
|
);
|
|
9036
9152
|
filesData[filepath] = {
|
|
9037
9153
|
chunks: dedupedChunks,
|
|
@@ -9044,18 +9160,26 @@ function buildScanLimitNote(hitScanLimit) {
|
|
|
9044
9160
|
return hitScanLimit ? "Scanned 10,000 chunks (limit reached). Test associations may be incomplete for large codebases." : void 0;
|
|
9045
9161
|
}
|
|
9046
9162
|
function buildSingleFileResponse(filepath, filesData, indexInfo, note) {
|
|
9163
|
+
const data = filesData[filepath];
|
|
9047
9164
|
return {
|
|
9048
9165
|
indexInfo,
|
|
9049
9166
|
file: filepath,
|
|
9050
|
-
chunks:
|
|
9051
|
-
testAssociations:
|
|
9167
|
+
chunks: shapeResults(data.chunks, "get_files_context"),
|
|
9168
|
+
testAssociations: data.testAssociations,
|
|
9052
9169
|
...note && { note }
|
|
9053
9170
|
};
|
|
9054
9171
|
}
|
|
9055
9172
|
function buildMultiFileResponse(filesData, indexInfo, note) {
|
|
9173
|
+
const shaped = {};
|
|
9174
|
+
for (const [filepath, data] of Object.entries(filesData)) {
|
|
9175
|
+
shaped[filepath] = {
|
|
9176
|
+
chunks: shapeResults(data.chunks, "get_files_context"),
|
|
9177
|
+
testAssociations: data.testAssociations
|
|
9178
|
+
};
|
|
9179
|
+
}
|
|
9056
9180
|
return {
|
|
9057
9181
|
indexInfo,
|
|
9058
|
-
files:
|
|
9182
|
+
files: shaped,
|
|
9059
9183
|
...note && { note }
|
|
9060
9184
|
};
|
|
9061
9185
|
}
|
|
@@ -9101,8 +9225,7 @@ async function handleGetFilesContext(args, ctx) {
|
|
|
9101
9225
|
filepaths,
|
|
9102
9226
|
fileChunksMap,
|
|
9103
9227
|
relatedChunksMap,
|
|
9104
|
-
testAssociationsMap
|
|
9105
|
-
workspaceRoot
|
|
9228
|
+
testAssociationsMap
|
|
9106
9229
|
);
|
|
9107
9230
|
const totalChunks = Object.values(filesData).reduce(
|
|
9108
9231
|
(sum, f) => sum + f.chunks.length,
|
|
@@ -9160,11 +9283,12 @@ async function handleListFunctions(args, ctx) {
|
|
|
9160
9283
|
log(`Symbol query failed: ${error}`);
|
|
9161
9284
|
queryResult = await performContentScan(vectorDB, validatedArgs, log);
|
|
9162
9285
|
}
|
|
9163
|
-
|
|
9286
|
+
const dedupedResults = deduplicateResults(queryResult.results);
|
|
9287
|
+
log(`Found ${dedupedResults.length} matches using ${queryResult.method} method`);
|
|
9164
9288
|
return {
|
|
9165
9289
|
indexInfo: getIndexMetadata(),
|
|
9166
9290
|
method: queryResult.method,
|
|
9167
|
-
results:
|
|
9291
|
+
results: shapeResults(dedupedResults, "list_functions"),
|
|
9168
9292
|
note: queryResult.method === "content" ? 'Using content search. Run "lien reindex" to enable faster symbol-based queries.' : void 0
|
|
9169
9293
|
};
|
|
9170
9294
|
}
|
|
@@ -9680,7 +9804,11 @@ async function handleGetComplexity(args, ctx) {
|
|
|
9680
9804
|
threshold,
|
|
9681
9805
|
top ?? 10
|
|
9682
9806
|
);
|
|
9683
|
-
const
|
|
9807
|
+
const note = buildCrossRepoFallbackNote(fallback);
|
|
9808
|
+
if (note) {
|
|
9809
|
+
log("Warning: crossRepo=true requires Qdrant backend. Falling back to single-repo analysis.", "warning");
|
|
9810
|
+
}
|
|
9811
|
+
return {
|
|
9684
9812
|
indexInfo: getIndexMetadata(),
|
|
9685
9813
|
summary: {
|
|
9686
9814
|
filesAnalyzed: report.summary.filesAnalyzed,
|
|
@@ -9689,17 +9817,12 @@ async function handleGetComplexity(args, ctx) {
|
|
|
9689
9817
|
violationCount: violations.length,
|
|
9690
9818
|
bySeverity
|
|
9691
9819
|
},
|
|
9692
|
-
violations: topViolations
|
|
9820
|
+
violations: topViolations,
|
|
9821
|
+
...crossRepo && !fallback && allChunks.length > 0 && {
|
|
9822
|
+
groupedByRepo: groupViolationsByRepo(topViolations, allChunks)
|
|
9823
|
+
},
|
|
9824
|
+
...note && { note }
|
|
9693
9825
|
};
|
|
9694
|
-
if (crossRepo && !fallback && allChunks.length > 0) {
|
|
9695
|
-
response.groupedByRepo = groupViolationsByRepo(topViolations, allChunks);
|
|
9696
|
-
}
|
|
9697
|
-
const note = buildCrossRepoFallbackNote(fallback);
|
|
9698
|
-
if (note) {
|
|
9699
|
-
log("Warning: crossRepo=true requires Qdrant backend. Falling back to single-repo analysis.", "warning");
|
|
9700
|
-
response.note = note;
|
|
9701
|
-
}
|
|
9702
|
-
return response;
|
|
9703
9826
|
}
|
|
9704
9827
|
)(args);
|
|
9705
9828
|
}
|