@liendev/lien 0.32.0 → 0.34.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 +267 -112
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -8525,7 +8525,13 @@ var ListFunctionsSchema = external_exports.object({
|
|
|
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
|
+
symbolType: external_exports.enum(["function", "method", "class", "interface"]).optional().describe("Filter by symbol type. If omitted, returns all types."),
|
|
8529
|
+
limit: external_exports.number().int().min(1).max(200).default(50).describe(
|
|
8530
|
+
"Number of results to return.\n\nDefault: 50\nIncrease to 200 for broad exploration."
|
|
8531
|
+
),
|
|
8532
|
+
offset: external_exports.number().int().min(0).max(1e4).default(0).describe(
|
|
8533
|
+
"Skip first N results before applying limit, equivalent to pagination offset.\n\nDefault: 0"
|
|
8534
|
+
)
|
|
8529
8535
|
});
|
|
8530
8536
|
|
|
8531
8537
|
// src/mcp/schemas/dependents.schema.ts
|
|
@@ -8578,7 +8584,11 @@ IMPORTANT: Phrase queries as full questions starting with "How", "Where", "What"
|
|
|
8578
8584
|
|
|
8579
8585
|
Use natural language describing what the code DOES, not function names. For exact string matching, use grep instead.
|
|
8580
8586
|
|
|
8581
|
-
|
|
8587
|
+
Returns:
|
|
8588
|
+
- results[]: { content, score, relevance, metadata: { file, startLine, endLine, language?, symbolName?, symbolType?, signature?, enclosingSymbol? } }
|
|
8589
|
+
- enclosingSymbol: "Class.method" for methods, "functionName" for standalone functions, absent for block chunks
|
|
8590
|
+
- relevance: "highly_relevant" | "relevant" | "loosely_related" (not_relevant auto-filtered)
|
|
8591
|
+
- groupedByRepo?: Record<repoId, results[]> (when crossRepo=true)`
|
|
8582
8592
|
),
|
|
8583
8593
|
toMCPToolSchema(
|
|
8584
8594
|
FindSimilarSchema,
|
|
@@ -8594,7 +8604,13 @@ Optional filters:
|
|
|
8594
8604
|
- language: Filter by programming language (e.g., "typescript", "python")
|
|
8595
8605
|
- pathHint: Filter by file path substring (e.g., "src/api", "components")
|
|
8596
8606
|
|
|
8597
|
-
Low-relevance results (not_relevant) are automatically pruned
|
|
8607
|
+
Low-relevance results (not_relevant) are automatically pruned.
|
|
8608
|
+
|
|
8609
|
+
Returns:
|
|
8610
|
+
- results[]: { content, score, relevance, metadata: { file, startLine, endLine, language?, symbolName?, signature?, enclosingSymbol? } }
|
|
8611
|
+
- enclosingSymbol: "Class.method" for methods, "functionName" for standalone functions, absent for block chunks
|
|
8612
|
+
- relevance: "highly_relevant" | "relevant" | "loosely_related" (not_relevant auto-filtered)
|
|
8613
|
+
- filtersApplied?: { language?, pathHint?, prunedLowRelevance: number }`
|
|
8598
8614
|
),
|
|
8599
8615
|
toMCPToolSchema(
|
|
8600
8616
|
GetFilesContextSchema,
|
|
@@ -8653,7 +8669,16 @@ Examples:
|
|
|
8653
8669
|
|
|
8654
8670
|
Filter by symbol type (function, method, class, interface) to narrow results.
|
|
8655
8671
|
|
|
8656
|
-
10x faster than semantic_search for structural/architectural queries. Use semantic_search instead when searching by what code DOES
|
|
8672
|
+
10x faster than semantic_search for structural/architectural queries. Use semantic_search instead when searching by what code DOES.
|
|
8673
|
+
|
|
8674
|
+
Results are paginated (default: 50, max: 200). Use \`offset\` to page through large result sets.
|
|
8675
|
+
|
|
8676
|
+
Returns:
|
|
8677
|
+
- results[]: { content, metadata: { file, startLine, endLine, language?, symbolName?, symbolType?, signature?, enclosingSymbol? } }
|
|
8678
|
+
- enclosingSymbol: "Class.method" for methods, "functionName" for standalone functions, absent for block chunks
|
|
8679
|
+
- method: "symbols" | "content" (query method used)
|
|
8680
|
+
- hasMore: boolean (more results available)
|
|
8681
|
+
- nextOffset?: number (offset for next page, when hasMore=true)`
|
|
8657
8682
|
),
|
|
8658
8683
|
toMCPToolSchema(
|
|
8659
8684
|
GetDependentsSchema,
|
|
@@ -8663,11 +8688,15 @@ Filter by symbol type (function, method, class, interface) to narrow results.
|
|
|
8663
8688
|
- "Is this safe to delete?"
|
|
8664
8689
|
- "What imports this module?"
|
|
8665
8690
|
|
|
8666
|
-
|
|
8667
|
-
- List of files that import the target
|
|
8668
|
-
- Risk level (low/medium/high/critical) based on dependent count and complexity
|
|
8691
|
+
Example: get_dependents({ filepath: "src/utils/validate.ts" })
|
|
8669
8692
|
|
|
8670
|
-
|
|
8693
|
+
Returns:
|
|
8694
|
+
- dependentCount / productionDependentCount / testDependentCount
|
|
8695
|
+
- riskLevel: "low" | "medium" | "high" | "critical"
|
|
8696
|
+
- dependents[]: { filepath, isTestFile, usages[]? }
|
|
8697
|
+
- complexityMetrics: { averageComplexity, maxComplexity, highComplexityDependents[] }
|
|
8698
|
+
- totalUsageCount?: number (when symbol parameter provided)
|
|
8699
|
+
- groupedByRepo?: Record<repoId, dependents[]> (when crossRepo=true)`
|
|
8671
8700
|
),
|
|
8672
8701
|
toMCPToolSchema(
|
|
8673
8702
|
GetComplexitySchema,
|
|
@@ -8690,19 +8719,108 @@ Examples:
|
|
|
8690
8719
|
get_complexity({ files: ["src/auth.ts", "src/api/user.ts"] })
|
|
8691
8720
|
get_complexity({ threshold: 15 })
|
|
8692
8721
|
|
|
8693
|
-
Returns
|
|
8694
|
-
|
|
8695
|
-
|
|
8722
|
+
Returns:
|
|
8723
|
+
- summary: { filesAnalyzed, avgComplexity, maxComplexity, violationCount, bySeverity: { error, warning } }
|
|
8724
|
+
- violations[]: { filepath, symbolName, symbolType, complexity, metricType, threshold, severity, riskLevel, dependentCount }
|
|
8725
|
+
- metricType: "cyclomatic" | "cognitive" | "halstead_effort" | "halstead_bugs"
|
|
8726
|
+
- severity: "error" | "warning"
|
|
8727
|
+
- groupedByRepo?: Record<repoId, violations[]> (when crossRepo=true)`
|
|
8696
8728
|
)
|
|
8697
8729
|
];
|
|
8698
8730
|
|
|
8699
8731
|
// src/mcp/utils/tool-wrapper.ts
|
|
8700
8732
|
import { LienError, LienErrorCode } from "@liendev/core";
|
|
8733
|
+
|
|
8734
|
+
// src/mcp/utils/response-budget.ts
|
|
8735
|
+
var MAX_RESPONSE_CHARS = 12e3;
|
|
8736
|
+
function applyResponseBudget(result, maxChars = MAX_RESPONSE_CHARS) {
|
|
8737
|
+
const serialized = JSON.stringify(result);
|
|
8738
|
+
if (serialized.length <= maxChars) {
|
|
8739
|
+
return { result };
|
|
8740
|
+
}
|
|
8741
|
+
const originalChars = serialized.length;
|
|
8742
|
+
const cloned = JSON.parse(serialized);
|
|
8743
|
+
const arrays = findContentArrays(cloned);
|
|
8744
|
+
if (arrays.length === 0) {
|
|
8745
|
+
return { result };
|
|
8746
|
+
}
|
|
8747
|
+
for (const arr of arrays) {
|
|
8748
|
+
for (const item of arr) {
|
|
8749
|
+
item.content = truncateContent(item.content, 10);
|
|
8750
|
+
}
|
|
8751
|
+
}
|
|
8752
|
+
if (measureSize(cloned) <= maxChars) {
|
|
8753
|
+
return buildResult(cloned, originalChars, 1);
|
|
8754
|
+
}
|
|
8755
|
+
let currentSize = measureSize(cloned);
|
|
8756
|
+
for (const arr of arrays) {
|
|
8757
|
+
while (arr.length > 1 && currentSize > maxChars) {
|
|
8758
|
+
arr.pop();
|
|
8759
|
+
currentSize = measureSize(cloned);
|
|
8760
|
+
}
|
|
8761
|
+
}
|
|
8762
|
+
if (currentSize <= maxChars) {
|
|
8763
|
+
return buildResult(cloned, originalChars, 2);
|
|
8764
|
+
}
|
|
8765
|
+
for (const arr of arrays) {
|
|
8766
|
+
for (const item of arr) {
|
|
8767
|
+
item.content = truncateContent(item.content, 3);
|
|
8768
|
+
}
|
|
8769
|
+
}
|
|
8770
|
+
return buildResult(cloned, originalChars, 3);
|
|
8771
|
+
}
|
|
8772
|
+
function truncateContent(content, maxLines) {
|
|
8773
|
+
const lines = content.split("\n");
|
|
8774
|
+
if (lines.length <= maxLines) return content;
|
|
8775
|
+
return lines.slice(0, maxLines).join("\n") + "\n... (truncated)";
|
|
8776
|
+
}
|
|
8777
|
+
function measureSize(obj) {
|
|
8778
|
+
return JSON.stringify(obj).length;
|
|
8779
|
+
}
|
|
8780
|
+
function findContentArrays(obj) {
|
|
8781
|
+
const found = [];
|
|
8782
|
+
walk(obj, found);
|
|
8783
|
+
return found;
|
|
8784
|
+
}
|
|
8785
|
+
function walk(node, found) {
|
|
8786
|
+
if (node === null || typeof node !== "object") return;
|
|
8787
|
+
if (Array.isArray(node)) {
|
|
8788
|
+
if (node.length > 0 && node.every(
|
|
8789
|
+
(elem) => typeof elem === "object" && elem !== null && typeof elem.content === "string"
|
|
8790
|
+
)) {
|
|
8791
|
+
found.push(node);
|
|
8792
|
+
}
|
|
8793
|
+
return;
|
|
8794
|
+
}
|
|
8795
|
+
for (const value of Object.values(node)) {
|
|
8796
|
+
walk(value, found);
|
|
8797
|
+
}
|
|
8798
|
+
}
|
|
8799
|
+
function buildResult(cloned, originalChars, phase) {
|
|
8800
|
+
const finalChars = measureSize(cloned);
|
|
8801
|
+
return {
|
|
8802
|
+
result: cloned,
|
|
8803
|
+
truncation: {
|
|
8804
|
+
originalChars,
|
|
8805
|
+
finalChars,
|
|
8806
|
+
phase,
|
|
8807
|
+
message: `Response truncated from ${originalChars} to ${finalChars} chars (phase ${phase}/3). Use narrower filters or smaller limit for complete results.`
|
|
8808
|
+
}
|
|
8809
|
+
};
|
|
8810
|
+
}
|
|
8811
|
+
|
|
8812
|
+
// src/mcp/utils/tool-wrapper.ts
|
|
8701
8813
|
function wrapToolHandler(schema, handler) {
|
|
8702
8814
|
return async (args) => {
|
|
8703
8815
|
try {
|
|
8704
8816
|
const validated = schema.parse(args);
|
|
8705
|
-
const
|
|
8817
|
+
const rawResult = await handler(validated);
|
|
8818
|
+
const { result, truncation } = applyResponseBudget(rawResult);
|
|
8819
|
+
if (truncation && typeof result === "object" && result !== null) {
|
|
8820
|
+
const obj = result;
|
|
8821
|
+
obj.note = obj.note ? `${obj.note}
|
|
8822
|
+
${truncation.message}` : truncation.message;
|
|
8823
|
+
}
|
|
8706
8824
|
return {
|
|
8707
8825
|
content: [{
|
|
8708
8826
|
type: "text",
|
|
@@ -8710,45 +8828,48 @@ function wrapToolHandler(schema, handler) {
|
|
|
8710
8828
|
}]
|
|
8711
8829
|
};
|
|
8712
8830
|
} catch (error) {
|
|
8713
|
-
|
|
8714
|
-
return {
|
|
8715
|
-
isError: true,
|
|
8716
|
-
content: [{
|
|
8717
|
-
type: "text",
|
|
8718
|
-
text: JSON.stringify({
|
|
8719
|
-
error: "Invalid parameters",
|
|
8720
|
-
code: LienErrorCode.INVALID_INPUT,
|
|
8721
|
-
details: error.errors.map((e) => ({
|
|
8722
|
-
field: e.path.join("."),
|
|
8723
|
-
message: e.message
|
|
8724
|
-
}))
|
|
8725
|
-
}, null, 2)
|
|
8726
|
-
}]
|
|
8727
|
-
};
|
|
8728
|
-
}
|
|
8729
|
-
if (error instanceof LienError) {
|
|
8730
|
-
return {
|
|
8731
|
-
isError: true,
|
|
8732
|
-
content: [{
|
|
8733
|
-
type: "text",
|
|
8734
|
-
text: JSON.stringify(error.toJSON(), null, 2)
|
|
8735
|
-
}]
|
|
8736
|
-
};
|
|
8737
|
-
}
|
|
8738
|
-
console.error("Unexpected error in tool handler:", error);
|
|
8739
|
-
return {
|
|
8740
|
-
isError: true,
|
|
8741
|
-
content: [{
|
|
8742
|
-
type: "text",
|
|
8743
|
-
text: JSON.stringify({
|
|
8744
|
-
error: error instanceof Error ? error.message : "Unknown error",
|
|
8745
|
-
code: LienErrorCode.INTERNAL_ERROR
|
|
8746
|
-
}, null, 2)
|
|
8747
|
-
}]
|
|
8748
|
-
};
|
|
8831
|
+
return formatErrorResponse(error);
|
|
8749
8832
|
}
|
|
8750
8833
|
};
|
|
8751
8834
|
}
|
|
8835
|
+
function formatErrorResponse(error) {
|
|
8836
|
+
if (error instanceof ZodError) {
|
|
8837
|
+
return {
|
|
8838
|
+
isError: true,
|
|
8839
|
+
content: [{
|
|
8840
|
+
type: "text",
|
|
8841
|
+
text: JSON.stringify({
|
|
8842
|
+
error: "Invalid parameters",
|
|
8843
|
+
code: LienErrorCode.INVALID_INPUT,
|
|
8844
|
+
details: error.errors.map((e) => ({
|
|
8845
|
+
field: e.path.join("."),
|
|
8846
|
+
message: e.message
|
|
8847
|
+
}))
|
|
8848
|
+
}, null, 2)
|
|
8849
|
+
}]
|
|
8850
|
+
};
|
|
8851
|
+
}
|
|
8852
|
+
if (error instanceof LienError) {
|
|
8853
|
+
return {
|
|
8854
|
+
isError: true,
|
|
8855
|
+
content: [{
|
|
8856
|
+
type: "text",
|
|
8857
|
+
text: JSON.stringify(error.toJSON(), null, 2)
|
|
8858
|
+
}]
|
|
8859
|
+
};
|
|
8860
|
+
}
|
|
8861
|
+
console.error("Unexpected error in tool handler:", error);
|
|
8862
|
+
return {
|
|
8863
|
+
isError: true,
|
|
8864
|
+
content: [{
|
|
8865
|
+
type: "text",
|
|
8866
|
+
text: JSON.stringify({
|
|
8867
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
8868
|
+
code: LienErrorCode.INTERNAL_ERROR
|
|
8869
|
+
}, null, 2)
|
|
8870
|
+
}]
|
|
8871
|
+
};
|
|
8872
|
+
}
|
|
8752
8873
|
|
|
8753
8874
|
// src/mcp/utils/metadata-shaper.ts
|
|
8754
8875
|
var FIELD_ALLOWLISTS = {
|
|
@@ -8841,6 +8962,9 @@ function pickMetadata(metadata, allowlist) {
|
|
|
8841
8962
|
out[key] = cleaned;
|
|
8842
8963
|
}
|
|
8843
8964
|
}
|
|
8965
|
+
if (metadata.symbolName) {
|
|
8966
|
+
out["enclosingSymbol"] = metadata.parentClass ? `${metadata.parentClass}.${metadata.symbolName}` : metadata.symbolName;
|
|
8967
|
+
}
|
|
8844
8968
|
return result;
|
|
8845
8969
|
}
|
|
8846
8970
|
function shapeResultMetadata(result, tool) {
|
|
@@ -8868,6 +8992,33 @@ function groupResultsByRepo(results) {
|
|
|
8868
8992
|
}
|
|
8869
8993
|
return grouped;
|
|
8870
8994
|
}
|
|
8995
|
+
async function executeSearch(vectorDB, queryEmbedding, params, log) {
|
|
8996
|
+
const { query, limit, crossRepo, repoIds } = params;
|
|
8997
|
+
if (crossRepo && vectorDB instanceof QdrantDB) {
|
|
8998
|
+
const results2 = await vectorDB.searchCrossRepo(queryEmbedding, limit, { repoIds });
|
|
8999
|
+
log(`Found ${results2.length} results across ${Object.keys(groupResultsByRepo(results2)).length} repos`);
|
|
9000
|
+
return { results: results2, crossRepoFallback: false };
|
|
9001
|
+
}
|
|
9002
|
+
if (crossRepo) {
|
|
9003
|
+
log("Warning: crossRepo=true requires Qdrant backend. Falling back to single-repo search.", "warning");
|
|
9004
|
+
}
|
|
9005
|
+
const results = await vectorDB.search(queryEmbedding, limit, query);
|
|
9006
|
+
log(`Found ${results.length} results`);
|
|
9007
|
+
return { results, crossRepoFallback: !!crossRepo };
|
|
9008
|
+
}
|
|
9009
|
+
function processResults(rawResults, crossRepoFallback, log) {
|
|
9010
|
+
const notes = [];
|
|
9011
|
+
if (crossRepoFallback) {
|
|
9012
|
+
notes.push("Cross-repo search requires Qdrant backend. Fell back to single-repo search.");
|
|
9013
|
+
}
|
|
9014
|
+
const results = deduplicateResults(rawResults);
|
|
9015
|
+
if (results.length > 0 && results.every((r) => r.relevance === "not_relevant")) {
|
|
9016
|
+
notes.push("No relevant matches found.");
|
|
9017
|
+
log("Returning 0 results (all not_relevant)");
|
|
9018
|
+
return { results: [], notes };
|
|
9019
|
+
}
|
|
9020
|
+
return { results, notes };
|
|
9021
|
+
}
|
|
8871
9022
|
async function handleSemanticSearch(args, ctx) {
|
|
8872
9023
|
const { vectorDB, embeddings, log, checkAndReconnect, getIndexMetadata } = ctx;
|
|
8873
9024
|
return await wrapToolHandler(
|
|
@@ -8877,35 +9028,18 @@ async function handleSemanticSearch(args, ctx) {
|
|
|
8877
9028
|
log(`Searching for: "${query}"${crossRepo ? " (cross-repo)" : ""}`);
|
|
8878
9029
|
await checkAndReconnect();
|
|
8879
9030
|
const queryEmbedding = await embeddings.embed(query);
|
|
8880
|
-
|
|
8881
|
-
|
|
8882
|
-
|
|
8883
|
-
|
|
8884
|
-
log
|
|
8885
|
-
|
|
8886
|
-
|
|
8887
|
-
log("Warning: crossRepo=true requires Qdrant backend. Falling back to single-repo search.");
|
|
8888
|
-
crossRepoFallback = true;
|
|
8889
|
-
}
|
|
8890
|
-
results = await vectorDB.search(queryEmbedding, limit, query);
|
|
8891
|
-
log(`Found ${results.length} results`);
|
|
8892
|
-
}
|
|
8893
|
-
results = deduplicateResults(results);
|
|
8894
|
-
const notes = [];
|
|
8895
|
-
if (crossRepoFallback) {
|
|
8896
|
-
notes.push("Cross-repo search requires Qdrant backend. Fell back to single-repo search.");
|
|
8897
|
-
}
|
|
8898
|
-
if (results.length > 0 && results.every((r) => r.relevance === "not_relevant")) {
|
|
8899
|
-
notes.push("No relevant matches found.");
|
|
8900
|
-
log("Returning 0 results (all not_relevant)");
|
|
8901
|
-
return {
|
|
8902
|
-
indexInfo: getIndexMetadata(),
|
|
8903
|
-
results: [],
|
|
8904
|
-
note: notes.join(" ")
|
|
8905
|
-
};
|
|
8906
|
-
}
|
|
9031
|
+
const { results: rawResults, crossRepoFallback } = await executeSearch(
|
|
9032
|
+
vectorDB,
|
|
9033
|
+
queryEmbedding,
|
|
9034
|
+
{ query, limit: limit ?? 5, crossRepo, repoIds },
|
|
9035
|
+
log
|
|
9036
|
+
);
|
|
9037
|
+
const { results, notes } = processResults(rawResults, crossRepoFallback, log);
|
|
8907
9038
|
log(`Returning ${results.length} results`);
|
|
8908
9039
|
const shaped = shapeResults(results, "semantic_search");
|
|
9040
|
+
if (shaped.length === 0) {
|
|
9041
|
+
notes.push('0 results. Try rephrasing as a full question (e.g. "How does X work?"), or use grep for exact string matches. If the codebase was recently updated, run "lien reindex".');
|
|
9042
|
+
}
|
|
8909
9043
|
return {
|
|
8910
9044
|
indexInfo: getIndexMetadata(),
|
|
8911
9045
|
results: shaped,
|
|
@@ -8964,7 +9098,8 @@ async function handleFindSimilar(args, ctx) {
|
|
|
8964
9098
|
return {
|
|
8965
9099
|
indexInfo: getIndexMetadata(),
|
|
8966
9100
|
results: shapeResults(finalResults, "find_similar"),
|
|
8967
|
-
...hasFilters && { filtersApplied }
|
|
9101
|
+
...hasFilters && { filtersApplied },
|
|
9102
|
+
...finalResults.length === 0 && { note: "0 results. Ensure the code snippet is at least 24 characters and representative of the pattern. Try grep for exact string matches." }
|
|
8968
9103
|
};
|
|
8969
9104
|
}
|
|
8970
9105
|
)(args);
|
|
@@ -9063,19 +9198,15 @@ function isTestFile(filepath) {
|
|
|
9063
9198
|
}
|
|
9064
9199
|
|
|
9065
9200
|
// src/mcp/handlers/get-files-context.ts
|
|
9201
|
+
import { MAX_CHUNKS_PER_FILE } from "@liendev/core";
|
|
9066
9202
|
var SCAN_LIMIT = 1e4;
|
|
9067
9203
|
async function searchFileChunks(filepaths, ctx) {
|
|
9068
|
-
const { vectorDB,
|
|
9069
|
-
const
|
|
9070
|
-
filepaths
|
|
9071
|
-
|
|
9072
|
-
|
|
9073
|
-
|
|
9074
|
-
(embedding, i) => vectorDB.search(embedding, 50, filepaths[i])
|
|
9075
|
-
)
|
|
9076
|
-
);
|
|
9077
|
-
return filepaths.map((filepath, i) => {
|
|
9078
|
-
const allResults = allFileSearches[i];
|
|
9204
|
+
const { vectorDB, workspaceRoot } = ctx;
|
|
9205
|
+
const allResults = await vectorDB.scanWithFilter({
|
|
9206
|
+
file: filepaths,
|
|
9207
|
+
limit: filepaths.length * MAX_CHUNKS_PER_FILE
|
|
9208
|
+
});
|
|
9209
|
+
return filepaths.map((filepath) => {
|
|
9079
9210
|
const targetCanonical = getCanonicalPath(filepath, workspaceRoot);
|
|
9080
9211
|
return allResults.filter((r) => {
|
|
9081
9212
|
const chunkCanonical = getCanonicalPath(r.metadata.file, workspaceRoot);
|
|
@@ -9245,13 +9376,12 @@ async function handleGetFilesContext(args, ctx) {
|
|
|
9245
9376
|
}
|
|
9246
9377
|
|
|
9247
9378
|
// src/mcp/handlers/list-functions.ts
|
|
9248
|
-
async function performContentScan(vectorDB, args, log) {
|
|
9379
|
+
async function performContentScan(vectorDB, args, fetchLimit, log) {
|
|
9249
9380
|
log("Falling back to content scan...");
|
|
9250
9381
|
let results = await vectorDB.scanWithFilter({
|
|
9251
9382
|
language: args.language,
|
|
9252
9383
|
symbolType: args.symbolType,
|
|
9253
|
-
limit:
|
|
9254
|
-
// Fetch more, we'll filter by symbolName
|
|
9384
|
+
limit: fetchLimit
|
|
9255
9385
|
});
|
|
9256
9386
|
if (args.pattern) {
|
|
9257
9387
|
const regex = new RegExp(args.pattern, "i");
|
|
@@ -9261,10 +9391,38 @@ async function performContentScan(vectorDB, args, log) {
|
|
|
9261
9391
|
});
|
|
9262
9392
|
}
|
|
9263
9393
|
return {
|
|
9264
|
-
results
|
|
9394
|
+
results,
|
|
9265
9395
|
method: "content"
|
|
9266
9396
|
};
|
|
9267
9397
|
}
|
|
9398
|
+
async function queryWithFallback(vectorDB, args, fetchLimit, log) {
|
|
9399
|
+
try {
|
|
9400
|
+
const results = await vectorDB.querySymbols({
|
|
9401
|
+
language: args.language,
|
|
9402
|
+
pattern: args.pattern,
|
|
9403
|
+
symbolType: args.symbolType,
|
|
9404
|
+
limit: fetchLimit
|
|
9405
|
+
});
|
|
9406
|
+
if (results.length === 0 && (args.language || args.pattern || args.symbolType)) {
|
|
9407
|
+
log("No symbol results, falling back to content scan...");
|
|
9408
|
+
return await performContentScan(vectorDB, args, fetchLimit, log);
|
|
9409
|
+
}
|
|
9410
|
+
return { results, method: "symbols" };
|
|
9411
|
+
} catch (error) {
|
|
9412
|
+
log(`Symbol query failed: ${error}`);
|
|
9413
|
+
return await performContentScan(vectorDB, args, fetchLimit, log);
|
|
9414
|
+
}
|
|
9415
|
+
}
|
|
9416
|
+
function paginateResults(results, offset, limit) {
|
|
9417
|
+
const dedupedResults = deduplicateResults(results);
|
|
9418
|
+
const hasMore = dedupedResults.length > offset + limit;
|
|
9419
|
+
const paginatedResults = dedupedResults.slice(offset, offset + limit);
|
|
9420
|
+
return {
|
|
9421
|
+
paginatedResults,
|
|
9422
|
+
hasMore,
|
|
9423
|
+
...hasMore ? { nextOffset: offset + limit } : {}
|
|
9424
|
+
};
|
|
9425
|
+
}
|
|
9268
9426
|
async function handleListFunctions(args, ctx) {
|
|
9269
9427
|
const { vectorDB, log, checkAndReconnect, getIndexMetadata } = ctx;
|
|
9270
9428
|
return await wrapToolHandler(
|
|
@@ -9272,31 +9430,28 @@ async function handleListFunctions(args, ctx) {
|
|
|
9272
9430
|
async (validatedArgs) => {
|
|
9273
9431
|
log("Listing functions with symbol metadata...");
|
|
9274
9432
|
await checkAndReconnect();
|
|
9275
|
-
|
|
9276
|
-
|
|
9277
|
-
|
|
9278
|
-
|
|
9279
|
-
|
|
9280
|
-
|
|
9281
|
-
|
|
9282
|
-
|
|
9283
|
-
|
|
9284
|
-
|
|
9285
|
-
|
|
9286
|
-
|
|
9287
|
-
|
|
9288
|
-
|
|
9289
|
-
} catch (error) {
|
|
9290
|
-
log(`Symbol query failed: ${error}`);
|
|
9291
|
-
queryResult = await performContentScan(vectorDB, validatedArgs, log);
|
|
9433
|
+
const limit = validatedArgs.limit ?? 50;
|
|
9434
|
+
const offset = validatedArgs.offset ?? 0;
|
|
9435
|
+
const fetchLimit = limit + offset + 1;
|
|
9436
|
+
const queryResult = await queryWithFallback(vectorDB, validatedArgs, fetchLimit, log);
|
|
9437
|
+
const { paginatedResults, hasMore, nextOffset } = paginateResults(queryResult.results, offset, limit);
|
|
9438
|
+
log(`Found ${paginatedResults.length} matches using ${queryResult.method} method`);
|
|
9439
|
+
const notes = [];
|
|
9440
|
+
if (queryResult.results.length === 0) {
|
|
9441
|
+
notes.push('0 results. Try a broader regex pattern (e.g. ".*") or omit the symbolType filter. Use semantic_search for behavior-based queries.');
|
|
9442
|
+
} else if (paginatedResults.length === 0 && offset > 0) {
|
|
9443
|
+
notes.push("No results for this page. The offset is beyond the available results; try reducing or resetting the offset to 0.");
|
|
9444
|
+
}
|
|
9445
|
+
if (queryResult.method === "content") {
|
|
9446
|
+
notes.push('Using content search. Run "lien reindex" to enable faster symbol-based queries.');
|
|
9292
9447
|
}
|
|
9293
|
-
const dedupedResults = deduplicateResults(queryResult.results);
|
|
9294
|
-
log(`Found ${dedupedResults.length} matches using ${queryResult.method} method`);
|
|
9295
9448
|
return {
|
|
9296
9449
|
indexInfo: getIndexMetadata(),
|
|
9297
9450
|
method: queryResult.method,
|
|
9298
|
-
|
|
9299
|
-
|
|
9451
|
+
hasMore,
|
|
9452
|
+
...nextOffset !== void 0 ? { nextOffset } : {},
|
|
9453
|
+
results: shapeResults(paginatedResults, "list_functions"),
|
|
9454
|
+
...notes.length > 0 && { note: notes.join(" ") }
|
|
9300
9455
|
};
|
|
9301
9456
|
}
|
|
9302
9457
|
)(args);
|