@chiway/contextweaver 1.4.0 → 1.5.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 +1 -1
- package/README.md +482 -196
- package/README.zh-CN.md +669 -0
- package/dist/{SearchService-OS7CYHNJ.js → SearchService-WVD6THR3.js} +116 -74
- package/dist/chunk-2EVCLNYN.js +223 -0
- package/dist/{chunk-ZOMGPIU6.js → chunk-3BNHQV5W.js} +1 -5
- package/dist/chunk-BFCIZ52F.js +102 -0
- package/dist/chunk-H4MGLXXF.js +115 -0
- package/dist/{lock-FL54LIQL.js → chunk-HHYPQA3X.js} +1 -1
- package/dist/chunk-IZ6IUHNN.js +77 -0
- package/dist/chunk-LB42CZEB.js +18 -0
- package/dist/chunk-MN6BQJDB.js +85 -0
- package/dist/{chunk-EMSMLPMK.js → chunk-ORYIVY7D.js} +10 -117
- package/dist/{chunk-RGJSXUFS.js → chunk-PPLFJGO3.js} +60 -0
- package/dist/chunk-TPM6YP43.js +38 -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-YMQWNIQI.js +143 -0
- package/dist/{chunk-X7PAYQMT.js → chunk-YSQI5IRI.js} +125 -5
- package/dist/{codebaseRetrieval-3Z4CRA7X.js → codebaseRetrieval-4BFIM7PU.js} +5 -2
- package/dist/{db-PMVM7557.js → db-GBCLP4GG.js} +15 -1
- package/dist/findReferences-EBYR3VNL.js +16 -0
- package/dist/getSymbolDefinition-ZQK65FPN.js +17 -0
- package/dist/index.js +244 -41
- package/dist/listFiles-W7C5UYOP.js +14 -0
- package/dist/loadConfig-XTVT2OWW.js +9 -0
- package/dist/lock-HNKQ6X5B.js +8 -0
- package/dist/scanner-OVMAMQSQ.js +13 -0
- package/dist/server-ZIJIRVWH.js +347 -0
- package/dist/stats-AGKUCJQI.js +12 -0
- package/dist/{vectorStore-HPQZOVWF.js → vectorStore-4ODCERRO.js} +1 -1
- package/package.json +15 -23
- package/dist/scanner-2XGJWYHR.js +0 -11
- package/dist/server-XK6EINRV.js +0 -146
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getDefaultEnvFileContent
|
|
3
|
+
} from "./chunk-TPM6YP43.js";
|
|
4
|
+
import {
|
|
5
|
+
logger
|
|
6
|
+
} from "./chunk-JVKVSTQ3.js";
|
|
7
|
+
|
|
8
|
+
// src/mcp/tools/shared.ts
|
|
9
|
+
import fs from "fs";
|
|
10
|
+
import os from "os";
|
|
11
|
+
import path from "path";
|
|
12
|
+
var BASE_DIR = path.join(os.homedir(), ".contextweaver");
|
|
13
|
+
var INDEX_LOCK_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
14
|
+
function isProjectIndexed(projectId) {
|
|
15
|
+
const dbPath = path.join(BASE_DIR, projectId, "index.db");
|
|
16
|
+
return fs.existsSync(dbPath);
|
|
17
|
+
}
|
|
18
|
+
async function ensureDefaultEnvFile() {
|
|
19
|
+
const configDir = BASE_DIR;
|
|
20
|
+
const envFile = path.join(configDir, ".env");
|
|
21
|
+
if (fs.existsSync(envFile)) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
if (!fs.existsSync(configDir)) {
|
|
25
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
26
|
+
logger.info({ configDir }, "\u521B\u5EFA\u914D\u7F6E\u76EE\u5F55");
|
|
27
|
+
}
|
|
28
|
+
fs.writeFileSync(envFile, getDefaultEnvFileContent());
|
|
29
|
+
logger.info({ envFile }, "\u5DF2\u521B\u5EFA\u9ED8\u8BA4 .env \u914D\u7F6E\u6587\u4EF6");
|
|
30
|
+
}
|
|
31
|
+
async function ensureIndexed(repoPath, projectId, options = {}) {
|
|
32
|
+
const { onProgress, vectorIndex = true } = options;
|
|
33
|
+
const { withLock } = await import("./lock-HNKQ6X5B.js");
|
|
34
|
+
const { scan } = await import("./scanner-OVMAMQSQ.js");
|
|
35
|
+
await withLock(
|
|
36
|
+
projectId,
|
|
37
|
+
"index",
|
|
38
|
+
async () => {
|
|
39
|
+
const wasIndexed = isProjectIndexed(projectId);
|
|
40
|
+
if (!wasIndexed) {
|
|
41
|
+
logger.info(
|
|
42
|
+
{ repoPath, projectId: projectId.slice(0, 10), vectorIndex },
|
|
43
|
+
"\u4EE3\u7801\u5E93\u672A\u521D\u59CB\u5316\uFF0C\u5F00\u59CB\u9996\u6B21\u7D22\u5F15..."
|
|
44
|
+
);
|
|
45
|
+
onProgress?.(0, 100, "\u4EE3\u7801\u5E93\u672A\u7D22\u5F15\uFF0C\u5F00\u59CB\u9996\u6B21\u7D22\u5F15...");
|
|
46
|
+
} else {
|
|
47
|
+
logger.debug({ projectId: projectId.slice(0, 10), vectorIndex }, "\u6267\u884C\u589E\u91CF\u7D22\u5F15...");
|
|
48
|
+
}
|
|
49
|
+
const startTime = Date.now();
|
|
50
|
+
const stats = await scan(repoPath, { vectorIndex, onProgress });
|
|
51
|
+
const elapsed = Date.now() - startTime;
|
|
52
|
+
logger.info(
|
|
53
|
+
{
|
|
54
|
+
projectId: projectId.slice(0, 10),
|
|
55
|
+
isFirstTime: !wasIndexed,
|
|
56
|
+
totalFiles: stats.totalFiles,
|
|
57
|
+
added: stats.added,
|
|
58
|
+
modified: stats.modified,
|
|
59
|
+
deleted: stats.deleted,
|
|
60
|
+
vectorIndex: stats.vectorIndex,
|
|
61
|
+
elapsedMs: elapsed
|
|
62
|
+
},
|
|
63
|
+
"\u7D22\u5F15\u5B8C\u6210"
|
|
64
|
+
);
|
|
65
|
+
},
|
|
66
|
+
INDEX_LOCK_TIMEOUT_MS
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
function formatTextResponse(text) {
|
|
70
|
+
return {
|
|
71
|
+
content: [
|
|
72
|
+
{
|
|
73
|
+
type: "text",
|
|
74
|
+
text
|
|
75
|
+
}
|
|
76
|
+
]
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function formatEnvMissingResponse(missingVars) {
|
|
80
|
+
const configPath = "~/.contextweaver/.env";
|
|
81
|
+
const text = `## \u26A0\uFE0F \u914D\u7F6E\u7F3A\u5931
|
|
82
|
+
|
|
83
|
+
ContextWeaver \u9700\u8981\u914D\u7F6E Embedding API \u624D\u80FD\u5DE5\u4F5C\u3002
|
|
84
|
+
|
|
85
|
+
### \u7F3A\u5931\u7684\u73AF\u5883\u53D8\u91CF
|
|
86
|
+
${missingVars.map((v) => `- \`${v}\``).join("\n")}
|
|
87
|
+
|
|
88
|
+
### \u914D\u7F6E\u6B65\u9AA4
|
|
89
|
+
|
|
90
|
+
\u5DF2\u81EA\u52A8\u521B\u5EFA\u914D\u7F6E\u6587\u4EF6\uFF1A\`${configPath}\`
|
|
91
|
+
|
|
92
|
+
\u8BF7\u7F16\u8F91\u8BE5\u6587\u4EF6\uFF0C\u586B\u5199\u4F60\u7684 API Key\uFF1A
|
|
93
|
+
|
|
94
|
+
\`\`\`bash
|
|
95
|
+
# Embedding API \u914D\u7F6E\uFF08\u5FC5\u9700\uFF09
|
|
96
|
+
EMBEDDINGS_API_KEY=your-api-key-here # \u2190 \u66FF\u6362\u4E3A\u4F60\u7684 API Key
|
|
97
|
+
|
|
98
|
+
# Reranker \u914D\u7F6E\uFF08\u5FC5\u9700\uFF09
|
|
99
|
+
RERANK_API_KEY=your-api-key-here # \u2190 \u66FF\u6362\u4E3A\u4F60\u7684 API Key
|
|
100
|
+
\`\`\`
|
|
101
|
+
|
|
102
|
+
\u4FDD\u5B58\u6587\u4EF6\u540E\u91CD\u65B0\u8C03\u7528\u6B64\u5DE5\u5177\u5373\u53EF\u3002
|
|
103
|
+
`;
|
|
104
|
+
return formatTextResponse(text);
|
|
105
|
+
}
|
|
106
|
+
async function checkEnvOrRespond(missingVars) {
|
|
107
|
+
await ensureDefaultEnvFile();
|
|
108
|
+
return formatEnvMissingResponse(missingVars);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export {
|
|
112
|
+
ensureIndexed,
|
|
113
|
+
formatTextResponse,
|
|
114
|
+
checkEnvOrRespond
|
|
115
|
+
};
|
|
@@ -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,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
|
+
};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ensureIndexed,
|
|
3
|
+
formatTextResponse
|
|
4
|
+
} from "./chunk-H4MGLXXF.js";
|
|
5
|
+
import {
|
|
6
|
+
generateProjectId,
|
|
7
|
+
initDb
|
|
8
|
+
} from "./chunk-PPLFJGO3.js";
|
|
9
|
+
import {
|
|
10
|
+
logger
|
|
11
|
+
} from "./chunk-JVKVSTQ3.js";
|
|
12
|
+
|
|
13
|
+
// src/mcp/tools/listFiles.ts
|
|
14
|
+
import { z } from "zod";
|
|
15
|
+
var listFilesSchema = z.object({
|
|
16
|
+
repo_path: z.string().describe(
|
|
17
|
+
"The absolute file system path to the repository root. (e.g., '/Users/dev/my-project')"
|
|
18
|
+
),
|
|
19
|
+
glob: z.string().optional().describe("Optional glob pattern to filter returned file paths."),
|
|
20
|
+
language: z.string().optional().describe("Optional language filter matched against files.language."),
|
|
21
|
+
max_results: z.number().int().positive().max(1e3).optional().describe("Maximum number of files to return. Defaults to 200.")
|
|
22
|
+
});
|
|
23
|
+
function escapeRegexCharacter(char) {
|
|
24
|
+
return char.replace(/[|\\{}()[\]^$+?.]/g, "\\$&");
|
|
25
|
+
}
|
|
26
|
+
function globToRegExp(glob) {
|
|
27
|
+
const normalized = glob.replace(/\\/g, "/");
|
|
28
|
+
let pattern = "^";
|
|
29
|
+
for (let i = 0; i < normalized.length; ) {
|
|
30
|
+
if (normalized.slice(i, i + 3) === "**/") {
|
|
31
|
+
pattern += "(?:.*/)?";
|
|
32
|
+
i += 3;
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (normalized.slice(i, i + 2) === "**") {
|
|
36
|
+
pattern += ".*";
|
|
37
|
+
i += 2;
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
const char = normalized[i];
|
|
41
|
+
if (char === "*") {
|
|
42
|
+
pattern += "[^/]*";
|
|
43
|
+
} else if (char === "?") {
|
|
44
|
+
pattern += "[^/]";
|
|
45
|
+
} else {
|
|
46
|
+
pattern += escapeRegexCharacter(char);
|
|
47
|
+
}
|
|
48
|
+
i += 1;
|
|
49
|
+
}
|
|
50
|
+
return new RegExp(`${pattern}$`);
|
|
51
|
+
}
|
|
52
|
+
function formatSize(size) {
|
|
53
|
+
if (size < 1024) {
|
|
54
|
+
return `${size}B`;
|
|
55
|
+
}
|
|
56
|
+
const kb = size / 1024;
|
|
57
|
+
if (kb < 1024) {
|
|
58
|
+
return `${kb.toFixed(1)}KB`;
|
|
59
|
+
}
|
|
60
|
+
return `${(kb / 1024).toFixed(1)}MB`;
|
|
61
|
+
}
|
|
62
|
+
async function handleListFiles(args, onProgress) {
|
|
63
|
+
const { repo_path, glob, language, max_results = 200 } = args;
|
|
64
|
+
const projectId = generateProjectId(repo_path);
|
|
65
|
+
logger.info({ repo_path, glob, language, max_results }, "MCP list-files \u8C03\u7528\u5F00\u59CB");
|
|
66
|
+
await ensureIndexed(repo_path, projectId, { onProgress, vectorIndex: false });
|
|
67
|
+
const db = initDb(projectId);
|
|
68
|
+
try {
|
|
69
|
+
const rows = language ? db.prepare("SELECT path, language, size FROM files WHERE language = ? ORDER BY path").all(language) : db.prepare("SELECT path, language, size FROM files ORDER BY path").all();
|
|
70
|
+
const matcher = glob ? globToRegExp(glob) : null;
|
|
71
|
+
const filtered = matcher ? rows.filter((row) => matcher.test(row.path)) : rows;
|
|
72
|
+
const limited = filtered.slice(0, max_results);
|
|
73
|
+
const body = limited.length > 0 ? limited.map((row) => `- ${row.path} (${row.language}, ${formatSize(row.size)})`).join("\n") : "No files matched the requested filters.";
|
|
74
|
+
return formatTextResponse(`Found ${limited.length} files
|
|
75
|
+
|
|
76
|
+
${body}`);
|
|
77
|
+
} finally {
|
|
78
|
+
db.close();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export {
|
|
83
|
+
listFilesSchema,
|
|
84
|
+
handleListFiles
|
|
85
|
+
};
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
+
import {
|
|
2
|
+
checkEnvOrRespond,
|
|
3
|
+
ensureIndexed
|
|
4
|
+
} from "./chunk-H4MGLXXF.js";
|
|
1
5
|
import {
|
|
2
6
|
generateProjectId
|
|
3
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-PPLFJGO3.js";
|
|
4
8
|
import {
|
|
5
9
|
logger
|
|
6
10
|
} from "./chunk-JVKVSTQ3.js";
|
|
7
11
|
|
|
8
12
|
// src/mcp/tools/codebaseRetrieval.ts
|
|
9
|
-
import fs from "fs";
|
|
10
|
-
import os from "os";
|
|
11
|
-
import path from "path";
|
|
12
13
|
import { z } from "zod";
|
|
13
14
|
var codebaseRetrievalSchema = z.object({
|
|
14
15
|
repo_path: z.string().describe(
|
|
@@ -21,80 +22,6 @@ var codebaseRetrievalSchema = z.object({
|
|
|
21
22
|
"HARD FILTERS. Precise identifiers to narrow down results. Only use symbols KNOWN to exist to avoid false negatives."
|
|
22
23
|
)
|
|
23
24
|
});
|
|
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-FL54LIQL.js");
|
|
63
|
-
const { scan } = await import("./scanner-2XGJWYHR.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
25
|
async function handleCodebaseRetrieval(args, onProgress) {
|
|
99
26
|
const { repo_path, information_request, technical_terms } = args;
|
|
100
27
|
logger.info(
|
|
@@ -111,11 +38,10 @@ async function handleCodebaseRetrieval(args, onProgress) {
|
|
|
111
38
|
const allMissingVars = [...embeddingCheck.missingVars, ...rerankerCheck.missingVars];
|
|
112
39
|
if (allMissingVars.length > 0) {
|
|
113
40
|
logger.warn({ missingVars: allMissingVars }, "MCP \u73AF\u5883\u53D8\u91CF\u672A\u914D\u7F6E");
|
|
114
|
-
await
|
|
115
|
-
return formatEnvMissingResponse(allMissingVars);
|
|
41
|
+
return await checkEnvOrRespond(allMissingVars);
|
|
116
42
|
}
|
|
117
43
|
const projectId = generateProjectId(repo_path);
|
|
118
|
-
await ensureIndexed(repo_path, projectId, onProgress);
|
|
44
|
+
await ensureIndexed(repo_path, projectId, { onProgress });
|
|
119
45
|
const query = [information_request, ...technical_terms || []].filter(Boolean).join(" ");
|
|
120
46
|
logger.info(
|
|
121
47
|
{
|
|
@@ -124,8 +50,9 @@ async function handleCodebaseRetrieval(args, onProgress) {
|
|
|
124
50
|
},
|
|
125
51
|
"MCP \u67E5\u8BE2\u6784\u5EFA"
|
|
126
52
|
);
|
|
127
|
-
const { SearchService } = await import("./SearchService-
|
|
128
|
-
const
|
|
53
|
+
const { SearchService } = await import("./SearchService-WVD6THR3.js");
|
|
54
|
+
const { getSearchConfigOverrides } = await import("./loadConfig-XTVT2OWW.js");
|
|
55
|
+
const service = new SearchService(projectId, repo_path, getSearchConfigOverrides());
|
|
129
56
|
await service.init();
|
|
130
57
|
logger.debug("SearchService \u521D\u59CB\u5316\u5B8C\u6210");
|
|
131
58
|
const contextPack = await service.buildContextPack(query);
|
|
@@ -244,40 +171,6 @@ function detectLanguage(filePath) {
|
|
|
244
171
|
};
|
|
245
172
|
return langMap[ext] || ext || "plaintext";
|
|
246
173
|
}
|
|
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
174
|
|
|
282
175
|
export {
|
|
283
176
|
codebaseRetrievalSchema,
|
|
@@ -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,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
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// src/search/ChunkContentLoader.ts
|
|
2
|
+
var ChunkContentLoader = class _ChunkContentLoader {
|
|
3
|
+
constructor(db) {
|
|
4
|
+
this.db = db;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* 生成 cache key
|
|
8
|
+
*/
|
|
9
|
+
static key(slice) {
|
|
10
|
+
return `${slice.filePath}#${slice.start_index}#${slice.end_index}`;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* 批量加载 chunk 正文
|
|
14
|
+
*
|
|
15
|
+
* @returns Map<key, code>,key 由 ChunkContentLoader.key 生成
|
|
16
|
+
*/
|
|
17
|
+
loadMany(slices) {
|
|
18
|
+
const result = /* @__PURE__ */ new Map();
|
|
19
|
+
if (slices.length === 0) return result;
|
|
20
|
+
const byPath = /* @__PURE__ */ new Map();
|
|
21
|
+
for (const s of slices) {
|
|
22
|
+
let arr = byPath.get(s.filePath);
|
|
23
|
+
if (!arr) {
|
|
24
|
+
arr = [];
|
|
25
|
+
byPath.set(s.filePath, arr);
|
|
26
|
+
}
|
|
27
|
+
arr.push(s);
|
|
28
|
+
}
|
|
29
|
+
const stmt = this.db.prepare("SELECT content FROM files WHERE path = ?");
|
|
30
|
+
for (const [path, spans] of byPath) {
|
|
31
|
+
const row = stmt.get(path);
|
|
32
|
+
const content = row?.content ?? null;
|
|
33
|
+
for (const s of spans) {
|
|
34
|
+
const k = _ChunkContentLoader.key(s);
|
|
35
|
+
if (content === null) {
|
|
36
|
+
result.set(k, "");
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
const safeStart = Math.max(0, Math.min(s.start_index, content.length));
|
|
40
|
+
const safeEnd = Math.max(safeStart, Math.min(s.end_index, content.length));
|
|
41
|
+
result.set(k, content.slice(safeStart, safeEnd));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return result;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* 加载单个 chunk 正文(便捷方法,不推荐在批量场景使用)
|
|
48
|
+
*/
|
|
49
|
+
loadOne(slice) {
|
|
50
|
+
const map = this.loadMany([slice]);
|
|
51
|
+
return map.get(_ChunkContentLoader.key(slice)) ?? "";
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export {
|
|
56
|
+
ChunkContentLoader
|
|
57
|
+
};
|