@lyy0709/contextweaver 1.0.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/LICENSE +21 -0
- package/README.en.md +405 -0
- package/README.md +475 -0
- package/dist/SearchService-R7UMDQ6T.js +1623 -0
- package/dist/browser-VC5772XM.js +30 -0
- package/dist/chunk-6HF343R7.js +186 -0
- package/dist/chunk-CKN7LWEA.js +1337 -0
- package/dist/chunk-ECEVTSSZ.js +894 -0
- package/dist/chunk-LPFRFKFW.js +543 -0
- package/dist/chunk-V2USKRIC.js +310 -0
- package/dist/chunk-XVKMTPCT.js +297 -0
- package/dist/chunk-YVLGQTLG.js +170 -0
- package/dist/claude-IKIA62JA.js +42 -0
- package/dist/codebaseRetrieval-SCN3YIPM.js +11 -0
- package/dist/config-WTC56Y2R.js +22 -0
- package/dist/enhancer-QHNMR35J.js +8 -0
- package/dist/gemini-Q37K5XA5.js +44 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +202 -0
- package/dist/lock-K4TS4ENC.js +106 -0
- package/dist/logger-SF6S6GVR.js +9 -0
- package/dist/mcp/main.d.ts +1 -0
- package/dist/mcp/main.js +18 -0
- package/dist/openai-MOPZNA5I.js +34 -0
- package/dist/scanner-T7MGYXQV.js +10 -0
- package/dist/server-276GGS5G.js +614 -0
- package/dist/server-DENFYPME.js +263 -0
- package/package.json +82 -0
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
// src/config.ts
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import os from "os";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import dotenv from "dotenv";
|
|
6
|
+
var isDev = process.env.NODE_ENV === "dev";
|
|
7
|
+
var isMcpMode = process.argv.includes("mcp");
|
|
8
|
+
function loadEnv() {
|
|
9
|
+
const candidates = isDev ? [
|
|
10
|
+
path.join(process.cwd(), ".env"),
|
|
11
|
+
// 1. 当前目录(开发用)
|
|
12
|
+
path.join(os.homedir(), ".contextweaver", ".env")
|
|
13
|
+
// 2. 用户配置目录(回退)
|
|
14
|
+
] : [
|
|
15
|
+
path.join(os.homedir(), ".contextweaver", ".env")
|
|
16
|
+
// 生产环境只用用户配置
|
|
17
|
+
];
|
|
18
|
+
const envPath = candidates.find((p) => fs.existsSync(p));
|
|
19
|
+
if (envPath) {
|
|
20
|
+
const result = dotenv.config({ path: envPath, quiet: true });
|
|
21
|
+
if (result.error) {
|
|
22
|
+
console.error(`[config] \u52A0\u8F7D\u73AF\u5883\u53D8\u91CF\u5931\u8D25: ${result.error.message}`);
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
loadEnv();
|
|
28
|
+
var DEFAULT_API_KEY_PLACEHOLDER = "your-api-key-here";
|
|
29
|
+
function checkEmbeddingEnv() {
|
|
30
|
+
const missingVars = [];
|
|
31
|
+
const apiKey = process.env.EMBEDDINGS_API_KEY;
|
|
32
|
+
if (!apiKey || apiKey === DEFAULT_API_KEY_PLACEHOLDER) {
|
|
33
|
+
missingVars.push("EMBEDDINGS_API_KEY");
|
|
34
|
+
}
|
|
35
|
+
if (!process.env.EMBEDDINGS_BASE_URL) {
|
|
36
|
+
missingVars.push("EMBEDDINGS_BASE_URL");
|
|
37
|
+
}
|
|
38
|
+
if (!process.env.EMBEDDINGS_MODEL) {
|
|
39
|
+
missingVars.push("EMBEDDINGS_MODEL");
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
isValid: missingVars.length === 0,
|
|
43
|
+
missingVars
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function checkRerankerEnv() {
|
|
47
|
+
const missingVars = [];
|
|
48
|
+
const apiKey = process.env.RERANK_API_KEY;
|
|
49
|
+
if (!apiKey || apiKey === DEFAULT_API_KEY_PLACEHOLDER) {
|
|
50
|
+
missingVars.push("RERANK_API_KEY");
|
|
51
|
+
}
|
|
52
|
+
if (!process.env.RERANK_BASE_URL) {
|
|
53
|
+
missingVars.push("RERANK_BASE_URL");
|
|
54
|
+
}
|
|
55
|
+
if (!process.env.RERANK_MODEL) {
|
|
56
|
+
missingVars.push("RERANK_MODEL");
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
isValid: missingVars.length === 0,
|
|
60
|
+
missingVars
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
function checkEnhancerEnv() {
|
|
64
|
+
const missingVars = [];
|
|
65
|
+
const apiKey = process.env.PROMPT_ENHANCER_TOKEN;
|
|
66
|
+
if (!apiKey || apiKey === DEFAULT_API_KEY_PLACEHOLDER) {
|
|
67
|
+
missingVars.push("PROMPT_ENHANCER_TOKEN");
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
isValid: missingVars.length === 0,
|
|
71
|
+
missingVars
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
function getEmbeddingConfig() {
|
|
75
|
+
const apiKey = process.env.EMBEDDINGS_API_KEY;
|
|
76
|
+
const baseUrl = process.env.EMBEDDINGS_BASE_URL;
|
|
77
|
+
const model = process.env.EMBEDDINGS_MODEL;
|
|
78
|
+
const maxConcurrency = parseInt(process.env.EMBEDDINGS_MAX_CONCURRENCY || "10", 10);
|
|
79
|
+
if (!apiKey) {
|
|
80
|
+
throw new Error("EMBEDDINGS_API_KEY \u73AF\u5883\u53D8\u91CF\u672A\u8BBE\u7F6E");
|
|
81
|
+
}
|
|
82
|
+
if (!baseUrl) {
|
|
83
|
+
throw new Error("EMBEDDINGS_BASE_URL \u73AF\u5883\u53D8\u91CF\u672A\u8BBE\u7F6E");
|
|
84
|
+
}
|
|
85
|
+
if (!model) {
|
|
86
|
+
throw new Error("EMBEDDINGS_MODEL \u73AF\u5883\u53D8\u91CF\u672A\u8BBE\u7F6E");
|
|
87
|
+
}
|
|
88
|
+
const dimensions = parseInt(process.env.EMBEDDINGS_DIMENSIONS || "1024", 10);
|
|
89
|
+
return {
|
|
90
|
+
apiKey,
|
|
91
|
+
baseUrl,
|
|
92
|
+
model,
|
|
93
|
+
maxConcurrency: Number.isNaN(maxConcurrency) ? 4 : maxConcurrency,
|
|
94
|
+
dimensions: Number.isNaN(dimensions) ? 1024 : dimensions
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
function getRerankerConfig() {
|
|
98
|
+
const apiKey = process.env.RERANK_API_KEY;
|
|
99
|
+
const baseUrl = process.env.RERANK_BASE_URL;
|
|
100
|
+
const model = process.env.RERANK_MODEL;
|
|
101
|
+
const topN = parseInt(process.env.RERANK_TOP_N || "10", 10);
|
|
102
|
+
if (!apiKey) {
|
|
103
|
+
throw new Error("RERANK_API_KEY \u73AF\u5883\u53D8\u91CF\u672A\u8BBE\u7F6E");
|
|
104
|
+
}
|
|
105
|
+
if (!baseUrl) {
|
|
106
|
+
throw new Error("RERANK_BASE_URL \u73AF\u5883\u53D8\u91CF\u672A\u8BBE\u7F6E");
|
|
107
|
+
}
|
|
108
|
+
if (!model) {
|
|
109
|
+
throw new Error("RERANK_MODEL \u73AF\u5883\u53D8\u91CF\u672A\u8BBE\u7F6E");
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
apiKey,
|
|
113
|
+
baseUrl,
|
|
114
|
+
model,
|
|
115
|
+
topN: Number.isNaN(topN) ? 10 : topN
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
function getEnhancerConfig() {
|
|
119
|
+
const endpointRaw = process.env.PROMPT_ENHANCER_ENDPOINT || "openai";
|
|
120
|
+
const endpoint = endpointRaw.toLowerCase();
|
|
121
|
+
if (endpoint !== "openai" && endpoint !== "claude" && endpoint !== "gemini") {
|
|
122
|
+
throw new Error(
|
|
123
|
+
`PROMPT_ENHANCER_ENDPOINT \u73AF\u5883\u53D8\u91CF\u65E0\u6548: ${endpointRaw} (\u4EC5\u652F\u6301 openai/claude/gemini)`
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
const defaultBaseUrlByEndpoint = {
|
|
127
|
+
openai: "https://api.openai.com/v1/chat/completions",
|
|
128
|
+
claude: "https://api.anthropic.com/v1/messages",
|
|
129
|
+
gemini: "https://generativelanguage.googleapis.com/v1beta"
|
|
130
|
+
};
|
|
131
|
+
const defaultModelByEndpoint = {
|
|
132
|
+
openai: "gpt-4o-mini",
|
|
133
|
+
claude: "claude-sonnet-4-20250514",
|
|
134
|
+
gemini: "gemini-2.0-flash"
|
|
135
|
+
};
|
|
136
|
+
const apiKey = process.env.PROMPT_ENHANCER_TOKEN;
|
|
137
|
+
if (!apiKey) {
|
|
138
|
+
throw new Error("PROMPT_ENHANCER_TOKEN \u73AF\u5883\u53D8\u91CF\u672A\u8BBE\u7F6E");
|
|
139
|
+
}
|
|
140
|
+
const model = process.env.PROMPT_ENHANCER_MODEL || defaultModelByEndpoint[endpoint];
|
|
141
|
+
const baseUrl = process.env.PROMPT_ENHANCER_BASE_URL || defaultBaseUrlByEndpoint[endpoint];
|
|
142
|
+
return {
|
|
143
|
+
endpoint,
|
|
144
|
+
apiKey,
|
|
145
|
+
baseUrl,
|
|
146
|
+
model,
|
|
147
|
+
templatePath: process.env.PROMPT_ENHANCER_TEMPLATE
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
var DEFAULT_EXCLUDE_PATTERNS = [
|
|
151
|
+
// --- 1. 依赖与环境 (绝对黑名单) ---
|
|
152
|
+
"node_modules",
|
|
153
|
+
"bower_components",
|
|
154
|
+
".venv",
|
|
155
|
+
"venv",
|
|
156
|
+
// Python 虚拟环境
|
|
157
|
+
".env.*",
|
|
158
|
+
// 环境变量文件 (.env.local, .env.production 等)
|
|
159
|
+
// --- 2. 锁文件 (Token 杀手,且语义密度极低) ---
|
|
160
|
+
"package-lock.json",
|
|
161
|
+
"yarn.lock",
|
|
162
|
+
"pnpm-lock.yaml",
|
|
163
|
+
"bun.lockb",
|
|
164
|
+
"poetry.lock",
|
|
165
|
+
"Gemfile.lock",
|
|
166
|
+
"composer.lock",
|
|
167
|
+
"Cargo.lock",
|
|
168
|
+
// --- 3. 版本控制与 IDE ---
|
|
169
|
+
".git",
|
|
170
|
+
".svn",
|
|
171
|
+
".hg",
|
|
172
|
+
".idea",
|
|
173
|
+
".vscode",
|
|
174
|
+
".vs",
|
|
175
|
+
// --- 4. 构建产物与缓存 ---
|
|
176
|
+
// 通用构建输出
|
|
177
|
+
"dist",
|
|
178
|
+
"build",
|
|
179
|
+
"out",
|
|
180
|
+
"target",
|
|
181
|
+
// 编译产物
|
|
182
|
+
"*.pyc",
|
|
183
|
+
"*.pyo",
|
|
184
|
+
"*.pyd",
|
|
185
|
+
"*.so",
|
|
186
|
+
"*.dll",
|
|
187
|
+
"*.exe",
|
|
188
|
+
"*.bin",
|
|
189
|
+
"*.wasm",
|
|
190
|
+
// 现代前端框架产物
|
|
191
|
+
".next",
|
|
192
|
+
".nuxt",
|
|
193
|
+
".output",
|
|
194
|
+
".svelte-kit",
|
|
195
|
+
// Bundler 缓存
|
|
196
|
+
".turbo",
|
|
197
|
+
".parcel-cache",
|
|
198
|
+
".webpack",
|
|
199
|
+
".esbuild",
|
|
200
|
+
".rollup.cache",
|
|
201
|
+
// 测试覆盖率
|
|
202
|
+
"coverage",
|
|
203
|
+
".nyc_output",
|
|
204
|
+
// Python 缓存
|
|
205
|
+
"__pycache__",
|
|
206
|
+
".pytest_cache",
|
|
207
|
+
".mypy_cache",
|
|
208
|
+
".tox",
|
|
209
|
+
".eggs",
|
|
210
|
+
"*.egg-info",
|
|
211
|
+
// --- 5. 纯噪音文件 (无文本语义) ---
|
|
212
|
+
// 压缩文件与 SourceMap
|
|
213
|
+
"*.min.js",
|
|
214
|
+
"*.min.css",
|
|
215
|
+
"*.map",
|
|
216
|
+
// 图片与多媒体
|
|
217
|
+
"*.svg",
|
|
218
|
+
"*.png",
|
|
219
|
+
"*.jpg",
|
|
220
|
+
"*.jpeg",
|
|
221
|
+
"*.gif",
|
|
222
|
+
"*.ico",
|
|
223
|
+
"*.webp",
|
|
224
|
+
"*.bmp",
|
|
225
|
+
"*.pdf",
|
|
226
|
+
"*.mp3",
|
|
227
|
+
"*.mp4",
|
|
228
|
+
"*.wav",
|
|
229
|
+
"*.webm",
|
|
230
|
+
"*.ogg",
|
|
231
|
+
"*.flac",
|
|
232
|
+
// 字体文件
|
|
233
|
+
"*.woff",
|
|
234
|
+
"*.woff2",
|
|
235
|
+
"*.ttf",
|
|
236
|
+
"*.eot",
|
|
237
|
+
"*.otf",
|
|
238
|
+
// 压缩包
|
|
239
|
+
"*.zip",
|
|
240
|
+
"*.tar",
|
|
241
|
+
"*.gz",
|
|
242
|
+
"*.rar",
|
|
243
|
+
"*.7z",
|
|
244
|
+
// 系统垃圾
|
|
245
|
+
".DS_Store",
|
|
246
|
+
"Thumbs.db",
|
|
247
|
+
// --- 6. 测试噪音 (保留 *.test.ts,但剔除这些) ---
|
|
248
|
+
// Jest 快照
|
|
249
|
+
"__snapshots__",
|
|
250
|
+
"*.snap",
|
|
251
|
+
// 测试夹具与数据
|
|
252
|
+
"test/fixtures",
|
|
253
|
+
"tests/fixtures",
|
|
254
|
+
"__fixtures__",
|
|
255
|
+
"test/data",
|
|
256
|
+
"tests/data",
|
|
257
|
+
"testdata",
|
|
258
|
+
"test-data",
|
|
259
|
+
"testutils",
|
|
260
|
+
// Mock 数据
|
|
261
|
+
"mock",
|
|
262
|
+
"mocks",
|
|
263
|
+
"__mocks__",
|
|
264
|
+
"stub",
|
|
265
|
+
"stubs",
|
|
266
|
+
// --- 7. 第三方与生成文件 ---
|
|
267
|
+
// 第三方依赖目录
|
|
268
|
+
"vendor",
|
|
269
|
+
"vendors",
|
|
270
|
+
"third_party",
|
|
271
|
+
"thirdparty",
|
|
272
|
+
"3rdparty",
|
|
273
|
+
"external",
|
|
274
|
+
"externals",
|
|
275
|
+
// 生成文件
|
|
276
|
+
"generated",
|
|
277
|
+
"gen",
|
|
278
|
+
"auto-generated",
|
|
279
|
+
"*.generated.ts",
|
|
280
|
+
"*.generated.js",
|
|
281
|
+
"*.pb.go",
|
|
282
|
+
"*.pb.ts",
|
|
283
|
+
// protobuf 生成
|
|
284
|
+
// --- 8. 日志与临时文件 ---
|
|
285
|
+
"*.log",
|
|
286
|
+
".cache",
|
|
287
|
+
".tmp",
|
|
288
|
+
"tmp"
|
|
289
|
+
];
|
|
290
|
+
function getExcludePatterns() {
|
|
291
|
+
const envPatterns = process.env.IGNORE_PATTERNS;
|
|
292
|
+
const patterns = [...DEFAULT_EXCLUDE_PATTERNS];
|
|
293
|
+
if (envPatterns) {
|
|
294
|
+
const additional = envPatterns.split(",").map((p) => p.trim()).filter(Boolean);
|
|
295
|
+
patterns.push(...additional);
|
|
296
|
+
}
|
|
297
|
+
return patterns;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
export {
|
|
301
|
+
isDev,
|
|
302
|
+
isMcpMode,
|
|
303
|
+
checkEmbeddingEnv,
|
|
304
|
+
checkRerankerEnv,
|
|
305
|
+
checkEnhancerEnv,
|
|
306
|
+
getEmbeddingConfig,
|
|
307
|
+
getRerankerConfig,
|
|
308
|
+
getEnhancerConfig,
|
|
309
|
+
getExcludePatterns
|
|
310
|
+
};
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import {
|
|
2
|
+
generateProjectId
|
|
3
|
+
} from "./chunk-LPFRFKFW.js";
|
|
4
|
+
import {
|
|
5
|
+
logger
|
|
6
|
+
} from "./chunk-YVLGQTLG.js";
|
|
7
|
+
|
|
8
|
+
// src/mcp/tools/codebaseRetrieval.ts
|
|
9
|
+
import fs from "fs";
|
|
10
|
+
import os from "os";
|
|
11
|
+
import path from "path";
|
|
12
|
+
import { z } from "zod";
|
|
13
|
+
var codebaseRetrievalSchema = z.object({
|
|
14
|
+
repo_path: z.string().describe(
|
|
15
|
+
"The absolute file system path to the repository root. (e.g., '/Users/dev/my-project')"
|
|
16
|
+
),
|
|
17
|
+
information_request: z.string().describe(
|
|
18
|
+
"The SEMANTIC GOAL. Describe the functionality, logic, or behavior you are looking for in full natural language sentences. Focus on 'how it works' rather than exact names. (e.g., 'Trace the execution flow of the login process')"
|
|
19
|
+
),
|
|
20
|
+
technical_terms: z.array(z.string()).optional().describe(
|
|
21
|
+
"HARD FILTERS. Precise identifiers to narrow down results. Only use symbols KNOWN to exist to avoid false negatives."
|
|
22
|
+
)
|
|
23
|
+
});
|
|
24
|
+
var ZEN_CONFIG_OVERRIDE = {
|
|
25
|
+
// E1: 邻居扩展 - 前后看 2 个 chunk,保证代码块完整性
|
|
26
|
+
neighborHops: 2,
|
|
27
|
+
// E2: 面包屑补全 - 必须开启,保证能看到当前方法所属的 Class/Function 定义
|
|
28
|
+
breadcrumbExpandLimit: 3,
|
|
29
|
+
// E3: Import 扩展 - 强制关闭!
|
|
30
|
+
// 理由:跨文件是 Agent 的决策,不要预加载,防止 Token 爆炸
|
|
31
|
+
importFilesPerSeed: 0,
|
|
32
|
+
chunksPerImportFile: 0
|
|
33
|
+
};
|
|
34
|
+
var BASE_DIR = path.join(os.homedir(), ".contextweaver");
|
|
35
|
+
async function ensureDefaultEnvFile() {
|
|
36
|
+
const configDir = BASE_DIR;
|
|
37
|
+
const envFile = path.join(configDir, ".env");
|
|
38
|
+
if (fs.existsSync(envFile)) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
if (!fs.existsSync(configDir)) {
|
|
42
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
43
|
+
logger.info({ configDir }, "\u521B\u5EFA\u914D\u7F6E\u76EE\u5F55");
|
|
44
|
+
}
|
|
45
|
+
const defaultEnvContent = `# ContextWeaver \u793A\u4F8B\u73AF\u5883\u53D8\u91CF\u914D\u7F6E\u6587\u4EF6
|
|
46
|
+
|
|
47
|
+
# Embedding API \u914D\u7F6E\uFF08\u5FC5\u9700\uFF09
|
|
48
|
+
EMBEDDINGS_API_KEY=your-api-key-here
|
|
49
|
+
EMBEDDINGS_BASE_URL=https://api.siliconflow.cn/v1/embeddings
|
|
50
|
+
EMBEDDINGS_MODEL=BAAI/bge-m3
|
|
51
|
+
EMBEDDINGS_MAX_CONCURRENCY=10
|
|
52
|
+
EMBEDDINGS_DIMENSIONS=1024
|
|
53
|
+
|
|
54
|
+
# Reranker \u914D\u7F6E\uFF08\u5FC5\u9700\uFF09
|
|
55
|
+
RERANK_API_KEY=your-api-key-here
|
|
56
|
+
RERANK_BASE_URL=https://api.siliconflow.cn/v1/rerank
|
|
57
|
+
RERANK_MODEL=BAAI/bge-reranker-v2-m3
|
|
58
|
+
RERANK_TOP_N=20
|
|
59
|
+
|
|
60
|
+
# \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
|
|
61
|
+
# IGNORE_PATTERNS=.venv,node_modules
|
|
62
|
+
|
|
63
|
+
# Prompt Enhancer \u914D\u7F6E\uFF08\u53EF\u9009\uFF0C\u4F7F\u7528 enhance-prompt \u5DE5\u5177\u65F6\u9700\u8981\uFF09
|
|
64
|
+
# PROMPT_ENHANCER_ENDPOINT=openai
|
|
65
|
+
# PROMPT_ENHANCER_BASE_URL=
|
|
66
|
+
# PROMPT_ENHANCER_TOKEN=your-api-key-here
|
|
67
|
+
# PROMPT_ENHANCER_MODEL=
|
|
68
|
+
# PROMPT_ENHANCER_TEMPLATE=
|
|
69
|
+
`;
|
|
70
|
+
fs.writeFileSync(envFile, defaultEnvContent);
|
|
71
|
+
logger.info({ envFile }, "\u5DF2\u521B\u5EFA\u9ED8\u8BA4 .env \u914D\u7F6E\u6587\u4EF6");
|
|
72
|
+
}
|
|
73
|
+
function isProjectIndexed(projectId) {
|
|
74
|
+
const dbPath = path.join(BASE_DIR, projectId, "index.db");
|
|
75
|
+
return fs.existsSync(dbPath);
|
|
76
|
+
}
|
|
77
|
+
async function ensureIndexed(repoPath, projectId, onProgress) {
|
|
78
|
+
const { withLock } = await import("./lock-K4TS4ENC.js");
|
|
79
|
+
const { scan } = await import("./scanner-T7MGYXQV.js");
|
|
80
|
+
await withLock(projectId, "index", async () => {
|
|
81
|
+
const wasIndexed = isProjectIndexed(projectId);
|
|
82
|
+
if (!wasIndexed) {
|
|
83
|
+
logger.info(
|
|
84
|
+
{ repoPath, projectId: projectId.slice(0, 10) },
|
|
85
|
+
"\u4EE3\u7801\u5E93\u672A\u521D\u59CB\u5316\uFF0C\u5F00\u59CB\u9996\u6B21\u7D22\u5F15..."
|
|
86
|
+
);
|
|
87
|
+
onProgress?.(0, 100, "\u4EE3\u7801\u5E93\u672A\u7D22\u5F15\uFF0C\u5F00\u59CB\u9996\u6B21\u7D22\u5F15...");
|
|
88
|
+
} else {
|
|
89
|
+
logger.debug({ projectId: projectId.slice(0, 10) }, "\u6267\u884C\u589E\u91CF\u7D22\u5F15...");
|
|
90
|
+
}
|
|
91
|
+
const startTime = Date.now();
|
|
92
|
+
const stats = await scan(repoPath, { vectorIndex: true, onProgress });
|
|
93
|
+
const elapsed = Date.now() - startTime;
|
|
94
|
+
logger.info(
|
|
95
|
+
{
|
|
96
|
+
projectId: projectId.slice(0, 10),
|
|
97
|
+
isFirstTime: !wasIndexed,
|
|
98
|
+
totalFiles: stats.totalFiles,
|
|
99
|
+
added: stats.added,
|
|
100
|
+
modified: stats.modified,
|
|
101
|
+
deleted: stats.deleted,
|
|
102
|
+
vectorIndex: stats.vectorIndex,
|
|
103
|
+
elapsedMs: elapsed
|
|
104
|
+
},
|
|
105
|
+
"\u7D22\u5F15\u5B8C\u6210"
|
|
106
|
+
);
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
async function handleCodebaseRetrieval(args, configOverride = ZEN_CONFIG_OVERRIDE, onProgress) {
|
|
110
|
+
const { repo_path, information_request, technical_terms } = args;
|
|
111
|
+
logger.info(
|
|
112
|
+
{
|
|
113
|
+
repo_path,
|
|
114
|
+
information_request,
|
|
115
|
+
technical_terms
|
|
116
|
+
},
|
|
117
|
+
"MCP codebase-retrieval \u8C03\u7528\u5F00\u59CB"
|
|
118
|
+
);
|
|
119
|
+
const { checkEmbeddingEnv, checkRerankerEnv } = await import("./config-WTC56Y2R.js");
|
|
120
|
+
const embeddingCheck = checkEmbeddingEnv();
|
|
121
|
+
const rerankerCheck = checkRerankerEnv();
|
|
122
|
+
const allMissingVars = [...embeddingCheck.missingVars, ...rerankerCheck.missingVars];
|
|
123
|
+
if (allMissingVars.length > 0) {
|
|
124
|
+
logger.warn({ missingVars: allMissingVars }, "MCP \u73AF\u5883\u53D8\u91CF\u672A\u914D\u7F6E");
|
|
125
|
+
await ensureDefaultEnvFile();
|
|
126
|
+
return formatEnvMissingResponse(allMissingVars);
|
|
127
|
+
}
|
|
128
|
+
const projectId = generateProjectId(repo_path);
|
|
129
|
+
await ensureIndexed(repo_path, projectId, onProgress);
|
|
130
|
+
const query = [information_request, ...technical_terms || []].filter(Boolean).join(" ");
|
|
131
|
+
logger.info(
|
|
132
|
+
{
|
|
133
|
+
projectId: projectId.slice(0, 10),
|
|
134
|
+
query,
|
|
135
|
+
zenConfig: configOverride
|
|
136
|
+
},
|
|
137
|
+
"MCP \u67E5\u8BE2\u6784\u5EFA"
|
|
138
|
+
);
|
|
139
|
+
const { SearchService } = await import("./SearchService-R7UMDQ6T.js");
|
|
140
|
+
const service = new SearchService(projectId, repo_path, configOverride);
|
|
141
|
+
await service.init();
|
|
142
|
+
logger.debug("SearchService \u521D\u59CB\u5316\u5B8C\u6210");
|
|
143
|
+
const contextPack = await service.buildContextPack(query);
|
|
144
|
+
if (contextPack.seeds.length > 0) {
|
|
145
|
+
logger.info(
|
|
146
|
+
{
|
|
147
|
+
seeds: contextPack.seeds.map((s) => ({
|
|
148
|
+
file: s.filePath,
|
|
149
|
+
chunk: s.chunkIndex,
|
|
150
|
+
score: s.score.toFixed(4),
|
|
151
|
+
source: s.source
|
|
152
|
+
}))
|
|
153
|
+
},
|
|
154
|
+
"MCP \u641C\u7D22 seeds"
|
|
155
|
+
);
|
|
156
|
+
} else {
|
|
157
|
+
logger.warn("MCP \u641C\u7D22\u65E0 seeds \u547D\u4E2D");
|
|
158
|
+
}
|
|
159
|
+
if (contextPack.expanded.length > 0) {
|
|
160
|
+
logger.debug(
|
|
161
|
+
{
|
|
162
|
+
expandedCount: contextPack.expanded.length,
|
|
163
|
+
expanded: contextPack.expanded.slice(0, 5).map((e) => ({
|
|
164
|
+
file: e.filePath,
|
|
165
|
+
chunk: e.chunkIndex,
|
|
166
|
+
score: e.score.toFixed(4)
|
|
167
|
+
}))
|
|
168
|
+
},
|
|
169
|
+
"MCP \u6269\u5C55\u7ED3\u679C (\u524D5)"
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
logger.info(
|
|
173
|
+
{
|
|
174
|
+
seedCount: contextPack.seeds.length,
|
|
175
|
+
expandedCount: contextPack.expanded.length,
|
|
176
|
+
fileCount: contextPack.files.length,
|
|
177
|
+
totalSegments: contextPack.files.reduce((acc, f) => acc + f.segments.length, 0),
|
|
178
|
+
files: contextPack.files.map((f) => ({
|
|
179
|
+
path: f.filePath,
|
|
180
|
+
segments: f.segments.length,
|
|
181
|
+
lines: f.segments.map((s) => `L${s.startLine}-${s.endLine}`)
|
|
182
|
+
})),
|
|
183
|
+
timingMs: contextPack.debug?.timingMs
|
|
184
|
+
},
|
|
185
|
+
"MCP codebase-retrieval \u5B8C\u6210"
|
|
186
|
+
);
|
|
187
|
+
return formatMcpResponse(contextPack);
|
|
188
|
+
}
|
|
189
|
+
function formatMcpResponse(pack) {
|
|
190
|
+
const { files, seeds } = pack;
|
|
191
|
+
const fileBlocks = files.map((file) => {
|
|
192
|
+
const segments = file.segments.map((seg) => formatSegment(seg)).join("\n\n");
|
|
193
|
+
return segments;
|
|
194
|
+
}).join("\n\n---\n\n");
|
|
195
|
+
const summary = [
|
|
196
|
+
`Found ${seeds.length} relevant code blocks`,
|
|
197
|
+
`Files: ${files.length}`,
|
|
198
|
+
`Total segments: ${files.reduce((acc, f) => acc + f.segments.length, 0)}`
|
|
199
|
+
].join(" | ");
|
|
200
|
+
const text = `${summary}
|
|
201
|
+
|
|
202
|
+
${fileBlocks}`;
|
|
203
|
+
return {
|
|
204
|
+
content: [
|
|
205
|
+
{
|
|
206
|
+
type: "text",
|
|
207
|
+
text
|
|
208
|
+
}
|
|
209
|
+
]
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
function formatSegment(seg) {
|
|
213
|
+
const lang = detectLanguage(seg.filePath);
|
|
214
|
+
const header = `## ${seg.filePath} (L${seg.startLine}-${seg.endLine})`;
|
|
215
|
+
const breadcrumb = seg.breadcrumb ? `> ${seg.breadcrumb}` : "";
|
|
216
|
+
const code = `\`\`\`${lang}
|
|
217
|
+
${seg.text}
|
|
218
|
+
\`\`\``;
|
|
219
|
+
return [header, breadcrumb, code].filter(Boolean).join("\n");
|
|
220
|
+
}
|
|
221
|
+
function detectLanguage(filePath) {
|
|
222
|
+
const ext = filePath.split(".").pop()?.toLowerCase() || "";
|
|
223
|
+
const langMap = {
|
|
224
|
+
ts: "typescript",
|
|
225
|
+
tsx: "typescript",
|
|
226
|
+
js: "javascript",
|
|
227
|
+
jsx: "javascript",
|
|
228
|
+
py: "python",
|
|
229
|
+
rs: "rust",
|
|
230
|
+
go: "go",
|
|
231
|
+
java: "java",
|
|
232
|
+
c: "c",
|
|
233
|
+
cpp: "cpp",
|
|
234
|
+
h: "c",
|
|
235
|
+
hpp: "cpp",
|
|
236
|
+
cs: "csharp",
|
|
237
|
+
rb: "ruby",
|
|
238
|
+
php: "php",
|
|
239
|
+
swift: "swift",
|
|
240
|
+
kt: "kotlin",
|
|
241
|
+
scala: "scala",
|
|
242
|
+
sql: "sql",
|
|
243
|
+
sh: "bash",
|
|
244
|
+
bash: "bash",
|
|
245
|
+
zsh: "bash",
|
|
246
|
+
json: "json",
|
|
247
|
+
yaml: "yaml",
|
|
248
|
+
yml: "yaml",
|
|
249
|
+
xml: "xml",
|
|
250
|
+
html: "html",
|
|
251
|
+
css: "css",
|
|
252
|
+
scss: "scss",
|
|
253
|
+
less: "less",
|
|
254
|
+
md: "markdown",
|
|
255
|
+
toml: "toml"
|
|
256
|
+
};
|
|
257
|
+
return langMap[ext] || ext || "plaintext";
|
|
258
|
+
}
|
|
259
|
+
function formatEnvMissingResponse(missingVars) {
|
|
260
|
+
const configPath = "~/.contextweaver/.env";
|
|
261
|
+
const text = `## \u26A0\uFE0F \u914D\u7F6E\u7F3A\u5931
|
|
262
|
+
|
|
263
|
+
ContextWeaver \u9700\u8981\u914D\u7F6E Embedding API \u624D\u80FD\u5DE5\u4F5C\u3002
|
|
264
|
+
|
|
265
|
+
### \u7F3A\u5931\u7684\u73AF\u5883\u53D8\u91CF
|
|
266
|
+
${missingVars.map((v) => `- \`${v}\``).join("\n")}
|
|
267
|
+
|
|
268
|
+
### \u914D\u7F6E\u6B65\u9AA4
|
|
269
|
+
|
|
270
|
+
\u5DF2\u81EA\u52A8\u521B\u5EFA\u914D\u7F6E\u6587\u4EF6\uFF1A\`${configPath}\`
|
|
271
|
+
|
|
272
|
+
\u8BF7\u7F16\u8F91\u8BE5\u6587\u4EF6\uFF0C\u586B\u5199\u4F60\u7684 API Key\uFF1A
|
|
273
|
+
|
|
274
|
+
\`\`\`bash
|
|
275
|
+
# Embedding API \u914D\u7F6E\uFF08\u5FC5\u9700\uFF09
|
|
276
|
+
EMBEDDINGS_API_KEY=your-api-key-here # \u2190 \u66FF\u6362\u4E3A\u4F60\u7684 API Key
|
|
277
|
+
|
|
278
|
+
# Reranker \u914D\u7F6E\uFF08\u5FC5\u9700\uFF09
|
|
279
|
+
RERANK_API_KEY=your-api-key-here # \u2190 \u66FF\u6362\u4E3A\u4F60\u7684 API Key
|
|
280
|
+
\`\`\`
|
|
281
|
+
|
|
282
|
+
\u4FDD\u5B58\u6587\u4EF6\u540E\u91CD\u65B0\u8C03\u7528\u6B64\u5DE5\u5177\u5373\u53EF\u3002
|
|
283
|
+
`;
|
|
284
|
+
return {
|
|
285
|
+
content: [
|
|
286
|
+
{
|
|
287
|
+
type: "text",
|
|
288
|
+
text
|
|
289
|
+
}
|
|
290
|
+
]
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
export {
|
|
295
|
+
codebaseRetrievalSchema,
|
|
296
|
+
handleCodebaseRetrieval
|
|
297
|
+
};
|