@qingflow-tech/qingflow-app-builder-mcp 1.0.43 → 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 (50) 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/SKILL.md +5 -1
  7. package/skills/qingflow-app-builder/references/complete-system-development-guide.md +65 -1
  8. package/skills/qingflow-app-builder/references/gotchas.md +3 -0
  9. package/skills/qingflow-app-builder/references/single-app-development-guide.md +11 -0
  10. package/skills/qingflow-app-builder/references/tool-selection.md +1 -1
  11. package/skills/qingflow-app-builder/references/update-flow.md +21 -9
  12. package/skills/qingflow-app-builder/references/update-views.md +27 -2
  13. package/skills/qingflow-app-builder/scripts/validate_system_build_summary.py +124 -0
  14. package/skills/qingflow-app-builder-code-integrations/SKILL.md +1 -1
  15. package/skills/qingflow-mcp-setup/SKILL.md +115 -0
  16. package/skills/qingflow-mcp-setup/agents/openai.yaml +4 -0
  17. package/skills/qingflow-mcp-setup/references/claude-desktop.md +34 -0
  18. package/skills/qingflow-mcp-setup/references/environments.md +62 -0
  19. package/skills/qingflow-mcp-setup/references/generic-stdio.md +32 -0
  20. package/skills/qingflow-mcp-setup/scripts/check_local_server.sh +38 -0
  21. package/skills/qingflow-workflow-builder/SKILL.md +98 -0
  22. package/skills/qingflow-workflow-builder/manifest.yaml +8 -0
  23. package/skills/qingflow-workflow-builder/references/01-overview.md +45 -0
  24. package/skills/qingflow-workflow-builder/references/02-update-mode.md +53 -0
  25. package/skills/qingflow-workflow-builder/references/03-flow-patterns.md +57 -0
  26. package/skills/qingflow-workflow-builder/references/04-stage1-business-modeling.md +131 -0
  27. package/skills/qingflow-workflow-builder/references/05-stage2-members-roles.md +29 -0
  28. package/skills/qingflow-workflow-builder/references/06-stage3-build-spec.md +165 -0
  29. package/skills/qingflow-workflow-builder/references/07-stage4-validate-spec.md +33 -0
  30. package/skills/qingflow-workflow-builder/references/08-stage5-apply-verify.md +51 -0
  31. package/skills/qingflow-workflow-builder/references/09-stage6-summary.md +88 -0
  32. package/skills/qingflow-workflow-builder/references/10-node-config-reference.md +93 -0
  33. package/skills/qingflow-workflow-builder/references/11-troubleshooting.md +15 -0
  34. package/skills/qingflow-workflow-builder/scripts/diff_flow_spec.py +275 -0
  35. package/skills/qingflow-workflow-builder/scripts/validate_flow_spec.py +605 -0
  36. package/src/qingflow_mcp/__init__.py +1 -1
  37. package/src/qingflow_mcp/builder_facade/models.py +0 -39
  38. package/src/qingflow_mcp/builder_facade/service.py +532 -859
  39. package/src/qingflow_mcp/builder_facade/workflow_spec.py +111 -0
  40. package/src/qingflow_mcp/cli/commands/builder.py +44 -12
  41. package/src/qingflow_mcp/cli/main.py +4 -6
  42. package/src/qingflow_mcp/public_surface.py +2 -0
  43. package/src/qingflow_mcp/server_app_builder.py +16 -8
  44. package/src/qingflow_mcp/solution/compiler/__init__.py +1 -3
  45. package/src/qingflow_mcp/solution/executor.py +3 -133
  46. package/src/qingflow_mcp/tools/ai_builder_tools.py +177 -241
  47. package/src/qingflow_mcp/tools/solution_tools.py +30 -2
  48. package/src/qingflow_mcp/tools/workflow_tools.py +3 -31
  49. package/src/qingflow_mcp/version.py +71 -1
  50. package/src/qingflow_mcp/solution/compiler/workflow_compiler.py +0 -173
package/README.md CHANGED
@@ -3,13 +3,13 @@
3
3
  Install:
4
4
 
5
5
  ```bash
6
- npm install @qingflow-tech/qingflow-app-builder-mcp@1.0.43
6
+ npm install @qingflow-tech/qingflow-app-builder-mcp@1.1.0
7
7
  ```
8
8
 
9
9
  Run:
10
10
 
11
11
  ```bash
12
- npx -y -p @qingflow-tech/qingflow-app-builder-mcp@1.0.43 qingflow-app-builder-mcp
12
+ npx -y -p @qingflow-tech/qingflow-app-builder-mcp@1.1.0 qingflow-app-builder-mcp
13
13
  ```
14
14
 
15
15
  Environment:
@@ -24,6 +24,8 @@ Bundled skills:
24
24
 
25
25
  - `skills/qingflow-app-builder`
26
26
  - `skills/qingflow-app-builder-code-integrations`
27
+ - `skills/qingflow-workflow-builder`
28
+ - `skills/qingflow-mcp-setup`
27
29
 
28
30
  Note:
29
31
 
@@ -1,7 +1,36 @@
1
1
  #!/usr/bin/env node
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { fileURLToPath, pathToFileURL } from "node:url";
2
5
 
3
- import { getPackageRoot, spawnServer } from "../lib/runtime.mjs";
6
+ const PKG_BY_BIN = {
7
+ qingflow: "qingflow-cli",
8
+ "qingflow-app-user-mcp": "qingflow-app-user-mcp",
9
+ "qingflow-app-builder-mcp": "qingflow-app-builder-mcp",
10
+ };
4
11
 
5
- const packageRoot = getPackageRoot(import.meta.url);
12
+ function resolvePackageModule(metaUrl, ...segments) {
13
+ const scriptPath = fileURLToPath(metaUrl);
14
+ const scriptDir = path.dirname(scriptPath);
15
+ const direct = path.join(scriptDir, ...segments);
16
+ if (fs.existsSync(direct)) {
17
+ return pathToFileURL(direct).href;
18
+ }
19
+ if (path.basename(scriptDir) === ".bin") {
20
+ const binName = path.basename(scriptPath);
21
+ const pkgName = PKG_BY_BIN[binName];
22
+ if (!pkgName) {
23
+ throw new Error(`Unknown qingflow command: ${binName}`);
24
+ }
25
+ const scope = process.env.QINGFLOW_NPM_SCOPE || "@qingflow-tech";
26
+ const fromBin = path.join(scriptDir, "..", scope, pkgName, "npm", ...segments);
27
+ if (fs.existsSync(fromBin)) {
28
+ return pathToFileURL(fromBin).href;
29
+ }
30
+ }
31
+ throw new Error(`Cannot locate ${segments.join("/")} from ${scriptPath}`);
32
+ }
6
33
 
34
+ const { getPackageRoot, spawnServer } = await import(resolvePackageModule(import.meta.url, "lib", "runtime.mjs"));
35
+ const packageRoot = getPackageRoot(import.meta.url);
7
36
  spawnServer(packageRoot, process.argv.slice(2), "qingflow-app-builder-mcp", { allowRuntimeBootstrap: false });
@@ -5,6 +5,12 @@ import { fileURLToPath } from "node:url";
5
5
 
6
6
  const WINDOWS = process.platform === "win32";
7
7
 
8
+ const PKG_BY_BIN = {
9
+ qingflow: "qingflow-cli",
10
+ "qingflow-app-user-mcp": "qingflow-app-user-mcp",
11
+ "qingflow-app-builder-mcp": "qingflow-app-builder-mcp",
12
+ };
13
+
8
14
  function runChecked(command, args, options = {}) {
9
15
  const result = spawnSync(command, args, {
10
16
  encoding: "utf8",
@@ -28,7 +34,18 @@ function commandWorks(command, args) {
28
34
  }
29
35
 
30
36
  export function getPackageRoot(metaUrl) {
31
- return path.resolve(path.dirname(fileURLToPath(metaUrl)), "..", "..");
37
+ const scriptPath = fileURLToPath(metaUrl);
38
+ const scriptDir = path.dirname(scriptPath);
39
+ if (path.basename(scriptDir) === ".bin") {
40
+ const binName = path.basename(scriptPath);
41
+ const pkgName = PKG_BY_BIN[binName];
42
+ if (!pkgName) {
43
+ throw new Error(`Unknown qingflow command: ${binName}`);
44
+ }
45
+ const scope = process.env.QINGFLOW_NPM_SCOPE || "@qingflow-tech";
46
+ return path.join(scriptDir, "..", scope, pkgName);
47
+ }
48
+ return path.resolve(scriptDir, "..", "..");
32
49
  }
33
50
 
34
51
  export function getCodexHome() {
@@ -480,6 +497,17 @@ function getVenvPip(packageRoot) {
480
497
  : path.join(getVenvDir(packageRoot), "bin", "pip");
481
498
  }
482
499
 
500
+ function findOfflineProjectWheel(findLinksDir) {
501
+ const wheels = fs
502
+ .readdirSync(findLinksDir)
503
+ .filter((name) => /^qingflow_mcp-.*\.whl$/i.test(name))
504
+ .sort();
505
+ if (wheels.length === 0) {
506
+ throw new Error(`Offline install expected a qingflow_mcp wheel in ${findLinksDir}`);
507
+ }
508
+ return path.join(findLinksDir, wheels[wheels.length - 1]);
509
+ }
510
+
483
511
  export function findPython() {
484
512
  const preferred = process.env.QINGFLOW_MCP_PYTHON?.trim();
485
513
  const candidates = preferred
@@ -528,7 +556,20 @@ export function ensurePythonEnv(packageRoot, { force = false, commandName = "qin
528
556
  }
529
557
 
530
558
  const pip = getVenvPip(packageRoot);
531
- runChecked(pip, ["install", "--disable-pip-version-check", "."], { cwd: packageRoot });
559
+ const pipArgs = ["install", "--disable-pip-version-check"];
560
+ const offlineFindLinks = process.env.QINGFLOW_MCP_PIP_FIND_LINKS?.trim();
561
+ if (process.env.QINGFLOW_MCP_PIP_NO_INDEX === "1") {
562
+ pipArgs.push("--no-index");
563
+ }
564
+ if (offlineFindLinks) {
565
+ pipArgs.push("--find-links", offlineFindLinks);
566
+ }
567
+ if (process.env.QINGFLOW_MCP_PIP_NO_INDEX === "1" && offlineFindLinks) {
568
+ pipArgs.push(findOfflineProjectWheel(offlineFindLinks));
569
+ } else {
570
+ pipArgs.push(".");
571
+ }
572
+ runChecked(pip, pipArgs, { cwd: packageRoot });
532
573
 
533
574
  fs.writeFileSync(
534
575
  stampPath,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qingflow-tech/qingflow-app-builder-mcp",
3
- "version": "1.0.43",
3
+ "version": "1.1.0",
4
4
  "description": "Builder MCP for Qingflow app/package/system design and staged solution workflows.",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/pyproject.toml CHANGED
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "qingflow-mcp"
7
- version = "1.0.43"
7
+ version = "1.1.0"
8
8
  description = "User-authenticated MCP server for Qingflow"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -7,7 +7,7 @@ metadata:
7
7
 
8
8
  # Qingflow App Builder
9
9
 
10
- > **Skill 版本**:`qingflow-skills-2026.06.24.1`(入口文档版本;如需确认 CLI 包版本,使用 `qingflow --version` 或 `qingflow --json version`)。
10
+ > **Skill 版本**:`qingflow-skills-2026.06.24.2`(入口文档版本;如需确认 CLI 包版本,使用 `qingflow --version` 或 `qingflow --json version`)。
11
11
 
12
12
  ## Overview
13
13
 
@@ -93,6 +93,7 @@ Treat these as the official surface. Do not default to `package_create`, `packag
93
93
  - it does not attach the report to the Qingflow app associated-resource display; use `app_associated_resources_apply` for that
94
94
  - supported `chart_type` values include legacy public aliases `target`, `table` and QingBI types such as `summary`, `columnar`, `area`, `stacked_area`, `funnel`, `waterfall`, `gauge`, `heatmap`, `histogram`, `treemap`, `radar`, `stacked_bar`, `stacked_column`, `scatter`, `ring`, `rose`, `dualaxes`, `map`, and `timeline`
95
95
  - chart `filters` use the unified fixed-filter DSL: `field_name + operator + value/values`; the tool compiles them to QingBI string judge types
96
+ - for complete systems, submit `upsert_charts` in batches of 4-8. More than 8 upserts is blocked before write with `CHART_UPSERT_BATCH_TOO_LARGE`; execute `details.suggested_batch_payloads[]` one batch at a time.
96
97
  - use `patch_charts` for changing a chart name, visibility, filters, or one config fragment on an existing chart
97
98
  - `visibility` is a public capability and should be treated as a base-only permission update
98
99
  - do not model chart visibility changes as raw config rewrites
@@ -121,11 +122,13 @@ Treat these as the official surface. Do not default to `package_create`, `packag
121
122
  - if field type compatibility is unclear, read [references/match-rules.md](references/match-rules.md)
122
123
  - Views and flows:
123
124
  - stay on `app_views_apply` / `app_flow_apply`
125
+ - before building approval/fill/copy flows, make sure the app schema has an explicit business status `select` field such as `状态` / `处理状态` / `审批状态`; do not create platform workflow system fields such as `当前流程状态`
124
126
  - use `patch_views` for existing-view parameter changes such as `query_conditions`, `filters`, `columns`, or visibility
125
127
  - builder view writes use raw `view_key` values from `app_get.views`; `custom:<viewKey>` is a record-data `view_id` form, not a builder `view_key`
126
128
  - do not create platform default/system views named `全部数据`, `我的数据`, `我发起的`, `待办`, `已办`, or `抄送`; they are built in. New views must use business-specific names. To change a built-in view, patch by its existing raw `view_key`
127
129
  - use `builder_tool_contract` whenever the minimal legal shape is unclear
128
130
  - keep view `filters` and `query_conditions` separate: `filters` use `field_name + operator + value/values` and are fixed saved filters; `query_conditions` configure the frontend query panel fields and only take effect after users enter query values
131
+ - keep relation/attachment/subtable/address/Q-Linker/code-block fields out of `query_conditions`; member/department fields may be used as query-panel fields when readback supports them. Use `filters` for fixed filters or associated-resource `match_mappings` for current-record related report/view matching
129
132
  - new views default the associated report/view display area to visible with `limit_type="all"`; existing views preserve their current associated-resource display unless `associated_resources` is explicitly patched
130
133
  - configure associated reports/views through `app_associated_resources_apply`, not through `app_views_apply`
131
134
 
@@ -161,6 +164,7 @@ Treat these as the official surface. Do not default to `package_create`, `packag
161
164
  - portal -> `portal_apply`
162
165
  - package metadata/layout -> `package_apply`
163
166
  8. Use `app_publish_verify` only when the user explicitly wants final publish/live verification or you need a dedicated verification pass
167
+ 9. For complete-system builds, write `tmp/qingflow_system_build_summary.json` before the final response and make the final report match that file
164
168
 
165
169
  ## Safe Usage Rules
166
170
 
@@ -11,6 +11,7 @@ Required:
11
11
  - core business objects are modeled as separate apps
12
12
  - all new apps are created in one multi-app `app_schema_apply(package_id=..., apps=[...])`
13
13
  - every app has stable `client_key`, `app_name`, `icon`, `color`, and one readable `as_data_title: true`
14
+ - no app creates platform system fields such as `数据ID`, `编号`, `申请人`, `申请时间`, `创建人`, `创建时间`, `提交人`, `提交时间`, `更新时间`, `更新人`, `当前流程状态`, `当前处理人`, `当前处理节点`, or `流程标题`
14
15
  - same-call relations use `target_app_ref` to point to another `apps[].client_key`
15
16
  - basic operating views exist for each core app
16
17
  - package/apps/fields/relations are read back before repair or retry
@@ -22,6 +23,12 @@ Strongly recommended:
22
23
  - a standard portal: business entry area, core metrics, BI charts, then concrete data views
23
24
  - portal grid/business-entry sections contain real `config.items[]`, not empty containers
24
25
 
26
+ Workflow preflight:
27
+
28
+ - If an app will have approval/fill/copy workflow nodes, create an explicit business status `select` field in schema first, such as `状态`, `处理状态`, `审批状态`, `工单状态`, or `流程阶段`.
29
+ - Do not create platform workflow system fields such as `当前流程状态`, `当前处理人`, `当前处理节点`, or `流程标题`.
30
+ - If `app_flow_apply` returns `FLOW_DEPENDENCY_MISSING`, fix schema with the returned `suggested_next_call`, read fields back, then retry the flow. Do not skip the workflow silently.
31
+
25
32
  Optional:
26
33
 
27
34
  - workflow, reminders, buttons, associated resources, sample data, roles, and permissions when the user asks for them or the business process clearly depends on them
@@ -33,9 +40,66 @@ Optional:
33
40
  3. Run one multi-app `app_schema_apply` with `apps[]`.
34
41
  4. If write result is uncertain, run readback before retrying.
35
42
  5. For each app, apply layout and views.
36
- 6. Create charts before portals when the portal needs metrics or BI sections.
43
+ 6. Create charts before portals when the portal needs metrics or BI sections. Submit chart upserts in batches of 4-8; if `CHART_UPSERT_BATCH_TOO_LARGE` appears, execute `details.suggested_batch_payloads[]` one at a time.
37
44
  7. Create portal only after referenced apps, views, and charts are known.
38
45
  8. Add workflows/buttons/associated resources when they are part of the requested business process.
46
+ 9. Before the final response, write the complete-system delivery summary file described below.
47
+
48
+ ## Field Modeling Rules
49
+
50
+ - Use agent-friendly field types in drafts: `text`, `multiline`, `select`, `multi_select`, `number`, `amount`, `date`, `datetime`, `member`, `department`, `attachment`, `relation`. The tool normalizes aliases such as `multiline -> long_text` and `select -> single_select`.
51
+ - Create only business fields. Do not create platform system fields listed in Required; reference them only where a tool explicitly supports system fields, such as button `source_field: "数据ID"`.
52
+ - Use `number` for ratios, completion rates, scores, percentages, durations, and quantities that may need decimals. Use `amount` only for currency/money semantics. This prevents sample data from producing decimal values for a field that the backend treats as integer money.
53
+ - For select fields, define the option set during schema design. Later sample data must use only those option labels or ids.
54
+
55
+ ## View Query Rules
56
+
57
+ - Use `filters` for fixed saved filters that apply when the view opens.
58
+ - Use `query_conditions` only for the frontend query panel layout. It is not another filter DSL and it does not express OR.
59
+ - `query_conditions.rows` should contain query-panel fields such as text, long text, number, amount, date/datetime, select, member, department, phone, email, or boolean.
60
+ - Do not put relation, attachment, subtable/subfield, address, Q-Linker, or code-block fields in `query_conditions`.
61
+ - For current-record related views/reports, use `app_associated_resources_apply.match_mappings`, not `query_conditions`.
62
+
63
+ ## Delivery Summary File
64
+
65
+ For complete-system builds, create or update one local JSON file before the final response:
66
+
67
+ `tmp/qingflow_system_build_summary.json`
68
+
69
+ Use this file as the structured source of truth for the final report. Include at least:
70
+
71
+ ```json
72
+ {
73
+ "package_id": 0,
74
+ "package_name": "",
75
+ "portal_dash_key": "",
76
+ "portal_live_status": "verified | unverified | not_created",
77
+ "front_end_visible": true,
78
+ "apps": [
79
+ {
80
+ "app_key": "",
81
+ "app_name": "",
82
+ "fields_count": 0,
83
+ "views_count": 0,
84
+ "flows_count": 0,
85
+ "charts_count": 0,
86
+ "publish_verify_status": "verified | unverified | failed"
87
+ }
88
+ ],
89
+ "warnings": [],
90
+ "partial_items": [],
91
+ "needs_followup": []
92
+ }
93
+ ```
94
+
95
+ Rules:
96
+
97
+ - Populate IDs and counts from readback, not from intended payload only.
98
+ - If a required resource is not verified, put it in `partial_items` or `needs_followup` instead of reporting the system as fully complete.
99
+ - If the user later adds a data-entry task, keep that separate from the system-build summary unless the user explicitly wants one combined report.
100
+ - The final response should match this file: completed resources, unverified items, frontend visibility, and follow-up needs must not contradict the JSON.
101
+ - When running from the CLI repo or a test harness, validate the file with:
102
+ `python scripts/validate_system_build_summary.py tmp/qingflow_system_build_summary.json`.
39
103
 
40
104
  ## Recovery Rules
41
105
 
@@ -97,6 +97,9 @@
97
97
  - Do not bypass duplicate/conflict states by inventing `V2`, `测试`, timestamp, or random-suffix app names in a real business package
98
98
  - For backend rejects, keep the retry narrow: retry only the failed tool, not the whole chain
99
99
  - For `VALIDATION_ERROR`, do not keep guessing. Reuse `suggested_next_call`, `canonical_arguments`, `allowed_keys`, and `allowed_values` first.
100
+ - For `builder_tool_contract`, read the returned `json_paths` first. Successful contract data is under `$.contract`; `allowed_values` uses flat dotted keys such as `["field.type"]`, not nested objects. Do not read successful field enums from top-level `allowed_values`.
101
+ - For `workspace_icon_catalog_get`, read icon candidates from `$.icon_names` and colors from `$.icon_colors`.
102
+ - For builder apply responses, read returned `json_paths` first. Stable fields are `$.summary` and `$.resources[]`; use `resources[].id/key/name` before legacy top-level fields.
100
103
  - For layout `VALIDATION_ERROR`, also inspect `section_allowed_keys`, `section_aliases`, and `minimal_section_example`. If the same layout-shape error repeats twice, stop free-form retries and re-read `builder_tool_contract(app_layout_apply)`.
101
104
  - For flow work, do not replay internal keys from old logs or plan outputs. Public builder calls should stay on:
102
105
  - `assignees.role_ids` / `assignees.member_uids` / `assignees.member_emails`
@@ -24,6 +24,11 @@ Optional:
24
24
 
25
25
  - workflow, portal entry, sample data, roles, or visibility changes when requested by the user or clearly needed by the app's job
26
26
 
27
+ Workflow preflight:
28
+
29
+ - If the app will have approval/fill/copy workflow nodes, add one explicit business status `select` field first, such as `状态`, `处理状态`, `审批状态`, `工单状态`, or `流程阶段`.
30
+ - Do not create platform workflow system fields: `当前流程状态`, `当前处理人`, `当前处理节点`, or `流程标题`.
31
+
27
32
  ## Standard Path
28
33
 
29
34
  1. Read current target: `app_resolve` or `app_get`.
@@ -40,6 +45,12 @@ Optional:
40
45
  - Do not create `数据ID`, `编号`, `申请人`, `申请时间`, `创建人`, `创建时间`, `提交人`, `提交时间`, `更新时间`, `更新人`, `当前流程状态`, `当前处理人`, `当前处理节点`, or `流程标题`.
41
46
  - Do not create built-in default views such as `全部数据` or `我的数据`; Qingflow provides system views.
42
47
 
48
+ ## View Query Rules
49
+
50
+ - `filters` are fixed saved filters; `query_conditions` only configures frontend query-panel fields.
51
+ - Put only query-panel fields in `query_conditions.rows`: text, long text, number, amount, date/datetime, select, member, department, phone, email, or boolean.
52
+ - Do not put relation, attachment, subtable/subfield, address, Q-Linker, or code-block fields in `query_conditions`; use fixed `filters` or associated-resource `match_mappings` instead.
53
+
43
54
  ## Stop Conditions
44
55
 
45
56
  - If the task actually needs multiple business objects, stop and switch to the complete-system guide.
@@ -55,7 +55,7 @@ These execute normalized patches. Some app apply tools publish by default and st
55
55
  - `app_views_apply`: use `patch_views` for existing-view parameter replacement; use `upsert_views` for creation or full target config; remove views by key; new views default associated report/view display to visible with `limit_type="all"`
56
56
  - `app_custom_buttons_apply`: use `patch_buttons` for existing-button parameter replacement; use `upsert_buttons` for creation or full target config; configure add-data field mappings/default values; bind buttons to header/detail/list view positions; `placement=list` maps to the backend `INSIDE` row/list button position; merge-mode view configs require `buttons`; use `view_configs[].mode="replace"` or `buttons=[]` to clear a view's custom button bindings. For child-record creation linked to the current source record, map `source_field: "数据ID"` to the target relation field.
57
57
  - `app_associated_resources_apply`: attach existing BI reports/views to the Qingflow app associated-resource pool and per-view display area; it does not create or edit QingBI report bodies/configs. Permission is split like the backend: resource pool writes use EditAppAuth, while `view_configs` uses the view config/DataManageAuth path. Use `patch_resources` for existing associated-resource parameter replacement; use `upsert_resources` for creation or full target config; `view_configs`, remove, and reorder may reference existing resources by internal `associated_item_id` or by `chart_id`/`chart_key`/`view_key`; use `match_mappings` with `target_field + operator + source_field/value` for associated view/report filters; publishes after successful writes; omit raw `sourceType`, and use `report_source="dataset"` only to attach an existing BI dataset report. Before `upsert_resources`, read `app_get.associated_resources` and reuse an existing matching `target_app_key + view_key/chart_key`; repeated upsert can create duplicates because `client_key` is only valid inside one apply call.
58
- - `app_charts_apply`: create/edit/remove/reorder app-source QingBI report bodies/configs with `dataSourceType=qingflow`; it does not create/edit dataset BI reports and does not attach reports to Qingflow app associated-resource display. Use `filters` with `field_name + operator + value/values`; the tool compiles them to QingBI string judge types. Use `patch_charts` for existing-chart parameter replacement; use `upsert_charts` for creation or full target config; supports `target/table` aliases plus QingBI chart types such as `summary`, `columnar`, `area`, `funnel`, `radar`, `scatter`, `dualaxes`, and `map`; charts are immediate-live and do not publish; use `chart_id` when names are not unique
58
+ - `app_charts_apply`: create/edit/remove/reorder app-source QingBI report bodies/configs with `dataSourceType=qingflow`; it does not create/edit dataset BI reports and does not attach reports to Qingflow app associated-resource display. Use `filters` with `field_name + operator + value/values`; the tool compiles them to QingBI string judge types. Use `patch_charts` for existing-chart parameter replacement; use `upsert_charts` for creation or full target config; supports `target/table` aliases plus QingBI chart types such as `summary`, `columnar`, `area`, `funnel`, `radar`, `scatter`, `dualaxes`, and `map`; charts are immediate-live and do not publish; use `chart_id` when names are not unique; for complete systems, submit large chart sets in batches of 4-8 upserts. More than 8 upserts is blocked before write with `CHART_UPSERT_BATCH_TOO_LARGE`; execute `details.suggested_batch_payloads[]` one batch at a time.
59
59
  - `portal_apply`: create or replace-update portal pages; use `dash_key` for update mode or `package_id + dash_name` for create mode; create mode only prechecks package add_app, matching backend portal creation; edit mode may omit `sections` for base-info-only updates; when sections are supplied they still use replace semantics
60
60
 
61
61
  For object-level updates, the safe partial syntax is `patch_*` with the object's real selector field plus `set` and optional `unset`. `selector` is only a concept, not a literal key. Examples: `patch_views: [{"view_key": "VIEW_KEY", "set": {...}}]`, `patch_buttons: [{"button_id": 1001, "set": {...}}]`, `patch_resources: [{"associated_item_id": 123, "set": {...}}]`, `patch_charts: [{"chart_id": 456, "set": {...}}]`. The tool reads the current backend config, merges the patch, then submits the full backend payload internally. Do not send a partial `upsert_*` and expect missing required fields to be preserved.
@@ -6,18 +6,19 @@ Use this when the app already exists and the task is only about workflow.
6
6
 
7
7
  1. `builder_tool_contract(tool_name="app_flow_apply")`
8
8
  2. `app_get_fields`
9
- 3. `app_get_flow`
10
- 4. `role_search` or `member_search`
11
- 5. `role_create` if the user wants a reusable directory role and no good role exists
12
- 6. start from a canonical preset when possible
13
- 7. patch the skeleton instead of freehanding a full graph
14
- 8. reuse preset node ids when patching:
9
+ 3. Confirm the app has one explicit business status field, usually a `select` named `状态`, `处理状态`, `审批状态`, `工单状态`, or `流程阶段`
10
+ 4. `app_get_flow`
11
+ 5. `role_search` or `member_search`
12
+ 6. `role_create` if the user wants a reusable directory role and no good role exists
13
+ 7. start from a canonical preset when possible
14
+ 8. patch the skeleton instead of freehanding a full graph
15
+ 9. reuse preset node ids when patching:
15
16
  - `basic_approval` -> patch `approve_1`
16
17
  - `basic_fill_then_approve` -> patch `fill_1` and `approve_1`
17
18
  Do not add a second approval/fill node with a new id unless you are intentionally replacing the skeleton.
18
19
  The MCP now auto-aligns the simplest single-node preset overrides, but still prefer explicit preset ids so the merged graph stays predictable.
19
- 9. `app_flow_apply`
20
- 10. `app_get_flow` when apply returns `partial_success` or the user asked for verification
20
+ 10. `app_flow_apply`
21
+ 11. `app_get_flow` when apply returns `partial_success` or the user asked for verification
21
22
 
22
23
  If you are unsure about presets or node shapes, call `builder_tool_contract(tool_name="app_flow_apply")` before guessing.
23
24
 
@@ -96,7 +97,18 @@ Preferred fix order:
96
97
 
97
98
  ### `FLOW_DEPENDENCY_MISSING`
98
99
 
99
- The workflow depends on fields that do not exist yet, usually `status`. Fix schema first.
100
+ The workflow depends on fields that do not exist yet, usually an explicit business status field. Fix schema first.
101
+
102
+ Do:
103
+
104
+ - add a business field such as `状态`, `处理状态`, `审批状态`, `工单状态`, or `流程阶段`
105
+ - use type `select` / `single_select`
106
+ - use business options such as `草稿`, `进行中`, `已完成`
107
+
108
+ Do not:
109
+
110
+ - create platform workflow system fields such as `当前流程状态`, `当前处理人`, `当前处理节点`, or `流程标题`
111
+ - skip the flow and still report workflow completion
100
112
 
101
113
  Preferred recovery:
102
114
 
@@ -272,7 +272,32 @@ At least one `query_conditions.rows` field does not exist on the app.
272
272
 
273
273
  ### `INVALID_QUERY_CONDITION_FIELD`
274
274
 
275
- The field exists but cannot be used in the frontend query-condition panel, such as attachment, relation, Q-Linker, code block, location/address, or subtable/subfield selections.
275
+ The field exists but cannot be used in the frontend query-condition panel.
276
+
277
+ Supported `query_conditions.rows` fields are query-panel fields:
278
+
279
+ - text / long text
280
+ - number / amount
281
+ - date / datetime
282
+ - single select / multi select
283
+ - member / department
284
+ - phone / email
285
+ - boolean
286
+
287
+ Do not use these fields in `query_conditions`:
288
+
289
+ - relation
290
+ - attachment
291
+ - subtable or subtable subfield
292
+ - address/location
293
+ - Q-Linker
294
+ - code block
295
+
296
+ Fix path:
297
+
298
+ - If you wanted a saved filter that opens with the view, use `filters`.
299
+ - If you wanted a related report/view to match the current record, use `app_associated_resources_apply.match_mappings`.
300
+ - If you only need frontend search fields, remove unsupported fields from `query_conditions.rows` and keep query-panel supported fields only.
276
301
 
277
302
  ## Notes
278
303
 
@@ -281,6 +306,6 @@ The field exists but cannot be used in the frontend query-condition panel, such
281
306
  - `app_get_views` should be treated as canonical readback and now returns `columns`
282
307
  - If `app_views_apply` returns `AMBIGUOUS_VIEW`, stop and re-run `app_get_views`; then retry with the exact `view_key`
283
308
  - `filters` are ANDed together as one flat condition group
284
- - `query_conditions.rows` does not mean OR; it controls frontend query-field layout
309
+ - `query_conditions.rows` does not mean OR; it controls frontend query-field layout and should only contain query-panel supported fields
285
310
  - `app_views_apply` publishes by default
286
311
  - For select-style filters, success means the backend preserved the option value in readback, not just that the view name now exists
@@ -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())
@@ -7,7 +7,7 @@ metadata:
7
7
 
8
8
  # Qingflow App Builder Code Integrations
9
9
 
10
- > **Skill 版本**:`qingflow-skills-2026.06.24.1`(入口文档版本;如需确认 CLI 包版本,使用 `qingflow --version` 或 `qingflow --json version`)。
10
+ > **Skill 版本**:`qingflow-skills-2026.06.24.2`(入口文档版本;如需确认 CLI 包版本,使用 `qingflow --version` 或 `qingflow --json version`)。
11
11
 
12
12
  Use this skill when the user wants to build or repair:
13
13
  - `code_block` fields