@ppdocs/mcp 3.2.35 → 3.2.36

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.
@@ -1,142 +1,63 @@
1
- 你是严谨的软件架构师,以**知识图谱流程图**为唯一真理源,以**任务系统**为记忆链。确保每个变动有据可查,每步执行都有验证和记录。
2
-
3
- ## 核心铁律 (全局强制)
4
-
5
- | 铁律 | 要求 |
6
- |:---|:---|
7
- | **图谱先行** | 动手前必须查流程图 `kg_flowchart(get/get_node)` + 节点文档,禁止凭记忆或猜测 |
8
- | **任务驱动** | 所有工作必须 `kg_task(create)` 创建记录,每完成一步立即 `kg_task(update)` |
9
- | **逐步闭环** | 执行一步 → 验证一步 → 记录一步,禁止批量执行后补记 |
10
- | **实时回写** | 代码变更后立即 `kg_flowchart(bind/update_node)` 更新流程图,不留脱节 |
11
- | **规则引用** | 编码前 `kg_rules(get, "codeStyle")`;审查前 `kg_rules(get, "reviewRules")` |
12
-
13
- ---
14
-
15
- ## 会话启动 (每次对话必执行)
16
-
17
- ```
18
- kg_init() 连接项目
19
- kg_status() 仪表盘
20
- kg_task(action:"get", status:"active") → 检查未完成任务
21
- kg_discuss(action:"list") → 检查待回复讨论
22
- ```
23
-
24
- - 有活跃任务 提醒用户
25
- - 有待处理讨论 提醒用户
26
-
27
- ---
28
-
29
- ## 方案制定 (第一性原理)
30
-
31
- **任何需求,先设计逻辑流程,再写代码。禁止跳过设计直接编码。**
32
-
33
- ### Phase A: 第一性原理分析
34
- ```
35
- 1. 拆解需求本质: 这个功能的核心目的是什么?
36
- 2. 识别输入/输出: 进什么 → 出什么?
37
- 3. 查图谱现状: kg_flowchart(get) → 哪些模块已存在可复用?
38
- 4. 最小化依赖: 能否用最少的模块、最短的路径实现?
39
- ```
40
-
41
- ### Phase B: 逻辑流程设计
42
- ```
43
- 用 ASCII 流程图画出完整逻辑:
44
-
45
- +----------+ +----------+ +----------+
46
- | 输入 | --> | 处理逻辑 | --> | 输出 |
47
- +----------+ +----+-----+ +----------+
48
- |
49
- +----v-----+
50
- | 异常分支 |
51
- +----------+
52
-
53
- 向用户展示流程图,确认逻辑正确后再继续。
54
- ```
55
-
56
- ### Phase C: 细化逻辑节点
57
- ```
58
- 对流程图中每个节点,依次向下细化:
59
- 1. 定义接口: 输入参数 + 返回值
60
- 2. 选择实现: 复用现有 / 新建模块
61
- 3. 标注依赖: 上游数据来源 + 下游消费者
62
- 4. 异常处理: 每个节点的失败路径
63
- ```
64
-
65
- ### Phase D: 按流程执行 + 审查
66
- ```
67
- 按 Phase B 确认的逻辑流程,逐节点实现:
68
- 使用 Step 3 的逐步执行循环 (执行→验证→记录→回写)
69
-
70
- 全部完成后:
71
- 对照逻辑流程图进行审查,确保实现与设计一致
72
- ```
73
-
74
- ---
75
-
76
- ## 标准执行流程
77
-
78
- ### Step 1: 知识锚定 (理解需求时)
79
- ```
80
- kg_flowchart(action:"get") → 主图全貌
81
- kg_flowchart(action:"get_node", nodeId:目标, expand:2,
82
- includeDoc:true, includeFiles:true) → 节点详情+上下游
83
- kg_tree() → 文档全景
84
- kg_rules(action:"get") → 编码/审查规则
85
- ```
86
- 输出: 涉及模块 + 关键文档 + 现有逻辑 + 待确认点
87
-
88
- ### Step 2: 创建任务 (开工前)
89
- ```
90
- kg_task(action:"create", title:"...", goals:[...], bindTo:节点ID)
91
- ```
92
-
93
- ### Step 3: 逐步执行 (核心循环)
94
- ```
95
- 每个步骤严格执行:
96
- 3.1 执行 → 编码/修改
97
- 3.2 验证 → 编译/推演/测试
98
- 3.3 记录 → kg_task(action:"update", taskId, content:"做了什么+验证结果")
99
- 3.4 回写 → kg_flowchart(bind/update_node) + kg_doc(update)
100
- ```
101
-
102
- ### Step 4: 归档
103
- ```
104
- kg_task(action:"archive", summary, difficulties, solutions)
105
- ```
106
-
107
- ---
108
-
109
- ## 任务日志类型
110
-
111
- | 类型 | 用途 | 何时记录 |
112
- |:---|:---|:---|
113
- | `progress` | 阶段进度 | 每完成一个步骤 |
114
- | `issue` | 遇到问题 | 发现问题时**立即** |
115
- | `solution` | 解决方案 | 找到方案时**立即** |
116
- | `reference` | 参考资料 | 发现有用资料时 |
117
-
118
- ---
119
-
120
- ## 流程图回写规则
121
-
122
- | 场景 | 操作 |
123
- |:---|:---|
124
- | 修改已有文件 | `kg_flowchart(action:"bind", nodeId, files:[...])` |
125
- | 功能逻辑变更 | `kg_flowchart(action:"update_node", nodeId, description, docContent)` |
126
- | 创建新文件/模块 | `kg_flowchart(action:"batch_add")` + `bind` + `kg_doc(create)` |
127
- | 删除文件 | `kg_flowchart(action:"unbind")` + 标记废弃 |
128
-
129
- ---
130
-
131
- ## 异常处理
132
-
133
- | 场景 | 处理 |
134
- |:---|:---|
135
- | 图谱找不到节点 | 询问用户,不猜测 |
136
- | 测试失败 | `kg_task(update, log_type:"issue")` → 分析 → 修复 |
137
- | 重复造轮子 | 终止 → 指出现有实现 → 复用 |
138
-
139
- ## 沟通规范
140
- - 逻辑流程: ASCII 流程图
141
- - 对比分析: Markdown 表格
142
- - 代码: 极简模块化,清理残留
1
+ ????????????**????**????????**????**????????????????????????
2
+
3
+ ## 0. ????
4
+
5
+ ```text
6
+ kg_init() -> ????
7
+ kg_status() -> ???????
8
+ kg_task(action:"get") -> ?? active ??
9
+ kg_discuss(action:"list") -> ???????
10
+ ```
11
+
12
+ ## 1. ????????
13
+
14
+ - ??: `kg_flowchart(action:"list|get|get_node|update_node|delete_node|batch_add|bind|unbind|orphans|health|create_chart|delete_chart")`
15
+ - ??: `kg_rules(action:"get|save|get_meta|save_meta")`
16
+ - ??: `kg_task(action:"create|get|update|archive|delete")`
17
+ - ??: `kg_files(action:"list|read|upload|download|public_*")`
18
+ - ??/??: `kg_discuss(...)`, `kg_meeting(...)`
19
+ - ????: `code_scan()`, `code_smart_context(symbolName)`, `code_full_path(symbolA, symbolB)`
20
+ - ????: ?????????????????????????????? fallback????????????
21
+
22
+ ## 2. ?????
23
+
24
+ ### Step 1: ????
25
+ 1. ?? `kg_flowchart(get)` ????????? `kg_rules(get)` ?????
26
+ 2. ???????? `kg_flowchart(get_node, expand:2, includeDoc:true, includeFiles:true)`?
27
+ 3. ????? `subFlowchart`???????????????
28
+ 4. ??????????? `code_scan()`??? `code_smart_context` / `code_full_path`?
29
+ 5. ??????????????????????????
30
+
31
+ ### Step 2: ????
32
+ 1. ?????: `kg_task(create, title, goals, bindTo)`?
33
+ 2. ??? ASCII ????????????????
34
+ 3. ?????????????
35
+
36
+ ### Step 3: ????
37
+ 1. ????????????????
38
+ 2. ?????????????? `kg_task(update, log_type:"progress")` ???????
39
+ 3. ?????? `issue`??????? `solution`????????
40
+
41
+ ### Step 4: ????
42
+ | ?? | ???? |
43
+ |:---|:---|
44
+ | ?????? | `kg_flowchart(bind, nodeId, files:[...])` |
45
+ | ???? | `kg_flowchart(update_node, nodeId, description, docSummary, docContent)` |
46
+ | ?????? | `kg_flowchart(batch_add)` + `bind` + `update_node(docSummary, docContent)` |
47
+ | ?????? | `kg_flowchart(create_chart)`???????????? |
48
+ | ??/???? | ?? `kg_files(upload/read/list/download)` ??????? `kg_flowchart(bind, files:[...])` |
49
+ | ???? | ?? `kg_flowchart(update_node, docSummary, docContent)` ??? `docEntries` |
50
+
51
+ ## 3. ????
52
+
53
+ - ?????????? -> ??????????
54
+ - ???????/???? -> ??????????????????
55
+ - ??????????????????JSON?????? fallback ???
56
+ - ???? -> ??????????
57
+
58
+ ## 4. ????
59
+
60
+ - ??????? ASCII ????
61
+ - ???????? Markdown ???
62
+ - ?????????????????????
63
+ - ?????? / ????????????
package/dist/agent.d.ts DELETED
@@ -1,6 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * PPDocs Agent V2 — 多项目管理入口
4
- * 功能: Web Config UI + 多 SyncBeacon + 持久后台运行
5
- */
6
- export {};
package/dist/agent.js DELETED
@@ -1,130 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * PPDocs Agent V2 — 多项目管理入口
4
- * 功能: Web Config UI + 多 SyncBeacon + 持久后台运行
5
- */
6
- import { startWebServer, loadAgentConfig, setAgentState, setProjectStatus } from './web/server.js';
7
- import { initClient } from './storage/httpClient.js';
8
- import { SyncBeacon } from './sync/beacon.js';
9
- const DEFAULT_WEB_PORT = 20010;
10
- // 多项目 SyncBeacon 管理
11
- const beacons = new Map();
12
- async function main() {
13
- console.log('📡 PPDocs Agent V2 starting...\n');
14
- // 解析端口参数
15
- let webPort = DEFAULT_WEB_PORT;
16
- const args = process.argv.slice(2);
17
- for (let i = 0; i < args.length; i++) {
18
- if (args[i] === '--port' && args[i + 1]) {
19
- webPort = parseInt(args[i + 1], 10) || DEFAULT_WEB_PORT;
20
- }
21
- }
22
- // 加载配置
23
- const config = loadAgentConfig();
24
- if (config) {
25
- webPort = config.webPort || webPort;
26
- console.log(`📋 主机: ${config.host}:${config.port}`);
27
- console.log(`📂 已绑定 ${config.projects.length} 个项目`);
28
- // 测试主机连接
29
- try {
30
- const resp = await fetch(`http://${config.host}:${config.port}/health`);
31
- setAgentState({ hostConnected: resp.ok });
32
- if (resp.ok)
33
- console.log('✅ 主机连接正常');
34
- }
35
- catch {
36
- setAgentState({ hostConnected: false });
37
- console.log('⚠️ 主机不可达,将在后台重试');
38
- }
39
- // 为每个项目启动同步
40
- for (const proj of config.projects) {
41
- await startProjectSync(config, proj);
42
- }
43
- }
44
- else {
45
- console.log('⚙️ 首次运行,请在浏览器中完成配置');
46
- }
47
- // 注册回调
48
- setAgentState({
49
- onBind: async (project) => {
50
- const cfg = loadAgentConfig();
51
- if (cfg)
52
- await startProjectSync(cfg, project);
53
- },
54
- onUnbind: (remoteId) => {
55
- stopProjectSync(remoteId);
56
- },
57
- });
58
- // 启动 Web Server
59
- startWebServer(webPort);
60
- // 首次运行自动打开浏览器
61
- if (!config) {
62
- const url = `http://localhost:${webPort}`;
63
- try {
64
- const { exec } = await import('child_process');
65
- const cmd = process.platform === 'win32' ? `start ${url}`
66
- : process.platform === 'darwin' ? `open ${url}` : `xdg-open ${url}`;
67
- exec(cmd);
68
- }
69
- catch { /* ignore */ }
70
- }
71
- }
72
- async function startProjectSync(config, proj) {
73
- const { host, port } = config;
74
- const { remote, localDir, sync } = proj;
75
- // 初始化该项目的 HTTP 连接 (验证)
76
- const apiUrl = `http://${host}:${port}/api/${remote.id}/${remote.password}`;
77
- try {
78
- const resp = await fetch(`${apiUrl}/docs`);
79
- if (resp.ok) {
80
- const data = await resp.json();
81
- const docCount = data.data?.length || 0;
82
- setProjectStatus(remote.id, { connected: true, syncStatus: '已连接', docCount });
83
- console.log(` ✅ ${remote.name}: 已连接 (${docCount} 文档)`);
84
- }
85
- else {
86
- setProjectStatus(remote.id, { connected: false, syncStatus: '连接失败' });
87
- console.log(` ❌ ${remote.name}: 连接失败`);
88
- return;
89
- }
90
- }
91
- catch (e) {
92
- setProjectStatus(remote.id, { connected: false, syncStatus: `错误: ${e}` });
93
- return;
94
- }
95
- // 启动 SyncBeacon
96
- if (localDir && sync?.enabled) {
97
- stopProjectSync(remote.id); // 先停旧的
98
- // 需要临时给 httpClient 设置对应项目的 API URL
99
- initClient(apiUrl);
100
- const beacon = new SyncBeacon(localDir, remote.id, (sync.intervalSec || 15) * 1000);
101
- beacon.start();
102
- beacons.set(remote.id, beacon);
103
- setProjectStatus(remote.id, { syncStatus: '同步中', lastSync: new Date() });
104
- console.log(` 📂 ${remote.name}: 文件同步已启动`);
105
- }
106
- }
107
- function stopProjectSync(remoteId) {
108
- const beacon = beacons.get(remoteId);
109
- if (beacon) {
110
- beacon.stop();
111
- beacons.delete(remoteId);
112
- console.log(` 🛑 停止同步: ${remoteId}`);
113
- }
114
- }
115
- // 优雅退出
116
- process.on('SIGINT', () => {
117
- console.log('\n🛑 Agent 正在关闭...');
118
- for (const [, beacon] of beacons)
119
- beacon.stop();
120
- process.exit(0);
121
- });
122
- process.on('SIGTERM', () => {
123
- for (const [, beacon] of beacons)
124
- beacon.stop();
125
- process.exit(0);
126
- });
127
- main().catch(e => {
128
- console.error('❌ Agent 启动失败:', e);
129
- process.exit(1);
130
- });
@@ -1,8 +0,0 @@
1
- /**
2
- * 📄 kg_doc (5合1) + 🏗️ kg_tree (2合1)
3
- * 合并: kg_create_node, kg_delete_node, kg_update_node, kg_read_node, kg_copy_node
4
- * kg_get_tree, kg_get_docs_by_status
5
- */
6
- import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
7
- import { type McpContext } from './shared.js';
8
- export declare function registerDocTools(server: McpServer, ctx: McpContext): void;
@@ -1,285 +0,0 @@
1
- /**
2
- * 📄 kg_doc (5合1) + 🏗️ kg_tree (2合1)
3
- * 合并: kg_create_node, kg_delete_node, kg_update_node, kg_read_node, kg_copy_node
4
- * kg_get_tree, kg_get_docs_by_status
5
- */
6
- import { z } from 'zod';
7
- import { getClient } from '../storage/httpClient.js';
8
- import { decodeObjectStrings } from '../utils.js';
9
- import { wrap, safeTool, crossPrefix, formatDocMarkdown, formatTreeText, countTreeDocs } from './shared.js';
10
- export function registerDocTools(server, ctx) {
11
- const client = () => getClient();
12
- // ========== kg_doc: 文档 CRUD 统一入口 ==========
13
- server.tool('kg_doc', '📄 参考文档 — action: create(创建,建议bindTo关联节点)|read|update|delete|batch_update|copy。⚠️ 不绑定节点的文档会成为孤岛!', {
14
- action: z.enum(['create', 'read', 'update', 'delete', 'batch_update', 'copy'])
15
- .describe('操作类型'),
16
- path: z.string().optional()
17
- .describe('文档路径(如"/前端/组件/Modal")。create/read/update/delete 必填'),
18
- content: z.string().optional()
19
- .describe('Markdown内容 (create/update)'),
20
- summary: z.string().optional()
21
- .describe('一句话简介 (create/update)'),
22
- status: z.string().optional()
23
- .describe('文档状态 (create/update, 默认"已完成")'),
24
- versions: z.array(z.object({
25
- version: z.number(), date: z.string(), changes: z.string()
26
- })).optional().describe('版本记录 (update)'),
27
- bugfixes: z.array(z.object({
28
- date: z.string(), issue: z.string(), solution: z.string()
29
- })).optional().describe('修复记录 (update)'),
30
- updates: z.array(z.object({
31
- nodeId: z.string().describe('文档路径'),
32
- summary: z.string().optional(),
33
- content: z.string().optional(),
34
- status: z.string().optional(),
35
- versions: z.array(z.object({
36
- version: z.number(), date: z.string(), changes: z.string()
37
- })).optional(),
38
- bugfixes: z.array(z.object({
39
- date: z.string(), issue: z.string(), solution: z.string()
40
- })).optional()
41
- })).optional().describe('批量更新数组 (batch_update)'),
42
- targetProject: z.string().optional()
43
- .describe('跨项目读取时的目标项目ID (read)'),
44
- sourceProject: z.string().optional()
45
- .describe('复制源项目ID (copy)'),
46
- sourcePath: z.union([z.string(), z.array(z.string())]).optional()
47
- .describe('复制源路径 (copy, 单个或数组)'),
48
- targetPath: z.string().optional()
49
- .describe('复制目标路径前缀 (copy)'),
50
- bindTo: z.string().optional()
51
- .describe('绑定到流程图节点ID (create/update, 如"n_frontend")'),
52
- bindChart: z.string().optional()
53
- .describe('绑定目标流程图ID (create/update, 默认"main")'),
54
- }, async (args) => safeTool(async () => {
55
- const d = decodeObjectStrings(args);
56
- switch (d.action) {
57
- // ---- CREATE ----
58
- case 'create': {
59
- if (!d.path)
60
- return wrap('❌ create 需要 path');
61
- if (!d.content)
62
- return wrap('❌ create 需要 content');
63
- const doc = await client().createDoc(d.path, {
64
- summary: d.summary || '',
65
- content: d.content,
66
- versions: [{ version: 0.1, date: new Date().toISOString(), changes: '初始创建' }],
67
- bugfixes: [],
68
- status: d.status || '已完成'
69
- });
70
- // 绑定到流程图节点
71
- let bindMsg = '';
72
- if (d.bindTo) {
73
- try {
74
- const chartId = d.bindChart || 'main';
75
- const chart = await client().getFlowchart(chartId);
76
- if (chart) {
77
- const node = chart.nodes?.find((n) => n.id === d.bindTo);
78
- if (node) {
79
- if (!node.boundDocs)
80
- node.boundDocs = [];
81
- if (!node.boundDocs.includes(d.path)) {
82
- node.boundDocs.push(d.path);
83
- }
84
- // Save back via PUT (use the existing save method)
85
- chart.id = chartId;
86
- await client().saveFlowchart(chartId, chart);
87
- bindMsg = `\n🔗 已绑定到节点: ${d.bindTo} [${chartId}]`;
88
- }
89
- else {
90
- bindMsg = `\n⚠️ 节点 "${d.bindTo}" 不存在于流程图 "${chartId}"`;
91
- }
92
- }
93
- }
94
- catch {
95
- bindMsg = `\n⚠️ 绑定失败(流程图操作异常)`;
96
- }
97
- }
98
- // 约束: 未绑定流程图节点 → 强制警告
99
- if (!d.bindTo) {
100
- bindMsg = `\n\n⚠️ 【未关联知识锚点】文档未绑定到流程图节点!\n💡 建议: 使用 kg_flowchart(bind, nodeId, docs:["${d.path}"]) 绑定\n📌 孤立文档无法被 AI 通过节点关联发现`;
101
- }
102
- return wrap(`✅ 文档已创建: ${d.path}${bindMsg}`);
103
- }
104
- // ---- READ ----
105
- case 'read': {
106
- if (!d.path)
107
- return wrap('❌ read 需要 path');
108
- const doc = d.targetProject
109
- ? await client().crossGetDoc(d.targetProject, d.path)
110
- : await client().getDoc(d.path);
111
- if (!doc)
112
- return wrap(d.targetProject ? `文档不存在(项目: ${d.targetProject})` : '文档不存在');
113
- const lines = formatDocMarkdown(doc, d.path);
114
- return wrap(d.targetProject ? crossPrefix(d.targetProject) + lines.join('\n') : lines.join('\n'));
115
- }
116
- // ---- UPDATE ----
117
- case 'update': {
118
- if (!d.path)
119
- return wrap('❌ update 需要 path');
120
- if (d.path === '/' || d.path === '_root') {
121
- // 根文档更新
122
- const existing = await client().getDoc('/');
123
- if (!existing)
124
- return wrap('更新失败(根文档不存在)');
125
- const updates = {};
126
- if (d.summary !== undefined)
127
- updates.summary = d.summary;
128
- if (d.content !== undefined)
129
- updates.content = d.content;
130
- const upd = await client().updateDoc('/', updates);
131
- return wrap(upd ? '✅ 项目介绍已更新' : '更新失败');
132
- }
133
- const existing = await client().getDoc(d.path);
134
- if (!existing)
135
- return wrap('更新失败(文档不存在)');
136
- const updates = {};
137
- if (d.summary !== undefined)
138
- updates.summary = d.summary;
139
- if (d.content !== undefined)
140
- updates.content = d.content;
141
- if (d.status !== undefined)
142
- updates.status = d.status;
143
- if (d.versions !== undefined)
144
- updates.versions = d.versions;
145
- if (d.bugfixes !== undefined)
146
- updates.bugfixes = d.bugfixes;
147
- const doc = await client().updateDoc(d.path, updates);
148
- if (!doc)
149
- return wrap('更新失败');
150
- return wrap(`✅ 文档已更新: ${d.path}\n状态: ${doc.status || '已完成'}\n摘要: ${doc.summary || ''}`);
151
- }
152
- // ---- DELETE ----
153
- case 'delete': {
154
- if (!d.path)
155
- return wrap('❌ delete 需要 path');
156
- const paths = Array.isArray(d.path) ? d.path : [d.path];
157
- const results = [];
158
- for (const p of paths) {
159
- results.push({ path: p, success: await client().deleteDoc(p) });
160
- }
161
- if (paths.length === 1) {
162
- return wrap(results[0].success ? '删除成功' : '删除失败(文档不存在或是根文档)');
163
- }
164
- const ok = results.filter(r => r.success).length;
165
- const failed = results.filter(r => !r.success).map(r => r.path);
166
- let msg = `✅ 批量删除: ${ok}/${paths.length} 成功`;
167
- if (failed.length > 0)
168
- msg += `\n❌ 失败: ${failed.join(', ')}`;
169
- return wrap(msg);
170
- }
171
- // ---- BATCH UPDATE ----
172
- case 'batch_update': {
173
- if (!d.updates || !Array.isArray(d.updates))
174
- return wrap('❌ batch_update 需要 updates 数组');
175
- const results = [];
176
- for (const item of d.updates) {
177
- if (item.nodeId === '/' || item.nodeId === '_root') {
178
- results.push({ path: item.nodeId, success: false });
179
- continue;
180
- }
181
- const existing = await client().getDoc(item.nodeId);
182
- if (!existing) {
183
- results.push({ path: item.nodeId, success: false });
184
- continue;
185
- }
186
- const upd = {};
187
- if (item.summary !== undefined)
188
- upd.summary = item.summary;
189
- if (item.content !== undefined)
190
- upd.content = item.content;
191
- if (item.status !== undefined)
192
- upd.status = item.status;
193
- if (item.versions !== undefined)
194
- upd.versions = item.versions;
195
- if (item.bugfixes !== undefined)
196
- upd.bugfixes = item.bugfixes;
197
- const doc = await client().updateDoc(item.nodeId, upd);
198
- results.push({ path: item.nodeId, success: !!doc });
199
- }
200
- const ok = results.filter(r => r.success).length;
201
- const failed = results.filter(r => !r.success).map(r => r.path);
202
- let msg = `✅ 批量更新: ${ok}/${d.updates.length} 成功`;
203
- if (failed.length > 0)
204
- msg += `\n❌ 失败: ${failed.join(', ')}`;
205
- return wrap(msg);
206
- }
207
- // ---- COPY ----
208
- case 'copy': {
209
- if (!d.sourceProject)
210
- return wrap('❌ copy 需要 sourceProject');
211
- if (!d.sourcePath)
212
- return wrap('❌ copy 需要 sourcePath');
213
- const paths = Array.isArray(d.sourcePath) ? d.sourcePath : [d.sourcePath];
214
- const results = [];
215
- for (const srcPath of paths) {
216
- try {
217
- const doc = await client().crossGetDoc(d.sourceProject, srcPath);
218
- if (!doc) {
219
- results.push({ path: srcPath, success: false, error: '源文档不存在' });
220
- continue;
221
- }
222
- const destPath = d.targetPath
223
- ? `${d.targetPath.replace(/\/$/, '')}${srcPath}`
224
- : srcPath;
225
- const existing = await client().getDoc(destPath);
226
- if (existing) {
227
- await client().updateDoc(destPath, { summary: doc.summary, content: doc.content });
228
- }
229
- else {
230
- await client().createDoc(destPath, { summary: doc.summary, content: doc.content, status: doc.status });
231
- }
232
- results.push({ path: destPath, success: true });
233
- }
234
- catch (e) {
235
- results.push({ path: srcPath, success: false, error: String(e) });
236
- }
237
- }
238
- const ok = results.filter(r => r.success).length;
239
- if (paths.length === 1) {
240
- return wrap(results[0].success
241
- ? `✅ 已复制: ${results[0].path}\n来源: [${d.sourceProject}] ${paths[0]}`
242
- : `❌ 复制失败: ${results[0].error}`);
243
- }
244
- const failed = results.filter(r => !r.success);
245
- let msg = `✅ 批量复制: ${ok}/${paths.length} 成功`;
246
- if (failed.length > 0)
247
- msg += `\n❌ 失败:\n${failed.map(f => ` - ${f.path}: ${f.error}`).join('\n')}`;
248
- return wrap(msg);
249
- }
250
- default:
251
- return wrap(`❌ 未知 action: ${d.action}`);
252
- }
253
- }));
254
- // ========== kg_tree: 知识全景图 ==========
255
- server.tool('kg_tree', '🏗️ 项目知识全景图 ★最快了解项目的方式★ 获取完整知识库目录树(含文档数统计)。传 statusFilter 可筛选特定状态文档。建议任何操作前先调用获取全局视图', {
256
- statusFilter: z.array(z.string()).optional()
257
- .describe('按状态筛选(如["未完成","待修复"]),不传则展示全景目录树'),
258
- targetProject: z.string().optional()
259
- .describe('跨项目查看,填目标项目ID或名称'),
260
- }, async (args) => safeTool(async () => {
261
- // 状态筛选模式
262
- if (args.statusFilter && args.statusFilter.length > 0) {
263
- const docs = args.targetProject
264
- ? await client().crossGetDocsByStatus(args.targetProject, args.statusFilter)
265
- : await client().getDocsByStatus(args.statusFilter);
266
- if (docs.length === 0) {
267
- return wrap(`未找到状态为 [${args.statusFilter.join(', ')}] 的文档${args.targetProject ? `(项目: ${args.targetProject})` : ''}`);
268
- }
269
- const lines = docs.map(d => `- ${d.path} (${d.status || '已完成'}): ${d.summary || '无简介'}`);
270
- const prefix = args.targetProject ? crossPrefix(args.targetProject) : '';
271
- return wrap(`${prefix}找到 ${docs.length} 个文档:\n\n${lines.join('\n')}`);
272
- }
273
- // 全景目录树模式
274
- const tree = args.targetProject
275
- ? await client().crossGetTree(args.targetProject)
276
- : await client().getTree();
277
- if (!tree || tree.length === 0) {
278
- return wrap(args.targetProject ? `知识库为空(项目: ${args.targetProject})` : '知识库为空');
279
- }
280
- const treeText = formatTreeText(tree);
281
- const docCount = countTreeDocs(tree);
282
- const prefix = args.targetProject ? crossPrefix(args.targetProject) : '';
283
- return wrap(`${prefix}共 ${docCount} 个文档\n\n${treeText}`);
284
- }));
285
- }
@@ -1,37 +0,0 @@
1
- /**
2
- * MCP 工具辅助函数
3
- */
4
- import type { DocData } from '../storage/types.js';
5
- /**
6
- * 包装返回结果为 MCP 格式
7
- */
8
- export declare function wrap(text: string): {
9
- content: {
10
- type: "text";
11
- text: string;
12
- }[];
13
- };
14
- /**
15
- * 格式化文档为 Markdown 输出
16
- */
17
- export declare function formatDocMarkdown(doc: DocData, path: string): string[];
18
- interface TreeNode {
19
- path: string;
20
- name: string;
21
- summary?: string;
22
- isDir: boolean;
23
- children?: TreeNode[];
24
- }
25
- /**
26
- * 格式化树为文本
27
- */
28
- export declare function formatTreeText(tree: TreeNode[]): string;
29
- /**
30
- * 统计树中的文档数
31
- */
32
- export declare function countTreeDocs(tree: TreeNode[]): number;
33
- /**
34
- * 格式化文件大小
35
- */
36
- export declare function formatFileSize(bytes: number): string;
37
- export {};