@qingflow-tech/qingflow-app-builder-mcp 1.0.44 → 1.1.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.
Files changed (40) hide show
  1. package/README.md +4 -2
  2. package/npm/bin/qingflow-app-builder-mcp.mjs +31 -2
  3. package/npm/lib/runtime.mjs +43 -2
  4. package/package.json +1 -1
  5. package/pyproject.toml +1 -1
  6. package/skills/qingflow-app-builder-code-integrations/SKILL.md +1 -1
  7. package/skills/qingflow-mcp-setup/SKILL.md +115 -0
  8. package/skills/qingflow-mcp-setup/agents/openai.yaml +4 -0
  9. package/skills/qingflow-mcp-setup/references/claude-desktop.md +34 -0
  10. package/skills/qingflow-mcp-setup/references/environments.md +62 -0
  11. package/skills/qingflow-mcp-setup/references/generic-stdio.md +32 -0
  12. package/skills/qingflow-mcp-setup/scripts/check_local_server.sh +38 -0
  13. package/skills/qingflow-workflow-builder/SKILL.md +98 -0
  14. package/skills/qingflow-workflow-builder/manifest.yaml +8 -0
  15. package/skills/qingflow-workflow-builder/references/01-overview.md +45 -0
  16. package/skills/qingflow-workflow-builder/references/02-update-mode.md +53 -0
  17. package/skills/qingflow-workflow-builder/references/03-flow-patterns.md +57 -0
  18. package/skills/qingflow-workflow-builder/references/04-stage1-business-modeling.md +131 -0
  19. package/skills/qingflow-workflow-builder/references/05-stage2-members-roles.md +29 -0
  20. package/skills/qingflow-workflow-builder/references/06-stage3-build-spec.md +165 -0
  21. package/skills/qingflow-workflow-builder/references/07-stage4-validate-spec.md +33 -0
  22. package/skills/qingflow-workflow-builder/references/08-stage5-apply-verify.md +51 -0
  23. package/skills/qingflow-workflow-builder/references/09-stage6-summary.md +88 -0
  24. package/skills/qingflow-workflow-builder/references/10-node-config-reference.md +93 -0
  25. package/skills/qingflow-workflow-builder/references/11-troubleshooting.md +15 -0
  26. package/skills/qingflow-workflow-builder/scripts/diff_flow_spec.py +275 -0
  27. package/skills/qingflow-workflow-builder/scripts/validate_flow_spec.py +605 -0
  28. package/src/qingflow_mcp/__init__.py +1 -1
  29. package/src/qingflow_mcp/builder_facade/models.py +0 -39
  30. package/src/qingflow_mcp/builder_facade/service.py +262 -862
  31. package/src/qingflow_mcp/builder_facade/workflow_spec.py +111 -0
  32. package/src/qingflow_mcp/cli/commands/builder.py +44 -12
  33. package/src/qingflow_mcp/public_surface.py +2 -0
  34. package/src/qingflow_mcp/server_app_builder.py +16 -8
  35. package/src/qingflow_mcp/solution/compiler/__init__.py +1 -3
  36. package/src/qingflow_mcp/solution/executor.py +3 -133
  37. package/src/qingflow_mcp/tools/ai_builder_tools.py +92 -233
  38. package/src/qingflow_mcp/tools/solution_tools.py +30 -2
  39. package/src/qingflow_mcp/tools/workflow_tools.py +3 -31
  40. package/src/qingflow_mcp/solution/compiler/workflow_compiler.py +0 -173
@@ -0,0 +1,275 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ 对比两个工作流 spec 的差异,输出新增/删除/修改的节点和边。
4
+ 用于更新模式下辅助 agent 判断是否遵循最小修改原则。
5
+
6
+ 用法:
7
+ python3 diff_flow_spec.py <old_spec.json> <new_spec.json>
8
+ python3 diff_flow_spec.py <old_spec.json> <new_spec.json> --json # JSON 输出
9
+ """
10
+
11
+ import json
12
+ import sys
13
+
14
+
15
+ def load_json(path):
16
+ with open(path, 'r', encoding='utf-8') as f:
17
+ return json.load(f)
18
+
19
+
20
+ def extract_edges(spec):
21
+ """从 spec 中提取边列表,兼容 edges 在不同层级的情况"""
22
+ e = spec.get('edges', [])
23
+ if isinstance(e, dict) and 'edges' in e:
24
+ return e['edges']
25
+ if isinstance(e, list):
26
+ return e
27
+ return []
28
+
29
+
30
+ def node_key(n):
31
+ """节点的唯一标识"""
32
+ return n.get('id', '')
33
+
34
+
35
+ def edge_key(e):
36
+ """边的唯一标识:from→to"""
37
+ return (e.get('from', ''), e.get('to', ''))
38
+
39
+
40
+ def node_identity(n):
41
+ """节点的完整内容(不含 id,用于判断是否修改)"""
42
+ return {
43
+ 'type': n.get('type', ''),
44
+ 'name': n.get('name', ''),
45
+ 'attrs': n.get('attrs', {}),
46
+ }
47
+
48
+
49
+ def edge_identity(e):
50
+ """边的完整内容(不含 from/to,用于判断是否修改)"""
51
+ return {
52
+ 'label': e.get('label', ''),
53
+ 'condition': e.get('condition', {}),
54
+ }
55
+
56
+
57
+ def diff_nodes(old_nodes, new_nodes):
58
+ """对比节点差异"""
59
+ old_by_id = {node_key(n): n for n in old_nodes}
60
+ new_by_id = {node_key(n): n for n in new_nodes}
61
+
62
+ old_ids = set(old_by_id.keys())
63
+ new_ids = set(new_by_id.keys())
64
+
65
+ deleted_ids = sorted(old_ids - new_ids)
66
+ added_ids = sorted(new_ids - old_ids)
67
+ common_ids = sorted(old_ids & new_ids)
68
+
69
+ modified = []
70
+ id_changes = []
71
+ for nid in common_ids:
72
+ old_ident = node_identity(old_by_id[nid])
73
+ new_ident = node_identity(new_by_id[nid])
74
+ if old_ident != new_ident:
75
+ modified.append({
76
+ 'id': nid,
77
+ 'old': {'type': old_ident['type'], 'name': old_ident['name']},
78
+ 'new': {'type': new_ident['type'], 'name': new_ident['name']},
79
+ 'attrs_changed': old_ident['attrs'] != new_ident['attrs'],
80
+ 'type_changed': old_ident['type'] != new_ident['type'],
81
+ 'name_changed': old_ident['name'] != new_ident['name'],
82
+ })
83
+
84
+ # 检测可能的不必要 ID 变更(内容相同但 ID 不同)
85
+ old_by_identity = {}
86
+ for n in old_nodes:
87
+ ident = json.dumps(node_identity(n), sort_keys=True, ensure_ascii=False)
88
+ old_by_identity.setdefault(ident, []).append(n)
89
+
90
+ for n in new_nodes:
91
+ if node_key(n) in added_ids:
92
+ ident = json.dumps(node_identity(n), sort_keys=True, ensure_ascii=False)
93
+ if ident in old_by_identity and old_by_identity[ident]:
94
+ old_match = old_by_identity[ident][0]
95
+ if node_key(old_match) in deleted_ids:
96
+ id_changes.append({
97
+ 'old_id': node_key(old_match),
98
+ 'new_id': node_key(n),
99
+ 'identity': node_identity(n),
100
+ 'warning': '节点内容未变但 ID 已变更,可能导致后端不支持配置丢失',
101
+ })
102
+
103
+ return {
104
+ 'deleted': [{'id': nid, 'name': old_by_id[nid].get('name', ''), 'type': old_by_id[nid].get('type', '')} for nid in deleted_ids],
105
+ 'added': [{'id': nid, 'name': new_by_id[nid].get('name', ''), 'type': new_by_id[nid].get('type', '')} for nid in added_ids],
106
+ 'modified': modified,
107
+ 'id_changes': id_changes,
108
+ 'unchanged': len(common_ids) - len(modified),
109
+ }
110
+
111
+
112
+ def diff_edges(old_edges, new_edges):
113
+ """对比边差异"""
114
+ old_by_key = {edge_key(e): e for e in old_edges}
115
+ new_by_key = {edge_key(e): e for e in new_edges}
116
+
117
+ old_keys = set(old_by_key.keys())
118
+ new_keys = set(new_by_key.keys())
119
+
120
+ deleted_keys = sorted(old_keys - new_keys)
121
+ added_keys = sorted(new_keys - old_keys)
122
+ common_keys = sorted(old_keys & new_keys)
123
+
124
+ modified = []
125
+ for key in common_keys:
126
+ old_ident = edge_identity(old_by_key[key])
127
+ new_ident = edge_identity(new_by_key[key])
128
+ if old_ident != new_ident:
129
+ modified.append({
130
+ 'from': key[0],
131
+ 'to': key[1],
132
+ 'old_condition': old_ident['condition'],
133
+ 'new_condition': new_ident['condition'],
134
+ 'label_changed': old_ident['label'] != new_ident['label'],
135
+ 'condition_changed': old_ident['condition'] != new_ident['condition'],
136
+ })
137
+
138
+ return {
139
+ 'deleted': [{'from': k[0], 'to': k[1]} for k in deleted_keys],
140
+ 'added': [{'from': k[0], 'to': k[1]} for k in added_keys],
141
+ 'modified': modified,
142
+ 'unchanged': len(common_keys) - len(modified),
143
+ }
144
+
145
+
146
+ def print_human(result):
147
+ """人类可读输出"""
148
+ nodes = result['nodes']
149
+ edges = result['edges']
150
+
151
+ print("=" * 60)
152
+ print("工作流 Spec 差异分析")
153
+ print("=" * 60)
154
+
155
+ # 节点汇总
156
+ print(f"\n📊 节点汇总:")
157
+ print(f" 删除: {len(nodes['deleted'])} | 新增: {len(nodes['added'])} | "
158
+ f"修改: {len(nodes['modified'])} | 未变: {nodes['unchanged']}")
159
+
160
+ if nodes['id_changes']:
161
+ print(f"\n⚠️ 检测到 {len(nodes['id_changes'])} 个可能的 ID 变更(内容相同但 ID 不同):")
162
+ for ic in nodes['id_changes']:
163
+ print(f" {ic['old_id']} → {ic['new_id']} ({ic['identity']['name']})")
164
+ print(f" ⚠ {ic['warning']}")
165
+
166
+ if nodes['deleted']:
167
+ print(f"\n🗑 删除的节点 ({len(nodes['deleted'])}):")
168
+ for d in nodes['deleted']:
169
+ print(f" - [{d['type']}] {d['id']} ({d['name']})")
170
+
171
+ if nodes['added']:
172
+ print(f"\n➕ 新增的节点 ({len(nodes['added'])}):")
173
+ for a in nodes['added']:
174
+ print(f" - [{a['type']}] {a['id']} ({a['name']})")
175
+
176
+ if nodes['modified']:
177
+ print(f"\n✏️ 修改的节点 ({len(nodes['modified'])}):")
178
+ for m in nodes['modified']:
179
+ changes = []
180
+ if m['type_changed']:
181
+ changes.append(f"type: {m['old']['type']} → {m['new']['type']}")
182
+ if m['name_changed']:
183
+ changes.append(f"name: {m['old']['name']} → {m['new']['name']}")
184
+ if m['attrs_changed']:
185
+ changes.append("attrs 已变更")
186
+ print(f" - {m['id']}: {', '.join(changes) if changes else '无实质变更'}")
187
+
188
+ # 边汇总
189
+ print(f"\n📊 边汇总:")
190
+ print(f" 删除: {len(edges['deleted'])} | 新增: {len(edges['added'])} | "
191
+ f"修改: {len(edges['modified'])} | 未变: {edges['unchanged']}")
192
+
193
+ if edges['deleted']:
194
+ print(f"\n🗑 删除的边 ({len(edges['deleted'])}):")
195
+ for d in edges['deleted']:
196
+ print(f" - {d['from']} → {d['to']}")
197
+
198
+ if edges['added']:
199
+ print(f"\n➕ 新增的边 ({len(edges['added'])}):")
200
+ for a in edges['added']:
201
+ print(f" - {a['from']} → {a['to']}")
202
+
203
+ if edges['modified']:
204
+ print(f"\n✏️ 修改的边 ({len(edges['modified'])}):")
205
+ for m in edges['modified']:
206
+ changes = []
207
+ if m['label_changed']:
208
+ changes.append("label 已变更")
209
+ if m['condition_changed']:
210
+ changes.append("condition 已变更")
211
+ if changes:
212
+ print(f" - {m['from']} → {m['to']}: {', '.join(changes)}")
213
+ else:
214
+ print(f" - {m['from']} → {m['to']}: 无实质变更")
215
+
216
+ # 最小修改原则评估
217
+ print(f"\n📋 最小修改原则评估:")
218
+ issues = 0
219
+ if nodes['id_changes']:
220
+ print(f" ❌ 存在 ID 变更({len(nodes['id_changes'])} 个节点),可能导致后端不支持配置丢失")
221
+ issues += 1
222
+ if nodes['deleted']:
223
+ print(f" ⚠️ 删除了 {len(nodes['deleted'])} 个节点,请确认是否为业务需要")
224
+ issues += 1
225
+ if not nodes['deleted'] and not nodes['id_changes'] and nodes['modified']:
226
+ print(f" ✅ 仅修改已有节点,未删除或变更 ID,符合最小修改原则")
227
+ if not nodes['deleted'] and not nodes['modified'] and not nodes['added'] and not nodes['id_changes']:
228
+ print(f" ✅ 无任何变更")
229
+
230
+ print(f"\n总结: 错误 {issues} 项")
231
+
232
+
233
+ def main():
234
+ if len(sys.argv) < 3:
235
+ print("用法: python3 diff_flow_spec.py <old_spec.json> <new_spec.json> [--json]")
236
+ sys.exit(1)
237
+
238
+ old_file = sys.argv[1]
239
+ new_file = sys.argv[2]
240
+ output_json = '--json' in sys.argv
241
+
242
+ try:
243
+ old_spec = load_json(old_file)
244
+ except Exception as e:
245
+ print(f"FATAL: 无法读取旧 spec 文件 {old_file}: {e}")
246
+ sys.exit(1)
247
+
248
+ try:
249
+ new_spec = load_json(new_file)
250
+ except Exception as e:
251
+ print(f"FATAL: 无法读取新 spec 文件 {new_file}: {e}")
252
+ sys.exit(1)
253
+
254
+ old_nodes = old_spec.get('nodes', [])
255
+ new_nodes = new_spec.get('nodes', [])
256
+ old_edges = extract_edges(old_spec)
257
+ new_edges = extract_edges(new_spec)
258
+
259
+ result = {
260
+ 'nodes': diff_nodes(old_nodes, new_nodes),
261
+ 'edges': diff_edges(old_edges, new_edges),
262
+ }
263
+
264
+ if output_json:
265
+ print(json.dumps(result, indent=2, ensure_ascii=False))
266
+ else:
267
+ print_human(result)
268
+
269
+ # 如果有 ID 变更,返回非零以便脚本判断
270
+ has_id_changes = len(result['nodes']['id_changes']) > 0
271
+ sys.exit(1 if has_id_changes else 0)
272
+
273
+
274
+ if __name__ == '__main__':
275
+ main()