@josephyan/qingflow-cli 1.1.4 → 1.1.5

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 (154) hide show
  1. package/README.md +7 -3
  2. package/docs/local-agent-install.md +57 -6
  3. package/entry_point.py +1 -1
  4. package/npm/bin/qingflow-skills.mjs +5 -0
  5. package/npm/bin/qingflow.mjs +1 -34
  6. package/npm/lib/runtime.mjs +21 -101
  7. package/npm/scripts/postinstall.mjs +1 -10
  8. package/package.json +3 -2
  9. package/pyproject.toml +1 -1
  10. package/skills/qingflow-cli/SKILL.md +58 -44
  11. package/skills/qingflow-cli/manifest.yaml +1 -1
  12. package/skills/qingflow-cli/reference/00-INDEX.md +35 -0
  13. package/skills/qingflow-cli/reference/builder/10-build-single-app.md +38 -0
  14. package/skills/qingflow-cli/reference/builder/20-build-complete-system.md +39 -0
  15. package/skills/qingflow-cli/reference/{QINGFLOW_CLI_SCHEMA_APPLY_FIELD_TYPES_AND_SCENARIOS.md → builder/30-schema-fields.md} +52 -10
  16. package/skills/qingflow-cli/reference/builder/40-layout.md +52 -0
  17. package/skills/qingflow-cli/reference/{QINGFLOW_CLI_BUILDER_VIEWS_WORKFLOW.md → builder/50-views.md} +39 -15
  18. package/skills/qingflow-cli/reference/{QINGFLOW_CLI_BUILDER_CHARTS_WORKFLOW.md → builder/60-charts.md} +36 -13
  19. package/skills/qingflow-cli/reference/{QINGFLOW_CLI_BUILDER_PORTAL_WORKFLOW.md → builder/70-portal.md} +36 -13
  20. package/skills/qingflow-cli/reference/builder/80-buttons-associated-resources.md +41 -0
  21. package/skills/qingflow-cli/reference/builder/90-workflow.md +34 -0
  22. package/skills/qingflow-cli/reference/builder/99-publish-verify.md +46 -0
  23. package/skills/qingflow-cli/reference/builder/README.md +41 -0
  24. package/skills/qingflow-cli/reference/builder/code-integrations/README.md +130 -0
  25. package/skills/qingflow-cli/reference/builder/code-integrations/code-block.md +66 -0
  26. package/skills/qingflow-cli/reference/builder/code-integrations/q-linker.md +77 -0
  27. package/skills/qingflow-cli/reference/{QINGFLOW_CLI_BUILDER_APP_DELIVERY_WORKFLOW.md → builder/reference/app-delivery-sop.md} +26 -16
  28. package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/README.md +293 -0
  29. package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/build-complete-system.md +809 -0
  30. package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/build-single-app.md +830 -0
  31. package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/complete-system-development-guide.md +123 -0
  32. package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/create-app.md +182 -0
  33. package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/environments.md +63 -0
  34. package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/flow-actors-and-permissions.md +142 -0
  35. package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/gotchas.md +108 -0
  36. package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/match-rules.md +114 -0
  37. package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/public-surface-sync.md +75 -0
  38. package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/single-app-development-guide.md +58 -0
  39. package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/solution-playbooks.md +52 -0
  40. package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/tool-selection.md +107 -0
  41. package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/update-flow.md +7 -0
  42. package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/update-layout.md +7 -0
  43. package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/update-schema.md +7 -0
  44. package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/update-views.md +7 -0
  45. package/skills/qingflow-cli/reference/builder/workflow/01-overview.md +45 -0
  46. package/skills/qingflow-cli/reference/builder/workflow/02-update-mode.md +53 -0
  47. package/skills/qingflow-cli/reference/builder/workflow/03-flow-patterns.md +57 -0
  48. package/skills/qingflow-cli/reference/builder/workflow/04-stage1-business-modeling.md +131 -0
  49. package/skills/qingflow-cli/reference/builder/workflow/05-stage2-members-roles.md +29 -0
  50. package/skills/qingflow-cli/reference/builder/workflow/06-stage3-build-spec.md +165 -0
  51. package/skills/qingflow-cli/reference/builder/workflow/07-stage4-validate-spec.md +33 -0
  52. package/skills/qingflow-cli/reference/builder/workflow/08-stage5-apply-verify.md +51 -0
  53. package/skills/qingflow-cli/reference/builder/workflow/09-stage6-summary.md +88 -0
  54. package/skills/qingflow-cli/reference/builder/workflow/10-node-config-reference.md +93 -0
  55. package/skills/qingflow-cli/reference/builder/workflow/11-troubleshooting.md +15 -0
  56. package/skills/qingflow-cli/reference/builder/workflow/README.md +88 -0
  57. package/skills/qingflow-cli/reference/builder/workflow/workflow-schema.json +1754 -0
  58. package/skills/qingflow-cli/reference/{QINGFLOW_CLI_ADMIN_CHEATSHEET.md → core/QINGFLOW_CLI_ADMIN_CHEATSHEET.md} +3 -3
  59. package/skills/qingflow-cli/reference/{QINGFLOW_CLI_DATA_RETRIEVAL_WORKFLOW.md → core/QINGFLOW_CLI_DATA_RETRIEVAL_WORKFLOW.md} +6 -6
  60. package/skills/qingflow-cli/reference/{QINGFLOW_CLI_EXPLORATION_REPORT.md → core/QINGFLOW_CLI_EXPLORATION_REPORT.md} +2 -2
  61. package/skills/qingflow-cli/reference/{QINGFLOW_CLI_FIELD_DATA_TYPES.md → core/QINGFLOW_CLI_FIELD_DATA_TYPES.md} +11 -11
  62. package/skills/qingflow-cli/reference/{QINGFLOW_CLI_MEMBER_CHEATSHEET.md → core/QINGFLOW_CLI_MEMBER_CHEATSHEET.md} +4 -4
  63. package/skills/qingflow-cli/reference/{QINGFLOW_CLI_ONE_SHOT_CHEATSHEET.md → core/QINGFLOW_CLI_ONE_SHOT_CHEATSHEET.md} +4 -4
  64. package/skills/qingflow-cli/reference/{QINGFLOW_CLI_RECORD_CREATE_WORKFLOW.md → record/QINGFLOW_CLI_RECORD_CREATE_WORKFLOW.md} +3 -3
  65. package/skills/qingflow-cli/reference/record/QINGFLOW_CLI_RECORD_DELETE_WORKFLOW.md +31 -0
  66. package/skills/qingflow-cli/reference/{QINGFLOW_CLI_RECORD_IMPORT_WORKFLOW.md → record/QINGFLOW_CLI_RECORD_IMPORT_WORKFLOW.md} +4 -4
  67. package/skills/qingflow-cli/reference/{QINGFLOW_CLI_RECORD_UPDATE_WORKFLOW.md → record/QINGFLOW_CLI_RECORD_UPDATE_WORKFLOW.md} +7 -7
  68. package/skills/qingflow-cli/reference/record/analysis/README.md +130 -0
  69. package/skills/qingflow-cli/reference/record/analysis/analysis-gotchas.md +91 -0
  70. package/skills/qingflow-cli/reference/record/analysis/analysis-patterns.md +112 -0
  71. package/skills/qingflow-cli/reference/record/analysis/business-context.md +74 -0
  72. package/skills/qingflow-cli/reference/record/analysis/confidence-reporting.md +69 -0
  73. package/skills/qingflow-cli/reference/record/analysis/data-access-playbook.md +106 -0
  74. package/skills/qingflow-cli/reference/record/analysis/pandas-recipes.md +172 -0
  75. package/skills/qingflow-cli/reference/record/analysis/report-format.md +76 -0
  76. package/skills/qingflow-cli/reference/record/insert/README.md +75 -0
  77. package/skills/qingflow-cli/reference/{QINGFLOW_CLI_TASK_CONTEXT_WORKFLOW.md → task/QINGFLOW_CLI_TASK_CONTEXT_WORKFLOW.md} +5 -5
  78. package/skills/qingflow-cli/reference/task/ops/README.md +131 -0
  79. package/skills/qingflow-cli/reference/task/ops/environments.md +43 -0
  80. package/skills/qingflow-cli/reference/task/ops/workflow-usage.md +26 -0
  81. package/skills/qingflow-cli/scripts/validate_system_build_summary.py +124 -0
  82. package/skills/qingflow-cli/scripts/workflow/diff_flow_spec.py +275 -0
  83. package/skills/qingflow-cli/scripts/workflow/validate_flow_spec.py +605 -0
  84. package/skills/qingflow-mcp-setup/SKILL.md +115 -0
  85. package/skills/qingflow-mcp-setup/agents/openai.yaml +4 -0
  86. package/skills/qingflow-mcp-setup/references/claude-desktop.md +34 -0
  87. package/skills/qingflow-mcp-setup/references/environments.md +62 -0
  88. package/skills/qingflow-mcp-setup/references/generic-stdio.md +32 -0
  89. package/skills/qingflow-mcp-setup/scripts/check_local_server.sh +38 -0
  90. package/src/qingflow_mcp/__init__.py +1 -1
  91. package/src/qingflow_mcp/__main__.py +6 -2
  92. package/src/qingflow_mcp/builder_facade/models.py +282 -102
  93. package/src/qingflow_mcp/builder_facade/service.py +4166 -929
  94. package/src/qingflow_mcp/cli/commands/builder.py +316 -298
  95. package/src/qingflow_mcp/cli/commands/chart.py +1 -1
  96. package/src/qingflow_mcp/cli/commands/common.py +12 -3
  97. package/src/qingflow_mcp/cli/commands/exports.py +2 -2
  98. package/src/qingflow_mcp/cli/commands/imports.py +3 -3
  99. package/src/qingflow_mcp/cli/commands/portal.py +2 -2
  100. package/src/qingflow_mcp/cli/commands/record.py +101 -27
  101. package/src/qingflow_mcp/cli/commands/task.py +28 -47
  102. package/src/qingflow_mcp/cli/commands/view.py +1 -1
  103. package/src/qingflow_mcp/cli/context.py +0 -3
  104. package/src/qingflow_mcp/cli/formatters.py +784 -16
  105. package/src/qingflow_mcp/cli/main.py +117 -33
  106. package/src/qingflow_mcp/errors.py +43 -2
  107. package/src/qingflow_mcp/public_surface.py +26 -17
  108. package/src/qingflow_mcp/response_trim.py +81 -17
  109. package/src/qingflow_mcp/server.py +14 -12
  110. package/src/qingflow_mcp/server_app_builder.py +65 -21
  111. package/src/qingflow_mcp/server_app_user.py +22 -16
  112. package/src/qingflow_mcp/session_store.py +11 -7
  113. package/src/qingflow_mcp/solution/compiler/__init__.py +3 -1
  114. package/src/qingflow_mcp/solution/compiler/workflow_compiler.py +173 -0
  115. package/src/qingflow_mcp/solution/executor.py +245 -18
  116. package/src/qingflow_mcp/tools/ai_builder_tools.py +1780 -406
  117. package/src/qingflow_mcp/tools/app_tools.py +184 -43
  118. package/src/qingflow_mcp/tools/approval_tools.py +197 -35
  119. package/src/qingflow_mcp/tools/auth_tools.py +92 -16
  120. package/src/qingflow_mcp/tools/code_block_tools.py +298 -40
  121. package/src/qingflow_mcp/tools/custom_button_tools.py +64 -10
  122. package/src/qingflow_mcp/tools/directory_tools.py +236 -72
  123. package/src/qingflow_mcp/tools/export_tools.py +244 -34
  124. package/src/qingflow_mcp/tools/feedback_tools.py +9 -0
  125. package/src/qingflow_mcp/tools/file_tools.py +9 -3
  126. package/src/qingflow_mcp/tools/import_tools.py +336 -49
  127. package/src/qingflow_mcp/tools/navigation_tools.py +91 -12
  128. package/src/qingflow_mcp/tools/package_tools.py +118 -6
  129. package/src/qingflow_mcp/tools/portal_tools.py +39 -3
  130. package/src/qingflow_mcp/tools/qingbi_report_tools.py +116 -7
  131. package/src/qingflow_mcp/tools/record_tools.py +1141 -356
  132. package/src/qingflow_mcp/tools/resource_read_tools.py +188 -39
  133. package/src/qingflow_mcp/tools/role_tools.py +80 -9
  134. package/src/qingflow_mcp/tools/solution_tools.py +59 -45
  135. package/src/qingflow_mcp/tools/task_context_tools.py +662 -158
  136. package/src/qingflow_mcp/tools/task_tools.py +113 -29
  137. package/src/qingflow_mcp/tools/view_tools.py +106 -3
  138. package/src/qingflow_mcp/tools/workflow_tools.py +48 -4
  139. package/src/qingflow_mcp/tools/workspace_tools.py +71 -3
  140. /package/skills/qingflow-cli/reference/{QINGFLOW_CLI_BUILDER_MATCH_RULES.md → builder/reference/match-rules.md} +0 -0
  141. /package/skills/qingflow-cli/reference/{QINGFLOW_CLI_BUILDER_WORKSPACE_ICONS.md → builder/reference/workspace-icons.md} +0 -0
  142. /package/skills/qingflow-cli/reference/{charts_remove.example.json → examples/charts/charts_remove.example.json} +0 -0
  143. /package/skills/qingflow-cli/reference/{charts_reorder.example.json → examples/charts/charts_reorder.example.json} +0 -0
  144. /package/skills/qingflow-cli/reference/{charts_upsert_bar.example.json → examples/charts/charts_upsert_bar.example.json} +0 -0
  145. /package/skills/qingflow-cli/reference/{charts_upsert_dashboard_starter.example.json → examples/charts/charts_upsert_dashboard_starter.example.json} +0 -0
  146. /package/skills/qingflow-cli/reference/{charts_upsert_minimal.example.json → examples/charts/charts_upsert_minimal.example.json} +0 -0
  147. /package/skills/qingflow-cli/reference/{portal_sections_all_types.example.json → examples/portal/portal_sections_all_types.example.json} +0 -0
  148. /package/skills/qingflow-cli/reference/{portal_sections_five_types.example.json → examples/portal/portal_sections_five_types.example.json} +0 -0
  149. /package/skills/qingflow-cli/reference/{portal_sections_standard_workbench.example.json → examples/portal/portal_sections_standard_workbench.example.json} +0 -0
  150. /package/skills/qingflow-cli/reference/{_batch_schema_complex.json → examples/schema/_batch_schema_complex.json} +0 -0
  151. /package/skills/qingflow-cli/reference/{_batch_schema_scalar.json → examples/schema/_batch_schema_scalar.json} +0 -0
  152. /package/skills/qingflow-cli/reference/{schema_add_fields_minimal.example.json → examples/schema/schema_add_fields_minimal.example.json} +0 -0
  153. /package/skills/qingflow-cli/reference/{schema_apply_add_fields_all_types.json → examples/schema/schema_apply_add_fields_all_types.json} +0 -0
  154. /package/skills/qingflow-cli/reference/{views_upsert_table_minimal.example.json → examples/views/views_upsert_table_minimal.example.json} +0 -0
@@ -2,7 +2,7 @@
2
2
 
3
3
  本文与 **MCP / 服务端公开工具命名**对齐,并给出已安装的 `**qingflow` CLI** 等价命令。编写前已在当前环境对 `**task list` / `task get` / `task log`** 实跑;`**task report`** 在抽样任务上遇到过后端 `**40038` Object not exist**(见文末「实测排障」),属数据/权限/报表生命周期问题,**不否定**标准编排顺序。
4
4
 
5
- **必须落盘**:`task` 的 `list`、`get`、`log`、`report` 以及 `**task action` 的响应**若体积大,均应 `> tmp/qingflow_*.json`,与主技能 [SKILL.md](../SKILL.md) 一致。
5
+ **必须落盘**:`task` 的 `list`、`get`、`log`、`report` 以及 `**task action` 的响应**若体积大,均应 `> tmp/qingflow_*.json`,与主技能 [SKILL.md](../../SKILL.md) 一致。
6
6
 
7
7
  ---
8
8
 
@@ -106,7 +106,7 @@ qingflow --json task action \
106
106
  > tmp/qingflow_task_action.json
107
107
  ```
108
108
 
109
- `**--action` 允许字面量**(实现校验,大小写不敏感):
109
+ `**--action` 允许字面量**(实现校验,大小写不敏感):
110
110
  `approve`、`reject`、`rollback`、`transfer`、`urge`、`save_only`。
111
111
 
112
112
  **约束摘要**(与实现一致):
@@ -126,7 +126,7 @@ qingflow --json task action \
126
126
  ## 4. 与「应用内 `record list`」的边界
127
127
 
128
128
  - **任务中心待办 / 已办 / 抄送**:用 `**task list --task-box …`**,**不要**再用过时的 `record list --view-id system:todo` 等当作任务箱。
129
- - **业务表读数**:仍在 `app get` 的 `**accessible_views`** 里选 `**custom:`* 等**,走 [QINGFLOW_CLI_DATA_RETRIEVAL_WORKFLOW.md](./QINGFLOW_CLI_DATA_RETRIEVAL_WORKFLOW.md)。
129
+ - **业务表读数**:仍在 `app get` 的 `**accessible_views`** 里选 `**custom:`* 等**,走 [QINGFLOW_CLI_DATA_RETRIEVAL_WORKFLOW.md](../core/QINGFLOW_CLI_DATA_RETRIEVAL_WORKFLOW.md)。
130
130
 
131
131
  ---
132
132
 
@@ -147,5 +147,5 @@ qingflow --json task action \
147
147
 
148
148
  ## 6. 交叉引用
149
149
 
150
- - 主技能 [SKILL.md](../SKILL.md):**落盘规则**、`**task-box` / `flow-status`** 枚举、与 `record` 混用雷区。
151
- - [QINGFLOW_CLI_MEMBER_CHEATSHEET.md](./QINGFLOW_CLI_MEMBER_CHEATSHEET.md):成员侧最短路径。
150
+ - 主技能 [SKILL.md](../../SKILL.md):**落盘规则**、`**task-box` / `flow-status`** 枚举、与 `record` 混用雷区。
151
+ - [QINGFLOW_CLI_MEMBER_CHEATSHEET.md](../core/QINGFLOW_CLI_MEMBER_CHEATSHEET.md):成员侧最短路径。
@@ -0,0 +1,131 @@
1
+ # Qingflow CLI Task Ops
2
+
3
+ ## Overview
4
+
5
+ This section is for task workflow operations only. Do not use it for record CRUD or final statistical analysis.
6
+
7
+ ## Default Paths
8
+
9
+ Use exactly one of these default paths:
10
+
11
+ 1. Find target todos
12
+ `qingflow task list`
13
+
14
+ 2. Read one task context
15
+ `task list -> exact target -> task get`
16
+
17
+ 3. Read associated approval context
18
+ `task get -> task report` or `task log`
19
+
20
+ 4. Execute workflow action
21
+ `task list -> exact target -> task get -> task action`
22
+
23
+ 5. Execute a user-specified action on an already-clear target
24
+ `task list -> exact target -> (optional task get) -> task action`
25
+
26
+ ## Core Tools
27
+
28
+ - `qingflow task list`
29
+ - `qingflow task get`
30
+ - `qingflow task action`
31
+ - `qingflow task report`
32
+ - `qingflow task log`
33
+
34
+ ## Supporting Tools
35
+
36
+ - `app_list`
37
+ - `app_search`
38
+
39
+ ## Standard Operating Order
40
+
41
+ Use one of these two modes:
42
+
43
+ 1. Recommendation mode
44
+ 1. Discover the exact target with `task list`
45
+ 2. Read node context with `task get`
46
+ 3. Before giving any approval recommendation, read `task log`
47
+ 4. If `task get` returns any `associated_reports`, read every visible report through `task report`
48
+ 5. Give a recommendation only after reviewing node context, workflow log, and associated reports
49
+ 6. Wait for explicit user confirmation before `task action`
50
+
51
+ 2. User-directed execution mode
52
+ 1. Discover the exact target with `task list`
53
+ 2. If the target or action requirements are ambiguous, read `task get`; otherwise go straight to `task action`
54
+ 3. Execute through `task action --task-id ...`
55
+ 4. After actions, report the exact `task_id`, executed action, and any returned `app_key / record_id` plus warnings
56
+
57
+ ## Task-Center Rules
58
+
59
+ - Use `task list` for flat browsing
60
+ - `task_box` must be one of:
61
+ - `todo`
62
+ - `initiated`
63
+ - `cc`
64
+ - `done`
65
+ - `flow_status` must be one of:
66
+ - `all`
67
+ - `in_progress`
68
+ - `approved`
69
+ - `rejected`
70
+ - `pending_fix`
71
+ - `urged`
72
+ - `overdue`
73
+ - `due_soon`
74
+ - `unread`
75
+ - `ended`
76
+ - `task list` is the only public task discovery path
77
+ - `task list --query` uses backend `searchKey` first; only when backend returns zero rows does CLI apply a local fallback match on normalized `app_name / workflow_node_name / app_key / record_id`
78
+ - Treat `task_id` from `task list.data.items[]` as the public action locator. Do not reconstruct action identity from `app_key + record_id + workflow_node_id`.
79
+ - Default box usage:
80
+ - `todo`: `task list -> task get -> task log / task report -> recommendation -> explicit user confirmation -> task action`
81
+ - `initiated`: `task list -> record get`
82
+ - `done`: `task list -> record get`
83
+ - `cc`: `task list -> record get`
84
+ - Treat `initiated`, `done`, and `cc` primarily as list-plus-record-detail flows, not task action flows
85
+
86
+ ## Workflow Usage Actions
87
+
88
+ - `task get.capabilities.available_actions` is the source of truth for executable actions
89
+ - Current public actions are:
90
+ - `approve`
91
+ - `reject`
92
+ - `rollback`
93
+ - `transfer`
94
+ - `urge`
95
+ - `save_only`
96
+ - Before any approve/reject/rollback/transfer recommendation, always review `task log` when `task get.visibility.audit_record_visible=true`
97
+ - If `task get` returns visible `associated_reports`, review each one with `task report`; do not rely on report summary alone
98
+ - Do not give an approval recommendation based only on `task get`
99
+ - Do not execute `task action` until the user explicitly confirms the chosen action
100
+ - Exception: if the user has already explicitly authorized a concrete action on exact targets, you may execute directly after exact target resolution
101
+ - Avoid actions on ambiguous tasks or records
102
+ - Summarize the final action and the exact `task_id`
103
+ - `reject` requires `payload.audit_feedback`
104
+ - `save_only` requires non-empty `fields` and is only available when the backend exposes editable fields for the current node
105
+ - `task action` distinguishes action execution from workflow continuation. Read `verification.runtime_continuation_verified` before claiming the workflow actually moved on.
106
+ - If `task action` returns `partial_success` with `WORKFLOW_CONTINUATION_UNVERIFIED`, report the action as sent but the downstream continuation as unverified.
107
+ - If `task action` returns `TASK_CONTEXT_VISIBILITY_UNVERIFIED` after a `46001`-style context loss, do not claim the task was already processed unless the workflow log or record state proves it.
108
+ - If `task_action_execute` returns `TASK_RUNTIME_CONSUMED_AFTER_ACTION`, treat that as a normal post-success state: the current node runtime was consumed, the workflow likely continued, and `46001` does not by itself mean the action failed
109
+
110
+ ## Feedback Escalation
111
+
112
+ - If task capabilities, associated report detail, workflow log visibility, or action support still cannot satisfy the user's goal after reasonable use of this skill, summarize the exact gap in plain language.
113
+ - Ask whether the user wants you to submit product feedback.
114
+ - Only after explicit user confirmation, call `feedback_submit`.
115
+
116
+ ## Response Interpretation
117
+
118
+ - `task_list` returns normalized todo rows and is the only default discovery path
119
+ - `task_list` may return `TASK_LIST_QUERY_FALLBACK_APPLIED`; this means backend search missed the query and MCP recovered the result through local exact-field fallback
120
+ - `task_get` returns node context summary, not full historical report data
121
+ - `task_associated_report_detail_get` may return either:
122
+ - `result_type=view_list`
123
+ - `result_type=chart_data`
124
+ - `task_workflow_log_get` returns workflow log detail only when the node grants log visibility
125
+ - A successful approve/reject/rollback/transfer may still lose the current-node runtime immediately; treat `record_state_readable=false + backend 46001` as a post-action runtime loss unless continuation verification says otherwise
126
+ - Treat `request_route` as the source of truth for live route debugging
127
+ - If only part of the requested work is completed, explicitly disclose which parts are done and which are not
128
+
129
+ ## Resources
130
+
131
+ - Workflow and task usage actions: [references/workflow-usage.md](./workflow-usage.md)
@@ -0,0 +1,43 @@
1
+ # Environment Switching
2
+
3
+ Use this reference before any workflow usage action, comment, or task-center operation that might affect live work.
4
+
5
+ ## Step 1: Resolve the active environment
6
+
7
+ Decide explicitly whether the task targets:
8
+
9
+ - `test`: demo, mock data, smoke usage validation, training scenarios
10
+ - `prod`: real operational tasks, comments, and workflow actions
11
+
12
+ If the user did not specify an environment, default to `prod`.
13
+
14
+ ## Test Environment
15
+
16
+ Use test for:
17
+
18
+ - workflow walkthroughs
19
+ - user acceptance demos
20
+ - comment or transfer rehearsals
21
+
22
+ ## Production Environment
23
+
24
+ Use production for:
25
+
26
+ - live task-center operations
27
+ - live comments on real business records
28
+ - approve / reject / rollback / transfer / urge on real work
29
+
30
+ Production guardrails:
31
+
32
+ - never assume a task id, record id, or workflow node id
33
+ - find the exact target first
34
+ - if the task can be answered read-only, do not act
35
+
36
+ ## Reporting Rule
37
+
38
+ For task ops, always report:
39
+
40
+ - active environment
41
+ - target app or task box
42
+ - operation type: read, comment, approve, reject, rollback, transfer, urge, or mark_read
43
+ - affected task ids or record ids
@@ -0,0 +1,26 @@
1
+ # Workflow and Task Usage Actions
2
+
3
+ Use these when the user is operating inside an existing process, not redesigning it.
4
+
5
+ Examples:
6
+
7
+ - add a comment to a record
8
+ - approve or reject a workflow task
9
+ - transfer a task
10
+ - roll back a task
11
+ - list todo, initiated, done, or cc tasks
12
+ - inspect workload by worksheet or workflow node
13
+ - urge a pending task
14
+
15
+ Rules:
16
+
17
+ - if the user starts from inbox, todo, workload, cc, or bottleneck language, use `task_*` first
18
+ - use `task_summary` for headline counts
19
+ - use `task_list` for flat browsing
20
+ - use `task_facets` when worksheet or workflow-node buckets matter
21
+ - treat task counts as task-center counts, not record counts
22
+ - switch to `record_get` only after locating the exact business record behind a task
23
+ - identify the exact target first
24
+ - for approve or reject, identify the exact `workflow_node_id` first; prefer task-center results or current audit info, then use `task_approve` or `task_reject`
25
+ - avoid usage-side workflow actions on ambiguous records
26
+ - summarize the final action and target task ids or record ids
@@ -0,0 +1,124 @@
1
+ #!/usr/bin/env python3
2
+ from __future__ import annotations
3
+
4
+ import argparse
5
+ import json
6
+ import sys
7
+ from pathlib import Path
8
+ from typing import Any
9
+
10
+
11
+ VALID_PORTAL_STATUSES = {"verified", "unverified", "not_created"}
12
+ VALID_APP_VERIFY_STATUSES = {"verified", "unverified", "failed"}
13
+
14
+
15
+ def main(argv: list[str] | None = None) -> int:
16
+ parser = argparse.ArgumentParser(description="Validate a Qingflow complete-system delivery summary JSON file.")
17
+ parser.add_argument("summary_file", nargs="?", default="tmp/qingflow_system_build_summary.json")
18
+ args = parser.parse_args(argv)
19
+ path = Path(args.summary_file)
20
+ issues = validate_summary_file(path)
21
+ if issues:
22
+ _emit({"status": "failed", "error_code": "SYSTEM_BUILD_SUMMARY_INVALID", "path": str(path), "issues": issues})
23
+ return 1
24
+ payload = json.loads(path.read_text(encoding="utf-8"))
25
+ _emit(
26
+ {
27
+ "status": "success",
28
+ "path": str(path),
29
+ "summary": {
30
+ "package_id": payload.get("package_id"),
31
+ "package_name": payload.get("package_name"),
32
+ "portal_dash_key": payload.get("portal_dash_key"),
33
+ "portal_live_status": payload.get("portal_live_status"),
34
+ "app_count": len(payload.get("apps") or []),
35
+ "partial_count": len(payload.get("partial_items") or []),
36
+ "needs_followup_count": len(payload.get("needs_followup") or []),
37
+ },
38
+ }
39
+ )
40
+ return 0
41
+
42
+
43
+ def validate_summary_file(path: Path) -> list[dict[str, Any]]:
44
+ if not path.exists():
45
+ return [_issue("MISSING_FILE", "$", f"summary file does not exist: {path}")]
46
+ try:
47
+ payload = json.loads(path.read_text(encoding="utf-8"))
48
+ except OSError as exc:
49
+ return [_issue("READ_FAILED", "$", str(exc))]
50
+ except json.JSONDecodeError as exc:
51
+ return [_issue("INVALID_JSON", "$", exc.msg)]
52
+ if not isinstance(payload, dict):
53
+ return [_issue("INVALID_ROOT", "$", "summary root must be a JSON object")]
54
+
55
+ issues: list[dict[str, Any]] = []
56
+ _require_positive_int(payload, "package_id", issues)
57
+ _require_nonempty_string(payload, "package_name", issues)
58
+ _require_bool(payload, "front_end_visible", issues)
59
+ _require_string_enum(payload, "portal_live_status", VALID_PORTAL_STATUSES, issues)
60
+ if payload.get("portal_live_status") == "verified":
61
+ _require_nonempty_string(payload, "portal_dash_key", issues)
62
+ elif "portal_dash_key" in payload and payload.get("portal_dash_key") is not None and not isinstance(payload.get("portal_dash_key"), str):
63
+ issues.append(_issue("INVALID_TYPE", "$.portal_dash_key", "portal_dash_key must be a string when present"))
64
+
65
+ apps = payload.get("apps")
66
+ if not isinstance(apps, list) or not apps:
67
+ issues.append(_issue("INVALID_APPS", "$.apps", "apps must be a non-empty array"))
68
+ else:
69
+ for index, app in enumerate(apps):
70
+ prefix = f"$.apps[{index}]"
71
+ if not isinstance(app, dict):
72
+ issues.append(_issue("INVALID_APP_ITEM", prefix, "app item must be an object"))
73
+ continue
74
+ _require_nonempty_string(app, "app_key", issues, prefix)
75
+ _require_nonempty_string(app, "app_name", issues, prefix)
76
+ for key in ("fields_count", "views_count", "flows_count", "charts_count"):
77
+ _require_nonnegative_int(app, key, issues, prefix)
78
+ _require_string_enum(app, "publish_verify_status", VALID_APP_VERIFY_STATUSES, issues, prefix)
79
+
80
+ for key in ("warnings", "partial_items", "needs_followup"):
81
+ if not isinstance(payload.get(key), list):
82
+ issues.append(_issue("INVALID_TYPE", f"$.{key}", f"{key} must be an array"))
83
+ return issues
84
+
85
+
86
+ def _require_positive_int(payload: dict[str, Any], key: str, issues: list[dict[str, Any]], prefix: str = "$") -> None:
87
+ value = payload.get(key)
88
+ if isinstance(value, bool) or not isinstance(value, int) or value <= 0:
89
+ issues.append(_issue("INVALID_TYPE", f"{prefix}.{key}", f"{key} must be a positive integer"))
90
+
91
+
92
+ def _require_nonnegative_int(payload: dict[str, Any], key: str, issues: list[dict[str, Any]], prefix: str = "$") -> None:
93
+ value = payload.get(key)
94
+ if isinstance(value, bool) or not isinstance(value, int) or value < 0:
95
+ issues.append(_issue("INVALID_TYPE", f"{prefix}.{key}", f"{key} must be a non-negative integer"))
96
+
97
+
98
+ def _require_nonempty_string(payload: dict[str, Any], key: str, issues: list[dict[str, Any]], prefix: str = "$") -> None:
99
+ value = payload.get(key)
100
+ if not isinstance(value, str) or not value.strip():
101
+ issues.append(_issue("INVALID_TYPE", f"{prefix}.{key}", f"{key} must be a non-empty string"))
102
+
103
+
104
+ def _require_bool(payload: dict[str, Any], key: str, issues: list[dict[str, Any]], prefix: str = "$") -> None:
105
+ if not isinstance(payload.get(key), bool):
106
+ issues.append(_issue("INVALID_TYPE", f"{prefix}.{key}", f"{key} must be a boolean"))
107
+
108
+
109
+ def _require_string_enum(payload: dict[str, Any], key: str, allowed: set[str], issues: list[dict[str, Any]], prefix: str = "$") -> None:
110
+ value = payload.get(key)
111
+ if value not in allowed:
112
+ issues.append(_issue("INVALID_VALUE", f"{prefix}.{key}", f"{key} must be one of {sorted(allowed)}"))
113
+
114
+
115
+ def _issue(code: str, path: str, message: str) -> dict[str, Any]:
116
+ return {"code": code, "path": path, "message": message}
117
+
118
+
119
+ def _emit(payload: dict[str, Any]) -> None:
120
+ print(json.dumps(payload, ensure_ascii=False, indent=2))
121
+
122
+
123
+ if __name__ == "__main__":
124
+ sys.exit(main())
@@ -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()