@ppdocs/mcp 2.7.2 → 2.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +143 -98
- 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,142 @@
|
|
|
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
|
|
43
|
-
|
|
44
|
-
|
|
26
|
+
// 2. 删除文档 (支持批量)
|
|
27
|
+
server.tool('kg_delete_node', '删除文档(支持批量,根文档不可删除)', { nodeId: z.union([z.string(), z.array(z.string())]).describe('文档路径或路径数组') }, async (args) => {
|
|
28
|
+
const paths = Array.isArray(args.nodeId) ? args.nodeId : [args.nodeId];
|
|
29
|
+
const results = [];
|
|
30
|
+
for (const path of paths) {
|
|
31
|
+
const success = await storage.deleteDoc(projectId, path);
|
|
32
|
+
results.push({ path, success });
|
|
33
|
+
}
|
|
34
|
+
const successCount = results.filter(r => r.success).length;
|
|
35
|
+
const failedPaths = results.filter(r => !r.success).map(r => r.path);
|
|
36
|
+
if (paths.length === 1) {
|
|
37
|
+
return wrap(results[0].success ? '删除成功' : '删除失败(文档不存在或是根文档)');
|
|
38
|
+
}
|
|
39
|
+
let msg = `✅ 批量删除完成: ${successCount}/${paths.length} 成功`;
|
|
40
|
+
if (failedPaths.length > 0) {
|
|
41
|
+
msg += `\n❌ 失败: ${failedPaths.join(', ')}`;
|
|
42
|
+
}
|
|
43
|
+
return wrap(msg);
|
|
45
44
|
});
|
|
46
|
-
// 3.
|
|
47
|
-
server.tool('kg_update_node', '
|
|
48
|
-
nodeId: z.
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
summary: z.string().optional().describe('一句话简介
|
|
53
|
-
|
|
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('关联的源文件路径数组'),
|
|
45
|
+
// 3. 更新文档 (支持批量)
|
|
46
|
+
server.tool('kg_update_node', '更新文档内容(支持批量)', {
|
|
47
|
+
nodeId: z.union([
|
|
48
|
+
z.string(),
|
|
49
|
+
z.array(z.string())
|
|
50
|
+
]).describe('文档路径或路径数组'),
|
|
51
|
+
summary: z.string().optional().describe('一句话简介'),
|
|
52
|
+
content: z.string().optional().describe('Markdown内容'),
|
|
63
53
|
versions: z.array(z.object({
|
|
64
54
|
version: z.number(),
|
|
65
55
|
date: z.string(),
|
|
66
56
|
changes: z.string()
|
|
67
|
-
})).optional().describe('版本记录
|
|
57
|
+
})).optional().describe('版本记录'),
|
|
68
58
|
bugfixes: z.array(z.object({
|
|
69
|
-
id: z.string(),
|
|
70
59
|
date: z.string(),
|
|
71
60
|
issue: z.string(),
|
|
72
|
-
solution: z.string()
|
|
73
|
-
impact: z.string().optional()
|
|
61
|
+
solution: z.string()
|
|
74
62
|
})).optional().describe('修复记录'),
|
|
75
|
-
|
|
63
|
+
updates: z.array(z.object({
|
|
64
|
+
nodeId: z.string().describe('文档路径'),
|
|
65
|
+
summary: z.string().optional(),
|
|
66
|
+
content: z.string().optional(),
|
|
67
|
+
versions: z.array(z.object({
|
|
68
|
+
version: z.number(),
|
|
69
|
+
date: z.string(),
|
|
70
|
+
changes: z.string()
|
|
71
|
+
})).optional(),
|
|
72
|
+
bugfixes: z.array(z.object({
|
|
73
|
+
date: z.string(),
|
|
74
|
+
issue: z.string(),
|
|
75
|
+
solution: z.string()
|
|
76
|
+
})).optional()
|
|
77
|
+
})).optional().describe('批量更新数组(每项独立配置)')
|
|
76
78
|
}, async (args) => {
|
|
77
79
|
const decoded = decodeObjectStrings(args);
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
80
|
+
// 批量更新模式: 使用 updates 数组
|
|
81
|
+
if (decoded.updates && Array.isArray(decoded.updates)) {
|
|
82
|
+
const results = [];
|
|
83
|
+
for (const item of decoded.updates) {
|
|
84
|
+
if (item.nodeId === '/' || item.nodeId === '_root') {
|
|
85
|
+
results.push({ path: item.nodeId, success: false });
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
const existing = await storage.getDoc(projectId, item.nodeId);
|
|
89
|
+
if (!existing) {
|
|
90
|
+
results.push({ path: item.nodeId, success: false });
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
const updates = {};
|
|
94
|
+
if (item.summary !== undefined)
|
|
95
|
+
updates.summary = item.summary;
|
|
96
|
+
if (item.content !== undefined)
|
|
97
|
+
updates.content = item.content;
|
|
98
|
+
if (item.versions !== undefined)
|
|
99
|
+
updates.versions = item.versions;
|
|
100
|
+
if (item.bugfixes !== undefined)
|
|
101
|
+
updates.bugfixes = item.bugfixes;
|
|
102
|
+
const doc = await storage.updateDoc(projectId, item.nodeId, updates);
|
|
103
|
+
results.push({ path: item.nodeId, success: !!doc });
|
|
104
|
+
}
|
|
105
|
+
const successCount = results.filter(r => r.success).length;
|
|
106
|
+
const failedPaths = results.filter(r => !r.success).map(r => r.path);
|
|
107
|
+
let msg = `✅ 批量更新完成: ${successCount}/${decoded.updates.length} 成功`;
|
|
108
|
+
if (failedPaths.length > 0) {
|
|
109
|
+
msg += `\n❌ 失败: ${failedPaths.join(', ')}`;
|
|
110
|
+
}
|
|
111
|
+
return wrap(msg);
|
|
112
|
+
}
|
|
113
|
+
// 单个更新模式
|
|
114
|
+
const { nodeId, summary, content, versions, bugfixes } = decoded;
|
|
115
|
+
const singleNodeId = Array.isArray(nodeId) ? nodeId[0] : nodeId;
|
|
116
|
+
// 根文档必须使用 kg_update_root 更新
|
|
117
|
+
if (singleNodeId === '/' || singleNodeId === '_root') {
|
|
118
|
+
return wrap('❌ 根文档请使用 kg_update_root 方法更新');
|
|
82
119
|
}
|
|
83
|
-
//
|
|
84
|
-
|
|
85
|
-
if (
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
120
|
+
// 获取现有文档
|
|
121
|
+
const existing = await storage.getDoc(projectId, singleNodeId);
|
|
122
|
+
if (!existing) {
|
|
123
|
+
return wrap('更新失败(文档不存在)');
|
|
124
|
+
}
|
|
125
|
+
// 构建更新内容
|
|
126
|
+
const updates = {};
|
|
127
|
+
if (summary !== undefined)
|
|
128
|
+
updates.summary = summary;
|
|
129
|
+
if (content !== undefined)
|
|
130
|
+
updates.content = content;
|
|
89
131
|
if (versions !== undefined)
|
|
90
132
|
updates.versions = versions;
|
|
91
133
|
if (bugfixes !== undefined)
|
|
92
134
|
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) : '更新失败(节点不存在或已锁定)');
|
|
135
|
+
const doc = await storage.updateDoc(projectId, singleNodeId, updates);
|
|
136
|
+
return wrap(doc ? JSON.stringify(doc, null, 2) : '更新失败');
|
|
100
137
|
});
|
|
101
|
-
// 3.5
|
|
102
|
-
server.tool('kg_update_root', '更新项目介绍(
|
|
138
|
+
// 3.5 更新根文档 (项目介绍)
|
|
139
|
+
server.tool('kg_update_root', '更新项目介绍(根文档)', {
|
|
103
140
|
title: z.string().optional().describe('项目标题'),
|
|
104
141
|
description: z.string().optional().describe('项目介绍(Markdown)')
|
|
105
142
|
}, async (args) => {
|
|
@@ -107,13 +144,19 @@ export function registerTools(server, projectId, _user) {
|
|
|
107
144
|
if (decoded.title === undefined && decoded.description === undefined) {
|
|
108
145
|
return wrap('❌ 请至少提供 title 或 description');
|
|
109
146
|
}
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
114
|
-
|
|
147
|
+
const existing = await storage.getDoc(projectId, '/');
|
|
148
|
+
if (!existing) {
|
|
149
|
+
return wrap('更新失败(根文档不存在)');
|
|
150
|
+
}
|
|
151
|
+
const updates = {};
|
|
152
|
+
if (decoded.title !== undefined)
|
|
153
|
+
updates.summary = decoded.title;
|
|
154
|
+
if (decoded.description !== undefined)
|
|
155
|
+
updates.content = decoded.description;
|
|
156
|
+
const doc = await storage.updateDoc(projectId, '/', updates);
|
|
157
|
+
return wrap(doc ? '✅ 项目介绍已更新' : '更新失败');
|
|
115
158
|
});
|
|
116
|
-
// 3.6 获取项目规则
|
|
159
|
+
// 3.6 获取项目规则
|
|
117
160
|
server.tool('kg_get_rules', '获取项目规则(可指定类型或获取全部)', {
|
|
118
161
|
ruleType: z.enum(['userStyles', 'testRules', 'reviewRules', 'codeStyle', 'unitTests']).optional()
|
|
119
162
|
.describe('规则类型: userStyles=用户沟通规则, codeStyle=编码风格规则, reviewRules=代码审查规则, testRules=错误分析规则, unitTests=代码测试规则。不传则返回全部')
|
|
@@ -125,7 +168,7 @@ export function registerTools(server, projectId, _user) {
|
|
|
125
168
|
}
|
|
126
169
|
return { content: [{ type: 'text', text: rules }] };
|
|
127
170
|
});
|
|
128
|
-
// 3.7 保存项目规则
|
|
171
|
+
// 3.7 保存项目规则
|
|
129
172
|
server.tool('kg_save_rules', '保存单个类型的项目规则(独立文件存储)', {
|
|
130
173
|
ruleType: z.enum(['userStyles', 'testRules', 'reviewRules', 'codeStyle', 'unitTests'])
|
|
131
174
|
.describe('规则类型: userStyles=用户沟通规则, codeStyle=编码风格规则, reviewRules=代码审查规则, testRules=错误分析规则, unitTests=代码测试规则'),
|
|
@@ -138,26 +181,28 @@ export function registerTools(server, projectId, _user) {
|
|
|
138
181
|
}
|
|
139
182
|
return wrap(`✅ ${RULE_TYPE_LABELS[decoded.ruleType]}已保存 (${decoded.rules.length} 条)`);
|
|
140
183
|
});
|
|
141
|
-
// 5.
|
|
142
|
-
server.tool('kg_read_node', '
|
|
143
|
-
nodeId: z.string().describe('
|
|
184
|
+
// 5. 读取单个文档详情
|
|
185
|
+
server.tool('kg_read_node', '读取文档完整内容(简介、正文、版本历史、修复记录)', {
|
|
186
|
+
nodeId: z.string().describe('文档路径')
|
|
144
187
|
}, async (args) => {
|
|
145
|
-
const
|
|
146
|
-
if (!
|
|
147
|
-
return wrap('
|
|
188
|
+
const doc = await storage.getDoc(projectId, args.nodeId);
|
|
189
|
+
if (!doc) {
|
|
190
|
+
return wrap('文档不存在');
|
|
148
191
|
}
|
|
149
|
-
//
|
|
150
|
-
const lines =
|
|
192
|
+
// 格式化文档
|
|
193
|
+
const lines = formatDocMarkdown(doc, args.nodeId);
|
|
151
194
|
return wrap(lines.join('\n'));
|
|
152
195
|
});
|
|
153
196
|
// 10. 获取知识库树状图
|
|
154
197
|
server.tool('kg_get_tree', '获取知识库的目录树结构(按 path 分组)', {}, async () => {
|
|
155
198
|
try {
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
199
|
+
const tree = await storage.getTree(projectId);
|
|
200
|
+
if (tree && tree.length > 0) {
|
|
201
|
+
const treeText = formatTreeText(tree);
|
|
202
|
+
const docCount = countTreeDocs(tree);
|
|
203
|
+
return wrap(`共 ${docCount} 个文档\n\n${treeText}`);
|
|
204
|
+
}
|
|
205
|
+
return wrap('知识库为空');
|
|
161
206
|
}
|
|
162
207
|
catch (e) {
|
|
163
208
|
return wrap(JSON.stringify({
|
|
@@ -182,7 +227,7 @@ export function registerTools(server, projectId, _user) {
|
|
|
182
227
|
}, _user);
|
|
183
228
|
return wrap(JSON.stringify(task, null, 2));
|
|
184
229
|
});
|
|
185
|
-
// 8. 读取任务
|
|
230
|
+
// 8. 读取任务
|
|
186
231
|
server.tool('task_get', '读取任务详情(按名字搜索,支持当前任务和历史任务)', {
|
|
187
232
|
title: z.string().describe('任务名称(模糊匹配)'),
|
|
188
233
|
status: z.enum(['active', 'archived', 'all']).optional().describe('状态筛选: active=进行中, archived=已归档, all=全部(默认)')
|
|
@@ -221,7 +266,7 @@ export function registerTools(server, projectId, _user) {
|
|
|
221
266
|
}));
|
|
222
267
|
return wrap(`找到 ${matched.length} 个匹配任务:\n${JSON.stringify(list, null, 2)}\n\n请提供更精确的任务名称`);
|
|
223
268
|
});
|
|
224
|
-
// 9. 更新任务
|
|
269
|
+
// 9. 更新任务
|
|
225
270
|
server.tool('task_update', '更新任务(添加进展日志)', {
|
|
226
271
|
taskId: z.string().describe('任务ID'),
|
|
227
272
|
log_type: z.enum(['progress', 'issue', 'solution', 'reference']).describe('日志类型: progress=进展, issue=问题, solution=方案, reference=参考'),
|