@chiway/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,286 @@
1
+ import {
2
+ generateProjectId
3
+ } from "./chunk-B6OWNBOD.js";
4
+ import {
5
+ logger
6
+ } from "./chunk-AMQQK4P7.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 BASE_DIR = path.join(os.homedir(), ".contextweaver");
25
+ var INDEX_LOCK_TIMEOUT_MS = 10 * 60 * 1e3;
26
+ async function ensureDefaultEnvFile() {
27
+ const configDir = BASE_DIR;
28
+ const envFile = path.join(configDir, ".env");
29
+ if (fs.existsSync(envFile)) {
30
+ return;
31
+ }
32
+ if (!fs.existsSync(configDir)) {
33
+ fs.mkdirSync(configDir, { recursive: true });
34
+ logger.info({ configDir }, "\u521B\u5EFA\u914D\u7F6E\u76EE\u5F55");
35
+ }
36
+ const defaultEnvContent = `# ContextWeaver \u793A\u4F8B\u73AF\u5883\u53D8\u91CF\u914D\u7F6E\u6587\u4EF6
37
+
38
+ # Embedding API \u914D\u7F6E\uFF08\u5FC5\u9700\uFF09
39
+ EMBEDDINGS_API_KEY=your-api-key-here
40
+ EMBEDDINGS_BASE_URL=https://api.siliconflow.cn/v1/embeddings
41
+ EMBEDDINGS_MODEL=BAAI/bge-m3
42
+ EMBEDDINGS_MAX_CONCURRENCY=10
43
+ EMBEDDINGS_DIMENSIONS=1024
44
+
45
+ # Reranker \u914D\u7F6E\uFF08\u5FC5\u9700\uFF09
46
+ RERANK_API_KEY=your-api-key-here
47
+ RERANK_BASE_URL=https://api.siliconflow.cn/v1/rerank
48
+ RERANK_MODEL=BAAI/bge-reranker-v2-m3
49
+ RERANK_TOP_N=20
50
+
51
+ # \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
52
+ # IGNORE_PATTERNS=.venv,node_modules
53
+ `;
54
+ fs.writeFileSync(envFile, defaultEnvContent);
55
+ logger.info({ envFile }, "\u5DF2\u521B\u5EFA\u9ED8\u8BA4 .env \u914D\u7F6E\u6587\u4EF6");
56
+ }
57
+ function isProjectIndexed(projectId) {
58
+ const dbPath = path.join(BASE_DIR, projectId, "index.db");
59
+ return fs.existsSync(dbPath);
60
+ }
61
+ async function ensureIndexed(repoPath, projectId, onProgress) {
62
+ const { withLock } = await import("./lock-DVY3KJSK.js");
63
+ const { scan } = await import("./scanner-SZ2BDYDS.js");
64
+ await withLock(
65
+ projectId,
66
+ "index",
67
+ async () => {
68
+ const wasIndexed = isProjectIndexed(projectId);
69
+ if (!wasIndexed) {
70
+ logger.info(
71
+ { repoPath, projectId: projectId.slice(0, 10) },
72
+ "\u4EE3\u7801\u5E93\u672A\u521D\u59CB\u5316\uFF0C\u5F00\u59CB\u9996\u6B21\u7D22\u5F15..."
73
+ );
74
+ onProgress?.(0, 100, "\u4EE3\u7801\u5E93\u672A\u7D22\u5F15\uFF0C\u5F00\u59CB\u9996\u6B21\u7D22\u5F15...");
75
+ } else {
76
+ logger.debug({ projectId: projectId.slice(0, 10) }, "\u6267\u884C\u589E\u91CF\u7D22\u5F15...");
77
+ }
78
+ const startTime = Date.now();
79
+ const stats = await scan(repoPath, { vectorIndex: true, onProgress });
80
+ const elapsed = Date.now() - startTime;
81
+ logger.info(
82
+ {
83
+ projectId: projectId.slice(0, 10),
84
+ isFirstTime: !wasIndexed,
85
+ totalFiles: stats.totalFiles,
86
+ added: stats.added,
87
+ modified: stats.modified,
88
+ deleted: stats.deleted,
89
+ vectorIndex: stats.vectorIndex,
90
+ elapsedMs: elapsed
91
+ },
92
+ "\u7D22\u5F15\u5B8C\u6210"
93
+ );
94
+ },
95
+ INDEX_LOCK_TIMEOUT_MS
96
+ );
97
+ }
98
+ async function handleCodebaseRetrieval(args, onProgress) {
99
+ const { repo_path, information_request, technical_terms } = args;
100
+ logger.info(
101
+ {
102
+ repo_path,
103
+ information_request,
104
+ technical_terms
105
+ },
106
+ "MCP codebase-retrieval \u8C03\u7528\u5F00\u59CB"
107
+ );
108
+ const { checkEmbeddingEnv, checkRerankerEnv } = await import("./config-BWZ6CU3W.js");
109
+ const embeddingCheck = checkEmbeddingEnv();
110
+ const rerankerCheck = checkRerankerEnv();
111
+ const allMissingVars = [...embeddingCheck.missingVars, ...rerankerCheck.missingVars];
112
+ if (allMissingVars.length > 0) {
113
+ logger.warn({ missingVars: allMissingVars }, "MCP \u73AF\u5883\u53D8\u91CF\u672A\u914D\u7F6E");
114
+ await ensureDefaultEnvFile();
115
+ return formatEnvMissingResponse(allMissingVars);
116
+ }
117
+ const projectId = generateProjectId(repo_path);
118
+ await ensureIndexed(repo_path, projectId, onProgress);
119
+ const query = [information_request, ...technical_terms || []].filter(Boolean).join(" ");
120
+ logger.info(
121
+ {
122
+ projectId: projectId.slice(0, 10),
123
+ query
124
+ },
125
+ "MCP \u67E5\u8BE2\u6784\u5EFA"
126
+ );
127
+ const { SearchService } = await import("./SearchService-533KL2HP.js");
128
+ const service = new SearchService(projectId, repo_path);
129
+ await service.init();
130
+ logger.debug("SearchService \u521D\u59CB\u5316\u5B8C\u6210");
131
+ const contextPack = await service.buildContextPack(query);
132
+ if (contextPack.seeds.length > 0) {
133
+ logger.info(
134
+ {
135
+ seeds: contextPack.seeds.map((s) => ({
136
+ file: s.filePath,
137
+ chunk: s.chunkIndex,
138
+ score: s.score.toFixed(4),
139
+ source: s.source
140
+ }))
141
+ },
142
+ "MCP \u641C\u7D22 seeds"
143
+ );
144
+ } else {
145
+ logger.warn("MCP \u641C\u7D22\u65E0 seeds \u547D\u4E2D");
146
+ }
147
+ if (contextPack.expanded.length > 0) {
148
+ logger.debug(
149
+ {
150
+ expandedCount: contextPack.expanded.length,
151
+ expanded: contextPack.expanded.slice(0, 5).map((e) => ({
152
+ file: e.filePath,
153
+ chunk: e.chunkIndex,
154
+ score: e.score.toFixed(4)
155
+ }))
156
+ },
157
+ "MCP \u6269\u5C55\u7ED3\u679C (\u524D5)"
158
+ );
159
+ }
160
+ logger.info(
161
+ {
162
+ seedCount: contextPack.seeds.length,
163
+ expandedCount: contextPack.expanded.length,
164
+ fileCount: contextPack.files.length,
165
+ totalSegments: contextPack.files.reduce((acc, f) => acc + f.segments.length, 0),
166
+ files: contextPack.files.map((f) => ({
167
+ path: f.filePath,
168
+ segments: f.segments.length,
169
+ lines: f.segments.map((s) => `L${s.startLine}-${s.endLine}`)
170
+ })),
171
+ timingMs: contextPack.debug?.timingMs
172
+ },
173
+ "MCP codebase-retrieval \u5B8C\u6210"
174
+ );
175
+ return formatMcpResponse(contextPack);
176
+ }
177
+ function formatMcpResponse(pack) {
178
+ const { files, seeds } = pack;
179
+ const fileBlocks = files.map((file) => {
180
+ const segments = file.segments.map((seg) => formatSegment(seg)).join("\n\n");
181
+ return segments;
182
+ }).join("\n\n---\n\n");
183
+ const summary = [
184
+ `Found ${seeds.length} relevant code blocks`,
185
+ `Files: ${files.length}`,
186
+ `Total segments: ${files.reduce((acc, f) => acc + f.segments.length, 0)}`
187
+ ].join(" | ");
188
+ const text = `${summary}
189
+
190
+ ${fileBlocks}`;
191
+ return {
192
+ content: [
193
+ {
194
+ type: "text",
195
+ text
196
+ }
197
+ ]
198
+ };
199
+ }
200
+ function formatSegment(seg) {
201
+ const lang = detectLanguage(seg.filePath);
202
+ const header = `## ${seg.filePath} (L${seg.startLine}-${seg.endLine})`;
203
+ const breadcrumb = seg.breadcrumb ? `> ${seg.breadcrumb}` : "";
204
+ const code = `\`\`\`${lang}
205
+ ${seg.text}
206
+ \`\`\``;
207
+ return [header, breadcrumb, code].filter(Boolean).join("\n");
208
+ }
209
+ function detectLanguage(filePath) {
210
+ const ext = filePath.split(".").pop()?.toLowerCase() || "";
211
+ const langMap = {
212
+ ts: "typescript",
213
+ tsx: "typescript",
214
+ js: "javascript",
215
+ jsx: "javascript",
216
+ py: "python",
217
+ rs: "rust",
218
+ go: "go",
219
+ java: "java",
220
+ c: "c",
221
+ cpp: "cpp",
222
+ h: "c",
223
+ hpp: "cpp",
224
+ cs: "csharp",
225
+ rb: "ruby",
226
+ php: "php",
227
+ swift: "swift",
228
+ kt: "kotlin",
229
+ scala: "scala",
230
+ sql: "sql",
231
+ sh: "bash",
232
+ bash: "bash",
233
+ zsh: "bash",
234
+ json: "json",
235
+ yaml: "yaml",
236
+ yml: "yaml",
237
+ xml: "xml",
238
+ html: "html",
239
+ css: "css",
240
+ scss: "scss",
241
+ less: "less",
242
+ md: "markdown",
243
+ toml: "toml"
244
+ };
245
+ return langMap[ext] || ext || "plaintext";
246
+ }
247
+ function formatEnvMissingResponse(missingVars) {
248
+ const configPath = "~/.contextweaver/.env";
249
+ const text = `## \u26A0\uFE0F \u914D\u7F6E\u7F3A\u5931
250
+
251
+ ContextWeaver \u9700\u8981\u914D\u7F6E Embedding API \u624D\u80FD\u5DE5\u4F5C\u3002
252
+
253
+ ### \u7F3A\u5931\u7684\u73AF\u5883\u53D8\u91CF
254
+ ${missingVars.map((v) => `- \`${v}\``).join("\n")}
255
+
256
+ ### \u914D\u7F6E\u6B65\u9AA4
257
+
258
+ \u5DF2\u81EA\u52A8\u521B\u5EFA\u914D\u7F6E\u6587\u4EF6\uFF1A\`${configPath}\`
259
+
260
+ \u8BF7\u7F16\u8F91\u8BE5\u6587\u4EF6\uFF0C\u586B\u5199\u4F60\u7684 API Key\uFF1A
261
+
262
+ \`\`\`bash
263
+ # Embedding API \u914D\u7F6E\uFF08\u5FC5\u9700\uFF09
264
+ EMBEDDINGS_API_KEY=your-api-key-here # \u2190 \u66FF\u6362\u4E3A\u4F60\u7684 API Key
265
+
266
+ # Reranker \u914D\u7F6E\uFF08\u5FC5\u9700\uFF09
267
+ RERANK_API_KEY=your-api-key-here # \u2190 \u66FF\u6362\u4E3A\u4F60\u7684 API Key
268
+ \`\`\`
269
+
270
+ \u4FDD\u5B58\u6587\u4EF6\u540E\u91CD\u65B0\u8C03\u7528\u6B64\u5DE5\u5177\u5373\u53EF\u3002
271
+ `;
272
+ return {
273
+ content: [
274
+ {
275
+ type: "text",
276
+ text
277
+ }
278
+ ]
279
+ };
280
+ }
281
+
282
+ export {
283
+ codebaseRetrievalSchema,
284
+ handleCodebaseRetrieval
285
+ };
286
+ //# sourceMappingURL=chunk-EZG4H4MN.js.map