flowseeker 0.1.8 → 0.1.9
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/CHANGELOG.md +12 -0
- package/dist/chat/nativeChatParticipant.js +1 -1
- package/dist/cli/main.js +19 -3
- package/dist/cli/runEvaluation.js +103 -0
- package/dist/eval/accuracyV2.js +483 -0
- package/dist/eval/goldenTask.js +192 -0
- package/dist/framework/laravel.js +177 -0
- package/dist/index/semanticChunkIndex.js +388 -0
- package/dist/index/workspaceIndex.js +339 -29
- package/dist/mcp/mcpTools.js +1627 -2
- package/dist/pipeline/contextBlueprint.js +15 -2
- package/dist/pipeline/fileScanner.js +116 -1
- package/dist/pipeline/fusionTrace.js +149 -0
- package/dist/pipeline/nodeScan.js +13 -2
- package/dist/pipeline/roleRefinement.js +62 -0
- package/dist/pipeline/runHeadless.js +25 -5
- package/dist/pipeline/runPipeline.js +2 -2
- package/dist/pipeline/solvePacket.js +112 -6
- package/dist/pipeline/taskUnderstanding.js +2 -2
- package/dist/ui/chatViewProvider.js +1 -1
- package/package.json +2 -2
|
@@ -36,8 +36,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.loadOrBuildWorkspaceIndex = loadOrBuildWorkspaceIndex;
|
|
37
37
|
exports.selectIndexCandidates = selectIndexCandidates;
|
|
38
38
|
exports.clearNodeWorkspaceIndex = clearNodeWorkspaceIndex;
|
|
39
|
+
exports.computeChunkStats = computeChunkStats;
|
|
40
|
+
exports.scoreChunksForTask = scoreChunksForTask;
|
|
39
41
|
const crypto = __importStar(require("crypto"));
|
|
40
42
|
const fs = __importStar(require("fs/promises"));
|
|
43
|
+
const fssync = __importStar(require("fs"));
|
|
41
44
|
const path = __importStar(require("path"));
|
|
42
45
|
const dependencyExtractor_1 = require("./dependencyExtractor");
|
|
43
46
|
const fileDiscovery_1 = require("./fileDiscovery");
|
|
@@ -52,7 +55,7 @@ const subsystem_1 = require("../pipeline/subsystem");
|
|
|
52
55
|
const indexVersion = 7;
|
|
53
56
|
const regexExtractorVersion = "structured-regex-mvp-1";
|
|
54
57
|
const treeSitterExtractorVersion = "tree-sitter-mvp-1";
|
|
55
|
-
const chunkerVersion = "
|
|
58
|
+
const chunkerVersion = "chunk-record-mvp-1";
|
|
56
59
|
const graphVersion = "indexed-edges-mvp-1";
|
|
57
60
|
const tokenLimitPerFile = 2500;
|
|
58
61
|
async function loadOrBuildWorkspaceIndex(rootPath, config, options = {}) {
|
|
@@ -513,7 +516,7 @@ async function indexFile(discovered, prior, config) {
|
|
|
513
516
|
const calls = extractCalls(structuredNodes);
|
|
514
517
|
const subsystemTerms = (0, subsystem_1.inferSubsystemTerms)(discovered.relativePath, symbols.map((symbol) => symbol.name));
|
|
515
518
|
const imports = (0, dependencyExtractor_1.extractImports)(content).map((item) => item.source).slice(0, 50);
|
|
516
|
-
const chunks = buildIndexedChunks(discovered.relativePath, contentHash, structuredNodes);
|
|
519
|
+
const chunks = buildIndexedChunks(discovered.relativePath, contentHash, structuredNodes, language ?? "unknown", content, imports, calls, effectiveExtractorVersion, config);
|
|
517
520
|
const edges = buildIndexedEdges(discovered.relativePath, imports, symbols, calls);
|
|
518
521
|
return {
|
|
519
522
|
reused: false,
|
|
@@ -751,38 +754,180 @@ function extractStructuredNodesForIndex(relativePath, content) {
|
|
|
751
754
|
}
|
|
752
755
|
return (0, structuredExtractor_1.extractStructuredNodes)(content.slice(0, 200000), language);
|
|
753
756
|
}
|
|
754
|
-
function buildIndexedChunks(relativePath, contentHash, nodes) {
|
|
755
|
-
const
|
|
756
|
-
|
|
757
|
-
.
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
757
|
+
function buildIndexedChunks(relativePath, contentHash, nodes, language, content, allImports, allCalls, parserVer, config) {
|
|
758
|
+
const declNodes = nodes.filter((node) => node.kind === "declaration" && node.name).slice(0, 120);
|
|
759
|
+
if (declNodes.length > 0) {
|
|
760
|
+
return declNodes.map((node) => {
|
|
761
|
+
const snippet = getSnippet(content, node.range.startLine, node.range.endLine);
|
|
762
|
+
const chunkKind = symbolKindToChunkKind(node.symbolKind, relativePath);
|
|
763
|
+
const chunkSlot = inferChunkSlot(relativePath, chunkKind);
|
|
764
|
+
const lineRange = node.range;
|
|
765
|
+
return {
|
|
766
|
+
id: `${relativePath}:${lineRange.startLine}-${lineRange.endLine}:${node.name}`,
|
|
767
|
+
// Public ChunkRecord fields
|
|
768
|
+
filePath: relativePath,
|
|
769
|
+
startLine: lineRange.startLine,
|
|
770
|
+
endLine: lineRange.endLine,
|
|
771
|
+
language,
|
|
772
|
+
kind: chunkKind,
|
|
773
|
+
slot: chunkSlot,
|
|
774
|
+
symbolName: node.name ?? "",
|
|
775
|
+
parentSymbol: node.parentFqn ?? "",
|
|
776
|
+
textHash: contentHash,
|
|
777
|
+
tokenCount: (0, text_1.fastTokenEstimate)(snippet),
|
|
778
|
+
terms: extractChunkTerms(node.name ?? "", snippet),
|
|
779
|
+
routes: extractRouteHints(snippet, relativePath),
|
|
780
|
+
annotations: extractChunkAnnotations(nodes, lineRange),
|
|
781
|
+
parserKind: node.extractorKind ?? "structured-regex",
|
|
782
|
+
parserVersion: parserVer,
|
|
783
|
+
contentPreview: snippet.slice(0, 300),
|
|
784
|
+
imports: allImports.slice(0, 10),
|
|
785
|
+
calls: allCalls.slice(0, 10),
|
|
786
|
+
// Compatibility fields
|
|
787
|
+
relativePath,
|
|
788
|
+
range: lineRange,
|
|
789
|
+
symbolFqn: node.fqn ?? node.name,
|
|
790
|
+
};
|
|
791
|
+
});
|
|
768
792
|
}
|
|
769
|
-
|
|
770
|
-
|
|
793
|
+
// Fallback: line window chunks for symbol-less files
|
|
794
|
+
return buildFallbackChunks(relativePath, contentHash, language, content, allImports, allCalls, parserVer, config);
|
|
795
|
+
}
|
|
796
|
+
function buildFallbackChunks(relativePath, contentHash, language, content, allImports, allCalls, parserVer, config) {
|
|
797
|
+
const lines = content.split(/\r?\n/);
|
|
798
|
+
const chunkSize = Math.max(5, config.index.chunkSizeLines || 80);
|
|
799
|
+
const overlap = Math.max(0, config.index.chunkOverlapLines ?? 12);
|
|
800
|
+
const step = Math.max(1, chunkSize - overlap);
|
|
801
|
+
const maxChunks = 50;
|
|
802
|
+
const chunks = [];
|
|
803
|
+
for (let start = 0; start < lines.length && chunks.length < maxChunks; start += step) {
|
|
804
|
+
const end = Math.min(lines.length, start + chunkSize);
|
|
805
|
+
const snippet = lines.slice(start, end).join("\n");
|
|
806
|
+
const chunkKind = inferFileChunkKind(relativePath);
|
|
807
|
+
const chunkSlot = inferChunkSlot(relativePath, chunkKind);
|
|
808
|
+
const firstLine = start + 1;
|
|
809
|
+
const lastLine = end;
|
|
810
|
+
chunks.push({
|
|
811
|
+
id: `${relativePath}:L${firstLine}-${lastLine}`,
|
|
812
|
+
// Public ChunkRecord fields
|
|
813
|
+
filePath: relativePath,
|
|
814
|
+
startLine: firstLine,
|
|
815
|
+
endLine: lastLine,
|
|
816
|
+
language,
|
|
817
|
+
kind: chunkKind,
|
|
818
|
+
slot: chunkSlot,
|
|
819
|
+
symbolName: "",
|
|
820
|
+
parentSymbol: "",
|
|
821
|
+
textHash: contentHash,
|
|
822
|
+
tokenCount: (0, text_1.fastTokenEstimate)(snippet),
|
|
823
|
+
terms: extractChunkTerms("", snippet),
|
|
824
|
+
routes: extractRouteHints(snippet, relativePath),
|
|
825
|
+
annotations: [],
|
|
826
|
+
parserKind: "unknown",
|
|
827
|
+
parserVersion: parserVer,
|
|
828
|
+
contentPreview: snippet.slice(0, 200),
|
|
829
|
+
imports: allImports.slice(0, 10),
|
|
830
|
+
calls: allCalls.slice(0, 10),
|
|
831
|
+
// Compatibility fields
|
|
771
832
|
relativePath,
|
|
772
|
-
range: { startLine:
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
833
|
+
range: { startLine: firstLine, endLine: lastLine },
|
|
834
|
+
});
|
|
835
|
+
}
|
|
836
|
+
return chunks;
|
|
837
|
+
}
|
|
838
|
+
function getSnippet(content, startLine, endLine) {
|
|
839
|
+
const lines = content.split(/\r?\n/);
|
|
840
|
+
const start = Math.max(0, startLine - 1);
|
|
841
|
+
const end = Math.min(lines.length, endLine + 2);
|
|
842
|
+
return lines.slice(start, end).join("\n");
|
|
843
|
+
}
|
|
844
|
+
function symbolKindToChunkKind(symbolKind, relativePath) {
|
|
845
|
+
const lower = (relativePath || "").toLowerCase();
|
|
846
|
+
if (lower.includes("/routes/") || lower.includes("/router/") || lower.includes("/urls/"))
|
|
847
|
+
return "route";
|
|
848
|
+
if (symbolKind === "class" || symbolKind === "interface") {
|
|
849
|
+
if (lower.includes("/controller") || lower.includes("/handler") || lower.includes("/view"))
|
|
850
|
+
return "controller_action";
|
|
851
|
+
if (lower.includes("/service") || lower.includes("/usecase") || lower.includes("/action") || lower.includes("/export"))
|
|
852
|
+
return "service_method";
|
|
853
|
+
if (lower.includes("/model") || lower.includes("/entity") || lower.includes("/repository"))
|
|
854
|
+
return "model_entity";
|
|
855
|
+
if (lower.includes("/middleware") || lower.includes("/guard") || lower.includes("/auth"))
|
|
856
|
+
return "middleware";
|
|
857
|
+
if (lower.includes("/job") || lower.includes("/event") || lower.includes("/listener"))
|
|
858
|
+
return "job";
|
|
859
|
+
return "class";
|
|
860
|
+
}
|
|
861
|
+
if (symbolKind === "method")
|
|
862
|
+
return "method";
|
|
863
|
+
if (symbolKind === "function")
|
|
864
|
+
return "function";
|
|
865
|
+
if (/(^|\/)(tests?|specs?|__tests__)(\/|$)/.test(lower))
|
|
866
|
+
return "test_case";
|
|
867
|
+
if (/(^|\/)(config|configs?|settings?)(\/|$)|\.(json|ya?ml|toml|ini|env)$/.test(lower))
|
|
868
|
+
return "config_block";
|
|
869
|
+
if (/(^|\/)(migrations?|schema|schemas)(\/|$)/.test(lower))
|
|
870
|
+
return "schema";
|
|
871
|
+
return "unknown";
|
|
872
|
+
}
|
|
873
|
+
function inferChunkSlot(relativePath, chunkKind) {
|
|
874
|
+
const lower = relativePath.toLowerCase().replace(/\\/g, "/");
|
|
875
|
+
if (/(^|\/)(routes?|router|urls)(\/|$)/.test(lower))
|
|
876
|
+
return "entry";
|
|
877
|
+
if (/(^|\/)(controllers?|handlers?)(\/|$)/.test(lower))
|
|
878
|
+
return "handler";
|
|
879
|
+
if (/(^|\/)(services?|usecases?|actions?|exports?|domain)(\/|$)/.test(lower))
|
|
880
|
+
return "domain";
|
|
881
|
+
if (/(^|\/)(models?|entities?|repositories?)(\/|$)/.test(lower))
|
|
882
|
+
return "data";
|
|
883
|
+
if (/(^|\/)(tests?|specs?|__tests__)(\/|$)/.test(lower))
|
|
884
|
+
return "tests";
|
|
885
|
+
if (/(^|\/)(config|configs?|settings?)(\/|$)/.test(lower))
|
|
886
|
+
return "config";
|
|
887
|
+
if (/(^|\/)(middleware|guards?|policies?|auth)(\/|$)/.test(lower))
|
|
888
|
+
return "permission";
|
|
889
|
+
if (/(^|\/)(jobs?|events?|listeners?|mail|notifications?)(\/|$)/.test(lower))
|
|
890
|
+
return "side_effect";
|
|
891
|
+
return "unknown";
|
|
776
892
|
}
|
|
777
|
-
function
|
|
893
|
+
function extractChunkTerms(symbolName, snippet) {
|
|
894
|
+
const words = new Set();
|
|
895
|
+
for (const w of symbolName.split(/(?=[A-Z])|_|-|\./)) {
|
|
896
|
+
const t = w.toLowerCase().trim();
|
|
897
|
+
if (t && t.length > 1)
|
|
898
|
+
words.add(t);
|
|
899
|
+
}
|
|
900
|
+
const textWords = snippet.toLowerCase().match(/\b[a-z_]{3,20}\b/g);
|
|
901
|
+
if (textWords)
|
|
902
|
+
for (const w of textWords)
|
|
903
|
+
words.add(w);
|
|
904
|
+
return Array.from(words).slice(0, 30);
|
|
905
|
+
}
|
|
906
|
+
function extractRouteHints(snippet, relativePath) {
|
|
907
|
+
const routes = [];
|
|
908
|
+
const lower = relativePath.toLowerCase();
|
|
909
|
+
if (lower.includes("route") || lower.includes("url")) {
|
|
910
|
+
const matches = snippet.match(/['"`]([\/][^'"`\s]{2,80})['"`]/g);
|
|
911
|
+
if (matches)
|
|
912
|
+
for (const m of matches)
|
|
913
|
+
routes.push(m.replace(/['"`]/g, ""));
|
|
914
|
+
}
|
|
915
|
+
return routes.slice(0, 5);
|
|
916
|
+
}
|
|
917
|
+
function extractChunkAnnotations(nodes, range) {
|
|
918
|
+
return nodes
|
|
919
|
+
.filter((n) => n.kind === "annotation" && n.range.startLine >= range.startLine - 2 && n.range.endLine <= range.startLine)
|
|
920
|
+
.map((n) => n.name ?? "")
|
|
921
|
+
.filter(Boolean)
|
|
922
|
+
.slice(0, 5);
|
|
923
|
+
}
|
|
924
|
+
function inferFileChunkKind(relativePath) {
|
|
778
925
|
const normalized = relativePath.replace(/\\/g, "/").toLowerCase();
|
|
779
|
-
if (/\.(md|mdx|txt|rst)$/.test(normalized))
|
|
926
|
+
if (/\.(md|mdx|txt|rst)$/.test(normalized))
|
|
780
927
|
return "doc";
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
}
|
|
785
|
-
return "file_region";
|
|
928
|
+
if (/(^|\/)(config|configs?|settings?)(\/|$)|\.(json|ya?ml|toml|ini|env)$/.test(normalized))
|
|
929
|
+
return "config_block";
|
|
930
|
+
return "unknown";
|
|
786
931
|
}
|
|
787
932
|
function buildIndexedEdges(relativePath, imports, symbols, calls) {
|
|
788
933
|
const edges = [];
|
|
@@ -865,6 +1010,171 @@ function formatProgressPath(relativePath) {
|
|
|
865
1010
|
function yieldToHost() {
|
|
866
1011
|
return new Promise((resolve) => setTimeout(resolve, 0));
|
|
867
1012
|
}
|
|
1013
|
+
function computeChunkStats(index) {
|
|
1014
|
+
const chunksByKind = {};
|
|
1015
|
+
const parserKindCounts = {};
|
|
1016
|
+
let totalChunks = 0;
|
|
1017
|
+
let filesWithChunks = 0;
|
|
1018
|
+
for (const file of index.files) {
|
|
1019
|
+
if (file.chunks && file.chunks.length > 0) {
|
|
1020
|
+
filesWithChunks++;
|
|
1021
|
+
totalChunks += file.chunks.length;
|
|
1022
|
+
for (const chunk of file.chunks) {
|
|
1023
|
+
const kind = chunk.kind || "unknown";
|
|
1024
|
+
chunksByKind[kind] = (chunksByKind[kind] || 0) + 1;
|
|
1025
|
+
const pk = chunk.parserKind || "unknown";
|
|
1026
|
+
parserKindCounts[pk] = (parserKindCounts[pk] || 0) + 1;
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
return { totalChunks, chunksByKind, filesWithChunks, parserKindCounts };
|
|
1031
|
+
}
|
|
1032
|
+
function scoreChunksForTask(filePaths, profile, workspaceRoot, options = {}) {
|
|
1033
|
+
const maxPerFile = options.maxChunksPerFile ?? 3;
|
|
1034
|
+
const maxTotal = options.maxTotalChunks ?? 30;
|
|
1035
|
+
// Load chunks from cache — match files by normalized path
|
|
1036
|
+
const normalizeFP = (p) => p.replace(/\\/g, "/");
|
|
1037
|
+
const normalizedTargets = filePaths.map(normalizeFP);
|
|
1038
|
+
let allChunks = [];
|
|
1039
|
+
try {
|
|
1040
|
+
const cacheFile = cachePath(workspaceRoot);
|
|
1041
|
+
if (fssync.existsSync(cacheFile)) {
|
|
1042
|
+
const raw = JSON.parse(fssync.readFileSync(cacheFile, "utf8"));
|
|
1043
|
+
allChunks = raw.files
|
|
1044
|
+
.filter((f) => {
|
|
1045
|
+
const nf = normalizeFP(f.relativePath);
|
|
1046
|
+
return normalizedTargets.some((fp) => nf === fp || nf.endsWith("/" + fp) || fp.endsWith("/" + nf));
|
|
1047
|
+
})
|
|
1048
|
+
.flatMap((f) => f.chunks || []);
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
catch {
|
|
1052
|
+
return [];
|
|
1053
|
+
}
|
|
1054
|
+
if (allChunks.length === 0)
|
|
1055
|
+
return [];
|
|
1056
|
+
const taskTerms = new Set(profile.keywords.map((k) => k.toLowerCase()));
|
|
1057
|
+
const taskConcepts = new Set(profile.concepts.map((c) => c.toLowerCase()));
|
|
1058
|
+
const requiredSlots = new Set(profile.blueprint.requiredSlots);
|
|
1059
|
+
const positiveTerms = [...taskTerms, ...taskConcepts];
|
|
1060
|
+
const scoreChunk = (chunk) => {
|
|
1061
|
+
let score = 0;
|
|
1062
|
+
const reasons = [];
|
|
1063
|
+
// Term matching against chunk body
|
|
1064
|
+
for (const term of taskTerms) {
|
|
1065
|
+
if (chunk.contentPreview.toLowerCase().includes(term)) {
|
|
1066
|
+
score += 3;
|
|
1067
|
+
if (reasons.length < 4)
|
|
1068
|
+
reasons.push(`matched task term: ${term}`);
|
|
1069
|
+
}
|
|
1070
|
+
for (const ct of chunk.terms) {
|
|
1071
|
+
if (ct.includes(term) || term.includes(ct)) {
|
|
1072
|
+
score += 2;
|
|
1073
|
+
break;
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
// Symbol name matching
|
|
1078
|
+
if (chunk.symbolName) {
|
|
1079
|
+
const sym = chunk.symbolName.toLowerCase();
|
|
1080
|
+
for (const term of positiveTerms) {
|
|
1081
|
+
if (sym.includes(term) || term.includes(sym)) {
|
|
1082
|
+
score += 4;
|
|
1083
|
+
if (reasons.length < 4)
|
|
1084
|
+
reasons.push(`matched symbol name: ${chunk.symbolName}`);
|
|
1085
|
+
break;
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
// Slot / context matching
|
|
1090
|
+
if (requiredSlots.has(chunk.slot)) {
|
|
1091
|
+
score += 2;
|
|
1092
|
+
if (reasons.length < 4)
|
|
1093
|
+
reasons.push(`matched context slot: ${chunk.slot}`);
|
|
1094
|
+
}
|
|
1095
|
+
// Route evidence
|
|
1096
|
+
if (chunk.routes.length > 0) {
|
|
1097
|
+
for (const route of chunk.routes) {
|
|
1098
|
+
for (const term of taskTerms) {
|
|
1099
|
+
if (route.toLowerCase().includes(term)) {
|
|
1100
|
+
score += 3;
|
|
1101
|
+
if (reasons.length < 4)
|
|
1102
|
+
reasons.push(`matched route evidence: ${route}`);
|
|
1103
|
+
break;
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
// Annotation matching
|
|
1109
|
+
for (const ann of chunk.annotations) {
|
|
1110
|
+
for (const term of positiveTerms) {
|
|
1111
|
+
if (ann.toLowerCase().includes(term)) {
|
|
1112
|
+
score += 1;
|
|
1113
|
+
break;
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
// Path evidence
|
|
1118
|
+
const pathLower = (chunk.filePath || chunk.relativePath || "").toLowerCase();
|
|
1119
|
+
for (const term of taskTerms) {
|
|
1120
|
+
if (pathLower.includes(term)) {
|
|
1121
|
+
score += 1;
|
|
1122
|
+
break;
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
// Import/call matching
|
|
1126
|
+
for (const imp of chunk.imports) {
|
|
1127
|
+
for (const term of positiveTerms) {
|
|
1128
|
+
if (imp.toLowerCase().includes(term)) {
|
|
1129
|
+
score += 1;
|
|
1130
|
+
break;
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
for (const call of chunk.calls) {
|
|
1135
|
+
for (const term of positiveTerms) {
|
|
1136
|
+
if (call.toLowerCase().includes(term)) {
|
|
1137
|
+
score += 1;
|
|
1138
|
+
break;
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
if (reasons.length === 0 && score > 0)
|
|
1143
|
+
reasons.push("weak term match");
|
|
1144
|
+
return { score, reasons };
|
|
1145
|
+
};
|
|
1146
|
+
const matches = [];
|
|
1147
|
+
const seenFiles = new Set();
|
|
1148
|
+
for (const fp of filePaths) {
|
|
1149
|
+
if (seenFiles.has(fp))
|
|
1150
|
+
continue;
|
|
1151
|
+
seenFiles.add(fp);
|
|
1152
|
+
const fileChunks = allChunks.filter((c) => {
|
|
1153
|
+
const cp = c.filePath || c.relativePath || "";
|
|
1154
|
+
return cp === fp || fp.endsWith(cp) || cp.endsWith(fp);
|
|
1155
|
+
});
|
|
1156
|
+
if (fileChunks.length === 0)
|
|
1157
|
+
continue;
|
|
1158
|
+
const scored = fileChunks.map((c) => ({ chunk: c, ...scoreChunk(c) }));
|
|
1159
|
+
scored.sort((a, b) => b.score - a.score);
|
|
1160
|
+
const top = scored.slice(0, maxPerFile).filter((s) => s.score > 0);
|
|
1161
|
+
for (const s of top) {
|
|
1162
|
+
matches.push({
|
|
1163
|
+
filePath: s.chunk.filePath || s.chunk.relativePath || fp,
|
|
1164
|
+
startLine: s.chunk.startLine ?? s.chunk.range?.startLine ?? 1,
|
|
1165
|
+
endLine: s.chunk.endLine ?? s.chunk.range?.endLine ?? 1,
|
|
1166
|
+
kind: s.chunk.kind,
|
|
1167
|
+
slot: s.chunk.slot,
|
|
1168
|
+
symbolName: s.chunk.symbolName || s.chunk.symbolFqn || null,
|
|
1169
|
+
score: s.score,
|
|
1170
|
+
reasons: s.reasons,
|
|
1171
|
+
contentPreview: s.chunk.contentPreview.slice(0, 200),
|
|
1172
|
+
});
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
matches.sort((a, b) => b.score - a.score);
|
|
1176
|
+
return matches.slice(0, maxTotal);
|
|
1177
|
+
}
|
|
868
1178
|
async function readIndex(rootPath) {
|
|
869
1179
|
const filePath = cachePath(rootPath);
|
|
870
1180
|
try {
|