@ppdocs/mcp 2.6.7 → 2.6.9

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/README.md CHANGED
@@ -109,10 +109,9 @@ npx @ppdocs/mcp init -p <projectId> -k <key> --codex
109
109
  | `kg_update_node` | 更新节点内容 |
110
110
  | `kg_delete_node` | 删除节点 |
111
111
  | `kg_lock_node` | 锁定节点 (AI 只能锁定,解锁需前端手动) |
112
- | `kg_list_nodes` | 列出所有节点 |
112
+ | `kg_list_nodes` | 列出节点 (支持 status/edges 过滤,maxEdges=0 查孤立节点) |
113
113
  | `kg_search` | 关键词搜索 |
114
114
  | `kg_find_path` | 查找依赖路径 |
115
- | `kg_find_orphans` | 查找孤立节点 |
116
115
  | `kg_get_relations` | 获取节点关系 |
117
116
 
118
117
  ### 任务管理工具
@@ -175,6 +174,10 @@ npx @ppdocs/mcp init -p <projectId> -k <key> --codex
175
174
 
176
175
  ## 更新日志
177
176
 
177
+ ### v2.6.9
178
+ - ✨ `kg_list_nodes` 支持 status/minEdges/maxEdges 过滤
179
+ - 🗑️ 移除 `kg_find_orphans` (用 `kg_list_nodes(maxEdges: 0)` 替代)
180
+
178
181
  ### v2.5.0
179
182
  - ✨ 新增 CLI init 命令,自动安装工作流模板
180
183
  - ✨ 支持 Codex 模式 (--codex)
package/dist/cli.js CHANGED
@@ -242,7 +242,6 @@ function generateHooksConfig() {
242
242
  hooks: {
243
243
  UserPromptSubmit: [
244
244
  {
245
- matcher: '*',
246
245
  hooks: [{ type: 'command', command }]
247
246
  }
248
247
  ]
@@ -266,12 +265,12 @@ function installClaudeTemplates(cwd) {
266
265
  copyDirRecursive(srcHooks, destHooks);
267
266
  console.log(`✅ Installed .claude/hooks/`);
268
267
  }
269
- // 3. 生成跨平台 hooks 配置并合并到 settings.local.json
270
- const settingsLocalPath = path.join(claudeDir, 'settings.local.json');
268
+ // 3. 生成跨平台 hooks 配置并合并到 settings.json (hooks 必须在 settings.json 才能生效)
269
+ const settingsPath = path.join(claudeDir, 'settings.json');
271
270
  let existingSettings = {};
272
- if (fs.existsSync(settingsLocalPath)) {
271
+ if (fs.existsSync(settingsPath)) {
273
272
  try {
274
- existingSettings = JSON.parse(fs.readFileSync(settingsLocalPath, 'utf-8'));
273
+ existingSettings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
275
274
  }
276
275
  catch { /* ignore */ }
277
276
  }
@@ -281,8 +280,8 @@ function installClaudeTemplates(cwd) {
281
280
  ...hooksConfig
282
281
  };
283
282
  fs.mkdirSync(claudeDir, { recursive: true });
284
- fs.writeFileSync(settingsLocalPath, JSON.stringify(mergedSettings, null, 2));
285
- console.log(`✅ Configured .claude/settings.local.json hooks (${process.platform})`);
283
+ fs.writeFileSync(settingsPath, JSON.stringify(mergedSettings, null, 2));
284
+ console.log(`✅ Configured .claude/settings.json hooks (${process.platform})`);
286
285
  }
287
286
  /** 安装 Codex 模板 (生成 AGENTS.md) */
288
287
  function installCodexTemplates(cwd) {
@@ -5,11 +5,16 @@
5
5
  * API URL 格式: http://localhost:20001/api/:projectId/:password/...
6
6
  */
7
7
  import type { NodeData, SearchResult, PathResult, BugfixRecord, Task, TaskSummary, TaskLogType, TaskExperience } from './types.js';
8
+ export interface ListNodesFilter {
9
+ status?: 'incomplete' | 'complete' | 'fixing' | 'refactoring' | 'deprecated';
10
+ minEdges?: number;
11
+ maxEdges?: number;
12
+ }
8
13
  export declare class PpdocsApiClient {
9
14
  private baseUrl;
10
15
  constructor(apiUrl: string);
11
16
  private request;
12
- listNodes(): Promise<NodeData[]>;
17
+ listNodes(filter?: ListNodesFilter): Promise<NodeData[]>;
13
18
  getNode(nodeId: string): Promise<NodeData | null>;
14
19
  createNode(node: Partial<NodeData>): Promise<NodeData>;
15
20
  updateNode(nodeId: string, updates: Partial<NodeData>): Promise<NodeData | null>;
@@ -22,12 +27,6 @@ export declare class PpdocsApiClient {
22
27
  lockNode(nodeId: string, locked: boolean): Promise<NodeData | null>;
23
28
  searchNodes(keywords: string[], limit?: number): Promise<SearchResult[]>;
24
29
  findPath(startId: string, endId: string): Promise<PathResult | null>;
25
- findOrphans(): Promise<Array<{
26
- id: string;
27
- title: string;
28
- type: string;
29
- status: string;
30
- }>>;
31
30
  getRelations(nodeId: string): Promise<Array<{
32
31
  nodeId: string;
33
32
  title: string;
@@ -52,7 +51,7 @@ export declare class PpdocsApiClient {
52
51
  completeTask(taskId: string, experience: TaskExperience): Promise<Task | null>;
53
52
  }
54
53
  export declare function initClient(apiUrl: string): void;
55
- export declare function listNodes(_projectId: string): Promise<NodeData[]>;
54
+ export declare function listNodes(_projectId: string, filter?: ListNodesFilter): Promise<NodeData[]>;
56
55
  export declare function getNode(_projectId: string, nodeId: string): Promise<NodeData | null>;
57
56
  export declare function createNode(_projectId: string, node: Partial<NodeData>): Promise<NodeData>;
58
57
  export declare function updateNode(_projectId: string, nodeId: string, updates: Partial<NodeData>): Promise<NodeData | null>;
@@ -65,12 +64,6 @@ export declare function deleteNode(_projectId: string, nodeId: string): Promise<
65
64
  export declare function lockNode(_projectId: string, nodeId: string, locked: boolean): Promise<NodeData | null>;
66
65
  export declare function searchNodes(_projectId: string, keywords: string[], limit?: number): Promise<SearchResult[]>;
67
66
  export declare function findPath(_projectId: string, startId: string, endId: string): Promise<PathResult | null>;
68
- export declare function findOrphans(_projectId: string): Promise<Array<{
69
- id: string;
70
- title: string;
71
- type: string;
72
- status: string;
73
- }>>;
74
67
  export declare function getRelations(_projectId: string, nodeId: string): Promise<Array<{
75
68
  nodeId: string;
76
69
  title: string;
@@ -99,8 +99,34 @@ export class PpdocsApiClient {
99
99
  }
100
100
  }
101
101
  // ============ 节点操作 ============
102
- async listNodes() {
103
- return this.request('/nodes');
102
+ async listNodes(filter) {
103
+ const nodes = await this.request('/nodes');
104
+ if (!filter)
105
+ return nodes;
106
+ // 计算每个节点的连接数 (入边+出边)
107
+ const edgeCounts = new Map();
108
+ nodes.forEach(n => edgeCounts.set(n.id, 0));
109
+ nodes.forEach(n => {
110
+ (n.dependencies || []).forEach(dep => {
111
+ // 出边: 当前节点依赖别人
112
+ edgeCounts.set(n.id, (edgeCounts.get(n.id) || 0) + 1);
113
+ // 入边: 被依赖的节点 (按 signature 匹配)
114
+ const target = nodes.find(t => t.signature?.toLowerCase() === dep.name.toLowerCase());
115
+ if (target)
116
+ edgeCounts.set(target.id, (edgeCounts.get(target.id) || 0) + 1);
117
+ });
118
+ });
119
+ // 过滤
120
+ return nodes.filter(n => {
121
+ if (filter.status && n.status !== filter.status)
122
+ return false;
123
+ const edges = edgeCounts.get(n.id) || 0;
124
+ if (filter.minEdges !== undefined && edges < filter.minEdges)
125
+ return false;
126
+ if (filter.maxEdges !== undefined && edges > filter.maxEdges)
127
+ return false;
128
+ return true;
129
+ });
104
130
  }
105
131
  async getNode(nodeId) {
106
132
  try {
@@ -228,9 +254,6 @@ export class PpdocsApiClient {
228
254
  return null;
229
255
  }
230
256
  }
231
- async findOrphans() {
232
- return this.request('/orphans');
233
- }
234
257
  async getRelations(nodeId) {
235
258
  const result = await this.request(`/relations/${nodeId}`);
236
259
  // 转换字段名 (API 返回 type,前端期望 edgeType)
@@ -323,8 +346,8 @@ function getClient() {
323
346
  return client;
324
347
  }
325
348
  // 导出与 fileStorage 相同的函数签名
326
- export async function listNodes(_projectId) {
327
- return getClient().listNodes();
349
+ export async function listNodes(_projectId, filter) {
350
+ return getClient().listNodes(filter);
328
351
  }
329
352
  export async function getNode(_projectId, nodeId) {
330
353
  return getClient().getNode(nodeId);
@@ -350,9 +373,6 @@ export async function searchNodes(_projectId, keywords, limit) {
350
373
  export async function findPath(_projectId, startId, endId) {
351
374
  return getClient().findPath(startId, endId);
352
375
  }
353
- export async function findOrphans(_projectId) {
354
- return getClient().findOrphans();
355
- }
356
376
  export async function getRelations(_projectId, nodeId) {
357
377
  return getClient().getRelations(nodeId);
358
378
  }
@@ -120,21 +120,20 @@ export function registerTools(server, projectId, _user) {
120
120
  };
121
121
  return wrap(projectId, JSON.stringify(output, null, 2));
122
122
  });
123
- // 7. 列出所有节点
124
- server.tool('kg_list_nodes', '列出项目全部节点概览', {}, async () => {
125
- const nodes = await storage.listNodes(projectId);
123
+ // 7. 列出节点 (支持过滤)
124
+ server.tool('kg_list_nodes', '列出节点,支持按状态/连接数过滤。maxEdges=0 可查孤立节点', {
125
+ status: z.enum(['incomplete', 'complete', 'fixing', 'refactoring', 'deprecated']).optional().describe('状态过滤'),
126
+ minEdges: z.number().optional().describe('最小连接数(含)'),
127
+ maxEdges: z.number().optional().describe('最大连接数(含),0=孤立节点')
128
+ }, async (args) => {
129
+ const filter = (args.status || args.minEdges !== undefined || args.maxEdges !== undefined)
130
+ ? { status: args.status, minEdges: args.minEdges, maxEdges: args.maxEdges }
131
+ : undefined;
132
+ const nodes = await storage.listNodes(projectId, filter);
126
133
  const output = nodes.map(n => ({ id: n.id, title: n.title, type: n.type, status: n.status, locked: n.locked }));
127
134
  return wrap(projectId, JSON.stringify(output, null, 2));
128
135
  });
129
- // 8. 查找孤立节点
130
- server.tool('kg_find_orphans', '查找无连线的孤立节点(用于清理)', {}, async () => {
131
- const orphans = await storage.findOrphans(projectId);
132
- if (orphans.length === 0) {
133
- return wrap(projectId, '没有孤立节点');
134
- }
135
- return wrap(projectId, JSON.stringify(orphans, null, 2));
136
- });
137
- // 9. 查询节点关系网 (支持多层)
136
+ // 8. 查询节点关系网 (支持多层)
138
137
  server.tool('kg_get_relations', '获取节点的上下游关系(谁依赖它/它依赖谁),支持多层查询', {
139
138
  nodeId: z.string().describe('节点ID'),
140
139
  depth: z.number().min(1).max(3).optional().describe('查询层数(默认1,最大3)')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ppdocs/mcp",
3
- "version": "2.6.7",
3
+ "version": "2.6.9",
4
4
  "description": "ppdocs MCP Server - Knowledge Graph for Claude",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",