@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.
@@ -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
+ };