@ppdocs/mcp 3.10.0 → 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 +27 -2
- package/dist/index.js +48 -1
- package/dist/storage/httpClient.d.ts +22 -1
- package/dist/storage/httpClient.js +50 -0
- package/dist/storage/types.d.ts +62 -0
- package/dist/tools/flowchart.js +1 -0
- package/dist/tools/index.d.ts +2 -2
- package/dist/tools/index.js +4 -2
- package/dist/tools/pitfalls.d.ts +6 -0
- package/dist/tools/pitfalls.js +190 -0
- package/dist/tools/refs.js +148 -8
- package/dist/tools/tasks.js +2 -1
- package/package.json +1 -1
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/index.js
CHANGED
|
@@ -19,6 +19,51 @@ import { loadConfig, generateAgentId } from './config.js';
|
|
|
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,7 +77,9 @@ 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 初始化项目上下文...`);
|
|
@@ -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}` : '';
|
package/dist/storage/types.d.ts
CHANGED
|
@@ -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
|
+
}
|
package/dist/tools/flowchart.js
CHANGED
|
@@ -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', {
|
package/dist/tools/index.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* MCP 工具注册入口
|
|
3
|
-
*
|
|
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 (
|
|
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个)
|
package/dist/tools/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* MCP 工具注册入口
|
|
3
|
-
*
|
|
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 (
|
|
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协作
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 💡 kg_pitfall — 踩坑经验管理
|
|
3
|
+
* 记录项目中遇到的问题、根因和解决方案,避免重复踩坑
|
|
4
|
+
*/
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
import { getClient } from '../storage/httpClient.js';
|
|
7
|
+
import { decodeObjectStrings } from '../utils.js';
|
|
8
|
+
import { safeTool, wrap } from './shared.js';
|
|
9
|
+
const SEVERITY_ICONS = {
|
|
10
|
+
critical: '🔴',
|
|
11
|
+
warning: '🟡',
|
|
12
|
+
info: '🔵',
|
|
13
|
+
};
|
|
14
|
+
const STATUS_ICONS = {
|
|
15
|
+
open: '🔓',
|
|
16
|
+
resolved: '✅',
|
|
17
|
+
recurring: '🔄',
|
|
18
|
+
};
|
|
19
|
+
export function registerPitfallTools(server) {
|
|
20
|
+
const client = () => getClient();
|
|
21
|
+
server.tool('kg_pitfall', '💡 踩坑经验 — 记录和查询项目中的踩坑经验,避免重复踩坑。\n' +
|
|
22
|
+
'遇到问题时用 create 记录现象和根因,找到方案后用 resolve 标记解决。\n' +
|
|
23
|
+
'下次遇到类似问题先 search 查历史经验。\n' +
|
|
24
|
+
'actions: create|get|update|search|resolve|list|delete', {
|
|
25
|
+
action: z.enum(['create', 'get', 'update', 'search', 'resolve', 'list', 'delete'])
|
|
26
|
+
.describe('操作类型'),
|
|
27
|
+
id: z.string().optional().describe('踩坑经验 ID (get/update/resolve/delete)'),
|
|
28
|
+
title: z.string().optional().describe('标题 (create)'),
|
|
29
|
+
category: z.string().optional().describe('分类: protocol/concurrency/config/logic/ui/other (create/update/search)'),
|
|
30
|
+
symptom: z.string().optional().describe('现象描述 Markdown (create/update)'),
|
|
31
|
+
root_cause: z.string().optional().describe('根因分析 Markdown (create/update)'),
|
|
32
|
+
solution: z.string().optional().describe('解决方案 Markdown (create/update/resolve)'),
|
|
33
|
+
prevention: z.string().optional().describe('预防措施 (create/update)'),
|
|
34
|
+
severity: z.string().optional().describe('严重程度: critical/warning/info (create/update)'),
|
|
35
|
+
related_nodes: z.array(z.string()).optional().describe('关联流程图节点 ID 列表 (create/update)'),
|
|
36
|
+
source_task: z.string().optional().describe('来源任务 ID (create)'),
|
|
37
|
+
tags: z.array(z.string()).optional().describe('标签列表 (create/update)'),
|
|
38
|
+
query: z.string().optional().describe('搜索关键词 (search)'),
|
|
39
|
+
status: z.string().optional().describe('状态过滤: open/resolved/recurring (search/list)'),
|
|
40
|
+
}, async (args) => safeTool(async () => {
|
|
41
|
+
const decoded = decodeObjectStrings(args);
|
|
42
|
+
switch (decoded.action) {
|
|
43
|
+
case 'list': {
|
|
44
|
+
const pitfalls = decoded.status || decoded.category
|
|
45
|
+
? await client().searchPitfalls('', decoded.status, decoded.category)
|
|
46
|
+
: await client().listPitfalls();
|
|
47
|
+
if (pitfalls.length === 0)
|
|
48
|
+
return wrap('当前没有踩坑经验记录');
|
|
49
|
+
const lines = [
|
|
50
|
+
`💡 踩坑经验 (${pitfalls.length})`,
|
|
51
|
+
'',
|
|
52
|
+
'| 状态 | 严重 | 标题 | 分类 | 标签 | 更新 |',
|
|
53
|
+
'|:-----|:-----|:-----|:-----|:-----|:-----|',
|
|
54
|
+
];
|
|
55
|
+
for (const p of pitfalls) {
|
|
56
|
+
const statusIcon = STATUS_ICONS[p.status] || p.status;
|
|
57
|
+
const sevIcon = SEVERITY_ICONS[p.severity] || p.severity;
|
|
58
|
+
const tags = p.tags.length > 0 ? p.tags.join(',') : '-';
|
|
59
|
+
const date = p.updated_at.substring(0, 10);
|
|
60
|
+
lines.push(`| ${statusIcon} | ${sevIcon} | ${p.title} | ${p.category} | ${tags} | ${date} |`);
|
|
61
|
+
}
|
|
62
|
+
return wrap(lines.join('\n'));
|
|
63
|
+
}
|
|
64
|
+
case 'get': {
|
|
65
|
+
if (!decoded.id)
|
|
66
|
+
return wrap('❌ get 需要 id');
|
|
67
|
+
const p = await client().getPitfall(decoded.id);
|
|
68
|
+
if (!p)
|
|
69
|
+
return wrap(`未找到踩坑经验: ${decoded.id}`);
|
|
70
|
+
const sevIcon = SEVERITY_ICONS[p.severity] || p.severity;
|
|
71
|
+
const statusIcon = STATUS_ICONS[p.status] || p.status;
|
|
72
|
+
const lines = [
|
|
73
|
+
`# ${sevIcon} ${p.title}`,
|
|
74
|
+
'',
|
|
75
|
+
`| 字段 | 值 |`,
|
|
76
|
+
`|:---|:---|`,
|
|
77
|
+
`| ID | ${p.id} |`,
|
|
78
|
+
`| 状态 | ${statusIcon} ${p.status} |`,
|
|
79
|
+
`| 严重程度 | ${sevIcon} ${p.severity} |`,
|
|
80
|
+
`| 分类 | ${p.category} |`,
|
|
81
|
+
`| 标签 | ${p.tags.join(', ') || '-'} |`,
|
|
82
|
+
`| 关联节点 | ${p.related_nodes.join(', ') || '-'} |`,
|
|
83
|
+
];
|
|
84
|
+
if (p.source_task)
|
|
85
|
+
lines.push(`| 来源任务 | ${p.source_task} |`);
|
|
86
|
+
lines.push(`| 创建 | ${p.created_at.substring(0, 19)} |`);
|
|
87
|
+
lines.push(`| 更新 | ${p.updated_at.substring(0, 19)} |`);
|
|
88
|
+
if (p.resolved_at)
|
|
89
|
+
lines.push(`| 解决 | ${p.resolved_at.substring(0, 19)} |`);
|
|
90
|
+
if (p.symptom) {
|
|
91
|
+
lines.push('', '## 现象', '', p.symptom);
|
|
92
|
+
}
|
|
93
|
+
if (p.root_cause) {
|
|
94
|
+
lines.push('', '## 根因', '', p.root_cause);
|
|
95
|
+
}
|
|
96
|
+
if (p.solution) {
|
|
97
|
+
lines.push('', '## 解决方案', '', p.solution);
|
|
98
|
+
}
|
|
99
|
+
if (p.prevention) {
|
|
100
|
+
lines.push('', '## 预防措施', '', p.prevention);
|
|
101
|
+
}
|
|
102
|
+
return wrap(lines.join('\n'));
|
|
103
|
+
}
|
|
104
|
+
case 'create': {
|
|
105
|
+
if (!decoded.title)
|
|
106
|
+
return wrap('❌ create 需要 title');
|
|
107
|
+
const pitfall = await client().createPitfall({
|
|
108
|
+
title: decoded.title,
|
|
109
|
+
category: decoded.category,
|
|
110
|
+
symptom: decoded.symptom,
|
|
111
|
+
root_cause: decoded.root_cause,
|
|
112
|
+
solution: decoded.solution,
|
|
113
|
+
prevention: decoded.prevention,
|
|
114
|
+
severity: decoded.severity,
|
|
115
|
+
related_nodes: decoded.related_nodes,
|
|
116
|
+
source_task: decoded.source_task,
|
|
117
|
+
tags: decoded.tags,
|
|
118
|
+
});
|
|
119
|
+
return wrap(`✅ 踩坑经验已创建\n\n- ID: ${pitfall.id}\n- 标题: ${pitfall.title}\n- 分类: ${pitfall.category}\n- 严重程度: ${pitfall.severity}`);
|
|
120
|
+
}
|
|
121
|
+
case 'update': {
|
|
122
|
+
if (!decoded.id)
|
|
123
|
+
return wrap('❌ update 需要 id');
|
|
124
|
+
const existing = await client().getPitfall(decoded.id);
|
|
125
|
+
if (!existing)
|
|
126
|
+
return wrap(`❌ 未找到踩坑经验: ${decoded.id}`);
|
|
127
|
+
const updated = {
|
|
128
|
+
...existing,
|
|
129
|
+
...(decoded.title && { title: decoded.title }),
|
|
130
|
+
...(decoded.category && { category: decoded.category }),
|
|
131
|
+
...(decoded.symptom && { symptom: decoded.symptom }),
|
|
132
|
+
...(decoded.root_cause && { root_cause: decoded.root_cause }),
|
|
133
|
+
...(decoded.solution && { solution: decoded.solution }),
|
|
134
|
+
...(decoded.prevention && { prevention: decoded.prevention }),
|
|
135
|
+
...(decoded.severity && { severity: decoded.severity }),
|
|
136
|
+
...(decoded.related_nodes && { related_nodes: decoded.related_nodes }),
|
|
137
|
+
...(decoded.tags && { tags: decoded.tags }),
|
|
138
|
+
updated_at: new Date().toISOString(),
|
|
139
|
+
};
|
|
140
|
+
await client().savePitfall(updated);
|
|
141
|
+
return wrap(`✅ 踩坑经验已更新 (${decoded.id})`);
|
|
142
|
+
}
|
|
143
|
+
case 'resolve': {
|
|
144
|
+
if (!decoded.id)
|
|
145
|
+
return wrap('❌ resolve 需要 id');
|
|
146
|
+
const existing = await client().getPitfall(decoded.id);
|
|
147
|
+
if (!existing)
|
|
148
|
+
return wrap(`❌ 未找到踩坑经验: ${decoded.id}`);
|
|
149
|
+
const now = new Date().toISOString();
|
|
150
|
+
const resolved = {
|
|
151
|
+
...existing,
|
|
152
|
+
status: 'resolved',
|
|
153
|
+
resolved_at: now,
|
|
154
|
+
updated_at: now,
|
|
155
|
+
...(decoded.solution && { solution: decoded.solution }),
|
|
156
|
+
...(decoded.prevention && { prevention: decoded.prevention }),
|
|
157
|
+
};
|
|
158
|
+
await client().savePitfall(resolved);
|
|
159
|
+
return wrap(`✅ 踩坑经验已标记为解决 (${decoded.id})`);
|
|
160
|
+
}
|
|
161
|
+
case 'search': {
|
|
162
|
+
const query = decoded.query || '';
|
|
163
|
+
const results = await client().searchPitfalls(query, decoded.status, decoded.category);
|
|
164
|
+
if (results.length === 0)
|
|
165
|
+
return wrap(`未找到匹配的踩坑经验 (搜索: "${query}")`);
|
|
166
|
+
const lines = [
|
|
167
|
+
`💡 搜索结果: "${query}" (${results.length} 条)`,
|
|
168
|
+
'',
|
|
169
|
+
'| 状态 | 严重 | 标题 | 分类 | ID |',
|
|
170
|
+
'|:-----|:-----|:-----|:-----|:---|',
|
|
171
|
+
];
|
|
172
|
+
for (const p of results) {
|
|
173
|
+
const statusIcon = STATUS_ICONS[p.status] || p.status;
|
|
174
|
+
const sevIcon = SEVERITY_ICONS[p.severity] || p.severity;
|
|
175
|
+
lines.push(`| ${statusIcon} | ${sevIcon} | ${p.title} | ${p.category} | ${p.id} |`);
|
|
176
|
+
}
|
|
177
|
+
lines.push('', '> 用 `kg_pitfall(get, id:"xxx")` 查看详情');
|
|
178
|
+
return wrap(lines.join('\n'));
|
|
179
|
+
}
|
|
180
|
+
case 'delete': {
|
|
181
|
+
if (!decoded.id)
|
|
182
|
+
return wrap('❌ delete 需要 id');
|
|
183
|
+
const ok = await client().deletePitfall(decoded.id);
|
|
184
|
+
return wrap(ok ? `✅ 踩坑经验已删除 (${decoded.id})` : `ℹ️ 未找到踩坑经验 (${decoded.id})`);
|
|
185
|
+
}
|
|
186
|
+
default:
|
|
187
|
+
return wrap(`❌ 未知 action: ${decoded.action}`);
|
|
188
|
+
}
|
|
189
|
+
}));
|
|
190
|
+
}
|
package/dist/tools/refs.js
CHANGED
|
@@ -2,6 +2,13 @@ import { z } from 'zod';
|
|
|
2
2
|
import { getClient } from '../storage/httpClient.js';
|
|
3
3
|
import { decodeObjectStrings } from '../utils.js';
|
|
4
4
|
import { safeTool, wrap } from './shared.js';
|
|
5
|
+
function formatBytes(bytes) {
|
|
6
|
+
if (bytes < 1024)
|
|
7
|
+
return `${bytes} B`;
|
|
8
|
+
if (bytes < 1048576)
|
|
9
|
+
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
10
|
+
return `${(bytes / 1048576).toFixed(1)} MB`;
|
|
11
|
+
}
|
|
5
12
|
const linkSchema = z.object({
|
|
6
13
|
url: z.string(),
|
|
7
14
|
label: z.string(),
|
|
@@ -19,8 +26,10 @@ export function registerReferenceTools(server) {
|
|
|
19
26
|
const client = () => getClient();
|
|
20
27
|
server.tool('kg_ref', '📎 外部参考 — 管理项目引用的第三方资料、链接、脚本和样例文件。\n' +
|
|
21
28
|
'涉及参考资料时主动调用:list 查看已有 → get 读详情 → read_file 读附件内容 → save 新增。\n' +
|
|
22
|
-
'
|
|
23
|
-
|
|
29
|
+
'★ fetch: 输入URL自动拉取网页内容到本地,生成解析报告,支持持续更新(refetch)。\n' +
|
|
30
|
+
'★ import_file: 导入本地文件内容到参考系统(自动保存内容副本+版本追踪)。\n' +
|
|
31
|
+
'actions: list|get|save|delete|read_file|fetch|refetch|import_file|reimport', {
|
|
32
|
+
action: z.enum(['list', 'get', 'save', 'delete', 'read_file', 'fetch', 'refetch', 'import_file', 'reimport']).describe('操作类型'),
|
|
24
33
|
id: z.string().optional().describe('参考 ID'),
|
|
25
34
|
title: z.string().optional().describe('参考标题 (save)'),
|
|
26
35
|
summary: z.string().optional().describe('Markdown 总结 (save)'),
|
|
@@ -28,7 +37,8 @@ export function registerReferenceTools(server) {
|
|
|
28
37
|
files: z.array(fileSchema).optional().describe('参考文件 (save)'),
|
|
29
38
|
scripts: z.array(fileSchema).optional().describe('解析脚本 (save)'),
|
|
30
39
|
adoptedBy: z.array(adoptedNodeSchema).optional().describe('已采用该参考的节点 (save)'),
|
|
31
|
-
path: z.string().optional().describe('参考文件路径 (read_file)'),
|
|
40
|
+
path: z.string().optional().describe('参考文件路径 (read_file/import_file)'),
|
|
41
|
+
url: z.string().optional().describe('要拉取的文档 URL (fetch/refetch)'),
|
|
32
42
|
}, async (args) => safeTool(async () => {
|
|
33
43
|
const decoded = decodeObjectStrings(args);
|
|
34
44
|
switch (decoded.action) {
|
|
@@ -39,11 +49,12 @@ export function registerReferenceTools(server) {
|
|
|
39
49
|
const lines = [
|
|
40
50
|
`📎 外部参考 (${refs.length})`,
|
|
41
51
|
'',
|
|
42
|
-
'| ID | 标题 | 链接 | 文件 | 已采用 |',
|
|
43
|
-
'
|
|
52
|
+
'| ID | 标题 | 链接 | 文件 | 拉取 | 已采用 |',
|
|
53
|
+
'|:---|:---|---:|---:|---:|---:|',
|
|
44
54
|
];
|
|
45
55
|
for (const ref of refs) {
|
|
46
|
-
|
|
56
|
+
const fetchCount = ref.fetchHistory?.length || 0;
|
|
57
|
+
lines.push(`| ${ref.id} | ${ref.title} | ${ref.links.length} | ${ref.files.length} | ${fetchCount} | ${ref.adoptedBy.length} |`);
|
|
47
58
|
}
|
|
48
59
|
return wrap(lines.join('\n'));
|
|
49
60
|
}
|
|
@@ -61,9 +72,17 @@ export function registerReferenceTools(server) {
|
|
|
61
72
|
`- Files: ${ref.files.length}`,
|
|
62
73
|
`- Scripts: ${ref.scripts.length}`,
|
|
63
74
|
`- AdoptedBy: ${ref.adoptedBy.length}`,
|
|
64
|
-
'',
|
|
65
|
-
ref.summary || '(无摘要)',
|
|
66
75
|
];
|
|
76
|
+
if (ref.sourceUrl) {
|
|
77
|
+
lines.push(`- SourceURL: ${ref.sourceUrl}`);
|
|
78
|
+
}
|
|
79
|
+
if (ref.fetchHistory && ref.fetchHistory.length > 0) {
|
|
80
|
+
lines.push(`- FetchCount: ${ref.fetchHistory.length} 次`);
|
|
81
|
+
lines.push(`- LastFetch: ${ref.fetchHistory[ref.fetchHistory.length - 1].fetchedAt}`);
|
|
82
|
+
}
|
|
83
|
+
lines.push(`- Created: ${ref.createdAt}`);
|
|
84
|
+
lines.push(`- Updated: ${ref.updatedAt}`);
|
|
85
|
+
lines.push('', ref.summary || '(无摘要)');
|
|
67
86
|
if (ref.links.length > 0) {
|
|
68
87
|
lines.push('', '## Links');
|
|
69
88
|
for (const link of ref.links) {
|
|
@@ -82,6 +101,20 @@ export function registerReferenceTools(server) {
|
|
|
82
101
|
lines.push(`- ${script.name}: ${script.path}`);
|
|
83
102
|
}
|
|
84
103
|
}
|
|
104
|
+
if (ref.fetchHistory && ref.fetchHistory.length > 0) {
|
|
105
|
+
lines.push('', '## Fetch History');
|
|
106
|
+
lines.push('| # | 时间 | 标题 | 大小 | 文件 |');
|
|
107
|
+
lines.push('|:--|:-----|:-----|-----:|:-----|');
|
|
108
|
+
ref.fetchHistory.forEach((h, i) => {
|
|
109
|
+
lines.push(`| ${i + 1} | ${h.fetchedAt.substring(0, 19)} | ${h.title} | ${formatBytes(h.contentLength)} | ${h.contentFile} |`);
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
if (ref.adoptedBy.length > 0) {
|
|
113
|
+
lines.push('', '## AdoptedBy');
|
|
114
|
+
for (const node of ref.adoptedBy) {
|
|
115
|
+
lines.push(`- ${node.nodeLabel} [${node.chartId}/${node.nodeId}]`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
85
118
|
return wrap(lines.join('\n'));
|
|
86
119
|
}
|
|
87
120
|
case 'save': {
|
|
@@ -121,6 +154,113 @@ export function registerReferenceTools(server) {
|
|
|
121
154
|
const content = await client().readReferenceFile(decoded.path);
|
|
122
155
|
return wrap(content);
|
|
123
156
|
}
|
|
157
|
+
case 'fetch': {
|
|
158
|
+
if (!decoded.url)
|
|
159
|
+
return wrap('❌ fetch 需要 url (要拉取的文档地址)');
|
|
160
|
+
const result = await client().fetchRefUrl(decoded.url, decoded.id);
|
|
161
|
+
const lines = [
|
|
162
|
+
`✅ 文档已拉取并保存`,
|
|
163
|
+
'',
|
|
164
|
+
`| 字段 | 值 |`,
|
|
165
|
+
`|:---|:---|`,
|
|
166
|
+
`| 参考 ID | ${result.refId} |`,
|
|
167
|
+
`| 标题 | ${result.title} |`,
|
|
168
|
+
`| 内容文件 | ${result.contentFile} |`,
|
|
169
|
+
`| 内容大小 | ${formatBytes(result.contentLength)} |`,
|
|
170
|
+
`| 累计拉取 | ${result.fetchCount} 次 |`,
|
|
171
|
+
'',
|
|
172
|
+
'### 内容预览',
|
|
173
|
+
'',
|
|
174
|
+
result.contentPreview.substring(0, 500),
|
|
175
|
+
'',
|
|
176
|
+
`> 💡 用 \`kg_ref(get, id:"${result.refId}")\` 查看完整详情`,
|
|
177
|
+
`> 💡 用 \`kg_ref(read_file, path:"${result.contentFile}")\` 读取完整内容`,
|
|
178
|
+
`> 💡 用 \`kg_ref(refetch, id:"${result.refId}")\` 重新拉取最新版本`,
|
|
179
|
+
];
|
|
180
|
+
return wrap(lines.join('\n'));
|
|
181
|
+
}
|
|
182
|
+
case 'refetch': {
|
|
183
|
+
if (!decoded.id)
|
|
184
|
+
return wrap('❌ refetch 需要 id (参考 ID)');
|
|
185
|
+
const ref = await client().getReference(decoded.id);
|
|
186
|
+
if (!ref)
|
|
187
|
+
return wrap(`❌ 未找到参考: ${decoded.id}`);
|
|
188
|
+
const refetchUrl = decoded.url || ref.sourceUrl;
|
|
189
|
+
if (!refetchUrl)
|
|
190
|
+
return wrap(`❌ 参考 ${decoded.id} 没有 sourceUrl,请提供 url 参数`);
|
|
191
|
+
const result = await client().fetchRefUrl(refetchUrl, decoded.id);
|
|
192
|
+
const lines = [
|
|
193
|
+
`✅ 文档已重新拉取 (第 ${result.fetchCount} 次)`,
|
|
194
|
+
'',
|
|
195
|
+
`| 字段 | 值 |`,
|
|
196
|
+
`|:---|:---|`,
|
|
197
|
+
`| 参考 ID | ${result.refId} |`,
|
|
198
|
+
`| 标题 | ${result.title} |`,
|
|
199
|
+
`| 新内容文件 | ${result.contentFile} |`,
|
|
200
|
+
`| 内容大小 | ${formatBytes(result.contentLength)} |`,
|
|
201
|
+
'',
|
|
202
|
+
'### 内容预览',
|
|
203
|
+
'',
|
|
204
|
+
result.contentPreview.substring(0, 300),
|
|
205
|
+
];
|
|
206
|
+
return wrap(lines.join('\n'));
|
|
207
|
+
}
|
|
208
|
+
case 'import_file': {
|
|
209
|
+
if (!decoded.id)
|
|
210
|
+
return wrap('❌ import_file 需要 id (参考 ID)');
|
|
211
|
+
if (!decoded.path)
|
|
212
|
+
return wrap('❌ import_file 需要 path (本地文件路径)');
|
|
213
|
+
const result = await client().importLocalFile(decoded.id, decoded.path);
|
|
214
|
+
const lines = [
|
|
215
|
+
`✅ 文件内容已导入参考系统`,
|
|
216
|
+
'',
|
|
217
|
+
`| 字段 | 值 |`,
|
|
218
|
+
`|:---|:---|`,
|
|
219
|
+
`| 参考 ID | ${result.refId} |`,
|
|
220
|
+
`| 源文件 | ${result.sourcePath} |`,
|
|
221
|
+
`| 存储文件 | ${result.storedFile} |`,
|
|
222
|
+
`| 内容大小 | ${formatBytes(result.contentLength)} |`,
|
|
223
|
+
`| 累计导入 | ${result.importCount} 次 |`,
|
|
224
|
+
'',
|
|
225
|
+
'### 内容预览',
|
|
226
|
+
'',
|
|
227
|
+
result.contentPreview.substring(0, 300),
|
|
228
|
+
'',
|
|
229
|
+
`> 💡 用 \`kg_ref(read_file, path:"${result.storedFile}")\` 读取完整内容`,
|
|
230
|
+
`> 💡 用 \`kg_ref(reimport, id:"${result.refId}", path:"${result.sourcePath}")\` 重新导入最新版本`,
|
|
231
|
+
];
|
|
232
|
+
return wrap(lines.join('\n'));
|
|
233
|
+
}
|
|
234
|
+
case 'reimport': {
|
|
235
|
+
if (!decoded.id)
|
|
236
|
+
return wrap('❌ reimport 需要 id (参考 ID)');
|
|
237
|
+
const ref = await client().getReference(decoded.id);
|
|
238
|
+
if (!ref)
|
|
239
|
+
return wrap(`❌ 未找到参考: ${decoded.id}`);
|
|
240
|
+
// 获取源文件路径: 优先使用参数中的 path, 否则取最后一次导入的源路径
|
|
241
|
+
const reimportPath = decoded.path ||
|
|
242
|
+
(ref.fileImportHistory && ref.fileImportHistory.length > 0
|
|
243
|
+
? ref.fileImportHistory[ref.fileImportHistory.length - 1].sourcePath
|
|
244
|
+
: null);
|
|
245
|
+
if (!reimportPath)
|
|
246
|
+
return wrap(`❌ 参考 ${decoded.id} 没有导入历史,请提供 path 参数`);
|
|
247
|
+
const result = await client().importLocalFile(decoded.id, reimportPath);
|
|
248
|
+
const lines = [
|
|
249
|
+
`✅ 文件已重新导入 (第 ${result.importCount} 次)`,
|
|
250
|
+
'',
|
|
251
|
+
`| 字段 | 值 |`,
|
|
252
|
+
`|:---|:---|`,
|
|
253
|
+
`| 参考 ID | ${result.refId} |`,
|
|
254
|
+
`| 源文件 | ${result.sourcePath} |`,
|
|
255
|
+
`| 新存储文件 | ${result.storedFile} |`,
|
|
256
|
+
`| 内容大小 | ${formatBytes(result.contentLength)} |`,
|
|
257
|
+
'',
|
|
258
|
+
'### 内容预览',
|
|
259
|
+
'',
|
|
260
|
+
result.contentPreview.substring(0, 300),
|
|
261
|
+
];
|
|
262
|
+
return wrap(lines.join('\n'));
|
|
263
|
+
}
|
|
124
264
|
default:
|
|
125
265
|
return wrap(`❌ 未知 action: ${decoded.action}`);
|
|
126
266
|
}
|
package/dist/tools/tasks.js
CHANGED
|
@@ -8,7 +8,8 @@ import { decodeObjectStrings } from '../utils.js';
|
|
|
8
8
|
import { wrap, safeTool } from './shared.js';
|
|
9
9
|
export function registerTaskTools(server, ctx) {
|
|
10
10
|
const client = () => getClient();
|
|
11
|
-
server.tool('kg_task', '📝 任务记录 — action: create(创建,建议bindTo关联节点)|get(查询)|update(追加进度)|archive(归档)|delete(删除)。⚠️ 每完成一个步骤必须立即update
|
|
11
|
+
server.tool('kg_task', '📝 任务记录 — action: create(创建,建议bindTo关联节点)|get(查询)|update(追加进度)|archive(归档)|delete(删除)。⚠️ 每完成一个步骤必须立即update!\n' +
|
|
12
|
+
'★ 涉及逻辑变更的任务,创建后必须先执行 design-first 工作流(kg_workflow(id:"design-first")),在流程图中完成设计并验证后才能编码。', {
|
|
12
13
|
action: z.enum(['create', 'get', 'update', 'archive', 'delete'])
|
|
13
14
|
.describe('操作类型'),
|
|
14
15
|
title: z.string().optional()
|