@ppdocs/mcp 3.2.19 → 3.2.21
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 +53 -14
- package/dist/storage/httpClient.js +229 -51
- package/dist/storage/types.d.ts +0 -21
- package/dist/tools/analyzer.d.ts +2 -1
- package/dist/tools/analyzer.js +138 -24
- package/dist/tools/discussion.d.ts +3 -2
- package/dist/tools/discussion.js +109 -50
- package/dist/tools/docs.d.ts +2 -1
- package/dist/tools/docs.js +1 -1
- package/dist/tools/files.d.ts +1 -1
- package/dist/tools/files.js +61 -8
- package/dist/tools/index.d.ts +3 -2
- package/dist/tools/index.js +10 -6
- package/dist/tools/meeting.d.ts +7 -0
- package/dist/tools/meeting.js +97 -0
- package/dist/tools/tasks.d.ts +2 -1
- package/dist/tools/tasks.js +2 -2
- package/package.json +1 -1
- package/templates/commands/pp/audit.md +87 -0
- package/templates/commands/pp/diagnose.md +82 -0
- package/templates/commands/pp/discuss.md +90 -0
- package/templates/commands/pp/execute.md +84 -0
- package/templates/hooks/SystemPrompt.md +16 -0
- package/dist/storage/discussion.d.ts +0 -33
- package/dist/storage/discussion.js +0 -116
package/dist/tools/analyzer.js
CHANGED
|
@@ -16,14 +16,14 @@ const SYMBOL_ICONS = {
|
|
|
16
16
|
const SEVERITY_ICONS = {
|
|
17
17
|
critical: '🔴', warning: '🟡', info: '🟢',
|
|
18
18
|
};
|
|
19
|
-
export function registerAnalyzerTools(server,
|
|
19
|
+
export function registerAnalyzerTools(server, ctx) {
|
|
20
20
|
const client = () => getClient();
|
|
21
21
|
// ===== code_scan: 扫描项目代码 =====
|
|
22
22
|
server.tool('code_scan', '📡 扫描项目代码, 构建索引。返回文件数、符号数、语言统计。★首次使用 code_query/code_impact/code_context 前必须先执行★', {
|
|
23
|
-
projectPath: z.string().describe('项目源码的绝对路径(如"D:/projects/myapp")'),
|
|
23
|
+
projectPath: z.string().optional().describe('项目源码的绝对路径(如"D:/projects/myapp")。不传则自动从Beacon同步目录或项目配置解析'),
|
|
24
24
|
force: z.boolean().optional().describe('是否强制全量重建(默认false, 增量更新)'),
|
|
25
25
|
}, async (args) => safeTool(async () => {
|
|
26
|
-
const result = await client().analyzerScan(args.projectPath, args.force ?? false);
|
|
26
|
+
const result = await client().analyzerScan(args.projectPath || '', args.force ?? false);
|
|
27
27
|
return wrap([
|
|
28
28
|
`✅ 代码扫描完成`,
|
|
29
29
|
``,
|
|
@@ -36,10 +36,10 @@ export function registerAnalyzerTools(server, projectId) {
|
|
|
36
36
|
}));
|
|
37
37
|
// ===== code_query: 搜索代码符号 =====
|
|
38
38
|
server.tool('code_query', '🔤 搜索代码符号(函数/类/方法/接口/类型)。返回匹配列表+文件路径+行号。定位代码位置的最快方式。需先运行 code_scan', {
|
|
39
|
-
projectPath: z.string().describe('项目源码的绝对路径'),
|
|
39
|
+
projectPath: z.string().optional().describe('项目源码的绝对路径(不传则自动解析)'),
|
|
40
40
|
query: z.string().describe('搜索关键词(函数名/类名/方法名等)'),
|
|
41
41
|
}, async (args) => safeTool(async () => {
|
|
42
|
-
const results = await client().analyzerQuery(args.projectPath, args.query);
|
|
42
|
+
const results = await client().analyzerQuery(args.projectPath || '', args.query);
|
|
43
43
|
if (!results || results.length === 0) {
|
|
44
44
|
return wrap(`未找到匹配 "${args.query}" 的符号。请确认已运行 code_scan`);
|
|
45
45
|
}
|
|
@@ -57,11 +57,11 @@ export function registerAnalyzerTools(server, projectId) {
|
|
|
57
57
|
}));
|
|
58
58
|
// ===== code_impact: 分层影响分析 =====
|
|
59
59
|
server.tool('code_impact', '💥 爆炸半径分析 ★修改代码前必查★ 分析修改一个函数/类/类型会影响多少文件。BFS分层输出: L1🔴直接引用=必须检查, L2🟡间接引用=建议检查, L3🟢传递引用=注意。修改任何公共接口、函数签名、类型定义前务必先运行!', {
|
|
60
|
-
projectPath: z.string().describe('项目源码的绝对路径'),
|
|
60
|
+
projectPath: z.string().optional().describe('项目源码的绝对路径(不传则自动解析)'),
|
|
61
61
|
symbolName: z.string().describe('要分析的符号名称(如"AuthService", "handleLogin")'),
|
|
62
62
|
depth: z.number().optional().describe('分析深度层级(1-5, 默认2)。1=仅直接引用, 3=深度追踪'),
|
|
63
63
|
}, async (args) => safeTool(async () => {
|
|
64
|
-
const result = await client().analyzerImpactTree(args.projectPath, args.symbolName, args.depth ?? 2);
|
|
64
|
+
const result = await client().analyzerImpactTree(args.projectPath || '', args.symbolName, args.depth ?? 2);
|
|
65
65
|
if (!result) {
|
|
66
66
|
return wrap(`未找到符号 "${args.symbolName}"。请确认名称正确且已运行 code_scan`);
|
|
67
67
|
}
|
|
@@ -91,22 +91,22 @@ export function registerAnalyzerTools(server, projectId) {
|
|
|
91
91
|
}));
|
|
92
92
|
// ===== code_context: 文件360°上下文 =====
|
|
93
93
|
server.tool('code_context', '🔍 文件360°上下文 — 定义了什么符号、导入了什么、被谁引用。修改文件前使用, 快速了解所有依赖关系, 避免遗漏', {
|
|
94
|
-
projectPath: z.string().describe('项目源码的绝对路径'),
|
|
94
|
+
projectPath: z.string().optional().describe('项目源码的绝对路径(不传则自动解析)'),
|
|
95
95
|
filePath: z.string().describe('目标文件的相对路径(如"src/services/auth.ts")'),
|
|
96
96
|
}, async (args) => safeTool(async () => {
|
|
97
|
-
const
|
|
98
|
-
if (!
|
|
97
|
+
const fileCtx = await client().analyzerContext(args.projectPath || '', args.filePath);
|
|
98
|
+
if (!fileCtx) {
|
|
99
99
|
return wrap(`未找到文件 "${args.filePath}"。请确认路径正确, 路径格式为相对路径(如 src/main.ts)`);
|
|
100
100
|
}
|
|
101
101
|
const lines = [
|
|
102
|
-
`📄 ${
|
|
103
|
-
`语言: ${
|
|
102
|
+
`📄 ${fileCtx.filePath}`,
|
|
103
|
+
`语言: ${fileCtx.language} | ${fileCtx.linesTotal} 行`,
|
|
104
104
|
``,
|
|
105
105
|
];
|
|
106
106
|
// 定义的符号
|
|
107
|
-
if (
|
|
108
|
-
lines.push(`### 🔤 定义的符号 (${
|
|
109
|
-
for (const sym of
|
|
107
|
+
if (fileCtx.symbolsDefined.length > 0) {
|
|
108
|
+
lines.push(`### 🔤 定义的符号 (${fileCtx.symbolsDefined.length})`);
|
|
109
|
+
for (const sym of fileCtx.symbolsDefined) {
|
|
110
110
|
const icon = SYMBOL_ICONS[sym.kind] || '?';
|
|
111
111
|
const exp = sym.exported ? ' [export]' : '';
|
|
112
112
|
lines.push(` ${icon} ${sym.name}${exp} L${sym.lineStart}-L${sym.lineEnd}`);
|
|
@@ -114,18 +114,18 @@ export function registerAnalyzerTools(server, projectId) {
|
|
|
114
114
|
lines.push('');
|
|
115
115
|
}
|
|
116
116
|
// 导入
|
|
117
|
-
if (
|
|
118
|
-
lines.push(`### 📥 导入 (${
|
|
119
|
-
for (const imp of
|
|
117
|
+
if (fileCtx.imports.length > 0) {
|
|
118
|
+
lines.push(`### 📥 导入 (${fileCtx.imports.length})`);
|
|
119
|
+
for (const imp of fileCtx.imports) {
|
|
120
120
|
const specs = imp.specifiers.length > 0 ? `{ ${imp.specifiers.join(', ')} }` : '*';
|
|
121
121
|
lines.push(` → ${imp.source} ${specs} L${imp.line}`);
|
|
122
122
|
}
|
|
123
123
|
lines.push('');
|
|
124
124
|
}
|
|
125
125
|
// 被谁引用
|
|
126
|
-
if (
|
|
127
|
-
lines.push(`### 📤 被引用 (${
|
|
128
|
-
for (const by of
|
|
126
|
+
if (fileCtx.importedBy.length > 0) {
|
|
127
|
+
lines.push(`### 📤 被引用 (${fileCtx.importedBy.length})`);
|
|
128
|
+
for (const by of fileCtx.importedBy) {
|
|
129
129
|
const specs = by.specifiers.length > 0 ? `{ ${by.specifiers.join(', ')} }` : '';
|
|
130
130
|
lines.push(` ← ${by.filePath} ${specs}`);
|
|
131
131
|
}
|
|
@@ -135,16 +135,17 @@ export function registerAnalyzerTools(server, projectId) {
|
|
|
135
135
|
}));
|
|
136
136
|
// ===== code_path: 两点间链路追踪 =====
|
|
137
137
|
server.tool('code_path', '🔗 两点链路追踪 — 给定两个符号(如 fileA.funcA 和 fileB.funcB),自动搜索中间的引用链路,返回完整路径 + 绑定的知识图谱文档', {
|
|
138
|
-
projectPath: z.string().describe('项目源码的绝对路径'),
|
|
138
|
+
projectPath: z.string().optional().describe('项目源码的绝对路径(不传则自动解析)'),
|
|
139
139
|
symbolA: z.string().describe('起点符号名(如 "handleLogin")'),
|
|
140
140
|
symbolB: z.string().describe('终点符号名(如 "AuthService")'),
|
|
141
141
|
maxDepth: z.number().optional().describe('最大搜索深度(1-5, 默认3)'),
|
|
142
142
|
}, async (args) => safeTool(async () => {
|
|
143
143
|
const depth = Math.min(5, Math.max(1, args.maxDepth ?? 3));
|
|
144
|
+
const pp = args.projectPath || '';
|
|
144
145
|
// 1. 获取两个符号的影响树
|
|
145
146
|
const [treeA, treeB] = await Promise.all([
|
|
146
|
-
client().analyzerImpactTree(
|
|
147
|
-
client().analyzerImpactTree(
|
|
147
|
+
client().analyzerImpactTree(pp, args.symbolA, depth),
|
|
148
|
+
client().analyzerImpactTree(pp, args.symbolB, depth),
|
|
148
149
|
]);
|
|
149
150
|
if (!treeA)
|
|
150
151
|
return wrap(`❌ 未找到起点符号 "${args.symbolA}"。请确认名称正确且已运行 code_scan`);
|
|
@@ -260,4 +261,117 @@ export function registerAnalyzerTools(server, projectId) {
|
|
|
260
261
|
}
|
|
261
262
|
return wrap(lines.join('\n'));
|
|
262
263
|
}));
|
|
264
|
+
// ===== code_smart_context: 代码+文档全关联上下文 =====
|
|
265
|
+
server.tool('code_smart_context', '🔍 代码+文档全关联上下文 — 输入一个函数名,一次调用返回:代码依赖、关联文档、匹配规则、活跃任务、影响范围摘要。需先运行 code_scan', {
|
|
266
|
+
projectPath: z.string().optional().describe('项目源码的绝对路径(不传则自动解析)'),
|
|
267
|
+
symbolName: z.string().describe('要查询的符号名称(如"handleLogin", "AuthService")'),
|
|
268
|
+
}, async (args) => safeTool(async () => {
|
|
269
|
+
const smartCtx = await client().analyzerSmartContext(args.projectPath || '', args.symbolName);
|
|
270
|
+
if (!smartCtx) {
|
|
271
|
+
return wrap(`未找到符号 "${args.symbolName}"。请确认名称正确且已运行 code_scan`);
|
|
272
|
+
}
|
|
273
|
+
const lines = [
|
|
274
|
+
`🔍 智能上下文: ${smartCtx.symbol.name}`,
|
|
275
|
+
``,
|
|
276
|
+
];
|
|
277
|
+
// 符号信息
|
|
278
|
+
const icon = SYMBOL_ICONS[smartCtx.symbol.kind] || '?';
|
|
279
|
+
const exp = smartCtx.symbol.exported ? ' [export]' : '';
|
|
280
|
+
lines.push(`### ${icon} 符号信息`);
|
|
281
|
+
lines.push(` ${smartCtx.symbol.name}${exp} (${smartCtx.symbol.kind})`);
|
|
282
|
+
lines.push(` 📍 ${smartCtx.symbol.filePath}:${smartCtx.symbol.lineStart}-${smartCtx.symbol.lineEnd}`);
|
|
283
|
+
if (smartCtx.symbol.signature)
|
|
284
|
+
lines.push(` 📝 ${smartCtx.symbol.signature}`);
|
|
285
|
+
lines.push('');
|
|
286
|
+
// 文件上下文
|
|
287
|
+
lines.push(`### 📄 文件上下文: ${smartCtx.fileContext.filePath}`);
|
|
288
|
+
lines.push(` 语言: ${smartCtx.fileContext.language} | ${smartCtx.fileContext.linesTotal} 行`);
|
|
289
|
+
lines.push(` 定义: ${smartCtx.fileContext.symbolsDefined.length} 个符号 | 导入: ${smartCtx.fileContext.imports.length} | 被引用: ${smartCtx.fileContext.importedBy.length}`);
|
|
290
|
+
lines.push('');
|
|
291
|
+
// 影响范围
|
|
292
|
+
if (smartCtx.impactSummary.totalFiles > 0) {
|
|
293
|
+
lines.push(`### 💥 影响范围: ${smartCtx.impactSummary.totalFiles} 个文件`);
|
|
294
|
+
for (const f of smartCtx.impactSummary.l1Files.slice(0, 10)) {
|
|
295
|
+
lines.push(` 🔴 ${f}`);
|
|
296
|
+
}
|
|
297
|
+
lines.push('');
|
|
298
|
+
}
|
|
299
|
+
// 关联文档
|
|
300
|
+
if (smartCtx.relatedDocs.length > 0) {
|
|
301
|
+
lines.push(`### 📚 关联知识文档 (${smartCtx.relatedDocs.length})`);
|
|
302
|
+
for (const doc of smartCtx.relatedDocs) {
|
|
303
|
+
lines.push(` 📄 ${doc.path} — ${doc.summary}`);
|
|
304
|
+
}
|
|
305
|
+
lines.push('');
|
|
306
|
+
}
|
|
307
|
+
// 匹配规则
|
|
308
|
+
if (smartCtx.matchedRules.length > 0) {
|
|
309
|
+
lines.push(`### 📏 匹配规则 (${smartCtx.matchedRules.length})`);
|
|
310
|
+
for (const rule of smartCtx.matchedRules) {
|
|
311
|
+
lines.push(` [${rule.ruleType}] ${rule.content.substring(0, 100)}`);
|
|
312
|
+
}
|
|
313
|
+
lines.push('');
|
|
314
|
+
}
|
|
315
|
+
// 活跃任务
|
|
316
|
+
if (smartCtx.activeTasks.length > 0) {
|
|
317
|
+
lines.push(`### 📝 活跃任务 (${smartCtx.activeTasks.length})`);
|
|
318
|
+
for (const task of smartCtx.activeTasks) {
|
|
319
|
+
lines.push(` 🔵 [${task.id}] ${task.title}`);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return wrap(lines.join('\n'));
|
|
323
|
+
}));
|
|
324
|
+
// ===== code_full_path: 全关联路径 =====
|
|
325
|
+
server.tool('code_full_path', '🔗 全关联路径 — 两个符号之间不仅返回代码引用链路,还返回共享的KG文档、共同导入、祖先模块。需先运行 code_scan', {
|
|
326
|
+
projectPath: z.string().optional().describe('项目源码的绝对路径(不传则自动解析)'),
|
|
327
|
+
symbolA: z.string().describe('起点符号名(如 "handleLogin")'),
|
|
328
|
+
symbolB: z.string().describe('终点符号名(如 "AuthService")'),
|
|
329
|
+
maxDepth: z.number().optional().describe('最大搜索深度(1-5, 默认3)'),
|
|
330
|
+
}, async (args) => safeTool(async () => {
|
|
331
|
+
const result = await client().analyzerFullPath(args.projectPath || '', args.symbolA, args.symbolB, args.maxDepth ?? 3);
|
|
332
|
+
if (!result) {
|
|
333
|
+
return wrap(`❌ 无法构建路径。请确认两个符号名称正确且已运行 code_scan`);
|
|
334
|
+
}
|
|
335
|
+
const lines = [
|
|
336
|
+
`🔗 全关联路径`,
|
|
337
|
+
``,
|
|
338
|
+
`📍 ${result.symbolA.name} (${result.symbolA.kind}) → ${result.symbolA.filePath}:${result.symbolA.lineStart}`,
|
|
339
|
+
`📍 ${result.symbolB.name} (${result.symbolB.kind}) → ${result.symbolB.filePath}:${result.symbolB.lineStart}`,
|
|
340
|
+
``,
|
|
341
|
+
`📊 ${result.pathSummary}`,
|
|
342
|
+
``,
|
|
343
|
+
];
|
|
344
|
+
// 代码路径
|
|
345
|
+
if (result.codePath.length > 0) {
|
|
346
|
+
lines.push(`### 🔗 代码引用链路 (${result.codePath.length} 步)`);
|
|
347
|
+
for (const step of result.codePath.slice(0, 20)) {
|
|
348
|
+
lines.push(` L${step.depth} ${step.filePath} [${step.refKind}] ← ${step.symbolName}`);
|
|
349
|
+
}
|
|
350
|
+
lines.push('');
|
|
351
|
+
}
|
|
352
|
+
// 共享文档
|
|
353
|
+
if (result.sharedDocs.length > 0) {
|
|
354
|
+
lines.push(`### 📚 共享KG文档 (${result.sharedDocs.length})`);
|
|
355
|
+
for (const doc of result.sharedDocs) {
|
|
356
|
+
lines.push(` 📄 ${doc.path} — ${doc.summary}`);
|
|
357
|
+
}
|
|
358
|
+
lines.push('');
|
|
359
|
+
}
|
|
360
|
+
// 共同导入
|
|
361
|
+
if (result.commonImports.length > 0) {
|
|
362
|
+
lines.push(`### 📥 共同导入 (${result.commonImports.length})`);
|
|
363
|
+
for (const imp of result.commonImports) {
|
|
364
|
+
lines.push(` → ${imp}`);
|
|
365
|
+
}
|
|
366
|
+
lines.push('');
|
|
367
|
+
}
|
|
368
|
+
// 共同祖先
|
|
369
|
+
if (result.commonAncestors.length > 0) {
|
|
370
|
+
lines.push(`### 📁 共同祖先目录`);
|
|
371
|
+
for (const anc of result.commonAncestors) {
|
|
372
|
+
lines.push(` 📂 ${anc}`);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
return wrap(lines.join('\n'));
|
|
376
|
+
}));
|
|
263
377
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 💬 kg_discuss (6→1)
|
|
3
|
-
* 合并:
|
|
4
|
-
*
|
|
3
|
+
* 合并: list, read, create, reply, close, delete
|
|
4
|
+
* 统一走 HTTP → Rust 后端 (单一写入者)
|
|
5
|
+
* sender 格式: "projectId:user" (如 "p-ca3sgejg:张三")
|
|
5
6
|
*/
|
|
6
7
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
7
8
|
import { type McpContext } from './shared.js';
|
package/dist/tools/discussion.js
CHANGED
|
@@ -1,51 +1,111 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 💬 kg_discuss (6→1)
|
|
3
|
-
* 合并:
|
|
4
|
-
*
|
|
3
|
+
* 合并: list, read, create, reply, close, delete
|
|
4
|
+
* 统一走 HTTP → Rust 后端 (单一写入者)
|
|
5
|
+
* sender 格式: "projectId:user" (如 "p-ca3sgejg:张三")
|
|
5
6
|
*/
|
|
6
7
|
import { z } from 'zod';
|
|
7
8
|
import { getClient } from '../storage/httpClient.js';
|
|
8
9
|
import { decodeObjectStrings } from '../utils.js';
|
|
9
10
|
import { wrap, safeTool } from './shared.js';
|
|
10
|
-
|
|
11
|
+
function sender(ctx) {
|
|
12
|
+
return `${ctx.projectId}:${ctx.user}`;
|
|
13
|
+
}
|
|
14
|
+
function relativeTime(iso) {
|
|
15
|
+
try {
|
|
16
|
+
const diff = Date.now() - new Date(iso).getTime();
|
|
17
|
+
if (diff < 60000)
|
|
18
|
+
return '刚刚';
|
|
19
|
+
if (diff < 3600000)
|
|
20
|
+
return `${Math.floor(diff / 60000)}分钟前`;
|
|
21
|
+
if (diff < 86400000)
|
|
22
|
+
return `${Math.floor(diff / 3600000)}小时前`;
|
|
23
|
+
return `${Math.floor(diff / 86400000)}天前`;
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return iso;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function formatList(items, ctx) {
|
|
30
|
+
const lines = [
|
|
31
|
+
`本项目: ${sender(ctx)}`,
|
|
32
|
+
``,
|
|
33
|
+
`📋 活跃讨论 (${items.length}个)`,
|
|
34
|
+
``,
|
|
35
|
+
`| ID | 标题 | 发起方 | 参与方 | 摘要 | 回复 | 更新 |`,
|
|
36
|
+
`|:---|:---|:---|:---|:---|:---:|:---|`,
|
|
37
|
+
];
|
|
38
|
+
for (const d of items) {
|
|
39
|
+
const others = d.participants.filter(p => p !== d.initiator).join(', ') || '—';
|
|
40
|
+
lines.push(`| ${d.id} | ${d.title} | ${d.initiator} | ${others} | ${d.summary} | ${d.status} | ${relativeTime(d.updatedAt)} |`);
|
|
41
|
+
}
|
|
42
|
+
return lines.join('\n');
|
|
43
|
+
}
|
|
44
|
+
function formatDetailView(d) {
|
|
45
|
+
const msgs = d.messages || [];
|
|
46
|
+
const lines = [
|
|
47
|
+
`💬 ${d.title}`,
|
|
48
|
+
`发起: ${d.initiator} | 参与: ${d.participants.length}方 | 状态: ${d.status}`,
|
|
49
|
+
`📌 ${d.summary}`,
|
|
50
|
+
`📊 总消息: ${d.totalCount} | 未读: ${d.unreadCount}`,
|
|
51
|
+
``,
|
|
52
|
+
];
|
|
53
|
+
msgs.forEach((m, i) => {
|
|
54
|
+
const readTag = m.isRead ? '📖' : '🆕';
|
|
55
|
+
lines.push(`--- ${readTag} 消息 ${i + 1}/${msgs.length} [${m.sender}] ${relativeTime(m.timestamp)} ---`);
|
|
56
|
+
// 已读且无 content → 显示摘要
|
|
57
|
+
if (m.isRead && !m.content) {
|
|
58
|
+
lines.push(`[摘要] ${m.summary}`);
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
lines.push(m.content || m.summary || '(无内容)');
|
|
62
|
+
}
|
|
63
|
+
lines.push('');
|
|
64
|
+
});
|
|
65
|
+
return lines.join('\n');
|
|
66
|
+
}
|
|
11
67
|
export function registerDiscussionTools(server, ctx) {
|
|
12
68
|
const client = () => getClient();
|
|
13
|
-
server.tool('kg_discuss', '💬 跨项目讨论 — 发起、回复、归档协同讨论。action: list(列出活跃讨论)|read(
|
|
14
|
-
action: z.enum(['list', 'read', 'create', 'reply', 'close', 'delete'])
|
|
69
|
+
server.tool('kg_discuss', '💬 跨项目讨论 — 发起、回复、归档协同讨论。action: list(列出活跃讨论)|read(读取详情,自动追踪已读)|create(发起)|reply(回复)|complete(标记完成)|close(结案归档)|delete(删除)|history(查看历史讨论)', {
|
|
70
|
+
action: z.enum(['list', 'read', 'create', 'reply', 'complete', 'close', 'delete', 'history'])
|
|
15
71
|
.describe('操作类型'),
|
|
16
72
|
id: z.string().optional()
|
|
17
|
-
.describe('讨论哈希ID (read/reply/close/delete)'),
|
|
73
|
+
.describe('讨论哈希ID (read/reply/complete/close/delete)'),
|
|
18
74
|
ids: z.array(z.string()).optional()
|
|
19
75
|
.describe('批量读取的讨论ID数组 (read)'),
|
|
20
76
|
title: z.string().optional()
|
|
21
77
|
.describe('讨论标题 (create)'),
|
|
22
78
|
participants: z.array(z.string()).optional()
|
|
23
79
|
.describe('参与项目ID数组 (create, 不含自己)'),
|
|
80
|
+
summary: z.string().optional()
|
|
81
|
+
.describe('消息摘要 (create/reply, 不传则自动截取content前50字)'),
|
|
24
82
|
content: z.string().optional()
|
|
25
|
-
.describe('
|
|
83
|
+
.describe('消息详细内容Markdown (create/reply)'),
|
|
26
84
|
conclusion: z.string().optional()
|
|
27
85
|
.describe('结案总结 (close)'),
|
|
28
86
|
newSummary: z.string().optional()
|
|
29
|
-
.describe('
|
|
87
|
+
.describe('更新讨论进展摘要 (reply)'),
|
|
88
|
+
mode: z.enum(['auto', 'full']).optional()
|
|
89
|
+
.describe('读取模式 (read, 默认auto: 已读消息仅返回摘要)'),
|
|
30
90
|
}, async (args) => safeTool(async () => {
|
|
31
91
|
const decoded = decodeObjectStrings(args);
|
|
92
|
+
const me = sender(ctx);
|
|
32
93
|
switch (decoded.action) {
|
|
33
94
|
case 'list': {
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
return wrap(`当前无活跃的讨论 (本项目ID: ${ctx.projectId})${cleanMsg}`);
|
|
39
|
-
return wrap(`本项目ID: ${ctx.projectId}\n活跃讨论区 (${active.length} 个):${cleanMsg}\n\n` + JSON.stringify(active, null, 2));
|
|
95
|
+
const active = await client().discussionList();
|
|
96
|
+
if (!Array.isArray(active) || active.length === 0)
|
|
97
|
+
return wrap(`当前无活跃的讨论 (本项目: ${me})`);
|
|
98
|
+
return wrap(formatList(active, ctx));
|
|
40
99
|
}
|
|
41
100
|
case 'read': {
|
|
42
101
|
const readIds = decoded.ids || (decoded.id ? [decoded.id] : []);
|
|
43
102
|
if (readIds.length === 0)
|
|
44
103
|
return wrap('❌ read 需要 id 或 ids');
|
|
45
|
-
const
|
|
46
|
-
|
|
104
|
+
const readMode = decoded.mode || 'auto';
|
|
105
|
+
const discussions = await client().discussionReadByIds(readIds, me, readMode);
|
|
106
|
+
if (!Array.isArray(discussions) || discussions.length === 0)
|
|
47
107
|
return wrap('未找到对应的讨论记录');
|
|
48
|
-
return wrap(
|
|
108
|
+
return wrap(discussions.map(formatDetailView).join('\n\n━━━━━━━━━━━━━━━━━━━━\n\n'));
|
|
49
109
|
}
|
|
50
110
|
case 'create': {
|
|
51
111
|
if (!decoded.title)
|
|
@@ -54,55 +114,54 @@ export function registerDiscussionTools(server, ctx) {
|
|
|
54
114
|
return wrap('❌ create 需要 participants');
|
|
55
115
|
if (!decoded.content)
|
|
56
116
|
return wrap('❌ create 需要 content');
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
return wrap('❌ 活跃讨论已达上限(10条)');
|
|
60
|
-
const id = DiscussionManager.create(decoded.title, ctx.projectId, decoded.participants, decoded.content);
|
|
61
|
-
return wrap(`✅ 讨论已发起,ID: ${id}\n发起方: ${ctx.projectId}\n参与方: ${decoded.participants.join(', ')}`);
|
|
117
|
+
const result = await client().discussionCreate(decoded.title, me, decoded.participants, decoded.content, decoded.summary);
|
|
118
|
+
return wrap(`✅ 讨论已发起\nID: ${result.id}\n发起方: ${me}\n参与方: ${decoded.participants.join(', ')}`);
|
|
62
119
|
}
|
|
63
120
|
case 'reply': {
|
|
64
121
|
if (!decoded.id)
|
|
65
122
|
return wrap('❌ reply 需要 id');
|
|
66
123
|
if (!decoded.content)
|
|
67
124
|
return wrap('❌ reply 需要 content');
|
|
68
|
-
|
|
69
|
-
return wrap(
|
|
125
|
+
await client().discussionReply(decoded.id, me, decoded.content, decoded.summary, decoded.newSummary);
|
|
126
|
+
return wrap(`✅ 回复成功 (ID: ${decoded.id}, 身份: ${me})`);
|
|
127
|
+
}
|
|
128
|
+
case 'history': {
|
|
129
|
+
const all = await client().discussionListAll(me);
|
|
130
|
+
if (!Array.isArray(all) || all.length === 0)
|
|
131
|
+
return wrap(`暂无参与过的讨论记录 (身份: ${me})`);
|
|
132
|
+
const lines = [
|
|
133
|
+
`本项目: ${me}`,
|
|
134
|
+
``,
|
|
135
|
+
`📋 讨论历史 (${all.length}个)`,
|
|
136
|
+
``,
|
|
137
|
+
`| ID | 标题 | 发起方 | 状态 | 消息数 | 更新 |`,
|
|
138
|
+
`|:---|:---|:---|:---|:---:|:---|`,
|
|
139
|
+
];
|
|
140
|
+
for (const d of all) {
|
|
141
|
+
const statusIcon = d.status === 'active' ? '🟢' : '⚪';
|
|
142
|
+
lines.push(`| ${d.id} | ${d.title} | ${d.initiator} | ${statusIcon} ${d.status} | ${d.messageCount ?? 0} | ${relativeTime(d.updatedAt)} |`);
|
|
143
|
+
}
|
|
144
|
+
return wrap(lines.join('\n'));
|
|
145
|
+
}
|
|
146
|
+
case 'complete': {
|
|
147
|
+
if (!decoded.id)
|
|
148
|
+
return wrap('❌ complete 需要 id');
|
|
149
|
+
await client().discussionComplete(decoded.id);
|
|
150
|
+
return wrap(`✅ 讨论已标记完成 (ID: ${decoded.id})`);
|
|
70
151
|
}
|
|
71
152
|
case 'close': {
|
|
72
153
|
if (!decoded.id)
|
|
73
154
|
return wrap('❌ close 需要 id');
|
|
74
155
|
if (!decoded.conclusion)
|
|
75
156
|
return wrap('❌ close 需要 conclusion');
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
return wrap('❌ 讨论不存在或已被归档清理');
|
|
79
|
-
let md = `# 跨项目协同: ${topic.title}\n\n`;
|
|
80
|
-
md += `**发起方**: ${topic.initiator}\n`;
|
|
81
|
-
md += `**参与方**: ${topic.participants.join(', ')}\n`;
|
|
82
|
-
md += `**结案总结**: ${decoded.conclusion}\n\n`;
|
|
83
|
-
md += `## 讨论还原 (Timeline)\n\n`;
|
|
84
|
-
for (const msg of topic.messages) {
|
|
85
|
-
md += `### [${msg.sender}] (${msg.timestamp})\n${msg.content}\n\n`;
|
|
86
|
-
}
|
|
87
|
-
const safeTitle = topic.title.replace(/[\/\\?%*:|"<>]/g, '_');
|
|
88
|
-
const path = `/跨项目协同记录/${decoded.id}_${safeTitle}`;
|
|
89
|
-
try {
|
|
90
|
-
await client().createDoc(path, {
|
|
91
|
-
summary: decoded.conclusion, content: md,
|
|
92
|
-
versions: [{ version: 1.0, date: new Date().toISOString(), changes: '结案归档' }],
|
|
93
|
-
bugfixes: [], status: '已完成'
|
|
94
|
-
});
|
|
95
|
-
return wrap(`✅ 讨论已结案并归档: \`${path}\``);
|
|
96
|
-
}
|
|
97
|
-
catch (err) {
|
|
98
|
-
return wrap(`⚠️ 讨论已清除,但归档失败: ${err}\n\n${md}`);
|
|
99
|
-
}
|
|
157
|
+
const result = await client().discussionClose(decoded.id, decoded.conclusion);
|
|
158
|
+
return wrap(`✅ 讨论已结案并归档: \`${result.archived_path}\``);
|
|
100
159
|
}
|
|
101
160
|
case 'delete': {
|
|
102
161
|
if (!decoded.id)
|
|
103
162
|
return wrap('❌ delete 需要 id');
|
|
104
|
-
|
|
105
|
-
return wrap(
|
|
163
|
+
await client().discussionDelete(decoded.id);
|
|
164
|
+
return wrap(`✅ 讨论已删除 (ID: ${decoded.id})`);
|
|
106
165
|
}
|
|
107
166
|
default:
|
|
108
167
|
return wrap(`❌ 未知 action: ${decoded.action}`);
|
package/dist/tools/docs.d.ts
CHANGED
|
@@ -4,4 +4,5 @@
|
|
|
4
4
|
* kg_get_tree, kg_get_docs_by_status
|
|
5
5
|
*/
|
|
6
6
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
7
|
-
|
|
7
|
+
import { type McpContext } from './shared.js';
|
|
8
|
+
export declare function registerDocTools(server: McpServer, ctx: McpContext): void;
|
package/dist/tools/docs.js
CHANGED
|
@@ -7,7 +7,7 @@ import { z } from 'zod';
|
|
|
7
7
|
import { getClient } from '../storage/httpClient.js';
|
|
8
8
|
import { decodeObjectStrings } from '../utils.js';
|
|
9
9
|
import { wrap, safeTool, crossPrefix, formatDocMarkdown, formatTreeText, countTreeDocs } from './shared.js';
|
|
10
|
-
export function registerDocTools(server,
|
|
10
|
+
export function registerDocTools(server, ctx) {
|
|
11
11
|
const client = () => getClient();
|
|
12
12
|
// ========== kg_doc: 文档 CRUD 统一入口 ==========
|
|
13
13
|
server.tool('kg_doc', '📄 知识文档操作 — 创建、读取、更新、删除、复制文档。action: create(创建)|read(读取)|update(更新)|delete(删除)|batch_update(批量更新)|copy(跨项目复制)', {
|
package/dist/tools/files.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 📁 kg_files (5→1)
|
|
3
3
|
* 合并: project_list_files, project_read_file, project_upload, project_download
|
|
4
|
-
*
|
|
4
|
+
* + 公共文件池: public_list, public_read, public_upload, public_download, public_delete
|
|
5
5
|
*/
|
|
6
6
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
7
7
|
export declare function registerFileTools(server: McpServer): void;
|
package/dist/tools/files.js
CHANGED
|
@@ -1,26 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 📁 kg_files (5→1)
|
|
3
3
|
* 合并: project_list_files, project_read_file, project_upload, project_download
|
|
4
|
-
*
|
|
4
|
+
* + 公共文件池: public_list, public_read, public_upload, public_download, public_delete
|
|
5
5
|
*/
|
|
6
6
|
import { z } from 'zod';
|
|
7
7
|
import { getClient } from '../storage/httpClient.js';
|
|
8
8
|
import { wrap, safeTool, crossPrefix, formatFileSize } from './shared.js';
|
|
9
9
|
export function registerFileTools(server) {
|
|
10
10
|
const client = () => getClient();
|
|
11
|
-
server.tool('kg_files', '📁 项目文件操作 — 浏览目录、读取文件、上传下载。action: list(目录浏览)|read(读取文件)|upload(
|
|
12
|
-
action: z.enum(['list', 'read', 'upload', 'download'])
|
|
11
|
+
server.tool('kg_files', '📁 项目文件操作 — 浏览目录、读取文件、上传下载。action: list(目录浏览)|read(读取文件)|upload(上传)|download(下载文件)|public_list(公共文件池浏览)|public_read(读取公共文件)|public_upload(上传到公共池)|public_download(下载公共文件)|public_delete(删除公共文件)|public_mkdir(创建公共池子目录)|public_rename(重命名公共文件)', {
|
|
12
|
+
action: z.enum(['list', 'read', 'upload', 'download', 'public_list', 'public_read', 'public_upload', 'public_download', 'public_delete', 'public_mkdir', 'public_rename'])
|
|
13
13
|
.describe('操作类型'),
|
|
14
14
|
path: z.string().optional()
|
|
15
|
-
.describe('文件路径 (read/download, 如"src/main.ts")'),
|
|
15
|
+
.describe('文件路径 (read/download/public_read/public_download/public_delete/public_mkdir, 如"src/main.ts")'),
|
|
16
16
|
dir: z.string().optional()
|
|
17
|
-
.describe('子目录路径 (list, 如"src/components")'),
|
|
17
|
+
.describe('子目录路径 (list/public_list, 如"src/components")'),
|
|
18
18
|
localDir: z.string().optional()
|
|
19
|
-
.describe('
|
|
19
|
+
.describe('本地路径(目录或单文件) (upload/public_upload)'),
|
|
20
20
|
localPath: z.string().optional()
|
|
21
|
-
.describe('本地保存路径 (download)'),
|
|
21
|
+
.describe('本地保存路径 (download/public_download)'),
|
|
22
22
|
remoteDir: z.string().optional()
|
|
23
|
-
.describe('远程目标子目录 (upload)'),
|
|
23
|
+
.describe('远程目标子目录 (upload/public_upload)'),
|
|
24
|
+
newName: z.string().optional()
|
|
25
|
+
.describe('新名称 (public_rename)'),
|
|
24
26
|
targetProject: z.string().optional()
|
|
25
27
|
.describe('跨项目操作的目标项目ID'),
|
|
26
28
|
}, async (args) => safeTool(async () => {
|
|
@@ -64,6 +66,57 @@ export function registerFileTools(server) {
|
|
|
64
66
|
const prefix = args.targetProject ? crossPrefix(args.targetProject) : '';
|
|
65
67
|
return wrap(`${prefix}✅ 已下载\n\n- 本地路径: ${result.localPath}\n- 文件数量: ${result.fileCount}`);
|
|
66
68
|
}
|
|
69
|
+
// ===== 公共文件池 =====
|
|
70
|
+
case 'public_list': {
|
|
71
|
+
const files = await client().publicFilesList(args.dir);
|
|
72
|
+
if (files.length === 0)
|
|
73
|
+
return wrap('📦 公共文件池' + (args.dir ? ` /${args.dir}` : '') + ' 为空');
|
|
74
|
+
const lines = files.map(f => {
|
|
75
|
+
const icon = f.isDir ? '📁' : '📄';
|
|
76
|
+
const size = f.isDir ? '' : ` (${formatFileSize(f.size)})`;
|
|
77
|
+
return `${icon} ${f.name}${size}`;
|
|
78
|
+
});
|
|
79
|
+
const dirLabel = args.dir || '/';
|
|
80
|
+
return wrap(`📦 公共文件池 ${dirLabel} (${files.length} 项)\n\n${lines.join('\n')}`);
|
|
81
|
+
}
|
|
82
|
+
case 'public_read': {
|
|
83
|
+
if (!args.path)
|
|
84
|
+
return wrap('❌ public_read 需要 path');
|
|
85
|
+
const content = await client().publicFilesRead(args.path);
|
|
86
|
+
return wrap(`📦 公共文件 ${args.path}\n\n\`\`\`\n${content}\n\`\`\``);
|
|
87
|
+
}
|
|
88
|
+
case 'public_upload': {
|
|
89
|
+
if (!args.localDir)
|
|
90
|
+
return wrap('❌ public_upload 需要 localDir');
|
|
91
|
+
const result = await client().publicFilesUpload(args.localDir, args.remoteDir);
|
|
92
|
+
return wrap(`✅ 已上传到公共文件池\n\n- 文件数量: ${result.fileCount}\n- 目标目录: ${args.remoteDir || '/'}`);
|
|
93
|
+
}
|
|
94
|
+
case 'public_download': {
|
|
95
|
+
if (!args.path)
|
|
96
|
+
return wrap('❌ public_download 需要 path');
|
|
97
|
+
const result = await client().publicFilesDownload(args.path, args.localPath);
|
|
98
|
+
return wrap(`✅ 已从公共文件池下载\n\n- 本地路径: ${result.localPath}\n- 文件数量: ${result.fileCount}`);
|
|
99
|
+
}
|
|
100
|
+
case 'public_delete': {
|
|
101
|
+
if (!args.path)
|
|
102
|
+
return wrap('❌ public_delete 需要 path');
|
|
103
|
+
await client().publicFilesDelete(args.path);
|
|
104
|
+
return wrap(`✅ 已从公共文件池删除: ${args.path}`);
|
|
105
|
+
}
|
|
106
|
+
case 'public_mkdir': {
|
|
107
|
+
if (!args.path)
|
|
108
|
+
return wrap('❌ public_mkdir 需要 path');
|
|
109
|
+
await client().publicFilesMkdir(args.path);
|
|
110
|
+
return wrap(`✅ 已创建公共文件池目录: /${args.path}`);
|
|
111
|
+
}
|
|
112
|
+
case 'public_rename': {
|
|
113
|
+
if (!args.path)
|
|
114
|
+
return wrap('❌ public_rename 需要 path');
|
|
115
|
+
if (!args.newName)
|
|
116
|
+
return wrap('❌ public_rename 需要 newName');
|
|
117
|
+
await client().publicFilesRename(args.path, args.newName);
|
|
118
|
+
return wrap(`✅ 已重命名: ${args.path} → ${args.newName}`);
|
|
119
|
+
}
|
|
67
120
|
default:
|
|
68
121
|
return wrap(`❌ 未知 action: ${args.action}`);
|
|
69
122
|
}
|
package/dist/tools/index.d.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* MCP 工具注册入口
|
|
3
|
-
*
|
|
3
|
+
* 18 个工具, 7 个子模块
|
|
4
4
|
*
|
|
5
5
|
* 🔗 初始化: kg_init (1个)
|
|
6
6
|
* 📊 导航: kg_status, kg_tree (2个)
|
|
7
7
|
* 📚 知识: kg_doc, kg_projects, kg_rules (3个)
|
|
8
8
|
* 📝 工作流: kg_task, kg_files, kg_discuss (3个)
|
|
9
|
-
* 🔬 代码分析: code_scan, code_query, code_impact, code_context, code_path (
|
|
9
|
+
* 🔬 代码分析: code_scan, code_query, code_impact, code_context, code_path, code_smart_context, code_full_path (7个)
|
|
10
|
+
* 🏛️ 协作: kg_meeting (1个)
|
|
10
11
|
*/
|
|
11
12
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
12
13
|
export declare function registerTools(server: McpServer, projectId: string, user: string, onProjectChange?: (newProjectId: string, newApiUrl: string) => void): void;
|