@ppdocs/mcp 3.2.21 → 3.2.23
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 +21 -22
- package/dist/storage/httpClient.d.ts +13 -12
- package/dist/storage/httpClient.js +60 -32
- package/dist/tools/analyzer.d.ts +4 -3
- package/dist/tools/analyzer.js +7 -236
- package/dist/tools/discussion.d.ts +8 -2
- package/dist/tools/discussion.js +123 -34
- package/dist/tools/docs.js +41 -3
- package/dist/tools/flowchart.d.ts +7 -0
- package/dist/tools/flowchart.js +483 -0
- package/dist/tools/index.d.ts +8 -7
- package/dist/tools/index.js +12 -8
- package/dist/tools/rules.js +19 -1
- package/dist/tools/tasks.d.ts +2 -2
- package/dist/tools/tasks.js +112 -24
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -412,30 +412,29 @@ function generateHooksConfig(cwd) {
|
|
|
412
412
|
}
|
|
413
413
|
};
|
|
414
414
|
}
|
|
415
|
-
/** 生成 MCP 权限配置 (允许全部 ppdocs-kg
|
|
415
|
+
/** 生成 MCP 权限配置 (允许全部 ppdocs-kg 工具) */
|
|
416
416
|
function generateMcpPermissions() {
|
|
417
417
|
const mcpMethods = [
|
|
418
|
-
//
|
|
419
|
-
'
|
|
420
|
-
'
|
|
421
|
-
'
|
|
422
|
-
|
|
423
|
-
'
|
|
424
|
-
'
|
|
425
|
-
'
|
|
426
|
-
|
|
427
|
-
'
|
|
428
|
-
'
|
|
429
|
-
'
|
|
430
|
-
|
|
431
|
-
'
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
'
|
|
436
|
-
'
|
|
437
|
-
'
|
|
438
|
-
'task_complete',
|
|
418
|
+
// 初始化 + 导航
|
|
419
|
+
'kg_init',
|
|
420
|
+
'kg_status',
|
|
421
|
+
'kg_tree',
|
|
422
|
+
// 知识管理
|
|
423
|
+
'kg_doc',
|
|
424
|
+
'kg_projects',
|
|
425
|
+
'kg_rules',
|
|
426
|
+
// 工作流
|
|
427
|
+
'kg_task',
|
|
428
|
+
'kg_files',
|
|
429
|
+
'kg_discuss',
|
|
430
|
+
// 流程图
|
|
431
|
+
'kg_flowchart',
|
|
432
|
+
// 协作
|
|
433
|
+
'kg_meeting',
|
|
434
|
+
// 代码分析
|
|
435
|
+
'code_scan',
|
|
436
|
+
'code_smart_context',
|
|
437
|
+
'code_full_path',
|
|
439
438
|
];
|
|
440
439
|
return mcpMethods.map(m => `mcp__ppdocs-kg__${m}`);
|
|
441
440
|
}
|
|
@@ -34,14 +34,25 @@ export declare class PpdocsApiClient {
|
|
|
34
34
|
getGlobalRulesMeta(): Promise<Record<string, RuleMeta>>;
|
|
35
35
|
saveGlobalRulesMeta(meta: Record<string, RuleMeta>): Promise<boolean>;
|
|
36
36
|
listTasks(status?: 'active' | 'archived'): Promise<TaskSummary[]>;
|
|
37
|
-
getTask(taskId: string): Promise<Task | null>;
|
|
37
|
+
getTask(taskId: string, mode?: 'smart' | 'full'): Promise<Task | null>;
|
|
38
38
|
createTask(task: {
|
|
39
39
|
title: string;
|
|
40
40
|
description: string;
|
|
41
41
|
goals: string[];
|
|
42
42
|
}, creator: string): Promise<Task>;
|
|
43
|
-
addTaskLog(taskId: string,
|
|
43
|
+
addTaskLog(taskId: string, content: string, logType?: TaskLogType): Promise<Task | null>;
|
|
44
44
|
completeTask(taskId: string, experience: TaskExperience): Promise<Task | null>;
|
|
45
|
+
deleteTask(taskId: string): Promise<boolean>;
|
|
46
|
+
listFlowcharts(): Promise<unknown[]>;
|
|
47
|
+
getFlowchart(chartId: string): Promise<unknown>;
|
|
48
|
+
batchAddToFlowchart(chartId: string, nodes: unknown[], edges: unknown[]): Promise<unknown>;
|
|
49
|
+
saveFlowchart(chartId: string, data: unknown): Promise<unknown>;
|
|
50
|
+
/** 原子更新单个节点 (后端自动处理并发) */
|
|
51
|
+
updateFlowchartNode(chartId: string, nodeId: string, node: unknown, changeDesc?: string): Promise<unknown>;
|
|
52
|
+
/** 原子删除单个节点 (后端自动清理关联边) */
|
|
53
|
+
deleteFlowchartNode(chartId: string, nodeId: string): Promise<unknown>;
|
|
54
|
+
getFlowchartOrphans(): Promise<unknown[]>;
|
|
55
|
+
getFlowchartHealth(): Promise<unknown[]>;
|
|
45
56
|
/** 列出所有可访问的项目 */
|
|
46
57
|
crossListProjects(): Promise<{
|
|
47
58
|
id: string;
|
|
@@ -87,12 +98,6 @@ export declare class PpdocsApiClient {
|
|
|
87
98
|
}>;
|
|
88
99
|
/** 扫描项目代码, 构建/更新索引 */
|
|
89
100
|
analyzerScan(projectPath: string, force?: boolean): Promise<unknown>;
|
|
90
|
-
/** 查询代码符号 (模糊匹配) */
|
|
91
|
-
analyzerQuery(projectPath: string, query: string): Promise<unknown>;
|
|
92
|
-
/** 分层影响分析 (BFS 递归, 可指定深度) */
|
|
93
|
-
analyzerImpactTree(projectPath: string, symbolName: string, depth?: number): Promise<unknown>;
|
|
94
|
-
/** 获取文件 360° 上下文 */
|
|
95
|
-
analyzerContext(projectPath: string, filePath: string): Promise<unknown>;
|
|
96
101
|
/** 智能上下文: 代码+文档+规则+任务全关联 */
|
|
97
102
|
analyzerSmartContext(projectPath: string, symbolName: string): Promise<unknown>;
|
|
98
103
|
/** 全关联路径: 两个符号之间的代码+文档路径 */
|
|
@@ -114,10 +119,6 @@ export declare class PpdocsApiClient {
|
|
|
114
119
|
discussionComplete(id: string): Promise<boolean>;
|
|
115
120
|
/** 删除讨论 (公开路由) */
|
|
116
121
|
discussionDelete(id: string): Promise<boolean>;
|
|
117
|
-
/** 结案归档讨论 (认证路由) */
|
|
118
|
-
discussionClose(id: string, conclusion: string): Promise<{
|
|
119
|
-
archived_path: string;
|
|
120
|
-
}>;
|
|
121
122
|
/** 列出公共文件 */
|
|
122
123
|
publicFilesList(dir?: string): Promise<FileInfo[]>;
|
|
123
124
|
/** 读取公共文件 (文本) */
|
|
@@ -277,9 +277,10 @@ export class PpdocsApiClient {
|
|
|
277
277
|
const query = status ? `?status=${status}` : '';
|
|
278
278
|
return this.request(`/tasks${query}`);
|
|
279
279
|
}
|
|
280
|
-
async getTask(taskId) {
|
|
280
|
+
async getTask(taskId, mode) {
|
|
281
281
|
try {
|
|
282
|
-
|
|
282
|
+
const query = mode ? `?mode=${mode}` : '';
|
|
283
|
+
return await this.request(`/tasks/${taskId}${query}`);
|
|
283
284
|
}
|
|
284
285
|
catch {
|
|
285
286
|
return null;
|
|
@@ -300,11 +301,14 @@ export class PpdocsApiClient {
|
|
|
300
301
|
body: JSON.stringify(payload)
|
|
301
302
|
});
|
|
302
303
|
}
|
|
303
|
-
async addTaskLog(taskId,
|
|
304
|
+
async addTaskLog(taskId, content, logType) {
|
|
304
305
|
try {
|
|
306
|
+
const body = { content };
|
|
307
|
+
if (logType)
|
|
308
|
+
body.log_type = logType;
|
|
305
309
|
return await this.request(`/tasks/${taskId}/logs`, {
|
|
306
310
|
method: 'POST',
|
|
307
|
-
body: JSON.stringify(
|
|
311
|
+
body: JSON.stringify(body)
|
|
308
312
|
});
|
|
309
313
|
}
|
|
310
314
|
catch {
|
|
@@ -322,6 +326,58 @@ export class PpdocsApiClient {
|
|
|
322
326
|
return null;
|
|
323
327
|
}
|
|
324
328
|
}
|
|
329
|
+
async deleteTask(taskId) {
|
|
330
|
+
try {
|
|
331
|
+
await this.request(`/tasks/${taskId}`, { method: 'DELETE' });
|
|
332
|
+
return true;
|
|
333
|
+
}
|
|
334
|
+
catch {
|
|
335
|
+
return false;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
// ============ 流程图管理 ============
|
|
339
|
+
async listFlowcharts() {
|
|
340
|
+
return this.request('/flowcharts');
|
|
341
|
+
}
|
|
342
|
+
async getFlowchart(chartId) {
|
|
343
|
+
try {
|
|
344
|
+
return await this.request(`/flowcharts/${chartId}`);
|
|
345
|
+
}
|
|
346
|
+
catch {
|
|
347
|
+
return null;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
async batchAddToFlowchart(chartId, nodes, edges) {
|
|
351
|
+
return this.request(`/flowcharts/${chartId}/batch`, {
|
|
352
|
+
method: 'POST',
|
|
353
|
+
body: JSON.stringify({ nodes, edges })
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
async saveFlowchart(chartId, data) {
|
|
357
|
+
return this.request(`/flowcharts/${chartId}`, {
|
|
358
|
+
method: 'PUT',
|
|
359
|
+
body: JSON.stringify(data)
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
/** 原子更新单个节点 (后端自动处理并发) */
|
|
363
|
+
async updateFlowchartNode(chartId, nodeId, node, changeDesc) {
|
|
364
|
+
return this.request(`/flowcharts/${chartId}/nodes/${nodeId}`, {
|
|
365
|
+
method: 'PUT',
|
|
366
|
+
body: JSON.stringify({ node, change_desc: changeDesc || 'MCP update' })
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
/** 原子删除单个节点 (后端自动清理关联边) */
|
|
370
|
+
async deleteFlowchartNode(chartId, nodeId) {
|
|
371
|
+
return this.request(`/flowcharts/${chartId}/nodes/${nodeId}`, {
|
|
372
|
+
method: 'DELETE'
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
async getFlowchartOrphans() {
|
|
376
|
+
return this.request('/flowcharts/orphans');
|
|
377
|
+
}
|
|
378
|
+
async getFlowchartHealth() {
|
|
379
|
+
return this.request('/flowcharts/health');
|
|
380
|
+
}
|
|
325
381
|
// ============ 跨项目只读访问 ============
|
|
326
382
|
/** 列出所有可访问的项目 */
|
|
327
383
|
async crossListProjects() {
|
|
@@ -465,27 +521,6 @@ export class PpdocsApiClient {
|
|
|
465
521
|
body: JSON.stringify({ project_path: projectPath, force }),
|
|
466
522
|
});
|
|
467
523
|
}
|
|
468
|
-
/** 查询代码符号 (模糊匹配) */
|
|
469
|
-
async analyzerQuery(projectPath, query) {
|
|
470
|
-
return this.request('/analyzer/query', {
|
|
471
|
-
method: 'POST',
|
|
472
|
-
body: JSON.stringify({ project_path: projectPath, query }),
|
|
473
|
-
});
|
|
474
|
-
}
|
|
475
|
-
/** 分层影响分析 (BFS 递归, 可指定深度) */
|
|
476
|
-
async analyzerImpactTree(projectPath, symbolName, depth = 2) {
|
|
477
|
-
return this.request('/analyzer/impact-tree', {
|
|
478
|
-
method: 'POST',
|
|
479
|
-
body: JSON.stringify({ project_path: projectPath, symbol_name: symbolName, depth }),
|
|
480
|
-
});
|
|
481
|
-
}
|
|
482
|
-
/** 获取文件 360° 上下文 */
|
|
483
|
-
async analyzerContext(projectPath, filePath) {
|
|
484
|
-
return this.request('/analyzer/context', {
|
|
485
|
-
method: 'POST',
|
|
486
|
-
body: JSON.stringify({ project_path: projectPath, file_path: filePath }),
|
|
487
|
-
});
|
|
488
|
-
}
|
|
489
524
|
/** 智能上下文: 代码+文档+规则+任务全关联 */
|
|
490
525
|
async analyzerSmartContext(projectPath, symbolName) {
|
|
491
526
|
return this.request('/analyzer/smart-context', {
|
|
@@ -560,13 +595,6 @@ export class PpdocsApiClient {
|
|
|
560
595
|
method: 'DELETE',
|
|
561
596
|
});
|
|
562
597
|
}
|
|
563
|
-
/** 结案归档讨论 (认证路由) */
|
|
564
|
-
async discussionClose(id, conclusion) {
|
|
565
|
-
return this.request(`/discussions/${encodeURIComponent(id)}/close`, {
|
|
566
|
-
method: 'POST',
|
|
567
|
-
body: JSON.stringify({ conclusion }),
|
|
568
|
-
});
|
|
569
|
-
}
|
|
570
598
|
// ============ 公共文件池 ============
|
|
571
599
|
/** 列出公共文件 */
|
|
572
600
|
async publicFilesList(dir) {
|
package/dist/tools/analyzer.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* 代码分析引擎工具 (
|
|
3
|
-
* code_scan,
|
|
2
|
+
* 代码分析引擎工具 (3个)
|
|
3
|
+
* code_scan, code_smart_context, code_full_path
|
|
4
4
|
*
|
|
5
|
-
*
|
|
5
|
+
* code_smart_context: 代码+KG文档全关联 (含影响范围)
|
|
6
|
+
* code_full_path: 两点间代码链路+共享KG文档
|
|
6
7
|
*/
|
|
7
8
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
8
9
|
import { type McpContext } from './shared.js';
|
package/dist/tools/analyzer.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* 代码分析引擎工具 (
|
|
3
|
-
* code_scan,
|
|
2
|
+
* 代码分析引擎工具 (3个)
|
|
3
|
+
* code_scan, code_smart_context, code_full_path
|
|
4
4
|
*
|
|
5
|
-
*
|
|
5
|
+
* code_smart_context: 代码+KG文档全关联 (含影响范围)
|
|
6
|
+
* code_full_path: 两点间代码链路+共享KG文档
|
|
6
7
|
*/
|
|
7
8
|
import { z } from 'zod';
|
|
8
9
|
import { getClient } from '../storage/httpClient.js';
|
|
@@ -13,13 +14,10 @@ const SYMBOL_ICONS = {
|
|
|
13
14
|
type_alias: '𝐓', enum: '𝐄', struct: '𝐒', trait: '⚡',
|
|
14
15
|
constant: '𝐊', variable: '𝑣',
|
|
15
16
|
};
|
|
16
|
-
const SEVERITY_ICONS = {
|
|
17
|
-
critical: '🔴', warning: '🟡', info: '🟢',
|
|
18
|
-
};
|
|
19
17
|
export function registerAnalyzerTools(server, ctx) {
|
|
20
18
|
const client = () => getClient();
|
|
21
19
|
// ===== code_scan: 扫描项目代码 =====
|
|
22
|
-
server.tool('code_scan', '📡 扫描项目代码, 构建索引。返回文件数、符号数、语言统计。★首次使用
|
|
20
|
+
server.tool('code_scan', '📡 扫描项目代码, 构建索引。返回文件数、符号数、语言统计。★首次使用 code_smart_context/code_full_path 前必须先执行★', {
|
|
23
21
|
projectPath: z.string().optional().describe('项目源码的绝对路径(如"D:/projects/myapp")。不传则自动从Beacon同步目录或项目配置解析'),
|
|
24
22
|
force: z.boolean().optional().describe('是否强制全量重建(默认false, 增量更新)'),
|
|
25
23
|
}, async (args) => safeTool(async () => {
|
|
@@ -34,235 +32,8 @@ export function registerAnalyzerTools(server, ctx) {
|
|
|
34
32
|
`- 索引时间: ${result.indexedAt}`,
|
|
35
33
|
].join('\n'));
|
|
36
34
|
}));
|
|
37
|
-
// ===== code_query: 搜索代码符号 =====
|
|
38
|
-
server.tool('code_query', '🔤 搜索代码符号(函数/类/方法/接口/类型)。返回匹配列表+文件路径+行号。定位代码位置的最快方式。需先运行 code_scan', {
|
|
39
|
-
projectPath: z.string().optional().describe('项目源码的绝对路径(不传则自动解析)'),
|
|
40
|
-
query: z.string().describe('搜索关键词(函数名/类名/方法名等)'),
|
|
41
|
-
}, async (args) => safeTool(async () => {
|
|
42
|
-
const results = await client().analyzerQuery(args.projectPath || '', args.query);
|
|
43
|
-
if (!results || results.length === 0) {
|
|
44
|
-
return wrap(`未找到匹配 "${args.query}" 的符号。请确认已运行 code_scan`);
|
|
45
|
-
}
|
|
46
|
-
const lines = results.map(r => {
|
|
47
|
-
const icon = SYMBOL_ICONS[r.symbol.kind] || '?';
|
|
48
|
-
const exp = r.symbol.exported ? ' [export]' : '';
|
|
49
|
-
const parent = r.symbol.parent ? ` ◀ ${r.symbol.parent}` : '';
|
|
50
|
-
return ` ${icon} ${r.symbol.name}${parent}${exp} → ${r.filePath}:${r.symbol.lineStart}`;
|
|
51
|
-
});
|
|
52
|
-
return wrap([
|
|
53
|
-
`🔤 找到 ${results.length} 个匹配符号:`,
|
|
54
|
-
``,
|
|
55
|
-
...lines,
|
|
56
|
-
].join('\n'));
|
|
57
|
-
}));
|
|
58
|
-
// ===== code_impact: 分层影响分析 =====
|
|
59
|
-
server.tool('code_impact', '💥 爆炸半径分析 ★修改代码前必查★ 分析修改一个函数/类/类型会影响多少文件。BFS分层输出: L1🔴直接引用=必须检查, L2🟡间接引用=建议检查, L3🟢传递引用=注意。修改任何公共接口、函数签名、类型定义前务必先运行!', {
|
|
60
|
-
projectPath: z.string().optional().describe('项目源码的绝对路径(不传则自动解析)'),
|
|
61
|
-
symbolName: z.string().describe('要分析的符号名称(如"AuthService", "handleLogin")'),
|
|
62
|
-
depth: z.number().optional().describe('分析深度层级(1-5, 默认2)。1=仅直接引用, 3=深度追踪'),
|
|
63
|
-
}, async (args) => safeTool(async () => {
|
|
64
|
-
const result = await client().analyzerImpactTree(args.projectPath || '', args.symbolName, args.depth ?? 2);
|
|
65
|
-
if (!result) {
|
|
66
|
-
return wrap(`未找到符号 "${args.symbolName}"。请确认名称正确且已运行 code_scan`);
|
|
67
|
-
}
|
|
68
|
-
if (result.levels.length === 0) {
|
|
69
|
-
return wrap(`✅ "${args.symbolName}" 没有被其他文件引用, 修改安全`);
|
|
70
|
-
}
|
|
71
|
-
// 生成分层树形输出
|
|
72
|
-
const lines = [
|
|
73
|
-
`🎯 ${result.symbolName} (${result.symbolKind})`,
|
|
74
|
-
`📍 定义: ${result.definedIn}:${result.lineStart}`,
|
|
75
|
-
``,
|
|
76
|
-
`📊 ${result.summary}`,
|
|
77
|
-
``,
|
|
78
|
-
];
|
|
79
|
-
for (const level of result.levels) {
|
|
80
|
-
const icon = SEVERITY_ICONS[level.severity] || '⚪';
|
|
81
|
-
const label = level.depth === 1 ? '直接引用 (必须检查)' :
|
|
82
|
-
level.depth === 2 ? '间接引用 (建议检查)' :
|
|
83
|
-
'传递引用 (注意)';
|
|
84
|
-
lines.push(`### ${icon} L${level.depth} ${label} (${level.count}个)`);
|
|
85
|
-
for (const entry of level.entries) {
|
|
86
|
-
lines.push(` ${entry.filePath}:${entry.line} [${entry.refKind}] ${entry.context}`);
|
|
87
|
-
}
|
|
88
|
-
lines.push('');
|
|
89
|
-
}
|
|
90
|
-
return wrap(lines.join('\n'));
|
|
91
|
-
}));
|
|
92
|
-
// ===== code_context: 文件360°上下文 =====
|
|
93
|
-
server.tool('code_context', '🔍 文件360°上下文 — 定义了什么符号、导入了什么、被谁引用。修改文件前使用, 快速了解所有依赖关系, 避免遗漏', {
|
|
94
|
-
projectPath: z.string().optional().describe('项目源码的绝对路径(不传则自动解析)'),
|
|
95
|
-
filePath: z.string().describe('目标文件的相对路径(如"src/services/auth.ts")'),
|
|
96
|
-
}, async (args) => safeTool(async () => {
|
|
97
|
-
const fileCtx = await client().analyzerContext(args.projectPath || '', args.filePath);
|
|
98
|
-
if (!fileCtx) {
|
|
99
|
-
return wrap(`未找到文件 "${args.filePath}"。请确认路径正确, 路径格式为相对路径(如 src/main.ts)`);
|
|
100
|
-
}
|
|
101
|
-
const lines = [
|
|
102
|
-
`📄 ${fileCtx.filePath}`,
|
|
103
|
-
`语言: ${fileCtx.language} | ${fileCtx.linesTotal} 行`,
|
|
104
|
-
``,
|
|
105
|
-
];
|
|
106
|
-
// 定义的符号
|
|
107
|
-
if (fileCtx.symbolsDefined.length > 0) {
|
|
108
|
-
lines.push(`### 🔤 定义的符号 (${fileCtx.symbolsDefined.length})`);
|
|
109
|
-
for (const sym of fileCtx.symbolsDefined) {
|
|
110
|
-
const icon = SYMBOL_ICONS[sym.kind] || '?';
|
|
111
|
-
const exp = sym.exported ? ' [export]' : '';
|
|
112
|
-
lines.push(` ${icon} ${sym.name}${exp} L${sym.lineStart}-L${sym.lineEnd}`);
|
|
113
|
-
}
|
|
114
|
-
lines.push('');
|
|
115
|
-
}
|
|
116
|
-
// 导入
|
|
117
|
-
if (fileCtx.imports.length > 0) {
|
|
118
|
-
lines.push(`### 📥 导入 (${fileCtx.imports.length})`);
|
|
119
|
-
for (const imp of fileCtx.imports) {
|
|
120
|
-
const specs = imp.specifiers.length > 0 ? `{ ${imp.specifiers.join(', ')} }` : '*';
|
|
121
|
-
lines.push(` → ${imp.source} ${specs} L${imp.line}`);
|
|
122
|
-
}
|
|
123
|
-
lines.push('');
|
|
124
|
-
}
|
|
125
|
-
// 被谁引用
|
|
126
|
-
if (fileCtx.importedBy.length > 0) {
|
|
127
|
-
lines.push(`### 📤 被引用 (${fileCtx.importedBy.length})`);
|
|
128
|
-
for (const by of fileCtx.importedBy) {
|
|
129
|
-
const specs = by.specifiers.length > 0 ? `{ ${by.specifiers.join(', ')} }` : '';
|
|
130
|
-
lines.push(` ← ${by.filePath} ${specs}`);
|
|
131
|
-
}
|
|
132
|
-
lines.push('');
|
|
133
|
-
}
|
|
134
|
-
return wrap(lines.join('\n'));
|
|
135
|
-
}));
|
|
136
|
-
// ===== code_path: 两点间链路追踪 =====
|
|
137
|
-
server.tool('code_path', '🔗 两点链路追踪 — 给定两个符号(如 fileA.funcA 和 fileB.funcB),自动搜索中间的引用链路,返回完整路径 + 绑定的知识图谱文档', {
|
|
138
|
-
projectPath: z.string().optional().describe('项目源码的绝对路径(不传则自动解析)'),
|
|
139
|
-
symbolA: z.string().describe('起点符号名(如 "handleLogin")'),
|
|
140
|
-
symbolB: z.string().describe('终点符号名(如 "AuthService")'),
|
|
141
|
-
maxDepth: z.number().optional().describe('最大搜索深度(1-5, 默认3)'),
|
|
142
|
-
}, async (args) => safeTool(async () => {
|
|
143
|
-
const depth = Math.min(5, Math.max(1, args.maxDepth ?? 3));
|
|
144
|
-
const pp = args.projectPath || '';
|
|
145
|
-
// 1. 获取两个符号的影响树
|
|
146
|
-
const [treeA, treeB] = await Promise.all([
|
|
147
|
-
client().analyzerImpactTree(pp, args.symbolA, depth),
|
|
148
|
-
client().analyzerImpactTree(pp, args.symbolB, depth),
|
|
149
|
-
]);
|
|
150
|
-
if (!treeA)
|
|
151
|
-
return wrap(`❌ 未找到起点符号 "${args.symbolA}"。请确认名称正确且已运行 code_scan`);
|
|
152
|
-
if (!treeB)
|
|
153
|
-
return wrap(`❌ 未找到终点符号 "${args.symbolB}"。请确认名称正确且已运行 code_scan`);
|
|
154
|
-
const graphA = new Map(); // 从 A 出发可达的文件
|
|
155
|
-
const graphB = new Map(); // 从 B 出发可达的文件
|
|
156
|
-
const buildGraph = (tree, graph) => {
|
|
157
|
-
graph.set(tree.definedIn, []);
|
|
158
|
-
for (const level of tree.levels) {
|
|
159
|
-
for (const entry of level.entries) {
|
|
160
|
-
if (!graph.has(entry.filePath))
|
|
161
|
-
graph.set(entry.filePath, []);
|
|
162
|
-
graph.get(entry.filePath).push({
|
|
163
|
-
file: entry.filePath,
|
|
164
|
-
line: entry.line,
|
|
165
|
-
depth: level.depth,
|
|
166
|
-
from: tree.symbolName,
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
};
|
|
171
|
-
buildGraph(treeA, graphA);
|
|
172
|
-
buildGraph(treeB, graphB);
|
|
173
|
-
// 3. 查找交集: A 和 B 都能到达的文件
|
|
174
|
-
const intersection = [];
|
|
175
|
-
for (const [fileA] of graphA) {
|
|
176
|
-
if (graphB.has(fileA)) {
|
|
177
|
-
const refsA = graphA.get(fileA);
|
|
178
|
-
const refsB = graphB.get(fileA);
|
|
179
|
-
const minDepthA = refsA.length > 0 ? Math.min(...refsA.map(r => r.depth)) : 0;
|
|
180
|
-
const minDepthB = refsB.length > 0 ? Math.min(...refsB.map(r => r.depth)) : 0;
|
|
181
|
-
intersection.push({ file: fileA, depthFromA: minDepthA, depthFromB: minDepthB });
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
// 4. 尝试获取 KG 文档绑定
|
|
185
|
-
let docBindings = {};
|
|
186
|
-
try {
|
|
187
|
-
const docClient = client();
|
|
188
|
-
const allDocs = await docClient.listDocs();
|
|
189
|
-
for (const doc of allDocs) {
|
|
190
|
-
if (!doc.isDir && doc.summary) {
|
|
191
|
-
docBindings[doc.name] = doc.summary;
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
catch { /* KG 不可用时忽略 */ }
|
|
196
|
-
// 5. 格式化输出
|
|
197
|
-
const lines = [
|
|
198
|
-
`🔗 两点链路追踪`,
|
|
199
|
-
``,
|
|
200
|
-
`📍 起点: ${treeA.symbolName} (${treeA.symbolKind}) → ${treeA.definedIn}:${treeA.lineStart}`,
|
|
201
|
-
`📍 终点: ${treeB.symbolName} (${treeB.symbolKind}) → ${treeB.definedIn}:${treeB.lineStart}`,
|
|
202
|
-
``,
|
|
203
|
-
];
|
|
204
|
-
// 直接引用关系
|
|
205
|
-
if (treeA.definedIn === treeB.definedIn) {
|
|
206
|
-
lines.push(`✅ 两个符号定义在同一文件中!`);
|
|
207
|
-
lines.push('');
|
|
208
|
-
}
|
|
209
|
-
// 检查 A 的影响树是否直接包含 B 的文件
|
|
210
|
-
const directAtoB = graphA.has(treeB.definedIn);
|
|
211
|
-
const directBtoA = graphB.has(treeA.definedIn);
|
|
212
|
-
if (directAtoB || directBtoA) {
|
|
213
|
-
lines.push(`### ✅ 发现直接链路`);
|
|
214
|
-
if (directAtoB) {
|
|
215
|
-
const refs = graphA.get(treeB.definedIn);
|
|
216
|
-
lines.push(` ${treeA.symbolName} → [L${refs[0]?.depth || 1}] → ${treeB.definedIn} (含 ${treeB.symbolName})`);
|
|
217
|
-
}
|
|
218
|
-
if (directBtoA) {
|
|
219
|
-
const refs = graphB.get(treeA.definedIn);
|
|
220
|
-
lines.push(` ${treeB.symbolName} → [L${refs[0]?.depth || 1}] → ${treeA.definedIn} (含 ${treeA.symbolName})`);
|
|
221
|
-
}
|
|
222
|
-
lines.push('');
|
|
223
|
-
}
|
|
224
|
-
if (intersection.length > 0) {
|
|
225
|
-
// 按总深度排序
|
|
226
|
-
intersection.sort((a, b) => (a.depthFromA + a.depthFromB) - (b.depthFromA + b.depthFromB));
|
|
227
|
-
lines.push(`### 🔗 共经文件 (${intersection.length}个)`);
|
|
228
|
-
lines.push(`| 文件 | 距${treeA.symbolName} | 距${treeB.symbolName} | 📚 绑定文档 |`);
|
|
229
|
-
lines.push(`|:---|:---:|:---:|:---|`);
|
|
230
|
-
for (const node of intersection.slice(0, 20)) {
|
|
231
|
-
const fileName = node.file.split('/').pop() || node.file;
|
|
232
|
-
const docBind = docBindings[fileName] || '—';
|
|
233
|
-
lines.push(`| ${node.file} | L${node.depthFromA} | L${node.depthFromB} | ${docBind} |`);
|
|
234
|
-
}
|
|
235
|
-
if (intersection.length > 20) {
|
|
236
|
-
lines.push(`| ... | | | (还有 ${intersection.length - 20} 个) |`);
|
|
237
|
-
}
|
|
238
|
-
lines.push('');
|
|
239
|
-
}
|
|
240
|
-
else if (!directAtoB && !directBtoA) {
|
|
241
|
-
lines.push(`⚠️ 在 ${depth} 层深度内未发现 ${treeA.symbolName} 与 ${treeB.symbolName} 的引用链路`);
|
|
242
|
-
lines.push(`建议: 增加 maxDepth 参数(当前=${depth}, 最大=5) 重试`);
|
|
243
|
-
lines.push('');
|
|
244
|
-
}
|
|
245
|
-
// 附加: 各符号的影响概要
|
|
246
|
-
lines.push(`### 📊 影响概要`);
|
|
247
|
-
lines.push(`- ${treeA.symbolName}: ${treeA.summary}`);
|
|
248
|
-
lines.push(`- ${treeB.symbolName}: ${treeB.summary}`);
|
|
249
|
-
// 附加: 绑定的 KG 文档
|
|
250
|
-
const relevantDocs = [];
|
|
251
|
-
const symbolNames = [treeA.symbolName, treeB.symbolName];
|
|
252
|
-
for (const [docName, docSummary] of Object.entries(docBindings)) {
|
|
253
|
-
if (symbolNames.some(s => docName.toLowerCase().includes(s.toLowerCase()) || s.toLowerCase().includes(docName.toLowerCase()))) {
|
|
254
|
-
relevantDocs.push(` 📚 ${docName}: ${docSummary}`);
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
if (relevantDocs.length > 0) {
|
|
258
|
-
lines.push('');
|
|
259
|
-
lines.push(`### 📚 相关知识图谱文档`);
|
|
260
|
-
lines.push(...relevantDocs);
|
|
261
|
-
}
|
|
262
|
-
return wrap(lines.join('\n'));
|
|
263
|
-
}));
|
|
264
35
|
// ===== code_smart_context: 代码+文档全关联上下文 =====
|
|
265
|
-
server.tool('code_smart_context', '🔍 代码+文档全关联上下文 —
|
|
36
|
+
server.tool('code_smart_context', '🔍 代码+文档全关联上下文 — 输入一个函数名,一次调用返回:代码依赖、关联文档、匹配规则、活跃任务、影响范围摘要。需先运行 code_scan', {
|
|
266
37
|
projectPath: z.string().optional().describe('项目源码的绝对路径(不传则自动解析)'),
|
|
267
38
|
symbolName: z.string().describe('要查询的符号名称(如"handleLogin", "AuthService")'),
|
|
268
39
|
}, async (args) => safeTool(async () => {
|
|
@@ -322,7 +93,7 @@ export function registerAnalyzerTools(server, ctx) {
|
|
|
322
93
|
return wrap(lines.join('\n'));
|
|
323
94
|
}));
|
|
324
95
|
// ===== code_full_path: 全关联路径 =====
|
|
325
|
-
server.tool('code_full_path', '🔗 全关联路径 —
|
|
96
|
+
server.tool('code_full_path', '🔗 全关联路径 — 两个符号之间不仅返回代码引用链路,还返回共享的KG文档、共同导入、祖先模块。需先运行 code_scan', {
|
|
326
97
|
projectPath: z.string().optional().describe('项目源码的绝对路径(不传则自动解析)'),
|
|
327
98
|
symbolA: z.string().describe('起点符号名(如 "handleLogin")'),
|
|
328
99
|
symbolB: z.string().describe('终点符号名(如 "AuthService")'),
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* 💬 kg_discuss (
|
|
3
|
-
* 合并: list, read, create, reply, close, delete
|
|
2
|
+
* 💬 kg_discuss (8 actions)
|
|
3
|
+
* 合并: list, read, create, reply, close, delete, complete, history
|
|
4
4
|
* 统一走 HTTP → Rust 后端 (单一写入者)
|
|
5
5
|
* sender 格式: "projectId:user" (如 "p-ca3sgejg:张三")
|
|
6
|
+
*
|
|
7
|
+
* 权限模型:
|
|
8
|
+
* list/history — 任何人可列出(全局可见)
|
|
9
|
+
* create — 任何人可发起(指定参与项目ID)
|
|
10
|
+
* read/reply — 仅讨论组内成员(initiator + participants)
|
|
11
|
+
* complete/close/delete — 仅发起人
|
|
6
12
|
*/
|
|
7
13
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
8
14
|
import { type McpContext } from './shared.js';
|