mcp-probe-kit 3.3.0 → 3.6.1

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 (88) hide show
  1. package/README.md +781 -765
  2. package/build/index.js +198 -191
  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/__tests__/workspace-root.unit.test.d.ts +1 -0
  20. package/build/lib/__tests__/workspace-root.unit.test.js +31 -0
  21. package/build/lib/agents-md-template.js +10 -2
  22. package/build/lib/dev-workflow.d.ts +36 -0
  23. package/build/lib/dev-workflow.js +497 -0
  24. package/build/lib/handles.d.ts +31 -0
  25. package/build/lib/handles.js +36 -0
  26. package/build/lib/mcp-apps.d.ts +14 -0
  27. package/build/lib/mcp-apps.js +234 -0
  28. package/build/lib/mcp-tool-skill-registry.d.ts +46 -0
  29. package/build/lib/mcp-tool-skill-registry.js +256 -0
  30. package/build/lib/memory-client.d.ts +19 -0
  31. package/build/lib/memory-client.js +69 -0
  32. package/build/lib/memory-orchestration.d.ts +5 -0
  33. package/build/lib/memory-orchestration.js +20 -0
  34. package/build/lib/merge-agents-md.d.ts +2 -2
  35. package/build/lib/merge-agents-md.js +7 -4
  36. package/build/lib/output-schema-registry.d.ts +11 -0
  37. package/build/lib/output-schema-registry.js +80 -0
  38. package/build/lib/spec-gate.d.ts +32 -0
  39. package/build/lib/spec-gate.js +83 -0
  40. package/build/lib/task-defaults.d.ts +6 -0
  41. package/build/lib/task-defaults.js +20 -0
  42. package/build/lib/tool-annotations.js +3 -0
  43. package/build/lib/toolset-manager.js +2 -0
  44. package/build/lib/workflow-skill-installer.d.ts +40 -0
  45. package/build/lib/workflow-skill-installer.js +172 -0
  46. package/build/lib/workflow-skill-template.d.ts +9 -0
  47. package/build/lib/workflow-skill-template.js +85 -0
  48. package/build/lib/workflow-skill-version.d.ts +15 -0
  49. package/build/lib/workflow-skill-version.js +68 -0
  50. package/build/lib/workspace-root.d.ts +4 -0
  51. package/build/lib/workspace-root.js +33 -0
  52. package/build/schemas/basic-tools.d.ts +4 -0
  53. package/build/schemas/basic-tools.js +4 -0
  54. package/build/schemas/index.d.ts +114 -0
  55. package/build/schemas/index.js +1 -0
  56. package/build/schemas/memory-tools.d.ts +83 -0
  57. package/build/schemas/memory-tools.js +43 -0
  58. package/build/schemas/orchestration-tools.d.ts +8 -0
  59. package/build/schemas/orchestration-tools.js +8 -0
  60. package/build/schemas/output/code-insight-tools.d.ts +65 -0
  61. package/build/schemas/output/code-insight-tools.js +42 -0
  62. package/build/schemas/output/memory-tools.d.ts +576 -0
  63. package/build/schemas/output/memory-tools.js +148 -0
  64. package/build/schemas/project-tools.d.ts +19 -0
  65. package/build/schemas/project-tools.js +20 -0
  66. package/build/tools/__tests__/delete_memory_asset.unit.test.d.ts +1 -0
  67. package/build/tools/__tests__/delete_memory_asset.unit.test.js +87 -0
  68. package/build/tools/__tests__/search_memory.unit.test.js +1 -0
  69. package/build/tools/__tests__/start_bugfix.unit.test.js +34 -1
  70. package/build/tools/__tests__/update_memory_asset.unit.test.d.ts +1 -0
  71. package/build/tools/__tests__/update_memory_asset.unit.test.js +82 -0
  72. package/build/tools/__tests__/workflow.unit.test.d.ts +1 -0
  73. package/build/tools/__tests__/workflow.unit.test.js +24 -0
  74. package/build/tools/code_insight.js +5 -1
  75. package/build/tools/delete_memory_asset.d.ts +7 -0
  76. package/build/tools/delete_memory_asset.js +57 -0
  77. package/build/tools/index.d.ts +3 -0
  78. package/build/tools/index.js +3 -0
  79. package/build/tools/init_project.js +170 -158
  80. package/build/tools/search_memory.js +5 -2
  81. package/build/tools/start_bugfix.js +37 -4
  82. package/build/tools/start_feature.js +4 -3
  83. package/build/tools/start_ui.js +5 -4
  84. package/build/tools/update_memory_asset.d.ts +7 -0
  85. package/build/tools/update_memory_asset.js +98 -0
  86. package/build/tools/workflow.d.ts +13 -0
  87. package/build/tools/workflow.js +69 -0
  88. package/package.json +5 -3
@@ -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
+ });
@@ -0,0 +1,110 @@
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 { VERSION } from "../../version.js";
6
+ import { ensureAgentsMdSkillReference, ensureMcpProbeKitBootstrap, ensureMcpProbeSkill, resolveProjectRootFromToolArgs, } from "../workflow-skill-installer.js";
7
+ import { formatSkillVersionMarker, agentsContextNeedsUpgrade, compareSemver, skillContentNeedsUpgrade, } from "../workflow-skill-version.js";
8
+ import { LEGACY_WORKFLOW_SKILL_REL_PATH, MCP_PROBE_SKILL_REL_PATH, } from "../workflow-skill-template.js";
9
+ import { wrapMcpProbeBlock } from "../merge-agents-md.js";
10
+ const tempDirs = [];
11
+ afterEach(() => {
12
+ while (tempDirs.length > 0) {
13
+ const dir = tempDirs.pop();
14
+ if (dir) {
15
+ fs.rmSync(dir, { recursive: true, force: true });
16
+ }
17
+ }
18
+ });
19
+ describe("workflow-skill-version", () => {
20
+ test("compareSemver 比较主次补丁", () => {
21
+ expect(compareSemver("3.5.0", "3.5.1")).toBeLessThan(0);
22
+ expect(compareSemver("3.6.0", "3.5.9")).toBeGreaterThan(0);
23
+ expect(compareSemver("3.5.0", "3.5.0")).toBe(0);
24
+ });
25
+ test("无版本标记视为需要升级", () => {
26
+ expect(skillContentNeedsUpgrade("# custom", VERSION)).toBe(true);
27
+ expect(agentsContextNeedsUpgrade("<!-- mcp-probe:context begin -->", VERSION)).toBe(true);
28
+ });
29
+ });
30
+ describe("workflow-skill-installer", () => {
31
+ test("缺失时创建 mcp-probe-kit Skill", () => {
32
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), "wf-skill-"));
33
+ tempDirs.push(root);
34
+ const result = ensureMcpProbeSkill(root);
35
+ expect(result.created).toBe(true);
36
+ expect(result.updated).toBe(false);
37
+ expect(result.version).toBe(VERSION);
38
+ const text = fs.readFileSync(path.join(root, MCP_PROBE_SKILL_REL_PATH), "utf8");
39
+ expect(text).toContain(formatSkillVersionMarker(VERSION));
40
+ expect(text).toContain("MCP 调用时机");
41
+ });
42
+ test("同版本 Skill 不覆盖", () => {
43
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), "wf-skill-"));
44
+ tempDirs.push(root);
45
+ const skillPath = path.join(root, MCP_PROBE_SKILL_REL_PATH);
46
+ const content = `${formatSkillVersionMarker(VERSION)}\n# same version\n`;
47
+ fs.mkdirSync(path.dirname(skillPath), { recursive: true });
48
+ fs.writeFileSync(skillPath, content, "utf8");
49
+ const result = ensureMcpProbeSkill(root);
50
+ expect(result.created).toBe(false);
51
+ expect(result.updated).toBe(false);
52
+ expect(fs.readFileSync(skillPath, "utf8")).toBe(content);
53
+ });
54
+ test("旧版本 Skill 会升级覆盖", () => {
55
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), "wf-skill-"));
56
+ tempDirs.push(root);
57
+ const skillPath = path.join(root, MCP_PROBE_SKILL_REL_PATH);
58
+ fs.mkdirSync(path.dirname(skillPath), { recursive: true });
59
+ fs.writeFileSync(skillPath, `${formatSkillVersionMarker("0.1.0")}\n# stale tools list\n`, "utf8");
60
+ const result = ensureMcpProbeSkill(root);
61
+ expect(result.updated).toBe(true);
62
+ expect(result.previousVersion).toBe("0.1.0");
63
+ const text = fs.readFileSync(skillPath, "utf8");
64
+ expect(text).toContain(formatSkillVersionMarker(VERSION));
65
+ expect(text).toContain("何时调用");
66
+ expect(text).not.toContain("stale tools list");
67
+ });
68
+ test("无 AGENTS.md 时创建并引用 Skill", () => {
69
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), "wf-skill-"));
70
+ tempDirs.push(root);
71
+ const result = ensureAgentsMdSkillReference(root);
72
+ expect(result.created).toBe(true);
73
+ const agents = fs.readFileSync(path.join(root, "AGENTS.md"), "utf8");
74
+ expect(agents).toContain("mcp-probe:context-version");
75
+ expect(agents).toContain(MCP_PROBE_SKILL_REL_PATH);
76
+ });
77
+ test("已有 AGENTS.md 但缺 Skill 引用时更新", () => {
78
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), "wf-skill-"));
79
+ tempDirs.push(root);
80
+ fs.writeFileSync(path.join(root, "AGENTS.md"), "# Custom rules\n", "utf8");
81
+ const result = ensureAgentsMdSkillReference(root);
82
+ expect(result.updated).toBe(true);
83
+ const agents = fs.readFileSync(path.join(root, "AGENTS.md"), "utf8");
84
+ expect(agents).toContain("# Custom rules");
85
+ expect(agents).toContain(MCP_PROBE_SKILL_REL_PATH);
86
+ });
87
+ test("旧 workflow Skill 路径会触发 AGENTS.md 更新", () => {
88
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), "wf-skill-"));
89
+ tempDirs.push(root);
90
+ const legacyBlock = wrapMcpProbeBlock(`## MCP\nSee [skill](${LEGACY_WORKFLOW_SKILL_REL_PATH})`, "0.1.0");
91
+ fs.writeFileSync(path.join(root, "AGENTS.md"), `${legacyBlock}\n`, "utf8");
92
+ const result = ensureAgentsMdSkillReference(root);
93
+ expect(result.updated).toBe(true);
94
+ const agents = fs.readFileSync(path.join(root, "AGENTS.md"), "utf8");
95
+ expect(agents).toContain(MCP_PROBE_SKILL_REL_PATH);
96
+ expect(agents).toContain(`mcp-probe:context-version: ${VERSION}`);
97
+ });
98
+ test("bootstrap 同时处理 Skill 与 AGENTS.md", () => {
99
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), "wf-skill-"));
100
+ tempDirs.push(root);
101
+ const result = ensureMcpProbeKitBootstrap(root);
102
+ expect(result.skill.created).toBe(true);
103
+ expect(result.agentsMd.created).toBe(true);
104
+ });
105
+ test("从 tool args 解析 project_root", () => {
106
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), "wf-skill-"));
107
+ tempDirs.push(root);
108
+ expect(resolveProjectRootFromToolArgs({ project_root: root })).toBe(path.resolve(root));
109
+ });
110
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,31 @@
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 { resolveFromWorkspaceFolderPathsEnv, resolveWorkspaceRoot, } from "../workspace-root.js";
6
+ const original = process.env.WORKSPACE_FOLDER_PATHS;
7
+ const tempDirs = [];
8
+ afterEach(() => {
9
+ if (original === undefined) {
10
+ delete process.env.WORKSPACE_FOLDER_PATHS;
11
+ }
12
+ else {
13
+ process.env.WORKSPACE_FOLDER_PATHS = original;
14
+ }
15
+ while (tempDirs.length > 0) {
16
+ const dir = tempDirs.pop();
17
+ if (dir) {
18
+ fs.rmSync(dir, { recursive: true, force: true });
19
+ }
20
+ }
21
+ });
22
+ describe("workspace-root WORKSPACE_FOLDER_PATHS", () => {
23
+ test("解析 JSON 数组形式", () => {
24
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), "ws-"));
25
+ tempDirs.push(root);
26
+ fs.writeFileSync(path.join(root, "package.json"), "{}", "utf8");
27
+ process.env.WORKSPACE_FOLDER_PATHS = JSON.stringify([root]);
28
+ expect(resolveFromWorkspaceFolderPathsEnv()).toBe(path.resolve(root));
29
+ expect(resolveWorkspaceRoot("")).toBe(path.resolve(root));
30
+ });
31
+ });
@@ -1,4 +1,5 @@
1
1
  import { relativeLink } from "./project-context-layout.js";
2
+ import { MCP_PROBE_SKILL_REL_PATH } from "./workflow-skill-template.js";
2
3
  function link(layout, targetRel) {
3
4
  return relativeLink(layout.indexPath, targetRel);
4
5
  }
@@ -8,6 +9,8 @@ function memorySection(locale) {
8
9
  记忆(需 MEMORY_QDRANT_URL 等已配置):
9
10
  - 检索:\`start_*\` 命中后**自动注入**历史经验全文;中途补查可用 \`search_memory\`;单条精读仍可用 \`read_memory_asset\`
10
11
  - 沉淀:跨仓库共享**勿填** source_project/source_path;路径写进 content;summary 写检索关键词
12
+ - 修正:已有资产可用 \`update_memory_asset\` 按 asset_id 原地更新(保留 ID)
13
+ - 清理:过时/错误/重复沉淀可用 \`delete_memory_asset\`(删除前建议 \`read_memory_asset\` 确认)
11
14
  - Bug 修完验证通过 → **必须** \`memorize_asset\` type=\`bugfix\` tags=\`bugfix,root-cause\`(content 含【现象】【根因】【修复】【验证】)
12
15
  - 功能/UI 可复用产出 → \`memorize_asset\` type=\`pattern\`/\`component\``;
13
16
  }
@@ -15,6 +18,8 @@ function memorySection(locale) {
15
18
  Memory (requires MEMORY_* env):
16
19
  - Search: \`start_*\` auto-injects full memory hits; use \`search_memory\` mid-task; \`read_memory_asset\` for a specific id
17
20
  - Store: do NOT use source_project/source_path for cross-repo pools; put paths in content; write keyword-rich summary
21
+ - Update: fix existing entries in place with \`update_memory_asset\` by asset_id (preserves ID)
22
+ - Cleanup: remove stale/wrong/duplicate entries with \`delete_memory_asset\` (confirm via \`read_memory_asset\` first)
18
23
  - After verified bugfix → MUST \`memorize_asset\` type=\`bugfix\` (sections: symptom, root cause, fix, verification)
19
24
  - Reusable feature/UI → \`memorize_asset\` type=\`pattern\`/\`component\``;
20
25
  }
@@ -25,10 +30,12 @@ export function generateAgentsMdInner(input) {
25
30
  const { layout, locale } = input;
26
31
  const graph = link(layout, layout.latestMarkdownPath);
27
32
  const ctxIndex = link(layout, layout.legacyIndexPath);
33
+ const skillLink = link(layout, MCP_PROBE_SKILL_REL_PATH);
28
34
  if (locale === "zh-CN") {
29
35
  return `## MCP(必须先调)
30
- 需已配置 mcp-probe-kit。\`start_*\` 若返回 delegated plan,逐步执行完再结束。
36
+ 需已配置 mcp-probe-kit。写代码前先读 Skill:[MCP 调用时机](${skillLink})(\`${MCP_PROBE_SKILL_REL_PATH}\`,首次 MCP 调用自动创建)。
31
37
 
38
+ - 不确定用哪个 MCP → \`workflow\`(返回 firstTool)
32
39
  - 新功能 → \`start_feature\`(会先搜记忆)
33
40
  - Bug → \`start_bugfix\`(会先搜记忆)
34
41
  - UI → \`start_ui\`(会先搜记忆)
@@ -40,8 +47,9 @@ export function generateAgentsMdInner(input) {
40
47
  图谱:大改前读 [latest](${graph});过期 \`code_insight\` mode=auto save_to_docs=true${memorySection(locale)}`;
41
48
  }
42
49
  return `## MCP (call first)
43
- Requires mcp-probe-kit. Complete every \`start_*\` delegated plan step before done.
50
+ Requires mcp-probe-kit. Before coding, read Skill: [When to call MCP](${skillLink}) (\`${MCP_PROBE_SKILL_REL_PATH}\`, auto-created on first MCP call).
44
51
 
52
+ - Unsure which MCP → \`workflow\` (returns firstTool)
45
53
  - Feature → \`start_feature\` (searches memory first)
46
54
  - Bug → \`start_bugfix\` (searches memory first)
47
55
  - UI → \`start_ui\` (searches memory first)
@@ -0,0 +1,36 @@
1
+ /**
2
+ * 开发工作流路由:根据用户意图生成「何时调哪个 MCP 工具」的委托式指南。
3
+ * 解决 Agent 直接写代码、跳过 start_* / code_insight / check_spec 的问题。
4
+ */
5
+ export type WorkflowScenario = 'feature' | 'bugfix' | 'ui' | 'explore' | 'commit' | 'review' | 'refactor' | 'onboard' | 'spec' | 'memory' | 'unknown';
6
+ export interface WorkflowToolStep {
7
+ tool: string;
8
+ required: boolean;
9
+ when: string;
10
+ note?: string;
11
+ }
12
+ export interface WorkflowPhase {
13
+ id: string;
14
+ title: string;
15
+ when: string;
16
+ steps: WorkflowToolStep[];
17
+ }
18
+ export interface DevWorkflowPlan {
19
+ scenario: WorkflowScenario;
20
+ scenarioLabel: string;
21
+ confidence: 'high' | 'medium' | 'low';
22
+ summary: string;
23
+ firstTool: string;
24
+ firstToolArgsHint?: Record<string, unknown>;
25
+ phases: WorkflowPhase[];
26
+ avoid: string[];
27
+ memoryNotes: string[];
28
+ }
29
+ export declare function detectWorkflowScenario(intent: string, explicit?: string): {
30
+ scenario: WorkflowScenario;
31
+ confidence: 'high' | 'medium' | 'low';
32
+ };
33
+ export declare function buildDevWorkflow(intent: string, options?: {
34
+ scenario?: string;
35
+ }): DevWorkflowPlan;
36
+ export declare function renderWorkflowMarkdown(plan: DevWorkflowPlan, intent: string): string;