@ppdocs/mcp 3.12.0 → 3.13.1
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 +18 -15
- package/dist/cli.js +56 -41
- package/dist/config.d.ts +23 -3
- package/dist/config.js +94 -36
- package/dist/index.d.ts +1 -1
- package/dist/index.js +45 -17
- package/dist/retainedCoreMatrix.d.ts +19 -0
- package/dist/retainedCoreMatrix.js +40 -0
- package/dist/storage/httpClient.d.ts +6 -75
- package/dist/storage/httpClient.js +5 -177
- package/dist/storage/types.d.ts +0 -35
- package/dist/tools/analyzer.d.ts +1 -4
- package/dist/tools/analyzer.js +4 -7
- package/dist/tools/flowchart.js +1 -4
- package/dist/tools/index.d.ts +8 -10
- package/dist/tools/index.js +8 -32
- package/dist/tools/kg_status.d.ts +1 -1
- package/dist/tools/kg_status.js +4 -6
- package/dist/tools/refs.js +38 -172
- package/dist/tools/shared.d.ts +2 -2
- package/dist/tools/shared.js +1 -1
- package/dist/tools/tasks.d.ts +1 -2
- package/dist/tools/tasks.js +32 -47
- package/dist/tools/workflow.js +2 -3
- package/package.json +1 -1
- package/templates/AGENT.md +2 -5
- package/templates/README.md +0 -0
- package/templates/commands/init.md +0 -0
- package/templates/commands/pp/Zero_Defec_Genesis.md +0 -0
- package/templates/commands/pp/diagnose.md +0 -0
- package/templates/commands/pp/init.md +2 -1
- package/templates/commands/pp/review.md +0 -0
- package/templates/commands/pp/sync.md +2 -1
- package/templates/cursorrules.md +2 -5
- package/templates/hooks/SystemPrompt.md +1 -1
- package/templates/hooks/hook.py +0 -0
- package/templates/kiro-rules/ppdocs.md +2 -5
- package/dist/tools/discussion.d.ts +0 -15
- package/dist/tools/discussion.js +0 -264
- package/dist/tools/doc_query.d.ts +0 -10
- package/dist/tools/doc_query.js +0 -185
- package/dist/tools/files.d.ts +0 -6
- package/dist/tools/files.js +0 -107
- package/dist/tools/init.d.ts +0 -12
- package/dist/tools/init.js +0 -89
- package/dist/tools/meeting.d.ts +0 -7
- package/dist/tools/meeting.js +0 -97
- package/dist/tools/pitfalls.d.ts +0 -6
- package/dist/tools/pitfalls.js +0 -190
- package/dist/tools/projects.d.ts +0 -7
- package/dist/tools/projects.js +0 -19
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 💬 kg_discuss (8 actions)
|
|
3
|
-
* 合并: list, read, create, reply, close, delete, complete, history
|
|
4
|
-
* 统一走 HTTP → Rust 后端 (单一写入者)
|
|
5
|
-
* sender 格式: "projectId:user" (如 "p-ca3sgejg:张三")
|
|
6
|
-
*
|
|
7
|
-
* 权限模型:
|
|
8
|
-
* list/history — 任何人可列出(全局可见)
|
|
9
|
-
* create — 任何人可发起(指定参与项目ID)
|
|
10
|
-
* read/reply — 仅讨论组内成员(initiator + participants)
|
|
11
|
-
* complete/close/delete — 仅发起人
|
|
12
|
-
*/
|
|
13
|
-
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
14
|
-
import { type McpContext } from './shared.js';
|
|
15
|
-
export declare function registerDiscussionTools(server: McpServer, ctx: McpContext): void;
|
package/dist/tools/discussion.js
DELETED
|
@@ -1,264 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 💬 kg_discuss (8 actions)
|
|
3
|
-
* 合并: list, read, create, reply, close, delete, complete, history
|
|
4
|
-
* 统一走 HTTP → Rust 后端 (单一写入者)
|
|
5
|
-
* sender 格式: "projectId:user" (如 "p-ca3sgejg:张三")
|
|
6
|
-
*
|
|
7
|
-
* 权限模型:
|
|
8
|
-
* list/history — 任何人可列出(全局可见)
|
|
9
|
-
* create — 任何人可发起(指定参与项目ID)
|
|
10
|
-
* read/reply — 仅讨论组内成员(initiator + participants)
|
|
11
|
-
* complete/close/delete — 仅发起人
|
|
12
|
-
*/
|
|
13
|
-
import { z } from 'zod';
|
|
14
|
-
import { getClient } from '../storage/httpClient.js';
|
|
15
|
-
import { decodeObjectStrings } from '../utils.js';
|
|
16
|
-
import { wrap, safeTool } from './shared.js';
|
|
17
|
-
function sender(ctx) {
|
|
18
|
-
return `${ctx.projectId}:${ctx.user}:${ctx.agentId}`;
|
|
19
|
-
}
|
|
20
|
-
/** 提取 sender 中的 projectId */
|
|
21
|
-
function senderProjectId(senderStr) {
|
|
22
|
-
return senderStr.split(':')[0] || senderStr;
|
|
23
|
-
}
|
|
24
|
-
/** 检查项目是否是讨论的成员 (发起方 或 参与方) */
|
|
25
|
-
function isMember(brief, projectId) {
|
|
26
|
-
const initiatorPid = senderProjectId(brief.initiator);
|
|
27
|
-
if (initiatorPid === projectId)
|
|
28
|
-
return true;
|
|
29
|
-
return brief.participants.some(p => {
|
|
30
|
-
// participants 可能是 "projectId" 或 "projectId:user"
|
|
31
|
-
return senderProjectId(p) === projectId;
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
/** 检查是否是讨论的发起人 */
|
|
35
|
-
function isInitiator(brief, me, projectId) {
|
|
36
|
-
return brief.initiator === me || senderProjectId(brief.initiator) === projectId;
|
|
37
|
-
}
|
|
38
|
-
/** 根据 ID 获取讨论摘要 (用于权限检查) */
|
|
39
|
-
async function getBrief(discussionId) {
|
|
40
|
-
try {
|
|
41
|
-
const all = await getClient().discussionList();
|
|
42
|
-
return all.find(d => d.id === discussionId) || null;
|
|
43
|
-
}
|
|
44
|
-
catch {
|
|
45
|
-
return null;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
function relativeTime(iso) {
|
|
49
|
-
try {
|
|
50
|
-
const diff = Date.now() - new Date(iso).getTime();
|
|
51
|
-
if (diff < 60000)
|
|
52
|
-
return '刚刚';
|
|
53
|
-
if (diff < 3600000)
|
|
54
|
-
return `${Math.floor(diff / 60000)}分钟前`;
|
|
55
|
-
if (diff < 86400000)
|
|
56
|
-
return `${Math.floor(diff / 3600000)}小时前`;
|
|
57
|
-
return `${Math.floor(diff / 86400000)}天前`;
|
|
58
|
-
}
|
|
59
|
-
catch {
|
|
60
|
-
return iso;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
function formatList(items, ctx) {
|
|
64
|
-
const lines = [
|
|
65
|
-
`本项目: ${sender(ctx)}`,
|
|
66
|
-
``,
|
|
67
|
-
`📋 活跃讨论 (${items.length}个)`,
|
|
68
|
-
``,
|
|
69
|
-
`| ID | 标题 | 发起方 | 参与方 | 摘要 | 回复 | 更新 |`,
|
|
70
|
-
`|:---|:---|:---|:---|:---|:---:|:---|`,
|
|
71
|
-
];
|
|
72
|
-
for (const d of items) {
|
|
73
|
-
const others = d.participants.filter(p => p !== d.initiator).join(', ') || '—';
|
|
74
|
-
const memberTag = isMember(d, ctx.projectId) ? '✅' : '🔒';
|
|
75
|
-
lines.push(`| ${d.id} | ${memberTag} ${d.title} | ${d.initiator} | ${others} | ${d.summary} | ${d.status} | ${relativeTime(d.updatedAt)} |`);
|
|
76
|
-
}
|
|
77
|
-
lines.push('');
|
|
78
|
-
lines.push('✅ = 你是成员(可读写) | 🔒 = 非成员(仅可查看标题)');
|
|
79
|
-
return lines.join('\n');
|
|
80
|
-
}
|
|
81
|
-
function formatDetailView(d) {
|
|
82
|
-
const msgs = d.messages || [];
|
|
83
|
-
const lines = [
|
|
84
|
-
`💬 ${d.title}`,
|
|
85
|
-
`发起: ${d.initiator} | 参与: ${d.participants.length}方 | 状态: ${d.status}`,
|
|
86
|
-
`📌 ${d.summary}`,
|
|
87
|
-
`📊 总消息: ${d.totalCount} | 未读: ${d.unreadCount}`,
|
|
88
|
-
``,
|
|
89
|
-
];
|
|
90
|
-
msgs.forEach((m, i) => {
|
|
91
|
-
const readTag = m.isRead ? '📖' : '🆕';
|
|
92
|
-
lines.push(`--- ${readTag} 消息 ${i + 1}/${msgs.length} [${m.sender}] ${relativeTime(m.timestamp)} ---`);
|
|
93
|
-
// 已读且无 content → 显示摘要
|
|
94
|
-
if (m.isRead && !m.content) {
|
|
95
|
-
lines.push(`[摘要] ${m.summary}`);
|
|
96
|
-
}
|
|
97
|
-
else {
|
|
98
|
-
lines.push(m.content || m.summary || '(无内容)');
|
|
99
|
-
}
|
|
100
|
-
lines.push('');
|
|
101
|
-
});
|
|
102
|
-
return lines.join('\n');
|
|
103
|
-
}
|
|
104
|
-
export function registerDiscussionTools(server, ctx) {
|
|
105
|
-
const client = () => getClient();
|
|
106
|
-
server.tool('kg_discuss', '💬 跨项目讨论 — action 可选: 省略时无 id/ids 则列出讨论,有 id/ids 则读取详情。亦可显式指定 list|read|create|reply|complete|close|delete|history。权限: 仅成员可读写,仅发起人可删除/归档', {
|
|
107
|
-
action: z.enum(['list', 'read', 'create', 'reply', 'complete', 'close', 'delete', 'history']).optional()
|
|
108
|
-
.describe('操作类型(可选:省略时根据 id/ids 自动为 read,否则为 list)'),
|
|
109
|
-
id: z.string().optional()
|
|
110
|
-
.describe('讨论哈希ID (read/reply/complete/close/delete)'),
|
|
111
|
-
ids: z.array(z.string()).optional()
|
|
112
|
-
.describe('批量读取的讨论ID数组 (read)'),
|
|
113
|
-
title: z.string().optional()
|
|
114
|
-
.describe('讨论标题 (create)'),
|
|
115
|
-
participants: z.array(z.string()).optional()
|
|
116
|
-
.describe('参与项目ID数组 (create, 不含自己)'),
|
|
117
|
-
summary: z.string().optional()
|
|
118
|
-
.describe('消息摘要 (create/reply, 不传则自动截取content前50字)'),
|
|
119
|
-
content: z.string().optional()
|
|
120
|
-
.describe('消息详细内容Markdown (create/reply)'),
|
|
121
|
-
conclusion: z.string().optional()
|
|
122
|
-
.describe('结案总结 (close)'),
|
|
123
|
-
newSummary: z.string().optional()
|
|
124
|
-
.describe('更新讨论进展摘要 (reply)'),
|
|
125
|
-
mode: z.enum(['auto', 'full']).optional()
|
|
126
|
-
.describe('读取模式 (read, 默认auto: 已读消息仅返回摘要)'),
|
|
127
|
-
}, async (args) => safeTool(async () => {
|
|
128
|
-
const decoded = decodeObjectStrings(args);
|
|
129
|
-
const me = sender(ctx);
|
|
130
|
-
const myPid = ctx.projectId;
|
|
131
|
-
// 智能默认:未指定 action 时,有 id/ids → read,否则 → list
|
|
132
|
-
let action = decoded.action;
|
|
133
|
-
if (!action) {
|
|
134
|
-
const hasIds = Boolean(decoded.id) || (Array.isArray(decoded.ids) && decoded.ids.length > 0);
|
|
135
|
-
action = hasIds ? 'read' : 'list';
|
|
136
|
-
}
|
|
137
|
-
switch (action) {
|
|
138
|
-
// ============ 公开操作 ============
|
|
139
|
-
case 'list': {
|
|
140
|
-
const all = await client().discussionList();
|
|
141
|
-
if (!Array.isArray(all))
|
|
142
|
-
return wrap(`当前无活跃的讨论 (本项目: ${me})`);
|
|
143
|
-
const active = all.filter(d => isMember(d, myPid));
|
|
144
|
-
if (active.length === 0)
|
|
145
|
-
return wrap(`当前无参与的活跃讨论 (本项目: ${myPid})`);
|
|
146
|
-
return wrap(formatList(active, ctx));
|
|
147
|
-
}
|
|
148
|
-
case 'history': {
|
|
149
|
-
const all = await client().discussionListAll(me);
|
|
150
|
-
if (!Array.isArray(all) || all.length === 0)
|
|
151
|
-
return wrap(`暂无参与过的讨论记录 (身份: ${me})`);
|
|
152
|
-
const lines = [
|
|
153
|
-
`本项目: ${me}`,
|
|
154
|
-
``,
|
|
155
|
-
`📋 讨论历史 (${all.length}个)`,
|
|
156
|
-
``,
|
|
157
|
-
`| ID | 标题 | 发起方 | 状态 | 消息数 | 更新 |`,
|
|
158
|
-
`|:---|:---|:---|:---|:---:|:---|`,
|
|
159
|
-
];
|
|
160
|
-
for (const d of all) {
|
|
161
|
-
const statusIcon = d.status === 'active' ? '🟢' : '⚪';
|
|
162
|
-
lines.push(`| ${d.id} | ${d.title} | ${d.initiator} | ${statusIcon} ${d.status} | ${d.messageCount ?? 0} | ${relativeTime(d.updatedAt)} |`);
|
|
163
|
-
}
|
|
164
|
-
return wrap(lines.join('\n'));
|
|
165
|
-
}
|
|
166
|
-
case 'create': {
|
|
167
|
-
if (!decoded.title)
|
|
168
|
-
return wrap('❌ create 需要 title');
|
|
169
|
-
if (!decoded.participants)
|
|
170
|
-
return wrap('❌ create 需要 participants');
|
|
171
|
-
if (!decoded.content)
|
|
172
|
-
return wrap('❌ create 需要 content');
|
|
173
|
-
const result = await client().discussionCreate(decoded.title, me, decoded.participants, decoded.content, decoded.summary);
|
|
174
|
-
return wrap(`✅ 讨论已发起\nID: ${result.id}\n发起方: ${me}\n参与方: ${decoded.participants.join(', ')}\n\n🔒 权限: 仅以上项目可读写此讨论,仅发起方可删除/归档`);
|
|
175
|
-
}
|
|
176
|
-
// ============ 成员操作 (需验证成员身份) ============
|
|
177
|
-
case 'read': {
|
|
178
|
-
const readIds = decoded.ids || (decoded.id ? [decoded.id] : []);
|
|
179
|
-
if (readIds.length === 0)
|
|
180
|
-
return wrap('❌ read 需要 id 或 ids');
|
|
181
|
-
// 权限检查: 逐个验证成员身份
|
|
182
|
-
const allActive = await client().discussionList();
|
|
183
|
-
const deniedIds = [];
|
|
184
|
-
const allowedIds = [];
|
|
185
|
-
for (const rid of readIds) {
|
|
186
|
-
const brief = allActive.find(d => d.id === rid);
|
|
187
|
-
if (!brief) {
|
|
188
|
-
allowedIds.push(rid); // 找不到就放行(可能是历史讨论)
|
|
189
|
-
}
|
|
190
|
-
else if (isMember(brief, myPid)) {
|
|
191
|
-
allowedIds.push(rid);
|
|
192
|
-
}
|
|
193
|
-
else {
|
|
194
|
-
deniedIds.push(rid);
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
if (deniedIds.length > 0 && allowedIds.length === 0) {
|
|
198
|
-
return wrap(`🔒 无权限: 你 (${myPid}) 不是这些讨论的成员\n拒绝: ${deniedIds.join(', ')}`);
|
|
199
|
-
}
|
|
200
|
-
const readMode = decoded.mode || 'auto';
|
|
201
|
-
const discussions = await client().discussionReadByIds(allowedIds, me, readMode);
|
|
202
|
-
if (!Array.isArray(discussions) || discussions.length === 0)
|
|
203
|
-
return wrap('未找到对应的讨论记录');
|
|
204
|
-
let result = discussions.map(formatDetailView).join('\n\n━━━━━━━━━━━━━━━━━━━━\n\n');
|
|
205
|
-
if (deniedIds.length > 0) {
|
|
206
|
-
result += `\n\n🔒 已跳过 ${deniedIds.length} 个无权限的讨论: ${deniedIds.join(', ')}`;
|
|
207
|
-
}
|
|
208
|
-
return wrap(result);
|
|
209
|
-
}
|
|
210
|
-
case 'reply': {
|
|
211
|
-
if (!decoded.id)
|
|
212
|
-
return wrap('❌ reply 需要 id');
|
|
213
|
-
if (!decoded.content)
|
|
214
|
-
return wrap('❌ reply 需要 content');
|
|
215
|
-
// 权限检查: 仅成员可回复
|
|
216
|
-
const brief = await getBrief(decoded.id);
|
|
217
|
-
if (brief && !isMember(brief, myPid)) {
|
|
218
|
-
return wrap(`🔒 无权限: 你 (${myPid}) 不是讨论 "${brief.title}" 的成员\n成员: ${brief.initiator}, ${brief.participants.join(', ')}`);
|
|
219
|
-
}
|
|
220
|
-
await client().discussionReply(decoded.id, me, decoded.content, decoded.summary, decoded.newSummary);
|
|
221
|
-
return wrap(`✅ 回复成功 (ID: ${decoded.id}, 身份: ${me})`);
|
|
222
|
-
}
|
|
223
|
-
// ============ 发起人操作 (需验证发起人身份) ============
|
|
224
|
-
case 'complete': {
|
|
225
|
-
if (!decoded.id)
|
|
226
|
-
return wrap('❌ complete 需要 id');
|
|
227
|
-
// 权限检查: 仅发起人可标记完成
|
|
228
|
-
const brief = await getBrief(decoded.id);
|
|
229
|
-
if (brief && !isInitiator(brief, me, myPid)) {
|
|
230
|
-
return wrap(`🔒 无权限: 仅发起人 (${brief.initiator}) 可标记完成\n你的身份: ${me}`);
|
|
231
|
-
}
|
|
232
|
-
await client().discussionComplete(decoded.id);
|
|
233
|
-
return wrap(`✅ 讨论已标记完成 (ID: ${decoded.id})`);
|
|
234
|
-
}
|
|
235
|
-
case 'close': {
|
|
236
|
-
if (!decoded.id)
|
|
237
|
-
return wrap('❌ close 需要 id');
|
|
238
|
-
if (!decoded.conclusion)
|
|
239
|
-
return wrap('❌ close 需要 conclusion');
|
|
240
|
-
// 权限检查: 仅发起人可归档
|
|
241
|
-
const brief = await getBrief(decoded.id);
|
|
242
|
-
if (brief && !isInitiator(brief, me, myPid)) {
|
|
243
|
-
return wrap(`🔒 无权限: 仅发起人 (${brief.initiator}) 可归档讨论\n你的身份: ${me}`);
|
|
244
|
-
}
|
|
245
|
-
const result = await client().discussionClose(decoded.id, decoded.conclusion);
|
|
246
|
-
const archivePath = result?.archived_path ? `\n📦 已归档: ${result.archived_path}` : '';
|
|
247
|
-
return wrap(`✅ 讨论已结案并归档 (ID: ${decoded.id})\n📋 ${decoded.conclusion}${archivePath}`);
|
|
248
|
-
}
|
|
249
|
-
case 'delete': {
|
|
250
|
-
if (!decoded.id)
|
|
251
|
-
return wrap('❌ delete 需要 id');
|
|
252
|
-
// 权限检查: 仅发起人可删除
|
|
253
|
-
const brief = await getBrief(decoded.id);
|
|
254
|
-
if (brief && !isInitiator(brief, me, myPid)) {
|
|
255
|
-
return wrap(`🔒 无权限: 仅发起人 (${brief.initiator}) 可删除讨论\n你的身份: ${me}`);
|
|
256
|
-
}
|
|
257
|
-
await client().discussionDelete(decoded.id);
|
|
258
|
-
return wrap(`✅ 讨论已删除 (ID: ${decoded.id})`);
|
|
259
|
-
}
|
|
260
|
-
default:
|
|
261
|
-
return wrap(`❌ 未知 action: ${action}`);
|
|
262
|
-
}
|
|
263
|
-
}));
|
|
264
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* kg_doc — 文档查询视图
|
|
3
|
-
* 纯查询工具,数据来源为流程图节点的 docEntries + description
|
|
4
|
-
* 不创建独立文档,只是提供面向文档的查询入口
|
|
5
|
-
*
|
|
6
|
-
* read 默认返回当前文档,history=true 返回历史列表,index=N 返回指定历史条目
|
|
7
|
-
*/
|
|
8
|
-
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
9
|
-
import { type McpContext } from './shared.js';
|
|
10
|
-
export declare function registerDocQueryTools(server: McpServer, _ctx: McpContext): void;
|
package/dist/tools/doc_query.js
DELETED
|
@@ -1,185 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* kg_doc — 文档查询视图
|
|
3
|
-
* 纯查询工具,数据来源为流程图节点的 docEntries + description
|
|
4
|
-
* 不创建独立文档,只是提供面向文档的查询入口
|
|
5
|
-
*
|
|
6
|
-
* read 默认返回当前文档,history=true 返回历史列表,index=N 返回指定历史条目
|
|
7
|
-
*/
|
|
8
|
-
import { z } from 'zod';
|
|
9
|
-
import { getClient } from '../storage/httpClient.js';
|
|
10
|
-
import { decodeObjectStrings } from '../utils.js';
|
|
11
|
-
import { wrap, safeTool } from './shared.js';
|
|
12
|
-
function normalizeText(value) {
|
|
13
|
-
return (value ?? '').trim().toLowerCase();
|
|
14
|
-
}
|
|
15
|
-
/** 文档专用评分 — 侧重 summary/content/description */
|
|
16
|
-
function scoreDocMatch(node, terms) {
|
|
17
|
-
const description = normalizeText(node.description);
|
|
18
|
-
const entries = node.docEntries ?? [];
|
|
19
|
-
const allSummaries = entries.map((e) => e.summary).join(' ').toLowerCase();
|
|
20
|
-
const allContents = entries.map((e) => e.content).join(' ').toLowerCase();
|
|
21
|
-
const label = normalizeText(node.label);
|
|
22
|
-
let score = 0;
|
|
23
|
-
let matched = 0;
|
|
24
|
-
for (const term of terms) {
|
|
25
|
-
let hit = false;
|
|
26
|
-
if (allSummaries.includes(term)) {
|
|
27
|
-
score += 40;
|
|
28
|
-
hit = true;
|
|
29
|
-
}
|
|
30
|
-
if (allContents.includes(term)) {
|
|
31
|
-
score += 30;
|
|
32
|
-
hit = true;
|
|
33
|
-
}
|
|
34
|
-
if (description.includes(term)) {
|
|
35
|
-
score += 20;
|
|
36
|
-
hit = true;
|
|
37
|
-
}
|
|
38
|
-
if (label.includes(term)) {
|
|
39
|
-
score += 10;
|
|
40
|
-
hit = true;
|
|
41
|
-
}
|
|
42
|
-
if (hit)
|
|
43
|
-
matched += 1;
|
|
44
|
-
}
|
|
45
|
-
return matched === 0 ? 0 : score + matched * 50;
|
|
46
|
-
}
|
|
47
|
-
/** 加载所有流程图的完整数据 */
|
|
48
|
-
async function loadAllCharts() {
|
|
49
|
-
const client = getClient();
|
|
50
|
-
const briefs = (await client.listFlowcharts());
|
|
51
|
-
const loaded = await Promise.all(briefs.map(async (b) => (await client.getFlowchart(b.id))));
|
|
52
|
-
return loaded.filter((c) => Boolean(c));
|
|
53
|
-
}
|
|
54
|
-
/** 格式化日期为简短形式 */
|
|
55
|
-
function shortDate(iso) {
|
|
56
|
-
try {
|
|
57
|
-
return iso.slice(0, 10);
|
|
58
|
-
}
|
|
59
|
-
catch {
|
|
60
|
-
return iso;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
export function registerDocQueryTools(server, _ctx) {
|
|
64
|
-
server.tool('kg_doc', '📘 文档查询视图 — 跨流程图搜索和阅读节点内嵌文档(docEntries + description)。纯查询,不创建独立文档。\n' +
|
|
65
|
-
'调用方式:search(query:"关键词") 全文搜索 | list 列出有文档的节点 | read(nodeId) 读取当前文档。\n' +
|
|
66
|
-
'read(nodeId, history:true) 查看历史列表(标题+日期) | read(nodeId, index:N) 读取第N条历史详情(0=最新)。\n' +
|
|
67
|
-
'actions: search(全文搜索)|list(文档列表)|read(读取文档,支持history/index参数)', {
|
|
68
|
-
action: z.enum(['search', 'list', 'read']).describe('操作类型 (Allowed: search, list, read)'),
|
|
69
|
-
query: z.string().optional().describe('搜索关键词 (search)'),
|
|
70
|
-
nodeId: z.string().optional().describe('节点ID (read)'),
|
|
71
|
-
chartId: z.string().optional().describe('限定流程图ID'),
|
|
72
|
-
history: z.boolean().optional().describe('read时传true返回历史列表(标题+日期)'),
|
|
73
|
-
index: z.number().optional().describe('read时指定历史条目序号(0=最新, 1=次新...)'),
|
|
74
|
-
limit: z.number().optional().describe('搜索结果限制, default 10'),
|
|
75
|
-
}, async (args) => safeTool(async () => {
|
|
76
|
-
const decoded = decodeObjectStrings(args);
|
|
77
|
-
switch (decoded.action) {
|
|
78
|
-
case 'search': {
|
|
79
|
-
const rawQuery = decoded.query?.trim();
|
|
80
|
-
if (!rawQuery)
|
|
81
|
-
return wrap('search requires query.');
|
|
82
|
-
const terms = rawQuery.toLowerCase().split(/\s+/).filter(Boolean);
|
|
83
|
-
const charts = decoded.chartId
|
|
84
|
-
? [(await getClient().getFlowchart(decoded.chartId))].filter((c) => Boolean(c))
|
|
85
|
-
: await loadAllCharts();
|
|
86
|
-
const matches = charts
|
|
87
|
-
.flatMap((chart) => chart.nodes
|
|
88
|
-
.filter((n) => (n.docEntries && n.docEntries.length > 0) || n.description)
|
|
89
|
-
.map((node) => ({ chart, node, score: scoreDocMatch(node, terms) })))
|
|
90
|
-
.filter((m) => m.score > 0)
|
|
91
|
-
.sort((a, b) => b.score - a.score)
|
|
92
|
-
.slice(0, decoded.limit ?? 10);
|
|
93
|
-
if (matches.length === 0)
|
|
94
|
-
return wrap(`No docs matched: ${rawQuery}`);
|
|
95
|
-
const lines = [`Doc search: "${rawQuery}" (${matches.length} results)`, ''];
|
|
96
|
-
for (const { chart, node } of matches) {
|
|
97
|
-
const docCount = node.docEntries?.length ?? 0;
|
|
98
|
-
const latest = docCount > 0 ? node.docEntries[docCount - 1] : null;
|
|
99
|
-
lines.push(`- **${node.label}** [${chart.id}/${node.id}] history=${docCount}`);
|
|
100
|
-
if (latest)
|
|
101
|
-
lines.push(` current: ${shortDate(latest.date)} — ${latest.summary}`);
|
|
102
|
-
if (node.description)
|
|
103
|
-
lines.push(` desc: ${node.description.slice(0, 80)}${node.description.length > 80 ? '...' : ''}`);
|
|
104
|
-
}
|
|
105
|
-
return wrap(lines.join('\n'));
|
|
106
|
-
}
|
|
107
|
-
case 'list': {
|
|
108
|
-
const charts = decoded.chartId
|
|
109
|
-
? [(await getClient().getFlowchart(decoded.chartId))].filter((c) => Boolean(c))
|
|
110
|
-
: await loadAllCharts();
|
|
111
|
-
const nodesWithDocs = charts.flatMap((chart) => chart.nodes
|
|
112
|
-
.filter((n) => (n.docEntries && n.docEntries.length > 0) || n.description)
|
|
113
|
-
.map((n) => ({ chart, node: n })));
|
|
114
|
-
if (nodesWithDocs.length === 0)
|
|
115
|
-
return wrap('No nodes with documentation found.');
|
|
116
|
-
const lines = [`Documented nodes: ${nodesWithDocs.length}`, ''];
|
|
117
|
-
for (const { chart, node } of nodesWithDocs) {
|
|
118
|
-
const docCount = node.docEntries?.length ?? 0;
|
|
119
|
-
const latest = docCount > 0 ? node.docEntries[docCount - 1] : null;
|
|
120
|
-
const latestInfo = latest ? ` | ${shortDate(latest.date)} ${latest.summary}` : '';
|
|
121
|
-
lines.push(`- ${node.label} [${chart.id}/${node.id}] history=${docCount}${latestInfo}`);
|
|
122
|
-
}
|
|
123
|
-
return wrap(lines.join('\n'));
|
|
124
|
-
}
|
|
125
|
-
case 'read': {
|
|
126
|
-
if (!decoded.nodeId)
|
|
127
|
-
return wrap('read requires nodeId.');
|
|
128
|
-
const chartId = decoded.chartId || 'main';
|
|
129
|
-
const chart = (await getClient().getFlowchart(chartId));
|
|
130
|
-
if (!chart)
|
|
131
|
-
return wrap(`Flowchart not found: ${chartId}`);
|
|
132
|
-
const node = chart.nodes.find((n) => n.id === decoded.nodeId);
|
|
133
|
-
if (!node)
|
|
134
|
-
return wrap(`Node not found: ${decoded.nodeId} in ${chartId}`);
|
|
135
|
-
const entries = node.docEntries ?? [];
|
|
136
|
-
// history=true → 列出历史记录(标题+日期)
|
|
137
|
-
if (decoded.history) {
|
|
138
|
-
if (entries.length === 0)
|
|
139
|
-
return wrap(`No history for ${node.label} [${chartId}/${decoded.nodeId}]`);
|
|
140
|
-
const lines = [`## ${node.label} — history (${entries.length})`, ''];
|
|
141
|
-
for (let i = entries.length - 1; i >= 0; i--) {
|
|
142
|
-
const e = entries[i];
|
|
143
|
-
const tag = i === entries.length - 1 ? ' (current)' : '';
|
|
144
|
-
lines.push(`${entries.length - 1 - i}. ${shortDate(e.date)} — ${e.summary}${tag}`);
|
|
145
|
-
}
|
|
146
|
-
lines.push('', '_Use read(nodeId, index:N) to view specific entry._');
|
|
147
|
-
return wrap(lines.join('\n'));
|
|
148
|
-
}
|
|
149
|
-
// index=N → 读取指定历史条目 (0=最新)
|
|
150
|
-
if (decoded.index !== undefined) {
|
|
151
|
-
const idx = entries.length - 1 - decoded.index;
|
|
152
|
-
if (idx < 0 || idx >= entries.length) {
|
|
153
|
-
return wrap(`Index out of range: ${decoded.index} (total: ${entries.length})`);
|
|
154
|
-
}
|
|
155
|
-
const entry = entries[idx];
|
|
156
|
-
const lines = [
|
|
157
|
-
`## ${node.label} — #${decoded.index}`,
|
|
158
|
-
`${shortDate(entry.date)} — ${entry.summary}`,
|
|
159
|
-
'',
|
|
160
|
-
entry.content,
|
|
161
|
-
];
|
|
162
|
-
return wrap(lines.join('\n'));
|
|
163
|
-
}
|
|
164
|
-
// 默认: 返回当前文档 (description + 最新 docEntry)
|
|
165
|
-
const lines = [`## ${node.label} [${chartId}/${node.id}]`, ''];
|
|
166
|
-
if (node.description) {
|
|
167
|
-
lines.push(node.description, '');
|
|
168
|
-
}
|
|
169
|
-
if (entries.length > 0) {
|
|
170
|
-
const latest = entries[entries.length - 1];
|
|
171
|
-
lines.push(`---`, `**${latest.summary}** (${shortDate(latest.date)})`, '', latest.content);
|
|
172
|
-
if (entries.length > 1) {
|
|
173
|
-
lines.push('', `_${entries.length - 1} older entries. Use read(nodeId, history:true) to browse._`);
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
else if (!node.description) {
|
|
177
|
-
lines.push('_No documentation._');
|
|
178
|
-
}
|
|
179
|
-
return wrap(lines.join('\n'));
|
|
180
|
-
}
|
|
181
|
-
default:
|
|
182
|
-
return wrap(`Unknown action: ${String(decoded.action)}`);
|
|
183
|
-
}
|
|
184
|
-
}));
|
|
185
|
-
}
|
package/dist/tools/files.d.ts
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 📁 kg_files — 文件浏览/读取/下载 (上传已移除,全部本地化)
|
|
3
|
-
* 公共文件池: public_list, public_read, public_download, public_delete, public_mkdir, public_rename
|
|
4
|
-
*/
|
|
5
|
-
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
6
|
-
export declare function registerFileTools(server: McpServer): void;
|
package/dist/tools/files.js
DELETED
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 📁 kg_files — 文件浏览/读取/下载 (上传已移除,全部本地化)
|
|
3
|
-
* 公共文件池: public_list, public_read, public_download, public_delete, public_mkdir, public_rename
|
|
4
|
-
*/
|
|
5
|
-
import { z } from 'zod';
|
|
6
|
-
import { getClient } from '../storage/httpClient.js';
|
|
7
|
-
import { wrap, safeTool, crossPrefix, formatFileSize } from './shared.js';
|
|
8
|
-
export function registerFileTools(server) {
|
|
9
|
-
const client = () => getClient();
|
|
10
|
-
server.tool('kg_files', '📁 项目文件操作 — 浏览目录、读取文件、下载。action: list(目录浏览)|read(读取文件)|download(下载文件)|public_list(公共文件池浏览)|public_read(读取公共文件)|public_download(下载公共文件)|public_delete(删除公共文件)|public_mkdir(创建公共池子目录)|public_rename(重命名公共文件)', {
|
|
11
|
-
action: z.enum(['list', 'read', 'download', 'public_list', 'public_read', 'public_download', 'public_delete', 'public_mkdir', 'public_rename'])
|
|
12
|
-
.describe('操作类型'),
|
|
13
|
-
path: z.string().optional()
|
|
14
|
-
.describe('文件路径 (read/download/public_read/public_download/public_delete/public_mkdir, 如"src/main.ts")'),
|
|
15
|
-
dir: z.string().optional()
|
|
16
|
-
.describe('子目录路径 (list/public_list, 如"src/components")'),
|
|
17
|
-
localPath: z.string().optional()
|
|
18
|
-
.describe('本地保存路径 (download/public_download)'),
|
|
19
|
-
newName: z.string().optional()
|
|
20
|
-
.describe('新名称 (public_rename)'),
|
|
21
|
-
targetProject: z.string().optional()
|
|
22
|
-
.describe('跨项目操作的目标项目ID'),
|
|
23
|
-
}, async (args) => safeTool(async () => {
|
|
24
|
-
switch (args.action) {
|
|
25
|
-
case 'list': {
|
|
26
|
-
const files = args.targetProject
|
|
27
|
-
? await client().crossListFiles(args.targetProject, args.dir)
|
|
28
|
-
: await client().listFiles(args.dir);
|
|
29
|
-
if (files.length === 0)
|
|
30
|
-
return wrap('目录为空');
|
|
31
|
-
const lines = files.map(f => {
|
|
32
|
-
const icon = f.isDir ? '📁' : '📄';
|
|
33
|
-
const size = f.isDir ? '' : ` (${formatFileSize(f.size)})`;
|
|
34
|
-
return `${icon} ${f.name}${size}`;
|
|
35
|
-
});
|
|
36
|
-
const prefix = args.targetProject ? crossPrefix(args.targetProject) : '';
|
|
37
|
-
const dirLabel = args.dir || '/';
|
|
38
|
-
return wrap(`${prefix}📂 ${dirLabel} (${files.length} 项)\n\n${lines.join('\n')}`);
|
|
39
|
-
}
|
|
40
|
-
case 'read': {
|
|
41
|
-
if (!args.path)
|
|
42
|
-
return wrap('❌ read 需要 path');
|
|
43
|
-
const content = args.targetProject
|
|
44
|
-
? await client().crossReadFile(args.targetProject, args.path)
|
|
45
|
-
: await client().readFile(args.path);
|
|
46
|
-
const prefix = args.targetProject ? crossPrefix(args.targetProject) : '';
|
|
47
|
-
return wrap(`${prefix}📄 ${args.path}\n\n\`\`\`\n${content}\n\`\`\``);
|
|
48
|
-
}
|
|
49
|
-
case 'download': {
|
|
50
|
-
if (!args.path)
|
|
51
|
-
return wrap('❌ download 需要 path');
|
|
52
|
-
const result = args.targetProject
|
|
53
|
-
? await client().crossDownload(args.targetProject, args.path, args.localPath)
|
|
54
|
-
: await client().download(args.path, args.localPath);
|
|
55
|
-
const prefix = args.targetProject ? crossPrefix(args.targetProject) : '';
|
|
56
|
-
return wrap(`${prefix}✅ 已下载\n\n- 本地路径: ${result.localPath}\n- 文件数量: ${result.fileCount}`);
|
|
57
|
-
}
|
|
58
|
-
// ===== 公共文件池 =====
|
|
59
|
-
case 'public_list': {
|
|
60
|
-
const files = await client().publicFilesList(args.dir);
|
|
61
|
-
if (files.length === 0)
|
|
62
|
-
return wrap('📦 公共文件池' + (args.dir ? ` /${args.dir}` : '') + ' 为空');
|
|
63
|
-
const lines = files.map(f => {
|
|
64
|
-
const icon = f.isDir ? '📁' : '📄';
|
|
65
|
-
const size = f.isDir ? '' : ` (${formatFileSize(f.size)})`;
|
|
66
|
-
return `${icon} ${f.name}${size}`;
|
|
67
|
-
});
|
|
68
|
-
const dirLabel = args.dir || '/';
|
|
69
|
-
return wrap(`📦 公共文件池 ${dirLabel} (${files.length} 项)\n\n${lines.join('\n')}`);
|
|
70
|
-
}
|
|
71
|
-
case 'public_read': {
|
|
72
|
-
if (!args.path)
|
|
73
|
-
return wrap('❌ public_read 需要 path');
|
|
74
|
-
const content = await client().publicFilesRead(args.path);
|
|
75
|
-
return wrap(`📦 公共文件 ${args.path}\n\n\`\`\`\n${content}\n\`\`\``);
|
|
76
|
-
}
|
|
77
|
-
case 'public_download': {
|
|
78
|
-
if (!args.path)
|
|
79
|
-
return wrap('❌ public_download 需要 path');
|
|
80
|
-
const result = await client().publicFilesDownload(args.path, args.localPath);
|
|
81
|
-
return wrap(`✅ 已从公共文件池下载\n\n- 本地路径: ${result.localPath}\n- 文件数量: ${result.fileCount}`);
|
|
82
|
-
}
|
|
83
|
-
case 'public_delete': {
|
|
84
|
-
if (!args.path)
|
|
85
|
-
return wrap('❌ public_delete 需要 path');
|
|
86
|
-
await client().publicFilesDelete(args.path);
|
|
87
|
-
return wrap(`✅ 已从公共文件池删除: ${args.path}`);
|
|
88
|
-
}
|
|
89
|
-
case 'public_mkdir': {
|
|
90
|
-
if (!args.path)
|
|
91
|
-
return wrap('❌ public_mkdir 需要 path');
|
|
92
|
-
await client().publicFilesMkdir(args.path);
|
|
93
|
-
return wrap(`✅ 已创建公共文件池目录: /${args.path}`);
|
|
94
|
-
}
|
|
95
|
-
case 'public_rename': {
|
|
96
|
-
if (!args.path)
|
|
97
|
-
return wrap('❌ public_rename 需要 path');
|
|
98
|
-
if (!args.newName)
|
|
99
|
-
return wrap('❌ public_rename 需要 newName');
|
|
100
|
-
await client().publicFilesRename(args.path, args.newName);
|
|
101
|
-
return wrap(`✅ 已重命名: ${args.path} → ${args.newName}`);
|
|
102
|
-
}
|
|
103
|
-
default:
|
|
104
|
-
return wrap(`❌ 未知 action: ${args.action}`);
|
|
105
|
-
}
|
|
106
|
-
}));
|
|
107
|
-
}
|
package/dist/tools/init.d.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* kg_init 工具 — 项目上下文初始化
|
|
3
|
-
*
|
|
4
|
-
* 解决全局共用 MCP 进程时的多项目隔离问题:
|
|
5
|
-
* - 智能体调用 kg_init 传入 projectPath
|
|
6
|
-
* - MCP 从该路径读取 .ppdocs 配置
|
|
7
|
-
* - 重新初始化 HTTP Client 指向正确项目
|
|
8
|
-
* - 更新共享 McpContext,所有工具自动获取新 projectId
|
|
9
|
-
*/
|
|
10
|
-
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
11
|
-
import { type McpContext } from './shared.js';
|
|
12
|
-
export declare function registerInitTool(server: McpServer, ctx: McpContext, onProjectChange: (newProjectId: string, newApiUrl: string) => void): void;
|