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.
Files changed (62) hide show
  1. package/README.md +87 -55
  2. package/build/index.js +3 -1
  3. package/build/lib/__tests__/agents-md-template.unit.test.d.ts +1 -0
  4. package/build/lib/__tests__/agents-md-template.unit.test.js +27 -0
  5. package/build/lib/__tests__/memory-config.unit.test.js +9 -0
  6. package/build/lib/__tests__/memory-injection.unit.test.d.ts +1 -0
  7. package/build/lib/__tests__/memory-injection.unit.test.js +51 -0
  8. package/build/lib/__tests__/memory-orchestration.unit.test.d.ts +1 -0
  9. package/build/lib/__tests__/memory-orchestration.unit.test.js +84 -0
  10. package/build/lib/__tests__/memory-payload.unit.test.d.ts +1 -0
  11. package/build/lib/__tests__/memory-payload.unit.test.js +35 -0
  12. package/build/lib/__tests__/project-context-layout.unit.test.d.ts +1 -0
  13. package/build/lib/__tests__/project-context-layout.unit.test.js +80 -0
  14. package/build/lib/agents-md-template.d.ts +25 -0
  15. package/build/lib/agents-md-template.js +57 -0
  16. package/build/lib/memory-client.d.ts +8 -1
  17. package/build/lib/memory-client.js +53 -44
  18. package/build/lib/memory-config.d.ts +8 -0
  19. package/build/lib/memory-config.js +19 -0
  20. package/build/lib/memory-orchestration.d.ts +10 -3
  21. package/build/lib/memory-orchestration.js +146 -7
  22. package/build/lib/memory-payload.d.ts +21 -0
  23. package/build/lib/memory-payload.js +65 -0
  24. package/build/lib/merge-agents-md.d.ts +6 -0
  25. package/build/lib/merge-agents-md.js +51 -0
  26. package/build/lib/project-context-layout.d.ts +78 -0
  27. package/build/lib/project-context-layout.js +350 -0
  28. package/build/lib/workspace-root.js +6 -1
  29. package/build/resources/ui-ux-data/metadata.json +1 -1
  30. package/build/schemas/index.d.ts +62 -11
  31. package/build/schemas/memory-tools.d.ts +38 -9
  32. package/build/schemas/memory-tools.js +24 -9
  33. package/build/schemas/project-tools.d.ts +24 -2
  34. package/build/schemas/project-tools.js +24 -2
  35. package/build/tools/__tests__/code_insight.unit.test.js +3 -3
  36. package/build/tools/__tests__/init_project_context.unit.test.js +32 -21
  37. package/build/tools/__tests__/start_feature.unit.test.js +2 -1
  38. package/build/tools/code_insight.js +11 -9
  39. package/build/tools/index.d.ts +1 -0
  40. package/build/tools/index.js +1 -0
  41. package/build/tools/init_project_context.js +563 -506
  42. package/build/tools/memorize_asset.js +12 -0
  43. package/build/tools/scan_and_extract_patterns.js +7 -7
  44. package/build/tools/search_memory.d.ts +7 -0
  45. package/build/tools/search_memory.js +57 -0
  46. package/build/tools/start_bugfix.js +257 -251
  47. package/build/tools/start_feature.js +140 -134
  48. package/build/tools/start_ui.js +405 -405
  49. package/docs/.mcp-probe/layout.json +11 -0
  50. package/docs/data/tools.js +18 -0
  51. package/docs/i18n/all-tools/en.json +6 -1
  52. package/docs/i18n/all-tools/ja.json +2 -1
  53. package/docs/i18n/all-tools/ko.json +2 -1
  54. package/docs/i18n/all-tools/zh-CN.json +7 -2
  55. package/docs/i18n/en.json +38 -7
  56. package/docs/i18n/ja.json +9 -2
  57. package/docs/i18n/ko.json +9 -2
  58. package/docs/i18n/zh-CN.json +40 -9
  59. package/docs/memory-local-setup.md +314 -0
  60. package/docs/memory-local-setup.zh-CN.md +283 -0
  61. package/docs/pages/getting-started.html +252 -33
  62. 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 28 tools—it's a context-aware system that helps AI agents grasp what you're building.
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 **28 tools** covering the complete workflow from product analysis to final release (Requirements → Design → Development → Quality → Release), all tools support **structured output**.
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
 
@@ -41,7 +41,8 @@ A powerful MCP (Model Context Protocol) server providing **28 tools** covering t
41
41
  **👉 [https://mcp-probe-kit.bytezonex.com](https://mcp-probe-kit.bytezonex.com/)**
42
42
 
43
43
  - [Quick Start](https://mcp-probe-kit.bytezonex.com/pages/getting-started.html) - Setup in 5 minutes
44
- - [All Tools](https://mcp-probe-kit.bytezonex.com/pages/all-tools.html) - Complete list of 28 tools
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 29 tools
45
46
  - [Best Practices](https://mcp-probe-kit.bytezonex.com/pages/examples.html) - Full development workflow guide
46
47
  - [v3.0 Migration Guide](https://mcp-probe-kit.bytezonex.com/pages/migration.html) - Upgrade from v2.x to v3.0
47
48
 
@@ -63,8 +64,8 @@ A powerful MCP (Model Context Protocol) server providing **28 tools** covering t
63
64
  - `init_project`, `init_project_context`, `add_feature`, `estimate`, `interview`, `ask_user`
64
65
  - **🎨 UI/UX Utilities** (3 tools) - Design systems and UI data synchronization
65
66
  - `ui_design_system`, `ui_search`, `sync_ui_data`
66
- - **🧠 Memory & Cursor History** (6 tools) - Reusable asset memory and local Cursor conversation retrieval
67
- - `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`
68
69
 
69
70
  ### 🧠 Code Graph Bridge (GitNexus)
70
71
 
@@ -99,16 +100,19 @@ A powerful MCP (Model Context Protocol) server providing **28 tools** covering t
99
100
  - Linux: `~/.config/Cursor/User/globalStorage/state.vscdb`
100
101
 
101
102
  **Memory tools:**
103
+ - `search_memory` - Semantic search across the shared memory pool (optionally prefer `type` / `tags`)
102
104
  - `memorize_asset` - Persist reusable code/spec/pattern assets into vector memory
103
105
  - `read_memory_asset` - Read full asset content by `asset_id`
104
106
  - `scan_and_extract_patterns` - Extract reusable patterns from code/file/directory before deciding whether to persist
105
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
+
106
110
  **Memory backend and embedding configuration:**
107
111
  - Vector database: **Qdrant**
108
- - Recommended local setup: `Qdrant + Ollama`
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))
109
113
  - Supported embedding providers:
110
114
  - `ollama`
111
- - `openai-compatible`
115
+ - `openai-compatible` (Infinity, OpenAI, etc.)
112
116
  - Required environment variables for memory write/search:
113
117
  - `MEMORY_QDRANT_URL`
114
118
  - `MEMORY_EMBEDDING_URL`
@@ -120,16 +124,18 @@ A powerful MCP (Model Context Protocol) server providing **28 tools** covering t
120
124
  - `MEMORY_EMBEDDING_PROVIDER` (`ollama` by default)
121
125
  - `MEMORY_SEARCH_LIMIT` (default: `3`)
122
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)
123
131
  - Behavior notes:
124
132
  - Read-only memory access only requires `MEMORY_QDRANT_URL`
125
133
  - Memory write is enabled only when `MEMORY_QDRANT_URL`, `MEMORY_EMBEDDING_URL`, and `MEMORY_EMBEDDING_MODEL` are all configured
126
134
  - The Qdrant collection is auto-created on first write, and vector dimension is inferred from the first embedding response
127
135
 
128
- **Recommended local memory setup (Qdrant + Ollama):**
129
- ```bash
130
- docker run -d --name mcp-qdrant -p 6333:6333 qdrant/qdrant
131
- ollama pull nomic-embed-text
132
- ```
136
+ **Recommended local memory setup (Qdrant + Nomic Embed / Infinity):**
137
+
138
+ Full Docker Compose, ports, and troubleshooting: **[docs/memory-local-setup.md](docs/memory-local-setup.md)**
133
139
 
134
140
  ```json
135
141
  {
@@ -138,11 +144,13 @@ ollama pull nomic-embed-text
138
144
  "command": "npx",
139
145
  "args": ["-y", "mcp-probe-kit@latest"],
140
146
  "env": {
141
- "MEMORY_QDRANT_URL": "http://127.0.0.1:6333",
147
+ "MEMORY_QDRANT_URL": "http://127.0.0.1:50008",
148
+ "MEMORY_QDRANT_API_KEY": "your-qdrant-api-key",
142
149
  "MEMORY_QDRANT_COLLECTION": "mcp_probe_memory",
143
- "MEMORY_EMBEDDING_PROVIDER": "ollama",
144
- "MEMORY_EMBEDDING_URL": "http://127.0.0.1:11434/api/embeddings",
145
- "MEMORY_EMBEDDING_MODEL": "nomic-embed-text",
150
+ "MEMORY_EMBEDDING_PROVIDER": "openai-compatible",
151
+ "MEMORY_EMBEDDING_URL": "http://127.0.0.1:50012/embeddings",
152
+ "MEMORY_EMBEDDING_MODEL": "nomic-ai/nomic-embed-text-v1.5",
153
+ "MEMORY_EMBEDDING_API_KEY": "your-infinity-api-key",
146
154
  "MEMORY_SEARCH_LIMIT": "3",
147
155
  "MEMORY_SUMMARY_MAX_CHARS": "280"
148
156
  }
@@ -151,7 +159,21 @@ ollama pull nomic-embed-text
151
159
  }
152
160
  ```
153
161
 
154
- **OpenAI-compatible embedding setup:**
162
+ **Alternative: Qdrant + Ollama** (if you already run Ollama):
163
+
164
+ ```bash
165
+ docker run -d --name mcp-qdrant -p 6333:6333 qdrant/qdrant
166
+ ollama pull nomic-embed-text
167
+ ```
168
+
169
+ ```json
170
+ "MEMORY_QDRANT_URL": "http://127.0.0.1:6333",
171
+ "MEMORY_EMBEDDING_PROVIDER": "ollama",
172
+ "MEMORY_EMBEDDING_URL": "http://127.0.0.1:11434/api/embeddings",
173
+ "MEMORY_EMBEDDING_MODEL": "nomic-embed-text"
174
+ ```
175
+
176
+ **OpenAI-compatible embedding (hosted API):**
155
177
  ```json
156
178
  {
157
179
  "mcpServers": {
@@ -416,6 +438,27 @@ No installation needed, use the latest version directly.
416
438
  }
417
439
  ```
418
440
 
441
+ #### OpenCode Configuration
442
+
443
+ **Config file location:**
444
+ - Project-level: `opencode.json` (in project root)
445
+ - Global: `~/.config/opencode/opencode.json`
446
+
447
+ **Config content:**
448
+ ```json
449
+ {
450
+ "mcp": {
451
+ "mcp-probe-kit": {
452
+ "type": "local",
453
+ "command": ["npx", "-y", "mcp-probe-kit@latest"],
454
+ "enabled": true
455
+ }
456
+ }
457
+ }
458
+ ```
459
+
460
+ > **Note:** OpenCode uses `opencode.json` with a different schema from Cursor/Claude Desktop. The key `mcp` replaces `mcpServers`, `command` is an array, `type: "local"` is required, and environment variables use `environment` instead of `env`. See [OpenCode MCP docs](https://opencode.ai/docs/mcp) for details.
461
+
419
462
  ### Method 2: Global Installation
420
463
 
421
464
  ```bash
@@ -440,21 +483,14 @@ If you want to use `memorize_asset`, `read_memory_asset`, and `scan_and_extract_
440
483
  1. A **Qdrant** vector database
441
484
  2. An **embedding service** in either `ollama` or `openai-compatible` mode
442
485
 
443
- #### Option A: Qdrant + Ollama
486
+ **Full guide (Docker Compose for Qdrant + Infinity, ports `50008` / `50012`, MCP env, smoke tests):**
444
487
 
445
- Start Qdrant with Docker:
488
+ - English: [docs/memory-local-setup.md](docs/memory-local-setup.md)
489
+ - 中文: [docs/memory-local-setup.zh-CN.md](docs/memory-local-setup.zh-CN.md)
446
490
 
447
- ```bash
448
- docker run -d --name mcp-qdrant -p 6333:6333 qdrant/qdrant
449
- ```
450
-
451
- Start Ollama and pull the default embedding model:
452
-
453
- ```bash
454
- ollama pull nomic-embed-text
455
- ```
491
+ #### Option A: Qdrant + Nomic Embed / Infinity (recommended)
456
492
 
457
- Recommended MCP config env:
493
+ Lightweight local stack; no Ollama. Deploy Qdrant and `nomic-embed` via Docker Compose (see guide), then:
458
494
 
459
495
  ```json
460
496
  {
@@ -463,11 +499,13 @@ Recommended MCP config env:
463
499
  "command": "npx",
464
500
  "args": ["-y", "mcp-probe-kit@latest"],
465
501
  "env": {
466
- "MEMORY_QDRANT_URL": "http://127.0.0.1:6333",
502
+ "MEMORY_QDRANT_URL": "http://127.0.0.1:50008",
503
+ "MEMORY_QDRANT_API_KEY": "your-qdrant-api-key",
467
504
  "MEMORY_QDRANT_COLLECTION": "mcp_probe_memory",
468
- "MEMORY_EMBEDDING_PROVIDER": "ollama",
469
- "MEMORY_EMBEDDING_URL": "http://127.0.0.1:11434/api/embeddings",
470
- "MEMORY_EMBEDDING_MODEL": "nomic-embed-text",
505
+ "MEMORY_EMBEDDING_PROVIDER": "openai-compatible",
506
+ "MEMORY_EMBEDDING_URL": "http://127.0.0.1:50012/embeddings",
507
+ "MEMORY_EMBEDDING_MODEL": "nomic-ai/nomic-embed-text-v1.5",
508
+ "MEMORY_EMBEDDING_API_KEY": "your-infinity-api-key",
471
509
  "MEMORY_SEARCH_LIMIT": "3",
472
510
  "MEMORY_SUMMARY_MAX_CHARS": "280"
473
511
  }
@@ -476,36 +514,30 @@ Recommended MCP config env:
476
514
  }
477
515
  ```
478
516
 
479
- #### Option B: Qdrant + OpenAI-Compatible Embedding API
517
+ > Embedding URL must be `/embeddings` (not `/v1/embeddings`). Qdrant requires `api-key` when `QDRANT__SERVICE__API_KEY` is set.
480
518
 
481
- Start Qdrant with Docker:
519
+ #### Option B: Qdrant + Ollama
482
520
 
483
521
  ```bash
484
522
  docker run -d --name mcp-qdrant -p 6333:6333 qdrant/qdrant
523
+ ollama pull nomic-embed-text
524
+ ```
525
+
526
+ ```json
527
+ "MEMORY_QDRANT_URL": "http://127.0.0.1:6333",
528
+ "MEMORY_EMBEDDING_PROVIDER": "ollama",
529
+ "MEMORY_EMBEDDING_URL": "http://127.0.0.1:11434/api/embeddings",
530
+ "MEMORY_EMBEDDING_MODEL": "nomic-embed-text"
485
531
  ```
486
532
 
487
- Then point the embedding config to an OpenAI-compatible `/embeddings` endpoint:
533
+ #### Option C: Qdrant + hosted OpenAI-compatible API
488
534
 
489
535
  ```json
490
- {
491
- "mcpServers": {
492
- "mcp-probe-kit": {
493
- "command": "npx",
494
- "args": ["-y", "mcp-probe-kit@latest"],
495
- "env": {
496
- "MEMORY_QDRANT_URL": "http://127.0.0.1:6333",
497
- "MEMORY_QDRANT_COLLECTION": "mcp_probe_memory",
498
- "MEMORY_QDRANT_API_KEY": "",
499
- "MEMORY_EMBEDDING_PROVIDER": "openai-compatible",
500
- "MEMORY_EMBEDDING_URL": "https://your-embedding-endpoint/v1/embeddings",
501
- "MEMORY_EMBEDDING_API_KEY": "your-api-key",
502
- "MEMORY_EMBEDDING_MODEL": "text-embedding-3-small",
503
- "MEMORY_SEARCH_LIMIT": "3",
504
- "MEMORY_SUMMARY_MAX_CHARS": "280"
505
- }
506
- }
507
- }
508
- }
536
+ "MEMORY_QDRANT_URL": "http://127.0.0.1:50008",
537
+ "MEMORY_EMBEDDING_PROVIDER": "openai-compatible",
538
+ "MEMORY_EMBEDDING_URL": "https://your-embedding-endpoint/v1/embeddings",
539
+ "MEMORY_EMBEDDING_API_KEY": "your-api-key",
540
+ "MEMORY_EMBEDDING_MODEL": "text-embedding-3-small"
509
541
  ```
510
542
 
511
543
  #### Memory Environment Variables
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":
@@ -0,0 +1,27 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { generateAgentsMdInner } from '../agents-md-template.js';
3
+ import { resolveProjectContextLayout } from '../project-context-layout.js';
4
+ const layout = resolveProjectContextLayout(process.cwd());
5
+ const baseInput = {
6
+ layout,
7
+ locale: 'zh-CN',
8
+ projectName: 'demo',
9
+ projectVersion: '1.0.0',
10
+ description: 'test',
11
+ language: 'TypeScript',
12
+ category: 'library',
13
+ docs: [],
14
+ projectRootPosix: '/repo',
15
+ graphReady: false,
16
+ };
17
+ describe('generateAgentsMdInner', () => {
18
+ it('includes memory workflow in zh-CN template', () => {
19
+ const md = generateAgentsMdInner({ ...baseInput });
20
+ expect(md).toContain('记忆');
21
+ expect(md).toContain('start_bugfix');
22
+ expect(md).toContain('memorize_asset');
23
+ expect(md).toContain('bugfix');
24
+ expect(md).toContain('search_memory');
25
+ expect(md).toContain('自动注入');
26
+ });
27
+ });
@@ -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,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,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
+ });
@@ -0,0 +1,80 @@
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { describe, expect, test } from "vitest";
5
+ import { detectDocumentLocale, discoverProjectRootFromLayout, layoutAbsPath, readLayoutManifest, relativeLink, resolveProjectContextLayout, writeLayoutManifest, } from "../project-context-layout.js";
6
+ import { mergeAgentsMdBlock } from "../merge-agents-md.js";
7
+ describe("project-context-layout", () => {
8
+ test("relativeLink from AGENTS.md to docs paths", () => {
9
+ const link = relativeLink("AGENTS.md", "docs/project-context/tech-stack.md");
10
+ expect(link === "docs/project-context/tech-stack.md" || link === "./docs/project-context/tech-stack.md").toBe(true);
11
+ });
12
+ test("default layout uses AGENTS.md and docs/", () => {
13
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), "layout-"));
14
+ const layout = resolveProjectContextLayout(root, {});
15
+ expect(layout.indexPath).toBe("AGENTS.md");
16
+ expect(layout.contextRoot).toBe("docs");
17
+ expect(layout.modularDir).toBe("docs/project-context");
18
+ expect(layout.latestMarkdownPath).toBe("docs/graph-insights/latest.md");
19
+ });
20
+ test("writeLayoutManifest is portable (no absolute projectRoot)", () => {
21
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), "layout-manifest-"));
22
+ const layout = resolveProjectContextLayout(root, {});
23
+ const rel = writeLayoutManifest(root, layout);
24
+ expect(rel).toBe("docs/.mcp-probe/layout.json");
25
+ const manifest = readLayoutManifest(root);
26
+ expect(manifest?.indexPath).toBe("AGENTS.md");
27
+ expect(manifest?.projectRoot).toBeUndefined();
28
+ expect(manifest?.projectRootEnv).toBe("MCP_PROJECT_ROOT");
29
+ expect(discoverProjectRootFromLayout(root)).toBe(path.resolve(root));
30
+ });
31
+ test("ignores stale absolute projectRoot in manifest", () => {
32
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), "layout-stale-root-"));
33
+ const layout = resolveProjectContextLayout(root, {});
34
+ writeLayoutManifest(root, layout);
35
+ const manifestPath = path.join(root, "docs", ".mcp-probe", "layout.json");
36
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
37
+ manifest.projectRoot = "Z:/nonexistent/wrong-root";
38
+ fs.writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, "utf8");
39
+ expect(discoverProjectRootFromLayout(path.join(root, "src"))).toBe(path.resolve(root));
40
+ });
41
+ test("infers root for custom contextRoot manifest path", () => {
42
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), "layout-custom-docs-"));
43
+ const layout = resolveProjectContextLayout(root, { docs_dir: "documentation" });
44
+ writeLayoutManifest(root, layout);
45
+ expect(fs.existsSync(path.join(root, "documentation", ".mcp-probe", "layout.json"))).toBe(true);
46
+ const fromSub = resolveProjectContextLayout(path.join(root, "src"), {});
47
+ expect(fromSub.projectRoot).toBe(path.resolve(root));
48
+ expect(fromSub.contextRoot).toBe("documentation");
49
+ });
50
+ test("discovers projectRoot from layout.json when cwd is subdirectory", () => {
51
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), "layout-discover-"));
52
+ const layout = resolveProjectContextLayout(root, {});
53
+ writeLayoutManifest(root, layout);
54
+ const subDir = path.join(root, "src", "tools");
55
+ fs.mkdirSync(subDir, { recursive: true });
56
+ expect(discoverProjectRootFromLayout(subDir)).toBe(path.resolve(root));
57
+ const fromSub = resolveProjectContextLayout(subDir, {});
58
+ expect(fromSub.projectRoot).toBe(path.resolve(root));
59
+ expect(layoutAbsPath(fromSub, "docs/project-context.md")).toBe(path.join(root, "docs", "project-context.md"));
60
+ });
61
+ test("detectDocumentLocale zh from README", () => {
62
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), "layout-zh-"));
63
+ fs.writeFileSync(path.join(root, "README.md"), "# 测试项目\n\n这是一个中文项目说明,用于单元测试。\n".repeat(5), "utf8");
64
+ expect(detectDocumentLocale(root)).toBe("zh-CN");
65
+ });
66
+ });
67
+ describe("mergeAgentsMdBlock", () => {
68
+ test("prepends block when no marker exists", () => {
69
+ const { content, mergeMode } = mergeAgentsMdBlock("# User rules\n", "## MCP block");
70
+ expect(mergeMode).toBe("prepended");
71
+ expect(content.startsWith("<!-- mcp-probe:context begin")).toBe(true);
72
+ expect(content).toContain("# User rules");
73
+ });
74
+ test("replaces and moves block to top", () => {
75
+ const existing = "# Footer\n\n<!-- mcp-probe:context begin -->\nold\n<!-- mcp-probe:context end -->\n";
76
+ const { content, mergeMode } = mergeAgentsMdBlock(existing, "new inner");
77
+ expect(mergeMode).toBe("replaced-and-moved-to-top");
78
+ expect(content.indexOf("new inner")).toBeLessThan(content.indexOf("# Footer"));
79
+ });
80
+ });
@@ -0,0 +1,25 @@
1
+ import type { DocumentLocale, ProjectContextLayout } from "./project-context-layout.js";
2
+ export interface AgentsMdTemplateInput {
3
+ layout: ProjectContextLayout;
4
+ locale: DocumentLocale;
5
+ projectName: string;
6
+ projectVersion: string;
7
+ description: string;
8
+ language: string;
9
+ framework?: string;
10
+ category: string;
11
+ docs: Array<{
12
+ file: string;
13
+ title: string;
14
+ purpose: string;
15
+ }>;
16
+ projectRootPosix: string;
17
+ graphReady: boolean;
18
+ /** @deprecated kept for callers; memory rules are always included in AGENTS.md */
19
+ memoryEnabled?: boolean;
20
+ }
21
+ /**
22
+ * Ultra-compact AGENTS.md body (inside mcp-probe block).
23
+ */
24
+ export declare function generateAgentsMdInner(input: AgentsMdTemplateInput): string;
25
+ export declare function generateAgentsMdTemplate(input: AgentsMdTemplateInput): string;