@ppdocs/mcp 3.9.1 → 3.12.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.
- package/dist/cli.js +27 -2
- package/dist/config.d.ts +1 -0
- package/dist/config.js +16 -2
- package/dist/index.js +50 -3
- package/dist/storage/httpClient.d.ts +22 -1
- package/dist/storage/httpClient.js +50 -0
- package/dist/storage/types.d.ts +62 -0
- package/dist/tools/discussion.js +1 -3
- package/dist/tools/flowchart.js +4 -0
- package/dist/tools/index.d.ts +2 -2
- package/dist/tools/index.js +4 -2
- package/dist/tools/init.js +3 -1
- package/dist/tools/kg_status.d.ts +1 -0
- package/dist/tools/kg_status.js +33 -9
- package/dist/tools/pitfalls.d.ts +6 -0
- package/dist/tools/pitfalls.js +190 -0
- package/dist/tools/refs.js +148 -8
- package/dist/tools/shared.d.ts +1 -1
- package/dist/tools/shared.js +1 -1
- package/dist/tools/tasks.js +2 -1
- package/package.json +1 -1
- package/templates/AGENT.md +105 -64
- package/templates/cursorrules.md +105 -64
- package/templates/kiro-rules/ppdocs.md +105 -64
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 💡 kg_pitfall — 踩坑经验管理
|
|
3
|
+
* 记录项目中遇到的问题、根因和解决方案,避免重复踩坑
|
|
4
|
+
*/
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
import { getClient } from '../storage/httpClient.js';
|
|
7
|
+
import { decodeObjectStrings } from '../utils.js';
|
|
8
|
+
import { safeTool, wrap } from './shared.js';
|
|
9
|
+
const SEVERITY_ICONS = {
|
|
10
|
+
critical: '🔴',
|
|
11
|
+
warning: '🟡',
|
|
12
|
+
info: '🔵',
|
|
13
|
+
};
|
|
14
|
+
const STATUS_ICONS = {
|
|
15
|
+
open: '🔓',
|
|
16
|
+
resolved: '✅',
|
|
17
|
+
recurring: '🔄',
|
|
18
|
+
};
|
|
19
|
+
export function registerPitfallTools(server) {
|
|
20
|
+
const client = () => getClient();
|
|
21
|
+
server.tool('kg_pitfall', '💡 踩坑经验 — 记录和查询项目中的踩坑经验,避免重复踩坑。\n' +
|
|
22
|
+
'遇到问题时用 create 记录现象和根因,找到方案后用 resolve 标记解决。\n' +
|
|
23
|
+
'下次遇到类似问题先 search 查历史经验。\n' +
|
|
24
|
+
'actions: create|get|update|search|resolve|list|delete', {
|
|
25
|
+
action: z.enum(['create', 'get', 'update', 'search', 'resolve', 'list', 'delete'])
|
|
26
|
+
.describe('操作类型'),
|
|
27
|
+
id: z.string().optional().describe('踩坑经验 ID (get/update/resolve/delete)'),
|
|
28
|
+
title: z.string().optional().describe('标题 (create)'),
|
|
29
|
+
category: z.string().optional().describe('分类: protocol/concurrency/config/logic/ui/other (create/update/search)'),
|
|
30
|
+
symptom: z.string().optional().describe('现象描述 Markdown (create/update)'),
|
|
31
|
+
root_cause: z.string().optional().describe('根因分析 Markdown (create/update)'),
|
|
32
|
+
solution: z.string().optional().describe('解决方案 Markdown (create/update/resolve)'),
|
|
33
|
+
prevention: z.string().optional().describe('预防措施 (create/update)'),
|
|
34
|
+
severity: z.string().optional().describe('严重程度: critical/warning/info (create/update)'),
|
|
35
|
+
related_nodes: z.array(z.string()).optional().describe('关联流程图节点 ID 列表 (create/update)'),
|
|
36
|
+
source_task: z.string().optional().describe('来源任务 ID (create)'),
|
|
37
|
+
tags: z.array(z.string()).optional().describe('标签列表 (create/update)'),
|
|
38
|
+
query: z.string().optional().describe('搜索关键词 (search)'),
|
|
39
|
+
status: z.string().optional().describe('状态过滤: open/resolved/recurring (search/list)'),
|
|
40
|
+
}, async (args) => safeTool(async () => {
|
|
41
|
+
const decoded = decodeObjectStrings(args);
|
|
42
|
+
switch (decoded.action) {
|
|
43
|
+
case 'list': {
|
|
44
|
+
const pitfalls = decoded.status || decoded.category
|
|
45
|
+
? await client().searchPitfalls('', decoded.status, decoded.category)
|
|
46
|
+
: await client().listPitfalls();
|
|
47
|
+
if (pitfalls.length === 0)
|
|
48
|
+
return wrap('当前没有踩坑经验记录');
|
|
49
|
+
const lines = [
|
|
50
|
+
`💡 踩坑经验 (${pitfalls.length})`,
|
|
51
|
+
'',
|
|
52
|
+
'| 状态 | 严重 | 标题 | 分类 | 标签 | 更新 |',
|
|
53
|
+
'|:-----|:-----|:-----|:-----|:-----|:-----|',
|
|
54
|
+
];
|
|
55
|
+
for (const p of pitfalls) {
|
|
56
|
+
const statusIcon = STATUS_ICONS[p.status] || p.status;
|
|
57
|
+
const sevIcon = SEVERITY_ICONS[p.severity] || p.severity;
|
|
58
|
+
const tags = p.tags.length > 0 ? p.tags.join(',') : '-';
|
|
59
|
+
const date = p.updated_at.substring(0, 10);
|
|
60
|
+
lines.push(`| ${statusIcon} | ${sevIcon} | ${p.title} | ${p.category} | ${tags} | ${date} |`);
|
|
61
|
+
}
|
|
62
|
+
return wrap(lines.join('\n'));
|
|
63
|
+
}
|
|
64
|
+
case 'get': {
|
|
65
|
+
if (!decoded.id)
|
|
66
|
+
return wrap('❌ get 需要 id');
|
|
67
|
+
const p = await client().getPitfall(decoded.id);
|
|
68
|
+
if (!p)
|
|
69
|
+
return wrap(`未找到踩坑经验: ${decoded.id}`);
|
|
70
|
+
const sevIcon = SEVERITY_ICONS[p.severity] || p.severity;
|
|
71
|
+
const statusIcon = STATUS_ICONS[p.status] || p.status;
|
|
72
|
+
const lines = [
|
|
73
|
+
`# ${sevIcon} ${p.title}`,
|
|
74
|
+
'',
|
|
75
|
+
`| 字段 | 值 |`,
|
|
76
|
+
`|:---|:---|`,
|
|
77
|
+
`| ID | ${p.id} |`,
|
|
78
|
+
`| 状态 | ${statusIcon} ${p.status} |`,
|
|
79
|
+
`| 严重程度 | ${sevIcon} ${p.severity} |`,
|
|
80
|
+
`| 分类 | ${p.category} |`,
|
|
81
|
+
`| 标签 | ${p.tags.join(', ') || '-'} |`,
|
|
82
|
+
`| 关联节点 | ${p.related_nodes.join(', ') || '-'} |`,
|
|
83
|
+
];
|
|
84
|
+
if (p.source_task)
|
|
85
|
+
lines.push(`| 来源任务 | ${p.source_task} |`);
|
|
86
|
+
lines.push(`| 创建 | ${p.created_at.substring(0, 19)} |`);
|
|
87
|
+
lines.push(`| 更新 | ${p.updated_at.substring(0, 19)} |`);
|
|
88
|
+
if (p.resolved_at)
|
|
89
|
+
lines.push(`| 解决 | ${p.resolved_at.substring(0, 19)} |`);
|
|
90
|
+
if (p.symptom) {
|
|
91
|
+
lines.push('', '## 现象', '', p.symptom);
|
|
92
|
+
}
|
|
93
|
+
if (p.root_cause) {
|
|
94
|
+
lines.push('', '## 根因', '', p.root_cause);
|
|
95
|
+
}
|
|
96
|
+
if (p.solution) {
|
|
97
|
+
lines.push('', '## 解决方案', '', p.solution);
|
|
98
|
+
}
|
|
99
|
+
if (p.prevention) {
|
|
100
|
+
lines.push('', '## 预防措施', '', p.prevention);
|
|
101
|
+
}
|
|
102
|
+
return wrap(lines.join('\n'));
|
|
103
|
+
}
|
|
104
|
+
case 'create': {
|
|
105
|
+
if (!decoded.title)
|
|
106
|
+
return wrap('❌ create 需要 title');
|
|
107
|
+
const pitfall = await client().createPitfall({
|
|
108
|
+
title: decoded.title,
|
|
109
|
+
category: decoded.category,
|
|
110
|
+
symptom: decoded.symptom,
|
|
111
|
+
root_cause: decoded.root_cause,
|
|
112
|
+
solution: decoded.solution,
|
|
113
|
+
prevention: decoded.prevention,
|
|
114
|
+
severity: decoded.severity,
|
|
115
|
+
related_nodes: decoded.related_nodes,
|
|
116
|
+
source_task: decoded.source_task,
|
|
117
|
+
tags: decoded.tags,
|
|
118
|
+
});
|
|
119
|
+
return wrap(`✅ 踩坑经验已创建\n\n- ID: ${pitfall.id}\n- 标题: ${pitfall.title}\n- 分类: ${pitfall.category}\n- 严重程度: ${pitfall.severity}`);
|
|
120
|
+
}
|
|
121
|
+
case 'update': {
|
|
122
|
+
if (!decoded.id)
|
|
123
|
+
return wrap('❌ update 需要 id');
|
|
124
|
+
const existing = await client().getPitfall(decoded.id);
|
|
125
|
+
if (!existing)
|
|
126
|
+
return wrap(`❌ 未找到踩坑经验: ${decoded.id}`);
|
|
127
|
+
const updated = {
|
|
128
|
+
...existing,
|
|
129
|
+
...(decoded.title && { title: decoded.title }),
|
|
130
|
+
...(decoded.category && { category: decoded.category }),
|
|
131
|
+
...(decoded.symptom && { symptom: decoded.symptom }),
|
|
132
|
+
...(decoded.root_cause && { root_cause: decoded.root_cause }),
|
|
133
|
+
...(decoded.solution && { solution: decoded.solution }),
|
|
134
|
+
...(decoded.prevention && { prevention: decoded.prevention }),
|
|
135
|
+
...(decoded.severity && { severity: decoded.severity }),
|
|
136
|
+
...(decoded.related_nodes && { related_nodes: decoded.related_nodes }),
|
|
137
|
+
...(decoded.tags && { tags: decoded.tags }),
|
|
138
|
+
updated_at: new Date().toISOString(),
|
|
139
|
+
};
|
|
140
|
+
await client().savePitfall(updated);
|
|
141
|
+
return wrap(`✅ 踩坑经验已更新 (${decoded.id})`);
|
|
142
|
+
}
|
|
143
|
+
case 'resolve': {
|
|
144
|
+
if (!decoded.id)
|
|
145
|
+
return wrap('❌ resolve 需要 id');
|
|
146
|
+
const existing = await client().getPitfall(decoded.id);
|
|
147
|
+
if (!existing)
|
|
148
|
+
return wrap(`❌ 未找到踩坑经验: ${decoded.id}`);
|
|
149
|
+
const now = new Date().toISOString();
|
|
150
|
+
const resolved = {
|
|
151
|
+
...existing,
|
|
152
|
+
status: 'resolved',
|
|
153
|
+
resolved_at: now,
|
|
154
|
+
updated_at: now,
|
|
155
|
+
...(decoded.solution && { solution: decoded.solution }),
|
|
156
|
+
...(decoded.prevention && { prevention: decoded.prevention }),
|
|
157
|
+
};
|
|
158
|
+
await client().savePitfall(resolved);
|
|
159
|
+
return wrap(`✅ 踩坑经验已标记为解决 (${decoded.id})`);
|
|
160
|
+
}
|
|
161
|
+
case 'search': {
|
|
162
|
+
const query = decoded.query || '';
|
|
163
|
+
const results = await client().searchPitfalls(query, decoded.status, decoded.category);
|
|
164
|
+
if (results.length === 0)
|
|
165
|
+
return wrap(`未找到匹配的踩坑经验 (搜索: "${query}")`);
|
|
166
|
+
const lines = [
|
|
167
|
+
`💡 搜索结果: "${query}" (${results.length} 条)`,
|
|
168
|
+
'',
|
|
169
|
+
'| 状态 | 严重 | 标题 | 分类 | ID |',
|
|
170
|
+
'|:-----|:-----|:-----|:-----|:---|',
|
|
171
|
+
];
|
|
172
|
+
for (const p of results) {
|
|
173
|
+
const statusIcon = STATUS_ICONS[p.status] || p.status;
|
|
174
|
+
const sevIcon = SEVERITY_ICONS[p.severity] || p.severity;
|
|
175
|
+
lines.push(`| ${statusIcon} | ${sevIcon} | ${p.title} | ${p.category} | ${p.id} |`);
|
|
176
|
+
}
|
|
177
|
+
lines.push('', '> 用 `kg_pitfall(get, id:"xxx")` 查看详情');
|
|
178
|
+
return wrap(lines.join('\n'));
|
|
179
|
+
}
|
|
180
|
+
case 'delete': {
|
|
181
|
+
if (!decoded.id)
|
|
182
|
+
return wrap('❌ delete 需要 id');
|
|
183
|
+
const ok = await client().deletePitfall(decoded.id);
|
|
184
|
+
return wrap(ok ? `✅ 踩坑经验已删除 (${decoded.id})` : `ℹ️ 未找到踩坑经验 (${decoded.id})`);
|
|
185
|
+
}
|
|
186
|
+
default:
|
|
187
|
+
return wrap(`❌ 未知 action: ${decoded.action}`);
|
|
188
|
+
}
|
|
189
|
+
}));
|
|
190
|
+
}
|
package/dist/tools/refs.js
CHANGED
|
@@ -2,6 +2,13 @@ import { z } from 'zod';
|
|
|
2
2
|
import { getClient } from '../storage/httpClient.js';
|
|
3
3
|
import { decodeObjectStrings } from '../utils.js';
|
|
4
4
|
import { safeTool, wrap } from './shared.js';
|
|
5
|
+
function formatBytes(bytes) {
|
|
6
|
+
if (bytes < 1024)
|
|
7
|
+
return `${bytes} B`;
|
|
8
|
+
if (bytes < 1048576)
|
|
9
|
+
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
10
|
+
return `${(bytes / 1048576).toFixed(1)} MB`;
|
|
11
|
+
}
|
|
5
12
|
const linkSchema = z.object({
|
|
6
13
|
url: z.string(),
|
|
7
14
|
label: z.string(),
|
|
@@ -19,8 +26,10 @@ export function registerReferenceTools(server) {
|
|
|
19
26
|
const client = () => getClient();
|
|
20
27
|
server.tool('kg_ref', '📎 外部参考 — 管理项目引用的第三方资料、链接、脚本和样例文件。\n' +
|
|
21
28
|
'涉及参考资料时主动调用:list 查看已有 → get 读详情 → read_file 读附件内容 → save 新增。\n' +
|
|
22
|
-
'
|
|
23
|
-
|
|
29
|
+
'★ fetch: 输入URL自动拉取网页内容到本地,生成解析报告,支持持续更新(refetch)。\n' +
|
|
30
|
+
'★ import_file: 导入本地文件内容到参考系统(自动保存内容副本+版本追踪)。\n' +
|
|
31
|
+
'actions: list|get|save|delete|read_file|fetch|refetch|import_file|reimport', {
|
|
32
|
+
action: z.enum(['list', 'get', 'save', 'delete', 'read_file', 'fetch', 'refetch', 'import_file', 'reimport']).describe('操作类型'),
|
|
24
33
|
id: z.string().optional().describe('参考 ID'),
|
|
25
34
|
title: z.string().optional().describe('参考标题 (save)'),
|
|
26
35
|
summary: z.string().optional().describe('Markdown 总结 (save)'),
|
|
@@ -28,7 +37,8 @@ export function registerReferenceTools(server) {
|
|
|
28
37
|
files: z.array(fileSchema).optional().describe('参考文件 (save)'),
|
|
29
38
|
scripts: z.array(fileSchema).optional().describe('解析脚本 (save)'),
|
|
30
39
|
adoptedBy: z.array(adoptedNodeSchema).optional().describe('已采用该参考的节点 (save)'),
|
|
31
|
-
path: z.string().optional().describe('参考文件路径 (read_file)'),
|
|
40
|
+
path: z.string().optional().describe('参考文件路径 (read_file/import_file)'),
|
|
41
|
+
url: z.string().optional().describe('要拉取的文档 URL (fetch/refetch)'),
|
|
32
42
|
}, async (args) => safeTool(async () => {
|
|
33
43
|
const decoded = decodeObjectStrings(args);
|
|
34
44
|
switch (decoded.action) {
|
|
@@ -39,11 +49,12 @@ export function registerReferenceTools(server) {
|
|
|
39
49
|
const lines = [
|
|
40
50
|
`📎 外部参考 (${refs.length})`,
|
|
41
51
|
'',
|
|
42
|
-
'| ID | 标题 | 链接 | 文件 | 已采用 |',
|
|
43
|
-
'
|
|
52
|
+
'| ID | 标题 | 链接 | 文件 | 拉取 | 已采用 |',
|
|
53
|
+
'|:---|:---|---:|---:|---:|---:|',
|
|
44
54
|
];
|
|
45
55
|
for (const ref of refs) {
|
|
46
|
-
|
|
56
|
+
const fetchCount = ref.fetchHistory?.length || 0;
|
|
57
|
+
lines.push(`| ${ref.id} | ${ref.title} | ${ref.links.length} | ${ref.files.length} | ${fetchCount} | ${ref.adoptedBy.length} |`);
|
|
47
58
|
}
|
|
48
59
|
return wrap(lines.join('\n'));
|
|
49
60
|
}
|
|
@@ -61,9 +72,17 @@ export function registerReferenceTools(server) {
|
|
|
61
72
|
`- Files: ${ref.files.length}`,
|
|
62
73
|
`- Scripts: ${ref.scripts.length}`,
|
|
63
74
|
`- AdoptedBy: ${ref.adoptedBy.length}`,
|
|
64
|
-
'',
|
|
65
|
-
ref.summary || '(无摘要)',
|
|
66
75
|
];
|
|
76
|
+
if (ref.sourceUrl) {
|
|
77
|
+
lines.push(`- SourceURL: ${ref.sourceUrl}`);
|
|
78
|
+
}
|
|
79
|
+
if (ref.fetchHistory && ref.fetchHistory.length > 0) {
|
|
80
|
+
lines.push(`- FetchCount: ${ref.fetchHistory.length} 次`);
|
|
81
|
+
lines.push(`- LastFetch: ${ref.fetchHistory[ref.fetchHistory.length - 1].fetchedAt}`);
|
|
82
|
+
}
|
|
83
|
+
lines.push(`- Created: ${ref.createdAt}`);
|
|
84
|
+
lines.push(`- Updated: ${ref.updatedAt}`);
|
|
85
|
+
lines.push('', ref.summary || '(无摘要)');
|
|
67
86
|
if (ref.links.length > 0) {
|
|
68
87
|
lines.push('', '## Links');
|
|
69
88
|
for (const link of ref.links) {
|
|
@@ -82,6 +101,20 @@ export function registerReferenceTools(server) {
|
|
|
82
101
|
lines.push(`- ${script.name}: ${script.path}`);
|
|
83
102
|
}
|
|
84
103
|
}
|
|
104
|
+
if (ref.fetchHistory && ref.fetchHistory.length > 0) {
|
|
105
|
+
lines.push('', '## Fetch History');
|
|
106
|
+
lines.push('| # | 时间 | 标题 | 大小 | 文件 |');
|
|
107
|
+
lines.push('|:--|:-----|:-----|-----:|:-----|');
|
|
108
|
+
ref.fetchHistory.forEach((h, i) => {
|
|
109
|
+
lines.push(`| ${i + 1} | ${h.fetchedAt.substring(0, 19)} | ${h.title} | ${formatBytes(h.contentLength)} | ${h.contentFile} |`);
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
if (ref.adoptedBy.length > 0) {
|
|
113
|
+
lines.push('', '## AdoptedBy');
|
|
114
|
+
for (const node of ref.adoptedBy) {
|
|
115
|
+
lines.push(`- ${node.nodeLabel} [${node.chartId}/${node.nodeId}]`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
85
118
|
return wrap(lines.join('\n'));
|
|
86
119
|
}
|
|
87
120
|
case 'save': {
|
|
@@ -121,6 +154,113 @@ export function registerReferenceTools(server) {
|
|
|
121
154
|
const content = await client().readReferenceFile(decoded.path);
|
|
122
155
|
return wrap(content);
|
|
123
156
|
}
|
|
157
|
+
case 'fetch': {
|
|
158
|
+
if (!decoded.url)
|
|
159
|
+
return wrap('❌ fetch 需要 url (要拉取的文档地址)');
|
|
160
|
+
const result = await client().fetchRefUrl(decoded.url, decoded.id);
|
|
161
|
+
const lines = [
|
|
162
|
+
`✅ 文档已拉取并保存`,
|
|
163
|
+
'',
|
|
164
|
+
`| 字段 | 值 |`,
|
|
165
|
+
`|:---|:---|`,
|
|
166
|
+
`| 参考 ID | ${result.refId} |`,
|
|
167
|
+
`| 标题 | ${result.title} |`,
|
|
168
|
+
`| 内容文件 | ${result.contentFile} |`,
|
|
169
|
+
`| 内容大小 | ${formatBytes(result.contentLength)} |`,
|
|
170
|
+
`| 累计拉取 | ${result.fetchCount} 次 |`,
|
|
171
|
+
'',
|
|
172
|
+
'### 内容预览',
|
|
173
|
+
'',
|
|
174
|
+
result.contentPreview.substring(0, 500),
|
|
175
|
+
'',
|
|
176
|
+
`> 💡 用 \`kg_ref(get, id:"${result.refId}")\` 查看完整详情`,
|
|
177
|
+
`> 💡 用 \`kg_ref(read_file, path:"${result.contentFile}")\` 读取完整内容`,
|
|
178
|
+
`> 💡 用 \`kg_ref(refetch, id:"${result.refId}")\` 重新拉取最新版本`,
|
|
179
|
+
];
|
|
180
|
+
return wrap(lines.join('\n'));
|
|
181
|
+
}
|
|
182
|
+
case 'refetch': {
|
|
183
|
+
if (!decoded.id)
|
|
184
|
+
return wrap('❌ refetch 需要 id (参考 ID)');
|
|
185
|
+
const ref = await client().getReference(decoded.id);
|
|
186
|
+
if (!ref)
|
|
187
|
+
return wrap(`❌ 未找到参考: ${decoded.id}`);
|
|
188
|
+
const refetchUrl = decoded.url || ref.sourceUrl;
|
|
189
|
+
if (!refetchUrl)
|
|
190
|
+
return wrap(`❌ 参考 ${decoded.id} 没有 sourceUrl,请提供 url 参数`);
|
|
191
|
+
const result = await client().fetchRefUrl(refetchUrl, decoded.id);
|
|
192
|
+
const lines = [
|
|
193
|
+
`✅ 文档已重新拉取 (第 ${result.fetchCount} 次)`,
|
|
194
|
+
'',
|
|
195
|
+
`| 字段 | 值 |`,
|
|
196
|
+
`|:---|:---|`,
|
|
197
|
+
`| 参考 ID | ${result.refId} |`,
|
|
198
|
+
`| 标题 | ${result.title} |`,
|
|
199
|
+
`| 新内容文件 | ${result.contentFile} |`,
|
|
200
|
+
`| 内容大小 | ${formatBytes(result.contentLength)} |`,
|
|
201
|
+
'',
|
|
202
|
+
'### 内容预览',
|
|
203
|
+
'',
|
|
204
|
+
result.contentPreview.substring(0, 300),
|
|
205
|
+
];
|
|
206
|
+
return wrap(lines.join('\n'));
|
|
207
|
+
}
|
|
208
|
+
case 'import_file': {
|
|
209
|
+
if (!decoded.id)
|
|
210
|
+
return wrap('❌ import_file 需要 id (参考 ID)');
|
|
211
|
+
if (!decoded.path)
|
|
212
|
+
return wrap('❌ import_file 需要 path (本地文件路径)');
|
|
213
|
+
const result = await client().importLocalFile(decoded.id, decoded.path);
|
|
214
|
+
const lines = [
|
|
215
|
+
`✅ 文件内容已导入参考系统`,
|
|
216
|
+
'',
|
|
217
|
+
`| 字段 | 值 |`,
|
|
218
|
+
`|:---|:---|`,
|
|
219
|
+
`| 参考 ID | ${result.refId} |`,
|
|
220
|
+
`| 源文件 | ${result.sourcePath} |`,
|
|
221
|
+
`| 存储文件 | ${result.storedFile} |`,
|
|
222
|
+
`| 内容大小 | ${formatBytes(result.contentLength)} |`,
|
|
223
|
+
`| 累计导入 | ${result.importCount} 次 |`,
|
|
224
|
+
'',
|
|
225
|
+
'### 内容预览',
|
|
226
|
+
'',
|
|
227
|
+
result.contentPreview.substring(0, 300),
|
|
228
|
+
'',
|
|
229
|
+
`> 💡 用 \`kg_ref(read_file, path:"${result.storedFile}")\` 读取完整内容`,
|
|
230
|
+
`> 💡 用 \`kg_ref(reimport, id:"${result.refId}", path:"${result.sourcePath}")\` 重新导入最新版本`,
|
|
231
|
+
];
|
|
232
|
+
return wrap(lines.join('\n'));
|
|
233
|
+
}
|
|
234
|
+
case 'reimport': {
|
|
235
|
+
if (!decoded.id)
|
|
236
|
+
return wrap('❌ reimport 需要 id (参考 ID)');
|
|
237
|
+
const ref = await client().getReference(decoded.id);
|
|
238
|
+
if (!ref)
|
|
239
|
+
return wrap(`❌ 未找到参考: ${decoded.id}`);
|
|
240
|
+
// 获取源文件路径: 优先使用参数中的 path, 否则取最后一次导入的源路径
|
|
241
|
+
const reimportPath = decoded.path ||
|
|
242
|
+
(ref.fileImportHistory && ref.fileImportHistory.length > 0
|
|
243
|
+
? ref.fileImportHistory[ref.fileImportHistory.length - 1].sourcePath
|
|
244
|
+
: null);
|
|
245
|
+
if (!reimportPath)
|
|
246
|
+
return wrap(`❌ 参考 ${decoded.id} 没有导入历史,请提供 path 参数`);
|
|
247
|
+
const result = await client().importLocalFile(decoded.id, reimportPath);
|
|
248
|
+
const lines = [
|
|
249
|
+
`✅ 文件已重新导入 (第 ${result.importCount} 次)`,
|
|
250
|
+
'',
|
|
251
|
+
`| 字段 | 值 |`,
|
|
252
|
+
`|:---|:---|`,
|
|
253
|
+
`| 参考 ID | ${result.refId} |`,
|
|
254
|
+
`| 源文件 | ${result.sourcePath} |`,
|
|
255
|
+
`| 新存储文件 | ${result.storedFile} |`,
|
|
256
|
+
`| 内容大小 | ${formatBytes(result.contentLength)} |`,
|
|
257
|
+
'',
|
|
258
|
+
'### 内容预览',
|
|
259
|
+
'',
|
|
260
|
+
result.contentPreview.substring(0, 300),
|
|
261
|
+
];
|
|
262
|
+
return wrap(lines.join('\n'));
|
|
263
|
+
}
|
|
124
264
|
default:
|
|
125
265
|
return wrap(`❌ 未知 action: ${decoded.action}`);
|
|
126
266
|
}
|
package/dist/tools/shared.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ export interface McpContext {
|
|
|
9
9
|
agentId: string;
|
|
10
10
|
}
|
|
11
11
|
/** 创建可变上下文对象 (工具闭包捕获此对象的引用,kg_init 更新其属性) */
|
|
12
|
-
export declare function createContext(projectId: string, user: string, agentId
|
|
12
|
+
export declare function createContext(projectId: string, user: string, agentId: string): McpContext;
|
|
13
13
|
export declare function wrap(text: string): {
|
|
14
14
|
content: {
|
|
15
15
|
type: "text";
|
package/dist/tools/shared.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* 合并原 helpers.ts + 新增 safeTool / withCross 模式
|
|
4
4
|
*/
|
|
5
5
|
/** 创建可变上下文对象 (工具闭包捕获此对象的引用,kg_init 更新其属性) */
|
|
6
|
-
export function createContext(projectId, user, agentId
|
|
6
|
+
export function createContext(projectId, user, agentId) {
|
|
7
7
|
return { projectId, user, agentId };
|
|
8
8
|
}
|
|
9
9
|
// ==================== MCP 返回包装 ====================
|
package/dist/tools/tasks.js
CHANGED
|
@@ -8,7 +8,8 @@ import { decodeObjectStrings } from '../utils.js';
|
|
|
8
8
|
import { wrap, safeTool } from './shared.js';
|
|
9
9
|
export function registerTaskTools(server, ctx) {
|
|
10
10
|
const client = () => getClient();
|
|
11
|
-
server.tool('kg_task', '📝 任务记录 — action: create(创建,建议bindTo关联节点)|get(查询)|update(追加进度)|archive(归档)|delete(删除)。⚠️ 每完成一个步骤必须立即update
|
|
11
|
+
server.tool('kg_task', '📝 任务记录 — action: create(创建,建议bindTo关联节点)|get(查询)|update(追加进度)|archive(归档)|delete(删除)。⚠️ 每完成一个步骤必须立即update!\n' +
|
|
12
|
+
'★ 涉及逻辑变更的任务,创建后必须先执行 design-first 工作流(kg_workflow(id:"design-first")),在流程图中完成设计并验证后才能编码。', {
|
|
12
13
|
action: z.enum(['create', 'get', 'update', 'archive', 'delete'])
|
|
13
14
|
.describe('操作类型'),
|
|
14
15
|
title: z.string().optional()
|
package/package.json
CHANGED
package/templates/AGENT.md
CHANGED
|
@@ -1,64 +1,105 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
|
44
|
-
|
|
45
|
-
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
|
49
|
-
|
|
50
|
-
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
1
|
+
# ppdocs 知识图谱 — AI 协作规范
|
|
2
|
+
|
|
3
|
+
本项目使用 ppdocs 知识图谱系统管理项目知识。你拥有一组 MCP 工具来访问项目的架构图谱、文档、任务和代码分析能力。
|
|
4
|
+
|
|
5
|
+
## 首次对话启动
|
|
6
|
+
|
|
7
|
+
每次新对话开始时,按以下顺序获取项目上下文:
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
1. kg_status() → 项目概况(含架构摘要、活跃任务、核心模块列表)
|
|
11
|
+
2. kg_flowchart(get, "main") → 需要深入时查看完整架构图
|
|
12
|
+
3. kg_workflow() → 查看可用的标准工作流
|
|
13
|
+
4. kg_task(get) → 查看活跃任务详情
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
通常只需第 1 步就能获得足够的项目上下文开始工作。
|
|
17
|
+
|
|
18
|
+
## 工具速查
|
|
19
|
+
|
|
20
|
+
### 理解项目
|
|
21
|
+
| 工具 | 用途 |
|
|
22
|
+
|:---|:---|
|
|
23
|
+
| `kg_status()` | 一键获取项目全貌(推荐首选) |
|
|
24
|
+
| `kg_flowchart(get, chartId)` | 查看某张流程图的完整结构 |
|
|
25
|
+
| `kg_flowchart(get_node, nodeId, expand:2)` | 深入某个模块,含文档和绑定文件 |
|
|
26
|
+
| `kg_flowchart(search, query)` | 按关键词搜索流程图节点 |
|
|
27
|
+
| `kg_doc(search, query)` | 搜索节点内嵌文档 |
|
|
28
|
+
| `code_smart_context(symbolName)` | 获取代码符号的依赖和关联文档 |
|
|
29
|
+
| `code_full_path(symbolA, symbolB)` | 查找两个符号之间的关联路径 |
|
|
30
|
+
|
|
31
|
+
### 执行任务
|
|
32
|
+
| 工具 | 用途 |
|
|
33
|
+
|:---|:---|
|
|
34
|
+
| `kg_task(create, title, goals, bindTo)` | 开始新任务(绑定到流程图节点) |
|
|
35
|
+
| `kg_task(update, content, log_type)` | 记录进度/问题/方案(每步都要记录) |
|
|
36
|
+
| `kg_task(archive, summary, solutions)` | 完成任务并归档经验 |
|
|
37
|
+
| `kg_workflow(id)` | 获取标准工作流指导(如有匹配的工作流) |
|
|
38
|
+
|
|
39
|
+
### 回写知识
|
|
40
|
+
| 工具 | 用途 |
|
|
41
|
+
|:---|:---|
|
|
42
|
+
| `kg_flowchart(update_node, nodeId, docSummary, docContent)` | 更新节点文档 |
|
|
43
|
+
| `kg_flowchart(bind, nodeId, files:[...])` | 绑定源码文件到节点 |
|
|
44
|
+
| `kg_flowchart(batch_add, nodes, edges)` | 新增模块时批量创建节点 |
|
|
45
|
+
| `kg_flowchart(create_chart)` | 为复杂模块创建子流程图 |
|
|
46
|
+
|
|
47
|
+
### 协作与文件
|
|
48
|
+
| 工具 | 用途 |
|
|
49
|
+
|:---|:---|
|
|
50
|
+
| `kg_discuss(create/reply/list)` | 跨 AI 实例讨论 |
|
|
51
|
+
| `kg_meeting(join/post/status)` | 多 AI 协作会议 |
|
|
52
|
+
| `kg_files(list/read/download)` | 项目文件管理 |
|
|
53
|
+
| `kg_ref(list/get/save)` | 外部参考资料管理 |
|
|
54
|
+
|
|
55
|
+
## 核心原则
|
|
56
|
+
|
|
57
|
+
1. **先查图谱再看代码** — 在 grep/搜索代码之前,先用 `kg_flowchart(search)` 或 `kg_doc(search)` 查找相关节点,通常能直接定位到关键文件和逻辑说明,减少 80% 的盲目搜索。
|
|
58
|
+
|
|
59
|
+
2. **每完成一步就 update task** — 使用 `kg_task(update)` 记录每个阶段的进展,遇到问题用 `log_type:"issue"` 记录,找到方案用 `log_type:"solution"` 记录。
|
|
60
|
+
|
|
61
|
+
3. **改完代码必须回写图谱** — 修改了代码后,检查对应的流程图节点文档是否需要更新。新增文件要 `bind`,逻辑变更要 `update_node` 的 docContent。
|
|
62
|
+
|
|
63
|
+
4. **子图递归探索** — 节点如果有 `subFlowchart` 字段,说明它有更细粒度的子流程图。理解细节时要递归下探。
|
|
64
|
+
|
|
65
|
+
## 节点文档模板
|
|
66
|
+
|
|
67
|
+
创建或更新节点 `docContent` 时,推荐使用以下结构:
|
|
68
|
+
|
|
69
|
+
```markdown
|
|
70
|
+
## 职责
|
|
71
|
+
一句话说清本模块的核心职责。
|
|
72
|
+
|
|
73
|
+
## 输入 / 输出
|
|
74
|
+
- 输入: 进入本模块的数据或请求
|
|
75
|
+
- 输出: 本模块产出的数据或响应
|
|
76
|
+
|
|
77
|
+
## 核心逻辑
|
|
78
|
+
1. 步骤一
|
|
79
|
+
2. 步骤二
|
|
80
|
+
3. ...(3-7 步为宜)
|
|
81
|
+
|
|
82
|
+
## 边界条件
|
|
83
|
+
- 它不负责什么(明确排除)
|
|
84
|
+
- 错误处理策略
|
|
85
|
+
|
|
86
|
+
## 关键文件
|
|
87
|
+
- path/to/main.go — 主逻辑
|
|
88
|
+
- path/to/helper.go — 辅助函数
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## 错误处理
|
|
92
|
+
|
|
93
|
+
| 场景 | 处理方式 |
|
|
94
|
+
|:---|:---|
|
|
95
|
+
| API 超时/网络错误 | 重试一次,仍失败则告知用户 |
|
|
96
|
+
| JSON 解析失败 | fallback 到纯文本输出 |
|
|
97
|
+
| 找不到节点 | 用 `kg_flowchart(search)` 模糊搜索 |
|
|
98
|
+
| 工具调用失败 | 检查参数格式,确保 JSON 数组/对象格式正确 |
|
|
99
|
+
|
|
100
|
+
## 输出格式
|
|
101
|
+
|
|
102
|
+
- 逻辑说明用 ASCII 流程图
|
|
103
|
+
- 对比分析用 Markdown 表格
|
|
104
|
+
- 代码引用注明文件路径和行号
|
|
105
|
+
- 方案说明要简洁,避免不必要的冗余
|