@ppdocs/mcp 2.7.2 → 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.
- package/dist/storage/httpClient.d.ts +23 -31
- package/dist/storage/httpClient.js +94 -170
- package/dist/storage/types.d.ts +23 -55
- package/dist/storage/types.js +1 -1
- package/dist/tools/helpers.d.ts +9 -14
- package/dist/tools/helpers.js +37 -113
- package/dist/tools/index.js +77 -99
- package/package.json +1 -1
|
@@ -4,29 +4,26 @@
|
|
|
4
4
|
*
|
|
5
5
|
* API URL 格式: http://localhost:20001/api/:projectId/:password/...
|
|
6
6
|
*/
|
|
7
|
-
import type {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
import type { DocData, DocNode, DocSearchResult, Task, TaskSummary, TaskLogType, TaskExperience } from './types.js';
|
|
8
|
+
interface TreeNode {
|
|
9
|
+
path: string;
|
|
10
|
+
name: string;
|
|
11
|
+
summary?: string;
|
|
12
|
+
isDir: boolean;
|
|
13
|
+
children?: TreeNode[];
|
|
12
14
|
}
|
|
13
15
|
export declare class PpdocsApiClient {
|
|
14
16
|
private baseUrl;
|
|
15
17
|
constructor(apiUrl: string);
|
|
16
18
|
private request;
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
testRules?: string[];
|
|
26
|
-
reviewRules?: string[];
|
|
27
|
-
codeStyle?: string[];
|
|
28
|
-
}): Promise<NodeData | null>;
|
|
29
|
-
deleteNode(nodeId: string): Promise<boolean>;
|
|
19
|
+
listDocs(): Promise<DocNode[]>;
|
|
20
|
+
getDoc(docPath: string): Promise<DocData | null>;
|
|
21
|
+
createDoc(docPath: string, doc: Partial<DocData>): Promise<DocData>;
|
|
22
|
+
updateDoc(docPath: string, updates: Partial<DocData>): Promise<DocData | null>;
|
|
23
|
+
deleteDoc(docPath: string): Promise<boolean>;
|
|
24
|
+
searchDocs(keywords: string[], limit?: number): Promise<DocSearchResult[]>;
|
|
25
|
+
/** 获取目录树结构 */
|
|
26
|
+
getTree(): Promise<TreeNode[]>;
|
|
30
27
|
getRulesApi(ruleType: string): Promise<string[]>;
|
|
31
28
|
saveRulesApi(ruleType: string, rules: string[]): Promise<boolean>;
|
|
32
29
|
listTasks(status?: 'active' | 'archived'): Promise<TaskSummary[]>;
|
|
@@ -40,19 +37,13 @@ export declare class PpdocsApiClient {
|
|
|
40
37
|
completeTask(taskId: string, experience: TaskExperience): Promise<Task | null>;
|
|
41
38
|
}
|
|
42
39
|
export declare function initClient(apiUrl: string): void;
|
|
43
|
-
export declare function
|
|
44
|
-
export declare function
|
|
45
|
-
export declare function
|
|
46
|
-
export declare function
|
|
47
|
-
export declare function
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
userStyles?: string[];
|
|
51
|
-
testRules?: string[];
|
|
52
|
-
reviewRules?: string[];
|
|
53
|
-
codeStyle?: string[];
|
|
54
|
-
}): Promise<NodeData | null>;
|
|
55
|
-
export declare function deleteNode(_projectId: string, nodeId: string): Promise<boolean>;
|
|
40
|
+
export declare function listDocs(_projectId: string): Promise<DocNode[]>;
|
|
41
|
+
export declare function getDoc(_projectId: string, docPath: string): Promise<DocData | null>;
|
|
42
|
+
export declare function createDoc(_projectId: string, docPath: string, doc: Partial<DocData>): Promise<DocData>;
|
|
43
|
+
export declare function updateDoc(_projectId: string, docPath: string, updates: Partial<DocData>): Promise<DocData | null>;
|
|
44
|
+
export declare function deleteDoc(_projectId: string, docPath: string): Promise<boolean>;
|
|
45
|
+
export declare function searchDocs(_projectId: string, keywords: string[], limit?: number): Promise<DocSearchResult[]>;
|
|
46
|
+
export declare function getTree(_projectId: string): Promise<TreeNode[]>;
|
|
56
47
|
export declare function getRules(_projectId: string, ruleType: string): Promise<string[]>;
|
|
57
48
|
export declare function saveRules(_projectId: string, ruleType: string, rules: string[]): Promise<boolean>;
|
|
58
49
|
export declare function listTasks(_projectId: string, status?: 'active' | 'archived'): Promise<TaskSummary[]>;
|
|
@@ -64,3 +55,4 @@ export declare function createTask(_projectId: string, task: {
|
|
|
64
55
|
}, creator: string): Promise<Task>;
|
|
65
56
|
export declare function addTaskLog(_projectId: string, taskId: string, logType: TaskLogType, content: string): Promise<Task | null>;
|
|
66
57
|
export declare function completeTask(_projectId: string, taskId: string, experience: TaskExperience): Promise<Task | null>;
|
|
58
|
+
export type { TreeNode };
|
|
@@ -4,58 +4,38 @@
|
|
|
4
4
|
*
|
|
5
5
|
* API URL 格式: http://localhost:20001/api/:projectId/:password/...
|
|
6
6
|
*/
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const
|
|
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
|
-
const collides = (x, y) => nodes.some(n => Math.abs(n.x - x) < SMART_NODE_GAP && Math.abs(n.y - y) < SMART_NODE_GAP * 0.6);
|
|
37
|
-
if (!collides(cx, cy))
|
|
38
|
-
return { x: Math.round(cx), y: Math.round(cy) };
|
|
39
|
-
// 4. 螺旋搜索
|
|
40
|
-
const dirs = [[1, 0], [0, 1], [-1, 0], [0, -1]];
|
|
41
|
-
let x = cx, y = cy, step = 1, dir = 0, steps = 0, turns = 0;
|
|
42
|
-
for (let i = 0; i < SMART_SEARCH_RADIUS * SMART_SEARCH_RADIUS * 4; i++) {
|
|
43
|
-
x += dirs[dir][0] * SMART_NODE_GAP;
|
|
44
|
-
y += dirs[dir][1] * SMART_NODE_GAP * 0.6;
|
|
45
|
-
steps++;
|
|
46
|
-
if (!collides(x, y))
|
|
47
|
-
return { x: Math.round(x), y: Math.round(y) };
|
|
48
|
-
if (steps >= step) {
|
|
49
|
-
steps = 0;
|
|
50
|
-
dir = (dir + 1) % 4;
|
|
51
|
-
if (++turns >= 2) {
|
|
52
|
-
turns = 0;
|
|
53
|
-
step++;
|
|
54
|
-
}
|
|
7
|
+
/** 从扁平文档列表构建目录树 */
|
|
8
|
+
function buildTree(docs) {
|
|
9
|
+
const root = [];
|
|
10
|
+
const pathMap = new Map();
|
|
11
|
+
// 按路径深度排序,确保父目录先处理
|
|
12
|
+
const sorted = [...docs].sort((a, b) => {
|
|
13
|
+
const depthA = (a.path.match(/\//g) || []).length;
|
|
14
|
+
const depthB = (b.path.match(/\//g) || []).length;
|
|
15
|
+
return depthA - depthB;
|
|
16
|
+
});
|
|
17
|
+
for (const doc of sorted) {
|
|
18
|
+
const node = {
|
|
19
|
+
path: doc.path,
|
|
20
|
+
name: doc.name,
|
|
21
|
+
summary: doc.summary,
|
|
22
|
+
isDir: doc.isDir,
|
|
23
|
+
children: doc.isDir ? [] : undefined
|
|
24
|
+
};
|
|
25
|
+
// 找父路径
|
|
26
|
+
const lastSlash = doc.path.lastIndexOf('/');
|
|
27
|
+
const parentPath = lastSlash > 0 ? doc.path.substring(0, lastSlash) : '';
|
|
28
|
+
if (parentPath && pathMap.has(parentPath)) {
|
|
29
|
+
pathMap.get(parentPath).children.push(node);
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
root.push(node);
|
|
33
|
+
}
|
|
34
|
+
if (doc.isDir) {
|
|
35
|
+
pathMap.set(doc.path, node);
|
|
55
36
|
}
|
|
56
37
|
}
|
|
57
|
-
|
|
58
|
-
return { x: Math.round(maxX + SMART_NODE_GAP), y: Math.round(cy) };
|
|
38
|
+
return root;
|
|
59
39
|
}
|
|
60
40
|
// API 客户端类
|
|
61
41
|
export class PpdocsApiClient {
|
|
@@ -98,91 +78,51 @@ export class PpdocsApiClient {
|
|
|
98
78
|
clearTimeout(timeout);
|
|
99
79
|
}
|
|
100
80
|
}
|
|
101
|
-
// ============
|
|
102
|
-
async
|
|
103
|
-
|
|
104
|
-
if (!filter)
|
|
105
|
-
return nodes;
|
|
106
|
-
// 计算每个节点的连接数 (入边+出边)
|
|
107
|
-
const edgeCounts = new Map();
|
|
108
|
-
nodes.forEach(n => edgeCounts.set(n.id, 0));
|
|
109
|
-
nodes.forEach(n => {
|
|
110
|
-
(n.dependencies || []).forEach(dep => {
|
|
111
|
-
// 出边: 当前节点依赖别人
|
|
112
|
-
edgeCounts.set(n.id, (edgeCounts.get(n.id) || 0) + 1);
|
|
113
|
-
// 入边: 被依赖的节点 (按 signature 匹配)
|
|
114
|
-
const target = nodes.find(t => t.signature?.toLowerCase() === dep.name.toLowerCase());
|
|
115
|
-
if (target)
|
|
116
|
-
edgeCounts.set(target.id, (edgeCounts.get(target.id) || 0) + 1);
|
|
117
|
-
});
|
|
118
|
-
});
|
|
119
|
-
// 过滤
|
|
120
|
-
return nodes.filter(n => {
|
|
121
|
-
if (filter.status && n.status !== filter.status)
|
|
122
|
-
return false;
|
|
123
|
-
const edges = edgeCounts.get(n.id) || 0;
|
|
124
|
-
if (filter.minEdges !== undefined && edges < filter.minEdges)
|
|
125
|
-
return false;
|
|
126
|
-
if (filter.maxEdges !== undefined && edges > filter.maxEdges)
|
|
127
|
-
return false;
|
|
128
|
-
return true;
|
|
129
|
-
});
|
|
81
|
+
// ============ 文档操作 ============
|
|
82
|
+
async listDocs() {
|
|
83
|
+
return this.request('/docs');
|
|
130
84
|
}
|
|
131
|
-
async
|
|
85
|
+
async getDoc(docPath) {
|
|
132
86
|
try {
|
|
133
|
-
|
|
87
|
+
// 移除开头的斜杠,API 路径会自动处理
|
|
88
|
+
const cleanPath = docPath.replace(/^\//, '');
|
|
89
|
+
return await this.request(`/docs/${cleanPath}`);
|
|
134
90
|
}
|
|
135
91
|
catch {
|
|
136
92
|
return null;
|
|
137
93
|
}
|
|
138
94
|
}
|
|
139
|
-
async
|
|
140
|
-
|
|
141
|
-
let x = node.x ?? 0;
|
|
142
|
-
let y = node.y ?? 0;
|
|
143
|
-
// 如果未指定位置,自动计算
|
|
144
|
-
if (node.x === undefined && node.y === undefined) {
|
|
145
|
-
const existingNodes = await this.listNodes();
|
|
146
|
-
const pos = computeSmartPosition(node.dependencies, existingNodes);
|
|
147
|
-
x = pos.x;
|
|
148
|
-
y = pos.y;
|
|
149
|
-
}
|
|
95
|
+
async createDoc(docPath, doc) {
|
|
96
|
+
const cleanPath = docPath.replace(/^\//, '');
|
|
150
97
|
const payload = {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
categories: node.categories || [],
|
|
160
|
-
description: node.description || '',
|
|
161
|
-
dependencies: node.dependencies || [],
|
|
162
|
-
relatedFiles: node.relatedFiles || [],
|
|
163
|
-
createdAt: new Date().toISOString(),
|
|
164
|
-
updatedAt: new Date().toISOString(),
|
|
165
|
-
lastAccessedAt: new Date().toISOString(),
|
|
166
|
-
versions: [],
|
|
167
|
-
bugfixes: []
|
|
98
|
+
summary: doc.summary || '',
|
|
99
|
+
content: doc.content || '',
|
|
100
|
+
versions: doc.versions || [{
|
|
101
|
+
version: 0.1,
|
|
102
|
+
date: new Date().toISOString(),
|
|
103
|
+
changes: '初始创建'
|
|
104
|
+
}],
|
|
105
|
+
bugfixes: doc.bugfixes || []
|
|
168
106
|
};
|
|
169
|
-
return this.request(
|
|
107
|
+
return this.request(`/docs/${cleanPath}`, {
|
|
170
108
|
method: 'POST',
|
|
171
109
|
body: JSON.stringify(payload)
|
|
172
110
|
});
|
|
173
111
|
}
|
|
174
|
-
async
|
|
175
|
-
// 先获取现有节点,合并更新
|
|
176
|
-
const existing = await this.getNode(nodeId);
|
|
177
|
-
if (!existing)
|
|
178
|
-
return null;
|
|
179
|
-
const payload = {
|
|
180
|
-
...existing,
|
|
181
|
-
...updates,
|
|
182
|
-
updatedAt: new Date().toISOString()
|
|
183
|
-
};
|
|
112
|
+
async updateDoc(docPath, updates) {
|
|
184
113
|
try {
|
|
185
|
-
|
|
114
|
+
const cleanPath = docPath.replace(/^\//, '');
|
|
115
|
+
// 先获取现有文档
|
|
116
|
+
const existing = await this.getDoc(docPath);
|
|
117
|
+
if (!existing)
|
|
118
|
+
return null;
|
|
119
|
+
const payload = {
|
|
120
|
+
summary: updates.summary ?? existing.summary,
|
|
121
|
+
content: updates.content ?? existing.content,
|
|
122
|
+
versions: updates.versions ?? existing.versions,
|
|
123
|
+
bugfixes: updates.bugfixes ?? existing.bugfixes
|
|
124
|
+
};
|
|
125
|
+
return await this.request(`/docs/${cleanPath}`, {
|
|
186
126
|
method: 'PUT',
|
|
187
127
|
body: JSON.stringify(payload)
|
|
188
128
|
});
|
|
@@ -191,47 +131,28 @@ export class PpdocsApiClient {
|
|
|
191
131
|
return null;
|
|
192
132
|
}
|
|
193
133
|
}
|
|
194
|
-
async
|
|
195
|
-
// 专用根节点更新,支持所有规则字段
|
|
196
|
-
const root = await this.getNode('root');
|
|
197
|
-
if (!root)
|
|
198
|
-
return null;
|
|
199
|
-
if (root.locked)
|
|
200
|
-
return null; // 锁定时拒绝
|
|
201
|
-
// 构建更新载荷 (只传入有值的字段)
|
|
202
|
-
const payload = { updatedAt: new Date().toISOString() };
|
|
203
|
-
if (updates.title !== undefined)
|
|
204
|
-
payload.title = updates.title;
|
|
205
|
-
if (updates.description !== undefined)
|
|
206
|
-
payload.description = updates.description;
|
|
207
|
-
if (updates.userStyles !== undefined)
|
|
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;
|
|
215
|
-
try {
|
|
216
|
-
return await this.request('/nodes/root', {
|
|
217
|
-
method: 'PUT',
|
|
218
|
-
body: JSON.stringify({ ...root, ...payload })
|
|
219
|
-
});
|
|
220
|
-
}
|
|
221
|
-
catch {
|
|
222
|
-
return null;
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
async deleteNode(nodeId) {
|
|
134
|
+
async deleteDoc(docPath) {
|
|
226
135
|
try {
|
|
227
|
-
|
|
136
|
+
const cleanPath = docPath.replace(/^\//, '');
|
|
137
|
+
await this.request(`/docs/${cleanPath}`, { method: 'DELETE' });
|
|
228
138
|
return true;
|
|
229
139
|
}
|
|
230
140
|
catch {
|
|
231
141
|
return false;
|
|
232
142
|
}
|
|
233
143
|
}
|
|
234
|
-
|
|
144
|
+
async searchDocs(keywords, limit = 20) {
|
|
145
|
+
return this.request('/docs/search', {
|
|
146
|
+
method: 'POST',
|
|
147
|
+
body: JSON.stringify({ keywords, limit })
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
/** 获取目录树结构 */
|
|
151
|
+
async getTree() {
|
|
152
|
+
const docs = await this.listDocs();
|
|
153
|
+
return buildTree(docs);
|
|
154
|
+
}
|
|
155
|
+
// ============ 规则 API ============
|
|
235
156
|
async getRulesApi(ruleType) {
|
|
236
157
|
try {
|
|
237
158
|
return await this.request(`/rules/${ruleType}`);
|
|
@@ -303,7 +224,7 @@ export class PpdocsApiClient {
|
|
|
303
224
|
}
|
|
304
225
|
}
|
|
305
226
|
}
|
|
306
|
-
// ============ 模块级 API
|
|
227
|
+
// ============ 模块级 API ============
|
|
307
228
|
let client = null;
|
|
308
229
|
export function initClient(apiUrl) {
|
|
309
230
|
client = new PpdocsApiClient(apiUrl);
|
|
@@ -314,26 +235,29 @@ function getClient() {
|
|
|
314
235
|
}
|
|
315
236
|
return client;
|
|
316
237
|
}
|
|
317
|
-
//
|
|
318
|
-
export async function
|
|
319
|
-
return getClient().
|
|
238
|
+
// ============ 文档管理 ============
|
|
239
|
+
export async function listDocs(_projectId) {
|
|
240
|
+
return getClient().listDocs();
|
|
241
|
+
}
|
|
242
|
+
export async function getDoc(_projectId, docPath) {
|
|
243
|
+
return getClient().getDoc(docPath);
|
|
320
244
|
}
|
|
321
|
-
export async function
|
|
322
|
-
return getClient().
|
|
245
|
+
export async function createDoc(_projectId, docPath, doc) {
|
|
246
|
+
return getClient().createDoc(docPath, doc);
|
|
323
247
|
}
|
|
324
|
-
export async function
|
|
325
|
-
return getClient().
|
|
248
|
+
export async function updateDoc(_projectId, docPath, updates) {
|
|
249
|
+
return getClient().updateDoc(docPath, updates);
|
|
326
250
|
}
|
|
327
|
-
export async function
|
|
328
|
-
return getClient().
|
|
251
|
+
export async function deleteDoc(_projectId, docPath) {
|
|
252
|
+
return getClient().deleteDoc(docPath);
|
|
329
253
|
}
|
|
330
|
-
export async function
|
|
331
|
-
return getClient().
|
|
254
|
+
export async function searchDocs(_projectId, keywords, limit) {
|
|
255
|
+
return getClient().searchDocs(keywords, limit);
|
|
332
256
|
}
|
|
333
|
-
export async function
|
|
334
|
-
return getClient().
|
|
257
|
+
export async function getTree(_projectId) {
|
|
258
|
+
return getClient().getTree();
|
|
335
259
|
}
|
|
336
|
-
// ============ 规则管理
|
|
260
|
+
// ============ 规则管理 ============
|
|
337
261
|
export async function getRules(_projectId, ruleType) {
|
|
338
262
|
return getClient().getRulesApi(ruleType);
|
|
339
263
|
}
|
package/dist/storage/types.d.ts
CHANGED
|
@@ -1,80 +1,48 @@
|
|
|
1
|
-
|
|
2
|
-
export interface Edge {
|
|
3
|
-
source: string;
|
|
4
|
-
target: string;
|
|
5
|
-
type: EdgeType;
|
|
6
|
-
auto?: boolean;
|
|
7
|
-
}
|
|
8
|
-
export type NodeType = 'logic' | 'data' | 'intro';
|
|
9
|
-
export type NodeStatus = 'incomplete' | 'complete' | 'fixing' | 'refactoring' | 'deprecated';
|
|
10
|
-
export interface DataRef {
|
|
11
|
-
type: string;
|
|
12
|
-
description: string;
|
|
13
|
-
formatPath?: string;
|
|
14
|
-
}
|
|
15
|
-
export interface Dependency {
|
|
16
|
-
name: string;
|
|
17
|
-
description: string;
|
|
18
|
-
nodePath?: string;
|
|
19
|
-
}
|
|
1
|
+
/** 版本记录 */
|
|
20
2
|
export interface VersionRecord {
|
|
21
3
|
version: number;
|
|
22
4
|
date: string;
|
|
23
5
|
changes: string;
|
|
24
6
|
}
|
|
7
|
+
/** 错误修复记录 */
|
|
25
8
|
export interface BugfixRecord {
|
|
26
|
-
id: string;
|
|
27
9
|
date: string;
|
|
28
10
|
issue: string;
|
|
29
11
|
solution: string;
|
|
30
|
-
impact?: string;
|
|
31
12
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
signature: string;
|
|
42
|
-
categories: string[];
|
|
13
|
+
/** 文档数据 (4个核心字段) */
|
|
14
|
+
export interface DocData {
|
|
15
|
+
summary: string;
|
|
16
|
+
content: string;
|
|
17
|
+
versions: VersionRecord[];
|
|
18
|
+
bugfixes: BugfixRecord[];
|
|
19
|
+
}
|
|
20
|
+
/** 文档节点 (含路径信息,用于列表展示) */
|
|
21
|
+
export interface DocNode {
|
|
43
22
|
path: string;
|
|
44
|
-
|
|
45
|
-
summary
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
unitTests?: string[];
|
|
55
|
-
createdAt?: string;
|
|
56
|
-
updatedAt?: string;
|
|
57
|
-
lastAccessedAt?: string;
|
|
58
|
-
lastSyncAt?: string;
|
|
59
|
-
versions?: VersionRecord[];
|
|
60
|
-
bugfixes?: BugfixRecord[];
|
|
23
|
+
name: string;
|
|
24
|
+
summary: string;
|
|
25
|
+
isDir: boolean;
|
|
26
|
+
}
|
|
27
|
+
/** 文档搜索结果 */
|
|
28
|
+
export interface DocSearchResult {
|
|
29
|
+
path: string;
|
|
30
|
+
name: string;
|
|
31
|
+
summary: string;
|
|
32
|
+
score: number;
|
|
61
33
|
}
|
|
62
34
|
export interface ProjectMeta {
|
|
63
35
|
projectId: string;
|
|
64
36
|
projectName: string;
|
|
65
37
|
updatedAt: string;
|
|
66
|
-
|
|
38
|
+
password?: string;
|
|
67
39
|
}
|
|
68
40
|
export interface Project {
|
|
69
41
|
id: string;
|
|
70
42
|
name: string;
|
|
43
|
+
description?: string;
|
|
71
44
|
updatedAt: string;
|
|
72
45
|
}
|
|
73
|
-
export interface SearchResult {
|
|
74
|
-
node: NodeData;
|
|
75
|
-
score: number;
|
|
76
|
-
matches: string[];
|
|
77
|
-
}
|
|
78
46
|
export type TaskLogType = 'progress' | 'issue' | 'solution' | 'reference';
|
|
79
47
|
export interface TaskLog {
|
|
80
48
|
time: string;
|
package/dist/storage/types.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
//
|
|
1
|
+
// ppdocs - 文档类型定义 (简化版)
|
|
2
2
|
export {};
|
package/dist/tools/helpers.d.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* MCP 工具辅助函数
|
|
3
|
-
* 提取自 index.ts,避免重复代码
|
|
4
3
|
*/
|
|
5
|
-
import type {
|
|
4
|
+
import type { DocData } from '../storage/types.js';
|
|
6
5
|
/**
|
|
7
6
|
* 包装返回结果为 MCP 格式
|
|
8
7
|
*/
|
|
@@ -13,26 +12,22 @@ export declare function wrap(text: string): {
|
|
|
13
12
|
}[];
|
|
14
13
|
};
|
|
15
14
|
/**
|
|
16
|
-
*
|
|
15
|
+
* 格式化文档为 Markdown 输出
|
|
17
16
|
*/
|
|
18
|
-
export declare function
|
|
17
|
+
export declare function formatDocMarkdown(doc: DocData, path: string): string[];
|
|
19
18
|
interface TreeNode {
|
|
19
|
+
path: string;
|
|
20
20
|
name: string;
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
status?: string;
|
|
21
|
+
summary?: string;
|
|
22
|
+
isDir: boolean;
|
|
24
23
|
children?: TreeNode[];
|
|
25
24
|
}
|
|
26
|
-
/**
|
|
27
|
-
* 构建目录树结构
|
|
28
|
-
*/
|
|
29
|
-
export declare function buildDirectoryTree(nodes: NodeData[]): TreeNode;
|
|
30
25
|
/**
|
|
31
26
|
* 格式化树为文本
|
|
32
27
|
*/
|
|
33
|
-
export declare function formatTreeText(tree: TreeNode): string;
|
|
28
|
+
export declare function formatTreeText(tree: TreeNode[]): string;
|
|
34
29
|
/**
|
|
35
|
-
*
|
|
30
|
+
* 统计树中的文档数
|
|
36
31
|
*/
|
|
37
|
-
export declare function
|
|
32
|
+
export declare function countTreeDocs(tree: TreeNode[]): number;
|
|
38
33
|
export {};
|
package/dist/tools/helpers.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* MCP 工具辅助函数
|
|
3
|
-
* 提取自 index.ts,避免重复代码
|
|
4
3
|
*/
|
|
5
4
|
// ==================== 通用包装 ====================
|
|
6
5
|
/**
|
|
@@ -9,149 +8,74 @@
|
|
|
9
8
|
export function wrap(text) {
|
|
10
9
|
return { content: [{ type: 'text', text }] };
|
|
11
10
|
}
|
|
12
|
-
// ====================
|
|
11
|
+
// ==================== 文档格式化 ====================
|
|
13
12
|
/**
|
|
14
|
-
*
|
|
13
|
+
* 格式化文档为 Markdown 输出
|
|
15
14
|
*/
|
|
16
|
-
export function
|
|
15
|
+
export function formatDocMarkdown(doc, path) {
|
|
17
16
|
const lines = [];
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
17
|
+
const name = path.split('/').pop() || path;
|
|
18
|
+
// 标题和简介
|
|
19
|
+
lines.push(`## ${name}\n`);
|
|
20
|
+
if (doc.summary) {
|
|
21
|
+
lines.push(`> ${doc.summary}\n`);
|
|
22
22
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
lines.push(`| 类型 | ${node.type} |`);
|
|
28
|
-
lines.push(`| 状态 | ${node.status} |`);
|
|
29
|
-
lines.push(`| 签名 | ${node.signature} |`);
|
|
30
|
-
if (node.categories && node.categories.length > 0) {
|
|
31
|
-
lines.push(`| 标签 | ${node.categories.join(', ')} |`);
|
|
32
|
-
}
|
|
33
|
-
lines.push('');
|
|
34
|
-
// 关联文件
|
|
35
|
-
if (node.relatedFiles && node.relatedFiles.length > 0) {
|
|
36
|
-
lines.push('**关联文件**');
|
|
37
|
-
node.relatedFiles.forEach((f) => lines.push(`- ${f}`));
|
|
38
|
-
lines.push('');
|
|
39
|
-
}
|
|
40
|
-
// 依赖关系
|
|
41
|
-
if (node.dependencies && node.dependencies.length > 0) {
|
|
42
|
-
lines.push('**依赖关系**');
|
|
43
|
-
node.dependencies.forEach((d) => lines.push(`- ${d.name}: ${d.description}`));
|
|
44
|
-
lines.push('');
|
|
45
|
-
}
|
|
46
|
-
// 描述内容
|
|
47
|
-
if (node.description) {
|
|
48
|
-
lines.push('**描述内容**');
|
|
49
|
-
lines.push(node.description);
|
|
23
|
+
// 内容
|
|
24
|
+
if (doc.content) {
|
|
25
|
+
lines.push('**内容**');
|
|
26
|
+
lines.push(doc.content);
|
|
50
27
|
lines.push('');
|
|
51
28
|
}
|
|
52
29
|
// 更新历史
|
|
53
|
-
if (
|
|
30
|
+
if (doc.versions && doc.versions.length > 0) {
|
|
54
31
|
lines.push('**更新历史**');
|
|
55
32
|
lines.push('| 版本 | 日期 | 变更 |');
|
|
56
33
|
lines.push('|:---|:---|:---|');
|
|
57
|
-
|
|
34
|
+
doc.versions.forEach((v) => lines.push(`| ${v.version} | ${v.date} | ${v.changes} |`));
|
|
58
35
|
lines.push('');
|
|
59
36
|
}
|
|
60
37
|
// 修复历史
|
|
61
|
-
if (
|
|
38
|
+
if (doc.bugfixes && doc.bugfixes.length > 0) {
|
|
62
39
|
lines.push('**修复历史**');
|
|
63
|
-
lines.push('|
|
|
64
|
-
lines.push('
|
|
65
|
-
|
|
40
|
+
lines.push('| 日期 | 问题 | 方案 |');
|
|
41
|
+
lines.push('|:---|:---|:---|');
|
|
42
|
+
doc.bugfixes.forEach((b) => lines.push(`| ${b.date} | ${b.issue} | ${b.solution} |`));
|
|
66
43
|
lines.push('');
|
|
67
44
|
}
|
|
68
45
|
return lines;
|
|
69
46
|
}
|
|
70
|
-
/**
|
|
71
|
-
* 构建目录树结构
|
|
72
|
-
*/
|
|
73
|
-
export function buildDirectoryTree(nodes) {
|
|
74
|
-
const root = { name: '/', type: 'folder', children: [] };
|
|
75
|
-
// 过滤掉根节点
|
|
76
|
-
const docNodes = nodes.filter(n => !n.isOrigin && n.id !== 'root');
|
|
77
|
-
for (const node of docNodes) {
|
|
78
|
-
const pathSegments = (node.path || '').split('/').filter(Boolean);
|
|
79
|
-
let current = root;
|
|
80
|
-
// 遍历路径段,创建目录结构
|
|
81
|
-
for (const segment of pathSegments) {
|
|
82
|
-
if (!current.children)
|
|
83
|
-
current.children = [];
|
|
84
|
-
let child = current.children.find(c => c.name === segment && c.type === 'folder');
|
|
85
|
-
if (!child) {
|
|
86
|
-
child = { name: segment, type: 'folder', children: [] };
|
|
87
|
-
current.children.push(child);
|
|
88
|
-
}
|
|
89
|
-
current = child;
|
|
90
|
-
}
|
|
91
|
-
// 添加节点
|
|
92
|
-
if (!current.children)
|
|
93
|
-
current.children = [];
|
|
94
|
-
current.children.push({
|
|
95
|
-
name: node.title,
|
|
96
|
-
type: 'node',
|
|
97
|
-
nodeId: node.id,
|
|
98
|
-
status: node.status
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
// 递归排序 (目录在前,节点在后)
|
|
102
|
-
sortTree(root);
|
|
103
|
-
return root;
|
|
104
|
-
}
|
|
105
|
-
function sortTree(node) {
|
|
106
|
-
if (!node.children)
|
|
107
|
-
return;
|
|
108
|
-
node.children.sort((a, b) => {
|
|
109
|
-
if (a.type === 'folder' && b.type !== 'folder')
|
|
110
|
-
return -1;
|
|
111
|
-
if (a.type !== 'folder' && b.type === 'folder')
|
|
112
|
-
return 1;
|
|
113
|
-
return a.name.localeCompare(b.name, 'zh-CN');
|
|
114
|
-
});
|
|
115
|
-
for (const child of node.children) {
|
|
116
|
-
if (child.type === 'folder')
|
|
117
|
-
sortTree(child);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
47
|
/**
|
|
121
48
|
* 格式化树为文本
|
|
122
49
|
*/
|
|
123
50
|
export function formatTreeText(tree) {
|
|
124
|
-
function format(
|
|
51
|
+
function format(nodes, prefix = '') {
|
|
125
52
|
const lines = [];
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
const
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
const child = node.children[i];
|
|
136
|
-
const childIsLast = i === node.children.length - 1;
|
|
137
|
-
const newPrefix = node.name === '/' ? '' : prefix + childPrefix;
|
|
138
|
-
lines.push(...format(child, newPrefix, childIsLast));
|
|
53
|
+
nodes.forEach((node, index) => {
|
|
54
|
+
const isLast = index === nodes.length - 1;
|
|
55
|
+
const connector = isLast ? '└── ' : '├── ';
|
|
56
|
+
const childPrefix = isLast ? ' ' : '│ ';
|
|
57
|
+
const icon = node.isDir ? '📁' : '📄';
|
|
58
|
+
const summary = node.summary ? ` - ${node.summary}` : '';
|
|
59
|
+
lines.push(`${prefix}${connector}${icon} ${node.name}${summary}`);
|
|
60
|
+
if (node.children && node.children.length > 0) {
|
|
61
|
+
lines.push(...format(node.children, prefix + childPrefix));
|
|
139
62
|
}
|
|
140
|
-
}
|
|
63
|
+
});
|
|
141
64
|
return lines;
|
|
142
65
|
}
|
|
143
66
|
return format(tree).join('\n');
|
|
144
67
|
}
|
|
145
68
|
/**
|
|
146
|
-
*
|
|
69
|
+
* 统计树中的文档数
|
|
147
70
|
*/
|
|
148
|
-
export function
|
|
71
|
+
export function countTreeDocs(tree) {
|
|
149
72
|
let count = 0;
|
|
150
|
-
function traverse(
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
node.children
|
|
73
|
+
function traverse(nodes) {
|
|
74
|
+
for (const node of nodes) {
|
|
75
|
+
if (!node.isDir)
|
|
76
|
+
count++;
|
|
77
|
+
if (node.children)
|
|
78
|
+
traverse(node.children);
|
|
155
79
|
}
|
|
156
80
|
}
|
|
157
81
|
traverse(tree);
|
package/dist/tools/index.js
CHANGED
|
@@ -1,105 +1,75 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import * as storage from '../storage/httpClient.js';
|
|
3
3
|
import { decodeObjectStrings, getRules, RULE_TYPE_LABELS } from '../utils.js';
|
|
4
|
-
|
|
5
|
-
import { wrap, formatNodeMarkdown, buildDirectoryTree, formatTreeText, countTreeNodes } from './helpers.js';
|
|
4
|
+
import { wrap, formatDocMarkdown, formatTreeText, countTreeDocs } from './helpers.js';
|
|
6
5
|
export function registerTools(server, projectId, _user) {
|
|
7
|
-
//
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
16
|
+
content: decoded.content,
|
|
17
|
+
versions: [{
|
|
18
|
+
version: 0.1,
|
|
19
|
+
date: new Date().toISOString(),
|
|
20
|
+
changes: '初始创建'
|
|
21
|
+
}],
|
|
22
|
+
bugfixes: []
|
|
36
23
|
});
|
|
37
|
-
|
|
38
|
-
return wrap(JSON.stringify(node, null, 2));
|
|
24
|
+
return wrap(`✅ 文档已创建: ${decoded.path}\n\n${JSON.stringify(doc, null, 2)}`);
|
|
39
25
|
});
|
|
40
|
-
// 2.
|
|
41
|
-
server.tool('kg_delete_node', '
|
|
42
|
-
const success = await storage.
|
|
43
|
-
|
|
44
|
-
return wrap(success ? '删除成功' : '删除失败(节点不存在/已锁定/是根节点)');
|
|
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 ? '删除成功' : '删除失败(文档不存在或是根文档)');
|
|
45
30
|
});
|
|
46
|
-
// 3.
|
|
47
|
-
server.tool('kg_update_node', '
|
|
48
|
-
nodeId: z.string().describe('
|
|
49
|
-
|
|
50
|
-
|
|
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('关联的源文件路径数组'),
|
|
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('版本记录
|
|
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
|
-
|
|
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,
|
|
79
|
-
//
|
|
80
|
-
if (nodeId === '
|
|
81
|
-
return wrap('❌
|
|
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('更新失败(文档不存在)');
|
|
82
57
|
}
|
|
83
|
-
//
|
|
84
|
-
|
|
85
|
-
if (
|
|
86
|
-
updates.
|
|
87
|
-
if (
|
|
88
|
-
updates.
|
|
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
|
-
|
|
94
|
-
|
|
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
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
114
|
-
|
|
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,26 +114,28 @@ export function registerTools(server, projectId, _user) {
|
|
|
138
114
|
}
|
|
139
115
|
return wrap(`✅ ${RULE_TYPE_LABELS[decoded.ruleType]}已保存 (${decoded.rules.length} 条)`);
|
|
140
116
|
});
|
|
141
|
-
// 5.
|
|
142
|
-
server.tool('kg_read_node', '
|
|
143
|
-
nodeId: z.string().describe('
|
|
117
|
+
// 5. 读取单个文档详情
|
|
118
|
+
server.tool('kg_read_node', '读取文档完整内容(简介、正文、版本历史、修复记录)', {
|
|
119
|
+
nodeId: z.string().describe('文档路径')
|
|
144
120
|
}, async (args) => {
|
|
145
|
-
const
|
|
146
|
-
if (!
|
|
147
|
-
return wrap('
|
|
121
|
+
const doc = await storage.getDoc(projectId, args.nodeId);
|
|
122
|
+
if (!doc) {
|
|
123
|
+
return wrap('文档不存在');
|
|
148
124
|
}
|
|
149
|
-
//
|
|
150
|
-
const lines =
|
|
125
|
+
// 格式化文档
|
|
126
|
+
const lines = formatDocMarkdown(doc, args.nodeId);
|
|
151
127
|
return wrap(lines.join('\n'));
|
|
152
128
|
});
|
|
153
129
|
// 10. 获取知识库树状图
|
|
154
130
|
server.tool('kg_get_tree', '获取知识库的目录树结构(按 path 分组)', {}, async () => {
|
|
155
131
|
try {
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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}`);
|
|
137
|
+
}
|
|
138
|
+
return wrap('知识库为空');
|
|
161
139
|
}
|
|
162
140
|
catch (e) {
|
|
163
141
|
return wrap(JSON.stringify({
|
|
@@ -182,7 +160,7 @@ export function registerTools(server, projectId, _user) {
|
|
|
182
160
|
}, _user);
|
|
183
161
|
return wrap(JSON.stringify(task, null, 2));
|
|
184
162
|
});
|
|
185
|
-
// 8. 读取任务
|
|
163
|
+
// 8. 读取任务
|
|
186
164
|
server.tool('task_get', '读取任务详情(按名字搜索,支持当前任务和历史任务)', {
|
|
187
165
|
title: z.string().describe('任务名称(模糊匹配)'),
|
|
188
166
|
status: z.enum(['active', 'archived', 'all']).optional().describe('状态筛选: active=进行中, archived=已归档, all=全部(默认)')
|
|
@@ -221,7 +199,7 @@ export function registerTools(server, projectId, _user) {
|
|
|
221
199
|
}));
|
|
222
200
|
return wrap(`找到 ${matched.length} 个匹配任务:\n${JSON.stringify(list, null, 2)}\n\n请提供更精确的任务名称`);
|
|
223
201
|
});
|
|
224
|
-
// 9. 更新任务
|
|
202
|
+
// 9. 更新任务
|
|
225
203
|
server.tool('task_update', '更新任务(添加进展日志)', {
|
|
226
204
|
taskId: z.string().describe('任务ID'),
|
|
227
205
|
log_type: z.enum(['progress', 'issue', 'solution', 'reference']).describe('日志类型: progress=进展, issue=问题, solution=方案, reference=参考'),
|