mcp-probe-kit 3.0.18 → 3.0.21
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 +87 -55
- package/build/index.js +3 -1
- package/build/lib/__tests__/agents-md-template.unit.test.d.ts +1 -0
- package/build/lib/__tests__/agents-md-template.unit.test.js +27 -0
- package/build/lib/__tests__/memory-config.unit.test.js +9 -0
- package/build/lib/__tests__/memory-injection.unit.test.d.ts +1 -0
- package/build/lib/__tests__/memory-injection.unit.test.js +51 -0
- package/build/lib/__tests__/memory-orchestration.unit.test.d.ts +1 -0
- package/build/lib/__tests__/memory-orchestration.unit.test.js +84 -0
- package/build/lib/__tests__/memory-payload.unit.test.d.ts +1 -0
- package/build/lib/__tests__/memory-payload.unit.test.js +35 -0
- package/build/lib/__tests__/project-context-layout.unit.test.d.ts +1 -0
- package/build/lib/__tests__/project-context-layout.unit.test.js +80 -0
- package/build/lib/agents-md-template.d.ts +25 -0
- package/build/lib/agents-md-template.js +57 -0
- package/build/lib/memory-client.d.ts +8 -1
- package/build/lib/memory-client.js +53 -44
- package/build/lib/memory-config.d.ts +8 -0
- package/build/lib/memory-config.js +19 -0
- package/build/lib/memory-orchestration.d.ts +10 -3
- package/build/lib/memory-orchestration.js +146 -7
- package/build/lib/memory-payload.d.ts +21 -0
- package/build/lib/memory-payload.js +65 -0
- package/build/lib/merge-agents-md.d.ts +6 -0
- package/build/lib/merge-agents-md.js +51 -0
- package/build/lib/project-context-layout.d.ts +78 -0
- package/build/lib/project-context-layout.js +350 -0
- package/build/lib/workspace-root.js +6 -1
- package/build/resources/ui-ux-data/metadata.json +1 -1
- package/build/schemas/index.d.ts +62 -11
- package/build/schemas/memory-tools.d.ts +38 -9
- package/build/schemas/memory-tools.js +24 -9
- package/build/schemas/project-tools.d.ts +24 -2
- package/build/schemas/project-tools.js +24 -2
- package/build/tools/__tests__/code_insight.unit.test.js +3 -3
- package/build/tools/__tests__/init_project_context.unit.test.js +32 -21
- package/build/tools/__tests__/start_feature.unit.test.js +2 -1
- package/build/tools/code_insight.js +11 -9
- package/build/tools/index.d.ts +1 -0
- package/build/tools/index.js +1 -0
- package/build/tools/init_project_context.js +563 -506
- package/build/tools/memorize_asset.js +12 -0
- package/build/tools/scan_and_extract_patterns.js +7 -7
- package/build/tools/search_memory.d.ts +7 -0
- package/build/tools/search_memory.js +57 -0
- package/build/tools/start_bugfix.js +257 -251
- package/build/tools/start_feature.js +140 -134
- package/build/tools/start_ui.js +405 -405
- package/docs/.mcp-probe/layout.json +11 -0
- package/docs/data/tools.js +18 -0
- package/docs/i18n/all-tools/en.json +6 -1
- package/docs/i18n/all-tools/ja.json +2 -1
- package/docs/i18n/all-tools/ko.json +2 -1
- package/docs/i18n/all-tools/zh-CN.json +7 -2
- package/docs/i18n/en.json +38 -7
- package/docs/i18n/ja.json +9 -2
- package/docs/i18n/ko.json +9 -2
- package/docs/i18n/zh-CN.json +40 -9
- package/docs/memory-local-setup.md +314 -0
- package/docs/memory-local-setup.zh-CN.md +283 -0
- package/docs/pages/getting-started.html +252 -33
- package/package.json +2 -2
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { relativeLink } from "./project-context-layout.js";
|
|
2
|
+
function link(layout, targetRel) {
|
|
3
|
+
return relativeLink(layout.indexPath, targetRel);
|
|
4
|
+
}
|
|
5
|
+
function memorySection(locale) {
|
|
6
|
+
if (locale === "zh-CN") {
|
|
7
|
+
return `
|
|
8
|
+
记忆(需 MEMORY_QDRANT_URL 等已配置):
|
|
9
|
+
- 检索:\`start_*\` 命中后**自动注入**历史经验全文;中途补查可用 \`search_memory\`;单条精读仍可用 \`read_memory_asset\`
|
|
10
|
+
- 沉淀:跨仓库共享**勿填** source_project/source_path;路径写进 content;summary 写检索关键词
|
|
11
|
+
- Bug 修完验证通过 → **必须** \`memorize_asset\` type=\`bugfix\` tags=\`bugfix,root-cause\`(content 含【现象】【根因】【修复】【验证】)
|
|
12
|
+
- 功能/UI 可复用产出 → \`memorize_asset\` type=\`pattern\`/\`component\``;
|
|
13
|
+
}
|
|
14
|
+
return `
|
|
15
|
+
Memory (requires MEMORY_* env):
|
|
16
|
+
- Search: \`start_*\` auto-injects full memory hits; use \`search_memory\` mid-task; \`read_memory_asset\` for a specific id
|
|
17
|
+
- Store: do NOT use source_project/source_path for cross-repo pools; put paths in content; write keyword-rich summary
|
|
18
|
+
- After verified bugfix → MUST \`memorize_asset\` type=\`bugfix\` (sections: symptom, root cause, fix, verification)
|
|
19
|
+
- Reusable feature/UI → \`memorize_asset\` type=\`pattern\`/\`component\``;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Ultra-compact AGENTS.md body (inside mcp-probe block).
|
|
23
|
+
*/
|
|
24
|
+
export function generateAgentsMdInner(input) {
|
|
25
|
+
const { layout, locale } = input;
|
|
26
|
+
const graph = link(layout, layout.latestMarkdownPath);
|
|
27
|
+
const ctxIndex = link(layout, layout.legacyIndexPath);
|
|
28
|
+
if (locale === "zh-CN") {
|
|
29
|
+
return `## MCP(必须先调)
|
|
30
|
+
需已配置 mcp-probe-kit。\`start_*\` 若返回 delegated plan,逐步执行完再结束。
|
|
31
|
+
|
|
32
|
+
- 新功能 → \`start_feature\`(会先搜记忆)
|
|
33
|
+
- Bug → \`start_bugfix\`(会先搜记忆)
|
|
34
|
+
- UI → \`start_ui\`(会先搜记忆)
|
|
35
|
+
- 不熟代码 / 影响面 → \`code_insight\`(context / impact / auto)
|
|
36
|
+
- 缺上下文 → \`init_project_context\`
|
|
37
|
+
- 提交 → \`gencommit\`
|
|
38
|
+
|
|
39
|
+
上下文:写代码前先读 [project-context](${ctxIndex})(链到 \`${layout.modularDir}/\` 各文档)
|
|
40
|
+
图谱:大改前读 [latest](${graph});过期 \`code_insight\` mode=auto save_to_docs=true${memorySection(locale)}`;
|
|
41
|
+
}
|
|
42
|
+
return `## MCP (call first)
|
|
43
|
+
Requires mcp-probe-kit. Complete every \`start_*\` delegated plan step before done.
|
|
44
|
+
|
|
45
|
+
- Feature → \`start_feature\` (searches memory first)
|
|
46
|
+
- Bug → \`start_bugfix\` (searches memory first)
|
|
47
|
+
- UI → \`start_ui\` (searches memory first)
|
|
48
|
+
- Unfamiliar code / impact → \`code_insight\` (context / impact / auto)
|
|
49
|
+
- Missing context → \`init_project_context\`
|
|
50
|
+
- Commit → \`gencommit\`
|
|
51
|
+
|
|
52
|
+
Context: before coding read [project-context](${ctxIndex}) (links to \`${layout.modularDir}/\`)
|
|
53
|
+
Graph: read [latest](${graph}) before large changes; refresh \`code_insight\` mode=auto save_to_docs=true${memorySection(locale)}`;
|
|
54
|
+
}
|
|
55
|
+
export function generateAgentsMdTemplate(input) {
|
|
56
|
+
return generateAgentsMdInner(input);
|
|
57
|
+
}
|
|
@@ -24,8 +24,15 @@ export interface MemorySearchResult {
|
|
|
24
24
|
description: string;
|
|
25
25
|
summary: string;
|
|
26
26
|
tags: string[];
|
|
27
|
+
sourceProject?: string;
|
|
27
28
|
sourcePath?: string;
|
|
28
29
|
}
|
|
30
|
+
export interface MemorySearchOptions {
|
|
31
|
+
limit?: number;
|
|
32
|
+
minScore?: number;
|
|
33
|
+
preferTypes?: string[];
|
|
34
|
+
preferTags?: string[];
|
|
35
|
+
}
|
|
29
36
|
export declare function normalizeContentForHash(content: string): string;
|
|
30
37
|
export declare function sha256Hex(value: string): string;
|
|
31
38
|
export declare function buildMemoryContentHashes(content: string): {
|
|
@@ -55,7 +62,7 @@ export declare class MemoryClient {
|
|
|
55
62
|
sourcePath?: string;
|
|
56
63
|
usage?: string;
|
|
57
64
|
}): Promise<MemoryAsset>;
|
|
58
|
-
search(query: string,
|
|
65
|
+
search(query: string, options?: MemorySearchOptions): Promise<MemorySearchResult[]>;
|
|
59
66
|
getAsset(assetId: string): Promise<MemoryAsset | null>;
|
|
60
67
|
}
|
|
61
68
|
export declare function createMemoryClient(): MemoryClient;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createHash, randomUUID } from 'node:crypto';
|
|
2
2
|
import { getMemoryConfig, isMemoryEnabled, isMemoryReadEnabled } from './memory-config.js';
|
|
3
|
+
import { normalizeMemoryPayload, payloadToMemoryFields } from './memory-payload.js';
|
|
3
4
|
function truncate(value, maxChars) {
|
|
4
5
|
if (value.length <= maxChars) {
|
|
5
6
|
return value;
|
|
@@ -155,26 +156,14 @@ export class MemoryClient {
|
|
|
155
156
|
},
|
|
156
157
|
}),
|
|
157
158
|
});
|
|
158
|
-
const
|
|
159
|
-
if (!
|
|
159
|
+
const rawPayload = data.result?.points?.[0]?.payload;
|
|
160
|
+
if (!rawPayload) {
|
|
160
161
|
return null;
|
|
161
162
|
}
|
|
163
|
+
const fields = payloadToMemoryFields(rawPayload);
|
|
162
164
|
return {
|
|
163
|
-
|
|
164
|
-
|
|
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 || ''),
|
|
165
|
+
...fields,
|
|
166
|
+
id: fields.id || String(rawPayload.id || ''),
|
|
178
167
|
};
|
|
179
168
|
}
|
|
180
169
|
async upsertAsset(input) {
|
|
@@ -229,33 +218,41 @@ export class MemoryClient {
|
|
|
229
218
|
});
|
|
230
219
|
return asset;
|
|
231
220
|
}
|
|
232
|
-
async search(query,
|
|
221
|
+
async search(query, options = {}) {
|
|
233
222
|
if (!this.isEnabled()) {
|
|
234
223
|
return [];
|
|
235
224
|
}
|
|
225
|
+
const limit = options.limit ?? this.config.searchLimit;
|
|
226
|
+
const minScore = options.minScore ?? this.config.searchMinScore;
|
|
227
|
+
const fetchLimit = Math.min(Math.max(limit * 4, limit), 50);
|
|
236
228
|
const vector = await this.embed(query);
|
|
237
229
|
const data = await this.requestJson(`${this.config.qdrantUrl}/collections/${encodeURIComponent(this.config.qdrantCollection)}/points/search`, {
|
|
238
230
|
method: 'POST',
|
|
239
231
|
headers: this.buildHeaders(),
|
|
240
232
|
body: JSON.stringify({
|
|
241
233
|
vector,
|
|
242
|
-
limit,
|
|
234
|
+
limit: fetchLimit,
|
|
243
235
|
with_payload: true,
|
|
244
236
|
}),
|
|
245
237
|
});
|
|
246
|
-
|
|
247
|
-
const payload = point.payload || {};
|
|
238
|
+
const mapped = (data.result || []).map((point) => {
|
|
239
|
+
const payload = normalizeMemoryPayload(point.payload || {});
|
|
240
|
+
const fields = payloadToMemoryFields(payload);
|
|
248
241
|
return {
|
|
249
242
|
id: String(point.id),
|
|
250
243
|
score: numberOr(point.score, 0),
|
|
251
|
-
name:
|
|
252
|
-
type:
|
|
253
|
-
description:
|
|
254
|
-
summary: truncate(
|
|
255
|
-
tags:
|
|
256
|
-
|
|
244
|
+
name: fields.name,
|
|
245
|
+
type: fields.type,
|
|
246
|
+
description: fields.description,
|
|
247
|
+
summary: truncate(fields.summary, this.config.summaryMaxChars),
|
|
248
|
+
tags: fields.tags,
|
|
249
|
+
sourceProject: fields.sourceProject,
|
|
250
|
+
sourcePath: fields.sourcePath,
|
|
257
251
|
};
|
|
258
252
|
});
|
|
253
|
+
const ranked = rankSearchResults(mapped, options.preferTypes, options.preferTags);
|
|
254
|
+
const filtered = minScore > 0 ? ranked.filter((item) => item.score >= minScore) : ranked;
|
|
255
|
+
return filtered.slice(0, limit);
|
|
259
256
|
}
|
|
260
257
|
async getAsset(assetId) {
|
|
261
258
|
if (!this.isReadEnabled()) {
|
|
@@ -265,29 +262,41 @@ export class MemoryClient {
|
|
|
265
262
|
method: 'GET',
|
|
266
263
|
headers: this.buildHeaders(false),
|
|
267
264
|
});
|
|
268
|
-
const
|
|
269
|
-
if (!
|
|
265
|
+
const rawPayload = data.result?.payload;
|
|
266
|
+
if (!rawPayload) {
|
|
270
267
|
return null;
|
|
271
268
|
}
|
|
269
|
+
const fields = payloadToMemoryFields(rawPayload);
|
|
272
270
|
return {
|
|
273
|
-
|
|
274
|
-
|
|
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 || ''),
|
|
271
|
+
...fields,
|
|
272
|
+
id: fields.id || assetId,
|
|
288
273
|
};
|
|
289
274
|
}
|
|
290
275
|
}
|
|
276
|
+
function rankSearchResults(results, preferTypes = [], preferTags = []) {
|
|
277
|
+
if (preferTypes.length === 0 && preferTags.length === 0) {
|
|
278
|
+
return [...results].sort((a, b) => b.score - a.score);
|
|
279
|
+
}
|
|
280
|
+
const preferredTypes = new Set(preferTypes.map((item) => item.toLowerCase()));
|
|
281
|
+
const preferredTags = new Set(preferTags.map((item) => item.toLowerCase()));
|
|
282
|
+
const scoreBoost = (item) => {
|
|
283
|
+
let boost = 0;
|
|
284
|
+
if (preferredTypes.has(item.type.toLowerCase())) {
|
|
285
|
+
boost += 2;
|
|
286
|
+
}
|
|
287
|
+
if (item.tags.some((tag) => preferredTags.has(tag.toLowerCase()))) {
|
|
288
|
+
boost += 1;
|
|
289
|
+
}
|
|
290
|
+
return boost;
|
|
291
|
+
};
|
|
292
|
+
return [...results].sort((a, b) => {
|
|
293
|
+
const boostDiff = scoreBoost(b) - scoreBoost(a);
|
|
294
|
+
if (boostDiff !== 0) {
|
|
295
|
+
return boostDiff;
|
|
296
|
+
}
|
|
297
|
+
return b.score - a.score;
|
|
298
|
+
});
|
|
299
|
+
}
|
|
291
300
|
export function createMemoryClient() {
|
|
292
301
|
return new MemoryClient();
|
|
293
302
|
}
|
|
@@ -8,6 +8,14 @@ export interface MemoryConfig {
|
|
|
8
8
|
embeddingProvider: 'ollama' | 'openai-compatible';
|
|
9
9
|
searchLimit: number;
|
|
10
10
|
summaryMaxChars: number;
|
|
11
|
+
/** Show sourcePath in search injection (default false for cross-repo pools) */
|
|
12
|
+
searchShowSource: boolean;
|
|
13
|
+
/** Minimum cosine score to include in search results; 0 = disabled */
|
|
14
|
+
searchMinScore: number;
|
|
15
|
+
/** When set, show sourcePath only if payload.sourceProject matches */
|
|
16
|
+
repoId: string;
|
|
17
|
+
/** Max chars of each asset content injected into start_* guides */
|
|
18
|
+
injectionContentMaxChars: number;
|
|
11
19
|
}
|
|
12
20
|
export declare function getMemoryConfig(): MemoryConfig;
|
|
13
21
|
export declare function isMemoryEnabled(config?: MemoryConfig): boolean;
|
|
@@ -9,6 +9,21 @@ function getNumberEnv(name, fallback) {
|
|
|
9
9
|
const parsed = Number(raw);
|
|
10
10
|
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
11
11
|
}
|
|
12
|
+
function getOptionalNumberEnv(name, fallback) {
|
|
13
|
+
const raw = process.env[name]?.trim();
|
|
14
|
+
if (!raw) {
|
|
15
|
+
return fallback;
|
|
16
|
+
}
|
|
17
|
+
const parsed = Number(raw);
|
|
18
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : fallback;
|
|
19
|
+
}
|
|
20
|
+
function getBooleanEnv(name, fallback) {
|
|
21
|
+
const raw = process.env[name]?.trim().toLowerCase();
|
|
22
|
+
if (!raw) {
|
|
23
|
+
return fallback;
|
|
24
|
+
}
|
|
25
|
+
return raw === '1' || raw === 'true' || raw === 'yes' || raw === 'on';
|
|
26
|
+
}
|
|
12
27
|
export function getMemoryConfig() {
|
|
13
28
|
const provider = (process.env.MEMORY_EMBEDDING_PROVIDER || 'ollama').trim().toLowerCase();
|
|
14
29
|
return {
|
|
@@ -21,6 +36,10 @@ export function getMemoryConfig() {
|
|
|
21
36
|
embeddingProvider: provider === 'openai-compatible' ? 'openai-compatible' : 'ollama',
|
|
22
37
|
searchLimit: getNumberEnv('MEMORY_SEARCH_LIMIT', 3),
|
|
23
38
|
summaryMaxChars: getNumberEnv('MEMORY_SUMMARY_MAX_CHARS', 280),
|
|
39
|
+
searchShowSource: getBooleanEnv('MEMORY_SEARCH_SHOW_SOURCE', false),
|
|
40
|
+
searchMinScore: getOptionalNumberEnv('MEMORY_SEARCH_MIN_SCORE', 0),
|
|
41
|
+
repoId: (process.env.MEMORY_REPO_ID || '').trim(),
|
|
42
|
+
injectionContentMaxChars: getNumberEnv('MEMORY_INJECTION_CONTENT_MAX_CHARS', 1500),
|
|
24
43
|
};
|
|
25
44
|
}
|
|
26
45
|
export function isMemoryEnabled(config = getMemoryConfig()) {
|
|
@@ -1,15 +1,21 @@
|
|
|
1
|
-
import type { MemorySearchResult } from './memory-client.js';
|
|
1
|
+
import type { MemoryAsset, MemorySearchResult } from './memory-client.js';
|
|
2
|
+
import { type MemoryConfig } from './memory-config.js';
|
|
3
|
+
export type MemoryPlanKind = 'feature' | 'bugfix' | 'ui' | 'default';
|
|
2
4
|
export interface MemoryInjectionContext {
|
|
3
5
|
enabled: boolean;
|
|
4
6
|
available: boolean;
|
|
5
7
|
degraded: boolean;
|
|
6
8
|
query: string;
|
|
7
9
|
results: MemorySearchResult[];
|
|
10
|
+
/** Full assets keyed by search hit id (auto-loaded for start_* injection) */
|
|
11
|
+
assetsById: Record<string, MemoryAsset>;
|
|
8
12
|
error?: string;
|
|
9
13
|
}
|
|
10
|
-
export declare function
|
|
14
|
+
export declare function truncateInjectionText(value: string, maxChars: number): string;
|
|
15
|
+
export declare function loadMemoryInjectionContext(query: string, kind?: MemoryPlanKind): Promise<MemoryInjectionContext>;
|
|
16
|
+
export declare function shouldShowSourceInSearch(item: MemorySearchResult, config?: MemoryConfig): boolean;
|
|
11
17
|
export declare function renderMemoryGuideSection(context: MemoryInjectionContext): string;
|
|
12
|
-
export declare function buildMemoryPlanStep(): {
|
|
18
|
+
export declare function buildMemoryPlanStep(kind?: MemoryPlanKind): {
|
|
13
19
|
id: string;
|
|
14
20
|
tool: string;
|
|
15
21
|
when: string;
|
|
@@ -20,6 +26,7 @@ export declare function buildMemoryPlanStep(): {
|
|
|
20
26
|
summary: string;
|
|
21
27
|
content: string;
|
|
22
28
|
usage: string;
|
|
29
|
+
tags: string[];
|
|
23
30
|
confidence: number;
|
|
24
31
|
};
|
|
25
32
|
outputs: never[];
|
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
import { createMemoryClient } from './memory-client.js';
|
|
2
|
-
|
|
2
|
+
import { getMemoryConfig } from './memory-config.js';
|
|
3
|
+
function kindSearchPreferences(kind) {
|
|
4
|
+
switch (kind) {
|
|
5
|
+
case 'bugfix':
|
|
6
|
+
return { preferTypes: ['bugfix'], preferTags: ['bugfix', 'root-cause'] };
|
|
7
|
+
case 'ui':
|
|
8
|
+
return { preferTypes: ['component', 'pattern'], preferTags: ['ui', 'pattern'] };
|
|
9
|
+
case 'feature':
|
|
10
|
+
return { preferTypes: ['pattern', 'code'], preferTags: ['feature', 'pattern'] };
|
|
11
|
+
default:
|
|
12
|
+
return { preferTypes: [], preferTags: [] };
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export function truncateInjectionText(value, maxChars) {
|
|
16
|
+
if (value.length <= maxChars) {
|
|
17
|
+
return value;
|
|
18
|
+
}
|
|
19
|
+
return `${value.slice(0, Math.max(0, maxChars - 3))}...`;
|
|
20
|
+
}
|
|
21
|
+
async function loadFullAssets(results) {
|
|
22
|
+
const client = createMemoryClient();
|
|
23
|
+
if (!client.isReadEnabled() || results.length === 0) {
|
|
24
|
+
return {};
|
|
25
|
+
}
|
|
26
|
+
const entries = await Promise.all(results.map(async (item) => {
|
|
27
|
+
const asset = await client.getAsset(item.id);
|
|
28
|
+
return asset ? [item.id, asset] : null;
|
|
29
|
+
}));
|
|
30
|
+
return Object.fromEntries(entries.filter((entry) => entry !== null));
|
|
31
|
+
}
|
|
32
|
+
export async function loadMemoryInjectionContext(query, kind = 'default') {
|
|
3
33
|
const client = createMemoryClient();
|
|
4
34
|
if (!client.isEnabled()) {
|
|
5
35
|
return {
|
|
@@ -8,16 +38,23 @@ export async function loadMemoryInjectionContext(query) {
|
|
|
8
38
|
degraded: false,
|
|
9
39
|
query,
|
|
10
40
|
results: [],
|
|
41
|
+
assetsById: {},
|
|
11
42
|
};
|
|
12
43
|
}
|
|
13
44
|
try {
|
|
14
|
-
const
|
|
45
|
+
const prefs = kindSearchPreferences(kind);
|
|
46
|
+
const results = await client.search(query, {
|
|
47
|
+
preferTypes: prefs.preferTypes,
|
|
48
|
+
preferTags: prefs.preferTags,
|
|
49
|
+
});
|
|
50
|
+
const assetsById = await loadFullAssets(results);
|
|
15
51
|
return {
|
|
16
52
|
enabled: true,
|
|
17
53
|
available: true,
|
|
18
54
|
degraded: false,
|
|
19
55
|
query,
|
|
20
56
|
results,
|
|
57
|
+
assetsById,
|
|
21
58
|
};
|
|
22
59
|
}
|
|
23
60
|
catch (error) {
|
|
@@ -27,11 +64,57 @@ export async function loadMemoryInjectionContext(query) {
|
|
|
27
64
|
degraded: true,
|
|
28
65
|
query,
|
|
29
66
|
results: [],
|
|
67
|
+
assetsById: {},
|
|
30
68
|
error: error instanceof Error ? error.message : String(error),
|
|
31
69
|
};
|
|
32
70
|
}
|
|
33
71
|
}
|
|
72
|
+
function formatMemoryResultLabel(item) {
|
|
73
|
+
const kind = item.type === 'bugfix' || item.tags.includes('bugfix')
|
|
74
|
+
? '历史 Bug 修复'
|
|
75
|
+
: item.type === 'pattern' || item.type === 'component'
|
|
76
|
+
? '可复用模式'
|
|
77
|
+
: '历史资产';
|
|
78
|
+
return `${item.name} [${item.type}] (${kind})`;
|
|
79
|
+
}
|
|
80
|
+
export function shouldShowSourceInSearch(item, config = getMemoryConfig()) {
|
|
81
|
+
if (config.searchShowSource) {
|
|
82
|
+
return Boolean(item.sourcePath);
|
|
83
|
+
}
|
|
84
|
+
if (!config.repoId || !item.sourceProject || !item.sourcePath) {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
return item.sourceProject === config.repoId;
|
|
88
|
+
}
|
|
89
|
+
function formatSourceHint(item, config) {
|
|
90
|
+
if (!shouldShowSourceInSearch(item, config)) {
|
|
91
|
+
return '';
|
|
92
|
+
}
|
|
93
|
+
return `\n - 来源: ${item.sourcePath}`;
|
|
94
|
+
}
|
|
95
|
+
function formatAssetBody(asset, config) {
|
|
96
|
+
const lines = [
|
|
97
|
+
`### ${asset.name}`,
|
|
98
|
+
`- asset_id: ${asset.id}`,
|
|
99
|
+
asset.description ? `- 描述: ${asset.description}` : '',
|
|
100
|
+
asset.usage ? `- 适用: ${asset.usage}` : '',
|
|
101
|
+
asset.tags.length > 0 ? `- 标签: ${asset.tags.join(', ')}` : '',
|
|
102
|
+
'',
|
|
103
|
+
truncateInjectionText(asset.content, config.injectionContentMaxChars),
|
|
104
|
+
].filter(Boolean);
|
|
105
|
+
return lines.join('\n');
|
|
106
|
+
}
|
|
107
|
+
function formatResultBlock(item, index, context, config) {
|
|
108
|
+
const label = formatMemoryResultLabel(item);
|
|
109
|
+
const asset = context.assetsById[item.id];
|
|
110
|
+
const header = `${index + 1}. ${label} score=${item.score.toFixed(3)}\n - 摘要: ${item.summary}${formatSourceHint(item, config)}`;
|
|
111
|
+
if (asset?.content) {
|
|
112
|
+
return `${header}\n\n${formatAssetBody(asset, config)}\n`;
|
|
113
|
+
}
|
|
114
|
+
return `${header}\n - 全文加载失败,可手动: read_memory_asset {"asset_id": "${item.id}"}\n`;
|
|
115
|
+
}
|
|
34
116
|
export function renderMemoryGuideSection(context) {
|
|
117
|
+
const config = getMemoryConfig();
|
|
35
118
|
if (!context.enabled) {
|
|
36
119
|
return '';
|
|
37
120
|
}
|
|
@@ -39,25 +122,81 @@ export function renderMemoryGuideSection(context) {
|
|
|
39
122
|
return `\n\n## 🧠 记忆系统\n- 状态: 已配置但本次检索降级\n- 原因: ${context.error || '未知错误'}\n- 处理: 忽略记忆注入,继续主流程\n`;
|
|
40
123
|
}
|
|
41
124
|
if (context.results.length === 0) {
|
|
42
|
-
return `\n\n## 🧠 记忆系统\n- 状态: 已启用\n- 检索结果:
|
|
125
|
+
return `\n\n## 🧠 记忆系统\n- 状态: 已启用\n- 检索结果: 未找到高相关记录(含历史 Bug 修复与可复用模式)\n- 处理: 继续主流程;Bug 修复验证通过后必须 \`memorize_asset\` 沉淀;功能/UI 有可复用产出再沉淀\n`;
|
|
43
126
|
}
|
|
127
|
+
const loadedCount = context.results.filter((item) => context.assetsById[item.id]?.content).length;
|
|
44
128
|
const items = context.results
|
|
45
|
-
.map((item, index) =>
|
|
129
|
+
.map((item, index) => formatResultBlock(item, index, context, config))
|
|
46
130
|
.join('\n');
|
|
47
|
-
return `\n\n## 🧠 记忆系统\n- 状态: 已启用\n- 指令:
|
|
131
|
+
return `\n\n## 🧠 记忆系统\n- 状态: 已启用\n- 指令: 下列为已自动加载的历史经验全文(${loadedCount}/${context.results.length} 条);开干前直接复用,无需再调 \`read_memory_asset\`\n- 检索结果:\n${items}`;
|
|
48
132
|
}
|
|
49
|
-
export function buildMemoryPlanStep() {
|
|
133
|
+
export function buildMemoryPlanStep(kind = 'default') {
|
|
134
|
+
if (kind === 'bugfix') {
|
|
135
|
+
return {
|
|
136
|
+
id: 'memorize-bugfix',
|
|
137
|
+
tool: 'memorize_asset',
|
|
138
|
+
when: 'Bug 已修复且验证通过后(必须沉淀,便于下次同类问题检索)',
|
|
139
|
+
args: {
|
|
140
|
+
name: '[问题简述,如 登录超时-Redis连接池]',
|
|
141
|
+
type: 'bugfix',
|
|
142
|
+
description: '[现象、报错信息、复现条件]',
|
|
143
|
+
summary: '[检索用:关键词 + 根因 + 修复要点,一句话]',
|
|
144
|
+
content: '【现象】...\n【根因】...\n【修复】具体改动与关键代码/配置\n【验证】如何确认已修好',
|
|
145
|
+
usage: '[再次遇到何种症状时可参考]',
|
|
146
|
+
tags: ['bugfix', 'root-cause'],
|
|
147
|
+
confidence: 0.85,
|
|
148
|
+
},
|
|
149
|
+
outputs: [],
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
if (kind === 'ui') {
|
|
153
|
+
return {
|
|
154
|
+
id: 'memorize-ui',
|
|
155
|
+
tool: 'memorize_asset',
|
|
156
|
+
when: 'UI 实现完成且存在可复用组件/布局/交互模式',
|
|
157
|
+
args: {
|
|
158
|
+
name: '[UI 资产名称]',
|
|
159
|
+
type: 'component',
|
|
160
|
+
description: '[该 UI 模式解决什么问题]',
|
|
161
|
+
summary: '[检索用摘要]',
|
|
162
|
+
content: '[组件结构、样式约定或可复用片段]',
|
|
163
|
+
usage: '[适用页面/场景]',
|
|
164
|
+
tags: ['ui', 'pattern'],
|
|
165
|
+
confidence: 0.75,
|
|
166
|
+
},
|
|
167
|
+
outputs: [],
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
if (kind === 'feature') {
|
|
171
|
+
return {
|
|
172
|
+
id: 'memorize-feature',
|
|
173
|
+
tool: 'memorize_asset',
|
|
174
|
+
when: '功能完成且存在可复用实现/规范',
|
|
175
|
+
args: {
|
|
176
|
+
name: '[功能/模式名称]',
|
|
177
|
+
type: 'pattern',
|
|
178
|
+
description: '[该资产解决什么问题]',
|
|
179
|
+
summary: '[检索用摘要]',
|
|
180
|
+
content: '[可复用代码或流程]',
|
|
181
|
+
usage: '[适用场景与限制]',
|
|
182
|
+
tags: ['feature', 'pattern'],
|
|
183
|
+
confidence: 0.75,
|
|
184
|
+
},
|
|
185
|
+
outputs: [],
|
|
186
|
+
};
|
|
187
|
+
}
|
|
50
188
|
return {
|
|
51
189
|
id: 'memorize',
|
|
52
190
|
tool: 'memorize_asset',
|
|
53
191
|
when: '本次实现完成且确认存在可复用资产',
|
|
54
192
|
args: {
|
|
55
193
|
name: '[资产名称]',
|
|
56
|
-
type: '
|
|
194
|
+
type: 'pattern',
|
|
57
195
|
description: '[该资产解决了什么问题]',
|
|
58
196
|
summary: '[用于后续检索的简洁摘要]',
|
|
59
197
|
content: '[可复用代码或规范内容]',
|
|
60
198
|
usage: '[适用场景与限制]',
|
|
199
|
+
tags: ['pattern'],
|
|
61
200
|
confidence: 0.7,
|
|
62
201
|
},
|
|
63
202
|
outputs: [],
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unify legacy Qdrant payloads (kind/title/source) with the current asset schema.
|
|
3
|
+
*/
|
|
4
|
+
export declare function normalizeMemoryPayload(payload: Record<string, unknown>): Record<string, unknown>;
|
|
5
|
+
export declare function payloadToMemoryFields(payload: Record<string, unknown>): {
|
|
6
|
+
id: string;
|
|
7
|
+
name: string;
|
|
8
|
+
type: string;
|
|
9
|
+
description: string;
|
|
10
|
+
summary: string;
|
|
11
|
+
content: string;
|
|
12
|
+
tags: string[];
|
|
13
|
+
confidence: number;
|
|
14
|
+
sourceProject?: string;
|
|
15
|
+
sourcePath?: string;
|
|
16
|
+
usage?: string;
|
|
17
|
+
contentHash?: string;
|
|
18
|
+
normalizedContentHash?: string;
|
|
19
|
+
createdAt: string;
|
|
20
|
+
updatedAt: string;
|
|
21
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
function truncate(value, maxChars) {
|
|
2
|
+
if (value.length <= maxChars) {
|
|
3
|
+
return value;
|
|
4
|
+
}
|
|
5
|
+
return `${value.slice(0, Math.max(0, maxChars - 3))}...`;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Unify legacy Qdrant payloads (kind/title/source) with the current asset schema.
|
|
9
|
+
*/
|
|
10
|
+
export function normalizeMemoryPayload(payload) {
|
|
11
|
+
const normalized = { ...payload };
|
|
12
|
+
if (!normalized.name && typeof normalized.title === 'string') {
|
|
13
|
+
normalized.name = normalized.title;
|
|
14
|
+
}
|
|
15
|
+
if (!normalized.type && typeof normalized.kind === 'string') {
|
|
16
|
+
const kind = normalized.kind;
|
|
17
|
+
normalized.type = kind === 'extracted_pattern' ? 'pattern' : kind;
|
|
18
|
+
}
|
|
19
|
+
if (!normalized.description) {
|
|
20
|
+
if (typeof normalized.title === 'string') {
|
|
21
|
+
normalized.description = normalized.title;
|
|
22
|
+
}
|
|
23
|
+
else if (typeof normalized.source === 'string') {
|
|
24
|
+
normalized.description = `Legacy memory from ${normalized.source}`;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
if (!normalized.summary && typeof normalized.content === 'string') {
|
|
28
|
+
normalized.summary = truncate(normalized.content, 280);
|
|
29
|
+
}
|
|
30
|
+
if (!normalized.createdAt && typeof normalized.created_at === 'string') {
|
|
31
|
+
normalized.createdAt = normalized.created_at;
|
|
32
|
+
}
|
|
33
|
+
if (!normalized.updatedAt) {
|
|
34
|
+
if (typeof normalized.updated_at === 'string') {
|
|
35
|
+
normalized.updatedAt = normalized.updated_at;
|
|
36
|
+
}
|
|
37
|
+
else if (typeof normalized.createdAt === 'string') {
|
|
38
|
+
normalized.updatedAt = normalized.createdAt;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return normalized;
|
|
42
|
+
}
|
|
43
|
+
export function payloadToMemoryFields(payload) {
|
|
44
|
+
const p = normalizeMemoryPayload(payload);
|
|
45
|
+
const tags = Array.isArray(p.tags)
|
|
46
|
+
? p.tags.filter((item) => typeof item === 'string' && item.trim().length > 0)
|
|
47
|
+
: [];
|
|
48
|
+
return {
|
|
49
|
+
id: String(p.id || ''),
|
|
50
|
+
name: String(p.name || ''),
|
|
51
|
+
type: String(p.type || ''),
|
|
52
|
+
description: String(p.description || ''),
|
|
53
|
+
summary: String(p.summary || ''),
|
|
54
|
+
content: String(p.content || ''),
|
|
55
|
+
tags,
|
|
56
|
+
confidence: typeof p.confidence === 'number' && Number.isFinite(p.confidence) ? p.confidence : 0.5,
|
|
57
|
+
sourceProject: typeof p.sourceProject === 'string' ? p.sourceProject : undefined,
|
|
58
|
+
sourcePath: typeof p.sourcePath === 'string' ? p.sourcePath : undefined,
|
|
59
|
+
usage: typeof p.usage === 'string' ? p.usage : undefined,
|
|
60
|
+
contentHash: typeof p.contentHash === 'string' ? p.contentHash : undefined,
|
|
61
|
+
normalizedContentHash: typeof p.normalizedContentHash === 'string' ? p.normalizedContentHash : undefined,
|
|
62
|
+
createdAt: String(p.createdAt || ''),
|
|
63
|
+
updatedAt: String(p.updatedAt || ''),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export type AgentsMdMergeMode = "created" | "prepended" | "replaced-and-moved-to-top" | "skipped-empty";
|
|
2
|
+
export declare function wrapMcpProbeBlock(innerMarkdown: string): string;
|
|
3
|
+
export declare function mergeAgentsMdBlock(existingContent: string | null | undefined, generatedInner: string): {
|
|
4
|
+
content: string;
|
|
5
|
+
mergeMode: AgentsMdMergeMode;
|
|
6
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
const BLOCK_BEGIN = "<!-- mcp-probe:context begin — auto-generated; re-run init_project_context updates this block only -->";
|
|
2
|
+
const BLOCK_END = "<!-- mcp-probe:context end -->";
|
|
3
|
+
export function wrapMcpProbeBlock(innerMarkdown) {
|
|
4
|
+
return `${BLOCK_BEGIN}\n${innerMarkdown.trim()}\n${BLOCK_END}`;
|
|
5
|
+
}
|
|
6
|
+
function stripExistingBlock(content) {
|
|
7
|
+
const beginIdx = content.indexOf(BLOCK_BEGIN);
|
|
8
|
+
if (beginIdx === -1) {
|
|
9
|
+
const legacyBegin = content.indexOf("<!-- mcp-probe:context begin");
|
|
10
|
+
if (legacyBegin === -1) {
|
|
11
|
+
return content.trim();
|
|
12
|
+
}
|
|
13
|
+
const legacyEnd = content.indexOf(BLOCK_END);
|
|
14
|
+
if (legacyEnd === -1) {
|
|
15
|
+
return content.trim();
|
|
16
|
+
}
|
|
17
|
+
const legacyLineEnd = content.indexOf("-->", legacyBegin);
|
|
18
|
+
const blockStart = legacyLineEnd === -1 ? legacyBegin : legacyLineEnd + 3;
|
|
19
|
+
const before = content.slice(0, legacyBegin).trimEnd();
|
|
20
|
+
const after = content.slice(legacyEnd + BLOCK_END.length).trimStart();
|
|
21
|
+
return [before, after].filter(Boolean).join("\n\n").trim();
|
|
22
|
+
}
|
|
23
|
+
const endIdx = content.indexOf(BLOCK_END);
|
|
24
|
+
if (endIdx === -1) {
|
|
25
|
+
return content.trim();
|
|
26
|
+
}
|
|
27
|
+
const before = content.slice(0, beginIdx).trimEnd();
|
|
28
|
+
const after = content.slice(endIdx + BLOCK_END.length).trimStart();
|
|
29
|
+
return [before, after].filter(Boolean).join("\n\n").trim();
|
|
30
|
+
}
|
|
31
|
+
export function mergeAgentsMdBlock(existingContent, generatedInner) {
|
|
32
|
+
const block = wrapMcpProbeBlock(generatedInner);
|
|
33
|
+
if (!existingContent?.trim()) {
|
|
34
|
+
return { content: `${block}\n`, mergeMode: "created" };
|
|
35
|
+
}
|
|
36
|
+
const userBody = stripExistingBlock(existingContent);
|
|
37
|
+
const hadBlock = existingContent.includes(BLOCK_BEGIN) || existingContent.includes("<!-- mcp-probe:context begin");
|
|
38
|
+
if (!userBody) {
|
|
39
|
+
return { content: `${block}\n`, mergeMode: hadBlock ? "replaced-and-moved-to-top" : "created" };
|
|
40
|
+
}
|
|
41
|
+
if (!hadBlock) {
|
|
42
|
+
return {
|
|
43
|
+
content: `${block}\n\n${userBody}\n`,
|
|
44
|
+
mergeMode: "prepended",
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
content: `${block}\n\n${userBody}\n`,
|
|
49
|
+
mergeMode: "replaced-and-moved-to-top",
|
|
50
|
+
};
|
|
51
|
+
}
|