mcp-probe-kit 3.3.0 → 3.6.0

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 (81) hide show
  1. package/README.md +15 -7
  2. package/build/index.js +45 -79
  3. package/build/lib/__tests__/agents-md-template.unit.test.js +1 -0
  4. package/build/lib/__tests__/dev-workflow.unit.test.d.ts +1 -0
  5. package/build/lib/__tests__/dev-workflow.unit.test.js +22 -0
  6. package/build/lib/__tests__/handles.unit.test.d.ts +1 -0
  7. package/build/lib/__tests__/handles.unit.test.js +19 -0
  8. package/build/lib/__tests__/mcp-apps.unit.test.d.ts +1 -0
  9. package/build/lib/__tests__/mcp-apps.unit.test.js +52 -0
  10. package/build/lib/__tests__/mcp-tool-skill-registry.unit.test.d.ts +1 -0
  11. package/build/lib/__tests__/mcp-tool-skill-registry.unit.test.js +18 -0
  12. package/build/lib/__tests__/memory-client.unit.test.js +105 -0
  13. package/build/lib/__tests__/spec-gate.unit.test.d.ts +1 -0
  14. package/build/lib/__tests__/spec-gate.unit.test.js +54 -0
  15. package/build/lib/__tests__/task-defaults.unit.test.d.ts +1 -0
  16. package/build/lib/__tests__/task-defaults.unit.test.js +27 -0
  17. package/build/lib/__tests__/workflow-skill-installer.unit.test.d.ts +1 -0
  18. package/build/lib/__tests__/workflow-skill-installer.unit.test.js +110 -0
  19. package/build/lib/agents-md-template.js +10 -2
  20. package/build/lib/dev-workflow.d.ts +36 -0
  21. package/build/lib/dev-workflow.js +497 -0
  22. package/build/lib/handles.d.ts +31 -0
  23. package/build/lib/handles.js +36 -0
  24. package/build/lib/mcp-apps.d.ts +14 -0
  25. package/build/lib/mcp-apps.js +234 -0
  26. package/build/lib/mcp-tool-skill-registry.d.ts +46 -0
  27. package/build/lib/mcp-tool-skill-registry.js +256 -0
  28. package/build/lib/memory-client.d.ts +19 -0
  29. package/build/lib/memory-client.js +69 -0
  30. package/build/lib/memory-orchestration.d.ts +5 -0
  31. package/build/lib/memory-orchestration.js +20 -0
  32. package/build/lib/merge-agents-md.d.ts +2 -2
  33. package/build/lib/merge-agents-md.js +7 -4
  34. package/build/lib/output-schema-registry.d.ts +11 -0
  35. package/build/lib/output-schema-registry.js +80 -0
  36. package/build/lib/spec-gate.d.ts +32 -0
  37. package/build/lib/spec-gate.js +83 -0
  38. package/build/lib/task-defaults.d.ts +6 -0
  39. package/build/lib/task-defaults.js +20 -0
  40. package/build/lib/tool-annotations.js +3 -0
  41. package/build/lib/toolset-manager.js +2 -0
  42. package/build/lib/workflow-skill-installer.d.ts +38 -0
  43. package/build/lib/workflow-skill-installer.js +158 -0
  44. package/build/lib/workflow-skill-template.d.ts +9 -0
  45. package/build/lib/workflow-skill-template.js +85 -0
  46. package/build/lib/workflow-skill-version.d.ts +15 -0
  47. package/build/lib/workflow-skill-version.js +68 -0
  48. package/build/schemas/index.d.ts +110 -0
  49. package/build/schemas/index.js +1 -0
  50. package/build/schemas/memory-tools.d.ts +83 -0
  51. package/build/schemas/memory-tools.js +43 -0
  52. package/build/schemas/orchestration-tools.d.ts +8 -0
  53. package/build/schemas/orchestration-tools.js +8 -0
  54. package/build/schemas/output/code-insight-tools.d.ts +65 -0
  55. package/build/schemas/output/code-insight-tools.js +42 -0
  56. package/build/schemas/output/memory-tools.d.ts +576 -0
  57. package/build/schemas/output/memory-tools.js +148 -0
  58. package/build/schemas/project-tools.d.ts +19 -0
  59. package/build/schemas/project-tools.js +20 -0
  60. package/build/tools/__tests__/delete_memory_asset.unit.test.d.ts +1 -0
  61. package/build/tools/__tests__/delete_memory_asset.unit.test.js +87 -0
  62. package/build/tools/__tests__/search_memory.unit.test.js +1 -0
  63. package/build/tools/__tests__/start_bugfix.unit.test.js +34 -1
  64. package/build/tools/__tests__/update_memory_asset.unit.test.d.ts +1 -0
  65. package/build/tools/__tests__/update_memory_asset.unit.test.js +82 -0
  66. package/build/tools/__tests__/workflow.unit.test.d.ts +1 -0
  67. package/build/tools/__tests__/workflow.unit.test.js +24 -0
  68. package/build/tools/code_insight.js +5 -1
  69. package/build/tools/delete_memory_asset.d.ts +7 -0
  70. package/build/tools/delete_memory_asset.js +57 -0
  71. package/build/tools/index.d.ts +3 -0
  72. package/build/tools/index.js +3 -0
  73. package/build/tools/search_memory.js +5 -2
  74. package/build/tools/start_bugfix.js +37 -4
  75. package/build/tools/start_feature.js +4 -3
  76. package/build/tools/start_ui.js +5 -4
  77. package/build/tools/update_memory_asset.d.ts +7 -0
  78. package/build/tools/update_memory_asset.js +98 -0
  79. package/build/tools/workflow.d.ts +13 -0
  80. package/build/tools/workflow.js +69 -0
  81. package/package.json +5 -3
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 27 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 30 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 **27 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 **30 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 **27 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 27 tools
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
 
@@ -50,7 +50,7 @@ A powerful MCP (Model Context Protocol) server providing **27 tools** covering t
50
50
 
51
51
  ## ✨ Core Features
52
52
 
53
- ### 📦 27 Tools
53
+ ### 📦 29 Tools
54
54
 
55
55
  - **🔄 Workflow Orchestration** (6 tools) - One-click complex development workflows
56
56
  - `start_feature`, `start_bugfix`, `start_onboard`, `start_ui`, `start_product`, `start_ralph`
@@ -64,8 +64,8 @@ A powerful MCP (Model Context Protocol) server providing **27 tools** covering t
64
64
  - `init_project`, `init_project_context`, `add_feature`, `check_spec`, `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** (4 tools) - Reusable asset memory
68
- - `search_memory`, `read_memory_asset`, `memorize_asset`, `scan_and_extract_patterns`
67
+ - **🧠 Memory** (6 tools) - Reusable asset memory
68
+ - `search_memory`, `read_memory_asset`, `memorize_asset`, `update_memory_asset`, `delete_memory_asset`, `scan_and_extract_patterns`
69
69
 
70
70
  ### 🛡️ Quality Constraints (single source of truth)
71
71
 
@@ -108,6 +108,8 @@ All hard quality rules live in one module (`src/lib/quality-constraints.ts`) and
108
108
  - `search_memory` - Semantic search across the shared memory pool (optionally prefer `type` / `tags`); text output includes `id`, `score`, summary, description, and a `--- content ---` body (default up to 1500 chars via `MEMORY_SEARCH_CONTENT_MAX_CHARS`)
109
109
  - `memorize_asset` - Persist reusable code/spec/pattern assets into vector memory
110
110
  - `read_memory_asset` - Read full asset content by `asset_id` (text output includes the full `content` body)
111
+ - `update_memory_asset` - Update an existing asset by `asset_id` (preserves ID; `content` changes re-embed)
112
+ - `delete_memory_asset` - Delete an asset by `asset_id` from the shared pool
111
113
  - `scan_and_extract_patterns` - Extract reusable patterns from code/file/directory before deciding whether to persist
112
114
 
113
115
  **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`.
@@ -478,7 +480,13 @@ Use in config file:
478
480
 
479
481
  ### Optional Memory System Setup
480
482
 
481
- If you want to use `memorize_asset`, `read_memory_asset`, and `scan_and_extract_patterns`, you need both:
483
+ If you want to use `memorize_asset`, `update_memory_asset`, `read_memory_asset`, `delete_memory_asset`, and `scan_and_extract_patterns`, configure as follows:
484
+
485
+ - **Qdrant only** (`MEMORY_QDRANT_URL`): `read_memory_asset`, `delete_memory_asset`
486
+ - **Qdrant + embedding** (all three `MEMORY_*` write/search vars): `search_memory`, `memorize_asset`, `update_memory_asset`
487
+ - **No memory backend**: `scan_and_extract_patterns` (local scan only; persist via `memorize_asset` when ready)
488
+
489
+ For full write/search you need both:
482
490
 
483
491
  1. A **Qdrant** vector database
484
492
  2. An **embedding service** in either `ollama` or `openai-compatible` mode
package/build/index.js CHANGED
@@ -5,11 +5,16 @@ 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, checkSpec, startFeature, startBugfix, startOnboard, startRalph, interview, askUser, uiDesignSystem, uiSearch, syncUiData, startUi, startProduct, gitWorkReport, searchMemory, readMemoryAsset, memorizeAsset, scanAndExtractPatterns } from "./tools/index.js";
8
+ import { initProject, gencommit, codeReview, codeInsight, gentest, refactor, initProjectContext, addFeature, fixBug, estimate, checkSpec, workflow, startFeature, startBugfix, startOnboard, startRalph, interview, askUser, uiDesignSystem, uiSearch, syncUiData, startUi, startProduct, gitWorkReport, searchMemory, readMemoryAsset, memorizeAsset, deleteMemoryAsset, updateMemoryAsset, scanAndExtractPatterns } 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";
12
12
  import { withToolAnnotations } from "./lib/tool-annotations.js";
13
+ import { withOutputSchema } from "./lib/output-schema-registry.js";
14
+ import { shouldAutoEscalateToTask } from "./lib/task-defaults.js";
15
+ import { attachHandles } from "./lib/handles.js";
16
+ import { buildMcpAppHtml, isMcpUiAppTool } from "./lib/mcp-apps.js";
17
+ import { ensureMcpProbeKitBootstrapForToolCall } from "./lib/workflow-skill-installer.js";
13
18
  import { isAbortError, } from "./lib/tool-execution-context.js";
14
19
  const EXTENSIONS_CAPABILITY_KEY = "io.github.mybolide/extensions";
15
20
  const MAX_UI_APP_RESOURCES = 30;
@@ -82,80 +87,6 @@ function withTraceMeta(result, traceMeta) {
82
87
  },
83
88
  };
84
89
  }
85
- function escapeHtml(value) {
86
- return value
87
- .replace(/&/g, "&")
88
- .replace(/</g, "&lt;")
89
- .replace(/>/g, "&gt;")
90
- .replace(/"/g, "&quot;")
91
- .replace(/'/g, "&#39;");
92
- }
93
- function isUiTool(name) {
94
- return [
95
- "ui_design_system",
96
- "ui_search",
97
- "sync_ui_data",
98
- "start_ui",
99
- "start_product",
100
- ].includes(name);
101
- }
102
- function buildUiResourceHtml(name, args, result) {
103
- const structured = result.structuredContent
104
- ? JSON.stringify(result.structuredContent, null, 2)
105
- : "{}";
106
- const argJson = JSON.stringify(args ?? {}, null, 2);
107
- const textBlocks = Array.isArray(result.content)
108
- ? result.content
109
- .map((item) => {
110
- if (!item || typeof item !== "object") {
111
- return "";
112
- }
113
- const text = item.text;
114
- return typeof text === "string" ? text : "";
115
- })
116
- .filter(Boolean)
117
- .join("\n\n")
118
- : "";
119
- const now = new Date().toISOString();
120
- return `<!doctype html>
121
- <html lang="zh-CN">
122
- <head>
123
- <meta charset="utf-8">
124
- <meta name="viewport" content="width=device-width, initial-scale=1">
125
- <title>${escapeHtml(name)} · MCP Apps</title>
126
- <style>
127
- :root { color-scheme: light; }
128
- body { font-family: "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif; margin: 0; background: #f4f7fb; color: #1e2a35; }
129
- .wrap { max-width: 960px; margin: 0 auto; padding: 24px; }
130
- .card { background: #fff; border-radius: 14px; padding: 18px; box-shadow: 0 4px 18px rgba(30,42,53,.08); margin-bottom: 16px; }
131
- h1 { margin: 0 0 8px; font-size: 24px; }
132
- h2 { margin: 0 0 10px; font-size: 16px; color: #2f4a65; }
133
- pre { white-space: pre-wrap; word-break: break-word; background: #0f1720; color: #d9e7f7; border-radius: 10px; padding: 12px; font-size: 12px; line-height: 1.45; }
134
- .meta { color: #4f6880; font-size: 12px; }
135
- </style>
136
- </head>
137
- <body>
138
- <div class="wrap">
139
- <div class="card">
140
- <h1>${escapeHtml(name)}</h1>
141
- <div class="meta">Generated at ${escapeHtml(now)} · MCP Apps preview</div>
142
- </div>
143
- <div class="card">
144
- <h2>Text Output</h2>
145
- <pre>${escapeHtml(textBlocks || "(no text output)")}</pre>
146
- </div>
147
- <div class="card">
148
- <h2>Structured Content</h2>
149
- <pre>${escapeHtml(structured)}</pre>
150
- </div>
151
- <div class="card">
152
- <h2>Arguments</h2>
153
- <pre>${escapeHtml(argJson)}</pre>
154
- </div>
155
- </div>
156
- </body>
157
- </html>`;
158
- }
159
90
  function putUiAppResource(toolName, args, result) {
160
91
  const uid = `${Date.now()}-${Math.random().toString(16).slice(2, 10)}`;
161
92
  const uri = `ui://mcp-probe-kit/${toolName}/${uid}`;
@@ -164,7 +95,7 @@ function putUiAppResource(toolName, args, result) {
164
95
  name: `UI Preview · ${toolName}`,
165
96
  description: `MCP Apps preview generated by ${toolName}`,
166
97
  mimeType: "text/html",
167
- text: buildUiResourceHtml(toolName, args, result),
98
+ text: buildMcpAppHtml(toolName, args, result),
168
99
  createdAt: new Date().toISOString(),
169
100
  };
170
101
  uiAppResources.set(uri, entry);
@@ -384,13 +315,26 @@ function rememberGraphSnapshot(toolName, result) {
384
315
  }
385
316
  return snapshot;
386
317
  }
318
+ function withStructuredHandles(result, handles) {
319
+ if (!result.structuredContent || typeof result.structuredContent !== "object" || Array.isArray(result.structuredContent)) {
320
+ return result;
321
+ }
322
+ return {
323
+ ...result,
324
+ structuredContent: attachHandles(result.structuredContent, handles),
325
+ };
326
+ }
387
327
  function decorateResult(toolName, args, raw, traceMeta) {
388
328
  let result = withTraceMeta(raw, traceMeta);
389
329
  const snapshot = rememberGraphSnapshot(toolName, result);
390
330
  if (snapshot) {
391
331
  result = withGraphSnapshotMeta(result, snapshot);
332
+ result = withStructuredHandles(result, {
333
+ graph_snapshot: snapshot.uri,
334
+ graph_resource: snapshot.uri,
335
+ });
392
336
  }
393
- if (uiAppsEnabled && isUiTool(toolName) && !result.isError) {
337
+ if (uiAppsEnabled && isMcpUiAppTool(toolName) && !result.isError) {
394
338
  const resourceUri = putUiAppResource(toolName, args, result);
395
339
  result = withUiResourceMeta(result, resourceUri);
396
340
  }
@@ -411,10 +355,23 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
411
355
  const filteredTools = filterTools(allToolSchemas, toolset);
412
356
  console.error(`[MCP Probe Kit] 当前工具集: ${toolset} (${filteredTools.length}/${allToolSchemas.length} 个工具)`);
413
357
  return {
414
- tools: filteredTools.map(withToolAnnotations),
358
+ tools: filteredTools.map((tool) => withOutputSchema(withToolAnnotations(tool))),
415
359
  };
416
360
  });
417
361
  async function executeTool(name, args, context) {
362
+ const bootstrap = ensureMcpProbeKitBootstrapForToolCall(name, args);
363
+ if (bootstrap?.skill.created) {
364
+ console.error(`[MCP Probe Kit] 已创建 MCP Skill: ${bootstrap.skill.skillRelPath} (v${bootstrap.skill.version})`);
365
+ }
366
+ else if (bootstrap?.skill.updated) {
367
+ console.error(`[MCP Probe Kit] 已升级 MCP Skill: ${bootstrap.skill.skillRelPath} ${bootstrap.skill.previousVersion ?? "?"} → v${bootstrap.skill.version}`);
368
+ }
369
+ if (bootstrap?.agentsMd.created) {
370
+ console.error(`[MCP Probe Kit] 已创建 AGENTS.md(含 Skill 引用): ${bootstrap.agentsMd.path}`);
371
+ }
372
+ else if (bootstrap?.agentsMd.updated) {
373
+ console.error(`[MCP Probe Kit] 已更新 AGENTS.md(添加 Skill 引用): ${bootstrap.agentsMd.path}`);
374
+ }
418
375
  switch (name) {
419
376
  case "init_project":
420
377
  return await initProject(args);
@@ -430,6 +387,8 @@ async function executeTool(name, args, context) {
430
387
  return await refactor(args);
431
388
  case "init_project_context":
432
389
  return await initProjectContext(args);
390
+ case "workflow":
391
+ return await workflow(args);
433
392
  case "add_feature":
434
393
  return await addFeature(args);
435
394
  case "check_spec":
@@ -468,6 +427,10 @@ async function executeTool(name, args, context) {
468
427
  return await readMemoryAsset(args);
469
428
  case "memorize_asset":
470
429
  return await memorizeAsset(args);
430
+ case "delete_memory_asset":
431
+ return await deleteMemoryAsset(args);
432
+ case "update_memory_asset":
433
+ return await updateMemoryAsset(args);
471
434
  case "scan_and_extract_patterns":
472
435
  return await scanAndExtractPatterns(args);
473
436
  default:
@@ -491,8 +454,11 @@ function isTerminalTaskStatus(status) {
491
454
  // 处理工具调用
492
455
  server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
493
456
  const { name, arguments: args } = request.params;
494
- const taskRequest = request.params.task;
457
+ let taskRequest = request.params.task;
495
458
  const traceMeta = getTraceMeta(extra._meta);
459
+ if (shouldAutoEscalateToTask(name, Boolean(taskRequest))) {
460
+ taskRequest = taskRequest ?? {};
461
+ }
496
462
  const emitProgress = async (progress, message) => {
497
463
  const progressToken = extra._meta?.progressToken;
498
464
  if (progressToken === undefined) {
@@ -20,6 +20,7 @@ describe('generateAgentsMdInner', () => {
20
20
  expect(md).toContain('记忆');
21
21
  expect(md).toContain('start_bugfix');
22
22
  expect(md).toContain('memorize_asset');
23
+ expect(md).toContain('mcp-probe-kit/SKILL.md');
23
24
  expect(md).toContain('bugfix');
24
25
  expect(md).toContain('search_memory');
25
26
  expect(md).toContain('自动注入');
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,22 @@
1
+ import { describe, expect, test } from 'vitest';
2
+ import { buildDevWorkflow, detectWorkflowScenario } from '../dev-workflow.js';
3
+ describe('dev-workflow', () => {
4
+ test('识别 bugfix 场景', () => {
5
+ const result = detectWorkflowScenario('登录报错 NullReference 需要排查修复');
6
+ expect(result.scenario).toBe('bugfix');
7
+ });
8
+ test('识别 feature 场景', () => {
9
+ const result = detectWorkflowScenario('开发用户认证新功能');
10
+ expect(result.scenario).toBe('feature');
11
+ });
12
+ test('bugfix 计划首工具为 start_bugfix', () => {
13
+ const plan = buildDevWorkflow('TypeError in checkout');
14
+ expect(plan.firstTool).toBe('start_bugfix');
15
+ expect(plan.phases.some((p) => p.steps.some((s) => s.tool === 'start_bugfix'))).toBe(true);
16
+ });
17
+ test('显式 scenario 覆盖推断', () => {
18
+ const plan = buildDevWorkflow('随便', { scenario: 'ui' });
19
+ expect(plan.scenario).toBe('ui');
20
+ expect(plan.firstTool).toBe('start_ui');
21
+ });
22
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,19 @@
1
+ import { describe, expect, test } from 'vitest';
2
+ import { attachHandles, buildMemoryAssetHandles, mergeHandles, } from '../../lib/handles.js';
3
+ describe('handles', () => {
4
+ test('buildMemoryAssetHandles 生成 read 默认工具名', () => {
5
+ const handles = buildMemoryAssetHandles([{ id: 'a-1', name: 'demo' }]);
6
+ expect(handles).toEqual([{ id: 'a-1', name: 'demo', tool: 'read_memory_asset' }]);
7
+ });
8
+ test('attachHandles 合并已有 handles', () => {
9
+ const result = attachHandles({ handles: { graph_resource: 'probe://graph/latest' } }, { memory_assets: buildMemoryAssetHandles([{ id: 'x' }]) });
10
+ expect(result.handles.graph_resource).toBe('probe://graph/latest');
11
+ expect(result.handles.memory_assets).toHaveLength(1);
12
+ });
13
+ test('mergeHandles 后者覆盖 graph 字段', () => {
14
+ expect(mergeHandles({ graph_resource: 'a' }, { graph_resource: 'b', graph_snapshot: 'probe://graph/s1' })).toEqual({
15
+ graph_resource: 'b',
16
+ graph_snapshot: 'probe://graph/s1',
17
+ });
18
+ });
19
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,52 @@
1
+ import { describe, expect, test } from 'vitest';
2
+ import { buildCodeInsightAppHtml, buildSearchMemoryAppHtml, isMcpUiAppTool, } from '../mcp-apps.js';
3
+ describe('mcp-apps', () => {
4
+ test('search_memory 与 code_insight 属于 UI App 工具', () => {
5
+ expect(isMcpUiAppTool('search_memory')).toBe(true);
6
+ expect(isMcpUiAppTool('code_insight')).toBe(true);
7
+ expect(isMcpUiAppTool('gencommit')).toBe(false);
8
+ });
9
+ test('search_memory App 渲染命中卡片与 handles', () => {
10
+ const html = buildSearchMemoryAppHtml({ query: 'proxy bug' }, {
11
+ structuredContent: {
12
+ query: 'proxy bug',
13
+ count: 1,
14
+ results: [
15
+ {
16
+ id: 'asset-1',
17
+ name: 'feishu-proxy',
18
+ type: 'bugfix',
19
+ score: 0.88,
20
+ summary: 'proxy mismatch',
21
+ content: '【修复】done',
22
+ tags: ['bugfix'],
23
+ },
24
+ ],
25
+ handles: {
26
+ memory_assets: [{ id: 'asset-1', tool: 'read_memory_asset' }],
27
+ },
28
+ },
29
+ });
30
+ expect(html).toContain('search_memory');
31
+ expect(html).toContain('feishu-proxy');
32
+ expect(html).toContain('asset-1');
33
+ expect(html).toContain('read_memory_asset');
34
+ expect(html).toContain('proxy mismatch');
35
+ });
36
+ test('code_insight App 渲染状态与执行表', () => {
37
+ const html = buildCodeInsightAppHtml({ mode: 'auto' }, {
38
+ structuredContent: {
39
+ status: 'ok',
40
+ provider: 'gitnexus',
41
+ summary: 'login flow analyzed',
42
+ mode: { requested: 'auto', resolved: 'query' },
43
+ executions: [{ tool: 'query', ok: true, text: 'found login handler' }],
44
+ handles: { graph_resource: 'probe://graph/latest' },
45
+ },
46
+ });
47
+ expect(html).toContain('code_insight');
48
+ expect(html).toContain('login flow analyzed');
49
+ expect(html).toContain('probe://graph/latest');
50
+ expect(html).toContain('found login handler');
51
+ });
52
+ });
@@ -0,0 +1,18 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { allToolSchemas } from "../../schemas/index.js";
3
+ import { formatSkillRegistryMismatchMessage, listMcpToolSkillRegistryNames, validateMcpToolSkillRegistry, } from "../mcp-tool-skill-registry.js";
4
+ import { generateWorkflowSkillContent } from "../workflow-skill-template.js";
5
+ describe("mcp-tool-skill-registry", () => {
6
+ it("与 allToolSchemas 工具名一一对应", () => {
7
+ const registered = allToolSchemas.map((tool) => tool.name).sort();
8
+ const result = validateMcpToolSkillRegistry(registered);
9
+ expect(result.ok, formatSkillRegistryMismatchMessage(result)).toBe(true);
10
+ expect(listMcpToolSkillRegistryNames().length).toBe(registered.length);
11
+ });
12
+ it("生成的 Skill 正文包含每个注册工具", () => {
13
+ const content = generateWorkflowSkillContent("0.0.0-test");
14
+ for (const name of listMcpToolSkillRegistryNames()) {
15
+ expect(content).toContain(`\`${name}\``);
16
+ }
17
+ });
18
+ });
@@ -80,4 +80,109 @@ describe('memory-client 去重逻辑', () => {
80
80
  expect(result.normalizedContentHash).toBe(existingAsset.normalizedContentHash);
81
81
  expect(fetchMock.mock.calls.some(([url]) => String(url).includes('/points?wait=true'))).toBe(false);
82
82
  });
83
+ test('deleteAsset 删除已存在资产并返回快照', async () => {
84
+ vi.stubEnv('MEMORY_QDRANT_URL', 'http://127.0.0.1:50008');
85
+ const asset = {
86
+ id: 'asset-to-delete',
87
+ name: 'ObsoletePattern',
88
+ type: 'pattern',
89
+ description: '过时模式',
90
+ summary: '不再使用',
91
+ content: 'old snippet',
92
+ tags: ['pattern'],
93
+ confidence: 0.7,
94
+ createdAt: '2026-01-01T00:00:00.000Z',
95
+ updatedAt: '2026-01-01T00:00:00.000Z',
96
+ };
97
+ const fetchMock = vi.fn(async (input, init) => {
98
+ const url = String(input);
99
+ if (url.includes(`/points/${asset.id}`) && init?.method !== 'POST' && init?.method !== 'PUT') {
100
+ return new Response(JSON.stringify({ result: { payload: asset } }), {
101
+ status: 200,
102
+ headers: { 'Content-Type': 'application/json' },
103
+ });
104
+ }
105
+ if (url.includes('/points/delete')) {
106
+ return new Response(JSON.stringify({ status: 'ok' }), {
107
+ status: 200,
108
+ headers: { 'Content-Type': 'application/json' },
109
+ });
110
+ }
111
+ throw new Error(`unexpected fetch: ${url}`);
112
+ });
113
+ globalThis.fetch = fetchMock;
114
+ const client = new MemoryClient();
115
+ const result = await client.deleteAsset(asset.id);
116
+ expect(result.deleted).toBe(true);
117
+ expect(result.asset?.name).toBe('ObsoletePattern');
118
+ expect(fetchMock.mock.calls.some(([url, init]) => String(url).includes('/points/delete') && init?.method === 'POST')).toBe(true);
119
+ });
120
+ test('deleteAsset 对不存在的资产返回 deleted=false', async () => {
121
+ vi.stubEnv('MEMORY_QDRANT_URL', 'http://127.0.0.1:50008');
122
+ const fetchMock = vi.fn(async (input) => {
123
+ const url = String(input);
124
+ if (url.includes('/points/missing-id')) {
125
+ return new Response(JSON.stringify({ result: null }), {
126
+ status: 200,
127
+ headers: { 'Content-Type': 'application/json' },
128
+ });
129
+ }
130
+ throw new Error(`unexpected fetch: ${url}`);
131
+ });
132
+ globalThis.fetch = fetchMock;
133
+ const client = new MemoryClient();
134
+ const result = await client.deleteAsset('missing-id');
135
+ expect(result.deleted).toBe(false);
136
+ expect(result.asset).toBeNull();
137
+ });
138
+ test('updateAsset 更新已存在资产并保留 createdAt', async () => {
139
+ vi.stubEnv('MEMORY_QDRANT_URL', 'http://127.0.0.1:50008');
140
+ vi.stubEnv('MEMORY_EMBEDDING_URL', 'http://127.0.0.1:11434/api/embeddings');
141
+ vi.stubEnv('MEMORY_EMBEDDING_MODEL', 'nomic-embed-text');
142
+ const asset = {
143
+ id: 'asset-to-update',
144
+ name: 'PatternA',
145
+ type: 'pattern',
146
+ description: '旧描述',
147
+ summary: '旧摘要',
148
+ content: 'old snippet',
149
+ tags: ['pattern'],
150
+ confidence: 0.7,
151
+ createdAt: '2026-01-01T00:00:00.000Z',
152
+ updatedAt: '2026-01-01T00:00:00.000Z',
153
+ };
154
+ const fetchMock = vi.fn(async (input, init) => {
155
+ const url = String(input);
156
+ if (url.endsWith('/api/embeddings')) {
157
+ return new Response(JSON.stringify({ embedding: [0.1, 0.2, 0.3] }), {
158
+ status: 200,
159
+ headers: { 'Content-Type': 'application/json' },
160
+ });
161
+ }
162
+ if (url.includes(`/points/${asset.id}`) && init?.method !== 'POST' && init?.method !== 'PUT') {
163
+ return new Response(JSON.stringify({ result: { payload: asset } }), {
164
+ status: 200,
165
+ headers: { 'Content-Type': 'application/json' },
166
+ });
167
+ }
168
+ if (url.endsWith('/collections/mcp_probe_memory') && !init?.method) {
169
+ return new Response('', { status: 200 });
170
+ }
171
+ if (url.includes('/points?wait=true') && init?.method === 'PUT') {
172
+ return new Response(JSON.stringify({ status: 'ok' }), {
173
+ status: 200,
174
+ headers: { 'Content-Type': 'application/json' },
175
+ });
176
+ }
177
+ throw new Error(`unexpected fetch: ${url}`);
178
+ });
179
+ globalThis.fetch = fetchMock;
180
+ const client = new MemoryClient();
181
+ const result = await client.updateAsset(asset.id, { summary: '新摘要' });
182
+ expect(result.updated).toBe(true);
183
+ expect(result.asset?.summary).toBe('新摘要');
184
+ expect(result.asset?.createdAt).toBe(asset.createdAt);
185
+ expect(result.asset?.id).toBe(asset.id);
186
+ expect(fetchMock.mock.calls.some(([url, init]) => String(url).includes('/points?wait=true') && init?.method === 'PUT')).toBe(true);
187
+ });
83
188
  });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,54 @@
1
+ import * as fs from 'node:fs';
2
+ import * as os from 'node:os';
3
+ import * as path from 'node:path';
4
+ import { afterEach, describe, expect, test } from 'vitest';
5
+ import { resolveBugfixFeatureName, resolveBugfixSpecGate, specArtifactsExist, } from '../spec-gate.js';
6
+ const tempDirs = [];
7
+ afterEach(() => {
8
+ while (tempDirs.length > 0) {
9
+ const dir = tempDirs.pop();
10
+ if (dir) {
11
+ fs.rmSync(dir, { recursive: true, force: true });
12
+ }
13
+ }
14
+ });
15
+ function makeSpecTree(featureName) {
16
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'spec-gate-'));
17
+ tempDirs.push(root);
18
+ const specDir = path.join(root, 'docs', 'specs', featureName);
19
+ fs.mkdirSync(specDir, { recursive: true });
20
+ fs.writeFileSync(path.join(specDir, 'requirements.md'), '# requirements\n');
21
+ return root;
22
+ }
23
+ describe('spec-gate', () => {
24
+ test('显式 feature_name 优先', () => {
25
+ const root = makeSpecTree('user-auth');
26
+ expect(resolveBugfixFeatureName('payment', root, 'docs', 'user-auth login failed')).toBe('payment');
27
+ });
28
+ test('仅一个 spec 目录时自动识别', () => {
29
+ const root = makeSpecTree('only-feature');
30
+ expect(resolveBugfixFeatureName('', root, 'docs', 'random error')).toBe('only-feature');
31
+ });
32
+ test('错误描述唯一匹配 spec 目录', () => {
33
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'spec-gate-'));
34
+ tempDirs.push(root);
35
+ for (const name of ['user-auth', 'billing']) {
36
+ const specDir = path.join(root, 'docs', 'specs', name);
37
+ fs.mkdirSync(specDir, { recursive: true });
38
+ fs.writeFileSync(path.join(specDir, 'tasks.md'), '# tasks\n');
39
+ }
40
+ expect(resolveBugfixFeatureName('', root, 'docs', 'billing invoice total mismatch')).toBe('billing');
41
+ });
42
+ test('resolveBugfixSpecGate 在规格存在时返回上下文', () => {
43
+ const root = makeSpecTree('checkout-flow');
44
+ const gate = resolveBugfixSpecGate({
45
+ featureName: 'checkout-flow',
46
+ projectRoot: root,
47
+ docsDir: 'docs',
48
+ hintText: 'checkout failed',
49
+ });
50
+ expect(gate?.featureName).toBe('checkout-flow');
51
+ expect(gate?.specDir).toBe('docs/specs/checkout-flow');
52
+ expect(specArtifactsExist(root, 'docs', 'checkout-flow')).toBe(true);
53
+ });
54
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,27 @@
1
+ import { describe, expect, test, afterEach } from 'vitest';
2
+ import { isAutoTaskTool, shouldAutoEscalateToTask } from '../task-defaults.js';
3
+ const originalDisable = process.env.MCP_DISABLE_AUTO_TASK;
4
+ afterEach(() => {
5
+ if (originalDisable === undefined) {
6
+ delete process.env.MCP_DISABLE_AUTO_TASK;
7
+ }
8
+ else {
9
+ process.env.MCP_DISABLE_AUTO_TASK = originalDisable;
10
+ }
11
+ });
12
+ describe('task-defaults', () => {
13
+ test('code_insight 与 scan 属于自动 Task 工具', () => {
14
+ expect(isAutoTaskTool('code_insight')).toBe(true);
15
+ expect(isAutoTaskTool('scan_and_extract_patterns')).toBe(true);
16
+ expect(isAutoTaskTool('search_memory')).toBe(false);
17
+ });
18
+ test('默认对长耗时工具自动升级为 Task', () => {
19
+ delete process.env.MCP_DISABLE_AUTO_TASK;
20
+ expect(shouldAutoEscalateToTask('code_insight', false)).toBe(true);
21
+ expect(shouldAutoEscalateToTask('code_insight', true)).toBe(false);
22
+ });
23
+ test('MCP_DISABLE_AUTO_TASK=1 时关闭自动 Task', () => {
24
+ process.env.MCP_DISABLE_AUTO_TASK = '1';
25
+ expect(shouldAutoEscalateToTask('code_insight', false)).toBe(false);
26
+ });
27
+ });