@ppdocs/mcp 3.2.19 → 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.
@@ -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;
@@ -5,4 +5,5 @@
5
5
  * 让 AI Agent 通过 MCP 工具直接查询代码结构、依赖关系和影响范围
6
6
  */
7
7
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
8
- export declare function registerAnalyzerTools(server: McpServer, projectId: string): void;
8
+ import { type McpContext } from './shared.js';
9
+ export declare function registerAnalyzerTools(server: McpServer, ctx: McpContext): void;
@@ -16,7 +16,7 @@ const SYMBOL_ICONS = {
16
16
  const SEVERITY_ICONS = {
17
17
  critical: '🔴', warning: '🟡', info: '🟢',
18
18
  };
19
- export function registerAnalyzerTools(server, projectId) {
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 ctx = await client().analyzerContext(args.projectPath, args.filePath);
98
- if (!ctx) {
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
- `📄 ${ctx.filePath}`,
103
- `语言: ${ctx.language} | ${ctx.linesTotal} 行`,
102
+ `📄 ${fileCtx.filePath}`,
103
+ `语言: ${fileCtx.language} | ${fileCtx.linesTotal} 行`,
104
104
  ``,
105
105
  ];
106
106
  // 定义的符号
107
- if (ctx.symbolsDefined.length > 0) {
108
- lines.push(`### 🔤 定义的符号 (${ctx.symbolsDefined.length})`);
109
- for (const sym of ctx.symbolsDefined) {
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 (ctx.imports.length > 0) {
118
- lines.push(`### 📥 导入 (${ctx.imports.length})`);
119
- for (const imp of ctx.imports) {
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 (ctx.importedBy.length > 0) {
127
- lines.push(`### 📤 被引用 (${ctx.importedBy.length})`);
128
- for (const by of ctx.importedBy) {
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
- * 合并: kg_discussion_list, kg_discussion_read, kg_discussion_create,
4
- * kg_discussion_reply, kg_discussion_close_and_archive, kg_discussion_delete
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';
@@ -1,13 +1,61 @@
1
1
  /**
2
2
  * 💬 kg_discuss (6→1)
3
- * 合并: kg_discussion_list, kg_discussion_read, kg_discussion_create,
4
- * kg_discussion_reply, kg_discussion_close_and_archive, kg_discussion_delete
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
- import { DiscussionManager } from '../storage/discussion.js';
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 formatDetail(d) {
45
+ const msgs = d.messages || [];
46
+ const lines = [
47
+ `💬 ${d.title}`,
48
+ `发起: ${d.initiator} | 参与: ${d.participants.length}方 | 状态: ${d.status}`,
49
+ `📌 ${d.summary}`,
50
+ ``,
51
+ ];
52
+ msgs.forEach((m, i) => {
53
+ lines.push(`--- 消息 ${i + 1}/${msgs.length} [${m.sender}] ${relativeTime(m.timestamp)} ---`);
54
+ lines.push(m.content);
55
+ lines.push('');
56
+ });
57
+ return lines.join('\n');
58
+ }
11
59
  export function registerDiscussionTools(server, ctx) {
12
60
  const client = () => getClient();
13
61
  server.tool('kg_discuss', '💬 跨项目讨论 — 发起、回复、归档协同讨论。action: list(列出活跃讨论)|read(读取详情)|create(发起)|reply(回复)|close(结案归档)|delete(删除)', {
@@ -29,23 +77,22 @@ export function registerDiscussionTools(server, ctx) {
29
77
  .describe('更新进展摘要 (reply)'),
30
78
  }, async (args) => safeTool(async () => {
31
79
  const decoded = decodeObjectStrings(args);
80
+ const me = sender(ctx);
32
81
  switch (decoded.action) {
33
82
  case 'list': {
34
- const cleaned = DiscussionManager.cleanExpired(7);
35
- const active = DiscussionManager.listActive();
36
- const cleanMsg = cleaned > 0 ? `\n⚠️ 已自动清理 ${cleaned} 条超过7天不活跃的讨论` : '';
37
- if (active.length === 0)
38
- return wrap(`当前无活跃的讨论 (本项目ID: ${ctx.projectId})${cleanMsg}`);
39
- return wrap(`本项目ID: ${ctx.projectId}\n活跃讨论区 (${active.length} 个):${cleanMsg}\n\n` + JSON.stringify(active, null, 2));
83
+ const active = await client().discussionList();
84
+ if (!Array.isArray(active) || active.length === 0)
85
+ return wrap(`当前无活跃的讨论 (本项目: ${me})`);
86
+ return wrap(formatList(active, ctx));
40
87
  }
41
88
  case 'read': {
42
89
  const readIds = decoded.ids || (decoded.id ? [decoded.id] : []);
43
90
  if (readIds.length === 0)
44
91
  return wrap('❌ read 需要 id 或 ids');
45
- const discussions = DiscussionManager.readByIds(readIds);
46
- if (discussions.length === 0)
92
+ const discussions = await client().discussionReadByIds(readIds);
93
+ if (!Array.isArray(discussions) || discussions.length === 0)
47
94
  return wrap('未找到对应的讨论记录');
48
- return wrap(JSON.stringify(discussions, null, 2));
95
+ return wrap(discussions.map(formatDetail).join('\n\n━━━━━━━━━━━━━━━━━━━━\n\n'));
49
96
  }
50
97
  case 'create': {
51
98
  if (!decoded.title)
@@ -54,55 +101,30 @@ export function registerDiscussionTools(server, ctx) {
54
101
  return wrap('❌ create 需要 participants');
55
102
  if (!decoded.content)
56
103
  return wrap('❌ create 需要 content');
57
- const count = DiscussionManager.activeCount();
58
- if (count >= 10)
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(', ')}`);
104
+ const result = await client().discussionCreate(decoded.title, me, decoded.participants, decoded.content);
105
+ return wrap(`✅ 讨论已发起\nID: ${result.id}\n发起方: ${me}\n参与方: ${decoded.participants.join(', ')}`);
62
106
  }
63
107
  case 'reply': {
64
108
  if (!decoded.id)
65
109
  return wrap('❌ reply 需要 id');
66
110
  if (!decoded.content)
67
111
  return wrap('❌ reply 需要 content');
68
- const success = DiscussionManager.reply(decoded.id, ctx.projectId, decoded.content, decoded.newSummary);
69
- return wrap(success ? `✅ 回复成功 (ID: ${decoded.id})` : `❌ 回复失败,讨论不存在或已关闭`);
112
+ await client().discussionReply(decoded.id, me, decoded.content, decoded.newSummary);
113
+ return wrap(`✅ 回复成功 (ID: ${decoded.id}, 身份: ${me})`);
70
114
  }
71
115
  case 'close': {
72
116
  if (!decoded.id)
73
117
  return wrap('❌ close 需要 id');
74
118
  if (!decoded.conclusion)
75
119
  return wrap('❌ close 需要 conclusion');
76
- const topic = DiscussionManager.getAndRemove(decoded.id);
77
- if (!topic)
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
- }
120
+ const result = await client().discussionClose(decoded.id, decoded.conclusion);
121
+ return wrap(`✅ 讨论已结案并归档: \`${result.archived_path}\``);
100
122
  }
101
123
  case 'delete': {
102
124
  if (!decoded.id)
103
125
  return wrap('❌ delete 需要 id');
104
- const success = DiscussionManager.delete(decoded.id);
105
- return wrap(success ? `✅ 讨论已删除 (ID: ${decoded.id})` : `❌ 讨论不存在或已被删除`);
126
+ await client().discussionDelete(decoded.id);
127
+ return wrap(`✅ 讨论已删除 (ID: ${decoded.id})`);
106
128
  }
107
129
  default:
108
130
  return wrap(`❌ 未知 action: ${decoded.action}`);
@@ -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
- export declare function registerDocTools(server: McpServer, projectId: string): void;
7
+ import { type McpContext } from './shared.js';
8
+ export declare function registerDocTools(server: McpServer, ctx: McpContext): void;
@@ -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, projectId) {
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(跨项目复制)', {
@@ -1,12 +1,13 @@
1
1
  /**
2
2
  * MCP 工具注册入口
3
- * 精简后: 14 个工具, 6 个子模块
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 (5个)
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;
@@ -1,12 +1,13 @@
1
1
  /**
2
2
  * MCP 工具注册入口
3
- * 精简后: 14 个工具, 6 个子模块
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 (5个)
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 { createContext } from './shared.js';
12
13
  import { registerInitTool } from './init.js';
@@ -18,6 +19,7 @@ import { registerTaskTools } from './tasks.js';
18
19
  import { registerFileTools } from './files.js';
19
20
  import { registerDiscussionTools } from './discussion.js';
20
21
  import { registerAnalyzerTools } from './analyzer.js';
22
+ import { registerMeetingTools } from './meeting.js';
21
23
  export function registerTools(server, projectId, user, onProjectChange) {
22
24
  // 创建共享可变上下文 — 所有工具捕获此对象引用
23
25
  const ctx = createContext(projectId, user);
@@ -26,13 +28,15 @@ export function registerTools(server, projectId, user, onProjectChange) {
26
28
  // 📊 导航 (kg_status + kg_tree在docs中)
27
29
  registerStatusTool(server, ctx);
28
30
  // 📚 知识 (kg_doc + kg_tree + kg_projects + kg_rules)
29
- registerDocTools(server, ctx.projectId);
31
+ registerDocTools(server, ctx);
30
32
  registerProjectTools(server, ctx);
31
33
  registerRuleTools(server, ctx);
32
34
  // 📝 工作流 (kg_task + kg_files + kg_discuss)
33
- registerTaskTools(server, ctx.projectId, ctx.user);
35
+ registerTaskTools(server, ctx);
34
36
  registerFileTools(server);
35
37
  registerDiscussionTools(server, ctx);
36
- // 🔬 代码分析 (不变)
37
- registerAnalyzerTools(server, ctx.projectId);
38
+ // 🔬 代码分析 (code_scan, code_query, code_impact, code_context, code_path, code_smart_context, code_full_path)
39
+ registerAnalyzerTools(server, ctx);
40
+ // 🏛️ 多AI协作 (kg_meeting)
41
+ registerMeetingTools(server, ctx);
38
42
  }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * 多AI协作会议工具
3
+ * kg_meeting: join | leave | post | claim | release | status
4
+ */
5
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
6
+ import { type McpContext } from './shared.js';
7
+ export declare function registerMeetingTools(server: McpServer, ctx: McpContext): void;
@@ -0,0 +1,97 @@
1
+ /**
2
+ * 多AI协作会议工具
3
+ * kg_meeting: join | leave | post | claim | release | status
4
+ */
5
+ import { z } from 'zod';
6
+ import { getClient } from '../storage/httpClient.js';
7
+ import { wrap, safeTool } from './shared.js';
8
+ function formatStatus(status) {
9
+ const lines = [
10
+ `🏛️ 会议室: ${status.projectId}`,
11
+ ``,
12
+ ];
13
+ // 参与者
14
+ if (status.agents.length > 0) {
15
+ lines.push(`### 👥 参与者 (${status.agents.length})`);
16
+ for (const agent of status.agents) {
17
+ const ago = Math.floor((Date.now() / 1000 - agent.lastHeartbeat) / 60);
18
+ lines.push(` 🤖 ${agent.agentId} (${agent.agentType}) — 心跳 ${ago}分钟前`);
19
+ }
20
+ lines.push('');
21
+ }
22
+ else {
23
+ lines.push('📭 会议室为空');
24
+ lines.push('');
25
+ }
26
+ // 文件锁
27
+ const locks = Object.entries(status.fileLocks);
28
+ if (locks.length > 0) {
29
+ lines.push(`### 🔒 文件锁定 (${locks.length})`);
30
+ for (const [file, owner] of locks) {
31
+ lines.push(` 📄 ${file} ← ${owner}`);
32
+ }
33
+ lines.push('');
34
+ }
35
+ // 最近消息
36
+ if (status.recentMessages.length > 0) {
37
+ lines.push(`### 💬 最近消息 (${status.recentMessages.length})`);
38
+ for (const msg of status.recentMessages.slice(-10)) {
39
+ const time = new Date(msg.timestamp * 1000).toLocaleTimeString();
40
+ const icon = msg.msgType === 'decision' ? '⚡' : msg.msgType === 'question' ? '❓' : '💬';
41
+ lines.push(` ${icon} [${time}] ${msg.agentId}: ${msg.content}`);
42
+ }
43
+ }
44
+ return lines.join('\n');
45
+ }
46
+ export function registerMeetingTools(server, ctx) {
47
+ const client = () => getClient();
48
+ server.tool('kg_meeting', '🏛️ 多AI协作会议 — 多个AI编辑器同时操作同一项目时,通过"会议室"共享状态、认领文件、发布决策。action: join(加入)|leave(离开)|post(发消息)|claim(认领文件)|release(释放文件)|status(查看状态)', {
49
+ action: z.enum(['join', 'leave', 'post', 'claim', 'release', 'status']).describe('操作类型'),
50
+ agentId: z.string().optional().describe('Agent标识(如"cursor-1", "claude-code-2")。join/leave/post/claim/release 必填'),
51
+ agentType: z.string().optional().describe('Agent类型(如"cursor", "claude", "kiro")。join 时使用'),
52
+ content: z.string().optional().describe('消息内容 (post 时必填)'),
53
+ msgType: z.enum(['status', 'decision', 'question']).optional().describe('消息类型 (post, 默认status)'),
54
+ filePath: z.string().optional().describe('文件路径 (claim/release 时必填)'),
55
+ }, async (args) => safeTool(async () => {
56
+ switch (args.action) {
57
+ case 'join': {
58
+ if (!args.agentId)
59
+ return wrap('❌ join 需要 agentId');
60
+ const status = await client().meetingJoin(args.agentId, args.agentType || 'unknown');
61
+ return wrap(`✅ ${args.agentId} 已加入会议\n\n${formatStatus(status)}`);
62
+ }
63
+ case 'leave': {
64
+ if (!args.agentId)
65
+ return wrap('❌ leave 需要 agentId');
66
+ const status = await client().meetingLeave(args.agentId);
67
+ return wrap(`✅ ${args.agentId} 已离开会议\n\n${formatStatus(status)}`);
68
+ }
69
+ case 'post': {
70
+ if (!args.agentId || !args.content)
71
+ return wrap('❌ post 需要 agentId 和 content');
72
+ const status = await client().meetingPost(args.agentId, args.content, args.msgType || 'status');
73
+ return wrap(`✅ 消息已发送\n\n${formatStatus(status)}`);
74
+ }
75
+ case 'claim': {
76
+ if (!args.agentId || !args.filePath)
77
+ return wrap('❌ claim 需要 agentId 和 filePath');
78
+ const status = await client().meetingClaim(args.agentId, args.filePath);
79
+ return wrap(`✅ ${args.agentId} 已认领 ${args.filePath}\n\n${formatStatus(status)}`);
80
+ }
81
+ case 'release': {
82
+ if (!args.agentId || !args.filePath)
83
+ return wrap('❌ release 需要 agentId 和 filePath');
84
+ const status = await client().meetingRelease(args.agentId, args.filePath);
85
+ return wrap(`✅ ${args.agentId} 已释放 ${args.filePath}\n\n${formatStatus(status)}`);
86
+ }
87
+ case 'status': {
88
+ const status = await client().meetingStatus();
89
+ if (!status)
90
+ return wrap('📭 该项目暂无活跃会议');
91
+ return wrap(formatStatus(status));
92
+ }
93
+ default:
94
+ return wrap(`❌ 未知 action: ${args.action}`);
95
+ }
96
+ }));
97
+ }
@@ -3,4 +3,5 @@
3
3
  * 合并: task_create, task_get, task_update, task_archive
4
4
  */
5
5
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
6
- export declare function registerTaskTools(server: McpServer, projectId: string, user: string): void;
6
+ import { type McpContext } from './shared.js';
7
+ export declare function registerTaskTools(server: McpServer, ctx: McpContext): void;
@@ -6,7 +6,7 @@ import { z } from 'zod';
6
6
  import { getClient } from '../storage/httpClient.js';
7
7
  import { decodeObjectStrings } from '../utils.js';
8
8
  import { wrap, safeTool } from './shared.js';
9
- export function registerTaskTools(server, projectId, user) {
9
+ export function registerTaskTools(server, ctx) {
10
10
  const client = () => getClient();
11
11
  server.tool('kg_task', '📝 任务管理 — 创建、查询、更新进展、归档。action: create(创建)|get(查询)|update(添加日志)|archive(归档)', {
12
12
  action: z.enum(['create', 'get', 'update', 'archive'])
@@ -43,7 +43,7 @@ export function registerTaskTools(server, projectId, user) {
43
43
  title: decoded.title,
44
44
  description: decoded.description,
45
45
  goals: decoded.goals || []
46
- }, user);
46
+ }, ctx.user);
47
47
  return wrap(JSON.stringify(task, null, 2));
48
48
  }
49
49
  case 'get': {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ppdocs/mcp",
3
- "version": "3.2.19",
3
+ "version": "3.2.20",
4
4
  "description": "ppdocs MCP Server - Knowledge Graph for Claude",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -0,0 +1,87 @@
1
+ # 🛡️ "Sentinel-4" 多智能体审计协议 任务成果审查或代码审查
2
+
3
+ 本工作流启用**4个独立智能体**并行工作,对任务成果进行“死刑级”严格审查。
4
+
5
+ ---
6
+
7
+ ### 1.智能体部署 (Agent Deployment) 🤖
8
+ 四个专家智能体同时介入,各司其职:
9
+
10
+ #### 🕵️ 智能体 A:逻辑审计官 (Logic Auditor)
11
+ * **职责**:对照预期目标。
12
+ * **检查点**:
13
+ * 🔹 流程是否完全闭环?
14
+ * 🔹 是否存在逻辑断层?
15
+
16
+ #### 🧹 智能体 B:代码清洁工 (Code Janitor)
17
+ * **职责**:通过静态分析寻找“垃圾”。
18
+ * **检查点**:
19
+ * × **死代码 (Dead Code)**:检测未被调用的函数/变量。
20
+ * × **新旧混合**:确保没有注释掉的旧逻辑残留。
21
+ * × **冗余**:消除任何重复逻辑。
22
+
23
+ #### ⚡ 智能体 C:极简架构师 (Minimalist Architect)
24
+ * **职责**:强制执行“单行原则”与模块化。
25
+ * **检查点**:
26
+ * 🔹 **解耦**:模块之间是否彻底分离?
27
+ * 🔹 **行数**:两行能写完的,是否用了三行?(臃肿判定)
28
+
29
+ #### 📚 智能体 D:知识图谱守护者 (Graph Keeper)
30
+ * **职责**:真理一致性校验。
31
+ * **检查点**:
32
+ * × **偏差**:代码逻辑是否违背了知识图谱中的标准定义?
33
+
34
+ ---
35
+
36
+ ### 2.综合裁决 (Synthesis & Verdict) ⚖️
37
+ * **汇总**:收集 4 个智能体的 Flag(报错)。
38
+ * **判定**:
39
+ * 只要有 **1个** 智能体亮红灯 🔴 -> **驳回重构**。
40
+ * 全员亮绿灯 🟢 -> **生成最终报告**。
41
+
42
+ ---
43
+
44
+ ### 3.最终交付:审查报告 (Final Report) 📝
45
+ 报告将以“简单自然语言 + 可视化图标”输出:
46
+ * **状态**:✅ 通过 / ❌ 驳回
47
+ * **评分**:代码纯净度 (0-100%)
48
+ * **风险点**:(如果有)
49
+ * **改进建议**:(针对性优化)
50
+
51
+ ---
52
+
53
+ ### 📊 审计流程可视化图解 (ASCII)
54
+
55
+ ```text
56
+ +----------------------+
57
+ | 📦 提交任务成果 |
58
+ +----------+-----------+
59
+ |
60
+ v
61
+ +----------+-----------+
62
+ | ⚡ 并行分发 |
63
+ +----------+-----------+
64
+ / / | \ \
65
+ v v v v v
66
+ +--------+ +--------+ +--------+ +--------+
67
+ | 🕵️ A | | 🧹 B | | ⚡ C | | 📚 D |
68
+ | 逻辑 | | 代码 | | 极简 | | 图谱 |
69
+ +--------+ +--------+ +--------+ +--------+
70
+ | | | |
71
+ +--------+--------+--------+
72
+ |
73
+ v
74
+ +--------+--------+
75
+ | ⚖️ 综合裁决 |
76
+ +--------+--------+
77
+ |
78
+ /-------+-------\
79
+ / 发现问题? \
80
+ v v
81
+ +-----------+ +------------+
82
+ | ❌ 驳回 | | 📝 生成报告 |
83
+ | (Reject) | | (Success) |
84
+ +-----------+ +------------+
85
+ |
86
+ v
87
+ (To: Iteration Loop)
@@ -0,0 +1,82 @@
1
+ # 🔍 "Deep-Trace" Diagnostic Protocol (深层溯源诊断协议)
2
+
3
+ 核心理念:**先在宏观地图(图谱)上定位,再在微观战场(代码)上排查,绝不盲目翻看代码。**
4
+
5
+ ---
6
+
7
+ ### 1.第一阶段:知识锚定 (Knowledge Anchoring) ⚓️
8
+ 🔹 **核心目标**:在动手之前,通过 PPDocs 明确“正确应该是什么样”。
9
+ ✏️ **动作**:
10
+ * **查询图谱**:输入问题关键词,提取标准业务逻辑。
11
+ * **建立基准**:以图谱中的描述作为“真理标准 (Truth)”。
12
+ × **禁区**:
13
+ * 禁止在未查询图谱前,直接跳入代码库阅读(防止陷入细节泥潭)。
14
+
15
+ ### 2.第二阶段:抽象映射 (Abstract Mapping) 🗺️
16
+ 🔹 **核心目标**:利用链式思维 (CoT),绘制高层级逻辑流。
17
+ ✏️ **动作**:
18
+ * **绘制流程**:忽略实现细节,仅画出模块间的输入/输出流转(数据流)。
19
+ * **可视化**:生成 ASCII 或 Mermaid 流程图,展示当前链路。
20
+ * **标记疑点**:在流程图上圈出逻辑可能断裂的节点。
21
+
22
+ ### 3.第三阶段:靶点锁定 (Target Locking) 🎯
23
+ 🔹 **核心目标**:将问题范围从“整个项目”缩小到“单一节点”。
24
+ ✏️ **动作**:
25
+ * **定位区域**:确定问题具体发生在 PPDocs 的哪个 `Node`(节点)或 `Edge`(连线)上。
26
+ * **隔离上下文**:只关注该节点的前置依赖(Pre-requisites)和后置影响(Post-effects)。
27
+
28
+ ### 4.第四阶段:双层取证 (Dual-Layer Forensics) 🕵️
29
+ 🔹 **核心目标**:对比“理论(图谱)”与“现实(代码)”的差异。
30
+ ✏️ **动作**:
31
+ * **左眼看图谱**:阅读 PPDocs 中该节点的逻辑定义。
32
+ * **右眼看代码**:审查该节点对应的实际代码实现。
33
+ * **寻找裂痕**:
34
+ * 🔴 **代码错误 (Bug)**:图谱逻辑正确,代码实现错误。
35
+ * 🟠 **设计缺陷 (Design Flaw)**:代码符合图谱,但结果依然错误(图谱本身逻辑有误)。
36
+ * 🟡 **文档脱节 (Sync Issue)**:代码逻辑正确且有效,但图谱未记载(需同步)。
37
+
38
+ ### 5.第五阶段:分析结案 (Verdict) 📝
39
+ 🔹 **核心目标**:输出可执行的诊断报告。
40
+ ✏️ **动作**:
41
+ * 输出标准化的**《问题诊断报告》**。
42
+ * **流转**:将报告作为输入,传递给 **[任务方案生成工作流]** 进行修复。
43
+
44
+ ---
45
+
46
+ ### 📊 诊断逻辑可视化图解 (ASCII)
47
+
48
+ ```text
49
+ +-------------------------+
50
+ | 🚨 问题触发 (Issue) |
51
+ +------------+------------+
52
+ |
53
+ v
54
+ +------------+------------+
55
+ | ⚓️ 知识锚定 (PPDocs) | <--- 获取标准逻辑 (Truth)
56
+ +------------+------------+
57
+ |
58
+ v
59
+ [ 🗺️ 抽象逻辑层 ]
60
+ +-------------------------+
61
+ | Step1 -> Step2 ... | (绘制链式流程)
62
+ | | |
63
+ | [ ? ] <---------+--- 🎯 锁定可疑节点
64
+ +-----------+-------------+
65
+ |
66
+ v
67
+ +-----------+-------------+
68
+ | 🕵️ 双层取证 (Compare) |
69
+ +-----+-------------+-----+
70
+ | |
71
+ (📜 PPDocs) vs (💻 Code)
72
+ | |
73
+ +------+------+
74
+ |
75
+ v
76
+ /-------+-------\
77
+ / 根本原因? \
78
+ v v
79
+ +--------------+ +--------------+
80
+ | 🐛 代码 Bug | | 📐 设计/文档错 |
81
+ | (Fix Code) | | (Fix Graph) |
82
+ +--------------+ +--------------+
@@ -0,0 +1,90 @@
1
+ # 💬 Discussion Responder (讨论响应协议)
2
+
3
+ 本工作流用于检查、分析、回复跨项目讨论。可手动触发或会话启动时自动检查。
4
+
5
+ ---
6
+
7
+ ## 标准流程
8
+
9
+ ### Step 1: 扫描待处理讨论
10
+ ```
11
+ kg_discuss({ action: "list" })
12
+ ```
13
+ - 过滤涉及本项目的讨论 (sender 含本项目ID)
14
+ - 无讨论 → 输出 "当前无待处理讨论" → 结束
15
+
16
+ ### Step 2: 展示摘要
17
+ 以表格展示所有待处理讨论:
18
+
19
+ | ID | 标题 | 发起方 | 摘要 | 回复数 | 更新时间 |
20
+ |:---|:---|:---|:---|:---:|:---|
21
+
22
+ 用户选择要处理的讨论 (可多选)
23
+
24
+ ### Step 3: 读取详情 + 知识锚定
25
+ ```
26
+ kg_discuss({ action: "read", id: "选中ID" })
27
+ ```
28
+
29
+ **并行执行**:
30
+ 1. 阅读讨论全部消息, 理解各方立场
31
+ 2. `kg_search` 检索相关知识节点, 获取本项目的标准逻辑
32
+ 3. `kg_get_rules(ruleType: "codeStyle")` 获取编码规范 (如涉及代码)
33
+
34
+ ### Step 4: 分析与回复
35
+ **分析框架**:
36
+ | 维度 | 检查 |
37
+ |:---|:---|
38
+ | 技术可行性 | 本项目能否支持对方提出的方案? |
39
+ | 影响范围 | 改动会影响本项目哪些模块? |
40
+ | 替代方案 | 是否有更优解? |
41
+ | 依赖关系 | 需要对方先做什么? 本方需要先做什么? |
42
+
43
+ **回复**:
44
+ ```
45
+ kg_discuss({
46
+ action: "reply",
47
+ id: "讨论ID",
48
+ content: "基于分析的回复内容",
49
+ newSummary: "更新摘要 (可选, 如有阶段性结论)"
50
+ })
51
+ ```
52
+
53
+ ### Step 5: 结案判断
54
+ 如果所有方已达成共识:
55
+ ```
56
+ kg_discuss({
57
+ action: "close",
58
+ id: "讨论ID",
59
+ conclusion: "结案总结 (含最终方案 + 各方责任)"
60
+ })
61
+ ```
62
+ 归档文档自动存入知识图谱。
63
+
64
+ ---
65
+
66
+ ## 回复质量标准
67
+ | 要求 | 说明 |
68
+ |:---|:---|
69
+ | **有据可查** | 引用知识图谱节点或代码文件支撑观点 |
70
+ | **明确立场** | 同意/反对/有条件同意, 不含糊 |
71
+ | **可执行** | 回复中包含具体行动项, 非空谈 |
72
+ | **格式规范** | Markdown 格式, 要点用列表, 代码用代码块 |
73
+
74
+ ---
75
+
76
+ ## 发起新讨论
77
+ 当本项目需要向其他项目发起协商:
78
+ ```
79
+ kg_discuss({
80
+ action: "create",
81
+ title: "讨论标题",
82
+ participants: ["目标项目ID"],
83
+ content: "问题描述 + 本方立场 + 期望回复"
84
+ })
85
+ ```
86
+
87
+ **发起前检查**:
88
+ 1. `kg_search` 确认问题确实需要跨项目协商
89
+ 2. 明确参与方 (通过 `kg_projects` 查看可用项目)
90
+ 3. 内容包含: 背景、问题、本方方案、期望对方行动
@@ -0,0 +1,84 @@
1
+ # ⚡️ "Ockham's Blade" Execution Protocol (奥卡姆之刃执行协议)
2
+
3
+ 该工作流旨在执行已确认的方案。核心原则:**拒绝冗余,一行搞定,模块解耦,逻辑确权。**
4
+
5
+ ---
6
+
7
+ ### 1.第一阶段:逻辑确权 (Knowledge Anchor) 🧠
8
+ 🔹 **核心目标**:消除所有不确定性,确保逻辑绝对正确。
9
+ ✏️ **动作**:
10
+ * 解析任务需求。
11
+ * 遇到模糊逻辑,立即查询**知识图谱 (Knowledge Graph)** 确认标准流程。
12
+ × **禁区**:
13
+ * 禁止基于猜测编写代码。
14
+ * 禁止模糊定义的变量。
15
+
16
+ ### 2.第二阶段:架构定义 (Structure & Taxonomy) 🏗️
17
+ 🔹 **核心目标**:目录清晰,分类准确,模块独立。
18
+ ✏️ **动作**:
19
+ * 构建标准目录树 (Directory Tree)。
20
+ * 定义原子化模块 (Atomic Modules)。
21
+ * 确保接口通用,支持复用调用。
22
+ × **禁区**:
23
+ * 禁止模块间产生强耦合 (Zero Coupling)。
24
+ * 禁止混乱的文件层级。
25
+
26
+ ### 3.第三阶段:极简实现 (Minimalist Coding) 🚀
27
+ 🔹 **核心目标**:不造轮子,极致压缩代码行数。
28
+ ✏️ **动作**:
29
+ * 最大化调用现有库与复用功能。
30
+ * 利用链式调用、推导式等语法特性。
31
+ × **禁区**:
32
+ * **能一行代码解决的问题,严格禁止使用第二行。**
33
+ * 禁止重复造轮子 (DRY Principle)。
34
+
35
+ ### 4.第四阶段:最终交付 (Delivery) 📦
36
+ 🔹 **核心目标**:输出可直接运行、可维护的成果。
37
+ ✏️ **动作**:
38
+ * 输出标准化的文件结构。
39
+ * 交付模块化源码。
40
+
41
+ ---
42
+
43
+ ### 📊 执行逻辑可视化图解 (ASCII)
44
+
45
+ ```text
46
+ +-----------------------+
47
+ | 🚀 任务启动 (Start) |
48
+ +-----------+-----------+
49
+ |
50
+ v
51
+ +-----------+-----------+
52
+ | 🧠 逻辑是否清晰? | <-----+
53
+ +-----------+-----------+ |
54
+ (❌ NO) | (✅ YES) |
55
+ | v |
56
+ +------+-----+ +----------------+ |
57
+ | 🔍 查图谱 | | 🏗️ 架构设计 | |
58
+ +------------+ +-------+--------+ |
59
+ | |
60
+ v |
61
+ +-------+--------+ |
62
+ | ♻️ 存在轮子? | |
63
+ +-------+--------+ |
64
+ / \ |
65
+ (YES)/ \(NO)
66
+ v v
67
+ +-------+-------+ +-------+-------+
68
+ | ✏️ 直接调用 | | ✏️ 原子编写 |
69
+ +-------+-------+ +-------+-------+
70
+ | |
71
+ +----------+-----------+
72
+ |
73
+ v
74
+ +-------+-------+
75
+ | 📏 极简审查 | <-----+
76
+ +-------+-------+ |
77
+ | |
78
+ /-----------+----------\ |
79
+ / 是否多于一行? \ |
80
+ v v |
81
+ +-------+-------+ +-------+--+----+
82
+ | ✅ 达标 | | × 删减 (Trim) |
83
+ | (Delivery) | | 压缩代码 |
84
+ +---------------+ +---------------+
@@ -1,5 +1,8 @@
1
1
  你是严谨的软件架构师,围绕**用户知识图谱软件**工作,确保每个变动有据可查,每次成功沉淀为知识节点。
2
2
 
3
+ ## 会话启动检查
4
+ 每次会话开始时, 执行 `kg_discuss({ action: "list" })` 检查是否有涉及本项目的待回复讨论。如有, 主动提醒用户: "发现 N 条待处理讨论, 是否需要查看?"
5
+
3
6
  ## 核心宪法
4
7
  | 原则 | 要求 |
5
8
  |:---|:---|
@@ -100,3 +103,16 @@ task_create → task_add_log(progress/issue/solution/reference) → task_complet
100
103
  |:---|:---|:---|:---|
101
104
  | 复用 | 重复造轮子 | 调用utils | 无 |
102
105
  | 结构 | 混杂views | 迁移services | 需改引用 |
106
+
107
+ ---
108
+
109
+ ## 工作流速查
110
+ | 命令 | 用途 | 场景 |
111
+ |:---|:---|:---|
112
+ | `/pp:init` | 项目知识图谱初始化 | 新项目接入 |
113
+ | `/pp:sync` | 代码↔知识图谱同步 | 代码变更后 |
114
+ | `/pp:diagnose` | 深度问题诊断 | 排查 Bug |
115
+ | `/pp:execute` | 任务极简执行 | 已确认方案的编码 |
116
+ | `/pp:review` | 代码质量审查 | 轻量级审查 |
117
+ | `/pp:audit` | 多Agent严格审计 | 重要交付前 |
118
+ | `/pp:discuss` | 讨论响应与协作 | 跨项目协商 |
@@ -1,33 +0,0 @@
1
- export interface DiscussionMessage {
2
- id: string;
3
- sender: string;
4
- content: string;
5
- timestamp: string;
6
- }
7
- export interface DiscussionTopic {
8
- id: string;
9
- title: string;
10
- initiator: string;
11
- participants: string[];
12
- summary: string;
13
- status: 'active' | 'completed';
14
- created_at: string;
15
- updated_at: string;
16
- messages: DiscussionMessage[];
17
- }
18
- export declare class DiscussionManager {
19
- private static getFilePath;
20
- private static readAll;
21
- private static writeAll;
22
- static listActive(): Omit<DiscussionTopic, 'messages'>[];
23
- static readByIds(ids: string[]): DiscussionTopic[];
24
- static create(title: string, initiator: string, participants: string[], content: string): string;
25
- static reply(id: string, sender: string, content: string, newSummary?: string): boolean;
26
- static getAndRemove(id: string): DiscussionTopic | null;
27
- /** 按ID删除讨论(不归档,直接删除) */
28
- static delete(id: string): boolean;
29
- /** 清理过期讨论(默认7天不活跃) */
30
- static cleanExpired(days?: number): number;
31
- /** 获取活跃讨论数量 */
32
- static activeCount(): number;
33
- }
@@ -1,116 +0,0 @@
1
- import * as fs from 'fs';
2
- import * as path from 'path';
3
- import * as os from 'os';
4
- export class DiscussionManager {
5
- static getFilePath() {
6
- const dir = path.join(os.homedir(), '.ppdocs');
7
- if (!fs.existsSync(dir))
8
- fs.mkdirSync(dir, { recursive: true });
9
- return path.join(dir, 'discussions.json');
10
- }
11
- static readAll() {
12
- try {
13
- const file = this.getFilePath();
14
- if (!fs.existsSync(file))
15
- return [];
16
- return JSON.parse(fs.readFileSync(file, 'utf-8'));
17
- }
18
- catch {
19
- return [];
20
- }
21
- }
22
- static writeAll(data) {
23
- fs.writeFileSync(this.getFilePath(), JSON.stringify(data, null, 2), 'utf-8');
24
- }
25
- static listActive() {
26
- return this.readAll()
27
- .filter(t => t.status === 'active')
28
- .map(({ messages, ...rest }) => rest);
29
- }
30
- static readByIds(ids) {
31
- return this.readAll().filter(t => ids.includes(t.id));
32
- }
33
- static create(title, initiator, participants, content) {
34
- const id = `req_${Math.random().toString(36).substring(2, 9)}`;
35
- const now = new Date().toISOString();
36
- // 确保发起方始终在参与列表中
37
- const allParticipants = participants.includes(initiator) ? [...participants] : [initiator, ...participants];
38
- const topic = {
39
- id,
40
- title,
41
- initiator,
42
- participants: allParticipants,
43
- summary: "等待各方回复中...",
44
- status: 'active',
45
- created_at: now,
46
- updated_at: now,
47
- messages: [{
48
- id: `msg_${Math.random().toString(36).substring(2, 9)}`,
49
- sender: initiator,
50
- content,
51
- timestamp: now
52
- }]
53
- };
54
- const all = this.readAll();
55
- all.push(topic);
56
- this.writeAll(all);
57
- return id;
58
- }
59
- static reply(id, sender, content, newSummary) {
60
- const all = this.readAll();
61
- const topic = all.find(t => t.id === id);
62
- if (!topic || topic.status !== 'active')
63
- return false;
64
- const now = new Date().toISOString();
65
- topic.messages.push({
66
- id: `msg_${Math.random().toString(36).substring(2, 9)}`,
67
- sender,
68
- content,
69
- timestamp: now
70
- });
71
- topic.updated_at = now;
72
- if (newSummary) {
73
- topic.summary = newSummary;
74
- }
75
- // Auto add to participants if not exists
76
- if (!topic.participants.includes(sender)) {
77
- topic.participants.push(sender);
78
- }
79
- this.writeAll(all);
80
- return true;
81
- }
82
- static getAndRemove(id) {
83
- const all = this.readAll();
84
- const index = all.findIndex(t => t.id === id);
85
- if (index === -1)
86
- return null;
87
- const topic = all.splice(index, 1)[0];
88
- this.writeAll(all);
89
- return topic;
90
- }
91
- /** 按ID删除讨论(不归档,直接删除) */
92
- static delete(id) {
93
- const all = this.readAll();
94
- const index = all.findIndex(t => t.id === id);
95
- if (index === -1)
96
- return false;
97
- all.splice(index, 1);
98
- this.writeAll(all);
99
- return true;
100
- }
101
- /** 清理过期讨论(默认7天不活跃) */
102
- static cleanExpired(days = 7) {
103
- const all = this.readAll();
104
- const cutoff = Date.now() - days * 86400000;
105
- const before = all.length;
106
- const filtered = all.filter(t => new Date(t.updated_at).getTime() > cutoff);
107
- if (filtered.length < before) {
108
- this.writeAll(filtered);
109
- }
110
- return before - filtered.length;
111
- }
112
- /** 获取活跃讨论数量 */
113
- static activeCount() {
114
- return this.readAll().filter(t => t.status === 'active').length;
115
- }
116
- }