@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.
- package/dist/cli.js +4 -48
- package/dist/storage/httpClient.d.ts +2 -27
- package/dist/storage/httpClient.js +3 -132
- package/dist/storage/types.d.ts +0 -28
- package/dist/storage/types.js +1 -1
- package/dist/tools/flowchart.d.ts +1 -5
- package/dist/tools/flowchart.js +406 -454
- package/dist/tools/index.d.ts +3 -3
- package/dist/tools/index.js +10 -13
- package/dist/tools/kg_status.d.ts +1 -1
- package/dist/tools/kg_status.js +6 -20
- package/dist/tools/projects.d.ts +2 -3
- package/dist/tools/projects.js +2 -3
- package/dist/tools/shared.d.ts +0 -12
- package/dist/tools/shared.js +0 -59
- package/package.json +1 -1
- package/templates/AGENT.md +63 -38
- package/templates/cursorrules.md +63 -64
- package/templates/kiro-rules/ppdocs.md +63 -142
- package/dist/agent.d.ts +0 -6
- package/dist/agent.js +0 -130
- package/dist/tools/docs.d.ts +0 -8
- package/dist/tools/docs.js +0 -285
- package/dist/tools/helpers.d.ts +0 -37
- package/dist/tools/helpers.js +0 -94
- package/dist/vector/index.d.ts +0 -56
- package/dist/vector/index.js +0 -228
- package/dist/vector/manager.d.ts +0 -48
- package/dist/vector/manager.js +0 -250
- package/dist/web/server.d.ts +0 -43
- package/dist/web/server.js +0 -808
- package/dist/web/ui.d.ts +0 -5
- package/dist/web/ui.js +0 -642
package/dist/tools/flowchart.js
CHANGED
|
@@ -1,597 +1,549 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 🔀 kg_flowchart — 逻辑流程图批量操作
|
|
3
|
-
* AI 一次提交所有节点+边, 后端原子处理, 返回孤立检测结果
|
|
4
|
-
*/
|
|
5
1
|
import { z } from 'zod';
|
|
6
2
|
import { getClient } from '../storage/httpClient.js';
|
|
7
3
|
import { decodeObjectStrings } from '../utils.js';
|
|
8
4
|
import { wrap, safeTool } from './shared.js';
|
|
9
5
|
const NodeSchema = z.object({
|
|
10
|
-
id: z.string().describe('
|
|
11
|
-
label: z.string().describe('
|
|
12
|
-
description: z.string().optional().describe('
|
|
13
|
-
nodeType: z.string().optional().describe('
|
|
14
|
-
domain: z.string().optional().describe('
|
|
15
|
-
input: z.array(z.string()).optional(),
|
|
16
|
-
output: z.array(z.string()).optional(),
|
|
17
|
-
affiliation: z.string().optional().describe('
|
|
6
|
+
id: z.string().describe('node id'),
|
|
7
|
+
label: z.string().describe('node label'),
|
|
8
|
+
description: z.string().optional().describe('node description'),
|
|
9
|
+
nodeType: z.string().optional().describe('node type: super|process|data|entry'),
|
|
10
|
+
domain: z.string().optional().describe('domain: frontend|backend|mcp|system|infra'),
|
|
11
|
+
input: z.array(z.string()).optional().describe('input list'),
|
|
12
|
+
output: z.array(z.string()).optional().describe('output list'),
|
|
13
|
+
affiliation: z.string().optional().describe('parent node id, default root'),
|
|
14
|
+
subFlowchart: z.string().optional().describe('optional sub-chart id'),
|
|
18
15
|
});
|
|
19
16
|
const EdgeSchema = z.object({
|
|
20
|
-
from: z.string().describe('
|
|
21
|
-
to: z.string().describe('
|
|
22
|
-
label: z.string().optional().describe('
|
|
23
|
-
edgeType: z.string().optional().describe('
|
|
17
|
+
from: z.string().describe('from node id'),
|
|
18
|
+
to: z.string().describe('to node id'),
|
|
19
|
+
label: z.string().optional().describe('edge label'),
|
|
20
|
+
edgeType: z.string().optional().describe('edge type: call|event|data|reference'),
|
|
24
21
|
});
|
|
25
|
-
|
|
22
|
+
function ensureArray(value) {
|
|
23
|
+
return Array.isArray(value) ? [...value] : [];
|
|
24
|
+
}
|
|
25
|
+
function ensureNodeShape(node) {
|
|
26
|
+
return {
|
|
27
|
+
...node,
|
|
28
|
+
input: ensureArray(node.input),
|
|
29
|
+
output: ensureArray(node.output),
|
|
30
|
+
boundDocs: ensureArray(node.boundDocs),
|
|
31
|
+
boundFiles: ensureArray(node.boundFiles),
|
|
32
|
+
boundDirs: ensureArray(node.boundDirs),
|
|
33
|
+
boundTasks: ensureArray(node.boundTasks),
|
|
34
|
+
docEntries: Array.isArray(node.docEntries) ? [...node.docEntries] : [],
|
|
35
|
+
versions: Array.isArray(node.versions) ? [...node.versions] : [],
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function buildNode(input) {
|
|
39
|
+
const now = new Date().toISOString();
|
|
40
|
+
return {
|
|
41
|
+
id: input.id,
|
|
42
|
+
label: input.label,
|
|
43
|
+
description: input.description ?? '',
|
|
44
|
+
nodeType: input.nodeType ?? 'process',
|
|
45
|
+
domain: input.domain ?? 'system',
|
|
46
|
+
input: ensureArray(input.input),
|
|
47
|
+
output: ensureArray(input.output),
|
|
48
|
+
affiliation: input.affiliation ?? 'root',
|
|
49
|
+
boundDocs: [],
|
|
50
|
+
boundFiles: [],
|
|
51
|
+
boundDirs: [],
|
|
52
|
+
boundTasks: [],
|
|
53
|
+
subFlowchart: input.subFlowchart || null,
|
|
54
|
+
docEntries: [],
|
|
55
|
+
versions: [{ version: 'v1.0', date: now, changes: 'initial' }],
|
|
56
|
+
lastUpdated: now,
|
|
57
|
+
lastQueried: '',
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
function buildEdge(input) {
|
|
61
|
+
return {
|
|
62
|
+
from: input.from,
|
|
63
|
+
to: input.to,
|
|
64
|
+
label: input.label ?? null,
|
|
65
|
+
edgeType: input.edgeType ?? 'call',
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function formatNodeLine(node) {
|
|
69
|
+
const docCount = node.boundDocs?.length ?? 0;
|
|
70
|
+
const taskCount = node.boundTasks?.length ?? 0;
|
|
71
|
+
const fileCount = (node.boundFiles?.length ?? 0) + (node.boundDirs?.length ?? 0);
|
|
72
|
+
const badges = [
|
|
73
|
+
docCount > 0 ? `docs=${docCount}` : '',
|
|
74
|
+
taskCount > 0 ? `tasks=${taskCount}` : '',
|
|
75
|
+
fileCount > 0 ? `files=${fileCount}` : '',
|
|
76
|
+
node.subFlowchart ? `sub=${node.subFlowchart}` : '',
|
|
77
|
+
].filter(Boolean);
|
|
78
|
+
const suffix = badges.length > 0 ? ` | ${badges.join(' ')}` : '';
|
|
79
|
+
return `- ${node.label} [${node.id}] (${node.nodeType ?? 'process'}/${node.domain ?? 'system'})${suffix}`;
|
|
80
|
+
}
|
|
81
|
+
function formatEdgeLine(edge) {
|
|
82
|
+
const label = edge.label ? ` [${edge.label}]` : '';
|
|
83
|
+
const type = edge.edgeType ? ` (${edge.edgeType})` : '';
|
|
84
|
+
return `- ${edge.from} -> ${edge.to}${label}${type}`;
|
|
85
|
+
}
|
|
86
|
+
function appendDocEntry(node, summary, content) {
|
|
87
|
+
const nextNode = ensureNodeShape(node);
|
|
88
|
+
const entries = nextNode.docEntries ?? [];
|
|
89
|
+
let nextVer = 0.1;
|
|
90
|
+
if (entries.length > 0) {
|
|
91
|
+
const last = entries[entries.length - 1]?.version ?? '';
|
|
92
|
+
const parsed = Number.parseFloat(last.replace(/^v/, ''));
|
|
93
|
+
if (!Number.isNaN(parsed)) {
|
|
94
|
+
nextVer = Math.round((parsed + 0.1) * 10) / 10;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
entries.push({
|
|
98
|
+
version: `v${nextVer.toFixed(1)}`,
|
|
99
|
+
date: new Date().toISOString(),
|
|
100
|
+
summary: summary.replace(/\\n/g, '\n'),
|
|
101
|
+
content: content.replace(/\\n/g, '\n'),
|
|
102
|
+
});
|
|
103
|
+
nextNode.docEntries = entries;
|
|
104
|
+
return { node: nextNode, version: `v${nextVer.toFixed(1)}` };
|
|
105
|
+
}
|
|
106
|
+
function collectNeighborLayers(chart, startNodeId, maxDepth) {
|
|
107
|
+
const nodeMap = new Map(chart.nodes.map((node) => [node.id, node]));
|
|
108
|
+
const visited = new Set([startNodeId]);
|
|
109
|
+
let frontier = [startNodeId];
|
|
110
|
+
const layers = [];
|
|
111
|
+
for (let depth = 1; depth <= maxDepth; depth += 1) {
|
|
112
|
+
const layer = [];
|
|
113
|
+
const nextFrontier = [];
|
|
114
|
+
for (const currentId of frontier) {
|
|
115
|
+
for (const edge of chart.edges) {
|
|
116
|
+
if (edge.from === currentId && !visited.has(edge.to)) {
|
|
117
|
+
visited.add(edge.to);
|
|
118
|
+
nextFrontier.push(edge.to);
|
|
119
|
+
const node = nodeMap.get(edge.to);
|
|
120
|
+
layer.push(`- ${node?.label ?? edge.to} [${edge.to}]${edge.label ? ` (${edge.label})` : ''}${edge.edgeType ? ` [${edge.edgeType}]` : ''}`);
|
|
121
|
+
}
|
|
122
|
+
if (edge.to === currentId && !visited.has(edge.from)) {
|
|
123
|
+
visited.add(edge.from);
|
|
124
|
+
nextFrontier.push(edge.from);
|
|
125
|
+
const node = nodeMap.get(edge.from);
|
|
126
|
+
layer.push(`- ${node?.label ?? edge.from} [${edge.from}]${edge.label ? ` (${edge.label})` : ''}${edge.edgeType ? ` [${edge.edgeType}]` : ''}`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (layer.length === 0) {
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
layers.push(layer);
|
|
134
|
+
frontier = nextFrontier;
|
|
135
|
+
}
|
|
136
|
+
return layers;
|
|
137
|
+
}
|
|
138
|
+
export function registerFlowchartTools(server, _ctx) {
|
|
26
139
|
const client = () => getClient();
|
|
27
|
-
server.tool('kg_flowchart', '
|
|
28
|
-
action: z
|
|
29
|
-
.
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
nodeId: z.string().optional()
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
docSummary: z.string().optional().describe('update_node: doc summary (one-line)'),
|
|
58
|
-
docContent: z.string().optional().describe('update_node: doc content (full markdown)'),
|
|
59
|
-
// get_node: doc display control
|
|
60
|
-
fullDoc: z.boolean().optional().describe('get_node: show ALL doc entries in full (default false, only latest full)'),
|
|
61
|
-
// bind/unbind
|
|
62
|
-
files: z.array(z.string()).optional()
|
|
63
|
-
.describe('源代码文件路径 (bind/unbind)'),
|
|
64
|
-
dirs: z.array(z.string()).optional()
|
|
65
|
-
.describe('源代码目录路径 (bind/unbind)'),
|
|
66
|
-
docs: z.array(z.string()).optional()
|
|
67
|
-
.describe('知识文档路径 (bind/unbind)'),
|
|
68
|
-
tasks: z.array(z.string()).optional()
|
|
69
|
-
.describe('任务ID (bind/unbind)'),
|
|
70
|
-
// batch_add
|
|
71
|
-
nodes: z.array(NodeSchema).optional()
|
|
72
|
-
.describe('批量添加的节点数组 (batch_add)'),
|
|
73
|
-
edges: z.array(EdgeSchema).optional()
|
|
74
|
-
.describe('批量添加的边数组 (batch_add)'),
|
|
140
|
+
server.tool('kg_flowchart', 'Logical flowchart operations: list|get|get_node|update_node|delete_node|batch_add|bind|unbind|orphans|health|create_chart|delete_chart', {
|
|
141
|
+
action: z
|
|
142
|
+
.enum(['list', 'get', 'get_node', 'update_node', 'delete_node', 'batch_add', 'bind', 'unbind', 'orphans', 'health', 'create_chart', 'delete_chart'])
|
|
143
|
+
.describe('action type'),
|
|
144
|
+
chartId: z.string().optional().describe('chart id, default main'),
|
|
145
|
+
nodeId: z.string().optional().describe('node id for get_node/update_node/delete_node/bind/unbind'),
|
|
146
|
+
expand: z.number().optional().describe('get_node expansion depth, default 3'),
|
|
147
|
+
includeDocs: z.boolean().optional().describe('show bound docs in get_node, default true'),
|
|
148
|
+
includeTasks: z.boolean().optional().describe('show bound tasks in get_node, default true'),
|
|
149
|
+
includeFiles: z.boolean().optional().describe('show bound files in get_node, default true'),
|
|
150
|
+
includeDoc: z.boolean().optional().describe('show node docEntries in get_node, default true'),
|
|
151
|
+
label: z.string().optional().describe('update_node label'),
|
|
152
|
+
description: z.string().optional().describe('update_node description'),
|
|
153
|
+
nodeType: z.string().optional().describe('update_node node type'),
|
|
154
|
+
domain: z.string().optional().describe('update_node domain'),
|
|
155
|
+
input: z.array(z.string()).optional().describe('update_node input list'),
|
|
156
|
+
output: z.array(z.string()).optional().describe('update_node output list'),
|
|
157
|
+
subFlowchart: z.string().optional().describe('update_node sub chart id, pass empty string to clear'),
|
|
158
|
+
title: z.string().optional().describe('create_chart title'),
|
|
159
|
+
parentChart: z.string().optional().describe('create_chart parent chart id, default main'),
|
|
160
|
+
parentNode: z.string().optional().describe('create_chart parent node id'),
|
|
161
|
+
docSummary: z.string().optional().describe('update_node doc summary'),
|
|
162
|
+
docContent: z.string().optional().describe('update_node doc content'),
|
|
163
|
+
fullDoc: z.boolean().optional().describe('get_node show all docEntries in full'),
|
|
164
|
+
files: z.array(z.string()).optional().describe('bind/unbind file paths'),
|
|
165
|
+
dirs: z.array(z.string()).optional().describe('bind/unbind directory paths'),
|
|
166
|
+
docs: z.array(z.string()).optional().describe('bind/unbind doc paths'),
|
|
167
|
+
tasks: z.array(z.string()).optional().describe('bind/unbind task ids'),
|
|
168
|
+
nodes: z.array(NodeSchema).optional().describe('batch_add/create_chart nodes'),
|
|
169
|
+
edges: z.array(EdgeSchema).optional().describe('batch_add/create_chart edges'),
|
|
75
170
|
}, async (args) => safeTool(async () => {
|
|
76
171
|
const decoded = decodeObjectStrings(args);
|
|
77
172
|
switch (decoded.action) {
|
|
78
173
|
case 'list': {
|
|
79
|
-
const charts = await client().listFlowcharts();
|
|
80
|
-
if (!charts || charts.length === 0)
|
|
81
|
-
return wrap('
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
174
|
+
const charts = (await client().listFlowcharts());
|
|
175
|
+
if (!charts || charts.length === 0) {
|
|
176
|
+
return wrap('No flowcharts found.');
|
|
177
|
+
}
|
|
178
|
+
const lines = ['Flowcharts:', '', ...charts.map((chart) => {
|
|
179
|
+
const parent = chart.parentChart ? ` parent=${chart.parentChart}` : '';
|
|
180
|
+
return `- ${chart.title} [${chart.id}] ${chart.nodeCount} nodes / ${chart.edgeCount} edges${parent}`;
|
|
181
|
+
})];
|
|
182
|
+
return wrap(lines.join('\n'));
|
|
86
183
|
}
|
|
87
184
|
case 'get': {
|
|
88
185
|
const chartId = decoded.chartId || 'main';
|
|
89
|
-
const chart = await client().getFlowchart(chartId);
|
|
90
|
-
if (!chart)
|
|
91
|
-
return wrap(
|
|
92
|
-
|
|
93
|
-
const edges = chart.edges || [];
|
|
186
|
+
const chart = (await client().getFlowchart(chartId));
|
|
187
|
+
if (!chart) {
|
|
188
|
+
return wrap(`Flowchart not found: ${chartId}`);
|
|
189
|
+
}
|
|
94
190
|
const lines = [
|
|
95
|
-
|
|
96
|
-
chart.parentChart ?
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
191
|
+
`Flowchart: ${chart.title} [${chart.id}]`,
|
|
192
|
+
chart.parentChart ? `Parent chart: ${chart.parentChart}` : '',
|
|
193
|
+
chart.parentNode ? `Parent node: ${chart.parentNode}` : '',
|
|
194
|
+
'',
|
|
195
|
+
`Nodes (${chart.nodes.length}):`,
|
|
196
|
+
...chart.nodes.map(formatNodeLine),
|
|
197
|
+
'',
|
|
198
|
+
`Edges (${chart.edges.length}):`,
|
|
199
|
+
...chart.edges.map(formatEdgeLine),
|
|
200
|
+
].filter((line) => line.length > 0);
|
|
104
201
|
return wrap(lines.join('\n'));
|
|
105
202
|
}
|
|
106
203
|
case 'get_node': {
|
|
107
204
|
const chartId = decoded.chartId || 'main';
|
|
108
|
-
if (!decoded.nodeId)
|
|
109
|
-
return wrap('
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
const target =
|
|
116
|
-
if (!target)
|
|
117
|
-
return wrap(
|
|
118
|
-
|
|
119
|
-
const
|
|
205
|
+
if (!decoded.nodeId) {
|
|
206
|
+
return wrap('get_node requires nodeId.');
|
|
207
|
+
}
|
|
208
|
+
const chart = (await client().getFlowchart(chartId));
|
|
209
|
+
if (!chart) {
|
|
210
|
+
return wrap(`Flowchart not found: ${chartId}`);
|
|
211
|
+
}
|
|
212
|
+
const target = chart.nodes.find((node) => node.id === decoded.nodeId);
|
|
213
|
+
if (!target) {
|
|
214
|
+
return wrap(`Node not found: ${decoded.nodeId} in ${chartId}`);
|
|
215
|
+
}
|
|
216
|
+
const node = ensureNodeShape(target);
|
|
217
|
+
const expandDepth = decoded.expand ?? 3;
|
|
120
218
|
const showDocs = decoded.includeDocs !== false;
|
|
121
219
|
const showTasks = decoded.includeTasks !== false;
|
|
122
220
|
const showFiles = decoded.includeFiles !== false;
|
|
123
221
|
const showDoc = decoded.includeDoc !== false;
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
if (
|
|
132
|
-
lines.push(
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
if (
|
|
138
|
-
lines.push(
|
|
222
|
+
const lines = [
|
|
223
|
+
`Node: ${node.label} [${node.id}]`,
|
|
224
|
+
`Type: ${node.nodeType ?? 'process'} | Domain: ${node.domain ?? 'system'}`,
|
|
225
|
+
];
|
|
226
|
+
if (node.description) {
|
|
227
|
+
lines.push(`Description: ${node.description}`);
|
|
228
|
+
}
|
|
229
|
+
if (node.input && node.input.length > 0) {
|
|
230
|
+
lines.push(`Input: ${node.input.join(', ')}`);
|
|
231
|
+
}
|
|
232
|
+
if (node.output && node.output.length > 0) {
|
|
233
|
+
lines.push(`Output: ${node.output.join(', ')}`);
|
|
234
|
+
}
|
|
235
|
+
if (node.subFlowchart) {
|
|
236
|
+
lines.push(`Sub flowchart: ${node.subFlowchart}`);
|
|
237
|
+
}
|
|
238
|
+
if (node.lastUpdated) {
|
|
239
|
+
lines.push(`Last updated: ${node.lastUpdated}`);
|
|
240
|
+
}
|
|
241
|
+
if (node.lastQueried) {
|
|
242
|
+
lines.push(`Last queried: ${node.lastQueried}`);
|
|
243
|
+
}
|
|
139
244
|
lines.push('');
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
lines.push(`\n**${e.version}** (${e.date})${isLatest ? ' ★LATEST' : ''}`);
|
|
150
|
-
lines.push(`> ${e.summary}`);
|
|
151
|
-
lines.push(e.content);
|
|
245
|
+
if (showDoc && node.docEntries && node.docEntries.length > 0) {
|
|
246
|
+
lines.push(`Node docs (${node.docEntries.length}):`);
|
|
247
|
+
const showAll = decoded.fullDoc === true;
|
|
248
|
+
node.docEntries.forEach((entry, index) => {
|
|
249
|
+
const isLatest = index === node.docEntries.length - 1;
|
|
250
|
+
if (isLatest || showAll) {
|
|
251
|
+
lines.push(`- ${entry.version} ${entry.date}${isLatest ? ' latest' : ''}`);
|
|
252
|
+
lines.push(` summary: ${entry.summary}`);
|
|
253
|
+
lines.push(...entry.content.split('\n').map((line) => ` ${line}`));
|
|
152
254
|
}
|
|
153
255
|
else {
|
|
154
|
-
lines.push(
|
|
256
|
+
lines.push(`- ${entry.version} ${entry.date}: ${entry.summary}`);
|
|
155
257
|
}
|
|
156
|
-
}
|
|
258
|
+
});
|
|
157
259
|
lines.push('');
|
|
158
260
|
}
|
|
159
|
-
// ======== 爆炸扩展: N层关联节点 ========
|
|
160
261
|
if (expandDepth > 0) {
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
for (const currentId of frontier) {
|
|
168
|
-
// 出边
|
|
169
|
-
for (const e of allEdges) {
|
|
170
|
-
if (e.from === currentId && !visited.has(e.to)) {
|
|
171
|
-
visited.add(e.to);
|
|
172
|
-
nextFrontier.push(e.to);
|
|
173
|
-
const neighbor = nodeMap.get(e.to);
|
|
174
|
-
const label = neighbor?.label || e.to;
|
|
175
|
-
layerLines.push(` → ${label} [${e.to}] ${e.label ? `(${e.label})` : ''} ${e.edgeType ? `[${e.edgeType}]` : ''}`);
|
|
176
|
-
}
|
|
177
|
-
// 入边
|
|
178
|
-
if (e.to === currentId && !visited.has(e.from)) {
|
|
179
|
-
visited.add(e.from);
|
|
180
|
-
nextFrontier.push(e.from);
|
|
181
|
-
const neighbor = nodeMap.get(e.from);
|
|
182
|
-
const label = neighbor?.label || e.from;
|
|
183
|
-
layerLines.push(` ← ${label} [${e.from}] ${e.label ? `(${e.label})` : ''} ${e.edgeType ? `[${e.edgeType}]` : ''}`);
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
if (layerLines.length > 0)
|
|
188
|
-
layerResults.push(layerLines);
|
|
189
|
-
frontier = nextFrontier;
|
|
190
|
-
if (frontier.length === 0)
|
|
191
|
-
break;
|
|
192
|
-
}
|
|
193
|
-
if (layerResults.length > 0) {
|
|
194
|
-
lines.push(`### 🔗 关联节点 (扩展${layerResults.length}层, 共${visited.size - 1}个)`);
|
|
195
|
-
layerResults.forEach((layer, i) => {
|
|
196
|
-
lines.push(`**L${i + 1}** (${layer.length}个):`);
|
|
197
|
-
lines.push(...layer);
|
|
262
|
+
const layers = collectNeighborLayers(chart, node.id, expandDepth);
|
|
263
|
+
if (layers.length > 0) {
|
|
264
|
+
lines.push(`Linked nodes (${layers.length} layers):`);
|
|
265
|
+
layers.forEach((layer, index) => {
|
|
266
|
+
lines.push(`L${index + 1}:`);
|
|
267
|
+
lines.push(...layer.map((item) => ` ${item}`));
|
|
198
268
|
});
|
|
199
269
|
lines.push('');
|
|
200
270
|
}
|
|
201
271
|
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
lines.push(
|
|
205
|
-
for (const docPath of target.boundDocs) {
|
|
206
|
-
try {
|
|
207
|
-
const doc = await client().getDoc(docPath);
|
|
208
|
-
if (doc) {
|
|
209
|
-
lines.push(` 📄 ${docPath} — ${doc.summary || '无摘要'}`);
|
|
210
|
-
if (doc.status)
|
|
211
|
-
lines.push(` 状态: ${doc.status}`);
|
|
212
|
-
}
|
|
213
|
-
else {
|
|
214
|
-
lines.push(` 📄 ${docPath} (未找到)`);
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
catch {
|
|
218
|
-
lines.push(` 📄 ${docPath}`);
|
|
219
|
-
}
|
|
220
|
-
}
|
|
272
|
+
if (showDocs && node.boundDocs && node.boundDocs.length > 0) {
|
|
273
|
+
lines.push(`Bound docs (${node.boundDocs.length}):`);
|
|
274
|
+
node.boundDocs.forEach((docPath) => lines.push(`- ${docPath}`));
|
|
221
275
|
lines.push('');
|
|
222
276
|
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
for (const taskId of target.boundTasks) {
|
|
277
|
+
if (showTasks && node.boundTasks && node.boundTasks.length > 0) {
|
|
278
|
+
lines.push(`Bound tasks (${node.boundTasks.length}):`);
|
|
279
|
+
for (const taskId of node.boundTasks) {
|
|
227
280
|
try {
|
|
228
|
-
const task = await client().getTask(taskId);
|
|
281
|
+
const task = (await client().getTask(taskId));
|
|
229
282
|
if (task) {
|
|
230
|
-
lines.push(
|
|
283
|
+
lines.push(`- [${taskId}] ${task.title ?? ''} ${task.status ? `(${task.status})` : ''}`.trim());
|
|
231
284
|
}
|
|
232
285
|
else {
|
|
233
|
-
lines.push(
|
|
286
|
+
lines.push(`- [${taskId}]`);
|
|
234
287
|
}
|
|
235
288
|
}
|
|
236
289
|
catch {
|
|
237
|
-
lines.push(
|
|
290
|
+
lines.push(`- [${taskId}]`);
|
|
238
291
|
}
|
|
239
292
|
}
|
|
240
293
|
lines.push('');
|
|
241
294
|
}
|
|
242
|
-
// ======== 绑定的代码文件 ========
|
|
243
295
|
if (showFiles) {
|
|
244
|
-
const files =
|
|
245
|
-
const dirs =
|
|
246
|
-
if (files.length
|
|
247
|
-
lines.push(
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
for (const d of dirs)
|
|
251
|
-
lines.push(` 📁 ${d}`);
|
|
296
|
+
const files = node.boundFiles ?? [];
|
|
297
|
+
const dirs = node.boundDirs ?? [];
|
|
298
|
+
if (files.length > 0 || dirs.length > 0) {
|
|
299
|
+
lines.push(`Bound code (${files.length} files / ${dirs.length} dirs):`);
|
|
300
|
+
files.forEach((filePath) => lines.push(`- file: ${filePath}`));
|
|
301
|
+
dirs.forEach((dirPath) => lines.push(`- dir: ${dirPath}`));
|
|
252
302
|
lines.push('');
|
|
253
303
|
}
|
|
254
304
|
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
}
|
|
305
|
+
if (node.versions && node.versions.length > 0) {
|
|
306
|
+
lines.push(`Versions (${node.versions.length}):`);
|
|
307
|
+
node.versions.slice(-5).forEach((version) => {
|
|
308
|
+
lines.push(`- ${version.version} ${version.date}: ${version.changes}`);
|
|
309
|
+
});
|
|
261
310
|
}
|
|
262
|
-
return wrap(lines.join('\n'));
|
|
311
|
+
return wrap(lines.join('\n').trim());
|
|
263
312
|
}
|
|
264
313
|
case 'update_node': {
|
|
265
314
|
const chartId = decoded.chartId || 'main';
|
|
266
|
-
if (!decoded.nodeId)
|
|
267
|
-
return wrap('
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
315
|
+
if (!decoded.nodeId) {
|
|
316
|
+
return wrap('update_node requires nodeId.');
|
|
317
|
+
}
|
|
318
|
+
const chart = (await client().getFlowchart(chartId));
|
|
319
|
+
if (!chart) {
|
|
320
|
+
return wrap(`Flowchart not found: ${chartId}`);
|
|
321
|
+
}
|
|
322
|
+
const rawNode = chart.nodes.find((node) => node.id === decoded.nodeId);
|
|
323
|
+
if (!rawNode) {
|
|
324
|
+
return wrap(`Node not found: ${decoded.nodeId}`);
|
|
325
|
+
}
|
|
326
|
+
let nextNode = ensureNodeShape(rawNode);
|
|
274
327
|
const changes = [];
|
|
275
328
|
if (decoded.label !== undefined) {
|
|
276
|
-
|
|
329
|
+
nextNode.label = decoded.label;
|
|
277
330
|
changes.push('label');
|
|
278
331
|
}
|
|
279
332
|
if (decoded.description !== undefined) {
|
|
280
|
-
|
|
333
|
+
nextNode.description = decoded.description;
|
|
281
334
|
changes.push('description');
|
|
282
335
|
}
|
|
283
336
|
if (decoded.nodeType !== undefined) {
|
|
284
|
-
|
|
337
|
+
nextNode.nodeType = decoded.nodeType;
|
|
285
338
|
changes.push('nodeType');
|
|
286
339
|
}
|
|
287
340
|
if (decoded.domain !== undefined) {
|
|
288
|
-
|
|
341
|
+
nextNode.domain = decoded.domain;
|
|
289
342
|
changes.push('domain');
|
|
290
343
|
}
|
|
291
344
|
if (decoded.input !== undefined) {
|
|
292
|
-
|
|
345
|
+
nextNode.input = [...decoded.input];
|
|
293
346
|
changes.push('input');
|
|
294
347
|
}
|
|
295
348
|
if (decoded.output !== undefined) {
|
|
296
|
-
|
|
349
|
+
nextNode.output = [...decoded.output];
|
|
297
350
|
changes.push('output');
|
|
298
351
|
}
|
|
299
352
|
if (decoded.subFlowchart !== undefined) {
|
|
300
|
-
|
|
353
|
+
nextNode.subFlowchart = decoded.subFlowchart || null;
|
|
301
354
|
changes.push('subFlowchart');
|
|
302
355
|
}
|
|
303
|
-
// append doc entry
|
|
304
356
|
if (decoded.docSummary || decoded.docContent) {
|
|
305
357
|
if (!decoded.docSummary || !decoded.docContent) {
|
|
306
|
-
return wrap('
|
|
358
|
+
return wrap('update_node requires both docSummary and docContent when updating docs.');
|
|
307
359
|
}
|
|
308
|
-
const
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
// normalize escaped newlines from AI clients (e.g. "\\n" → actual newline)
|
|
318
|
-
const normalizedContent = decoded.docContent.replace(/\\n/g, '\n');
|
|
319
|
-
const normalizedSummary = decoded.docSummary.replace(/\\n/g, '\n');
|
|
320
|
-
docEntries.push({
|
|
321
|
-
version: `v${nextVer.toFixed(1)}`,
|
|
322
|
-
date: new Date().toISOString(),
|
|
323
|
-
summary: normalizedSummary,
|
|
324
|
-
content: normalizedContent,
|
|
325
|
-
});
|
|
326
|
-
node.docEntries = docEntries;
|
|
327
|
-
changes.push(`docEntries(v${nextVer.toFixed(1)})`);
|
|
328
|
-
}
|
|
329
|
-
if (changes.length === 0)
|
|
330
|
-
return wrap('ℹ️ 无变更 — 请提供要更新的字段');
|
|
331
|
-
node.lastUpdated = new Date().toISOString();
|
|
332
|
-
await client().saveFlowchart(chartId, chart);
|
|
333
|
-
return wrap(`✅ 节点已更新: ${node.label} [${chartId}/${decoded.nodeId}]\n更新字段: ${changes.join(', ')}`);
|
|
360
|
+
const result = appendDocEntry(nextNode, decoded.docSummary, decoded.docContent);
|
|
361
|
+
nextNode = result.node;
|
|
362
|
+
changes.push(`docEntries(${result.version})`);
|
|
363
|
+
}
|
|
364
|
+
if (changes.length === 0) {
|
|
365
|
+
return wrap('No fields were updated.');
|
|
366
|
+
}
|
|
367
|
+
await client().updateFlowchartNode(chartId, decoded.nodeId, nextNode, `MCP update: ${changes.join(', ')}`);
|
|
368
|
+
return wrap(`Node updated: ${nextNode.label} [${chartId}/${decoded.nodeId}]\nFields: ${changes.join(', ')}`);
|
|
334
369
|
}
|
|
335
370
|
case 'delete_node': {
|
|
336
371
|
const chartId = decoded.chartId || 'main';
|
|
337
|
-
if (!decoded.nodeId)
|
|
338
|
-
return wrap('
|
|
339
|
-
|
|
372
|
+
if (!decoded.nodeId) {
|
|
373
|
+
return wrap('delete_node requires nodeId.');
|
|
374
|
+
}
|
|
340
375
|
await client().deleteFlowchartNode(chartId, decoded.nodeId);
|
|
341
|
-
return wrap(
|
|
376
|
+
return wrap(`Node deleted: [${chartId}/${decoded.nodeId}]`);
|
|
342
377
|
}
|
|
343
378
|
case 'batch_add': {
|
|
344
379
|
const chartId = decoded.chartId || 'main';
|
|
345
380
|
if (!decoded.nodes || decoded.nodes.length === 0) {
|
|
346
|
-
return wrap('
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
const
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
input: n.input || [],
|
|
355
|
-
output: n.output || [],
|
|
356
|
-
description: n.description || '',
|
|
357
|
-
affiliation: n.affiliation || 'root',
|
|
358
|
-
boundDocs: [],
|
|
359
|
-
boundFiles: [],
|
|
360
|
-
boundDirs: [],
|
|
361
|
-
boundTasks: [],
|
|
362
|
-
subFlowchart: n.subFlowchart || null,
|
|
363
|
-
position: null,
|
|
364
|
-
versions: [{ version: 'v1.0', date: new Date().toISOString(), changes: 'initial' }],
|
|
365
|
-
lastUpdated: new Date().toISOString(),
|
|
366
|
-
lastQueried: '',
|
|
367
|
-
}));
|
|
368
|
-
const fullEdges = (decoded.edges || []).map((e) => ({
|
|
369
|
-
from: e.from,
|
|
370
|
-
to: e.to,
|
|
371
|
-
label: e.label || null,
|
|
372
|
-
edgeType: e.edgeType || 'call',
|
|
373
|
-
}));
|
|
374
|
-
const result = await client().batchAddToFlowchart(chartId, fullNodes, fullEdges);
|
|
375
|
-
// 构建报告
|
|
376
|
-
let report = `✅ 批量操作完成 [${chartId}]\n`;
|
|
377
|
-
report += ` 添加: ${result.addedNodes.length}节点 ${result.addedEdges}连线\n`;
|
|
381
|
+
return wrap('batch_add requires nodes.');
|
|
382
|
+
}
|
|
383
|
+
const result = (await client().batchAddToFlowchart(chartId, decoded.nodes.map(buildNode), (decoded.edges ?? []).map(buildEdge)));
|
|
384
|
+
const lines = [
|
|
385
|
+
`Batch add finished: [${chartId}]`,
|
|
386
|
+
`Added nodes: ${result.addedNodes.length}`,
|
|
387
|
+
`Added edges: ${result.addedEdges}`,
|
|
388
|
+
];
|
|
378
389
|
if (result.failedNodes.length > 0) {
|
|
379
|
-
|
|
380
|
-
result.failedNodes.forEach(([
|
|
381
|
-
report += ` • ${id}: ${reason}\n`;
|
|
382
|
-
});
|
|
390
|
+
lines.push('', 'Failed nodes:');
|
|
391
|
+
result.failedNodes.forEach(([nodeId, reason]) => lines.push(`- ${nodeId}: ${reason}`));
|
|
383
392
|
}
|
|
384
393
|
if (result.failedEdges.length > 0) {
|
|
385
|
-
|
|
386
|
-
result.failedEdges.forEach(([from, to, reason]) => {
|
|
387
|
-
report += ` • ${from} → ${to}: ${reason}\n`;
|
|
388
|
-
});
|
|
394
|
+
lines.push('', 'Failed edges:');
|
|
395
|
+
result.failedEdges.forEach(([from, to, reason]) => lines.push(`- ${from} -> ${to}: ${reason}`));
|
|
389
396
|
}
|
|
390
397
|
if (result.orphanedNodes.length > 0) {
|
|
391
|
-
|
|
392
|
-
result.orphanedNodes.forEach(
|
|
393
|
-
report += ` • ${id}\n`;
|
|
394
|
-
});
|
|
398
|
+
lines.push('', 'Orphaned nodes:');
|
|
399
|
+
result.orphanedNodes.forEach((nodeId) => lines.push(`- ${nodeId}`));
|
|
395
400
|
}
|
|
396
401
|
else {
|
|
397
|
-
|
|
402
|
+
lines.push('', 'Orphaned nodes: none');
|
|
398
403
|
}
|
|
399
|
-
return wrap(
|
|
404
|
+
return wrap(lines.join('\n'));
|
|
400
405
|
}
|
|
401
406
|
case 'orphans': {
|
|
402
|
-
const orphans = await client().getFlowchartOrphans();
|
|
407
|
+
const orphans = (await client().getFlowchartOrphans());
|
|
403
408
|
if (!orphans || orphans.length === 0) {
|
|
404
|
-
return wrap('
|
|
409
|
+
return wrap('No orphan reference docs reported by backend.');
|
|
405
410
|
}
|
|
406
|
-
const
|
|
407
|
-
|
|
408
|
-
.join('\n');
|
|
409
|
-
return wrap(`⚠️ 发现 ${orphans.length} 个孤立文档 (未绑定到任何流程图节点):\n\n${list}\n\n💡 使用 kg_doc(update, bindTo: "节点ID") 绑定`);
|
|
411
|
+
const lines = ['Orphan reference docs:', '', ...orphans.map((item) => `- ${item.docPath}${item.docSummary ? ` | ${item.docSummary}` : ''}`)];
|
|
412
|
+
return wrap(lines.join('\n'));
|
|
410
413
|
}
|
|
411
414
|
case 'health': {
|
|
412
|
-
const
|
|
413
|
-
if (!
|
|
414
|
-
return wrap('
|
|
415
|
-
}
|
|
416
|
-
const
|
|
417
|
-
const
|
|
418
|
-
const active = items.filter(
|
|
419
|
-
const
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
415
|
+
const items = (await client().getFlowchartHealth());
|
|
416
|
+
if (!items || items.length === 0) {
|
|
417
|
+
return wrap('No flowchart health data reported by backend.');
|
|
418
|
+
}
|
|
419
|
+
const stale = items.filter((item) => item.status === 'stale');
|
|
420
|
+
const normal = items.filter((item) => item.status === 'normal');
|
|
421
|
+
const active = items.filter((item) => item.status === 'active');
|
|
422
|
+
const lines = [
|
|
423
|
+
`Node health: ${items.length} nodes`,
|
|
424
|
+
`active: ${active.length}`,
|
|
425
|
+
`normal: ${normal.length}`,
|
|
426
|
+
`stale: ${stale.length}`,
|
|
427
|
+
];
|
|
424
428
|
if (stale.length > 0) {
|
|
425
|
-
|
|
426
|
-
stale.forEach(
|
|
427
|
-
report += ` • ${h.nodeLabel} [${h.chartId}/${h.nodeId}] — ${h.daysSinceQuery}天未查询\n`;
|
|
428
|
-
});
|
|
429
|
+
lines.push('', 'Stale nodes:');
|
|
430
|
+
stale.forEach((item) => lines.push(`- ${item.nodeLabel} [${item.chartId}/${item.nodeId}] ${item.daysSinceQuery} days`));
|
|
429
431
|
}
|
|
430
|
-
return wrap(
|
|
432
|
+
return wrap(lines.join('\n'));
|
|
431
433
|
}
|
|
432
434
|
case 'bind':
|
|
433
435
|
case 'unbind': {
|
|
434
436
|
const chartId = decoded.chartId || 'main';
|
|
435
|
-
if (!decoded.nodeId)
|
|
436
|
-
return wrap(
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
437
|
+
if (!decoded.nodeId) {
|
|
438
|
+
return wrap(`${decoded.action} requires nodeId.`);
|
|
439
|
+
}
|
|
440
|
+
const chart = (await client().getFlowchart(chartId));
|
|
441
|
+
if (!chart) {
|
|
442
|
+
return wrap(`Flowchart not found: ${chartId}`);
|
|
443
|
+
}
|
|
444
|
+
const rawNode = chart.nodes.find((node) => node.id === decoded.nodeId);
|
|
445
|
+
if (!rawNode) {
|
|
446
|
+
return wrap(`Node not found: ${decoded.nodeId} in ${chartId}`);
|
|
447
|
+
}
|
|
448
|
+
const node = ensureNodeShape(rawNode);
|
|
443
449
|
const isBind = decoded.action === 'bind';
|
|
444
450
|
const changes = [];
|
|
445
|
-
const
|
|
446
|
-
|
|
451
|
+
const mutate = (incoming, field, label) => {
|
|
452
|
+
const items = incoming ?? [];
|
|
453
|
+
if (items.length === 0) {
|
|
447
454
|
return;
|
|
448
|
-
|
|
449
|
-
|
|
455
|
+
}
|
|
456
|
+
const current = new Set(node[field] ?? []);
|
|
450
457
|
if (isBind) {
|
|
451
458
|
let added = 0;
|
|
452
459
|
for (const item of items) {
|
|
453
|
-
if (!
|
|
454
|
-
|
|
455
|
-
added
|
|
460
|
+
if (!current.has(item)) {
|
|
461
|
+
current.add(item);
|
|
462
|
+
added += 1;
|
|
456
463
|
}
|
|
457
464
|
}
|
|
458
|
-
if (added > 0)
|
|
465
|
+
if (added > 0) {
|
|
459
466
|
changes.push(`+${added} ${label}`);
|
|
467
|
+
}
|
|
460
468
|
}
|
|
461
469
|
else {
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
470
|
+
let removed = 0;
|
|
471
|
+
for (const item of items) {
|
|
472
|
+
if (current.delete(item)) {
|
|
473
|
+
removed += 1;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
if (removed > 0) {
|
|
466
477
|
changes.push(`-${removed} ${label}`);
|
|
478
|
+
}
|
|
467
479
|
}
|
|
480
|
+
node[field] = [...current];
|
|
468
481
|
};
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
482
|
+
mutate(decoded.files, 'boundFiles', 'files');
|
|
483
|
+
mutate(decoded.dirs, 'boundDirs', 'dirs');
|
|
484
|
+
mutate(decoded.docs, 'boundDocs', 'docs');
|
|
485
|
+
mutate(decoded.tasks, 'boundTasks', 'tasks');
|
|
473
486
|
if (changes.length === 0) {
|
|
474
|
-
return wrap(
|
|
475
|
-
}
|
|
476
|
-
await client().saveFlowchart(chartId, chart);
|
|
477
|
-
// P2: 双向同步 — 更新参考文档的 adoptedBy
|
|
478
|
-
const docsList = decoded.docs;
|
|
479
|
-
if (docsList && docsList.length > 0) {
|
|
480
|
-
const nodeId = decoded.nodeId;
|
|
481
|
-
for (const docPath of docsList) {
|
|
482
|
-
try {
|
|
483
|
-
const doc = await client().getDoc(docPath);
|
|
484
|
-
if (!doc?.content)
|
|
485
|
-
continue;
|
|
486
|
-
const content = doc.content;
|
|
487
|
-
// 解析 frontmatter
|
|
488
|
-
if (!content.startsWith('---'))
|
|
489
|
-
continue;
|
|
490
|
-
const endIdx = content.indexOf('---', 3);
|
|
491
|
-
if (endIdx === -1)
|
|
492
|
-
continue;
|
|
493
|
-
const yaml = content.slice(3, endIdx);
|
|
494
|
-
const body = content.slice(endIdx + 3);
|
|
495
|
-
// 提取现有 adoptedBy
|
|
496
|
-
const adoptedMatch = yaml.match(/adoptedBy:\s*\[(.*?)\]/);
|
|
497
|
-
let adopted = [];
|
|
498
|
-
if (adoptedMatch) {
|
|
499
|
-
adopted = adoptedMatch[1].split(',').map(s => s.trim()).filter(Boolean);
|
|
500
|
-
}
|
|
501
|
-
// 修改 adoptedBy
|
|
502
|
-
if (isBind) {
|
|
503
|
-
if (!adopted.includes(nodeId))
|
|
504
|
-
adopted.push(nodeId);
|
|
505
|
-
}
|
|
506
|
-
else {
|
|
507
|
-
adopted = adopted.filter(id => id !== nodeId);
|
|
508
|
-
}
|
|
509
|
-
// 重建 frontmatter
|
|
510
|
-
const newAdoptedLine = `adoptedBy: [${adopted.join(', ')}]`;
|
|
511
|
-
let newYaml;
|
|
512
|
-
if (adoptedMatch) {
|
|
513
|
-
newYaml = yaml.replace(/adoptedBy:\s*\[.*?\]/, newAdoptedLine);
|
|
514
|
-
}
|
|
515
|
-
else {
|
|
516
|
-
newYaml = yaml.trimEnd() + '\n' + newAdoptedLine + '\n';
|
|
517
|
-
}
|
|
518
|
-
const newContent = `---${newYaml}---${body}`;
|
|
519
|
-
await client().updateDoc(docPath, { content: newContent });
|
|
520
|
-
}
|
|
521
|
-
catch { /* 单个文档同步失败不阻断 */ }
|
|
522
|
-
}
|
|
487
|
+
return wrap('No bindings changed.');
|
|
523
488
|
}
|
|
524
|
-
|
|
525
|
-
return wrap(`${
|
|
489
|
+
await client().updateFlowchartNode(chartId, decoded.nodeId, node, `${isBind ? 'Bind' : 'Unbind'} resources: ${changes.join(', ')}`);
|
|
490
|
+
return wrap(`${isBind ? 'Bound' : 'Unbound'} resources for [${decoded.nodeId}]: ${changes.join(', ')}`);
|
|
526
491
|
}
|
|
527
492
|
case 'create_chart': {
|
|
528
493
|
const newChartId = decoded.chartId;
|
|
529
|
-
if (!newChartId)
|
|
530
|
-
return wrap('
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
const
|
|
494
|
+
if (!newChartId) {
|
|
495
|
+
return wrap('create_chart requires chartId.');
|
|
496
|
+
}
|
|
497
|
+
if (newChartId === 'main') {
|
|
498
|
+
return wrap('create_chart cannot create main.');
|
|
499
|
+
}
|
|
500
|
+
const existing = (await client().getFlowchart(newChartId));
|
|
501
|
+
if (existing) {
|
|
502
|
+
return wrap(`Flowchart already exists: ${newChartId}`);
|
|
503
|
+
}
|
|
504
|
+
const parentChartId = decoded.parentChart || 'main';
|
|
505
|
+
const subChart = {
|
|
541
506
|
id: newChartId,
|
|
542
507
|
title: decoded.title || newChartId,
|
|
543
|
-
parentNode:
|
|
544
|
-
parentChart:
|
|
545
|
-
nodes: [],
|
|
546
|
-
edges: [],
|
|
508
|
+
parentNode: decoded.parentNode || null,
|
|
509
|
+
parentChart: parentChartId,
|
|
510
|
+
nodes: (decoded.nodes ?? []).map(buildNode),
|
|
511
|
+
edges: (decoded.edges ?? []).map(buildEdge),
|
|
547
512
|
};
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
nodeType: n.nodeType || 'process', domain: n.domain || 'system',
|
|
553
|
-
input: n.input || [], output: n.output || [],
|
|
554
|
-
description: n.description || '', affiliation: n.affiliation || 'root',
|
|
555
|
-
boundDocs: [], boundFiles: [], boundDirs: [], boundTasks: [],
|
|
556
|
-
subFlowchart: null, position: null,
|
|
557
|
-
versions: [{ version: 'v1.0', date: new Date().toISOString(), changes: 'initial' }],
|
|
558
|
-
lastUpdated: new Date().toISOString(), lastQueried: '',
|
|
559
|
-
}));
|
|
560
|
-
}
|
|
561
|
-
if (decoded.edges && decoded.edges.length > 0) {
|
|
562
|
-
subChartData.edges = decoded.edges.map(e => ({
|
|
563
|
-
from: e.from, to: e.to, label: e.label || null, edgeType: e.edgeType || 'call',
|
|
564
|
-
}));
|
|
565
|
-
}
|
|
566
|
-
// 保存子图
|
|
567
|
-
await client().saveFlowchart(newChartId, subChartData);
|
|
568
|
-
// 自动更新父节点的 subFlowchart 引用
|
|
569
|
-
if (pNode) {
|
|
570
|
-
const parentChart = await client().getFlowchart(pChart);
|
|
513
|
+
await client().saveFlowchart(newChartId, subChart);
|
|
514
|
+
let parentMessage = `Parent chart: ${parentChartId}`;
|
|
515
|
+
if (decoded.parentNode) {
|
|
516
|
+
const parentChart = (await client().getFlowchart(parentChartId));
|
|
571
517
|
if (parentChart) {
|
|
572
|
-
const
|
|
573
|
-
if (
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
await client().
|
|
518
|
+
const parentNode = parentChart.nodes.find((node) => node.id === decoded.parentNode);
|
|
519
|
+
if (parentNode) {
|
|
520
|
+
const updatedParent = ensureNodeShape(parentNode);
|
|
521
|
+
updatedParent.subFlowchart = newChartId;
|
|
522
|
+
await client().updateFlowchartNode(parentChartId, decoded.parentNode, updatedParent, `Attach sub flowchart ${newChartId}`);
|
|
523
|
+
parentMessage = `Parent chart: ${parentChartId} | Parent node: ${decoded.parentNode}`;
|
|
577
524
|
}
|
|
525
|
+
else {
|
|
526
|
+
parentMessage = `Parent chart: ${parentChartId} | Parent node not found: ${decoded.parentNode}`;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
else {
|
|
530
|
+
parentMessage = `Parent chart not found: ${parentChartId}`;
|
|
578
531
|
}
|
|
579
532
|
}
|
|
580
|
-
|
|
581
|
-
return wrap(`✅ 子图已创建: ${decoded.title || newChartId} [${newChartId}]\n父图: ${pChart}${pNode ? ` → 节点: ${pNode}` : ''}\n初始: ${nodeCount}节点`);
|
|
533
|
+
return wrap(`Sub chart created: ${subChart.title} [${newChartId}]\n${parentMessage}\nInitial nodes: ${subChart.nodes.length}\nInitial edges: ${subChart.edges.length}`);
|
|
582
534
|
}
|
|
583
535
|
case 'delete_chart': {
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
if (chartId === 'main')
|
|
588
|
-
return wrap('
|
|
589
|
-
|
|
590
|
-
await client().deleteFlowchart(chartId);
|
|
591
|
-
return wrap(
|
|
536
|
+
if (!decoded.chartId) {
|
|
537
|
+
return wrap('delete_chart requires chartId.');
|
|
538
|
+
}
|
|
539
|
+
if (decoded.chartId === 'main') {
|
|
540
|
+
return wrap('delete_chart cannot remove main.');
|
|
541
|
+
}
|
|
542
|
+
await client().deleteFlowchart(decoded.chartId);
|
|
543
|
+
return wrap(`Flowchart deleted: [${decoded.chartId}]`);
|
|
592
544
|
}
|
|
593
545
|
default:
|
|
594
|
-
return wrap(
|
|
546
|
+
return wrap(`Unknown action: ${String(decoded.action)}`);
|
|
595
547
|
}
|
|
596
548
|
}));
|
|
597
549
|
}
|