@ppdocs/mcp 2.6.17 → 2.6.18
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/storage/httpClient.d.ts +6 -0
- package/dist/storage/httpClient.js +7 -1
- package/dist/storage/types.d.ts +4 -0
- package/dist/tools/index.js +106 -16
- package/dist/utils.d.ts +8 -2
- package/dist/utils.js +51 -26
- package/package.json +1 -1
- package/templates/commands/pp/init.md +234 -115
- package/templates/commands/pp/review.md +91 -46
- package/templates/commands/pp/sync.md +102 -74
- package/templates/hooks/N.md +94 -0
- package/templates/hooks/SystemPrompt.md +87 -149
|
@@ -22,6 +22,9 @@ export declare class PpdocsApiClient {
|
|
|
22
22
|
title?: string;
|
|
23
23
|
description?: string;
|
|
24
24
|
userStyles?: string[];
|
|
25
|
+
testRules?: string[];
|
|
26
|
+
reviewRules?: string[];
|
|
27
|
+
codeStyle?: string[];
|
|
25
28
|
}): Promise<NodeData | null>;
|
|
26
29
|
deleteNode(nodeId: string): Promise<boolean>;
|
|
27
30
|
lockNode(nodeId: string, locked: boolean): Promise<NodeData | null>;
|
|
@@ -59,6 +62,9 @@ export declare function updateRoot(_projectId: string, updates: {
|
|
|
59
62
|
title?: string;
|
|
60
63
|
description?: string;
|
|
61
64
|
userStyles?: string[];
|
|
65
|
+
testRules?: string[];
|
|
66
|
+
reviewRules?: string[];
|
|
67
|
+
codeStyle?: string[];
|
|
62
68
|
}): Promise<NodeData | null>;
|
|
63
69
|
export declare function deleteNode(_projectId: string, nodeId: string): Promise<boolean>;
|
|
64
70
|
export declare function lockNode(_projectId: string, nodeId: string, locked: boolean): Promise<NodeData | null>;
|
|
@@ -192,7 +192,7 @@ export class PpdocsApiClient {
|
|
|
192
192
|
}
|
|
193
193
|
}
|
|
194
194
|
async updateRoot(updates) {
|
|
195
|
-
//
|
|
195
|
+
// 专用根节点更新,支持所有规则字段
|
|
196
196
|
const root = await this.getNode('root');
|
|
197
197
|
if (!root)
|
|
198
198
|
return null;
|
|
@@ -206,6 +206,12 @@ export class PpdocsApiClient {
|
|
|
206
206
|
payload.description = updates.description;
|
|
207
207
|
if (updates.userStyles !== undefined)
|
|
208
208
|
payload.userStyles = updates.userStyles;
|
|
209
|
+
if (updates.testRules !== undefined)
|
|
210
|
+
payload.testRules = updates.testRules;
|
|
211
|
+
if (updates.reviewRules !== undefined)
|
|
212
|
+
payload.reviewRules = updates.reviewRules;
|
|
213
|
+
if (updates.codeStyle !== undefined)
|
|
214
|
+
payload.codeStyle = updates.codeStyle;
|
|
209
215
|
try {
|
|
210
216
|
return await this.request('/nodes/root', {
|
|
211
217
|
method: 'PUT',
|
package/dist/storage/types.d.ts
CHANGED
|
@@ -40,12 +40,16 @@ export interface NodeData {
|
|
|
40
40
|
isOrigin?: boolean;
|
|
41
41
|
signature: string;
|
|
42
42
|
categories: string[];
|
|
43
|
+
path: string;
|
|
43
44
|
description: string;
|
|
44
45
|
dataInput?: DataRef;
|
|
45
46
|
dataOutput?: DataRef;
|
|
46
47
|
dependencies: Dependency[];
|
|
47
48
|
relatedFiles?: string[];
|
|
48
49
|
userStyles?: string[];
|
|
50
|
+
testRules?: string[];
|
|
51
|
+
reviewRules?: string[];
|
|
52
|
+
codeStyle?: string[];
|
|
49
53
|
createdAt?: string;
|
|
50
54
|
updatedAt?: string;
|
|
51
55
|
lastAccessedAt?: string;
|
package/dist/tools/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import * as storage from '../storage/httpClient.js';
|
|
3
|
-
import { decodeUnicodeEscapes, decodeObjectStrings, wrapResult, clearStyleCache,
|
|
3
|
+
import { decodeUnicodeEscapes, decodeObjectStrings, wrapResult, clearStyleCache, getRules, RULE_TYPE_LABELS } from '../utils.js';
|
|
4
4
|
// 辅助函数: 包装返回结果
|
|
5
5
|
async function wrap(projectId, text) {
|
|
6
6
|
const wrapped = await wrapResult(projectId, text);
|
|
@@ -12,8 +12,9 @@ export function registerTools(server, projectId, _user) {
|
|
|
12
12
|
title: z.string().describe('节点标题'),
|
|
13
13
|
type: z.enum(['logic', 'data', 'intro']).describe('节点类型'),
|
|
14
14
|
description: z.string().describe('Markdown描述(用Mermaid流程图+表格,禁止纯文字)'),
|
|
15
|
+
path: z.string().min(1).describe('目录路径(必填,如"/前端/组件")'),
|
|
16
|
+
tags: z.array(z.string()).min(3).describe('分类标签(至少3个)'),
|
|
15
17
|
signature: z.string().optional().describe('唯一签名(用于依赖匹配,默认=title)'),
|
|
16
|
-
tags: z.array(z.string()).optional().describe('分类标签'),
|
|
17
18
|
dependencies: z.array(z.object({
|
|
18
19
|
name: z.string().describe('目标节点的signature'),
|
|
19
20
|
description: z.string().describe('依赖说明')
|
|
@@ -28,7 +29,8 @@ export function registerTools(server, projectId, _user) {
|
|
|
28
29
|
// x, y 不传递,由 httpClient 智能计算位置
|
|
29
30
|
locked: false,
|
|
30
31
|
signature: args.signature || args.title,
|
|
31
|
-
categories: args.tags
|
|
32
|
+
categories: args.tags,
|
|
33
|
+
path: args.path,
|
|
32
34
|
dependencies: args.dependencies || [],
|
|
33
35
|
relatedFiles: args.relatedFiles || []
|
|
34
36
|
});
|
|
@@ -45,10 +47,11 @@ export function registerTools(server, projectId, _user) {
|
|
|
45
47
|
title: z.string().optional().describe('新标题'),
|
|
46
48
|
signature: z.string().optional().describe('新签名'),
|
|
47
49
|
description: z.string().optional().describe('新描述(Markdown)'),
|
|
50
|
+
path: z.string().optional().describe('目录路径(如"/前端/组件")'),
|
|
48
51
|
x: z.number().optional().describe('X坐标'),
|
|
49
52
|
y: z.number().optional().describe('Y坐标'),
|
|
50
53
|
status: z.enum(['incomplete', 'complete', 'fixing', 'refactoring', 'deprecated']).optional().describe('状态'),
|
|
51
|
-
tags: z.array(z.string()).optional().describe('分类标签'),
|
|
54
|
+
tags: z.array(z.string()).min(3).optional().describe('分类标签(至少3个)'),
|
|
52
55
|
dependencies: z.array(z.object({
|
|
53
56
|
name: z.string(),
|
|
54
57
|
description: z.string()
|
|
@@ -67,7 +70,7 @@ export function registerTools(server, projectId, _user) {
|
|
|
67
70
|
impact: z.string().optional()
|
|
68
71
|
})).optional().describe('修复记录')
|
|
69
72
|
}, async (args) => {
|
|
70
|
-
const { nodeId, tags, relatedFiles, versions, bugfixes, ...rest } = args;
|
|
73
|
+
const { nodeId, tags, relatedFiles, versions, bugfixes, path, ...rest } = args;
|
|
71
74
|
// 根节点必须使用 kg_update_root 更新
|
|
72
75
|
if (nodeId === 'root') {
|
|
73
76
|
return wrap(projectId, '❌ 根节点请使用 kg_update_root 方法更新');
|
|
@@ -82,35 +85,50 @@ export function registerTools(server, projectId, _user) {
|
|
|
82
85
|
updates.versions = versions;
|
|
83
86
|
if (bugfixes !== undefined)
|
|
84
87
|
updates.bugfixes = bugfixes;
|
|
88
|
+
if (path !== undefined)
|
|
89
|
+
updates.path = path;
|
|
85
90
|
const node = await storage.updateNode(projectId, nodeId, updates);
|
|
86
91
|
return wrap(projectId, node ? JSON.stringify(node, null, 2) : '更新失败(节点不存在或已锁定)');
|
|
87
92
|
});
|
|
88
|
-
// 3.5 更新根节点 (
|
|
93
|
+
// 3.5 更新根节点 (专用方法,支持所有规则)
|
|
89
94
|
server.tool('kg_update_root', '更新根节点(项目规则,锁定时不可更新)', {
|
|
90
95
|
title: z.string().optional().describe('项目标题'),
|
|
91
96
|
description: z.string().optional().describe('项目描述(Markdown)'),
|
|
92
|
-
userStyles: z.array(z.string()).optional().describe('
|
|
97
|
+
userStyles: z.array(z.string()).optional().describe('项目规则(字符串数组)'),
|
|
98
|
+
testRules: z.array(z.string()).optional().describe('测试规则(字符串数组)'),
|
|
99
|
+
reviewRules: z.array(z.string()).optional().describe('审查规则(字符串数组)'),
|
|
100
|
+
codeStyle: z.array(z.string()).optional().describe('编码风格(字符串数组)')
|
|
93
101
|
}, async (args) => {
|
|
94
102
|
// 空参数检查
|
|
95
|
-
|
|
96
|
-
|
|
103
|
+
const hasUpdate = args.title !== undefined || args.description !== undefined ||
|
|
104
|
+
args.userStyles !== undefined || args.testRules !== undefined ||
|
|
105
|
+
args.reviewRules !== undefined || args.codeStyle !== undefined;
|
|
106
|
+
if (!hasUpdate) {
|
|
107
|
+
return wrap(projectId, '❌ 请至少提供一个更新参数');
|
|
97
108
|
}
|
|
98
109
|
const node = await storage.updateRoot(projectId, {
|
|
99
110
|
title: args.title,
|
|
100
111
|
description: args.description,
|
|
101
|
-
userStyles: args.userStyles
|
|
112
|
+
userStyles: args.userStyles,
|
|
113
|
+
testRules: args.testRules,
|
|
114
|
+
reviewRules: args.reviewRules,
|
|
115
|
+
codeStyle: args.codeStyle
|
|
102
116
|
});
|
|
103
117
|
if (node)
|
|
104
118
|
clearStyleCache();
|
|
105
119
|
return wrap(projectId, node ? JSON.stringify(node, null, 2) : '更新失败(根节点已锁定)');
|
|
106
120
|
});
|
|
107
121
|
// 3.6 获取项目规则 (独立方法,按需调用)
|
|
108
|
-
server.tool('kg_get_rules', '
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
122
|
+
server.tool('kg_get_rules', '获取项目规则(可指定类型或获取全部)', {
|
|
123
|
+
ruleType: z.enum(['userStyles', 'testRules', 'reviewRules', 'codeStyle']).optional()
|
|
124
|
+
.describe('规则类型: userStyles=项目规则, testRules=测试规则, reviewRules=审查规则, codeStyle=编码风格。不传则返回全部')
|
|
125
|
+
}, async (args) => {
|
|
126
|
+
const rules = await getRules(projectId, args.ruleType);
|
|
127
|
+
if (!rules || rules.trim() === '') {
|
|
128
|
+
const typeName = args.ruleType ? RULE_TYPE_LABELS[args.ruleType] : '项目';
|
|
129
|
+
return { content: [{ type: 'text', text: `暂无${typeName}规则` }] };
|
|
112
130
|
}
|
|
113
|
-
return { content: [{ type: 'text', text:
|
|
131
|
+
return { content: [{ type: 'text', text: rules }] };
|
|
114
132
|
});
|
|
115
133
|
// 4. 锁定节点 (只能锁定,解锁需用户在前端手动操作)
|
|
116
134
|
server.tool('kg_lock_node', '锁定节点(锁定后只能读取,解锁需用户在前端手动操作)', {
|
|
@@ -254,8 +272,80 @@ export function registerTools(server, projectId, _user) {
|
|
|
254
272
|
}
|
|
255
273
|
return wrap(projectId, lines.join('\n'));
|
|
256
274
|
});
|
|
275
|
+
// 10. 获取知识库树状图
|
|
276
|
+
server.tool('kg_get_tree', '获取知识库的目录树结构(按 path 分组)', {}, async () => {
|
|
277
|
+
const nodes = await storage.listNodes(projectId);
|
|
278
|
+
const root = { name: '/', type: 'folder', children: [] };
|
|
279
|
+
// 过滤掉根节点
|
|
280
|
+
const docNodes = nodes.filter(n => !n.isOrigin && n.id !== 'root');
|
|
281
|
+
for (const node of docNodes) {
|
|
282
|
+
const pathSegments = (node.path || '').split('/').filter(Boolean);
|
|
283
|
+
let current = root;
|
|
284
|
+
// 遍历路径段,创建目录结构
|
|
285
|
+
for (const segment of pathSegments) {
|
|
286
|
+
if (!current.children)
|
|
287
|
+
current.children = [];
|
|
288
|
+
let child = current.children.find(c => c.name === segment && c.type === 'folder');
|
|
289
|
+
if (!child) {
|
|
290
|
+
child = { name: segment, type: 'folder', children: [] };
|
|
291
|
+
current.children.push(child);
|
|
292
|
+
}
|
|
293
|
+
current = child;
|
|
294
|
+
}
|
|
295
|
+
// 添加节点
|
|
296
|
+
if (!current.children)
|
|
297
|
+
current.children = [];
|
|
298
|
+
current.children.push({
|
|
299
|
+
name: node.title,
|
|
300
|
+
type: 'node',
|
|
301
|
+
nodeId: node.id,
|
|
302
|
+
status: node.status
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
// 递归排序 (目录在前,节点在后)
|
|
306
|
+
function sortTree(node) {
|
|
307
|
+
if (!node.children)
|
|
308
|
+
return;
|
|
309
|
+
node.children.sort((a, b) => {
|
|
310
|
+
if (a.type === 'folder' && b.type !== 'folder')
|
|
311
|
+
return -1;
|
|
312
|
+
if (a.type !== 'folder' && b.type === 'folder')
|
|
313
|
+
return 1;
|
|
314
|
+
return a.name.localeCompare(b.name, 'zh-CN');
|
|
315
|
+
});
|
|
316
|
+
for (const child of node.children) {
|
|
317
|
+
if (child.type === 'folder')
|
|
318
|
+
sortTree(child);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
sortTree(root);
|
|
322
|
+
// 格式化为文本树
|
|
323
|
+
function formatTree(node, prefix = '', isLast = true) {
|
|
324
|
+
const lines = [];
|
|
325
|
+
const connector = isLast ? '└── ' : '├── ';
|
|
326
|
+
const childPrefix = isLast ? ' ' : '│ ';
|
|
327
|
+
if (node.name !== '/') {
|
|
328
|
+
const icon = node.type === 'folder' ? '📁' : '📄';
|
|
329
|
+
const status = node.status ? ` [${node.status}]` : '';
|
|
330
|
+
lines.push(`${prefix}${connector}${icon} ${node.name}${status}`);
|
|
331
|
+
}
|
|
332
|
+
if (node.children) {
|
|
333
|
+
const children = node.children;
|
|
334
|
+
for (let i = 0; i < children.length; i++) {
|
|
335
|
+
const child = children[i];
|
|
336
|
+
const childIsLast = i === children.length - 1;
|
|
337
|
+
const newPrefix = node.name === '/' ? '' : prefix + childPrefix;
|
|
338
|
+
lines.push(...formatTree(child, newPrefix, childIsLast));
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
return lines;
|
|
342
|
+
}
|
|
343
|
+
const treeText = formatTree(root).join('\n');
|
|
344
|
+
const summary = `共 ${docNodes.length} 个节点\n\n${treeText}`;
|
|
345
|
+
return wrap(projectId, summary);
|
|
346
|
+
});
|
|
257
347
|
// ===================== 任务管理工具 =====================
|
|
258
|
-
//
|
|
348
|
+
// 11. 创建任务
|
|
259
349
|
server.tool('task_create', '创建开发任务,记录目标和关联节点', {
|
|
260
350
|
title: z.string().describe('任务标题'),
|
|
261
351
|
description: z.string().describe('任务描述(Markdown)'),
|
package/dist/utils.d.ts
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* MCP Server 工具函数
|
|
3
3
|
*/
|
|
4
|
+
export type RuleType = 'userStyles' | 'testRules' | 'reviewRules' | 'codeStyle';
|
|
5
|
+
export declare const RULE_TYPE_LABELS: Record<RuleType, string>;
|
|
4
6
|
/**
|
|
5
|
-
*
|
|
7
|
+
* 获取指定类型的规则
|
|
8
|
+
*/
|
|
9
|
+
export declare function getRules(projectId: string, ruleType?: RuleType): Promise<string>;
|
|
10
|
+
/**
|
|
11
|
+
* 获取根节点的用户风格 (兼容旧接口)
|
|
6
12
|
*/
|
|
7
13
|
export declare function getRootStyle(projectId: string): Promise<string>;
|
|
8
14
|
/**
|
|
9
|
-
*
|
|
15
|
+
* 清除规则缓存 (当根节点更新时调用)
|
|
10
16
|
*/
|
|
11
17
|
export declare function clearStyleCache(): void;
|
|
12
18
|
/**
|
package/dist/utils.js
CHANGED
|
@@ -2,51 +2,76 @@
|
|
|
2
2
|
* MCP Server 工具函数
|
|
3
3
|
*/
|
|
4
4
|
import * as storage from './storage/httpClient.js';
|
|
5
|
-
//
|
|
6
|
-
|
|
5
|
+
// 规则类型显示名
|
|
6
|
+
export const RULE_TYPE_LABELS = {
|
|
7
|
+
userStyles: '项目规则',
|
|
8
|
+
testRules: '测试规则',
|
|
9
|
+
reviewRules: '审查规则',
|
|
10
|
+
codeStyle: '编码风格',
|
|
11
|
+
};
|
|
12
|
+
// 缓存根节点 (避免每次调用都请求)
|
|
13
|
+
let cachedRootNode = null;
|
|
7
14
|
let cacheProjectId = null;
|
|
8
15
|
/**
|
|
9
|
-
*
|
|
16
|
+
* 将字符串数组格式化为 Markdown 列表
|
|
10
17
|
*/
|
|
11
|
-
function
|
|
12
|
-
if (!
|
|
18
|
+
function formatRulesList(rules) {
|
|
19
|
+
if (!rules || rules.length === 0)
|
|
13
20
|
return '';
|
|
14
|
-
return
|
|
21
|
+
return rules.map((s, i) => `${i + 1}. ${s}`).join('\n\n');
|
|
15
22
|
}
|
|
16
23
|
/**
|
|
17
|
-
*
|
|
24
|
+
* 获取根节点数据 (带缓存)
|
|
18
25
|
*/
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
return cachedRootStyle;
|
|
26
|
+
async function getCachedRoot(projectId) {
|
|
27
|
+
if (cachedRootNode !== null && cacheProjectId === projectId) {
|
|
28
|
+
return cachedRootNode;
|
|
23
29
|
}
|
|
24
30
|
try {
|
|
25
31
|
const rootNode = await storage.getNode(projectId, 'root');
|
|
26
|
-
|
|
27
|
-
cachedRootStyle = '';
|
|
28
|
-
cacheProjectId = projectId;
|
|
29
|
-
return '';
|
|
30
|
-
}
|
|
31
|
-
// 使用 userStyles 字符串数组
|
|
32
|
-
if (rootNode.userStyles && rootNode.userStyles.length > 0) {
|
|
33
|
-
cachedRootStyle = formatUserStyles(rootNode.userStyles);
|
|
34
|
-
}
|
|
35
|
-
else {
|
|
36
|
-
cachedRootStyle = '';
|
|
37
|
-
}
|
|
32
|
+
cachedRootNode = rootNode;
|
|
38
33
|
cacheProjectId = projectId;
|
|
39
|
-
return
|
|
34
|
+
return cachedRootNode;
|
|
40
35
|
}
|
|
41
36
|
catch {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* 获取指定类型的规则
|
|
42
|
+
*/
|
|
43
|
+
export async function getRules(projectId, ruleType) {
|
|
44
|
+
const rootNode = await getCachedRoot(projectId);
|
|
45
|
+
if (!rootNode)
|
|
42
46
|
return '';
|
|
47
|
+
// 如果指定了类型,只返回该类型
|
|
48
|
+
if (ruleType) {
|
|
49
|
+
const rules = rootNode[ruleType];
|
|
50
|
+
if (!rules || rules.length === 0)
|
|
51
|
+
return '';
|
|
52
|
+
return `[${RULE_TYPE_LABELS[ruleType]}]\n${formatRulesList(rules)}`;
|
|
53
|
+
}
|
|
54
|
+
// 返回所有规则
|
|
55
|
+
const allRules = [];
|
|
56
|
+
for (const type of Object.keys(RULE_TYPE_LABELS)) {
|
|
57
|
+
const rules = rootNode[type];
|
|
58
|
+
if (rules && rules.length > 0) {
|
|
59
|
+
allRules.push(`[${RULE_TYPE_LABELS[type]}]\n${formatRulesList(rules)}`);
|
|
60
|
+
}
|
|
43
61
|
}
|
|
62
|
+
return allRules.join('\n\n---\n\n');
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* 获取根节点的用户风格 (兼容旧接口)
|
|
66
|
+
*/
|
|
67
|
+
export async function getRootStyle(projectId) {
|
|
68
|
+
return getRules(projectId, 'userStyles');
|
|
44
69
|
}
|
|
45
70
|
/**
|
|
46
|
-
*
|
|
71
|
+
* 清除规则缓存 (当根节点更新时调用)
|
|
47
72
|
*/
|
|
48
73
|
export function clearStyleCache() {
|
|
49
|
-
|
|
74
|
+
cachedRootNode = null;
|
|
50
75
|
cacheProjectId = null;
|
|
51
76
|
}
|
|
52
77
|
/**
|