@ppdocs/mcp 2.7.1 → 2.8.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.
@@ -1,105 +1,75 @@
1
1
  import { z } from 'zod';
2
2
  import * as storage from '../storage/httpClient.js';
3
- import { decodeUnicodeEscapes, decodeObjectStrings, getRules, RULE_TYPE_LABELS } from '../utils.js';
4
- // 向量搜索已迁移至 Tauri 后端,通过 HTTP API 调用
5
- import { wrap, fetchRelations, formatNodeMarkdown, formatRelationsMarkdown, buildDirectoryTree, formatTreeText, countTreeNodes } from './helpers.js';
3
+ import { decodeObjectStrings, getRules, RULE_TYPE_LABELS } from '../utils.js';
4
+ import { wrap, formatDocMarkdown, formatTreeText, countTreeDocs } from './helpers.js';
6
5
  export function registerTools(server, projectId, _user) {
7
- // 1. 创建节点
8
- server.tool('kg_create_node', '创建知识节点。type: logic=逻辑/函数, data=数据结构, intro=概念介绍。⚠️ tags至少提供3个分类标签', {
9
- title: z.string().describe('节点标题'),
10
- type: z.enum(['logic', 'data', 'intro']).describe('节点类型(logic/data/intro)'),
11
- description: z.string().describe('Markdown描述(用Mermaid流程图+表格,禁止纯文字)'),
12
- path: z.string().min(1).describe('目录路径(必填,如"/前端/组件")'),
13
- tags: z.array(z.string()).min(3).describe('⚠️ 分类标签(必填,至少3个)'),
14
- signature: z.string().optional().describe('唯一签名(用于依赖匹配,默认=title)'),
15
- summary: z.string().optional().describe('一句话简介(显示在标题下方)'),
16
- dependencies: z.array(z.object({
17
- name: z.string().describe('目标节点的signature'),
18
- description: z.string().describe('依赖说明')
19
- })).optional().describe('依赖列表(自动生成连线)'),
20
- relatedFiles: z.array(z.string()).optional().describe('关联的源文件路径数组,如 ["src/auth.ts"]')
6
+ // ===================== 文档管理 =====================
7
+ // 1. 创建文档
8
+ server.tool('kg_create_node', '创建知识文档。使用目录路径分类,文件名作为文档名', {
9
+ path: z.string().min(1).describe('完整文档路径(如"/前端/组件/Modal")'),
10
+ summary: z.string().optional().describe('一句话简介'),
11
+ content: z.string().describe('Markdown内容')
21
12
  }, async (args) => {
22
13
  const decoded = decodeObjectStrings(args);
23
- const node = await storage.createNode(projectId, {
24
- title: decoded.title,
25
- type: decoded.type,
26
- status: 'incomplete',
27
- description: decoded.description || '',
14
+ const doc = await storage.createDoc(projectId, decoded.path, {
28
15
  summary: decoded.summary || '',
29
- // x, y 不传递,由 httpClient 智能计算位置
30
- locked: false,
31
- signature: decoded.signature || decoded.title,
32
- categories: decoded.tags,
33
- path: decoded.path,
34
- dependencies: decoded.dependencies || [],
35
- relatedFiles: decoded.relatedFiles || []
16
+ content: decoded.content,
17
+ versions: [{
18
+ version: 0.1,
19
+ date: new Date().toISOString(),
20
+ changes: '初始创建'
21
+ }],
22
+ bugfixes: []
36
23
  });
37
- // 向量索引由 Tauri 后端自动维护,MCP 不再手动更新
38
- return wrap(JSON.stringify(node, null, 2));
39
- });
40
- // 2. 删除节点
41
- server.tool('kg_delete_node', '删除节点(锁定节点和根节点不可删除)', { nodeId: z.string().describe('节点ID') }, async (args) => {
42
- const success = await storage.deleteNode(projectId, args.nodeId);
43
- // 向量索引由 Tauri 后端自动维护,MCP 不再手动删除
44
- return wrap(success ? '删除成功' : '删除失败(节点不存在/已锁定/是根节点)');
45
- });
46
- // 3. 更新节点
47
- server.tool('kg_update_node', '更新节点内容(锁定节点不可更新)。⚠️ 更新tags时至少提供3个分类标签', {
48
- nodeId: z.string().describe('节点ID'),
49
- title: z.string().optional().describe('新标题'),
50
- signature: z.string().optional().describe('新签名'),
51
- description: z.string().optional().describe('新描述(Markdown)'),
52
- summary: z.string().optional().describe('一句话简介(显示在标题下方)'),
53
- path: z.string().optional().describe('目录路径(如"/前端/组件")'),
54
- x: z.number().optional().describe('X坐标'),
55
- y: z.number().optional().describe('Y坐标'),
56
- status: z.enum(['incomplete', 'complete', 'fixing', 'refactoring', 'deprecated']).optional().describe('状态'),
57
- tags: z.array(z.string()).min(3).optional().describe('⚠️ 分类标签(更新时至少3个)'),
58
- dependencies: z.array(z.object({
59
- name: z.string(),
60
- description: z.string()
61
- })).optional().describe('依赖列表'),
62
- relatedFiles: z.array(z.string()).optional().describe('关联的源文件路径数组'),
24
+ return wrap(`✅ 文档已创建: ${decoded.path}\n\n${JSON.stringify(doc, null, 2)}`);
25
+ });
26
+ // 2. 删除文档
27
+ server.tool('kg_delete_node', '删除文档(根文档不可删除)', { nodeId: z.string().describe('文档路径') }, async (args) => {
28
+ const success = await storage.deleteDoc(projectId, args.nodeId);
29
+ return wrap(success ? '删除成功' : '删除失败(文档不存在或是根文档)');
30
+ });
31
+ // 3. 更新文档
32
+ server.tool('kg_update_node', '更新文档内容', {
33
+ nodeId: z.string().describe('文档路径'),
34
+ summary: z.string().optional().describe('一句话简介'),
35
+ content: z.string().optional().describe('Markdown内容'),
63
36
  versions: z.array(z.object({
64
37
  version: z.number(),
65
38
  date: z.string(),
66
39
  changes: z.string()
67
- })).optional().describe('版本记录(0.1起始,AI控制递增)'),
40
+ })).optional().describe('版本记录'),
68
41
  bugfixes: z.array(z.object({
69
- id: z.string(),
70
42
  date: z.string(),
71
43
  issue: z.string(),
72
- solution: z.string(),
73
- impact: z.string().optional()
74
- })).optional().describe('修复记录'),
75
- lastSyncAt: z.string().optional().describe('代码↔图谱最后同步时间(ISO时间戳)')
44
+ solution: z.string()
45
+ })).optional().describe('修复记录')
76
46
  }, async (args) => {
77
47
  const decoded = decodeObjectStrings(args);
78
- const { nodeId, tags, relatedFiles, versions, bugfixes, path, lastSyncAt, ...rest } = decoded;
79
- // 根节点必须使用 kg_update_root 更新
80
- if (nodeId === 'root') {
81
- return wrap('❌ 根节点请使用 kg_update_root 方法更新');
82
- }
83
- // API 参数转换
84
- let updates = { ...rest };
85
- if (tags !== undefined)
86
- updates.categories = tags;
87
- if (relatedFiles !== undefined)
88
- updates.relatedFiles = relatedFiles;
48
+ const { nodeId, summary, content, versions, bugfixes } = decoded;
49
+ // 根文档必须使用 kg_update_root 更新
50
+ if (nodeId === '/' || nodeId === '_root') {
51
+ return wrap('❌ 根文档请使用 kg_update_root 方法更新');
52
+ }
53
+ // 获取现有文档
54
+ const existing = await storage.getDoc(projectId, nodeId);
55
+ if (!existing) {
56
+ return wrap('更新失败(文档不存在)');
57
+ }
58
+ // 构建更新内容
59
+ const updates = {};
60
+ if (summary !== undefined)
61
+ updates.summary = summary;
62
+ if (content !== undefined)
63
+ updates.content = content;
89
64
  if (versions !== undefined)
90
65
  updates.versions = versions;
91
66
  if (bugfixes !== undefined)
92
67
  updates.bugfixes = bugfixes;
93
- if (path !== undefined)
94
- updates.path = path;
95
- if (lastSyncAt !== undefined)
96
- updates.lastSyncAt = lastSyncAt;
97
- const node = await storage.updateNode(projectId, nodeId, updates);
98
- // 向量索引由 Tauri 后端自动维护,MCP 不再手动更新
99
- return wrap(node ? JSON.stringify(node, null, 2) : '更新失败(节点不存在或已锁定)');
68
+ const doc = await storage.updateDoc(projectId, nodeId, updates);
69
+ return wrap(doc ? JSON.stringify(doc, null, 2) : '更新失败');
100
70
  });
101
- // 3.5 更新根节点 (项目介绍)
102
- server.tool('kg_update_root', '更新项目介绍(根节点描述,锁定时不可更新)', {
71
+ // 3.5 更新根文档 (项目介绍)
72
+ server.tool('kg_update_root', '更新项目介绍(根文档)', {
103
73
  title: z.string().optional().describe('项目标题'),
104
74
  description: z.string().optional().describe('项目介绍(Markdown)')
105
75
  }, async (args) => {
@@ -107,13 +77,19 @@ export function registerTools(server, projectId, _user) {
107
77
  if (decoded.title === undefined && decoded.description === undefined) {
108
78
  return wrap('❌ 请至少提供 title 或 description');
109
79
  }
110
- const node = await storage.updateRoot(projectId, {
111
- title: decoded.title,
112
- description: decoded.description
113
- });
114
- return wrap(node ? '✅ 项目介绍已更新' : '更新失败(根节点已锁定)');
80
+ const existing = await storage.getDoc(projectId, '/');
81
+ if (!existing) {
82
+ return wrap('更新失败(根文档不存在)');
83
+ }
84
+ const updates = {};
85
+ if (decoded.title !== undefined)
86
+ updates.summary = decoded.title;
87
+ if (decoded.description !== undefined)
88
+ updates.content = decoded.description;
89
+ const doc = await storage.updateDoc(projectId, '/', updates);
90
+ return wrap(doc ? '✅ 项目介绍已更新' : '更新失败');
115
91
  });
116
- // 3.6 获取项目规则 (独立方法,按需调用)
92
+ // 3.6 获取项目规则
117
93
  server.tool('kg_get_rules', '获取项目规则(可指定类型或获取全部)', {
118
94
  ruleType: z.enum(['userStyles', 'testRules', 'reviewRules', 'codeStyle', 'unitTests']).optional()
119
95
  .describe('规则类型: userStyles=用户沟通规则, codeStyle=编码风格规则, reviewRules=代码审查规则, testRules=错误分析规则, unitTests=代码测试规则。不传则返回全部')
@@ -125,7 +101,7 @@ export function registerTools(server, projectId, _user) {
125
101
  }
126
102
  return { content: [{ type: 'text', text: rules }] };
127
103
  });
128
- // 3.7 保存项目规则 (独立存储)
104
+ // 3.7 保存项目规则
129
105
  server.tool('kg_save_rules', '保存单个类型的项目规则(独立文件存储)', {
130
106
  ruleType: z.enum(['userStyles', 'testRules', 'reviewRules', 'codeStyle', 'unitTests'])
131
107
  .describe('规则类型: userStyles=用户沟通规则, codeStyle=编码风格规则, reviewRules=代码审查规则, testRules=错误分析规则, unitTests=代码测试规则'),
@@ -138,274 +114,121 @@ export function registerTools(server, projectId, _user) {
138
114
  }
139
115
  return wrap(`✅ ${RULE_TYPE_LABELS[decoded.ruleType]}已保存 (${decoded.rules.length} 条)`);
140
116
  });
141
- // 4. 锁定节点 (只能锁定,解锁需用户在前端手动操作)
142
- server.tool('kg_lock_node', '锁定节点(锁定后只能读取,解锁需用户在前端手动操作)', {
143
- nodeId: z.string().describe('节点ID')
117
+ // 5. 读取单个文档详情
118
+ server.tool('kg_read_node', '读取文档完整内容(简介、正文、版本历史、修复记录)', {
119
+ nodeId: z.string().describe('文档路径')
144
120
  }, async (args) => {
145
- const node = await storage.lockNode(projectId, args.nodeId, true); // 强制锁定
146
- return wrap(node ? JSON.stringify(node, null, 2) : '操作失败');
147
- });
148
- // 5. 搜索节点 (混合检索: 向量语义 + 关键词)
149
- server.tool('kg_search', '智能搜索节点(语义+关键词混合)。支持中英文语义匹配,空白关键词[""]返回全部节点', {
150
- keywords: z.array(z.string()).describe('搜索词列表(⚠️建议3个以上关键词提高准确率),支持语义匹配(如"登录"可找到auth)'),
151
- limit: z.number().optional().describe('返回数量(默认10)'),
152
- mode: z.enum(['hybrid', 'keyword', 'semantic']).optional().describe('搜索模式: hybrid=混合(默认), keyword=仅关键词, semantic=仅语义')
153
- }, async (args) => {
154
- const limit = args.limit || 10;
155
- const mode = (args.mode || 'hybrid');
156
- const query = args.keywords.filter(k => k.trim()).join(' ');
157
- // 空白关键词返回全部
158
- if (!query) {
159
- const results = await storage.searchNodes(projectId, args.keywords, limit);
160
- const output = results.map(r => ({
161
- id: r.node.id,
162
- title: r.node.title,
163
- tags: r.node.categories || [],
164
- status: r.node.status,
165
- summary: r.node.summary || '',
166
- hitRate: `${Math.round(r.score)}%`
167
- }));
168
- return wrap(`${JSON.stringify(output, null, 2)}\n\n💡 需要详细内容请使用 kg_read_node(nodeId) 获取节点详情`);
121
+ const doc = await storage.getDoc(projectId, args.nodeId);
122
+ if (!doc) {
123
+ return wrap('文档不存在');
169
124
  }
170
- // 调用 Tauri 后端混合搜索 API
125
+ // 格式化文档
126
+ const lines = formatDocMarkdown(doc, args.nodeId);
127
+ return wrap(lines.join('\n'));
128
+ });
129
+ // 10. 获取知识库树状图
130
+ server.tool('kg_get_tree', '获取知识库的目录树结构(按 path 分组)', {}, async () => {
171
131
  try {
172
- const results = await storage.vectorSearch(projectId, query, limit, mode);
173
- // 补全节点信息 (tags, status, summary)
174
- const output = [];
175
- for (const r of results) {
176
- const node = await storage.getNode(projectId, r.id);
177
- output.push({
178
- id: r.id,
179
- title: r.title,
180
- tags: node?.categories || [],
181
- status: node?.status || 'incomplete',
182
- summary: node?.summary || '',
183
- score: `${Math.round(r.score)}%`,
184
- matchType: r.match_type
185
- });
132
+ const tree = await storage.getTree(projectId);
133
+ if (tree && tree.length > 0) {
134
+ const treeText = formatTreeText(tree);
135
+ const docCount = countTreeDocs(tree);
136
+ return wrap(`共 ${docCount} 个文档\n\n${treeText}`);
186
137
  }
187
- const json = JSON.stringify(output, null, 2);
188
- return wrap(`${json}\n\n💡 需要详细内容请使用 kg_read_node(nodeId) 获取节点详情\n🔍 搜索模式: ${mode}`);
138
+ return wrap('知识库为空');
189
139
  }
190
140
  catch (e) {
191
- // 后端搜索失败,降级为关键词搜索
192
- console.warn('[kg_search] Vector search failed, fallback to keyword:', e);
193
- const results = await storage.searchNodes(projectId, args.keywords, limit);
194
- const output = results.map(r => ({
195
- id: r.node.id,
196
- title: r.node.title,
197
- tags: r.node.categories || [],
198
- status: r.node.status,
199
- summary: r.node.summary || '',
200
- hitRate: `${Math.round(r.score)}%`
201
- }));
202
- return wrap(`${JSON.stringify(output, null, 2)}\n\n💡 需要详细内容请使用 kg_read_node(nodeId) 获取节点详情\n⚠️ 语义搜索不可用,已降级为关键词搜索`);
203
- }
204
- });
205
- // 6. 路径查找
206
- server.tool('kg_find_path', '查找两节点间的依赖路径', {
207
- startId: z.string().describe('起点节点ID'),
208
- endId: z.string().describe('终点节点ID')
209
- }, async (args) => {
210
- const result = await storage.findPath(projectId, args.startId, args.endId);
211
- if (!result)
212
- return wrap('未找到路径');
213
- const output = {
214
- pathLength: result.path.length,
215
- nodes: result.path.map(n => ({ id: n.id, title: n.title, type: n.type })),
216
- edges: result.edges.map(e => ({ from: e.source, to: e.target, type: e.type }))
217
- };
218
- return wrap(JSON.stringify(output, null, 2));
219
- });
220
- // 7. 列出节点 (支持过滤)
221
- server.tool('kg_list_nodes', '列出节点,支持按状态/连接数过滤。maxEdges=0 可查孤立节点', {
222
- status: z.enum(['incomplete', 'complete', 'fixing', 'refactoring', 'deprecated']).optional().describe('状态过滤'),
223
- minEdges: z.number().optional().describe('最小连接数(含)'),
224
- maxEdges: z.number().optional().describe('最大连接数(含),0=孤立节点')
225
- }, async (args) => {
226
- const filter = (args.status || args.minEdges !== undefined || args.maxEdges !== undefined)
227
- ? { status: args.status, minEdges: args.minEdges, maxEdges: args.maxEdges }
228
- : undefined;
229
- const nodes = await storage.listNodes(projectId, filter);
230
- const output = nodes.map(n => ({ id: n.id, title: n.title, type: n.type, status: n.status, locked: n.locked, summary: n.summary || '' }));
231
- return wrap(JSON.stringify(output, null, 2));
232
- });
233
- // 8. 查询节点关系网 (支持多层)
234
- server.tool('kg_get_relations', '获取节点的上下游关系(谁依赖它/它依赖谁),支持多层查询', {
235
- nodeId: z.string().describe('节点ID'),
236
- depth: z.number().min(1).max(3).optional().describe('查询层数(默认1,最大3)')
237
- }, async (args) => {
238
- const maxDepth = Math.min(args.depth || 1, 3);
239
- const { outgoing, incoming } = await fetchRelations(projectId, args.nodeId, maxDepth);
240
- if (outgoing.length === 0 && incoming.length === 0) {
241
- return wrap('该节点没有任何连线');
141
+ return wrap(JSON.stringify({
142
+ error: '无法获取知识库结构',
143
+ message: String(e),
144
+ tip: '请确保软件端已启动'
145
+ }, null, 2));
242
146
  }
243
- return wrap(JSON.stringify({ outgoing, incoming }, null, 2));
244
147
  });
245
- // 9. 读取单个节点详情
246
- server.tool('kg_read_node', '读取单个节点的完整内容(描述、关联文件、依赖、历史记录),可选包含上下游关系', {
247
- nodeId: z.string().describe('节点ID'),
248
- depth: z.number().min(0).max(3).optional().describe('关系查询层数(0=不含关系,1-3=含上下游关系,默认0)')
249
- }, async (args) => {
250
- const node = await storage.getNode(projectId, args.nodeId);
251
- if (!node) {
252
- return wrap('节点不存在');
253
- }
254
- // 格式化节点基础信息
255
- const lines = formatNodeMarkdown(node);
256
- // 上下游关系 (depth > 0 时获取)
257
- const depth = args.depth || 0;
258
- if (depth > 0) {
259
- const { outgoing, incoming } = await fetchRelations(projectId, args.nodeId, depth);
260
- lines.push(...formatRelationsMarkdown(outgoing, incoming));
261
- }
262
- return wrap(lines.join('\n'));
263
- });
264
- // 10. 获取知识库树状图
265
- server.tool('kg_get_tree', '获取知识库的目录树结构(按 path 分组)', {}, async () => {
266
- const nodes = await storage.listNodes(projectId);
267
- const tree = buildDirectoryTree(nodes);
268
- const treeText = formatTreeText(tree);
269
- const nodeCount = countTreeNodes(tree);
270
- return wrap(`共 ${nodeCount} 个节点\n\n${treeText}`);
271
- });
272
- // ===================== 任务管理工具 =====================
273
- // 11. 创建任务
274
- server.tool('task_create', '创建开发任务,记录目标和关联节点', {
148
+ // ===================== 任务管理 =====================
149
+ // 7. 创建任务
150
+ server.tool('task_create', '创建开发任务', {
275
151
  title: z.string().describe('任务标题'),
276
152
  description: z.string().describe('任务描述(Markdown)'),
277
- goals: z.array(z.string()).optional().describe('目标清单'),
278
- related_nodes: z.array(z.string()).optional().describe('关联节点ID')
153
+ goals: z.array(z.string()).optional().describe('目标清单')
279
154
  }, async (args) => {
280
- // 解码 Unicode 转义 (修复 MCP 传参中文乱码)
281
155
  const decoded = decodeObjectStrings(args);
282
156
  const task = await storage.createTask(projectId, {
283
157
  title: decoded.title,
284
158
  description: decoded.description,
285
- goals: decoded.goals || [],
286
- related_nodes: decoded.related_nodes
159
+ goals: decoded.goals || []
287
160
  }, _user);
288
161
  return wrap(JSON.stringify(task, null, 2));
289
162
  });
290
- // 11. 列出任务
291
- server.tool('task_list', '列出任务(active=进行中,archived=已归档)', {
292
- status: z.enum(['active', 'archived']).optional().describe('状态筛选')
293
- }, async (args) => {
294
- const tasks = await storage.listTasks(projectId, args.status);
295
- const output = tasks.map(t => ({
163
+ // 8. 读取任务
164
+ server.tool('task_get', '读取任务详情(按名字搜索,支持当前任务和历史任务)', {
165
+ title: z.string().describe('任务名称(模糊匹配)'),
166
+ status: z.enum(['active', 'archived', 'all']).optional().describe('状态筛选: active=进行中, archived=已归档, all=全部(默认)')
167
+ }, async (args) => {
168
+ const status = args.status || 'all';
169
+ const searchTitle = args.title.toLowerCase();
170
+ // 获取任务列表
171
+ let tasks;
172
+ if (status === 'all') {
173
+ const [active, archived] = await Promise.all([
174
+ storage.listTasks(projectId, 'active'),
175
+ storage.listTasks(projectId, 'archived')
176
+ ]);
177
+ tasks = [...active, ...archived];
178
+ }
179
+ else {
180
+ tasks = await storage.listTasks(projectId, status);
181
+ }
182
+ // 按名字模糊匹配
183
+ const matched = tasks.filter(t => t.title.toLowerCase().includes(searchTitle));
184
+ if (matched.length === 0) {
185
+ return wrap(`未找到匹配 "${args.title}" 的任务`);
186
+ }
187
+ // 如果只有一个匹配,返回完整详情
188
+ if (matched.length === 1) {
189
+ const task = await storage.getTask(projectId, matched[0].id);
190
+ return wrap(task ? JSON.stringify(task, null, 2) : '任务详情获取失败');
191
+ }
192
+ // 多个匹配,返回列表让用户选择
193
+ const list = matched.map(t => ({
296
194
  id: t.id,
297
195
  title: t.title,
298
196
  status: t.status,
299
197
  creator: t.creator,
300
- created_at: t.created_at,
301
- last_log: t.last_log
198
+ created_at: t.created_at
302
199
  }));
303
- return wrap(JSON.stringify(output, null, 2));
304
- });
305
- // 12. 获取任务详情
306
- server.tool('task_get', '获取任务完整信息(含全部日志)', {
307
- taskId: z.string().describe('任务ID')
308
- }, async (args) => {
309
- const task = await storage.getTask(projectId, args.taskId);
310
- if (!task) {
311
- return wrap('任务不存在');
312
- }
313
- return wrap(JSON.stringify(task, null, 2));
200
+ return wrap(`找到 ${matched.length} 个匹配任务:\n${JSON.stringify(list, null, 2)}\n\n请提供更精确的任务名称`);
314
201
  });
315
- // 13. 添加任务日志
316
- server.tool('task_add_log', '记录任务进展/问题/方案/参考', {
202
+ // 9. 更新任务
203
+ server.tool('task_update', '更新任务(添加进展日志)', {
317
204
  taskId: z.string().describe('任务ID'),
318
- log_type: z.enum(['progress', 'issue', 'solution', 'reference']).describe('日志类型'),
205
+ log_type: z.enum(['progress', 'issue', 'solution', 'reference']).describe('日志类型: progress=进展, issue=问题, solution=方案, reference=参考'),
319
206
  content: z.string().describe('日志内容(Markdown)')
320
207
  }, async (args) => {
321
- // 解码 Unicode 转义 (修复 MCP 传参中文乱码)
322
- const decodedContent = decodeUnicodeEscapes(args.content);
323
- const task = await storage.addTaskLog(projectId, args.taskId, args.log_type, decodedContent);
208
+ const decoded = decodeObjectStrings(args);
209
+ const task = await storage.addTaskLog(projectId, args.taskId, decoded.log_type, decoded.content);
324
210
  if (!task) {
325
- return wrap('添加失败(任务不存在或已归档)');
211
+ return wrap('更新失败(任务不存在或已归档)');
326
212
  }
327
- return wrap(`日志已添加,任务共有 ${task.logs.length} 条日志`);
213
+ return wrap(`✅ 日志已添加,任务共有 ${task.logs.length} 条日志`);
328
214
  });
329
- // 14. 完成任务
330
- server.tool('task_complete', '完成任务并归档,填写经验总结', {
215
+ // 10. 归档任务
216
+ server.tool('task_archive', '归档任务(完成并填写经验总结)', {
331
217
  taskId: z.string().describe('任务ID'),
332
218
  summary: z.string().describe('经验总结(Markdown)'),
333
219
  difficulties: z.array(z.string()).optional().describe('遇到的困难'),
334
- solutions: z.array(z.string()).optional().describe('解决方案'),
335
- references: z.array(z.object({
336
- title: z.string(),
337
- url: z.string().optional()
338
- })).optional().describe('参考资料')
220
+ solutions: z.array(z.string()).optional().describe('解决方案')
339
221
  }, async (args) => {
340
- // 解码 Unicode 转义 (修复 MCP 传参中文乱码)
341
- const decoded = decodeObjectStrings({
342
- summary: args.summary,
343
- difficulties: args.difficulties || [],
344
- solutions: args.solutions || [],
345
- references: args.references || []
222
+ const decoded = decodeObjectStrings(args);
223
+ const task = await storage.completeTask(projectId, args.taskId, {
224
+ summary: decoded.summary,
225
+ difficulties: decoded.difficulties || [],
226
+ solutions: decoded.solutions || [],
227
+ references: []
346
228
  });
347
- const task = await storage.completeTask(projectId, args.taskId, decoded);
348
229
  if (!task) {
349
- return wrap('完成失败(任务不存在或已归档)');
350
- }
351
- return wrap(`任务已完成归档: ${task.title}`);
352
- });
353
- // 15. 查询文件关联的节点
354
- server.tool('kg_find_by_file', '查询哪些节点绑定了指定文件(删除/重命名前检查)', {
355
- filePath: z.string().describe('文件相对路径,如 src/utils/format.ts')
356
- }, async (args) => {
357
- const nodes = await storage.listNodes(projectId);
358
- const filePath = args.filePath.replace(/\\/g, '/'); // 统一为正斜杠
359
- // 查找所有绑定了该文件的节点
360
- const boundBy = [];
361
- for (const node of nodes) {
362
- if (node.relatedFiles && node.relatedFiles.length > 0) {
363
- // 检查是否包含该文件 (支持部分匹配)
364
- const hasFile = node.relatedFiles.some(f => {
365
- const normalizedF = f.replace(/\\/g, '/');
366
- return normalizedF === filePath ||
367
- normalizedF.endsWith('/' + filePath) ||
368
- filePath.endsWith('/' + normalizedF);
369
- });
370
- if (hasFile) {
371
- boundBy.push({
372
- id: node.id,
373
- title: node.title,
374
- type: node.type,
375
- status: node.status
376
- });
377
- }
378
- }
379
- }
380
- const result = {
381
- file: filePath,
382
- boundBy,
383
- message: boundBy.length > 0
384
- ? `⚠️ 该文件被 ${boundBy.length} 个节点引用,删除前请更新节点`
385
- : '✅ 该文件无 KG 引用,可安全操作'
386
- };
387
- return wrap(JSON.stringify(result, null, 2));
388
- });
389
- // ===================== 向量索引管理 =====================
390
- // 注意: 向量索引由 Tauri 后端自动维护,MCP 只提供只读查询
391
- // 16. 查看索引状态 (只读)
392
- server.tool('kg_index_stats', '查看向量索引状态', {}, async () => {
393
- try {
394
- const stats = await storage.vectorStats(projectId);
395
- return wrap(JSON.stringify({
396
- model_downloaded: stats.model_downloaded,
397
- model_loaded: stats.model_loaded,
398
- model_path: stats.model_path,
399
- index_count: stats.index_count,
400
- tip: '向量索引由软件端自动维护,节点增删改时自动更新'
401
- }, null, 2));
402
- }
403
- catch (e) {
404
- return wrap(JSON.stringify({
405
- error: '无法获取索引状态',
406
- message: String(e),
407
- tip: '请确保软件端已启动'
408
- }, null, 2));
230
+ return wrap('归档失败(任务不存在或已归档)');
409
231
  }
232
+ return wrap(`✅ 任务已归档: ${task.title}`);
410
233
  });
411
234
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ppdocs/mcp",
3
- "version": "2.7.1",
3
+ "version": "2.8.0",
4
4
  "description": "ppdocs MCP Server - Knowledge Graph for Claude",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -0,0 +1,82 @@
1
+ # 🔍 "Deep-Trace" Diagnostic Protocol (深层溯源诊断协议)
2
+
3
+ 核心理念:**先在宏观地图(图谱)上定位,再在微观战场(代码)上排查,绝不盲目翻看代码。**
4
+
5
+ ---
6
+
7
+ ### 1.第一阶段:知识锚定 (Knowledge Anchoring) ⚓️
8
+ 🔹 **核心目标**:在动手之前,通过 PPDocs 明确“正确应该是什么样”。
9
+ ✏️ **动作**:
10
+ * **查询图谱**:输入问题关键词,提取标准业务逻辑。
11
+ * **建立基准**:以图谱中的描述作为“真理标准 (Truth)”。
12
+ × **禁区**:
13
+ * 禁止在未查询图谱前,直接跳入代码库阅读(防止陷入细节泥潭)。
14
+
15
+ ### 2.第二阶段:抽象映射 (Abstract Mapping) 🗺️
16
+ 🔹 **核心目标**:利用链式思维 (CoT),绘制高层级逻辑流。
17
+ ✏️ **动作**:
18
+ * **绘制流程**:忽略实现细节,仅画出模块间的输入/输出流转(数据流)。
19
+ * **可视化**:生成 ASCII 或 Mermaid 流程图,展示当前链路。
20
+ * **标记疑点**:在流程图上圈出逻辑可能断裂的节点。
21
+
22
+ ### 3.第三阶段:靶点锁定 (Target Locking) 🎯
23
+ 🔹 **核心目标**:将问题范围从“整个项目”缩小到“单一节点”。
24
+ ✏️ **动作**:
25
+ * **定位区域**:确定问题具体发生在 PPDocs 的哪个 `Node`(节点)或 `Edge`(连线)上。
26
+ * **隔离上下文**:只关注该节点的前置依赖(Pre-requisites)和后置影响(Post-effects)。
27
+
28
+ ### 4.第三阶段:双层取证 (Dual-Layer Forensics) 🕵️
29
+ 🔹 **核心目标**:对比“理论(图谱)”与“现实(代码)”的差异。
30
+ ✏️ **动作**:
31
+ * **左眼看图谱**:阅读 PPDocs 中该节点的逻辑定义。
32
+ * **右眼看代码**:审查该节点对应的实际代码实现。
33
+ * **寻找裂痕**:
34
+ * 🔴 **代码错误 (Bug)**:图谱逻辑正确,代码实现错误。
35
+ * 🟠 **设计缺陷 (Design Flaw)**:代码符合图谱,但结果依然错误(图谱本身逻辑有误)。
36
+ * 🟡 **文档脱节 (Sync Issue)**:代码逻辑正确且有效,但图谱未记载(需同步)。
37
+
38
+ ### 5.第五阶段:分析结案 (Verdict) 📝
39
+ 🔹 **核心目标**:输出可执行的诊断报告。
40
+ ✏️ **动作**:
41
+ * 输出标准化的**《问题诊断报告》**。
42
+ * **流转**:将报告作为输入,传递给 **[任务方案生成工作流]** 进行修复。
43
+
44
+ ---
45
+
46
+ ### 📊 诊断逻辑可视化图解 (ASCII)
47
+
48
+ ```text
49
+ +-------------------------+
50
+ | 🚨 问题触发 (Issue) |
51
+ +------------+------------+
52
+ |
53
+ v
54
+ +------------+------------+
55
+ | ⚓️ 知识锚定 (PPDocs) | <--- 获取标准逻辑 (Truth)
56
+ +------------+------------+
57
+ |
58
+ v
59
+ [ 🗺️ 抽象逻辑层 ]
60
+ +-------------------------+
61
+ | Step1 -> Step2 ... | (绘制链式流程)
62
+ | | |
63
+ | [ ? ] <---------+--- 🎯 锁定可疑节点
64
+ +-----------+-------------+
65
+ |
66
+ v
67
+ +-----------+-------------+
68
+ | 🕵️ 双层取证 (Compare) |
69
+ +-----+-------------+-----+
70
+ | |
71
+ (📜 PPDocs) vs (💻 Code)
72
+ | |
73
+ +------+------+
74
+ |
75
+ v
76
+ /-------+-------\
77
+ / 根本原因? \
78
+ v v
79
+ +--------------+ +--------------+
80
+ | 🐛 代码 Bug | | 📐 设计/文档错 |
81
+ | (Fix Code) | | (Fix Graph) |
82
+ +--------------+ +--------------+