mcp-probe-kit 3.0.16 → 3.0.18
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/README.md +603 -399
- package/build/index.js +13 -1
- package/build/lib/__tests__/memory-client.unit.test.d.ts +1 -0
- package/build/lib/__tests__/memory-client.unit.test.js +83 -0
- package/build/lib/__tests__/memory-config.unit.test.d.ts +1 -0
- package/build/lib/__tests__/memory-config.unit.test.js +33 -0
- package/build/lib/cursor-history-client.d.ts +54 -0
- package/build/lib/cursor-history-client.js +240 -0
- package/build/lib/gitnexus-bridge.js +6 -8
- package/build/lib/memory-client.d.ts +61 -0
- package/build/lib/memory-client.js +293 -0
- package/build/lib/memory-config.d.ts +14 -0
- package/build/lib/memory-config.js +31 -0
- package/build/lib/memory-orchestration.d.ts +26 -0
- package/build/lib/memory-orchestration.js +65 -0
- package/build/lib/project-detector.js +6 -4
- package/build/lib/workspace-root.d.ts +12 -0
- package/build/lib/workspace-root.js +153 -0
- package/build/resources/ui-ux-data/metadata.json +1 -1
- package/build/schemas/code-analysis-tools.d.ts +1 -1
- package/build/schemas/code-analysis-tools.js +1 -1
- package/build/schemas/index.d.ts +198 -4
- package/build/schemas/index.js +2 -0
- package/build/schemas/memory-tools.d.ts +191 -0
- package/build/schemas/memory-tools.js +106 -0
- package/build/schemas/orchestration-tools.d.ts +3 -3
- package/build/schemas/orchestration-tools.js +3 -3
- package/build/schemas/ui-ux-schemas.d.ts +8 -0
- package/build/schemas/ui-ux-schemas.js +4 -0
- package/build/tools/__tests__/cursor-history.unit.test.d.ts +1 -0
- package/build/tools/__tests__/cursor-history.unit.test.js +87 -0
- package/build/tools/__tests__/memorize_asset.unit.test.d.ts +1 -0
- package/build/tools/__tests__/memorize_asset.unit.test.js +68 -0
- package/build/tools/code_insight.d.ts +20 -0
- package/build/tools/code_insight.js +15 -0
- package/build/tools/cursor_list_conversations.d.ts +7 -0
- package/build/tools/cursor_list_conversations.js +35 -0
- package/build/tools/cursor_read_conversation.d.ts +7 -0
- package/build/tools/cursor_read_conversation.js +36 -0
- package/build/tools/cursor_search_conversations.d.ts +7 -0
- package/build/tools/cursor_search_conversations.js +36 -0
- package/build/tools/index.d.ts +6 -0
- package/build/tools/index.js +7 -0
- package/build/tools/init_project_context.d.ts +20 -1
- package/build/tools/init_project_context.js +114 -99
- package/build/tools/memorize_asset.d.ts +7 -0
- package/build/tools/memorize_asset.js +66 -0
- package/build/tools/read_memory_asset.d.ts +7 -0
- package/build/tools/read_memory_asset.js +26 -0
- package/build/tools/scan_and_extract_patterns.d.ts +27 -0
- package/build/tools/scan_and_extract_patterns.js +346 -0
- package/build/tools/start_bugfix.d.ts +20 -0
- package/build/tools/start_bugfix.js +97 -69
- package/build/tools/start_feature.d.ts +20 -0
- package/build/tools/start_feature.js +61 -31
- package/build/tools/start_onboard.d.ts +20 -0
- package/build/tools/start_onboard.js +15 -0
- package/build/tools/start_ui.d.ts +20 -0
- package/build/tools/start_ui.js +66 -32
- package/docs/data/tools.js +472 -373
- package/docs/i18n/all-tools/en.json +38 -5
- package/docs/i18n/all-tools/ja.json +14 -4
- package/docs/i18n/all-tools/ko.json +13 -3
- package/docs/i18n/all-tools/zh-CN.json +38 -5
- package/docs/i18n/en.json +48 -10
- package/docs/i18n/ja.json +47 -9
- package/docs/i18n/ko.json +47 -9
- package/docs/i18n/zh-CN.json +48 -10
- package/docs/pages/all-tools.html +515 -515
- package/docs/pages/examples.html +661 -661
- package/docs/pages/getting-started.html +673 -582
- package/docs/pages/migration.html +291 -291
- package/package.json +83 -82
- package/docs/debug-i18n.html +0 -163
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
2
|
+
import { getMemoryConfig, isMemoryEnabled, isMemoryReadEnabled } from './memory-config.js';
|
|
3
|
+
function truncate(value, maxChars) {
|
|
4
|
+
if (value.length <= maxChars) {
|
|
5
|
+
return value;
|
|
6
|
+
}
|
|
7
|
+
return `${value.slice(0, Math.max(0, maxChars - 3))}...`;
|
|
8
|
+
}
|
|
9
|
+
function ensureArray(value) {
|
|
10
|
+
if (!Array.isArray(value)) {
|
|
11
|
+
return [];
|
|
12
|
+
}
|
|
13
|
+
return value.filter((item) => typeof item === 'string' && item.trim().length > 0);
|
|
14
|
+
}
|
|
15
|
+
function numberOr(value, fallback) {
|
|
16
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : fallback;
|
|
17
|
+
}
|
|
18
|
+
function buildEmbeddingInput(input) {
|
|
19
|
+
return [
|
|
20
|
+
`name: ${input.name}`,
|
|
21
|
+
`type: ${input.type}`,
|
|
22
|
+
`description: ${input.description}`,
|
|
23
|
+
`summary: ${input.summary}`,
|
|
24
|
+
input.tags.length > 0 ? `tags: ${input.tags.join(', ')}` : '',
|
|
25
|
+
input.usage ? `usage: ${input.usage}` : '',
|
|
26
|
+
`content:\n${input.content}`,
|
|
27
|
+
]
|
|
28
|
+
.filter(Boolean)
|
|
29
|
+
.join('\n\n');
|
|
30
|
+
}
|
|
31
|
+
export function normalizeContentForHash(content) {
|
|
32
|
+
return content
|
|
33
|
+
.replace(/\r\n?/g, '\n')
|
|
34
|
+
.split('\n')
|
|
35
|
+
.map((line) => line.replace(/[ \t]+$/g, ''))
|
|
36
|
+
.join('\n')
|
|
37
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
38
|
+
.trim();
|
|
39
|
+
}
|
|
40
|
+
export function sha256Hex(value) {
|
|
41
|
+
return createHash('sha256').update(value).digest('hex');
|
|
42
|
+
}
|
|
43
|
+
export function buildMemoryContentHashes(content) {
|
|
44
|
+
return {
|
|
45
|
+
contentHash: sha256Hex(content),
|
|
46
|
+
normalizedContentHash: sha256Hex(normalizeContentForHash(content)),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
export class MemoryClient {
|
|
50
|
+
config;
|
|
51
|
+
constructor(config = getMemoryConfig()) {
|
|
52
|
+
this.config = config;
|
|
53
|
+
}
|
|
54
|
+
isEnabled() {
|
|
55
|
+
return isMemoryEnabled(this.config);
|
|
56
|
+
}
|
|
57
|
+
isReadEnabled() {
|
|
58
|
+
return isMemoryReadEnabled(this.config);
|
|
59
|
+
}
|
|
60
|
+
buildHeaders(includeJson = true) {
|
|
61
|
+
const headers = {};
|
|
62
|
+
if (includeJson) {
|
|
63
|
+
headers['Content-Type'] = 'application/json';
|
|
64
|
+
}
|
|
65
|
+
if (this.config.qdrantApiKey) {
|
|
66
|
+
headers['api-key'] = this.config.qdrantApiKey;
|
|
67
|
+
}
|
|
68
|
+
return headers;
|
|
69
|
+
}
|
|
70
|
+
buildEmbeddingHeaders() {
|
|
71
|
+
const headers = {
|
|
72
|
+
'Content-Type': 'application/json',
|
|
73
|
+
};
|
|
74
|
+
if (this.config.embeddingApiKey) {
|
|
75
|
+
headers.Authorization = `Bearer ${this.config.embeddingApiKey}`;
|
|
76
|
+
}
|
|
77
|
+
return headers;
|
|
78
|
+
}
|
|
79
|
+
async requestJson(url, init) {
|
|
80
|
+
const response = await fetch(url, init);
|
|
81
|
+
if (!response.ok) {
|
|
82
|
+
throw new Error(`HTTP ${response.status}: ${await response.text()}`);
|
|
83
|
+
}
|
|
84
|
+
return response.json();
|
|
85
|
+
}
|
|
86
|
+
async ensureCollection(vectorSize) {
|
|
87
|
+
const url = `${this.config.qdrantUrl}/collections/${encodeURIComponent(this.config.qdrantCollection)}`;
|
|
88
|
+
const exists = await fetch(url, { headers: this.buildHeaders(false) });
|
|
89
|
+
if (exists.ok) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (exists.status !== 404) {
|
|
93
|
+
throw new Error(`Qdrant collection check failed: HTTP ${exists.status}`);
|
|
94
|
+
}
|
|
95
|
+
await this.requestJson(url, {
|
|
96
|
+
method: 'PUT',
|
|
97
|
+
headers: this.buildHeaders(),
|
|
98
|
+
body: JSON.stringify({
|
|
99
|
+
vectors: {
|
|
100
|
+
size: vectorSize,
|
|
101
|
+
distance: 'Cosine',
|
|
102
|
+
},
|
|
103
|
+
}),
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
async embed(text) {
|
|
107
|
+
if (!this.config.embeddingUrl) {
|
|
108
|
+
throw new Error('MEMORY_EMBEDDING_URL 未配置');
|
|
109
|
+
}
|
|
110
|
+
if (this.config.embeddingProvider === 'openai-compatible') {
|
|
111
|
+
const data = await this.requestJson(this.config.embeddingUrl, {
|
|
112
|
+
method: 'POST',
|
|
113
|
+
headers: this.buildEmbeddingHeaders(),
|
|
114
|
+
body: JSON.stringify({
|
|
115
|
+
model: this.config.embeddingModel,
|
|
116
|
+
input: text,
|
|
117
|
+
}),
|
|
118
|
+
});
|
|
119
|
+
const vector = data.data?.[0]?.embedding;
|
|
120
|
+
if (!vector || !Array.isArray(vector) || vector.length === 0) {
|
|
121
|
+
throw new Error('Embedding 服务未返回有效向量');
|
|
122
|
+
}
|
|
123
|
+
return vector;
|
|
124
|
+
}
|
|
125
|
+
const data = await this.requestJson(this.config.embeddingUrl, {
|
|
126
|
+
method: 'POST',
|
|
127
|
+
headers: this.buildEmbeddingHeaders(),
|
|
128
|
+
body: JSON.stringify({
|
|
129
|
+
model: this.config.embeddingModel,
|
|
130
|
+
prompt: text,
|
|
131
|
+
}),
|
|
132
|
+
});
|
|
133
|
+
if (!data.embedding || !Array.isArray(data.embedding) || data.embedding.length === 0) {
|
|
134
|
+
throw new Error('Embedding 服务未返回有效向量');
|
|
135
|
+
}
|
|
136
|
+
return data.embedding;
|
|
137
|
+
}
|
|
138
|
+
async findExistingAssetByNormalizedContentHash(normalizedContentHash) {
|
|
139
|
+
const data = await this.requestJson(`${this.config.qdrantUrl}/collections/${encodeURIComponent(this.config.qdrantCollection)}/points/scroll`, {
|
|
140
|
+
method: 'POST',
|
|
141
|
+
headers: this.buildHeaders(),
|
|
142
|
+
body: JSON.stringify({
|
|
143
|
+
limit: 1,
|
|
144
|
+
with_payload: true,
|
|
145
|
+
with_vectors: false,
|
|
146
|
+
filter: {
|
|
147
|
+
must: [
|
|
148
|
+
{
|
|
149
|
+
key: 'normalizedContentHash',
|
|
150
|
+
match: {
|
|
151
|
+
value: normalizedContentHash,
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
],
|
|
155
|
+
},
|
|
156
|
+
}),
|
|
157
|
+
});
|
|
158
|
+
const payload = data.result?.points?.[0]?.payload;
|
|
159
|
+
if (!payload) {
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
return {
|
|
163
|
+
id: String(payload.id || ''),
|
|
164
|
+
name: String(payload.name || ''),
|
|
165
|
+
type: String(payload.type || ''),
|
|
166
|
+
description: String(payload.description || ''),
|
|
167
|
+
summary: String(payload.summary || ''),
|
|
168
|
+
content: String(payload.content || ''),
|
|
169
|
+
tags: ensureArray(payload.tags),
|
|
170
|
+
confidence: numberOr(payload.confidence, 0.5),
|
|
171
|
+
sourceProject: typeof payload.sourceProject === 'string' ? payload.sourceProject : undefined,
|
|
172
|
+
sourcePath: typeof payload.sourcePath === 'string' ? payload.sourcePath : undefined,
|
|
173
|
+
usage: typeof payload.usage === 'string' ? payload.usage : undefined,
|
|
174
|
+
contentHash: typeof payload.contentHash === 'string' ? payload.contentHash : undefined,
|
|
175
|
+
normalizedContentHash: typeof payload.normalizedContentHash === 'string' ? payload.normalizedContentHash : undefined,
|
|
176
|
+
createdAt: String(payload.createdAt || ''),
|
|
177
|
+
updatedAt: String(payload.updatedAt || ''),
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
async upsertAsset(input) {
|
|
181
|
+
if (!this.isEnabled()) {
|
|
182
|
+
throw new Error('记忆系统未启用');
|
|
183
|
+
}
|
|
184
|
+
const now = new Date().toISOString();
|
|
185
|
+
const hashes = buildMemoryContentHashes(input.content);
|
|
186
|
+
const asset = {
|
|
187
|
+
id: randomUUID(),
|
|
188
|
+
name: input.name,
|
|
189
|
+
type: input.type,
|
|
190
|
+
description: input.description,
|
|
191
|
+
summary: input.summary,
|
|
192
|
+
content: input.content,
|
|
193
|
+
tags: ensureArray(input.tags),
|
|
194
|
+
confidence: numberOr(input.confidence, 0.5),
|
|
195
|
+
sourceProject: input.sourceProject,
|
|
196
|
+
sourcePath: input.sourcePath,
|
|
197
|
+
usage: input.usage,
|
|
198
|
+
contentHash: hashes.contentHash,
|
|
199
|
+
normalizedContentHash: hashes.normalizedContentHash,
|
|
200
|
+
createdAt: now,
|
|
201
|
+
updatedAt: now,
|
|
202
|
+
};
|
|
203
|
+
const vector = await this.embed(buildEmbeddingInput({
|
|
204
|
+
name: asset.name,
|
|
205
|
+
type: asset.type,
|
|
206
|
+
description: asset.description,
|
|
207
|
+
summary: asset.summary,
|
|
208
|
+
tags: asset.tags,
|
|
209
|
+
usage: asset.usage,
|
|
210
|
+
content: asset.content,
|
|
211
|
+
}));
|
|
212
|
+
await this.ensureCollection(vector.length);
|
|
213
|
+
const existing = await this.findExistingAssetByNormalizedContentHash(asset.normalizedContentHash || '');
|
|
214
|
+
if (existing) {
|
|
215
|
+
return existing;
|
|
216
|
+
}
|
|
217
|
+
await this.requestJson(`${this.config.qdrantUrl}/collections/${encodeURIComponent(this.config.qdrantCollection)}/points?wait=true`, {
|
|
218
|
+
method: 'PUT',
|
|
219
|
+
headers: this.buildHeaders(),
|
|
220
|
+
body: JSON.stringify({
|
|
221
|
+
points: [
|
|
222
|
+
{
|
|
223
|
+
id: asset.id,
|
|
224
|
+
vector,
|
|
225
|
+
payload: asset,
|
|
226
|
+
},
|
|
227
|
+
],
|
|
228
|
+
}),
|
|
229
|
+
});
|
|
230
|
+
return asset;
|
|
231
|
+
}
|
|
232
|
+
async search(query, limit = this.config.searchLimit) {
|
|
233
|
+
if (!this.isEnabled()) {
|
|
234
|
+
return [];
|
|
235
|
+
}
|
|
236
|
+
const vector = await this.embed(query);
|
|
237
|
+
const data = await this.requestJson(`${this.config.qdrantUrl}/collections/${encodeURIComponent(this.config.qdrantCollection)}/points/search`, {
|
|
238
|
+
method: 'POST',
|
|
239
|
+
headers: this.buildHeaders(),
|
|
240
|
+
body: JSON.stringify({
|
|
241
|
+
vector,
|
|
242
|
+
limit,
|
|
243
|
+
with_payload: true,
|
|
244
|
+
}),
|
|
245
|
+
});
|
|
246
|
+
return (data.result || []).map((point) => {
|
|
247
|
+
const payload = point.payload || {};
|
|
248
|
+
return {
|
|
249
|
+
id: String(point.id),
|
|
250
|
+
score: numberOr(point.score, 0),
|
|
251
|
+
name: String(payload.name || ''),
|
|
252
|
+
type: String(payload.type || ''),
|
|
253
|
+
description: String(payload.description || ''),
|
|
254
|
+
summary: truncate(String(payload.summary || ''), this.config.summaryMaxChars),
|
|
255
|
+
tags: ensureArray(payload.tags),
|
|
256
|
+
sourcePath: typeof payload.sourcePath === 'string' ? payload.sourcePath : undefined,
|
|
257
|
+
};
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
async getAsset(assetId) {
|
|
261
|
+
if (!this.isReadEnabled()) {
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
264
|
+
const data = await this.requestJson(`${this.config.qdrantUrl}/collections/${encodeURIComponent(this.config.qdrantCollection)}/points/${encodeURIComponent(assetId)}`, {
|
|
265
|
+
method: 'GET',
|
|
266
|
+
headers: this.buildHeaders(false),
|
|
267
|
+
});
|
|
268
|
+
const payload = data.result?.payload;
|
|
269
|
+
if (!payload) {
|
|
270
|
+
return null;
|
|
271
|
+
}
|
|
272
|
+
return {
|
|
273
|
+
id: String(payload.id || assetId),
|
|
274
|
+
name: String(payload.name || ''),
|
|
275
|
+
type: String(payload.type || ''),
|
|
276
|
+
description: String(payload.description || ''),
|
|
277
|
+
summary: String(payload.summary || ''),
|
|
278
|
+
content: String(payload.content || ''),
|
|
279
|
+
tags: ensureArray(payload.tags),
|
|
280
|
+
confidence: numberOr(payload.confidence, 0.5),
|
|
281
|
+
sourceProject: typeof payload.sourceProject === 'string' ? payload.sourceProject : undefined,
|
|
282
|
+
sourcePath: typeof payload.sourcePath === 'string' ? payload.sourcePath : undefined,
|
|
283
|
+
usage: typeof payload.usage === 'string' ? payload.usage : undefined,
|
|
284
|
+
contentHash: typeof payload.contentHash === 'string' ? payload.contentHash : undefined,
|
|
285
|
+
normalizedContentHash: typeof payload.normalizedContentHash === 'string' ? payload.normalizedContentHash : undefined,
|
|
286
|
+
createdAt: String(payload.createdAt || ''),
|
|
287
|
+
updatedAt: String(payload.updatedAt || ''),
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
export function createMemoryClient() {
|
|
292
|
+
return new MemoryClient();
|
|
293
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface MemoryConfig {
|
|
2
|
+
qdrantUrl: string;
|
|
3
|
+
qdrantApiKey: string;
|
|
4
|
+
qdrantCollection: string;
|
|
5
|
+
embeddingUrl: string;
|
|
6
|
+
embeddingApiKey: string;
|
|
7
|
+
embeddingModel: string;
|
|
8
|
+
embeddingProvider: 'ollama' | 'openai-compatible';
|
|
9
|
+
searchLimit: number;
|
|
10
|
+
summaryMaxChars: number;
|
|
11
|
+
}
|
|
12
|
+
export declare function getMemoryConfig(): MemoryConfig;
|
|
13
|
+
export declare function isMemoryEnabled(config?: MemoryConfig): boolean;
|
|
14
|
+
export declare function isMemoryReadEnabled(config?: MemoryConfig): boolean;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
function normalizeBaseUrl(value) {
|
|
2
|
+
return (value || '').trim().replace(/\/+$/, '');
|
|
3
|
+
}
|
|
4
|
+
function getNumberEnv(name, fallback) {
|
|
5
|
+
const raw = process.env[name]?.trim();
|
|
6
|
+
if (!raw) {
|
|
7
|
+
return fallback;
|
|
8
|
+
}
|
|
9
|
+
const parsed = Number(raw);
|
|
10
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
11
|
+
}
|
|
12
|
+
export function getMemoryConfig() {
|
|
13
|
+
const provider = (process.env.MEMORY_EMBEDDING_PROVIDER || 'ollama').trim().toLowerCase();
|
|
14
|
+
return {
|
|
15
|
+
qdrantUrl: normalizeBaseUrl(process.env.MEMORY_QDRANT_URL),
|
|
16
|
+
qdrantApiKey: (process.env.MEMORY_QDRANT_API_KEY || '').trim(),
|
|
17
|
+
qdrantCollection: (process.env.MEMORY_QDRANT_COLLECTION || 'mcp_probe_memory').trim(),
|
|
18
|
+
embeddingUrl: normalizeBaseUrl(process.env.MEMORY_EMBEDDING_URL),
|
|
19
|
+
embeddingApiKey: (process.env.MEMORY_EMBEDDING_API_KEY || '').trim(),
|
|
20
|
+
embeddingModel: (process.env.MEMORY_EMBEDDING_MODEL || 'nomic-embed-text').trim(),
|
|
21
|
+
embeddingProvider: provider === 'openai-compatible' ? 'openai-compatible' : 'ollama',
|
|
22
|
+
searchLimit: getNumberEnv('MEMORY_SEARCH_LIMIT', 3),
|
|
23
|
+
summaryMaxChars: getNumberEnv('MEMORY_SUMMARY_MAX_CHARS', 280),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
export function isMemoryEnabled(config = getMemoryConfig()) {
|
|
27
|
+
return Boolean(config.qdrantUrl && config.embeddingUrl && config.embeddingModel);
|
|
28
|
+
}
|
|
29
|
+
export function isMemoryReadEnabled(config = getMemoryConfig()) {
|
|
30
|
+
return Boolean(config.qdrantUrl);
|
|
31
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { MemorySearchResult } from './memory-client.js';
|
|
2
|
+
export interface MemoryInjectionContext {
|
|
3
|
+
enabled: boolean;
|
|
4
|
+
available: boolean;
|
|
5
|
+
degraded: boolean;
|
|
6
|
+
query: string;
|
|
7
|
+
results: MemorySearchResult[];
|
|
8
|
+
error?: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function loadMemoryInjectionContext(query: string): Promise<MemoryInjectionContext>;
|
|
11
|
+
export declare function renderMemoryGuideSection(context: MemoryInjectionContext): string;
|
|
12
|
+
export declare function buildMemoryPlanStep(): {
|
|
13
|
+
id: string;
|
|
14
|
+
tool: string;
|
|
15
|
+
when: string;
|
|
16
|
+
args: {
|
|
17
|
+
name: string;
|
|
18
|
+
type: string;
|
|
19
|
+
description: string;
|
|
20
|
+
summary: string;
|
|
21
|
+
content: string;
|
|
22
|
+
usage: string;
|
|
23
|
+
confidence: number;
|
|
24
|
+
};
|
|
25
|
+
outputs: never[];
|
|
26
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { createMemoryClient } from './memory-client.js';
|
|
2
|
+
export async function loadMemoryInjectionContext(query) {
|
|
3
|
+
const client = createMemoryClient();
|
|
4
|
+
if (!client.isEnabled()) {
|
|
5
|
+
return {
|
|
6
|
+
enabled: false,
|
|
7
|
+
available: false,
|
|
8
|
+
degraded: false,
|
|
9
|
+
query,
|
|
10
|
+
results: [],
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
try {
|
|
14
|
+
const results = await client.search(query);
|
|
15
|
+
return {
|
|
16
|
+
enabled: true,
|
|
17
|
+
available: true,
|
|
18
|
+
degraded: false,
|
|
19
|
+
query,
|
|
20
|
+
results,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
return {
|
|
25
|
+
enabled: true,
|
|
26
|
+
available: false,
|
|
27
|
+
degraded: true,
|
|
28
|
+
query,
|
|
29
|
+
results: [],
|
|
30
|
+
error: error instanceof Error ? error.message : String(error),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export function renderMemoryGuideSection(context) {
|
|
35
|
+
if (!context.enabled) {
|
|
36
|
+
return '';
|
|
37
|
+
}
|
|
38
|
+
if (!context.available) {
|
|
39
|
+
return `\n\n## 🧠 记忆系统\n- 状态: 已配置但本次检索降级\n- 原因: ${context.error || '未知错误'}\n- 处理: 忽略记忆注入,继续主流程\n`;
|
|
40
|
+
}
|
|
41
|
+
if (context.results.length === 0) {
|
|
42
|
+
return `\n\n## 🧠 记忆系统\n- 状态: 已启用\n- 检索结果: 未找到高相关历史资产\n- 处理: 继续主流程;若本次产出存在高价值通用资产,结束后调用 \`memorize_asset\` 沉淀\n`;
|
|
43
|
+
}
|
|
44
|
+
const items = context.results
|
|
45
|
+
.map((item, index) => `${index + 1}. ${item.name} [${item.type}] score=${item.score.toFixed(3)}\n - 摘要: ${item.summary}\n - 读取: read_memory_asset {\"asset_id\": \"${item.id}\"}${item.sourcePath ? `\n - 来源: ${item.sourcePath}` : ''}`)
|
|
46
|
+
.join('\n');
|
|
47
|
+
return `\n\n## 🧠 记忆系统\n- 状态: 已启用\n- 指令: 优先复用以下历史成功经验;如果某条相关,再用 \`read_memory_asset\` 拉取完整内容,不要直接重写\n- 检索结果:\n${items}\n`;
|
|
48
|
+
}
|
|
49
|
+
export function buildMemoryPlanStep() {
|
|
50
|
+
return {
|
|
51
|
+
id: 'memorize',
|
|
52
|
+
tool: 'memorize_asset',
|
|
53
|
+
when: '本次实现完成且确认存在可复用资产',
|
|
54
|
+
args: {
|
|
55
|
+
name: '[资产名称]',
|
|
56
|
+
type: 'code',
|
|
57
|
+
description: '[该资产解决了什么问题]',
|
|
58
|
+
summary: '[用于后续检索的简洁摘要]',
|
|
59
|
+
content: '[可复用代码或规范内容]',
|
|
60
|
+
usage: '[适用场景与限制]',
|
|
61
|
+
confidence: 0.7,
|
|
62
|
+
},
|
|
63
|
+
outputs: [],
|
|
64
|
+
};
|
|
65
|
+
}
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import * as fs from 'fs';
|
|
10
10
|
import * as path from 'path';
|
|
11
|
+
import { resolveWorkspaceRoot } from './workspace-root.js';
|
|
11
12
|
// 检测规则配置
|
|
12
13
|
const detectionRules = {
|
|
13
14
|
javascript: {
|
|
@@ -116,7 +117,8 @@ const detectionRules = {
|
|
|
116
117
|
/**
|
|
117
118
|
* 检测项目类型
|
|
118
119
|
*/
|
|
119
|
-
export function detectProjectType(projectRoot
|
|
120
|
+
export function detectProjectType(projectRoot) {
|
|
121
|
+
const resolvedProjectRoot = resolveWorkspaceRoot(projectRoot);
|
|
120
122
|
const indicators = [];
|
|
121
123
|
let detectedLanguage = 'unknown';
|
|
122
124
|
let detectedFramework;
|
|
@@ -126,7 +128,7 @@ export function detectProjectType(projectRoot = process.cwd()) {
|
|
|
126
128
|
for (const [language, rule] of Object.entries(detectionRules)) {
|
|
127
129
|
// 检查语言特征文件是否存在
|
|
128
130
|
const languageIndicator = rule.indicators.find(indicator => {
|
|
129
|
-
const filePath = path.join(
|
|
131
|
+
const filePath = path.join(resolvedProjectRoot, indicator);
|
|
130
132
|
return fs.existsSync(filePath);
|
|
131
133
|
});
|
|
132
134
|
if (languageIndicator) {
|
|
@@ -134,7 +136,7 @@ export function detectProjectType(projectRoot = process.cwd()) {
|
|
|
134
136
|
indicators.push(languageIndicator);
|
|
135
137
|
confidence = 50; // 基础置信度
|
|
136
138
|
// 检测框架
|
|
137
|
-
const frameworkResult = detectFramework(
|
|
139
|
+
const frameworkResult = detectFramework(resolvedProjectRoot, language, rule);
|
|
138
140
|
if (frameworkResult) {
|
|
139
141
|
detectedFramework = frameworkResult.framework;
|
|
140
142
|
detectedCategory = frameworkResult.category;
|
|
@@ -146,7 +148,7 @@ export function detectProjectType(projectRoot = process.cwd()) {
|
|
|
146
148
|
}
|
|
147
149
|
// 如果没有检测到语言,尝试通过文件扩展名判断
|
|
148
150
|
if (detectedLanguage === 'unknown') {
|
|
149
|
-
const languageByExtension = detectLanguageByExtension(
|
|
151
|
+
const languageByExtension = detectLanguageByExtension(resolvedProjectRoot);
|
|
150
152
|
if (languageByExtension) {
|
|
151
153
|
detectedLanguage = languageByExtension.language;
|
|
152
154
|
confidence = languageByExtension.confidence;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare function isLikelyProjectNamedRelativePath(inputPath?: string): boolean;
|
|
2
|
+
export declare function buildProjectRootRetryHint(inputPath?: string): {
|
|
3
|
+
preferred: {
|
|
4
|
+
project_root: string;
|
|
5
|
+
path: string;
|
|
6
|
+
};
|
|
7
|
+
fallback: {
|
|
8
|
+
project_root: string;
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
export declare function resolveWorkspaceRoot(explicitProjectRoot?: string): string;
|
|
12
|
+
export declare function toWorkspacePath(explicitProjectRoot?: string): string;
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
const WORKSPACE_ENV_KEYS = [
|
|
5
|
+
"MCP_PROJECT_ROOT",
|
|
6
|
+
"MCP_WORKSPACE_ROOT",
|
|
7
|
+
"CURSOR_WORKSPACE_ROOT",
|
|
8
|
+
"WORKSPACE_ROOT",
|
|
9
|
+
"PROJECT_ROOT",
|
|
10
|
+
"INIT_CWD",
|
|
11
|
+
"PWD",
|
|
12
|
+
];
|
|
13
|
+
const WORKSPACE_MARKERS = [
|
|
14
|
+
".git",
|
|
15
|
+
"package.json",
|
|
16
|
+
"pnpm-workspace.yaml",
|
|
17
|
+
"yarn.lock",
|
|
18
|
+
"package-lock.json",
|
|
19
|
+
"tsconfig.json",
|
|
20
|
+
"Cargo.toml",
|
|
21
|
+
"go.mod",
|
|
22
|
+
"pyproject.toml",
|
|
23
|
+
"requirements.txt",
|
|
24
|
+
".cursor",
|
|
25
|
+
".vscode",
|
|
26
|
+
".kiro",
|
|
27
|
+
"src",
|
|
28
|
+
"app",
|
|
29
|
+
"docs",
|
|
30
|
+
];
|
|
31
|
+
const COMMON_ROOT_DIRS = new Set([
|
|
32
|
+
"src",
|
|
33
|
+
"app",
|
|
34
|
+
"lib",
|
|
35
|
+
"test",
|
|
36
|
+
"tests",
|
|
37
|
+
"spec",
|
|
38
|
+
"specs",
|
|
39
|
+
"docs",
|
|
40
|
+
"scripts",
|
|
41
|
+
"packages",
|
|
42
|
+
"services",
|
|
43
|
+
"server",
|
|
44
|
+
"client",
|
|
45
|
+
"components",
|
|
46
|
+
"utils",
|
|
47
|
+
"bin",
|
|
48
|
+
"config",
|
|
49
|
+
"examples",
|
|
50
|
+
]);
|
|
51
|
+
function safeResolve(value) {
|
|
52
|
+
const trimmed = value.trim();
|
|
53
|
+
if (!trimmed) {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
try {
|
|
57
|
+
return path.resolve(trimmed);
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function isExistingDirectory(target) {
|
|
64
|
+
if (!target) {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
try {
|
|
68
|
+
return fs.existsSync(target) && fs.statSync(target).isDirectory();
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function getRuntimePackageRoot() {
|
|
75
|
+
const moduleDir = path.dirname(fileURLToPath(import.meta.url));
|
|
76
|
+
return path.resolve(moduleDir, "..", "..");
|
|
77
|
+
}
|
|
78
|
+
function looksLikeWorkspaceRoot(target) {
|
|
79
|
+
return WORKSPACE_MARKERS.some((marker) => fs.existsSync(path.join(target, marker)));
|
|
80
|
+
}
|
|
81
|
+
function findWorkspaceAncestor(start, packageRoot) {
|
|
82
|
+
if (!isExistingDirectory(start)) {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
let current = start;
|
|
86
|
+
while (true) {
|
|
87
|
+
if (current !== packageRoot && looksLikeWorkspaceRoot(current)) {
|
|
88
|
+
return current;
|
|
89
|
+
}
|
|
90
|
+
const parent = path.dirname(current);
|
|
91
|
+
if (parent === current) {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
current = parent;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
export function isLikelyProjectNamedRelativePath(inputPath) {
|
|
98
|
+
if (!inputPath || path.isAbsolute(inputPath)) {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
const normalized = inputPath.replace(/\\/g, "/").replace(/^\.\//, "").trim();
|
|
102
|
+
if (!normalized || normalized.startsWith("../")) {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
const segments = normalized.split("/").filter(Boolean);
|
|
106
|
+
if (segments.length < 2) {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
const [firstSegment, secondSegment] = segments;
|
|
110
|
+
if (COMMON_ROOT_DIRS.has(firstSegment.toLowerCase())) {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
if (!/^[a-z0-9._-]+$/i.test(firstSegment)) {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
return COMMON_ROOT_DIRS.has(secondSegment.toLowerCase()) || firstSegment.includes("-");
|
|
117
|
+
}
|
|
118
|
+
export function buildProjectRootRetryHint(inputPath) {
|
|
119
|
+
const normalized = (inputPath || "").replace(/\\/g, "/").trim();
|
|
120
|
+
const relativePath = normalized.split("/").slice(1).join("/") || "app/utils";
|
|
121
|
+
return {
|
|
122
|
+
preferred: {
|
|
123
|
+
project_root: "C:/path/to/your/project",
|
|
124
|
+
path: relativePath,
|
|
125
|
+
},
|
|
126
|
+
fallback: {
|
|
127
|
+
project_root: "C:/path/to/your/project",
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
export function resolveWorkspaceRoot(explicitProjectRoot) {
|
|
132
|
+
const explicit = safeResolve(explicitProjectRoot || "");
|
|
133
|
+
if (isExistingDirectory(explicit)) {
|
|
134
|
+
return explicit;
|
|
135
|
+
}
|
|
136
|
+
const packageRoot = getRuntimePackageRoot();
|
|
137
|
+
const cwd = safeResolve(process.cwd()) || packageRoot;
|
|
138
|
+
for (const key of WORKSPACE_ENV_KEYS) {
|
|
139
|
+
const candidate = safeResolve(process.env[key] || "");
|
|
140
|
+
const resolved = findWorkspaceAncestor(candidate, packageRoot);
|
|
141
|
+
if (resolved) {
|
|
142
|
+
return resolved;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
const cwdWorkspace = findWorkspaceAncestor(cwd, packageRoot);
|
|
146
|
+
if (cwdWorkspace) {
|
|
147
|
+
return cwdWorkspace;
|
|
148
|
+
}
|
|
149
|
+
return packageRoot;
|
|
150
|
+
}
|
|
151
|
+
export function toWorkspacePath(explicitProjectRoot) {
|
|
152
|
+
return resolveWorkspaceRoot(explicitProjectRoot).replace(/\\/g, "/");
|
|
153
|
+
}
|
|
@@ -43,7 +43,7 @@ export declare const codeAnalysisToolSchemas: readonly [{
|
|
|
43
43
|
};
|
|
44
44
|
readonly project_root: {
|
|
45
45
|
readonly type: "string";
|
|
46
|
-
readonly description: "
|
|
46
|
+
readonly description: "项目根目录绝对路径。建议显式传入;当调用里还包含相对路径参数时,应统一相对该项目根目录解析,避免依赖客户端 cwd。";
|
|
47
47
|
};
|
|
48
48
|
readonly goal: {
|
|
49
49
|
readonly type: "string";
|