@ppdocs/mcp 3.9.1 → 3.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -90,7 +90,7 @@ function showHelp() {
90
90
  ppdocs MCP CLI
91
91
 
92
92
  Commands:
93
- init Full setup: .ppdocs + templates + MCP registration
93
+ init Full setup: .ppdocs + templates + MCP registration (Claude/Codex/OpenCode/Gemini)
94
94
  bind Lightweight: only create .ppdocs (for adding projects to existing MCP)
95
95
 
96
96
  Usage:
@@ -270,6 +270,13 @@ function autoRegisterMcp(cwd, apiUrl, user, skipGemini = false) {
270
270
  for (const pair of envPairs) {
271
271
  args.push(envFlag, pair);
272
272
  }
273
+ // macOS/Linux: 必须注入 PATH,否则 Claude Code 启动 MCP 时可能找不到 npx/node
274
+ // (Claude Code 进程的 PATH 通常不含 /opt/homebrew/bin 等 Homebrew/nvm 路径)
275
+ if (process.platform !== 'win32') {
276
+ const macExtraPaths = '/usr/local/bin:/opt/homebrew/bin:/home/linuxbrew/.linuxbrew/bin';
277
+ const fullPath = `${process.env.PATH || '/usr/bin:/bin'}:${macExtraPaths}`;
278
+ args.push(envFlag, `PATH=${fullPath}`);
279
+ }
273
280
  args.push(serverName, '--', 'npx', '-y', '@ppdocs/mcp@latest');
274
281
  const result = spawnSync(cli, args, { stdio: 'inherit', shell: true });
275
282
  if (result.status !== 0)
@@ -303,6 +310,24 @@ function autoRegisterMcp(cwd, apiUrl, user, skipGemini = false) {
303
310
  console.log(`⚠️ Codex MCP registration failed`);
304
311
  }
305
312
  }
313
+ // OpenCode CLI (opencode mcp add 支持 -e 环境变量注入)
314
+ if (commandExists('opencode')) {
315
+ detected.push('OpenCode');
316
+ try {
317
+ execSilent(`opencode mcp remove ${serverName}`);
318
+ mcpAdd('opencode', '-e');
319
+ }
320
+ catch {
321
+ // fallback: 写入 ~/.opencode/mcp.json
322
+ try {
323
+ const opencodeConfig = path.join(os.homedir(), '.opencode', 'mcp.json');
324
+ createMcpConfigAt(opencodeConfig, apiUrl, { user, projectRoot: cwd });
325
+ }
326
+ catch {
327
+ console.log(`⚠️ OpenCode MCP registration failed`);
328
+ }
329
+ }
330
+ }
306
331
  // Gemini CLI (gemini mcp add 不支持 --env,直接写 settings.json)
307
332
  if (commandExists('gemini') && !skipGemini) {
308
333
  detected.push('Gemini');
@@ -315,7 +340,7 @@ function autoRegisterMcp(cwd, apiUrl, user, skipGemini = false) {
315
340
  }
316
341
  }
317
342
  if (detected.length === 0) {
318
- console.log(`ℹ️ No AI CLI detected (claude/codex/gemini), creating .mcp.json for manual config`);
343
+ console.log(`ℹ️ No AI CLI detected (claude/codex/opencode/gemini), creating .mcp.json for manual config`);
319
344
  return false;
320
345
  }
321
346
  console.log(`✅ Registered MCP for: ${detected.join(', ')}`);
package/dist/config.d.ts CHANGED
@@ -12,4 +12,5 @@ export interface PpdocsConfig {
12
12
  export declare const PPDOCS_CONFIG_FILE = ".ppdocs";
13
13
  /** 生成随机用户名 */
14
14
  export declare function generateUser(): string;
15
+ export declare function generateAgentId(): string;
15
16
  export declare function loadConfig(): Promise<PpdocsConfig | null>;
package/dist/config.js CHANGED
@@ -10,6 +10,20 @@ export function generateUser() {
10
10
  const chars = 'abcdefghjkmnpqrstuvwxyz23456789';
11
11
  return Array.from({ length: 8 }, () => chars[Math.floor(Math.random() * chars.length)]).join('');
12
12
  }
13
+ /**
14
+ * 生成唯一 agentId (每个 MCP 进程实例一个)
15
+ * 格式: "a-{4位随机}" — 简短且足够区分同机多实例
16
+ * 进程级单例: 同一进程内多次调用返回相同值
17
+ */
18
+ let _cachedAgentId = null;
19
+ export function generateAgentId() {
20
+ if (!_cachedAgentId) {
21
+ const chars = 'abcdefghjkmnpqrstuvwxyz23456789';
22
+ const rand = Array.from({ length: 4 }, () => chars[Math.floor(Math.random() * chars.length)]).join('');
23
+ _cachedAgentId = `a-${rand}`;
24
+ }
25
+ return _cachedAgentId;
26
+ }
13
27
  // ============ 配置读取 ============
14
28
  function readEnvConfig() {
15
29
  const apiUrl = process.env.PPDOCS_API_URL;
@@ -20,7 +34,7 @@ function readEnvConfig() {
20
34
  apiUrl,
21
35
  projectId: match?.[1] || 'unknown',
22
36
  user: process.env.PPDOCS_USER || generateUser(),
23
- agentId: process.env.PPDOCS_AGENT_ID || 'default',
37
+ agentId: process.env.PPDOCS_AGENT_ID || generateAgentId(),
24
38
  source: 'env',
25
39
  };
26
40
  }
@@ -49,7 +63,7 @@ function readPpdocsFile() {
49
63
  const c = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
50
64
  if (!c.api || !c.projectId || !c.key)
51
65
  return null;
52
- return { apiUrl: `${c.api}/api/${c.projectId}/${c.key}`, projectId: c.projectId, user: c.user || generateUser(), agentId: c.agentId || process.env.PPDOCS_AGENT_ID || 'default', source: 'file' };
66
+ return { apiUrl: `${c.api}/api/${c.projectId}/${c.key}`, projectId: c.projectId, user: c.user || generateUser(), agentId: c.agentId || process.env.PPDOCS_AGENT_ID || generateAgentId(), source: 'file' };
53
67
  }
54
68
  catch {
55
69
  return null;
package/dist/index.js CHANGED
@@ -14,11 +14,56 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
14
14
  import { registerTools } from './tools/index.js';
15
15
  import { initClient } from './storage/httpClient.js';
16
16
  import { runCli } from './cli.js';
17
- import { loadConfig } from './config.js';
17
+ import { loadConfig, generateAgentId } from './config.js';
18
18
  // 从 package.json 读取版本号
19
19
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
20
20
  const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf-8'));
21
21
  const VERSION = pkg.version;
22
+ /** 启动时连通性检查 — 非阻塞,3秒超时,结果输出到 stderr 供调试 */
23
+ async function checkConnectivity(apiUrl) {
24
+ // Node.js 18+ 内置 fetch; 旧版本跳过检查
25
+ if (typeof globalThis.fetch !== 'function') {
26
+ console.error(`[connectivity] ⏭️ fetch unavailable (Node.js < 18), skipping connectivity check`);
27
+ return;
28
+ }
29
+ const controller = new AbortController();
30
+ const timer = setTimeout(() => controller.abort(), 3000);
31
+ try {
32
+ const response = await fetch(apiUrl, { signal: controller.signal });
33
+ if (response.ok) {
34
+ console.error(`[connectivity] ✅ ppdocs server reachable`);
35
+ }
36
+ else {
37
+ console.error(`[connectivity] ⚠️ ppdocs server responded HTTP ${response.status} — check API key/project ID`);
38
+ }
39
+ }
40
+ catch (err) {
41
+ const apiHost = apiUrl.replace(/\/api\/.*$/, '');
42
+ if (err instanceof Error && err.name === 'AbortError') {
43
+ console.error(`[connectivity] ❌ Connection TIMEOUT (3s) to ${apiHost}`);
44
+ console.error(`[connectivity] Possible causes:`);
45
+ console.error(`[connectivity] 1. ppdocs desktop app is not running on the target machine`);
46
+ console.error(`[connectivity] 2. Firewall blocking port (check Windows Firewall inbound rules)`);
47
+ console.error(`[connectivity] 3. IP address changed (re-run init with correct --api)`);
48
+ console.error(`[connectivity] 4. Not on the same network`);
49
+ }
50
+ else {
51
+ const msg = err instanceof Error ? err.message : String(err);
52
+ if (msg.includes('ECONNREFUSED')) {
53
+ console.error(`[connectivity] ❌ Connection REFUSED to ${apiHost}`);
54
+ console.error(`[connectivity] ppdocs desktop app is not running, or port is wrong`);
55
+ }
56
+ else {
57
+ console.error(`[connectivity] ❌ Cannot reach ${apiHost}: ${msg}`);
58
+ }
59
+ }
60
+ console.error(`[connectivity] API URL: ${apiUrl}`);
61
+ console.error(`[connectivity] MCP will still start — tools will fail until server is reachable`);
62
+ }
63
+ finally {
64
+ clearTimeout(timer);
65
+ }
66
+ }
22
67
  // 检查是否为 CLI 命令
23
68
  const args = process.argv.slice(2);
24
69
  if (args.length > 0) {
@@ -32,14 +77,16 @@ async function main() {
32
77
  // 有配置 → 自动初始化 (环境变量 / .ppdocs)
33
78
  if (config) {
34
79
  initClient(config.apiUrl);
35
- console.error(`ppdocs MCP v${VERSION} | project: ${config.projectId} | user: ${config.user}`);
80
+ console.error(`ppdocs MCP v${VERSION} | project: ${config.projectId} | user: ${config.user} | source: ${config.source}`);
81
+ // 启动时连通性检查 (非阻塞, 3秒超时)
82
+ checkConnectivity(config.apiUrl).catch(() => { });
36
83
  }
37
84
  else {
38
85
  console.error(`ppdocs MCP v${VERSION} | ⏳ 等待 kg_init 初始化项目上下文...`);
39
86
  }
40
87
  const projectId = config?.projectId || 'pending';
41
88
  const user = config?.user || 'agent';
42
- const agentId = config?.agentId || process.env.PPDOCS_AGENT_ID || 'default';
89
+ const agentId = config?.agentId || process.env.PPDOCS_AGENT_ID || generateAgentId();
43
90
  const server = new McpServer({ name: `ppdocs [${projectId}]`, version: VERSION }, { capabilities: { tools: {} } });
44
91
  registerTools(server, projectId, user, agentId, (newProjectId, newApiUrl) => {
45
92
  console.error(`[kg_init] 项目已切换: ${newProjectId}`);
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * API URL 格式: http://localhost:20099/api/:projectId/:password/...
6
6
  */
7
- import type { Task, TaskSummary, TaskLogType, TaskExperience, FileInfo, WorkflowSummary, WorkflowDoc, WorkflowSelector, Reference } from './types.js';
7
+ import type { Task, TaskSummary, TaskLogType, TaskExperience, FileInfo, WorkflowSummary, WorkflowDoc, WorkflowSelector, Reference, FetchResult, FileImportResult, Pitfall, PitfallSummary } from './types.js';
8
8
  export declare class PpdocsApiClient {
9
9
  private baseUrl;
10
10
  private serverUrl;
@@ -31,6 +31,27 @@ export declare class PpdocsApiClient {
31
31
  readReferenceFile(path: string): Promise<string>;
32
32
  /** 复制本地文件到参考文件库, 返回相对路径列表 */
33
33
  copyReferenceFiles(paths: string[], targetDir?: string): Promise<string[]>;
34
+ /** 从 URL 拉取文档内容并保存为参考 */
35
+ fetchRefUrl(url: string, refId?: string): Promise<FetchResult>;
36
+ /** 导入本地文件内容到参考系统 */
37
+ importLocalFile(refId: string, sourcePath: string): Promise<FileImportResult>;
38
+ listPitfalls(): Promise<PitfallSummary[]>;
39
+ getPitfall(pitfallId: string): Promise<Pitfall | null>;
40
+ createPitfall(input: {
41
+ title: string;
42
+ category?: string;
43
+ symptom?: string;
44
+ root_cause?: string;
45
+ solution?: string;
46
+ prevention?: string;
47
+ severity?: string;
48
+ related_nodes?: string[];
49
+ source_task?: string;
50
+ tags?: string[];
51
+ }): Promise<Pitfall>;
52
+ savePitfall(pitfall: Pitfall): Promise<boolean>;
53
+ deletePitfall(pitfallId: string): Promise<boolean>;
54
+ searchPitfalls(query: string, status?: string, category?: string): Promise<PitfallSummary[]>;
34
55
  listTasks(status?: 'active' | 'archived'): Promise<TaskSummary[]>;
35
56
  getTask(taskId: string, mode?: 'smart' | 'full'): Promise<Task | null>;
36
57
  createTask(task: {
@@ -168,6 +168,56 @@ export class PpdocsApiClient {
168
168
  body: JSON.stringify({ paths, target_dir: targetDir }),
169
169
  });
170
170
  }
171
+ /** 从 URL 拉取文档内容并保存为参考 */
172
+ async fetchRefUrl(url, refId) {
173
+ return this.request('/refs/fetch-url', {
174
+ method: 'POST',
175
+ body: JSON.stringify({ url, ref_id: refId }),
176
+ });
177
+ }
178
+ /** 导入本地文件内容到参考系统 */
179
+ async importLocalFile(refId, sourcePath) {
180
+ return this.request('/refs/import-file', {
181
+ method: 'POST',
182
+ body: JSON.stringify({ ref_id: refId, source_path: sourcePath }),
183
+ });
184
+ }
185
+ // ============ 踩坑经验 API ============
186
+ async listPitfalls() {
187
+ return this.request('/pitfalls');
188
+ }
189
+ async getPitfall(pitfallId) {
190
+ try {
191
+ return await this.request(`/pitfalls/${encodeURIComponent(pitfallId)}`);
192
+ }
193
+ catch {
194
+ return null;
195
+ }
196
+ }
197
+ async createPitfall(input) {
198
+ return this.request('/pitfalls', {
199
+ method: 'POST',
200
+ body: JSON.stringify(input),
201
+ });
202
+ }
203
+ async savePitfall(pitfall) {
204
+ await this.request(`/pitfalls/${encodeURIComponent(pitfall.id)}`, {
205
+ method: 'PUT',
206
+ body: JSON.stringify(pitfall),
207
+ });
208
+ return true;
209
+ }
210
+ async deletePitfall(pitfallId) {
211
+ return this.request(`/pitfalls/${encodeURIComponent(pitfallId)}`, {
212
+ method: 'DELETE',
213
+ });
214
+ }
215
+ async searchPitfalls(query, status, category) {
216
+ return this.request('/pitfalls/search', {
217
+ method: 'POST',
218
+ body: JSON.stringify({ query, status, category }),
219
+ });
220
+ }
171
221
  // ============ 任务管理 ============
172
222
  async listTasks(status) {
173
223
  const query = status ? `?status=${status}` : '';
@@ -45,6 +45,41 @@ export interface Reference {
45
45
  adoptedBy: AdoptedNode[];
46
46
  createdAt: string;
47
47
  updatedAt: string;
48
+ sourceUrl?: string;
49
+ fetchHistory?: FetchRecord[];
50
+ fileImportHistory?: FileImportRecord[];
51
+ }
52
+ export interface FetchRecord {
53
+ fetchedAt: string;
54
+ url: string;
55
+ title: string;
56
+ contentFile: string;
57
+ contentLength: number;
58
+ contentPreview: string;
59
+ }
60
+ export interface FetchResult {
61
+ refId: string;
62
+ title: string;
63
+ contentFile: string;
64
+ contentLength: number;
65
+ contentPreview: string;
66
+ fetchCount: number;
67
+ }
68
+ export interface FileImportRecord {
69
+ importedAt: string;
70
+ sourcePath: string;
71
+ storedFile: string;
72
+ contentLength: number;
73
+ contentPreview: string;
74
+ fileType: string;
75
+ }
76
+ export interface FileImportResult {
77
+ refId: string;
78
+ sourcePath: string;
79
+ storedFile: string;
80
+ contentLength: number;
81
+ contentPreview: string;
82
+ importCount: number;
48
83
  }
49
84
  export interface FileInfo {
50
85
  name: string;
@@ -95,3 +130,30 @@ export interface TaskSummary {
95
130
  updated_at: string;
96
131
  last_log?: string;
97
132
  }
133
+ export interface Pitfall {
134
+ id: string;
135
+ title: string;
136
+ category: string;
137
+ symptom: string;
138
+ root_cause: string;
139
+ solution: string;
140
+ prevention: string;
141
+ related_nodes: string[];
142
+ source_task?: string;
143
+ severity: string;
144
+ status: string;
145
+ tags: string[];
146
+ created_at: string;
147
+ updated_at: string;
148
+ resolved_at?: string;
149
+ }
150
+ export interface PitfallSummary {
151
+ id: string;
152
+ title: string;
153
+ category: string;
154
+ severity: string;
155
+ status: string;
156
+ tags: string[];
157
+ created_at: string;
158
+ updated_at: string;
159
+ }
@@ -15,9 +15,7 @@ import { getClient } from '../storage/httpClient.js';
15
15
  import { decodeObjectStrings } from '../utils.js';
16
16
  import { wrap, safeTool } from './shared.js';
17
17
  function sender(ctx) {
18
- return ctx.agentId && ctx.agentId !== 'default'
19
- ? `${ctx.projectId}:${ctx.user}:${ctx.agentId}`
20
- : `${ctx.projectId}:${ctx.user}`;
18
+ return `${ctx.projectId}:${ctx.user}:${ctx.agentId}`;
21
19
  }
22
20
  /** 提取 sender 中的 projectId */
23
21
  function senderProjectId(senderStr) {
@@ -318,6 +318,7 @@ function findDirectedPath(chart, fromId, toId) {
318
318
  export function registerFlowchartTools(server, _ctx) {
319
319
  const client = () => getClient();
320
320
  server.tool('kg_flowchart', '🔀 逻辑流程图 — 项目知识图谱的核心。每个节点代表一个模块/函数/概念,节点可绑定文件、内嵌版本化文档。\n' +
321
+ '★★ 编码前必须先设计:用 batch_add/update_node 在流程图中设计逻辑并验证每个节点正确后才能编码。执行 kg_workflow(id:"design-first") 查看完整工作流 ★★\n' +
321
322
  '⚡ 开始任何任务前必须先查图谱:search 搜关键词 → get_node 看详情 → 有 subFlowchart 则递归下探。\n' +
322
323
  '📝 完成修改后必须回写:bind 绑定文件 → update_node 更新描述和文档 → 新模块用 batch_add。\n' +
323
324
  'actions: list|get|search|get_relations|find_path|get_node|update_node|delete_node|batch_add|bind|unbind|orphans|health|create_chart|delete_chart', {
@@ -380,6 +381,9 @@ export function registerFlowchartTools(server, _ctx) {
380
381
  if (chart.parentChart) {
381
382
  lines.push(`parent: ${chart.parentChart}${chart.parentNode ? `/${chart.parentNode}` : ''}`);
382
383
  }
384
+ if (chart.projectBrief) {
385
+ lines.push('', chart.projectBrief);
386
+ }
383
387
  // Mermaid 流程图
384
388
  lines.push('', '```mermaid', 'graph TD');
385
389
  // 节点声明
@@ -1,11 +1,11 @@
1
1
  /**
2
2
  * MCP 工具注册入口
3
- * 14 个工具, 8 个子模块
3
+ * 15 个工具, 9 个子模块
4
4
  *
5
5
  * 🔗 初始化: kg_init (1个)
6
6
  * 📊 导航: kg_status (1个)
7
7
  * 📚 知识: kg_projects, kg_workflow (2个)
8
- * 📝 工作流: kg_task(任务记录), kg_files, kg_discuss(讨论区), kg_ref (4个)
8
+ * 📝 工作流: kg_task(任务记录), kg_files, kg_discuss(讨论区), kg_ref, kg_pitfall (5个)
9
9
  * 🔀 关系核心: kg_flowchart(逻辑流程图 — 关系型知识锚点) (1个)
10
10
  * 📘 文档查询: kg_doc(节点文档搜索/阅读/历史) (1个)
11
11
  * 🔬 代码分析: code_scan, code_smart_context, code_full_path (3个)
@@ -1,11 +1,11 @@
1
1
  /**
2
2
  * MCP 工具注册入口
3
- * 14 个工具, 8 个子模块
3
+ * 15 个工具, 9 个子模块
4
4
  *
5
5
  * 🔗 初始化: kg_init (1个)
6
6
  * 📊 导航: kg_status (1个)
7
7
  * 📚 知识: kg_projects, kg_workflow (2个)
8
- * 📝 工作流: kg_task(任务记录), kg_files, kg_discuss(讨论区), kg_ref (4个)
8
+ * 📝 工作流: kg_task(任务记录), kg_files, kg_discuss(讨论区), kg_ref, kg_pitfall (5个)
9
9
  * 🔀 关系核心: kg_flowchart(逻辑流程图 — 关系型知识锚点) (1个)
10
10
  * 📘 文档查询: kg_doc(节点文档搜索/阅读/历史) (1个)
11
11
  * 🔬 代码分析: code_scan, code_smart_context, code_full_path (3个)
@@ -20,6 +20,7 @@ import { registerTaskTools } from './tasks.js';
20
20
  import { registerFileTools } from './files.js';
21
21
  import { registerDiscussionTools } from './discussion.js';
22
22
  import { registerReferenceTools } from './refs.js';
23
+ import { registerPitfallTools } from './pitfalls.js';
23
24
  import { registerAnalyzerTools } from './analyzer.js';
24
25
  import { registerMeetingTools } from './meeting.js';
25
26
  import { registerFlowchartTools } from './flowchart.js';
@@ -38,6 +39,7 @@ export function registerTools(server, projectId, user, agentId, onProjectChange)
38
39
  registerFileTools(server);
39
40
  registerDiscussionTools(server, ctx);
40
41
  registerReferenceTools(server);
42
+ registerPitfallTools(server);
41
43
  // 🔬 代码分析
42
44
  registerAnalyzerTools(server, ctx);
43
45
  // 🏛️ 多AI协作
@@ -11,6 +11,7 @@ import * as fs from 'fs';
11
11
  import * as path from 'path';
12
12
  import { z } from 'zod';
13
13
  import { initClient } from '../storage/httpClient.js';
14
+ import { generateAgentId } from '../config.js';
14
15
  import { wrap, safeTool } from './shared.js';
15
16
  // 当前会话的 API URL (用于幂等检查)
16
17
  let currentApiUrl = null;
@@ -74,7 +75,8 @@ export function registerInitTool(server, ctx, onProjectChange) {
74
75
  // 4. 更新共享上下文 — 所有工具立刻获得新 projectId
75
76
  ctx.projectId = config.projectId;
76
77
  ctx.user = config.user;
77
- ctx.agentId = process.env.PPDOCS_AGENT_ID || 'default';
78
+ // agentId: 优先环境变量 > 保留当前值(进程启动时已生成)
79
+ ctx.agentId = process.env.PPDOCS_AGENT_ID || ctx.agentId || generateAgentId();
78
80
  // 5. 通知主进程
79
81
  onProjectChange(config.projectId, config.apiUrl);
80
82
  return wrap(`✅ 项目上下文已初始化\n\n` +
@@ -1,5 +1,6 @@
1
1
  /**
2
2
  * 📊 kg_status — 项目速览仪表盘
3
+ * 一次调用返回项目全貌:摘要、核心模块、活跃任务、快速导航
3
4
  */
4
5
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
5
6
  import { type McpContext } from './shared.js';
@@ -1,32 +1,56 @@
1
1
  /**
2
2
  * 📊 kg_status — 项目速览仪表盘
3
+ * 一次调用返回项目全貌:摘要、核心模块、活跃任务、快速导航
3
4
  */
4
5
  import { getClient } from '../storage/httpClient.js';
5
6
  import { wrap, safeTool } from './shared.js';
6
7
  export function registerStatusTool(server, ctx) {
7
8
  const client = () => getClient();
8
9
  server.tool('kg_status', '📊 项目速览仪表盘 — 一键了解项目健康。返回: 流程图节点数、活跃任务数、最近变更。每次对话开始建议首先调用', {}, async () => safeTool(async () => {
9
- const [charts, activeTasks, discussions] = await Promise.all([
10
+ const [charts, activeTasks, discussions, mainChart] = await Promise.all([
10
11
  client().listFlowcharts(),
11
12
  client().listTasks('active'),
12
13
  client().discussionList().catch(() => []),
14
+ client().getFlowchart('main').catch(() => null),
13
15
  ]);
14
16
  const nodeCount = charts.reduce((sum, c) => sum + (c.nodeCount || 0), 0);
15
17
  const activeDiscussions = discussions.filter((d) => d.status === 'active');
16
18
  const lines = [
17
- `📊 项目速览 [${ctx.projectId}]`,
18
- `🆔 身份: ${ctx.user}${ctx.agentId !== 'default' ? ':' + ctx.agentId : ''}`,
19
- ``,
20
- `🔀 流程图: ${charts.length} 张 | 📦 节点: ${nodeCount} 个`,
21
- `📝 活跃任务: ${activeTasks.length} 个`,
22
- `📨 活跃讨论: ${activeDiscussions.length} 个`,
19
+ `# 项目速览 [${ctx.projectId}]`,
20
+ `身份: ${ctx.user}:${ctx.agentId}`,
23
21
  ];
22
+ // --- Project Brief ---
23
+ if (mainChart?.projectBrief) {
24
+ lines.push('', '## 项目简介', mainChart.projectBrief);
25
+ }
26
+ // --- 核心模块 (从 main 图提取) ---
27
+ if (mainChart?.nodes && mainChart.nodes.length > 0) {
28
+ lines.push('', '## 核心模块');
29
+ for (const node of mainChart.nodes) {
30
+ const badges = [];
31
+ if (node.subFlowchart)
32
+ badges.push(`▶${node.subFlowchart}`);
33
+ const fileCount = (node.boundFiles?.length ?? 0) + (node.boundDirs?.length ?? 0);
34
+ if (fileCount > 0)
35
+ badges.push(`files=${fileCount}`);
36
+ const docCount = node.docEntries?.length ?? 0;
37
+ if (docCount > 0)
38
+ badges.push(`docs=${docCount}`);
39
+ const suffix = badges.length > 0 ? ` [${badges.join(' ')}]` : '';
40
+ const desc = node.description ? ` — ${node.description.slice(0, 80)}` : '';
41
+ lines.push(`- **${node.label}** \`${node.id}\`${desc}${suffix}`);
42
+ }
43
+ }
44
+ // --- 当前状态 ---
45
+ lines.push('', '## 当前状态', `- 流程图: ${charts.length} 张 | 节点: ${nodeCount} 个`, `- 活跃任务: ${activeTasks.length} 个`, `- 活跃讨论: ${activeDiscussions.length} 个`);
24
46
  if (activeTasks.length > 0) {
25
- lines.push(``, `🔧 进行中的任务:`);
47
+ lines.push('', '### 进行中的任务');
26
48
  for (const t of activeTasks.slice(0, 5)) {
27
- lines.push(` - ${t.title} (${t.id})`);
49
+ lines.push(`- ${t.title} (\`${t.id}\`)`);
28
50
  }
29
51
  }
52
+ // --- 快速导航 ---
53
+ lines.push('', '## 快速导航', '- `kg_flowchart(get, "main")` — 查看完整架构图', '- `kg_flowchart(get_node, "节点ID", expand:2)` — 深入某个模块', '- `kg_workflow()` — 查看可用工作流', '- `kg_task(get)` — 查看活跃任务详情');
30
54
  return wrap(lines.join('\n'));
31
55
  }));
32
56
  }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * 💡 kg_pitfall — 踩坑经验管理
3
+ * 记录项目中遇到的问题、根因和解决方案,避免重复踩坑
4
+ */
5
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
6
+ export declare function registerPitfallTools(server: McpServer): void;