@ppdocs/mcp 2.8.3 → 2.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +33 -2
- package/dist/storage/httpClient.d.ts +30 -0
- package/dist/storage/httpClient.js +70 -2
- package/dist/storage/types.d.ts +3 -1
- package/dist/tools/helpers.js +1 -1
- package/dist/tools/index.js +104 -8
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -14,19 +14,50 @@ function parseArgs(args) {
|
|
|
14
14
|
for (let i = 0; i < args.length; i++) {
|
|
15
15
|
const arg = args[i];
|
|
16
16
|
if (arg === '-p' || arg === '--project') {
|
|
17
|
+
if (i + 1 >= args.length || args[i + 1].startsWith('-')) {
|
|
18
|
+
console.error('Error: -p/--project requires a value');
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
17
21
|
opts.project = args[++i];
|
|
18
22
|
}
|
|
19
23
|
else if (arg === '-k' || arg === '--key') {
|
|
24
|
+
if (i + 1 >= args.length || args[i + 1].startsWith('-')) {
|
|
25
|
+
console.error('Error: -k/--key requires a value');
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
20
28
|
opts.key = args[++i];
|
|
21
29
|
}
|
|
22
30
|
else if (arg === '-u' || arg === '--user') {
|
|
31
|
+
if (i + 1 >= args.length || args[i + 1].startsWith('-')) {
|
|
32
|
+
console.error('Error: -u/--user requires a value');
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
23
35
|
opts.user = args[++i];
|
|
24
36
|
}
|
|
25
37
|
else if (arg === '--port') {
|
|
26
|
-
|
|
38
|
+
if (i + 1 >= args.length || args[i + 1].startsWith('-')) {
|
|
39
|
+
console.error('Error: --port requires a value');
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
const portVal = parseInt(args[++i], 10);
|
|
43
|
+
if (isNaN(portVal) || portVal < 1 || portVal > 65535) {
|
|
44
|
+
console.error('Error: --port must be a valid port number (1-65535)');
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
opts.port = portVal;
|
|
27
48
|
}
|
|
28
49
|
else if (arg === '--api') {
|
|
29
|
-
|
|
50
|
+
if (i + 1 >= args.length || args[i + 1].startsWith('-')) {
|
|
51
|
+
console.error('Error: --api requires a value');
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
const apiVal = args[++i].trim();
|
|
55
|
+
if (!apiVal) {
|
|
56
|
+
console.error('Error: --api value cannot be empty');
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
// 移除用户可能误加的协议前缀
|
|
60
|
+
opts.api = apiVal.replace(/^https?:\/\//, '');
|
|
30
61
|
}
|
|
31
62
|
else if (arg === '--codex') {
|
|
32
63
|
opts.codex = true;
|
|
@@ -22,6 +22,8 @@ export declare class PpdocsApiClient {
|
|
|
22
22
|
updateDoc(docPath: string, updates: Partial<DocData>): Promise<DocData | null>;
|
|
23
23
|
deleteDoc(docPath: string): Promise<boolean>;
|
|
24
24
|
searchDocs(keywords: string[], limit?: number): Promise<DocSearchResult[]>;
|
|
25
|
+
/** 按状态筛选文档 */
|
|
26
|
+
getDocsByStatus(statusList: string[]): Promise<DocNode[]>;
|
|
25
27
|
/** 获取目录树结构 */
|
|
26
28
|
getTree(): Promise<TreeNode[]>;
|
|
27
29
|
getRulesApi(ruleType: string): Promise<string[]>;
|
|
@@ -35,6 +37,23 @@ export declare class PpdocsApiClient {
|
|
|
35
37
|
}, creator: string): Promise<Task>;
|
|
36
38
|
addTaskLog(taskId: string, logType: TaskLogType, content: string): Promise<Task | null>;
|
|
37
39
|
completeTask(taskId: string, experience: TaskExperience): Promise<Task | null>;
|
|
40
|
+
/** 列出所有可访问的项目 */
|
|
41
|
+
crossListProjects(): Promise<{
|
|
42
|
+
id: string;
|
|
43
|
+
name: string;
|
|
44
|
+
description: string;
|
|
45
|
+
updated_at: string;
|
|
46
|
+
}[]>;
|
|
47
|
+
/** 跨项目: 列出文档 */
|
|
48
|
+
crossListDocs(target: string): Promise<DocNode[]>;
|
|
49
|
+
/** 跨项目: 获取文档 */
|
|
50
|
+
crossGetDoc(target: string, docPath: string): Promise<DocData | null>;
|
|
51
|
+
/** 跨项目: 获取目录树 */
|
|
52
|
+
crossGetTree(target: string): Promise<TreeNode[]>;
|
|
53
|
+
/** 跨项目: 按状态筛选文档 */
|
|
54
|
+
crossGetDocsByStatus(target: string, statusList: string[]): Promise<DocNode[]>;
|
|
55
|
+
/** 跨项目: 获取规则 */
|
|
56
|
+
crossGetRules(target: string, ruleType: string): Promise<string[]>;
|
|
38
57
|
}
|
|
39
58
|
export declare function initClient(apiUrl: string): void;
|
|
40
59
|
export declare function listDocs(_projectId: string): Promise<DocNode[]>;
|
|
@@ -43,6 +62,7 @@ export declare function createDoc(_projectId: string, docPath: string, doc: Part
|
|
|
43
62
|
export declare function updateDoc(_projectId: string, docPath: string, updates: Partial<DocData>): Promise<DocData | null>;
|
|
44
63
|
export declare function deleteDoc(_projectId: string, docPath: string): Promise<boolean>;
|
|
45
64
|
export declare function searchDocs(_projectId: string, keywords: string[], limit?: number): Promise<DocSearchResult[]>;
|
|
65
|
+
export declare function getDocsByStatus(_projectId: string, statusList: string[]): Promise<DocNode[]>;
|
|
46
66
|
export declare function getTree(_projectId: string): Promise<TreeNode[]>;
|
|
47
67
|
export declare function getRules(_projectId: string, ruleType: string): Promise<string[]>;
|
|
48
68
|
export declare function saveRules(_projectId: string, ruleType: string, rules: string[]): Promise<boolean>;
|
|
@@ -55,4 +75,14 @@ export declare function createTask(_projectId: string, task: {
|
|
|
55
75
|
}, creator: string): Promise<Task>;
|
|
56
76
|
export declare function addTaskLog(_projectId: string, taskId: string, logType: TaskLogType, content: string): Promise<Task | null>;
|
|
57
77
|
export declare function completeTask(_projectId: string, taskId: string, experience: TaskExperience): Promise<Task | null>;
|
|
78
|
+
export declare function crossListProjects(): Promise<{
|
|
79
|
+
id: string;
|
|
80
|
+
name: string;
|
|
81
|
+
description: string;
|
|
82
|
+
updated_at: string;
|
|
83
|
+
}[]>;
|
|
84
|
+
export declare function crossGetDoc(target: string, docPath: string): Promise<DocData | null>;
|
|
85
|
+
export declare function crossGetTree(target: string): Promise<TreeNode[]>;
|
|
86
|
+
export declare function crossGetDocsByStatus(target: string, statusList: string[]): Promise<DocNode[]>;
|
|
87
|
+
export declare function crossGetRules(target: string, ruleType: string): Promise<string[]>;
|
|
58
88
|
export type { TreeNode };
|
|
@@ -102,7 +102,8 @@ export class PpdocsApiClient {
|
|
|
102
102
|
date: new Date().toISOString(),
|
|
103
103
|
changes: '初始创建'
|
|
104
104
|
}],
|
|
105
|
-
bugfixes: doc.bugfixes || []
|
|
105
|
+
bugfixes: doc.bugfixes || [],
|
|
106
|
+
status: doc.status || '已完成' // 默认状态
|
|
106
107
|
};
|
|
107
108
|
return this.request(`/docs/${cleanPath}`, {
|
|
108
109
|
method: 'POST',
|
|
@@ -120,7 +121,8 @@ export class PpdocsApiClient {
|
|
|
120
121
|
summary: updates.summary ?? existing.summary,
|
|
121
122
|
content: updates.content ?? existing.content,
|
|
122
123
|
versions: updates.versions ?? existing.versions,
|
|
123
|
-
bugfixes: updates.bugfixes ?? existing.bugfixes
|
|
124
|
+
bugfixes: updates.bugfixes ?? existing.bugfixes,
|
|
125
|
+
status: updates.status ?? existing.status ?? '已完成'
|
|
124
126
|
};
|
|
125
127
|
return await this.request(`/docs/${cleanPath}`, {
|
|
126
128
|
method: 'PUT',
|
|
@@ -147,6 +149,13 @@ export class PpdocsApiClient {
|
|
|
147
149
|
body: JSON.stringify({ keywords, limit })
|
|
148
150
|
});
|
|
149
151
|
}
|
|
152
|
+
/** 按状态筛选文档 */
|
|
153
|
+
async getDocsByStatus(statusList) {
|
|
154
|
+
return this.request('/docs/by-status', {
|
|
155
|
+
method: 'POST',
|
|
156
|
+
body: JSON.stringify({ status_list: statusList })
|
|
157
|
+
});
|
|
158
|
+
}
|
|
150
159
|
/** 获取目录树结构 */
|
|
151
160
|
async getTree() {
|
|
152
161
|
const docs = await this.listDocs();
|
|
@@ -223,6 +232,46 @@ export class PpdocsApiClient {
|
|
|
223
232
|
return null;
|
|
224
233
|
}
|
|
225
234
|
}
|
|
235
|
+
// ============ 跨项目只读访问 ============
|
|
236
|
+
/** 列出所有可访问的项目 */
|
|
237
|
+
async crossListProjects() {
|
|
238
|
+
return this.request('/cross/projects');
|
|
239
|
+
}
|
|
240
|
+
/** 跨项目: 列出文档 */
|
|
241
|
+
async crossListDocs(target) {
|
|
242
|
+
return this.request(`/cross/${encodeURIComponent(target)}/docs`);
|
|
243
|
+
}
|
|
244
|
+
/** 跨项目: 获取文档 */
|
|
245
|
+
async crossGetDoc(target, docPath) {
|
|
246
|
+
try {
|
|
247
|
+
const cleanPath = docPath.replace(/^\//, '');
|
|
248
|
+
return await this.request(`/cross/${encodeURIComponent(target)}/docs/${cleanPath}`);
|
|
249
|
+
}
|
|
250
|
+
catch {
|
|
251
|
+
return null;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
/** 跨项目: 获取目录树 */
|
|
255
|
+
async crossGetTree(target) {
|
|
256
|
+
const docs = await this.crossListDocs(target);
|
|
257
|
+
return buildTree(docs);
|
|
258
|
+
}
|
|
259
|
+
/** 跨项目: 按状态筛选文档 */
|
|
260
|
+
async crossGetDocsByStatus(target, statusList) {
|
|
261
|
+
return this.request(`/cross/${encodeURIComponent(target)}/docs/by-status`, {
|
|
262
|
+
method: 'POST',
|
|
263
|
+
body: JSON.stringify({ status_list: statusList })
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
/** 跨项目: 获取规则 */
|
|
267
|
+
async crossGetRules(target, ruleType) {
|
|
268
|
+
try {
|
|
269
|
+
return await this.request(`/cross/${encodeURIComponent(target)}/rules/${ruleType}`);
|
|
270
|
+
}
|
|
271
|
+
catch {
|
|
272
|
+
return [];
|
|
273
|
+
}
|
|
274
|
+
}
|
|
226
275
|
}
|
|
227
276
|
// ============ 模块级 API ============
|
|
228
277
|
let client = null;
|
|
@@ -254,6 +303,9 @@ export async function deleteDoc(_projectId, docPath) {
|
|
|
254
303
|
export async function searchDocs(_projectId, keywords, limit) {
|
|
255
304
|
return getClient().searchDocs(keywords, limit);
|
|
256
305
|
}
|
|
306
|
+
export async function getDocsByStatus(_projectId, statusList) {
|
|
307
|
+
return getClient().getDocsByStatus(statusList);
|
|
308
|
+
}
|
|
257
309
|
export async function getTree(_projectId) {
|
|
258
310
|
return getClient().getTree();
|
|
259
311
|
}
|
|
@@ -280,3 +332,19 @@ export async function addTaskLog(_projectId, taskId, logType, content) {
|
|
|
280
332
|
export async function completeTask(_projectId, taskId, experience) {
|
|
281
333
|
return getClient().completeTask(taskId, experience);
|
|
282
334
|
}
|
|
335
|
+
// ============ 跨项目只读访问 ============
|
|
336
|
+
export async function crossListProjects() {
|
|
337
|
+
return getClient().crossListProjects();
|
|
338
|
+
}
|
|
339
|
+
export async function crossGetDoc(target, docPath) {
|
|
340
|
+
return getClient().crossGetDoc(target, docPath);
|
|
341
|
+
}
|
|
342
|
+
export async function crossGetTree(target) {
|
|
343
|
+
return getClient().crossGetTree(target);
|
|
344
|
+
}
|
|
345
|
+
export async function crossGetDocsByStatus(target, statusList) {
|
|
346
|
+
return getClient().crossGetDocsByStatus(target, statusList);
|
|
347
|
+
}
|
|
348
|
+
export async function crossGetRules(target, ruleType) {
|
|
349
|
+
return getClient().crossGetRules(target, ruleType);
|
|
350
|
+
}
|
package/dist/storage/types.d.ts
CHANGED
|
@@ -10,12 +10,13 @@ export interface BugfixRecord {
|
|
|
10
10
|
issue: string;
|
|
11
11
|
solution: string;
|
|
12
12
|
}
|
|
13
|
-
/** 文档数据 (
|
|
13
|
+
/** 文档数据 (5个核心字段) */
|
|
14
14
|
export interface DocData {
|
|
15
15
|
summary: string;
|
|
16
16
|
content: string;
|
|
17
17
|
versions: VersionRecord[];
|
|
18
18
|
bugfixes: BugfixRecord[];
|
|
19
|
+
status?: string;
|
|
19
20
|
}
|
|
20
21
|
/** 文档节点 (含路径信息,用于列表展示) */
|
|
21
22
|
export interface DocNode {
|
|
@@ -23,6 +24,7 @@ export interface DocNode {
|
|
|
23
24
|
name: string;
|
|
24
25
|
summary: string;
|
|
25
26
|
isDir: boolean;
|
|
27
|
+
status?: string;
|
|
26
28
|
}
|
|
27
29
|
/** 文档搜索结果 */
|
|
28
30
|
export interface DocSearchResult {
|
package/dist/tools/helpers.js
CHANGED
|
@@ -55,7 +55,7 @@ export function formatTreeText(tree) {
|
|
|
55
55
|
const connector = isLast ? '└── ' : '├── ';
|
|
56
56
|
const childPrefix = isLast ? ' ' : '│ ';
|
|
57
57
|
const icon = node.isDir ? '📁' : '📄';
|
|
58
|
-
const summary = node.summary ? `
|
|
58
|
+
const summary = node.summary ? ` # ${node.summary}` : '';
|
|
59
59
|
lines.push(`${prefix}${connector}${icon} ${node.name}${summary}`);
|
|
60
60
|
if (node.children && node.children.length > 0) {
|
|
61
61
|
lines.push(...format(node.children, prefix + childPrefix));
|
package/dist/tools/index.js
CHANGED
|
@@ -3,12 +3,31 @@ import * as storage from '../storage/httpClient.js';
|
|
|
3
3
|
import { decodeObjectStrings, getRules, RULE_TYPE_LABELS } from '../utils.js';
|
|
4
4
|
import { wrap, formatDocMarkdown, formatTreeText, countTreeDocs } from './helpers.js';
|
|
5
5
|
export function registerTools(server, projectId, _user) {
|
|
6
|
+
// ===================== 跨项目访问 =====================
|
|
7
|
+
// 0. 列出所有可访问的项目
|
|
8
|
+
server.tool('kg_list_projects', '列出所有可访问的项目(用于跨项目访问)', {}, async () => {
|
|
9
|
+
try {
|
|
10
|
+
const projects = await storage.crossListProjects();
|
|
11
|
+
if (projects.length === 0) {
|
|
12
|
+
return wrap('暂无可访问的项目');
|
|
13
|
+
}
|
|
14
|
+
const lines = projects.map(p => {
|
|
15
|
+
const isCurrent = p.id === projectId ? ' ★当前' : '';
|
|
16
|
+
return `- ${p.name} (${p.id})${isCurrent}`;
|
|
17
|
+
});
|
|
18
|
+
return wrap(`可访问的项目 (${projects.length} 个):\n\n${lines.join('\n')}`);
|
|
19
|
+
}
|
|
20
|
+
catch (e) {
|
|
21
|
+
return wrap(`获取项目列表失败: ${String(e)}`);
|
|
22
|
+
}
|
|
23
|
+
});
|
|
6
24
|
// ===================== 文档管理 =====================
|
|
7
25
|
// 1. 创建文档
|
|
8
26
|
server.tool('kg_create_node', '创建知识文档。使用目录路径分类,文件名作为文档名', {
|
|
9
27
|
path: z.string().min(1).describe('完整文档路径(如"/前端/组件/Modal")'),
|
|
10
28
|
summary: z.string().optional().describe('一句话简介'),
|
|
11
|
-
content: z.string().describe('Markdown内容')
|
|
29
|
+
content: z.string().describe('Markdown内容'),
|
|
30
|
+
status: z.string().optional().describe('文档状态(默认"已完成")')
|
|
12
31
|
}, async (args) => {
|
|
13
32
|
const decoded = decodeObjectStrings(args);
|
|
14
33
|
const doc = await storage.createDoc(projectId, decoded.path, {
|
|
@@ -19,7 +38,8 @@ export function registerTools(server, projectId, _user) {
|
|
|
19
38
|
date: new Date().toISOString(),
|
|
20
39
|
changes: '初始创建'
|
|
21
40
|
}],
|
|
22
|
-
bugfixes: []
|
|
41
|
+
bugfixes: [],
|
|
42
|
+
status: decoded.status || '已完成'
|
|
23
43
|
});
|
|
24
44
|
return wrap(`✅ 文档已创建: ${decoded.path}\n\n${JSON.stringify(doc, null, 2)}`);
|
|
25
45
|
});
|
|
@@ -47,6 +67,7 @@ export function registerTools(server, projectId, _user) {
|
|
|
47
67
|
nodeId: z.string().optional().describe('【单个模式】文档路径,如"/前端/组件/Button"'),
|
|
48
68
|
summary: z.string().optional().describe('【单个模式】一句话简介'),
|
|
49
69
|
content: z.string().optional().describe('【单个模式】Markdown内容'),
|
|
70
|
+
status: z.string().optional().describe('【单个模式】文档状态'),
|
|
50
71
|
versions: z.array(z.object({
|
|
51
72
|
version: z.number(),
|
|
52
73
|
date: z.string(),
|
|
@@ -61,6 +82,7 @@ export function registerTools(server, projectId, _user) {
|
|
|
61
82
|
nodeId: z.string().describe('文档路径'),
|
|
62
83
|
summary: z.string().optional(),
|
|
63
84
|
content: z.string().optional(),
|
|
85
|
+
status: z.string().optional(),
|
|
64
86
|
versions: z.array(z.object({
|
|
65
87
|
version: z.number(),
|
|
66
88
|
date: z.string(),
|
|
@@ -92,6 +114,8 @@ export function registerTools(server, projectId, _user) {
|
|
|
92
114
|
updates.summary = item.summary;
|
|
93
115
|
if (item.content !== undefined)
|
|
94
116
|
updates.content = item.content;
|
|
117
|
+
if (item.status !== undefined)
|
|
118
|
+
updates.status = item.status;
|
|
95
119
|
if (item.versions !== undefined)
|
|
96
120
|
updates.versions = item.versions;
|
|
97
121
|
if (item.bugfixes !== undefined)
|
|
@@ -108,7 +132,7 @@ export function registerTools(server, projectId, _user) {
|
|
|
108
132
|
return wrap(msg);
|
|
109
133
|
}
|
|
110
134
|
// 单个更新模式: 必须提供 nodeId
|
|
111
|
-
const { nodeId, summary, content, versions, bugfixes } = decoded;
|
|
135
|
+
const { nodeId, summary, content, status, versions, bugfixes } = decoded;
|
|
112
136
|
if (!nodeId) {
|
|
113
137
|
return wrap('❌ 请提供 nodeId(单个模式)或 updates 数组(批量模式)');
|
|
114
138
|
}
|
|
@@ -127,6 +151,8 @@ export function registerTools(server, projectId, _user) {
|
|
|
127
151
|
updates.summary = summary;
|
|
128
152
|
if (content !== undefined)
|
|
129
153
|
updates.content = content;
|
|
154
|
+
if (status !== undefined)
|
|
155
|
+
updates.status = status;
|
|
130
156
|
if (versions !== undefined)
|
|
131
157
|
updates.versions = versions;
|
|
132
158
|
if (bugfixes !== undefined)
|
|
@@ -156,10 +182,34 @@ export function registerTools(server, projectId, _user) {
|
|
|
156
182
|
return wrap(doc ? '✅ 项目介绍已更新' : '更新失败');
|
|
157
183
|
});
|
|
158
184
|
// 3.6 获取项目规则
|
|
159
|
-
server.tool('kg_get_rules', '获取项目规则(可指定类型或获取全部)', {
|
|
185
|
+
server.tool('kg_get_rules', '获取项目规则(可指定类型或获取全部)。支持跨项目只读访问', {
|
|
160
186
|
ruleType: z.enum(['userStyles', 'testRules', 'reviewRules', 'codeStyle', 'unitTests']).optional()
|
|
161
|
-
.describe('规则类型: userStyles=用户沟通规则, codeStyle=编码风格规则, reviewRules=代码审查规则, testRules=错误分析规则, unitTests=代码测试规则。不传则返回全部')
|
|
187
|
+
.describe('规则类型: userStyles=用户沟通规则, codeStyle=编码风格规则, reviewRules=代码审查规则, testRules=错误分析规则, unitTests=代码测试规则。不传则返回全部'),
|
|
188
|
+
targetProject: z.string().optional().describe('目标项目ID或名称(不填=当前项目,跨项目只读)')
|
|
162
189
|
}, async (args) => {
|
|
190
|
+
// 跨项目访问
|
|
191
|
+
if (args.targetProject) {
|
|
192
|
+
if (args.ruleType) {
|
|
193
|
+
const rules = await storage.crossGetRules(args.targetProject, args.ruleType);
|
|
194
|
+
if (rules.length === 0) {
|
|
195
|
+
return { content: [{ type: 'text', text: `暂无${RULE_TYPE_LABELS[args.ruleType]}规则(项目: ${args.targetProject})` }] };
|
|
196
|
+
}
|
|
197
|
+
return { content: [{ type: 'text', text: `[跨项目: ${args.targetProject}]\n\n${rules.join('\n')}` }] };
|
|
198
|
+
}
|
|
199
|
+
// 获取全部规则
|
|
200
|
+
const allRules = [];
|
|
201
|
+
for (const type of ['userStyles', 'codeStyle', 'reviewRules', 'testRules', 'unitTests']) {
|
|
202
|
+
const rules = await storage.crossGetRules(args.targetProject, type);
|
|
203
|
+
if (rules.length > 0) {
|
|
204
|
+
allRules.push(`## ${RULE_TYPE_LABELS[type]}\n${rules.join('\n')}`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
if (allRules.length === 0) {
|
|
208
|
+
return { content: [{ type: 'text', text: `暂无项目规则(项目: ${args.targetProject})` }] };
|
|
209
|
+
}
|
|
210
|
+
return { content: [{ type: 'text', text: `[跨项目: ${args.targetProject}]\n\n${allRules.join('\n\n')}` }] };
|
|
211
|
+
}
|
|
212
|
+
// 当前项目
|
|
163
213
|
const rules = await getRules(projectId, args.ruleType);
|
|
164
214
|
if (!rules || rules.trim() === '') {
|
|
165
215
|
const typeName = args.ruleType ? RULE_TYPE_LABELS[args.ruleType] : '项目';
|
|
@@ -181,9 +231,20 @@ export function registerTools(server, projectId, _user) {
|
|
|
181
231
|
return wrap(`✅ ${RULE_TYPE_LABELS[decoded.ruleType]}已保存 (${decoded.rules.length} 条)`);
|
|
182
232
|
});
|
|
183
233
|
// 5. 读取单个文档详情
|
|
184
|
-
server.tool('kg_read_node', '读取文档完整内容(简介、正文、版本历史、修复记录)', {
|
|
185
|
-
nodeId: z.string().describe('文档路径')
|
|
234
|
+
server.tool('kg_read_node', '读取文档完整内容(简介、正文、版本历史、修复记录)。支持跨项目只读访问', {
|
|
235
|
+
nodeId: z.string().describe('文档路径'),
|
|
236
|
+
targetProject: z.string().optional().describe('目标项目ID或名称(不填=当前项目,跨项目只读)')
|
|
186
237
|
}, async (args) => {
|
|
238
|
+
// 跨项目访问
|
|
239
|
+
if (args.targetProject) {
|
|
240
|
+
const doc = await storage.crossGetDoc(args.targetProject, args.nodeId);
|
|
241
|
+
if (!doc) {
|
|
242
|
+
return wrap(`文档不存在(项目: ${args.targetProject})`);
|
|
243
|
+
}
|
|
244
|
+
const lines = formatDocMarkdown(doc, args.nodeId);
|
|
245
|
+
return wrap(`[跨项目: ${args.targetProject}]\n\n${lines.join('\n')}`);
|
|
246
|
+
}
|
|
247
|
+
// 当前项目
|
|
187
248
|
const doc = await storage.getDoc(projectId, args.nodeId);
|
|
188
249
|
if (!doc) {
|
|
189
250
|
return wrap('文档不存在');
|
|
@@ -193,8 +254,21 @@ export function registerTools(server, projectId, _user) {
|
|
|
193
254
|
return wrap(lines.join('\n'));
|
|
194
255
|
});
|
|
195
256
|
// 10. 获取知识库树状图
|
|
196
|
-
server.tool('kg_get_tree', '
|
|
257
|
+
server.tool('kg_get_tree', '获取知识库目录树。格式: 📄 文档名 # 简介。支持跨项目只读访问', {
|
|
258
|
+
targetProject: z.string().optional().describe('目标项目ID或名称(不填=当前项目,跨项目只读)')
|
|
259
|
+
}, async (args) => {
|
|
197
260
|
try {
|
|
261
|
+
// 跨项目访问
|
|
262
|
+
if (args.targetProject) {
|
|
263
|
+
const tree = await storage.crossGetTree(args.targetProject);
|
|
264
|
+
if (tree && tree.length > 0) {
|
|
265
|
+
const treeText = formatTreeText(tree);
|
|
266
|
+
const docCount = countTreeDocs(tree);
|
|
267
|
+
return wrap(`[跨项目: ${args.targetProject}]\n\n共 ${docCount} 个文档\n\n${treeText}`);
|
|
268
|
+
}
|
|
269
|
+
return wrap(`知识库为空(项目: ${args.targetProject})`);
|
|
270
|
+
}
|
|
271
|
+
// 当前项目
|
|
198
272
|
const tree = await storage.getTree(projectId);
|
|
199
273
|
if (tree && tree.length > 0) {
|
|
200
274
|
const treeText = formatTreeText(tree);
|
|
@@ -211,6 +285,28 @@ export function registerTools(server, projectId, _user) {
|
|
|
211
285
|
}, null, 2));
|
|
212
286
|
}
|
|
213
287
|
});
|
|
288
|
+
// 11. 按状态筛选文档
|
|
289
|
+
server.tool('kg_get_docs_by_status', '获取指定状态的文档列表。支持跨项目只读访问', {
|
|
290
|
+
statusList: z.array(z.string()).describe('状态列表(如["未完成","待修复"])'),
|
|
291
|
+
targetProject: z.string().optional().describe('目标项目ID或名称(不填=当前项目,跨项目只读)')
|
|
292
|
+
}, async (args) => {
|
|
293
|
+
// 跨项目访问
|
|
294
|
+
if (args.targetProject) {
|
|
295
|
+
const docs = await storage.crossGetDocsByStatus(args.targetProject, args.statusList);
|
|
296
|
+
if (docs.length === 0) {
|
|
297
|
+
return wrap(`未找到状态为 [${args.statusList.join(', ')}] 的文档(项目: ${args.targetProject})`);
|
|
298
|
+
}
|
|
299
|
+
const lines = docs.map(d => `- ${d.path} (${d.status || '已完成'}): ${d.summary || '无简介'}`);
|
|
300
|
+
return wrap(`[跨项目: ${args.targetProject}]\n\n找到 ${docs.length} 个文档:\n\n${lines.join('\n')}`);
|
|
301
|
+
}
|
|
302
|
+
// 当前项目
|
|
303
|
+
const docs = await storage.getDocsByStatus(projectId, args.statusList);
|
|
304
|
+
if (docs.length === 0) {
|
|
305
|
+
return wrap(`未找到状态为 [${args.statusList.join(', ')}] 的文档`);
|
|
306
|
+
}
|
|
307
|
+
const lines = docs.map(d => `- ${d.path} (${d.status || '已完成'}): ${d.summary || '无简介'}`);
|
|
308
|
+
return wrap(`找到 ${docs.length} 个文档:\n\n${lines.join('\n')}`);
|
|
309
|
+
});
|
|
214
310
|
// ===================== 任务管理 =====================
|
|
215
311
|
// 7. 创建任务
|
|
216
312
|
server.tool('task_create', '创建开发任务', {
|