@ppdocs/mcp 3.2.18 → 3.2.20
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 +52 -39
- package/dist/storage/httpClient.d.ts +27 -0
- package/dist/storage/httpClient.js +102 -0
- package/dist/tools/analyzer.d.ts +2 -1
- package/dist/tools/analyzer.js +127 -14
- package/dist/tools/discussion.d.ts +3 -2
- package/dist/tools/discussion.js +67 -45
- package/dist/tools/docs.d.ts +2 -1
- package/dist/tools/docs.js +1 -1
- 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/cli.js
CHANGED
|
@@ -89,10 +89,12 @@ function showHelp() {
|
|
|
89
89
|
ppdocs MCP CLI
|
|
90
90
|
|
|
91
91
|
Commands:
|
|
92
|
-
init
|
|
92
|
+
init Full setup: .ppdocs + templates + MCP registration
|
|
93
|
+
bind Lightweight: only create .ppdocs (for adding projects to existing MCP)
|
|
93
94
|
|
|
94
95
|
Usage:
|
|
95
96
|
npx @ppdocs/mcp init -p <projectId> -k <key> [options]
|
|
97
|
+
npx @ppdocs/mcp bind -p <projectId> -k <key> [options]
|
|
96
98
|
|
|
97
99
|
Options:
|
|
98
100
|
-p, --project Project ID (required)
|
|
@@ -105,51 +107,55 @@ Options:
|
|
|
105
107
|
--sync <id> Sync knowledge base docs from a source project to ./docs/ppdocs/
|
|
106
108
|
|
|
107
109
|
Example:
|
|
108
|
-
npx @ppdocs/mcp init -p myproject -k abc123xyz
|
|
109
|
-
npx @ppdocs/mcp
|
|
110
|
-
npx @ppdocs/mcp init -p myproject -k abc123xyz --from _templates
|
|
111
|
-
npx @ppdocs/mcp init -p myproject -k abc123xyz --sync shared-rules
|
|
110
|
+
npx @ppdocs/mcp init -p myproject -k abc123xyz --api 192.168.1.100
|
|
111
|
+
npx @ppdocs/mcp bind -p myproject -k abc123xyz --api 192.168.1.100
|
|
112
112
|
`);
|
|
113
113
|
}
|
|
114
114
|
export async function runCli(args) {
|
|
115
115
|
const cmd = args[0];
|
|
116
|
-
if (cmd === 'init') {
|
|
116
|
+
if (cmd === 'init' || cmd === 'bind') {
|
|
117
117
|
const opts = parseArgs(args.slice(1));
|
|
118
118
|
if (!opts) {
|
|
119
119
|
console.error('Error: -p (project) and -k (key) are required\n');
|
|
120
120
|
showHelp();
|
|
121
121
|
process.exit(1);
|
|
122
122
|
}
|
|
123
|
-
|
|
123
|
+
if (cmd === 'bind') {
|
|
124
|
+
bindProject(opts);
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
await initProject(opts);
|
|
128
|
+
}
|
|
124
129
|
return true;
|
|
125
130
|
}
|
|
126
131
|
if (cmd === '--help' || cmd === '-h' || cmd === 'help') {
|
|
127
132
|
showHelp();
|
|
128
133
|
return true;
|
|
129
134
|
}
|
|
130
|
-
return false;
|
|
135
|
+
return false;
|
|
131
136
|
}
|
|
132
|
-
|
|
133
|
-
|
|
137
|
+
/** 轻量绑定: 仅创建 .ppdocs, 不注册 MCP 不安装模板 */
|
|
138
|
+
function bindProject(opts) {
|
|
139
|
+
writePpdocsConfig(process.cwd(), opts);
|
|
140
|
+
console.log(`\n🔗 Project bound: ${opts.project}\n AI will auto-connect via kg_init when opening this directory.\n`);
|
|
141
|
+
}
|
|
142
|
+
/** 写入 .ppdocs 配置文件 (init + bind 复用) */
|
|
143
|
+
function writePpdocsConfig(cwd, opts) {
|
|
134
144
|
const apiUrl = `http://${opts.api}:${opts.port}/api/${opts.project}/${opts.key}`;
|
|
135
|
-
// Create .ppdocs config
|
|
136
|
-
const ppdocsConfig = {
|
|
137
|
-
api: `http://${opts.api}:${opts.port}`,
|
|
138
|
-
projectId: opts.project,
|
|
139
|
-
key: opts.key,
|
|
140
|
-
user: opts.user,
|
|
141
|
-
};
|
|
142
145
|
const ppdocsPath = path.join(cwd, '.ppdocs');
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
if (stat.isDirectory()) {
|
|
147
|
-
fs.rmSync(ppdocsPath, { recursive: true });
|
|
148
|
-
console.log(`⚠️ Removed existing .ppdocs directory`);
|
|
149
|
-
}
|
|
146
|
+
if (fs.existsSync(ppdocsPath) && fs.statSync(ppdocsPath).isDirectory()) {
|
|
147
|
+
fs.rmSync(ppdocsPath, { recursive: true });
|
|
148
|
+
console.log(`⚠️ Removed existing .ppdocs directory`);
|
|
150
149
|
}
|
|
151
|
-
fs.writeFileSync(ppdocsPath, JSON.stringify(
|
|
150
|
+
fs.writeFileSync(ppdocsPath, JSON.stringify({
|
|
151
|
+
api: `http://${opts.api}:${opts.port}`, projectId: opts.project, key: opts.key, user: opts.user,
|
|
152
|
+
}, null, 2));
|
|
152
153
|
console.log(`✅ Created ${ppdocsPath}`);
|
|
154
|
+
return apiUrl;
|
|
155
|
+
}
|
|
156
|
+
async function initProject(opts) {
|
|
157
|
+
const cwd = process.cwd();
|
|
158
|
+
const apiUrl = writePpdocsConfig(cwd, opts);
|
|
153
159
|
// Install workflow templates based on IDE detection
|
|
154
160
|
const detectedIdes = detectIDEs(cwd);
|
|
155
161
|
let hasIdeDir = false;
|
|
@@ -179,7 +185,7 @@ async function initProject(opts) {
|
|
|
179
185
|
}
|
|
180
186
|
}
|
|
181
187
|
// 自动检测并注册 MCP (如果已写入 Antigravity 配置,跳过 gemini CLI 注册避免冲突)
|
|
182
|
-
const registered = autoRegisterMcp(
|
|
188
|
+
const registered = autoRegisterMcp(detectedIdes.includes('antigravity'));
|
|
183
189
|
// 如果没有检测到任何 AI CLI,并且也没有检测到配置文件夹,创建 .mcp.json 作为备用
|
|
184
190
|
if (!registered && !hasIdeDir) {
|
|
185
191
|
createMcpJson(cwd, apiUrl, opts.user);
|
|
@@ -276,31 +282,39 @@ function execSilent(cmd) {
|
|
|
276
282
|
}
|
|
277
283
|
catch { /* ignore */ }
|
|
278
284
|
}
|
|
279
|
-
/** 自动检测 AI CLI
|
|
280
|
-
function autoRegisterMcp(
|
|
285
|
+
/** 自动检测 AI CLI 并注册全局 MCP (不注入 env,支持多项目通过 kg_init 切换) */
|
|
286
|
+
function autoRegisterMcp(skipGemini = false) {
|
|
281
287
|
const detected = [];
|
|
282
288
|
const serverName = 'ppdocs-kg';
|
|
283
|
-
const
|
|
284
|
-
// Claude CLI
|
|
289
|
+
const addCmd = `-- npx -y @ppdocs/mcp@latest`;
|
|
290
|
+
// Claude CLI (幂等:已注册则跳过)
|
|
285
291
|
if (commandExists('claude')) {
|
|
286
292
|
detected.push('Claude');
|
|
287
293
|
try {
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
294
|
+
const list = execSync(`claude mcp list`, { encoding: 'utf-8' });
|
|
295
|
+
if (list.includes(serverName)) {
|
|
296
|
+
console.log(`✅ Claude MCP already configured`);
|
|
297
|
+
}
|
|
298
|
+
else {
|
|
299
|
+
execSync(`claude mcp add ${serverName} ${addCmd}`, { stdio: 'inherit' });
|
|
300
|
+
}
|
|
292
301
|
}
|
|
293
302
|
catch {
|
|
294
|
-
|
|
303
|
+
try {
|
|
304
|
+
execSilent(`claude mcp remove ${serverName}`);
|
|
305
|
+
execSync(`claude mcp add ${serverName} ${addCmd}`, { stdio: 'inherit' });
|
|
306
|
+
}
|
|
307
|
+
catch {
|
|
308
|
+
console.log(`⚠️ Claude MCP registration failed`);
|
|
309
|
+
}
|
|
295
310
|
}
|
|
296
311
|
}
|
|
297
|
-
// Codex CLI
|
|
312
|
+
// Codex CLI
|
|
298
313
|
if (commandExists('codex')) {
|
|
299
314
|
detected.push('Codex');
|
|
300
315
|
try {
|
|
301
|
-
console.log(`✅ Detected Codex CLI, registering MCP...`);
|
|
302
316
|
execSilent(`codex mcp remove ${serverName}`);
|
|
303
|
-
execSync(`codex mcp add ${serverName} ${
|
|
317
|
+
execSync(`codex mcp add ${serverName} ${addCmd}`, { stdio: 'inherit' });
|
|
304
318
|
}
|
|
305
319
|
catch {
|
|
306
320
|
console.log(`⚠️ Codex MCP registration failed`);
|
|
@@ -310,7 +324,6 @@ function autoRegisterMcp(apiUrl, user, skipGemini = false) {
|
|
|
310
324
|
if (commandExists('gemini') && !skipGemini) {
|
|
311
325
|
detected.push('Gemini');
|
|
312
326
|
try {
|
|
313
|
-
console.log(`✅ Detected Gemini CLI, registering MCP...`);
|
|
314
327
|
execSilent(`gemini mcp remove ${serverName}`);
|
|
315
328
|
execSync(`gemini mcp add ${serverName} "npx -y @ppdocs/mcp@latest"`, { stdio: 'inherit' });
|
|
316
329
|
}
|
|
@@ -104,6 +104,33 @@ export declare class PpdocsApiClient {
|
|
|
104
104
|
analyzerImpactTree(projectPath: string, symbolName: string, depth?: number): Promise<unknown>;
|
|
105
105
|
/** 获取文件 360° 上下文 */
|
|
106
106
|
analyzerContext(projectPath: string, filePath: string): Promise<unknown>;
|
|
107
|
+
/** 智能上下文: 代码+文档+规则+任务全关联 */
|
|
108
|
+
analyzerSmartContext(projectPath: string, symbolName: string): Promise<unknown>;
|
|
109
|
+
/** 全关联路径: 两个符号之间的代码+文档路径 */
|
|
110
|
+
analyzerFullPath(projectPath: string, symbolA: string, symbolB: string, maxDepth?: number): Promise<unknown>;
|
|
111
|
+
private publicRequest;
|
|
112
|
+
/** 列出活跃讨论 (公开路由) */
|
|
113
|
+
discussionList(): Promise<unknown>;
|
|
114
|
+
/** 创建讨论 (公开路由) */
|
|
115
|
+
discussionCreate(title: string, initiator: string, participants: string[], content: string): Promise<{
|
|
116
|
+
id: string;
|
|
117
|
+
}>;
|
|
118
|
+
/** 批量读取讨论 (公开路由) */
|
|
119
|
+
discussionReadByIds(ids: string[]): Promise<unknown>;
|
|
120
|
+
/** 回复讨论 (公开路由) */
|
|
121
|
+
discussionReply(id: string, sender: string, content: string, newSummary?: string): Promise<boolean>;
|
|
122
|
+
/** 删除讨论 (公开路由) */
|
|
123
|
+
discussionDelete(id: string): Promise<boolean>;
|
|
124
|
+
/** 结案归档讨论 (认证路由) */
|
|
125
|
+
discussionClose(id: string, conclusion: string): Promise<{
|
|
126
|
+
archived_path: string;
|
|
127
|
+
}>;
|
|
128
|
+
meetingJoin(agentId: string, agentType: string): Promise<unknown>;
|
|
129
|
+
meetingLeave(agentId: string): Promise<unknown>;
|
|
130
|
+
meetingPost(agentId: string, content: string, msgType?: string): Promise<unknown>;
|
|
131
|
+
meetingClaim(agentId: string, filePath: string): Promise<unknown>;
|
|
132
|
+
meetingRelease(agentId: string, filePath: string): Promise<unknown>;
|
|
133
|
+
meetingStatus(): Promise<unknown>;
|
|
107
134
|
}
|
|
108
135
|
export declare function initClient(apiUrl: string): void;
|
|
109
136
|
export declare function getClient(): PpdocsApiClient;
|
|
@@ -509,6 +509,108 @@ export class PpdocsApiClient {
|
|
|
509
509
|
body: JSON.stringify({ project_path: projectPath, file_path: filePath }),
|
|
510
510
|
});
|
|
511
511
|
}
|
|
512
|
+
/** 智能上下文: 代码+文档+规则+任务全关联 */
|
|
513
|
+
async analyzerSmartContext(projectPath, symbolName) {
|
|
514
|
+
return this.request('/analyzer/smart-context', {
|
|
515
|
+
method: 'POST',
|
|
516
|
+
body: JSON.stringify({ project_path: projectPath, symbol_name: symbolName }),
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
/** 全关联路径: 两个符号之间的代码+文档路径 */
|
|
520
|
+
async analyzerFullPath(projectPath, symbolA, symbolB, maxDepth = 3) {
|
|
521
|
+
return this.request('/analyzer/full-path', {
|
|
522
|
+
method: 'POST',
|
|
523
|
+
body: JSON.stringify({ project_path: projectPath, symbol_a: symbolA, symbol_b: symbolB, max_depth: maxDepth }),
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
// ============ 公开路由请求工具 (无需项目认证) ============
|
|
527
|
+
async publicRequest(path, options) {
|
|
528
|
+
const response = await fetch(`${this.serverUrl}${path}`, {
|
|
529
|
+
headers: { 'Content-Type': 'application/json' },
|
|
530
|
+
...options,
|
|
531
|
+
});
|
|
532
|
+
if (!response.ok) {
|
|
533
|
+
const error = await response.json().catch(() => ({ error: response.statusText }));
|
|
534
|
+
throw new Error(error.error || `HTTP ${response.status}`);
|
|
535
|
+
}
|
|
536
|
+
const json = await response.json();
|
|
537
|
+
if (!json.success)
|
|
538
|
+
throw new Error(json.error || 'Unknown error');
|
|
539
|
+
return json.data;
|
|
540
|
+
}
|
|
541
|
+
// ============ 讨论系统 ============
|
|
542
|
+
/** 列出活跃讨论 (公开路由) */
|
|
543
|
+
async discussionList() {
|
|
544
|
+
return this.publicRequest('/api/discussions');
|
|
545
|
+
}
|
|
546
|
+
/** 创建讨论 (公开路由) */
|
|
547
|
+
async discussionCreate(title, initiator, participants, content) {
|
|
548
|
+
return this.publicRequest('/api/discussions', {
|
|
549
|
+
method: 'POST',
|
|
550
|
+
body: JSON.stringify({ title, initiator, participants, content }),
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
/** 批量读取讨论 (公开路由) */
|
|
554
|
+
async discussionReadByIds(ids) {
|
|
555
|
+
return this.publicRequest('/api/discussions/read', {
|
|
556
|
+
method: 'POST',
|
|
557
|
+
body: JSON.stringify({ ids }),
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
/** 回复讨论 (公开路由) */
|
|
561
|
+
async discussionReply(id, sender, content, newSummary) {
|
|
562
|
+
return this.publicRequest(`/api/discussions/${encodeURIComponent(id)}/reply`, {
|
|
563
|
+
method: 'POST',
|
|
564
|
+
body: JSON.stringify({ sender, content, new_summary: newSummary }),
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
/** 删除讨论 (公开路由) */
|
|
568
|
+
async discussionDelete(id) {
|
|
569
|
+
return this.publicRequest(`/api/discussions/${encodeURIComponent(id)}`, {
|
|
570
|
+
method: 'DELETE',
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
/** 结案归档讨论 (认证路由) */
|
|
574
|
+
async discussionClose(id, conclusion) {
|
|
575
|
+
return this.request(`/discussions/${encodeURIComponent(id)}/close`, {
|
|
576
|
+
method: 'POST',
|
|
577
|
+
body: JSON.stringify({ conclusion }),
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
// ============ 多AI协作会议 ============
|
|
581
|
+
async meetingJoin(agentId, agentType) {
|
|
582
|
+
return this.request('/meeting/join', {
|
|
583
|
+
method: 'POST',
|
|
584
|
+
body: JSON.stringify({ agent_id: agentId, agent_type: agentType }),
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
async meetingLeave(agentId) {
|
|
588
|
+
return this.request('/meeting/leave', {
|
|
589
|
+
method: 'POST',
|
|
590
|
+
body: JSON.stringify({ agent_id: agentId }),
|
|
591
|
+
});
|
|
592
|
+
}
|
|
593
|
+
async meetingPost(agentId, content, msgType = 'status') {
|
|
594
|
+
return this.request('/meeting/post', {
|
|
595
|
+
method: 'POST',
|
|
596
|
+
body: JSON.stringify({ agent_id: agentId, content, msg_type: msgType }),
|
|
597
|
+
});
|
|
598
|
+
}
|
|
599
|
+
async meetingClaim(agentId, filePath) {
|
|
600
|
+
return this.request('/meeting/claim', {
|
|
601
|
+
method: 'POST',
|
|
602
|
+
body: JSON.stringify({ agent_id: agentId, file_path: filePath }),
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
async meetingRelease(agentId, filePath) {
|
|
606
|
+
return this.request('/meeting/release', {
|
|
607
|
+
method: 'POST',
|
|
608
|
+
body: JSON.stringify({ agent_id: agentId, file_path: filePath }),
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
async meetingStatus() {
|
|
612
|
+
return this.request('/meeting/status');
|
|
613
|
+
}
|
|
512
614
|
}
|
|
513
615
|
// ============ 模块级 API ============
|
|
514
616
|
let client = null;
|
package/dist/tools/analyzer.d.ts
CHANGED
|
@@ -5,4 +5,5 @@
|
|
|
5
5
|
* 让 AI Agent 通过 MCP 工具直接查询代码结构、依赖关系和影响范围
|
|
6
6
|
*/
|
|
7
7
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
8
|
-
|
|
8
|
+
import { type McpContext } from './shared.js';
|
|
9
|
+
export declare function registerAnalyzerTools(server: McpServer, ctx: McpContext): void;
|
package/dist/tools/analyzer.js
CHANGED
|
@@ -16,7 +16,7 @@ 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 前必须先执行★', {
|
|
@@ -94,19 +94,19 @@ export function registerAnalyzerTools(server, projectId) {
|
|
|
94
94
|
projectPath: z.string().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
|
}
|
|
@@ -260,4 +260,117 @@ export function registerAnalyzerTools(server, projectId) {
|
|
|
260
260
|
}
|
|
261
261
|
return wrap(lines.join('\n'));
|
|
262
262
|
}));
|
|
263
|
+
// ===== code_smart_context: 代码+文档全关联上下文 =====
|
|
264
|
+
server.tool('code_smart_context', '🔍 代码+文档全关联上下文 — 输入一个函数名,一次调用返回:代码依赖、关联文档、匹配规则、活跃任务、影响范围摘要。需先运行 code_scan', {
|
|
265
|
+
projectPath: z.string().describe('项目源码的绝对路径'),
|
|
266
|
+
symbolName: z.string().describe('要查询的符号名称(如"handleLogin", "AuthService")'),
|
|
267
|
+
}, async (args) => safeTool(async () => {
|
|
268
|
+
const smartCtx = await client().analyzerSmartContext(args.projectPath, args.symbolName);
|
|
269
|
+
if (!smartCtx) {
|
|
270
|
+
return wrap(`未找到符号 "${args.symbolName}"。请确认名称正确且已运行 code_scan`);
|
|
271
|
+
}
|
|
272
|
+
const lines = [
|
|
273
|
+
`🔍 智能上下文: ${smartCtx.symbol.name}`,
|
|
274
|
+
``,
|
|
275
|
+
];
|
|
276
|
+
// 符号信息
|
|
277
|
+
const icon = SYMBOL_ICONS[smartCtx.symbol.kind] || '?';
|
|
278
|
+
const exp = smartCtx.symbol.exported ? ' [export]' : '';
|
|
279
|
+
lines.push(`### ${icon} 符号信息`);
|
|
280
|
+
lines.push(` ${smartCtx.symbol.name}${exp} (${smartCtx.symbol.kind})`);
|
|
281
|
+
lines.push(` 📍 ${smartCtx.symbol.filePath}:${smartCtx.symbol.lineStart}-${smartCtx.symbol.lineEnd}`);
|
|
282
|
+
if (smartCtx.symbol.signature)
|
|
283
|
+
lines.push(` 📝 ${smartCtx.symbol.signature}`);
|
|
284
|
+
lines.push('');
|
|
285
|
+
// 文件上下文
|
|
286
|
+
lines.push(`### 📄 文件上下文: ${smartCtx.fileContext.filePath}`);
|
|
287
|
+
lines.push(` 语言: ${smartCtx.fileContext.language} | ${smartCtx.fileContext.linesTotal} 行`);
|
|
288
|
+
lines.push(` 定义: ${smartCtx.fileContext.symbolsDefined.length} 个符号 | 导入: ${smartCtx.fileContext.imports.length} | 被引用: ${smartCtx.fileContext.importedBy.length}`);
|
|
289
|
+
lines.push('');
|
|
290
|
+
// 影响范围
|
|
291
|
+
if (smartCtx.impactSummary.totalFiles > 0) {
|
|
292
|
+
lines.push(`### 💥 影响范围: ${smartCtx.impactSummary.totalFiles} 个文件`);
|
|
293
|
+
for (const f of smartCtx.impactSummary.l1Files.slice(0, 10)) {
|
|
294
|
+
lines.push(` 🔴 ${f}`);
|
|
295
|
+
}
|
|
296
|
+
lines.push('');
|
|
297
|
+
}
|
|
298
|
+
// 关联文档
|
|
299
|
+
if (smartCtx.relatedDocs.length > 0) {
|
|
300
|
+
lines.push(`### 📚 关联知识文档 (${smartCtx.relatedDocs.length})`);
|
|
301
|
+
for (const doc of smartCtx.relatedDocs) {
|
|
302
|
+
lines.push(` 📄 ${doc.path} — ${doc.summary}`);
|
|
303
|
+
}
|
|
304
|
+
lines.push('');
|
|
305
|
+
}
|
|
306
|
+
// 匹配规则
|
|
307
|
+
if (smartCtx.matchedRules.length > 0) {
|
|
308
|
+
lines.push(`### 📏 匹配规则 (${smartCtx.matchedRules.length})`);
|
|
309
|
+
for (const rule of smartCtx.matchedRules) {
|
|
310
|
+
lines.push(` [${rule.ruleType}] ${rule.content.substring(0, 100)}`);
|
|
311
|
+
}
|
|
312
|
+
lines.push('');
|
|
313
|
+
}
|
|
314
|
+
// 活跃任务
|
|
315
|
+
if (smartCtx.activeTasks.length > 0) {
|
|
316
|
+
lines.push(`### 📝 活跃任务 (${smartCtx.activeTasks.length})`);
|
|
317
|
+
for (const task of smartCtx.activeTasks) {
|
|
318
|
+
lines.push(` 🔵 [${task.id}] ${task.title}`);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
return wrap(lines.join('\n'));
|
|
322
|
+
}));
|
|
323
|
+
// ===== code_full_path: 全关联路径 =====
|
|
324
|
+
server.tool('code_full_path', '🔗 全关联路径 — 两个符号之间不仅返回代码引用链路,还返回共享的KG文档、共同导入、祖先模块。需先运行 code_scan', {
|
|
325
|
+
projectPath: z.string().describe('项目源码的绝对路径'),
|
|
326
|
+
symbolA: z.string().describe('起点符号名(如 "handleLogin")'),
|
|
327
|
+
symbolB: z.string().describe('终点符号名(如 "AuthService")'),
|
|
328
|
+
maxDepth: z.number().optional().describe('最大搜索深度(1-5, 默认3)'),
|
|
329
|
+
}, async (args) => safeTool(async () => {
|
|
330
|
+
const result = await client().analyzerFullPath(args.projectPath, args.symbolA, args.symbolB, args.maxDepth ?? 3);
|
|
331
|
+
if (!result) {
|
|
332
|
+
return wrap(`❌ 无法构建路径。请确认两个符号名称正确且已运行 code_scan`);
|
|
333
|
+
}
|
|
334
|
+
const lines = [
|
|
335
|
+
`🔗 全关联路径`,
|
|
336
|
+
``,
|
|
337
|
+
`📍 ${result.symbolA.name} (${result.symbolA.kind}) → ${result.symbolA.filePath}:${result.symbolA.lineStart}`,
|
|
338
|
+
`📍 ${result.symbolB.name} (${result.symbolB.kind}) → ${result.symbolB.filePath}:${result.symbolB.lineStart}`,
|
|
339
|
+
``,
|
|
340
|
+
`📊 ${result.pathSummary}`,
|
|
341
|
+
``,
|
|
342
|
+
];
|
|
343
|
+
// 代码路径
|
|
344
|
+
if (result.codePath.length > 0) {
|
|
345
|
+
lines.push(`### 🔗 代码引用链路 (${result.codePath.length} 步)`);
|
|
346
|
+
for (const step of result.codePath.slice(0, 20)) {
|
|
347
|
+
lines.push(` L${step.depth} ${step.filePath} [${step.refKind}] ← ${step.symbolName}`);
|
|
348
|
+
}
|
|
349
|
+
lines.push('');
|
|
350
|
+
}
|
|
351
|
+
// 共享文档
|
|
352
|
+
if (result.sharedDocs.length > 0) {
|
|
353
|
+
lines.push(`### 📚 共享KG文档 (${result.sharedDocs.length})`);
|
|
354
|
+
for (const doc of result.sharedDocs) {
|
|
355
|
+
lines.push(` 📄 ${doc.path} — ${doc.summary}`);
|
|
356
|
+
}
|
|
357
|
+
lines.push('');
|
|
358
|
+
}
|
|
359
|
+
// 共同导入
|
|
360
|
+
if (result.commonImports.length > 0) {
|
|
361
|
+
lines.push(`### 📥 共同导入 (${result.commonImports.length})`);
|
|
362
|
+
for (const imp of result.commonImports) {
|
|
363
|
+
lines.push(` → ${imp}`);
|
|
364
|
+
}
|
|
365
|
+
lines.push('');
|
|
366
|
+
}
|
|
367
|
+
// 共同祖先
|
|
368
|
+
if (result.commonAncestors.length > 0) {
|
|
369
|
+
lines.push(`### 📁 共同祖先目录`);
|
|
370
|
+
for (const anc of result.commonAncestors) {
|
|
371
|
+
lines.push(` 📂 ${anc}`);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
return wrap(lines.join('\n'));
|
|
375
|
+
}));
|
|
263
376
|
}
|
|
@@ -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';
|