@chiway/contextweaver 1.4.0 → 1.5.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/README.md +138 -28
- package/dist/{SearchService-OS7CYHNJ.js → SearchService-WVD6THR3.js} +116 -74
- package/dist/{chunk-ZOMGPIU6.js → chunk-3BNHQV5W.js} +1 -5
- package/dist/chunk-BFCIZ52F.js +102 -0
- package/dist/{chunk-X7PAYQMT.js → chunk-GDVB6PJ4.js} +21 -3
- package/dist/{lock-FL54LIQL.js → chunk-HHYPQA3X.js} +1 -1
- package/dist/chunk-ISVCQFB4.js +223 -0
- package/dist/chunk-IZ6IUHNN.js +77 -0
- package/dist/chunk-LB42CZEB.js +18 -0
- package/dist/{chunk-RGJSXUFS.js → chunk-PPLFJGO3.js} +60 -0
- package/dist/chunk-R6CNZXZ7.js +143 -0
- package/dist/chunk-TPM6YP43.js +38 -0
- package/dist/{chunk-EMSMLPMK.js → chunk-V3K4YVAR.js} +10 -117
- package/dist/chunk-VWBKZ6QL.js +115 -0
- package/dist/chunk-XFIM2T6S.js +57 -0
- package/dist/{chunk-AB24E3Z7.js → chunk-XMZZZKG7.js} +23 -79
- package/dist/chunk-XTWNT7KP.js +156 -0
- package/dist/chunk-Y6H7C3NA.js +85 -0
- package/dist/{codebaseRetrieval-3Z4CRA7X.js → codebaseRetrieval-DIS5RH2C.js} +5 -2
- package/dist/{db-PMVM7557.js → db-GBCLP4GG.js} +15 -1
- package/dist/findReferences-N7ML7TUP.js +16 -0
- package/dist/getSymbolDefinition-6KMY4H33.js +17 -0
- package/dist/index.js +244 -41
- package/dist/listFiles-4VT2TPJD.js +14 -0
- package/dist/loadConfig-XTVT2OWW.js +9 -0
- package/dist/lock-HNKQ6X5B.js +8 -0
- package/dist/scanner-QDFZJLP7.js +13 -0
- package/dist/server-UAI3U7AB.js +347 -0
- package/dist/stats-AGKUCJQI.js +12 -0
- package/dist/{vectorStore-HPQZOVWF.js → vectorStore-4ODCERRO.js} +1 -1
- package/package.json +9 -23
- package/dist/scanner-2XGJWYHR.js +0 -11
- package/dist/server-XK6EINRV.js +0 -146
|
@@ -2,10 +2,10 @@ import {
|
|
|
2
2
|
closeAllIndexers,
|
|
3
3
|
getIndexer,
|
|
4
4
|
invalidateAllExpanderCaches
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-XMZZZKG7.js";
|
|
6
6
|
import {
|
|
7
7
|
closeAllVectorStores
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-3BNHQV5W.js";
|
|
9
9
|
import {
|
|
10
10
|
batchDelete,
|
|
11
11
|
batchUpdateMtime,
|
|
@@ -17,9 +17,12 @@ import {
|
|
|
17
17
|
getAllPaths,
|
|
18
18
|
getFilesNeedingVectorIndex,
|
|
19
19
|
getStoredEmbeddingDimensions,
|
|
20
|
+
incrementIndexVersion,
|
|
21
|
+
incrementStat,
|
|
20
22
|
initDb,
|
|
23
|
+
setStatJson,
|
|
21
24
|
setStoredEmbeddingDimensions
|
|
22
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-PPLFJGO3.js";
|
|
23
26
|
import {
|
|
24
27
|
logger
|
|
25
28
|
} from "./chunk-JVKVSTQ3.js";
|
|
@@ -1382,6 +1385,19 @@ async function scan(rootPath, options = {}) {
|
|
|
1382
1385
|
logger.warn({ error: error.message }, "GC \u8DF3\u8FC7");
|
|
1383
1386
|
}
|
|
1384
1387
|
}
|
|
1388
|
+
const didWork = stats.added + stats.modified + stats.deleted > 0;
|
|
1389
|
+
if (didWork) {
|
|
1390
|
+
incrementIndexVersion(db);
|
|
1391
|
+
}
|
|
1392
|
+
try {
|
|
1393
|
+
if (didWork) {
|
|
1394
|
+
incrementStat(db, "stats.index.total_runs");
|
|
1395
|
+
}
|
|
1396
|
+
setStatJson(db, "stats.index.last_run_json", stats);
|
|
1397
|
+
setStatJson(db, "stats.index.last_run_at", Date.now());
|
|
1398
|
+
} catch (err) {
|
|
1399
|
+
logger.warn({ error: err.message }, "\u7D22\u5F15\u7EDF\u8BA1\u57CB\u70B9\u5931\u8D25");
|
|
1400
|
+
}
|
|
1385
1401
|
invalidateAllExpanderCaches();
|
|
1386
1402
|
return stats;
|
|
1387
1403
|
} finally {
|
|
@@ -1392,5 +1408,7 @@ async function scan(rootPath, options = {}) {
|
|
|
1392
1408
|
}
|
|
1393
1409
|
|
|
1394
1410
|
export {
|
|
1411
|
+
initFilter,
|
|
1412
|
+
isFiltered,
|
|
1395
1413
|
scan
|
|
1396
1414
|
};
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
logger
|
|
3
3
|
} from "./chunk-JVKVSTQ3.js";
|
|
4
|
-
import "./chunk-SKBAE26T.js";
|
|
5
4
|
|
|
6
5
|
// src/utils/lock.ts
|
|
7
6
|
import fs from "fs";
|
|
@@ -122,6 +121,7 @@ async function withLock(projectId, operation, fn, timeoutMs = 3e4) {
|
|
|
122
121
|
releaseLock(projectId);
|
|
123
122
|
}
|
|
124
123
|
}
|
|
124
|
+
|
|
125
125
|
export {
|
|
126
126
|
withLock
|
|
127
127
|
};
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import {
|
|
2
|
+
commonPrefixLength
|
|
3
|
+
} from "./chunk-LB42CZEB.js";
|
|
4
|
+
import {
|
|
5
|
+
ChunkContentLoader
|
|
6
|
+
} from "./chunk-XFIM2T6S.js";
|
|
7
|
+
import {
|
|
8
|
+
getVectorStore
|
|
9
|
+
} from "./chunk-3BNHQV5W.js";
|
|
10
|
+
import {
|
|
11
|
+
ensureIndexed,
|
|
12
|
+
formatTextResponse
|
|
13
|
+
} from "./chunk-VWBKZ6QL.js";
|
|
14
|
+
import {
|
|
15
|
+
generateProjectId,
|
|
16
|
+
initDb,
|
|
17
|
+
searchChunksFts
|
|
18
|
+
} from "./chunk-PPLFJGO3.js";
|
|
19
|
+
import {
|
|
20
|
+
logger
|
|
21
|
+
} from "./chunk-JVKVSTQ3.js";
|
|
22
|
+
|
|
23
|
+
// src/mcp/tools/getSymbolDefinition.ts
|
|
24
|
+
import { z } from "zod";
|
|
25
|
+
var getSymbolDefinitionSchema = z.object({
|
|
26
|
+
repo_path: z.string().describe(
|
|
27
|
+
"The absolute file system path to the repository root. (e.g., '/Users/dev/my-project')"
|
|
28
|
+
),
|
|
29
|
+
symbol: z.string().min(1).describe("The exact symbol name to resolve."),
|
|
30
|
+
hint_path: z.string().optional().describe("Optional preferred path used to disambiguate same-name definitions."),
|
|
31
|
+
max_results: z.number().int().positive().max(20).optional().describe("Maximum number of definitions to return. Defaults to 3.")
|
|
32
|
+
});
|
|
33
|
+
var LANGUAGE_DEFINITION_PATTERNS = {
|
|
34
|
+
typescript: [
|
|
35
|
+
"function\\s+{symbol}\\b",
|
|
36
|
+
"class\\s+{symbol}\\b",
|
|
37
|
+
"(?:const|let|var)\\s+{symbol}\\b",
|
|
38
|
+
"interface\\s+{symbol}\\b",
|
|
39
|
+
"type\\s+{symbol}\\b",
|
|
40
|
+
"enum\\s+{symbol}\\b"
|
|
41
|
+
],
|
|
42
|
+
javascript: [
|
|
43
|
+
"function\\s+{symbol}\\b",
|
|
44
|
+
"class\\s+{symbol}\\b",
|
|
45
|
+
"(?:const|let|var)\\s+{symbol}\\b"
|
|
46
|
+
],
|
|
47
|
+
python: ["def\\s+{symbol}\\b", "class\\s+{symbol}\\b"],
|
|
48
|
+
go: ["func\\s+{symbol}\\b", "type\\s+{symbol}\\b", "const\\s+{symbol}\\b", "var\\s+{symbol}\\b"],
|
|
49
|
+
rust: [
|
|
50
|
+
"fn\\s+{symbol}\\b",
|
|
51
|
+
"struct\\s+{symbol}\\b",
|
|
52
|
+
"enum\\s+{symbol}\\b",
|
|
53
|
+
"const\\s+{symbol}\\b"
|
|
54
|
+
],
|
|
55
|
+
java: [
|
|
56
|
+
"class\\s+{symbol}\\b",
|
|
57
|
+
"interface\\s+{symbol}\\b",
|
|
58
|
+
"enum\\s+{symbol}\\b",
|
|
59
|
+
"\\b{symbol}\\s*\\("
|
|
60
|
+
],
|
|
61
|
+
csharp: [
|
|
62
|
+
"class\\s+{symbol}\\b",
|
|
63
|
+
"interface\\s+{symbol}\\b",
|
|
64
|
+
"enum\\s+{symbol}\\b",
|
|
65
|
+
"\\b{symbol}\\s*\\("
|
|
66
|
+
],
|
|
67
|
+
cpp: ["class\\s+{symbol}\\b", "struct\\s+{symbol}\\b", "\\b{symbol}\\s*\\("],
|
|
68
|
+
c: ["\\b{symbol}\\s*\\("]
|
|
69
|
+
};
|
|
70
|
+
function escapeRegex(text) {
|
|
71
|
+
return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
72
|
+
}
|
|
73
|
+
function breadcrumbTail(breadcrumb) {
|
|
74
|
+
return breadcrumb.split(">").pop()?.trim() ?? "";
|
|
75
|
+
}
|
|
76
|
+
function countLinesBefore(content, index) {
|
|
77
|
+
let line = 1;
|
|
78
|
+
for (let i = 0; i < index && i < content.length; i++) {
|
|
79
|
+
if (content[i] === "\n") {
|
|
80
|
+
line += 1;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return line;
|
|
84
|
+
}
|
|
85
|
+
function computeEndLine(startLine, code) {
|
|
86
|
+
const normalized = code.replace(/\n+$/u, "");
|
|
87
|
+
if (!normalized) {
|
|
88
|
+
return startLine;
|
|
89
|
+
}
|
|
90
|
+
return startLine + normalized.split("\n").length - 1;
|
|
91
|
+
}
|
|
92
|
+
function detectLanguage(filePath) {
|
|
93
|
+
const ext = filePath.split(".").pop()?.toLowerCase() || "";
|
|
94
|
+
const langMap = {
|
|
95
|
+
ts: "typescript",
|
|
96
|
+
tsx: "typescript",
|
|
97
|
+
js: "javascript",
|
|
98
|
+
jsx: "javascript",
|
|
99
|
+
py: "python",
|
|
100
|
+
rs: "rust",
|
|
101
|
+
go: "go",
|
|
102
|
+
java: "java",
|
|
103
|
+
c: "c",
|
|
104
|
+
cpp: "cpp",
|
|
105
|
+
h: "c",
|
|
106
|
+
hpp: "cpp",
|
|
107
|
+
cs: "csharp",
|
|
108
|
+
md: "markdown",
|
|
109
|
+
json: "json"
|
|
110
|
+
};
|
|
111
|
+
return langMap[ext] || ext || "plaintext";
|
|
112
|
+
}
|
|
113
|
+
function formatDefinition(candidate) {
|
|
114
|
+
const header = `## ${candidate.chunk.file_path} (L${candidate.startLine}-L${candidate.endLine})`;
|
|
115
|
+
const breadcrumb = candidate.chunk.breadcrumb ? `> ${candidate.chunk.breadcrumb}` : "";
|
|
116
|
+
const code = `\`\`\`${detectLanguage(candidate.chunk.file_path)}
|
|
117
|
+
${candidate.code}
|
|
118
|
+
\`\`\``;
|
|
119
|
+
return [header, breadcrumb, code].filter(Boolean).join("\n");
|
|
120
|
+
}
|
|
121
|
+
function hasDefinitionPattern(language, code, symbol) {
|
|
122
|
+
const patterns = LANGUAGE_DEFINITION_PATTERNS[language] ?? LANGUAGE_DEFINITION_PATTERNS.typescript;
|
|
123
|
+
return patterns.some((pattern) => {
|
|
124
|
+
const source = pattern.replaceAll("{symbol}", escapeRegex(symbol));
|
|
125
|
+
return new RegExp(source, "u").test(code);
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
function rankCandidates(a, b) {
|
|
129
|
+
if (a.breadcrumbExact !== b.breadcrumbExact) {
|
|
130
|
+
return a.breadcrumbExact ? -1 : 1;
|
|
131
|
+
}
|
|
132
|
+
if (a.prefixScore !== b.prefixScore) {
|
|
133
|
+
return b.prefixScore - a.prefixScore;
|
|
134
|
+
}
|
|
135
|
+
if (a.score !== b.score) {
|
|
136
|
+
return b.score - a.score;
|
|
137
|
+
}
|
|
138
|
+
return a.chunk.file_path.localeCompare(b.chunk.file_path);
|
|
139
|
+
}
|
|
140
|
+
async function handleGetSymbolDefinition(args, onProgress) {
|
|
141
|
+
const { repo_path, symbol, hint_path, max_results = 3 } = args;
|
|
142
|
+
const projectId = generateProjectId(repo_path);
|
|
143
|
+
logger.info({ repo_path, symbol, hint_path, max_results }, "MCP get-symbol-definition \u8C03\u7528\u5F00\u59CB");
|
|
144
|
+
await ensureIndexed(repo_path, projectId, { onProgress });
|
|
145
|
+
const db = initDb(projectId);
|
|
146
|
+
try {
|
|
147
|
+
const hits = searchChunksFts(db, symbol, Math.max(max_results * 5, 20));
|
|
148
|
+
const uniquePaths = Array.from(new Set(hits.map((hit) => hit.filePath)));
|
|
149
|
+
const vectorStore = await getVectorStore(projectId);
|
|
150
|
+
const chunkMap = await vectorStore.getFilesChunks(uniquePaths);
|
|
151
|
+
const chunkByKey = /* @__PURE__ */ new Map();
|
|
152
|
+
for (const [filePath, chunks] of chunkMap) {
|
|
153
|
+
for (const chunk of chunks) {
|
|
154
|
+
chunkByKey.set(`${filePath}#${chunk.chunk_index}`, chunk);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
const slices = Array.from(chunkByKey.values()).map((chunk) => ({
|
|
158
|
+
filePath: chunk.file_path,
|
|
159
|
+
start_index: chunk.start_index,
|
|
160
|
+
end_index: chunk.end_index
|
|
161
|
+
}));
|
|
162
|
+
const loader = new ChunkContentLoader(db);
|
|
163
|
+
const codeMap = loader.loadMany(slices);
|
|
164
|
+
const fileContentStmt = db.prepare("SELECT content FROM files WHERE path = ?");
|
|
165
|
+
const fullFileCache = /* @__PURE__ */ new Map();
|
|
166
|
+
const candidates = /* @__PURE__ */ new Map();
|
|
167
|
+
for (const hit of hits) {
|
|
168
|
+
const chunk = chunkByKey.get(`${hit.filePath}#${hit.chunkIndex}`);
|
|
169
|
+
if (!chunk) {
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
const codeKey = ChunkContentLoader.key({
|
|
173
|
+
filePath: chunk.file_path,
|
|
174
|
+
start_index: chunk.start_index,
|
|
175
|
+
end_index: chunk.end_index
|
|
176
|
+
});
|
|
177
|
+
const code = codeMap.get(codeKey) ?? "";
|
|
178
|
+
if (!code) {
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
const breadcrumbExact = breadcrumbTail(chunk.breadcrumb) === symbol;
|
|
182
|
+
const definitionPattern = hasDefinitionPattern(chunk.language, code, symbol);
|
|
183
|
+
if (!breadcrumbExact && !definitionPattern) {
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
let fullContent = fullFileCache.get(chunk.file_path);
|
|
187
|
+
if (fullContent === void 0) {
|
|
188
|
+
const row = fileContentStmt.get(chunk.file_path);
|
|
189
|
+
fullContent = row?.content ?? "";
|
|
190
|
+
fullFileCache.set(chunk.file_path, fullContent);
|
|
191
|
+
}
|
|
192
|
+
const startLine = countLinesBefore(fullContent, chunk.start_index);
|
|
193
|
+
const candidate = {
|
|
194
|
+
chunk,
|
|
195
|
+
code,
|
|
196
|
+
score: hit.score,
|
|
197
|
+
breadcrumbExact,
|
|
198
|
+
prefixScore: hint_path ? commonPrefixLength(hint_path, chunk.file_path) : 0,
|
|
199
|
+
startLine,
|
|
200
|
+
endLine: computeEndLine(startLine, code)
|
|
201
|
+
};
|
|
202
|
+
const key = `${chunk.file_path}#${chunk.chunk_index}`;
|
|
203
|
+
const existing = candidates.get(key);
|
|
204
|
+
if (!existing || rankCandidates(candidate, existing) < 0) {
|
|
205
|
+
candidates.set(key, candidate);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
const ranked = Array.from(candidates.values()).sort(rankCandidates).slice(0, max_results);
|
|
209
|
+
const body = ranked.length > 0 ? ranked.map((candidate) => formatDefinition(candidate)).join("\n\n---\n\n") : "No likely symbol definitions found.";
|
|
210
|
+
return formatTextResponse(
|
|
211
|
+
`Found ${ranked.length} symbol definitions for "${symbol}"
|
|
212
|
+
|
|
213
|
+
${body}`
|
|
214
|
+
);
|
|
215
|
+
} finally {
|
|
216
|
+
db.close();
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export {
|
|
221
|
+
getSymbolDefinitionSchema,
|
|
222
|
+
handleGetSymbolDefinition
|
|
223
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_CONFIG,
|
|
3
|
+
SEARCH_CONFIG_BOUNDS
|
|
4
|
+
} from "./chunk-BFCIZ52F.js";
|
|
5
|
+
|
|
6
|
+
// src/search/loadConfig.ts
|
|
7
|
+
import crypto from "crypto";
|
|
8
|
+
var SEARCH_ENV_MAP = {
|
|
9
|
+
CW_SEARCH_WVEC: "wVec",
|
|
10
|
+
CW_SEARCH_WLEX: "wLex",
|
|
11
|
+
CW_SEARCH_RERANK_TOP_N: "rerankTopN",
|
|
12
|
+
CW_SEARCH_MAX_TOTAL_CHARS: "maxTotalChars",
|
|
13
|
+
CW_SEARCH_VECTOR_TOP_K: "vectorTopK",
|
|
14
|
+
CW_SEARCH_SMART_MAX_K: "smartMaxK",
|
|
15
|
+
CW_SEARCH_IMPORT_FILES_PER_SEED: "importFilesPerSeed"
|
|
16
|
+
};
|
|
17
|
+
var SEARCH_FINGERPRINT_FIELDS = Object.keys(DEFAULT_CONFIG);
|
|
18
|
+
function normalizeWeight(value) {
|
|
19
|
+
return Math.round(value * 1e6) / 1e6;
|
|
20
|
+
}
|
|
21
|
+
function clampValue(key, rawValue) {
|
|
22
|
+
const parsed = Number(rawValue);
|
|
23
|
+
if (!Number.isFinite(parsed)) {
|
|
24
|
+
return void 0;
|
|
25
|
+
}
|
|
26
|
+
const bounds = SEARCH_CONFIG_BOUNDS[key];
|
|
27
|
+
if (!bounds) {
|
|
28
|
+
return parsed;
|
|
29
|
+
}
|
|
30
|
+
const clamped = Math.min(bounds.max, Math.max(bounds.min, parsed));
|
|
31
|
+
return bounds.integer ? Math.round(clamped) : clamped;
|
|
32
|
+
}
|
|
33
|
+
function getSearchConfigOverrides() {
|
|
34
|
+
const overrides = {};
|
|
35
|
+
const explicitWeights = {};
|
|
36
|
+
for (const [envKey, configKey] of Object.entries(SEARCH_ENV_MAP)) {
|
|
37
|
+
const rawValue = process.env[envKey];
|
|
38
|
+
if (rawValue === void 0 || rawValue.trim() === "") {
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
const parsed = clampValue(configKey, rawValue);
|
|
42
|
+
if (parsed === void 0) {
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (configKey === "wVec" || configKey === "wLex") {
|
|
46
|
+
explicitWeights[configKey] = parsed;
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
overrides[configKey] = parsed;
|
|
50
|
+
}
|
|
51
|
+
if (explicitWeights.wVec !== void 0 && explicitWeights.wLex === void 0) {
|
|
52
|
+
overrides.wVec = explicitWeights.wVec;
|
|
53
|
+
overrides.wLex = normalizeWeight(1 - explicitWeights.wVec);
|
|
54
|
+
} else if (explicitWeights.wLex !== void 0 && explicitWeights.wVec === void 0) {
|
|
55
|
+
overrides.wLex = explicitWeights.wLex;
|
|
56
|
+
overrides.wVec = normalizeWeight(1 - explicitWeights.wLex);
|
|
57
|
+
} else {
|
|
58
|
+
if (explicitWeights.wVec !== void 0) {
|
|
59
|
+
overrides.wVec = explicitWeights.wVec;
|
|
60
|
+
}
|
|
61
|
+
if (explicitWeights.wLex !== void 0) {
|
|
62
|
+
overrides.wLex = explicitWeights.wLex;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return overrides;
|
|
66
|
+
}
|
|
67
|
+
function createSearchConfigFingerprint(config) {
|
|
68
|
+
const payload = Object.fromEntries(
|
|
69
|
+
SEARCH_FINGERPRINT_FIELDS.map((field) => [field, config[field]])
|
|
70
|
+
);
|
|
71
|
+
return crypto.createHash("sha256").update(JSON.stringify(payload)).digest("hex");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export {
|
|
75
|
+
getSearchConfigOverrides,
|
|
76
|
+
createSearchConfigFingerprint
|
|
77
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// src/search/resolvers/types.ts
|
|
2
|
+
function commonPrefixLength(path1, path2) {
|
|
3
|
+
const parts1 = path1.split("/");
|
|
4
|
+
const parts2 = path2.split("/");
|
|
5
|
+
let count = 0;
|
|
6
|
+
for (let i = 0; i < Math.min(parts1.length, parts2.length); i++) {
|
|
7
|
+
if (parts1[i] === parts2[i]) {
|
|
8
|
+
count++;
|
|
9
|
+
} else {
|
|
10
|
+
break;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
return count;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export {
|
|
17
|
+
commonPrefixLength
|
|
18
|
+
};
|
|
@@ -640,6 +640,7 @@ function clear(db) {
|
|
|
640
640
|
db.exec("DELETE FROM chunks_fts");
|
|
641
641
|
}
|
|
642
642
|
var METADATA_KEY_EMBEDDING_DIMENSIONS = "embedding_dimensions";
|
|
643
|
+
var METADATA_KEY_INDEX_VERSION = "index_version";
|
|
643
644
|
var METADATA_KEY_LANCEDB_MIGRATION_STATE = "lancedb_migration_displaycode_state";
|
|
644
645
|
var METADATA_KEY_LANCEDB_MIGRATION_LOCK = "lancedb_migration_lock";
|
|
645
646
|
var MIGRATION_LOCK_STALE_MS = 10 * 60 * 1e3;
|
|
@@ -663,6 +664,58 @@ function getStoredEmbeddingDimensions(db) {
|
|
|
663
664
|
function setStoredEmbeddingDimensions(db, dimensions) {
|
|
664
665
|
setMetadata(db, METADATA_KEY_EMBEDDING_DIMENSIONS, String(dimensions));
|
|
665
666
|
}
|
|
667
|
+
function incrementStat(db, key, by = 1) {
|
|
668
|
+
const delta = Math.trunc(by);
|
|
669
|
+
db.prepare(`
|
|
670
|
+
INSERT INTO metadata (key, value)
|
|
671
|
+
VALUES (?, ?)
|
|
672
|
+
ON CONFLICT(key) DO UPDATE SET value = CAST(CAST(value AS INTEGER) + CAST(? AS INTEGER) AS TEXT)
|
|
673
|
+
`).run(key, String(delta), delta);
|
|
674
|
+
}
|
|
675
|
+
function setStatJson(db, key, value) {
|
|
676
|
+
setMetadata(db, key, JSON.stringify(value));
|
|
677
|
+
}
|
|
678
|
+
function getStatJson(db, key) {
|
|
679
|
+
const raw = getMetadata(db, key);
|
|
680
|
+
if (raw === null) return null;
|
|
681
|
+
try {
|
|
682
|
+
return JSON.parse(raw);
|
|
683
|
+
} catch {
|
|
684
|
+
return null;
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
function getAllStats(db) {
|
|
688
|
+
const rows = db.prepare(`SELECT key, value FROM metadata WHERE key LIKE 'stats.%'`).all();
|
|
689
|
+
const out = {};
|
|
690
|
+
for (const row of rows) out[row.key] = row.value;
|
|
691
|
+
return out;
|
|
692
|
+
}
|
|
693
|
+
function collectHealthSnapshot(db) {
|
|
694
|
+
const agg = db.prepare("SELECT COUNT(*) as c, COALESCE(SUM(size), 0) as bytes FROM files").get();
|
|
695
|
+
const langRows = db.prepare("SELECT language, COUNT(*) as c FROM files GROUP BY language").all();
|
|
696
|
+
const byLanguage = {};
|
|
697
|
+
for (const row of langRows) byLanguage[row.language] = row.c;
|
|
698
|
+
return {
|
|
699
|
+
totalFiles: agg.c,
|
|
700
|
+
totalBytes: agg.bytes,
|
|
701
|
+
byLanguage,
|
|
702
|
+
pendingMarks: countPendingMarks(db),
|
|
703
|
+
migrationState: getLanceDbMigrationState(db),
|
|
704
|
+
embeddingDimensions: getStoredEmbeddingDimensions(db),
|
|
705
|
+
indexVersion: getIndexVersion(db)
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
function getIndexVersion(db) {
|
|
709
|
+
const value = getMetadata(db, METADATA_KEY_INDEX_VERSION);
|
|
710
|
+
if (value === null) return 0;
|
|
711
|
+
const parsed = parseInt(value, 10);
|
|
712
|
+
return Number.isNaN(parsed) ? 0 : parsed;
|
|
713
|
+
}
|
|
714
|
+
function incrementIndexVersion(db) {
|
|
715
|
+
const next = getIndexVersion(db) + 1;
|
|
716
|
+
setMetadata(db, METADATA_KEY_INDEX_VERSION, String(next));
|
|
717
|
+
return next;
|
|
718
|
+
}
|
|
666
719
|
function getLanceDbMigrationState(db) {
|
|
667
720
|
const value = getMetadata(db, METADATA_KEY_LANCEDB_MIGRATION_STATE);
|
|
668
721
|
if (value === "pending" || value === "done" || value === "aborted") return value;
|
|
@@ -732,6 +785,13 @@ export {
|
|
|
732
785
|
clear,
|
|
733
786
|
getStoredEmbeddingDimensions,
|
|
734
787
|
setStoredEmbeddingDimensions,
|
|
788
|
+
incrementStat,
|
|
789
|
+
setStatJson,
|
|
790
|
+
getStatJson,
|
|
791
|
+
getAllStats,
|
|
792
|
+
collectHealthSnapshot,
|
|
793
|
+
getIndexVersion,
|
|
794
|
+
incrementIndexVersion,
|
|
735
795
|
getLanceDbMigrationState,
|
|
736
796
|
setLanceDbMigrationState,
|
|
737
797
|
clearAllVectorIndexHash,
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ChunkContentLoader
|
|
3
|
+
} from "./chunk-XFIM2T6S.js";
|
|
4
|
+
import {
|
|
5
|
+
getVectorStore
|
|
6
|
+
} from "./chunk-3BNHQV5W.js";
|
|
7
|
+
import {
|
|
8
|
+
ensureIndexed,
|
|
9
|
+
formatTextResponse
|
|
10
|
+
} from "./chunk-VWBKZ6QL.js";
|
|
11
|
+
import {
|
|
12
|
+
generateProjectId,
|
|
13
|
+
initDb,
|
|
14
|
+
searchChunksFts
|
|
15
|
+
} from "./chunk-PPLFJGO3.js";
|
|
16
|
+
import {
|
|
17
|
+
logger
|
|
18
|
+
} from "./chunk-JVKVSTQ3.js";
|
|
19
|
+
|
|
20
|
+
// src/mcp/tools/findReferences.ts
|
|
21
|
+
import { z } from "zod";
|
|
22
|
+
var findReferencesSchema = z.object({
|
|
23
|
+
repo_path: z.string().describe(
|
|
24
|
+
"The absolute file system path to the repository root. (e.g., '/Users/dev/my-project')"
|
|
25
|
+
),
|
|
26
|
+
symbol: z.string().min(1).describe("The exact symbol name to search for."),
|
|
27
|
+
exclude_definition: z.boolean().optional().describe("Exclude chunks whose breadcrumb tail matches the symbol name."),
|
|
28
|
+
max_results: z.number().int().positive().max(200).optional().describe("Maximum number of references to return. Defaults to 50.")
|
|
29
|
+
});
|
|
30
|
+
function escapeRegex(text) {
|
|
31
|
+
return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
32
|
+
}
|
|
33
|
+
function buildSymbolPattern(symbol) {
|
|
34
|
+
return new RegExp(`(?<![\\w$])${escapeRegex(symbol)}(?![\\w$])`, "u");
|
|
35
|
+
}
|
|
36
|
+
function breadcrumbTail(breadcrumb) {
|
|
37
|
+
const tail = breadcrumb.split(">").pop();
|
|
38
|
+
return tail?.trim() ?? "";
|
|
39
|
+
}
|
|
40
|
+
function countLinesBefore(content, index) {
|
|
41
|
+
let line = 1;
|
|
42
|
+
for (let i = 0; i < index && i < content.length; i++) {
|
|
43
|
+
if (content[i] === "\n") {
|
|
44
|
+
line += 1;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return line;
|
|
48
|
+
}
|
|
49
|
+
function locateMatches(code, pattern, baseLine) {
|
|
50
|
+
return code.split("\n").flatMap(
|
|
51
|
+
(text, offset) => pattern.test(text) ? [
|
|
52
|
+
{
|
|
53
|
+
line: baseLine + offset,
|
|
54
|
+
snippet: text.trim()
|
|
55
|
+
}
|
|
56
|
+
] : []
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
async function handleFindReferences(args, onProgress) {
|
|
60
|
+
const { repo_path, symbol, exclude_definition = false, max_results = 50 } = args;
|
|
61
|
+
const projectId = generateProjectId(repo_path);
|
|
62
|
+
logger.info(
|
|
63
|
+
{ repo_path, symbol, exclude_definition, max_results },
|
|
64
|
+
"MCP find-references \u8C03\u7528\u5F00\u59CB"
|
|
65
|
+
);
|
|
66
|
+
await ensureIndexed(repo_path, projectId, { onProgress });
|
|
67
|
+
const db = initDb(projectId);
|
|
68
|
+
try {
|
|
69
|
+
const hits = searchChunksFts(db, symbol, Math.max(max_results * 2, 20));
|
|
70
|
+
const uniquePaths = Array.from(new Set(hits.map((hit) => hit.filePath)));
|
|
71
|
+
const vectorStore = await getVectorStore(projectId);
|
|
72
|
+
const chunkMap = await vectorStore.getFilesChunks(uniquePaths);
|
|
73
|
+
const chunkByKey = /* @__PURE__ */ new Map();
|
|
74
|
+
for (const [filePath, chunks] of chunkMap) {
|
|
75
|
+
for (const chunk of chunks) {
|
|
76
|
+
chunkByKey.set(`${filePath}#${chunk.chunk_index}`, chunk);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
const slices = Array.from(chunkByKey.values()).map((chunk) => ({
|
|
80
|
+
filePath: chunk.file_path,
|
|
81
|
+
start_index: chunk.start_index,
|
|
82
|
+
end_index: chunk.end_index
|
|
83
|
+
}));
|
|
84
|
+
const loader = new ChunkContentLoader(db);
|
|
85
|
+
const codeMap = loader.loadMany(slices);
|
|
86
|
+
const fileContentStmt = db.prepare("SELECT content FROM files WHERE path = ?");
|
|
87
|
+
const fullFileCache = /* @__PURE__ */ new Map();
|
|
88
|
+
const pattern = buildSymbolPattern(symbol);
|
|
89
|
+
const matches = [];
|
|
90
|
+
for (const hit of hits) {
|
|
91
|
+
if (matches.length >= max_results) {
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
const chunk = chunkByKey.get(`${hit.filePath}#${hit.chunkIndex}`);
|
|
95
|
+
if (!chunk) {
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (exclude_definition && breadcrumbTail(chunk.breadcrumb) === symbol) {
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
const sliceKey = ChunkContentLoader.key({
|
|
102
|
+
filePath: chunk.file_path,
|
|
103
|
+
start_index: chunk.start_index,
|
|
104
|
+
end_index: chunk.end_index
|
|
105
|
+
});
|
|
106
|
+
const code = codeMap.get(sliceKey) ?? "";
|
|
107
|
+
if (!code) {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
let fullContent = fullFileCache.get(chunk.file_path);
|
|
111
|
+
if (fullContent === void 0) {
|
|
112
|
+
const row = fileContentStmt.get(chunk.file_path);
|
|
113
|
+
fullContent = row?.content ?? "";
|
|
114
|
+
fullFileCache.set(chunk.file_path, fullContent);
|
|
115
|
+
}
|
|
116
|
+
const baseLine = countLinesBefore(fullContent, chunk.start_index);
|
|
117
|
+
for (const match of locateMatches(code, pattern, baseLine)) {
|
|
118
|
+
matches.push({
|
|
119
|
+
filePath: chunk.file_path,
|
|
120
|
+
line: match.line,
|
|
121
|
+
breadcrumb: chunk.breadcrumb,
|
|
122
|
+
snippet: match.snippet
|
|
123
|
+
});
|
|
124
|
+
if (matches.length >= max_results) {
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
const body = matches.length > 0 ? matches.map(
|
|
130
|
+
(match) => `- ${match.filePath}:${match.line} | ${match.breadcrumb || "-"} | ${match.snippet}`
|
|
131
|
+
).join("\n") : "No exact text references found.";
|
|
132
|
+
return formatTextResponse(`Found ${matches.length} text references for "${symbol}"
|
|
133
|
+
|
|
134
|
+
${body}`);
|
|
135
|
+
} finally {
|
|
136
|
+
db.close();
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export {
|
|
141
|
+
findReferencesSchema,
|
|
142
|
+
handleFindReferences
|
|
143
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_CONFIG
|
|
3
|
+
} from "./chunk-BFCIZ52F.js";
|
|
4
|
+
|
|
5
|
+
// src/defaultEnv.ts
|
|
6
|
+
function getDefaultEnvFileContent() {
|
|
7
|
+
return `# ContextWeaver \u793A\u4F8B\u73AF\u5883\u53D8\u91CF\u914D\u7F6E\u6587\u4EF6
|
|
8
|
+
|
|
9
|
+
# Embedding API \u914D\u7F6E\uFF08\u5FC5\u9700\uFF09
|
|
10
|
+
EMBEDDINGS_API_KEY=your-api-key-here
|
|
11
|
+
EMBEDDINGS_BASE_URL=https://api.siliconflow.cn/v1/embeddings
|
|
12
|
+
EMBEDDINGS_MODEL=BAAI/bge-m3
|
|
13
|
+
EMBEDDINGS_MAX_CONCURRENCY=10
|
|
14
|
+
EMBEDDINGS_DIMENSIONS=1024
|
|
15
|
+
|
|
16
|
+
# Reranker \u914D\u7F6E\uFF08\u5FC5\u9700\uFF09
|
|
17
|
+
RERANK_API_KEY=your-api-key-here
|
|
18
|
+
RERANK_BASE_URL=https://api.siliconflow.cn/v1/rerank
|
|
19
|
+
RERANK_MODEL=BAAI/bge-reranker-v2-m3
|
|
20
|
+
RERANK_TOP_N=20
|
|
21
|
+
|
|
22
|
+
# \u641C\u7D22\u53C2\u6570\uFF08\u53EF\u9009\uFF0C\u8986\u76D6\u5185\u7F6E\u9ED8\u8BA4\u503C\uFF09
|
|
23
|
+
CW_SEARCH_WVEC=${DEFAULT_CONFIG.wVec}
|
|
24
|
+
CW_SEARCH_WLEX=${DEFAULT_CONFIG.wLex}
|
|
25
|
+
CW_SEARCH_RERANK_TOP_N=${DEFAULT_CONFIG.rerankTopN}
|
|
26
|
+
CW_SEARCH_MAX_TOTAL_CHARS=${DEFAULT_CONFIG.maxTotalChars}
|
|
27
|
+
CW_SEARCH_VECTOR_TOP_K=${DEFAULT_CONFIG.vectorTopK}
|
|
28
|
+
CW_SEARCH_SMART_MAX_K=${DEFAULT_CONFIG.smartMaxK}
|
|
29
|
+
CW_SEARCH_IMPORT_FILES_PER_SEED=${DEFAULT_CONFIG.importFilesPerSeed}
|
|
30
|
+
|
|
31
|
+
# \u7D22\u5F15\u5FFD\u7565\u6A21\u5F0F\uFF08\u53EF\u9009\uFF0C\u9017\u53F7\u5206\u9694\uFF0C\u9ED8\u8BA4\u5DF2\u5305\u542B\u5E38\u89C1\u5FFD\u7565\u9879\uFF09
|
|
32
|
+
# IGNORE_PATTERNS=.venv,node_modules
|
|
33
|
+
`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export {
|
|
37
|
+
getDefaultEnvFileContent
|
|
38
|
+
};
|