mcp-probe-kit 3.0.19 → 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 +12 -5
- package/build/index.js +3 -1
- package/build/lib/__tests__/agents-md-template.unit.test.js +2 -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/agents-md-template.js +7 -5
- 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 +7 -2
- package/build/lib/memory-orchestration.js +81 -8
- package/build/lib/memory-payload.d.ts +21 -0
- package/build/lib/memory-payload.js +65 -0
- package/build/resources/ui-ux-data/metadata.json +1 -1
- package/build/schemas/index.d.ts +38 -9
- package/build/schemas/memory-tools.d.ts +38 -9
- package/build/schemas/memory-tools.js +24 -9
- package/build/tools/index.d.ts +1 -0
- package/build/tools/index.js +1 -0
- 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 +3 -3
- package/build/tools/start_feature.js +3 -3
- package/build/tools/start_ui.js +3 -3
- 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 +5 -5
- package/docs/i18n/ja.json +2 -2
- package/docs/i18n/ko.json +2 -2
- package/docs/i18n/zh-CN.json +7 -7
- package/docs/memory-local-setup.md +1 -1
- package/docs/memory-local-setup.zh-CN.md +5 -2
- package/docs/pages/getting-started.html +3 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
> **Talk is cheap, show me the Context.**
|
|
17
17
|
>
|
|
18
|
-
> mcp-probe-kit is a protocol-level toolkit designed for developers who want AI to truly understand their project's intent. It's not just a collection of
|
|
18
|
+
> mcp-probe-kit is a protocol-level toolkit designed for developers who want AI to truly understand their project's intent. It's not just a collection of 29 tools—it's a context-aware system that helps AI agents grasp what you're building.
|
|
19
19
|
|
|
20
20
|
**Languages**: [English](README.md) | [简体中文](i18n/README.zh-CN.md) | [日本語](i18n/README.ja-JP.md) | [한국어](i18n/README.ko-KR.md) | [Español](i18n/README.es-ES.md) | [Français](i18n/README.fr-FR.md) | [Deutsch](i18n/README.de-DE.md) | [Português (BR)](i18n/README.pt-BR.md)
|
|
21
21
|
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
|
|
27
27
|
> 🚀 AI-Powered Complete Development Toolkit - Covering the Entire Development Lifecycle
|
|
28
28
|
|
|
29
|
-
A powerful MCP (Model Context Protocol) server providing **
|
|
29
|
+
A powerful MCP (Model Context Protocol) server providing **29 tools** covering the complete workflow from product analysis to final release (Requirements → Design → Development → Quality → Release), all tools support **structured output**.
|
|
30
30
|
|
|
31
31
|
**🎉 v3.0 Major Update**: Streamlined tool count, focus on core competencies, eliminate choice paralysis, let AI do more native work
|
|
32
32
|
|
|
@@ -42,7 +42,7 @@ A powerful MCP (Model Context Protocol) server providing **28 tools** covering t
|
|
|
42
42
|
|
|
43
43
|
- [Quick Start](https://mcp-probe-kit.bytezonex.com/pages/getting-started.html) - Setup in 5 minutes
|
|
44
44
|
- [Local Memory Stack (Qdrant + Nomic Embed)](docs/memory-local-setup.md) - Docker Compose, ports `50008` / `50012`, MCP env
|
|
45
|
-
- [All Tools](https://mcp-probe-kit.bytezonex.com/pages/all-tools.html) - Complete list of
|
|
45
|
+
- [All Tools](https://mcp-probe-kit.bytezonex.com/pages/all-tools.html) - Complete list of 29 tools
|
|
46
46
|
- [Best Practices](https://mcp-probe-kit.bytezonex.com/pages/examples.html) - Full development workflow guide
|
|
47
47
|
- [v3.0 Migration Guide](https://mcp-probe-kit.bytezonex.com/pages/migration.html) - Upgrade from v2.x to v3.0
|
|
48
48
|
|
|
@@ -64,8 +64,8 @@ A powerful MCP (Model Context Protocol) server providing **28 tools** covering t
|
|
|
64
64
|
- `init_project`, `init_project_context`, `add_feature`, `estimate`, `interview`, `ask_user`
|
|
65
65
|
- **🎨 UI/UX Utilities** (3 tools) - Design systems and UI data synchronization
|
|
66
66
|
- `ui_design_system`, `ui_search`, `sync_ui_data`
|
|
67
|
-
- **🧠 Memory & Cursor History** (
|
|
68
|
-
- `read_memory_asset`, `memorize_asset`, `scan_and_extract_patterns`, `cursor_list_conversations`, `cursor_search_conversations`, `cursor_read_conversation`
|
|
67
|
+
- **🧠 Memory & Cursor History** (7 tools) - Reusable asset memory and local Cursor conversation retrieval
|
|
68
|
+
- `search_memory`, `read_memory_asset`, `memorize_asset`, `scan_and_extract_patterns`, `cursor_list_conversations`, `cursor_search_conversations`, `cursor_read_conversation`
|
|
69
69
|
|
|
70
70
|
### 🧠 Code Graph Bridge (GitNexus)
|
|
71
71
|
|
|
@@ -100,10 +100,13 @@ A powerful MCP (Model Context Protocol) server providing **28 tools** covering t
|
|
|
100
100
|
- Linux: `~/.config/Cursor/User/globalStorage/state.vscdb`
|
|
101
101
|
|
|
102
102
|
**Memory tools:**
|
|
103
|
+
- `search_memory` - Semantic search across the shared memory pool (optionally prefer `type` / `tags`)
|
|
103
104
|
- `memorize_asset` - Persist reusable code/spec/pattern assets into vector memory
|
|
104
105
|
- `read_memory_asset` - Read full asset content by `asset_id`
|
|
105
106
|
- `scan_and_extract_patterns` - Extract reusable patterns from code/file/directory before deciding whether to persist
|
|
106
107
|
|
|
108
|
+
**Cross-repo memory pools:** do not rely on `source_project` / `source_path` for shared retrieval; put file paths in `content` instead. Search injection hides foreign `sourcePath` unless `MEMORY_REPO_ID` matches or `MEMORY_SEARCH_SHOW_SOURCE=true`.
|
|
109
|
+
|
|
107
110
|
**Memory backend and embedding configuration:**
|
|
108
111
|
- Vector database: **Qdrant**
|
|
109
112
|
- **Recommended local setup:** `Qdrant (port 50008) + Infinity / nomic-embed (port 50012)` — lighter than Ollama; see **[Local Memory Stack guide](docs/memory-local-setup.md)** (中文: [memory-local-setup.zh-CN.md](docs/memory-local-setup.zh-CN.md))
|
|
@@ -121,6 +124,10 @@ A powerful MCP (Model Context Protocol) server providing **28 tools** covering t
|
|
|
121
124
|
- `MEMORY_EMBEDDING_PROVIDER` (`ollama` by default)
|
|
122
125
|
- `MEMORY_SEARCH_LIMIT` (default: `3`)
|
|
123
126
|
- `MEMORY_SUMMARY_MAX_CHARS` (default: `280`)
|
|
127
|
+
- `MEMORY_SEARCH_MIN_SCORE` (default: `0` = disabled; try `0.72` for noisy pools)
|
|
128
|
+
- `MEMORY_SEARCH_SHOW_SOURCE` (default: `false`)
|
|
129
|
+
- `MEMORY_REPO_ID` (optional; show `sourcePath` only when `sourceProject` matches)
|
|
130
|
+
- `MEMORY_INJECTION_CONTENT_MAX_CHARS` (default: `1500`; max content per hit injected into `start_*` guides)
|
|
124
131
|
- Behavior notes:
|
|
125
132
|
- Read-only memory access only requires `MEMORY_QDRANT_URL`
|
|
126
133
|
- Memory write is enabled only when `MEMORY_QDRANT_URL`, `MEMORY_EMBEDDING_URL`, and `MEMORY_EMBEDDING_MODEL` are all configured
|
package/build/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import { InMemoryTaskMessageQueue, InMemoryTaskStore, } from "@modelcontextproto
|
|
|
5
5
|
import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ProgressNotificationSchema, ReadResourceRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
6
6
|
import * as fs from "node:fs";
|
|
7
7
|
import * as path from "node:path";
|
|
8
|
-
import { initProject, gencommit, codeReview, codeInsight, gentest, refactor, initProjectContext, addFeature, fixBug, estimate, startFeature, startBugfix, startOnboard, startRalph, interview, askUser, uiDesignSystem, uiSearch, syncUiData, startUi, startProduct, gitWorkReport, readMemoryAsset, memorizeAsset, scanAndExtractPatterns, cursorListConversations, cursorSearchConversations, cursorReadConversation } from "./tools/index.js";
|
|
8
|
+
import { initProject, gencommit, codeReview, codeInsight, gentest, refactor, initProjectContext, addFeature, fixBug, estimate, startFeature, startBugfix, startOnboard, startRalph, interview, askUser, uiDesignSystem, uiSearch, syncUiData, startUi, startProduct, gitWorkReport, searchMemory, readMemoryAsset, memorizeAsset, scanAndExtractPatterns, cursorListConversations, cursorSearchConversations, cursorReadConversation } from "./tools/index.js";
|
|
9
9
|
import { VERSION, NAME } from "./version.js";
|
|
10
10
|
import { allToolSchemas } from "./schemas/index.js";
|
|
11
11
|
import { filterTools, getToolsetFromEnv } from "./lib/toolset-manager.js";
|
|
@@ -459,6 +459,8 @@ async function executeTool(name, args, context) {
|
|
|
459
459
|
return await startProduct((args ?? {}), context);
|
|
460
460
|
case "git_work_report":
|
|
461
461
|
return await gitWorkReport(args);
|
|
462
|
+
case "search_memory":
|
|
463
|
+
return await searchMemory(args);
|
|
462
464
|
case "read_memory_asset":
|
|
463
465
|
return await readMemoryAsset(args);
|
|
464
466
|
case "memorize_asset":
|
|
@@ -30,4 +30,13 @@ describe('memory-config 单元测试', () => {
|
|
|
30
30
|
expect(isMemoryReadEnabled(config)).toBe(true);
|
|
31
31
|
expect(isMemoryEnabled(config)).toBe(true);
|
|
32
32
|
});
|
|
33
|
+
test('记忆检索展示与阈值环境变量', () => {
|
|
34
|
+
vi.stubEnv('MEMORY_SEARCH_SHOW_SOURCE', 'true');
|
|
35
|
+
vi.stubEnv('MEMORY_SEARCH_MIN_SCORE', '0.72');
|
|
36
|
+
vi.stubEnv('MEMORY_REPO_ID', 'my-org/my-repo');
|
|
37
|
+
const config = getMemoryConfig();
|
|
38
|
+
expect(config.searchShowSource).toBe(true);
|
|
39
|
+
expect(config.searchMinScore).toBe(0.72);
|
|
40
|
+
expect(config.repoId).toBe('my-org/my-repo');
|
|
41
|
+
});
|
|
33
42
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { afterEach, describe, expect, test, vi } from 'vitest';
|
|
2
|
+
import { renderMemoryGuideSection, truncateInjectionText, } from '../memory-orchestration.js';
|
|
3
|
+
afterEach(() => {
|
|
4
|
+
vi.unstubAllEnvs();
|
|
5
|
+
});
|
|
6
|
+
describe('memory injection auto-load', () => {
|
|
7
|
+
test('renders full asset content without asking for read_memory_asset', () => {
|
|
8
|
+
vi.stubEnv('MEMORY_SEARCH_SHOW_SOURCE', '');
|
|
9
|
+
vi.stubEnv('MEMORY_REPO_ID', '');
|
|
10
|
+
const section = renderMemoryGuideSection({
|
|
11
|
+
enabled: true,
|
|
12
|
+
available: true,
|
|
13
|
+
degraded: false,
|
|
14
|
+
query: 'proxy 400',
|
|
15
|
+
results: [
|
|
16
|
+
{
|
|
17
|
+
id: 'asset-1',
|
|
18
|
+
score: 0.88,
|
|
19
|
+
name: 'feishu-proxy-bug',
|
|
20
|
+
type: 'bugfix',
|
|
21
|
+
description: 'Feishu proxy mismatch',
|
|
22
|
+
summary: 'proxy caused 400 on HTTPS',
|
|
23
|
+
tags: ['bugfix', 'proxy'],
|
|
24
|
+
},
|
|
25
|
+
],
|
|
26
|
+
assetsById: {
|
|
27
|
+
'asset-1': {
|
|
28
|
+
id: 'asset-1',
|
|
29
|
+
name: 'feishu-proxy-bug',
|
|
30
|
+
type: 'bugfix',
|
|
31
|
+
description: 'Feishu proxy mismatch',
|
|
32
|
+
summary: 'proxy caused 400 on HTTPS',
|
|
33
|
+
content: '【现象】submit 成功但 sync_failed\n【根因】HTTP_PROXY 污染\n【修复】proxy:false',
|
|
34
|
+
tags: ['bugfix'],
|
|
35
|
+
confidence: 0.9,
|
|
36
|
+
createdAt: '2026-01-01T00:00:00.000Z',
|
|
37
|
+
updatedAt: '2026-01-01T00:00:00.000Z',
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
expect(section).toContain('已自动加载的历史经验全文');
|
|
42
|
+
expect(section).toContain('【现象】submit 成功但 sync_failed');
|
|
43
|
+
expect(section).toContain('【根因】HTTP_PROXY 污染');
|
|
44
|
+
expect(section).toContain('无需再调 `read_memory_asset`');
|
|
45
|
+
expect(section).not.toMatch(/读取: read_memory_asset/);
|
|
46
|
+
});
|
|
47
|
+
test('truncateInjectionText limits long content', () => {
|
|
48
|
+
expect(truncateInjectionText('abcdef', 4)).toBe('a...');
|
|
49
|
+
expect(truncateInjectionText('abc', 10)).toBe('abc');
|
|
50
|
+
});
|
|
51
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { afterEach, describe, expect, test, vi } from 'vitest';
|
|
2
|
+
import { renderMemoryGuideSection, shouldShowSourceInSearch, } from '../memory-orchestration.js';
|
|
3
|
+
afterEach(() => {
|
|
4
|
+
vi.unstubAllEnvs();
|
|
5
|
+
});
|
|
6
|
+
describe('memory-orchestration', () => {
|
|
7
|
+
test('does not show source path by default', () => {
|
|
8
|
+
vi.stubEnv('MEMORY_SEARCH_SHOW_SOURCE', '');
|
|
9
|
+
vi.stubEnv('MEMORY_REPO_ID', '');
|
|
10
|
+
const section = renderMemoryGuideSection({
|
|
11
|
+
enabled: true,
|
|
12
|
+
available: true,
|
|
13
|
+
degraded: false,
|
|
14
|
+
query: '404 submit',
|
|
15
|
+
results: [
|
|
16
|
+
{
|
|
17
|
+
id: '1',
|
|
18
|
+
score: 0.91,
|
|
19
|
+
name: 'purchase-create-submit-404',
|
|
20
|
+
type: 'bugfix',
|
|
21
|
+
description: 'desc',
|
|
22
|
+
summary: 'summary',
|
|
23
|
+
tags: ['bugfix'],
|
|
24
|
+
sourceProject: 'zhixing/gongyingshang',
|
|
25
|
+
sourcePath: 'admin-api/app.js',
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
assetsById: {},
|
|
29
|
+
});
|
|
30
|
+
expect(section).toContain('purchase-create-submit-404');
|
|
31
|
+
expect(section).not.toContain('admin-api/app.js');
|
|
32
|
+
expect(section).not.toContain('来源:');
|
|
33
|
+
});
|
|
34
|
+
test('shows source path when MEMORY_REPO_ID matches', () => {
|
|
35
|
+
vi.stubEnv('MEMORY_SEARCH_SHOW_SOURCE', '');
|
|
36
|
+
vi.stubEnv('MEMORY_REPO_ID', 'zhixing/gongyingshang');
|
|
37
|
+
expect(shouldShowSourceInSearch({
|
|
38
|
+
id: '1',
|
|
39
|
+
score: 0.9,
|
|
40
|
+
name: 'x',
|
|
41
|
+
type: 'bugfix',
|
|
42
|
+
description: '',
|
|
43
|
+
summary: '',
|
|
44
|
+
tags: [],
|
|
45
|
+
sourceProject: 'zhixing/gongyingshang',
|
|
46
|
+
sourcePath: 'admin-api/app.js',
|
|
47
|
+
})).toBe(true);
|
|
48
|
+
expect(shouldShowSourceInSearch({
|
|
49
|
+
id: '2',
|
|
50
|
+
score: 0.9,
|
|
51
|
+
name: 'y',
|
|
52
|
+
type: 'bugfix',
|
|
53
|
+
description: '',
|
|
54
|
+
summary: '',
|
|
55
|
+
tags: [],
|
|
56
|
+
sourceProject: 'other/repo',
|
|
57
|
+
sourcePath: 'src/index.ts',
|
|
58
|
+
})).toBe(false);
|
|
59
|
+
});
|
|
60
|
+
test('MEMORY_SEARCH_SHOW_SOURCE=true always shows source', () => {
|
|
61
|
+
vi.stubEnv('MEMORY_SEARCH_SHOW_SOURCE', 'true');
|
|
62
|
+
vi.stubEnv('MEMORY_REPO_ID', '');
|
|
63
|
+
const section = renderMemoryGuideSection({
|
|
64
|
+
enabled: true,
|
|
65
|
+
available: true,
|
|
66
|
+
degraded: false,
|
|
67
|
+
query: 'q',
|
|
68
|
+
results: [
|
|
69
|
+
{
|
|
70
|
+
id: '1',
|
|
71
|
+
score: 0.8,
|
|
72
|
+
name: 'n',
|
|
73
|
+
type: 'bugfix',
|
|
74
|
+
description: '',
|
|
75
|
+
summary: 's',
|
|
76
|
+
tags: [],
|
|
77
|
+
sourcePath: 'admin-api/app.js',
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
assetsById: {},
|
|
81
|
+
});
|
|
82
|
+
expect(section).toContain('来源: admin-api/app.js');
|
|
83
|
+
});
|
|
84
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { describe, expect, test } from 'vitest';
|
|
2
|
+
import { normalizeMemoryPayload, payloadToMemoryFields } from '../memory-payload.js';
|
|
3
|
+
describe('memory-payload', () => {
|
|
4
|
+
test('maps legacy kind/title/source fields to current schema', () => {
|
|
5
|
+
const normalized = normalizeMemoryPayload({
|
|
6
|
+
kind: 'extracted_pattern',
|
|
7
|
+
title: 'Feishu submit success but sync_failed',
|
|
8
|
+
source: 'scan_and_extract_patterns-fallback',
|
|
9
|
+
content: 'Pattern: proxy mismatch',
|
|
10
|
+
tags: ['feishu', 'pattern'],
|
|
11
|
+
created_at: '2026-05-25T11:20:06.705Z',
|
|
12
|
+
});
|
|
13
|
+
expect(normalized.name).toBe('Feishu submit success but sync_failed');
|
|
14
|
+
expect(normalized.type).toBe('pattern');
|
|
15
|
+
expect(normalized.summary).toContain('Pattern');
|
|
16
|
+
expect(normalized.createdAt).toBe('2026-05-25T11:20:06.705Z');
|
|
17
|
+
});
|
|
18
|
+
test('payloadToMemoryFields preserves standard asset fields', () => {
|
|
19
|
+
const fields = payloadToMemoryFields({
|
|
20
|
+
id: 'asset-1',
|
|
21
|
+
name: 'purchase-create-submit-404',
|
|
22
|
+
type: 'bugfix',
|
|
23
|
+
description: '送审 404',
|
|
24
|
+
summary: 'res.data.purchase.id',
|
|
25
|
+
content: '【现象】404',
|
|
26
|
+
tags: ['bugfix'],
|
|
27
|
+
confidence: 0.95,
|
|
28
|
+
createdAt: '2026-05-27T04:28:04.684Z',
|
|
29
|
+
updatedAt: '2026-05-27T04:28:04.684Z',
|
|
30
|
+
});
|
|
31
|
+
expect(fields.name).toBe('purchase-create-submit-404');
|
|
32
|
+
expect(fields.type).toBe('bugfix');
|
|
33
|
+
expect(fields.tags).toEqual(['bugfix']);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
@@ -6,14 +6,16 @@ function memorySection(locale) {
|
|
|
6
6
|
if (locale === "zh-CN") {
|
|
7
7
|
return `
|
|
8
8
|
记忆(需 MEMORY_QDRANT_URL 等已配置):
|
|
9
|
-
- 检索:\`
|
|
10
|
-
-
|
|
11
|
-
-
|
|
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\``;
|
|
12
13
|
}
|
|
13
14
|
return `
|
|
14
15
|
Memory (requires MEMORY_* env):
|
|
15
|
-
- Search: \`
|
|
16
|
-
-
|
|
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)
|
|
17
19
|
- Reusable feature/UI → \`memorize_asset\` type=\`pattern\`/\`component\``;
|
|
18
20
|
}
|
|
19
21
|
/**
|
|
@@ -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,4 +1,5 @@
|
|
|
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';
|
|
2
3
|
export type MemoryPlanKind = 'feature' | 'bugfix' | 'ui' | 'default';
|
|
3
4
|
export interface MemoryInjectionContext {
|
|
4
5
|
enabled: boolean;
|
|
@@ -6,9 +7,13 @@ export interface MemoryInjectionContext {
|
|
|
6
7
|
degraded: boolean;
|
|
7
8
|
query: string;
|
|
8
9
|
results: MemorySearchResult[];
|
|
10
|
+
/** Full assets keyed by search hit id (auto-loaded for start_* injection) */
|
|
11
|
+
assetsById: Record<string, MemoryAsset>;
|
|
9
12
|
error?: string;
|
|
10
13
|
}
|
|
11
|
-
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;
|
|
12
17
|
export declare function renderMemoryGuideSection(context: MemoryInjectionContext): string;
|
|
13
18
|
export declare function buildMemoryPlanStep(kind?: MemoryPlanKind): {
|
|
14
19
|
id: string;
|