@hsingjui/contextweaver 0.0.1
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.md +380 -0
- package/dist/SearchService-YLOUJF4S.js +1496 -0
- package/dist/chunk-34YZ2U3O.js +1177 -0
- package/dist/chunk-5SRSUMKW.js +612 -0
- package/dist/chunk-5TV4JNTE.js +258 -0
- package/dist/chunk-6C2D5Y4R.js +798 -0
- package/dist/chunk-PN7DP6XL.js +158 -0
- package/dist/codebaseRetrieval-RDCNIUDM.js +10 -0
- package/dist/config-IEL3M4V5.js +18 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +130 -0
- package/dist/scanner-66CLKCSZ.js +9 -0
- package/dist/server-2SAFEAEY.js +131 -0
- package/package.json +59 -0
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import {
|
|
2
|
+
generateProjectId,
|
|
3
|
+
logger
|
|
4
|
+
} from "./chunk-5SRSUMKW.js";
|
|
5
|
+
|
|
6
|
+
// src/mcp/tools/codebaseRetrieval.ts
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
import fs from "fs";
|
|
9
|
+
import path from "path";
|
|
10
|
+
import os from "os";
|
|
11
|
+
var codebaseRetrievalSchema = z.object({
|
|
12
|
+
repo_path: z.string().describe("The absolute file system path to the repository root. (e.g., '/Users/dev/my-project')"),
|
|
13
|
+
information_request: z.string().describe("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')"),
|
|
14
|
+
technical_terms: z.array(z.string()).optional().describe("HARD FILTERS. Precise identifiers to narrow down results. Only use symbols KNOWN to exist to avoid false negatives.")
|
|
15
|
+
});
|
|
16
|
+
var ZEN_CONFIG_OVERRIDE = {
|
|
17
|
+
// E1: 邻居扩展 - 前后看 2 个 chunk,保证代码块完整性
|
|
18
|
+
neighborHops: 2,
|
|
19
|
+
// E2: 面包屑补全 - 必须开启,保证能看到当前方法所属的 Class/Function 定义
|
|
20
|
+
breadcrumbExpandLimit: 3,
|
|
21
|
+
// E3: Import 扩展 - 强制关闭!
|
|
22
|
+
// 理由:跨文件是 Agent 的决策,不要预加载,防止 Token 爆炸
|
|
23
|
+
importFilesPerSeed: 0,
|
|
24
|
+
chunksPerImportFile: 0
|
|
25
|
+
};
|
|
26
|
+
var BASE_DIR = path.join(os.homedir(), ".contextweaver");
|
|
27
|
+
async function ensureDefaultEnvFile() {
|
|
28
|
+
const configDir = BASE_DIR;
|
|
29
|
+
const envFile = path.join(configDir, ".env");
|
|
30
|
+
if (fs.existsSync(envFile)) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
if (!fs.existsSync(configDir)) {
|
|
34
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
35
|
+
logger.info({ configDir }, "\u521B\u5EFA\u914D\u7F6E\u76EE\u5F55");
|
|
36
|
+
}
|
|
37
|
+
const defaultEnvContent = `# ContextWeaver \u793A\u4F8B\u73AF\u5883\u53D8\u91CF\u914D\u7F6E\u6587\u4EF6
|
|
38
|
+
|
|
39
|
+
# Embedding API \u914D\u7F6E\uFF08\u5FC5\u9700\uFF09
|
|
40
|
+
EMBEDDINGS_API_KEY=your-api-key-here
|
|
41
|
+
EMBEDDINGS_BASE_URL=https://api.siliconflow.cn/v1/embeddings
|
|
42
|
+
EMBEDDINGS_MODEL=BAAI/bge-m3
|
|
43
|
+
EMBEDDINGS_MAX_CONCURRENCY=10
|
|
44
|
+
EMBEDDINGS_DIMENSIONS=1024
|
|
45
|
+
|
|
46
|
+
# Reranker \u914D\u7F6E\uFF08\u5FC5\u9700\uFF09
|
|
47
|
+
RERANK_API_KEY=your-api-key-here
|
|
48
|
+
RERANK_BASE_URL=https://api.siliconflow.cn/v1/rerank
|
|
49
|
+
RERANK_MODEL=BAAI/bge-reranker-v2-m3
|
|
50
|
+
RERANK_TOP_N=20
|
|
51
|
+
|
|
52
|
+
# \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
|
|
53
|
+
# IGNORE_PATTERNS=.venv,node_modules
|
|
54
|
+
`;
|
|
55
|
+
fs.writeFileSync(envFile, defaultEnvContent);
|
|
56
|
+
logger.info({ envFile }, "\u5DF2\u521B\u5EFA\u9ED8\u8BA4 .env \u914D\u7F6E\u6587\u4EF6");
|
|
57
|
+
}
|
|
58
|
+
function isProjectIndexed(projectId) {
|
|
59
|
+
const dbPath = path.join(BASE_DIR, projectId, "index.db");
|
|
60
|
+
return fs.existsSync(dbPath);
|
|
61
|
+
}
|
|
62
|
+
async function ensureIndexed(repoPath, projectId) {
|
|
63
|
+
const wasIndexed = isProjectIndexed(projectId);
|
|
64
|
+
const { scan } = await import("./scanner-66CLKCSZ.js");
|
|
65
|
+
if (!wasIndexed) {
|
|
66
|
+
logger.info({ repoPath, projectId: projectId.slice(0, 10) }, "\u4EE3\u7801\u5E93\u672A\u521D\u59CB\u5316\uFF0C\u5F00\u59CB\u9996\u6B21\u7D22\u5F15...");
|
|
67
|
+
} else {
|
|
68
|
+
logger.debug({ projectId: projectId.slice(0, 10) }, "\u6267\u884C\u589E\u91CF\u7D22\u5F15...");
|
|
69
|
+
}
|
|
70
|
+
const startTime = Date.now();
|
|
71
|
+
const stats = await scan(repoPath, { vectorIndex: true });
|
|
72
|
+
const elapsed = Date.now() - startTime;
|
|
73
|
+
logger.info({
|
|
74
|
+
projectId: projectId.slice(0, 10),
|
|
75
|
+
isFirstTime: !wasIndexed,
|
|
76
|
+
totalFiles: stats.totalFiles,
|
|
77
|
+
added: stats.added,
|
|
78
|
+
modified: stats.modified,
|
|
79
|
+
deleted: stats.deleted,
|
|
80
|
+
vectorIndex: stats.vectorIndex,
|
|
81
|
+
elapsedMs: elapsed
|
|
82
|
+
}, "\u7D22\u5F15\u5B8C\u6210");
|
|
83
|
+
}
|
|
84
|
+
async function handleCodebaseRetrieval(args, configOverride = ZEN_CONFIG_OVERRIDE) {
|
|
85
|
+
const { repo_path, information_request, technical_terms } = args;
|
|
86
|
+
logger.info({
|
|
87
|
+
repo_path,
|
|
88
|
+
information_request,
|
|
89
|
+
technical_terms
|
|
90
|
+
}, "MCP codebase-retrieval \u8C03\u7528\u5F00\u59CB");
|
|
91
|
+
const { checkEmbeddingEnv, checkRerankerEnv } = await import("./config-IEL3M4V5.js");
|
|
92
|
+
const embeddingCheck = checkEmbeddingEnv();
|
|
93
|
+
const rerankerCheck = checkRerankerEnv();
|
|
94
|
+
const allMissingVars = [...embeddingCheck.missingVars, ...rerankerCheck.missingVars];
|
|
95
|
+
if (allMissingVars.length > 0) {
|
|
96
|
+
logger.warn({ missingVars: allMissingVars }, "MCP \u73AF\u5883\u53D8\u91CF\u672A\u914D\u7F6E");
|
|
97
|
+
await ensureDefaultEnvFile();
|
|
98
|
+
return formatEnvMissingResponse(allMissingVars);
|
|
99
|
+
}
|
|
100
|
+
const projectId = generateProjectId(repo_path);
|
|
101
|
+
await ensureIndexed(repo_path, projectId);
|
|
102
|
+
const query = [
|
|
103
|
+
information_request,
|
|
104
|
+
...technical_terms || []
|
|
105
|
+
].filter(Boolean).join(" ");
|
|
106
|
+
logger.info({
|
|
107
|
+
projectId: projectId.slice(0, 10),
|
|
108
|
+
query,
|
|
109
|
+
zenConfig: configOverride
|
|
110
|
+
}, "MCP \u67E5\u8BE2\u6784\u5EFA");
|
|
111
|
+
const { SearchService } = await import("./SearchService-YLOUJF4S.js");
|
|
112
|
+
const service = new SearchService(projectId, repo_path, configOverride);
|
|
113
|
+
await service.init();
|
|
114
|
+
logger.debug("SearchService \u521D\u59CB\u5316\u5B8C\u6210");
|
|
115
|
+
const contextPack = await service.buildContextPack(query);
|
|
116
|
+
if (contextPack.seeds.length > 0) {
|
|
117
|
+
logger.info({
|
|
118
|
+
seeds: contextPack.seeds.map((s) => ({
|
|
119
|
+
file: s.filePath,
|
|
120
|
+
chunk: s.chunkIndex,
|
|
121
|
+
score: s.score.toFixed(4),
|
|
122
|
+
source: s.source
|
|
123
|
+
}))
|
|
124
|
+
}, "MCP \u641C\u7D22 seeds");
|
|
125
|
+
} else {
|
|
126
|
+
logger.warn("MCP \u641C\u7D22\u65E0 seeds \u547D\u4E2D");
|
|
127
|
+
}
|
|
128
|
+
if (contextPack.expanded.length > 0) {
|
|
129
|
+
logger.debug({
|
|
130
|
+
expandedCount: contextPack.expanded.length,
|
|
131
|
+
expanded: contextPack.expanded.slice(0, 5).map((e) => ({
|
|
132
|
+
file: e.filePath,
|
|
133
|
+
chunk: e.chunkIndex,
|
|
134
|
+
score: e.score.toFixed(4)
|
|
135
|
+
}))
|
|
136
|
+
}, "MCP \u6269\u5C55\u7ED3\u679C (\u524D5)");
|
|
137
|
+
}
|
|
138
|
+
logger.info({
|
|
139
|
+
seedCount: contextPack.seeds.length,
|
|
140
|
+
expandedCount: contextPack.expanded.length,
|
|
141
|
+
fileCount: contextPack.files.length,
|
|
142
|
+
totalSegments: contextPack.files.reduce((acc, f) => acc + f.segments.length, 0),
|
|
143
|
+
files: contextPack.files.map((f) => ({
|
|
144
|
+
path: f.filePath,
|
|
145
|
+
segments: f.segments.length,
|
|
146
|
+
lines: f.segments.map((s) => `L${s.startLine}-${s.endLine}`)
|
|
147
|
+
})),
|
|
148
|
+
timingMs: contextPack.debug?.timingMs
|
|
149
|
+
}, "MCP codebase-retrieval \u5B8C\u6210");
|
|
150
|
+
return formatMcpResponse(contextPack, information_request);
|
|
151
|
+
}
|
|
152
|
+
function formatMcpResponse(pack, originalRequest) {
|
|
153
|
+
const { files, seeds } = pack;
|
|
154
|
+
const fileBlocks = files.map((file) => {
|
|
155
|
+
const segments = file.segments.map((seg) => formatSegment(seg)).join("\n\n");
|
|
156
|
+
return segments;
|
|
157
|
+
}).join("\n\n---\n\n");
|
|
158
|
+
const summary = [
|
|
159
|
+
`Found ${seeds.length} relevant code blocks for: "${originalRequest}"`,
|
|
160
|
+
`Files: ${files.length}`,
|
|
161
|
+
`Total segments: ${files.reduce((acc, f) => acc + f.segments.length, 0)}`
|
|
162
|
+
].join(" | ");
|
|
163
|
+
const text = `${summary}
|
|
164
|
+
|
|
165
|
+
${fileBlocks}`;
|
|
166
|
+
return {
|
|
167
|
+
content: [
|
|
168
|
+
{
|
|
169
|
+
type: "text",
|
|
170
|
+
text
|
|
171
|
+
}
|
|
172
|
+
]
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
function formatSegment(seg) {
|
|
176
|
+
const lang = detectLanguage(seg.filePath);
|
|
177
|
+
const header = `## ${seg.filePath} (L${seg.startLine}-${seg.endLine})`;
|
|
178
|
+
const breadcrumb = seg.breadcrumb ? `> ${seg.breadcrumb}` : "";
|
|
179
|
+
const code = "```" + lang + "\n" + seg.text + "\n```";
|
|
180
|
+
return [header, breadcrumb, code].filter(Boolean).join("\n");
|
|
181
|
+
}
|
|
182
|
+
function detectLanguage(filePath) {
|
|
183
|
+
const ext = filePath.split(".").pop()?.toLowerCase() || "";
|
|
184
|
+
const langMap = {
|
|
185
|
+
ts: "typescript",
|
|
186
|
+
tsx: "typescript",
|
|
187
|
+
js: "javascript",
|
|
188
|
+
jsx: "javascript",
|
|
189
|
+
py: "python",
|
|
190
|
+
rs: "rust",
|
|
191
|
+
go: "go",
|
|
192
|
+
java: "java",
|
|
193
|
+
c: "c",
|
|
194
|
+
cpp: "cpp",
|
|
195
|
+
h: "c",
|
|
196
|
+
hpp: "cpp",
|
|
197
|
+
cs: "csharp",
|
|
198
|
+
rb: "ruby",
|
|
199
|
+
php: "php",
|
|
200
|
+
swift: "swift",
|
|
201
|
+
kt: "kotlin",
|
|
202
|
+
scala: "scala",
|
|
203
|
+
sql: "sql",
|
|
204
|
+
sh: "bash",
|
|
205
|
+
bash: "bash",
|
|
206
|
+
zsh: "bash",
|
|
207
|
+
json: "json",
|
|
208
|
+
yaml: "yaml",
|
|
209
|
+
yml: "yaml",
|
|
210
|
+
xml: "xml",
|
|
211
|
+
html: "html",
|
|
212
|
+
css: "css",
|
|
213
|
+
scss: "scss",
|
|
214
|
+
less: "less",
|
|
215
|
+
md: "markdown",
|
|
216
|
+
toml: "toml"
|
|
217
|
+
};
|
|
218
|
+
return langMap[ext] || ext || "plaintext";
|
|
219
|
+
}
|
|
220
|
+
function formatEnvMissingResponse(missingVars) {
|
|
221
|
+
const configPath = "~/.contextweaver/.env";
|
|
222
|
+
const text = `## \u26A0\uFE0F \u914D\u7F6E\u7F3A\u5931
|
|
223
|
+
|
|
224
|
+
ContextWeaver \u9700\u8981\u914D\u7F6E Embedding API \u624D\u80FD\u5DE5\u4F5C\u3002
|
|
225
|
+
|
|
226
|
+
### \u7F3A\u5931\u7684\u73AF\u5883\u53D8\u91CF
|
|
227
|
+
${missingVars.map((v) => `- \`${v}\``).join("\n")}
|
|
228
|
+
|
|
229
|
+
### \u914D\u7F6E\u6B65\u9AA4
|
|
230
|
+
|
|
231
|
+
\u5DF2\u81EA\u52A8\u521B\u5EFA\u914D\u7F6E\u6587\u4EF6\uFF1A\`${configPath}\`
|
|
232
|
+
|
|
233
|
+
\u8BF7\u7F16\u8F91\u8BE5\u6587\u4EF6\uFF0C\u586B\u5199\u4F60\u7684 API Key\uFF1A
|
|
234
|
+
|
|
235
|
+
\`\`\`bash
|
|
236
|
+
# Embedding API \u914D\u7F6E\uFF08\u5FC5\u9700\uFF09
|
|
237
|
+
EMBEDDINGS_API_KEY=your-api-key-here # \u2190 \u66FF\u6362\u4E3A\u4F60\u7684 API Key
|
|
238
|
+
|
|
239
|
+
# Reranker \u914D\u7F6E\uFF08\u5FC5\u9700\uFF09
|
|
240
|
+
RERANK_API_KEY=your-api-key-here # \u2190 \u66FF\u6362\u4E3A\u4F60\u7684 API Key
|
|
241
|
+
\`\`\`
|
|
242
|
+
|
|
243
|
+
\u4FDD\u5B58\u6587\u4EF6\u540E\u91CD\u65B0\u8C03\u7528\u6B64\u5DE5\u5177\u5373\u53EF\u3002
|
|
244
|
+
`;
|
|
245
|
+
return {
|
|
246
|
+
content: [
|
|
247
|
+
{
|
|
248
|
+
type: "text",
|
|
249
|
+
text
|
|
250
|
+
}
|
|
251
|
+
]
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export {
|
|
256
|
+
codebaseRetrievalSchema,
|
|
257
|
+
handleCodebaseRetrieval
|
|
258
|
+
};
|