@josephyan/qingflow-app-builder-mcp 1.0.9 → 1.1.1

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 (63) 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 +2 -1
  6. package/skills/qingflow-app-builder/SKILL.md +135 -14
  7. package/skills/qingflow-app-builder/references/build-complete-system.md +428 -0
  8. package/skills/qingflow-app-builder/references/build-single-app.md +530 -0
  9. package/skills/qingflow-app-builder/references/create-app.md +43 -3
  10. package/skills/qingflow-app-builder/references/gotchas.md +47 -3
  11. package/skills/qingflow-app-builder/references/match-rules.md +114 -0
  12. package/skills/qingflow-app-builder/references/public-surface-sync.md +75 -0
  13. package/skills/qingflow-app-builder/references/tool-selection.md +25 -10
  14. package/skills/qingflow-app-builder/references/update-flow.md +29 -1
  15. package/skills/qingflow-app-builder/references/update-schema.md +6 -2
  16. package/skills/qingflow-app-builder/references/update-views.md +135 -6
  17. package/skills/qingflow-mcp-setup/SKILL.md +111 -0
  18. package/skills/qingflow-mcp-setup/agents/openai.yaml +4 -0
  19. package/skills/qingflow-mcp-setup/references/claude-desktop.md +34 -0
  20. package/skills/qingflow-mcp-setup/references/environments.md +61 -0
  21. package/skills/qingflow-mcp-setup/references/generic-stdio.md +31 -0
  22. package/skills/qingflow-mcp-setup/scripts/check_local_server.sh +38 -0
  23. package/skills/qingflow-workflow-builder/SKILL.md +96 -0
  24. package/skills/qingflow-workflow-builder/manifest.yaml +8 -0
  25. package/skills/qingflow-workflow-builder/references/01-overview.md +45 -0
  26. package/skills/qingflow-workflow-builder/references/02-update-mode.md +53 -0
  27. package/skills/qingflow-workflow-builder/references/03-flow-patterns.md +57 -0
  28. package/skills/qingflow-workflow-builder/references/04-stage1-business-modeling.md +131 -0
  29. package/skills/qingflow-workflow-builder/references/05-stage2-members-roles.md +29 -0
  30. package/skills/qingflow-workflow-builder/references/06-stage3-build-spec.md +165 -0
  31. package/skills/qingflow-workflow-builder/references/07-stage4-validate-spec.md +33 -0
  32. package/skills/qingflow-workflow-builder/references/08-stage5-apply-verify.md +51 -0
  33. package/skills/qingflow-workflow-builder/references/09-stage6-summary.md +88 -0
  34. package/skills/qingflow-workflow-builder/references/10-node-config-reference.md +93 -0
  35. package/skills/qingflow-workflow-builder/references/11-troubleshooting.md +15 -0
  36. package/skills/qingflow-workflow-builder/scripts/diff_flow_spec.py +275 -0
  37. package/skills/qingflow-workflow-builder/scripts/validate_flow_spec.py +605 -0
  38. package/src/qingflow_mcp/__init__.py +1 -1
  39. package/src/qingflow_mcp/backend_client.py +55 -1
  40. package/src/qingflow_mcp/builder_facade/models.py +532 -48
  41. package/src/qingflow_mcp/builder_facade/service.py +9194 -2384
  42. package/src/qingflow_mcp/builder_facade/workflow_spec.py +111 -0
  43. package/src/qingflow_mcp/cli/commands/app.py +3 -16
  44. package/src/qingflow_mcp/cli/commands/builder.py +354 -56
  45. package/src/qingflow_mcp/cli/commands/record.py +112 -6
  46. package/src/qingflow_mcp/cli/formatters.py +102 -2
  47. package/src/qingflow_mcp/cli/main.py +204 -3
  48. package/src/qingflow_mcp/public_surface.py +12 -9
  49. package/src/qingflow_mcp/response_trim.py +287 -22
  50. package/src/qingflow_mcp/server.py +29 -20
  51. package/src/qingflow_mcp/server_app_builder.py +108 -30
  52. package/src/qingflow_mcp/server_app_user.py +28 -23
  53. package/src/qingflow_mcp/solution/compiler/__init__.py +1 -3
  54. package/src/qingflow_mcp/solution/compiler/icon_utils.py +294 -0
  55. package/src/qingflow_mcp/solution/executor.py +3 -133
  56. package/src/qingflow_mcp/tools/ai_builder_tools.py +2617 -440
  57. package/src/qingflow_mcp/tools/app_tools.py +53 -8
  58. package/src/qingflow_mcp/tools/package_tools.py +16 -2
  59. package/src/qingflow_mcp/tools/record_tools.py +14283 -9162
  60. package/src/qingflow_mcp/tools/resource_read_tools.py +3 -0
  61. package/src/qingflow_mcp/tools/solution_tools.py +30 -2
  62. package/src/qingflow_mcp/tools/workflow_tools.py +3 -31
  63. 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 @josephyan/qingflow-app-builder-mcp@1.0.9
6
+ npm install @josephyan/qingflow-app-builder-mcp@1.1.1
7
7
  ```
8
8
 
9
9
  Run:
10
10
 
11
11
  ```bash
12
- npx -y -p @josephyan/qingflow-app-builder-mcp@1.0.9 qingflow-app-builder-mcp
12
+ npx -y -p @josephyan/qingflow-app-builder-mcp@1.1.1 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 || "@josephyan";
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 || "@josephyan";
46
+ return path.join(scriptDir, "..", scope, pkgName);
47
+ }
48
+ return path.resolve(scriptDir, "..", "..");
32
49
  }
33
50
 
34
51
  export function getCodexHome() {
@@ -189,6 +206,17 @@ function getVenvPip(packageRoot) {
189
206
  : path.join(getVenvDir(packageRoot), "bin", "pip");
190
207
  }
191
208
 
209
+ function findOfflineProjectWheel(findLinksDir) {
210
+ const wheels = fs
211
+ .readdirSync(findLinksDir)
212
+ .filter((name) => /^qingflow_mcp-.*\.whl$/i.test(name))
213
+ .sort();
214
+ if (wheels.length === 0) {
215
+ throw new Error(`Offline install expected a qingflow_mcp wheel in ${findLinksDir}`);
216
+ }
217
+ return path.join(findLinksDir, wheels[wheels.length - 1]);
218
+ }
219
+
192
220
  export function findPython() {
193
221
  const preferred = process.env.QINGFLOW_MCP_PYTHON?.trim();
194
222
  const candidates = preferred
@@ -237,7 +265,20 @@ export function ensurePythonEnv(packageRoot, { force = false, commandName = "qin
237
265
  }
238
266
 
239
267
  const pip = getVenvPip(packageRoot);
240
- runChecked(pip, ["install", "--disable-pip-version-check", "."], { cwd: packageRoot });
268
+ const pipArgs = ["install", "--disable-pip-version-check"];
269
+ const offlineFindLinks = process.env.QINGFLOW_MCP_PIP_FIND_LINKS?.trim();
270
+ if (process.env.QINGFLOW_MCP_PIP_NO_INDEX === "1") {
271
+ pipArgs.push("--no-index");
272
+ }
273
+ if (offlineFindLinks) {
274
+ pipArgs.push("--find-links", offlineFindLinks);
275
+ }
276
+ if (process.env.QINGFLOW_MCP_PIP_NO_INDEX === "1" && offlineFindLinks) {
277
+ pipArgs.push(findOfflineProjectWheel(offlineFindLinks));
278
+ } else {
279
+ pipArgs.push(".");
280
+ }
281
+ runChecked(pip, pipArgs, { cwd: packageRoot });
241
282
 
242
283
  fs.writeFileSync(
243
284
  stampPath,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@josephyan/qingflow-app-builder-mcp",
3
- "version": "1.0.9",
3
+ "version": "1.1.1",
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.9"
7
+ version = "1.1.1"
8
8
  description = "User-authenticated MCP server for Qingflow"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -29,6 +29,7 @@ dependencies = [
29
29
  "openpyxl>=3.1,<4.0",
30
30
  "pydantic>=2.8,<3.0",
31
31
  "pycryptodome>=3.20,<4.0",
32
+ "pypdf>=5.0,<6.0",
32
33
  "python-socketio[client]>=5.11,<6.0",
33
34
  ]
34
35
 
@@ -14,7 +14,7 @@ Assumes MCP is already connected, authenticated, and on the correct workspace.
14
14
 
15
15
  Before any write or verification flow, identify whether the task targets `test` or `prod` and read [references/environments.md](references/environments.md). If the user did not specify one, default to `prod`.
16
16
 
17
- Before choosing a route, skim the shared maintenance baseline: [public-surface-sync.md](../qingflow-app-user/references/public-surface-sync.md).
17
+ Before choosing a route, skim the shared maintenance baseline: [public-surface-sync.md](references/public-surface-sync.md).
18
18
 
19
19
  ## Current Public Mental Model
20
20
 
@@ -35,10 +35,10 @@ Default modeling rules:
35
35
  ## Public Tools You Should Think In
36
36
 
37
37
  - Package: `package_get`, `package_apply`
38
- - App reads: `app_resolve`, `app_get`, `app_get_fields`, `app_get_layout`, `app_get_views`, `app_get_flow`, `app_get_charts`
38
+ - App reads: `app_resolve`, `app_get`, `app_get_fields`, `app_get_layout`, `app_get_views`, `app_get_flow`, `app_get_charts`, `app_get_buttons`, `app_get_associated_resources`
39
39
  - Builder reads: `portal_list`, `portal_get`, `view_get`, `chart_get`, `builder_tool_contract`
40
40
  - Directory: `member_search`, `role_search`, `role_create`
41
- - Writes: `app_schema_apply`, `app_layout_apply`, `app_flow_apply`, `app_views_apply`, `app_charts_apply`, `portal_apply`, `app_release_edit_lock_if_mine`
41
+ - Writes: `app_schema_apply`, `app_layout_apply`, `app_flow_apply`, `app_views_apply`, `app_custom_buttons_apply`, `app_associated_resources_apply`, `app_charts_apply`, `portal_apply`, `app_release_edit_lock_if_mine`
42
42
  - Verification: `app_publish_verify`
43
43
  - Cross-cutting escalation: `feedback_submit` after explicit user confirmation when the public builder surface still cannot satisfy the user's need
44
44
 
@@ -49,20 +49,61 @@ Treat these as the official surface. Do not default to `package_create`, `packag
49
49
  - Package work:
50
50
  - use `package_get(package_id=...)` to read one known package
51
51
  - use `package_apply(...)` for package creation, rename, icon, visibility, grouping, ordering, and app/portal layout
52
+ - Multi-app schema work:
53
+ - use one `app_schema_apply(apps=[...])` / CLI `builder schema apply --apps-file` when creating several apps in one package
54
+ - same-call relation fields may use `target_app_ref` to point at another `apps[].client_key`
52
55
  - App base permissions:
53
56
  - trust `app_get.editability.can_edit_app_base` for app base-info writes like app name, icon, and visibility
54
57
  - `can_edit_form` only means schema/form-route capability; it no longer implies app base-info write capability
58
+ - Partial update rule:
59
+ - for existing views, custom buttons, associated resources, and charts, prefer `patch_views` / `patch_buttons` / `patch_resources` / `patch_charts`
60
+ - each patch item uses the object's real selector field (`view_key` / `button_id` / `associated_item_id` / `chart_id`, or unique name where supported) plus `set` and optional `unset`; do not send a literal `selector` key for these tools
61
+ - use `upsert_*` for creation or full target configuration; do not send a partial `upsert_*` and expect the backend to merge missing required fields
55
62
  - Portal work:
56
63
  - `portal_apply` is the public write path
57
64
  - in edit mode, omitting `sections` means “preserve existing layout and update base info only”
58
65
  - supplying `sections` means full replace semantics for sections
66
+ - portal sections use a 24-column PC grid; all components sharing the same `y` coordinate must have identical `rows` values and their `cols` must sum to exactly 24; mixing `rows` in the same row causes height misalignment, and a `cols` sum under 24 leaves blank space at the row end; the next row's `y` must equal the previous row's `y` + `rows`
59
67
  - Chart work:
60
- - `app_charts_apply` is the public write path
68
+ - `app_charts_apply` is the public write path for app-source QingBI report bodies/configs; it creates/updates reports with `dataSourceType=qingflow`
69
+ - dataset BI reports are not created or edited by `app_charts_apply` yet; create them in QingBI first, then attach the existing report with `app_associated_resources_apply` and `report_source="dataset"`
70
+ - it does not attach the report to the Qingflow app associated-resource display; use `app_associated_resources_apply` for that
71
+ - 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`
72
+ - use `patch_charts` for changing a chart name, visibility, filters, or one config fragment on an existing chart
61
73
  - `visibility` is a public capability and should be treated as a base-only permission update
62
74
  - do not model chart visibility changes as raw config rewrites
75
+ - Button work:
76
+ - `app_custom_buttons_apply` is the public write path for custom button bodies and view placement
77
+ - use `patch_buttons` for changing a single existing button parameter such as URL, text, icon, style, or add-data mapping
78
+ - for add-data buttons, use `trigger_add_data_config.target_app_key + field_mappings/default_values`; do not handwrite raw `que_relation`
79
+ - use `field_mappings` for dynamic current-record values, including system fields such as `数据ID` (`field_id=-17`) and `编号` (`field_id=0`)
80
+ - to prefill a target relation field with the current source record, map `{"source_field": "数据ID", "target_field": "目标引用字段"}`; use `default_values` only for static constants
81
+ - if field type compatibility is unclear, read [references/match-rules.md](references/match-rules.md)
82
+ - bind buttons to views through `view_configs[].buttons[]`; default to `placement`: `header` or `detail`
83
+ - builder `view_configs[].view_key` uses the raw key from `app_get.views[].view_key`, without a `custom:` prefix
84
+ - `buttons` is required in merge mode; do not send a view config with only `view_key`
85
+ - `view_configs[].mode` defaults to `merge`; use `mode: "replace"` or an explicit empty `buttons: []` to clear a view's custom button bindings
86
+ - `placement=list` configures row/list buttons and maps to the backend `INSIDE` button position
87
+ - advanced bindings may use `button_limit`, `button_formula`, `button_formula_type`, and `print_tpls` only when visibility or print-template behavior is required
88
+ - Associated resources:
89
+ - `app_associated_resources_apply` is the public write path for the Qingflow app-level associated report/view pool and per-view display config
90
+ - it attaches existing BI reports/views for in-app display; it does not create or edit QingBI report bodies/configs, including dataset reports
91
+ - use `patch_resources` for changing match rules or other existing associated-resource parameters; the tool preserves backend-required raw fields internally
92
+ - `associated_item_id` is the backend internal associated-resource id; for `view_configs`, `remove_associated_item_ids`, and `reorder_associated_item_ids`, you may pass `associated_item_id` or an existing resource's `chart_id`/`chart_key`/`view_key`, and the tool resolves it to the internal id
93
+ - before creating a resource, check `app_get.associated_resources` for the same `target_app_key + view_key/chart_key`; if it already exists, use `patch_resources` with that `associated_item_id`
94
+ - `client_key` is only a same-call reference for `view_configs[].associated_item_refs`; it is not saved and cannot deduplicate later calls
95
+ - do not pass backend raw `sourceType`; view resources infer the internal Qingflow view source, report/chart resources default to BI app reports, and existing dataset reports use `report_source="dataset"`
96
+ - use `match_mappings` for associated view/report filters; dynamic conditions use `source_field`, static conditions use `value`
97
+ - if field type compatibility is unclear, read [references/match-rules.md](references/match-rules.md)
63
98
  - Views and flows:
64
99
  - stay on `app_views_apply` / `app_flow_apply`
100
+ - use `patch_views` for existing-view parameter changes such as `query_conditions`, `filters`, `columns`, or visibility
101
+ - 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`
65
102
  - use `builder_tool_contract` whenever the minimal legal shape is unclear
103
+ - keep view `filters` and `query_conditions` separate: `filters` are fixed saved filters; `query_conditions` configure the frontend query panel fields and only take effect after users enter query values
104
+ - when creating any new table or card view, always include `query_conditions` with `enabled: true`; populate `rows` with the fields most useful for searching — typically the data-title field, any status/type select fields, date fields, and member fields; do not omit `query_conditions` or leave `rows` empty on a new view
105
+ - 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
106
+ - configure associated reports/views through `app_associated_resources_apply`, not through `app_views_apply`
66
107
 
67
108
  ## Standard Operating Order
68
109
 
@@ -74,20 +115,22 @@ Treat these as the official surface. Do not default to `package_create`, `packag
74
115
  - package-scoped work with known id -> `package_get`
75
116
  - portal inventory -> `portal_list`
76
117
  5. Read only the smallest config slice needed:
77
- - app summary -> `app_get`
118
+ - app map -> `app_get` (default entry; includes compact views, charts, custom buttons, and associated resource pool)
78
119
  - fields -> `app_get_fields`
79
120
  - layout -> `app_get_layout`
80
- - views -> `app_get_views`
121
+ - views -> `app_get_views` only when the app_get compact list is not enough
81
122
  - flow -> `app_get_flow`
82
- - charts -> `app_get_charts`
123
+ - charts -> `app_get_charts` only when the app_get compact list is not enough
83
124
  - portal -> `portal_get`
84
125
  6. If the public shape is unclear, call `builder_tool_contract`
85
126
  7. Apply the smallest patch tool that fits:
86
127
  - fields -> `app_schema_apply`
87
128
  - layout -> `app_layout_apply`
88
129
  - flow -> `app_flow_apply`
89
- - views -> `app_views_apply`
90
- - charts -> `app_charts_apply`
130
+ - existing views -> `app_views_apply.patch_views`; new/full views -> `app_views_apply.upsert_views`
131
+ - existing buttons -> `app_custom_buttons_apply.patch_buttons`; new/full buttons -> `app_custom_buttons_apply.upsert_buttons`
132
+ - existing associated resources -> `app_associated_resources_apply.patch_resources`; new/full resources -> `app_associated_resources_apply.upsert_resources`
133
+ - existing charts -> `app_charts_apply.patch_charts`; new/full charts -> `app_charts_apply.upsert_charts`
91
134
  - portal -> `portal_apply`
92
135
  - package metadata/layout -> `package_apply`
93
136
  8. Use `app_publish_verify` only when the user explicitly wants final publish/live verification or you need a dedicated verification pass
@@ -98,7 +141,9 @@ Treat these as the official surface. Do not default to `package_create`, `packag
98
141
  - If `package_id` is unknown, derive it from related app/portal readback when possible; otherwise ask the user instead of falling back to hidden package lookup tools.
99
142
  - Do not use `package_create` or `package_attach_app` as a public default path. If they still appear in low-level code, treat them as internal/legacy implementation details.
100
143
  - Do not use raw `portal_*` writes or raw `qingbi_report_*` writes as the default builder strategy.
101
- - `app_schema_apply`, `app_layout_apply`, `app_flow_apply`, and `app_views_apply` publish by default; `app_charts_apply` is immediate-live without publish.
144
+ - Always pass `publish: true` (or CLI `--publish`) explicitly on `app_schema_apply`, `app_layout_apply`, `app_flow_apply`, `app_views_apply`, and `portal_apply` do not rely on the default. `app_custom_buttons_apply` and `app_associated_resources_apply` publish automatically after at least one write succeeds and do not accept a `publish` parameter. `app_charts_apply` is immediate-live without a publish step.
145
+ - When creating or updating fields, configure the app data title/cover directly in field JSON: `as_data_title: true` is required on exactly one readable top-level title field; `as_data_cover: true` is optional and only valid on one top-level `attachment` field.
146
+ - When any tool returns `error_code` or `VALIDATION_ERROR`, read the `message` field verbatim first before guessing at parameter or CLI parsing issues. A clear `message` like "relation field requires visible_fields" is the root cause — do not redirect to unrelated hypotheses such as `create_if_missing` parsing or flag conflicts.
102
147
  - If the same validation error repeats twice, stop guessing and re-read `builder_tool_contract`.
103
148
  - For workflow assignees, prefer `role_search` over explicit members unless the user explicitly wants named members.
104
149
  - Public flow building is still intentionally limited to stable linear workflows. If a requirement sounds like branches/conditions, explain the limitation instead of freehanding unsupported graph shapes.
@@ -107,11 +152,18 @@ Treat these as the official surface. Do not default to `package_create`, `packag
107
152
 
108
153
  ## Response Interpretation
109
154
 
155
+ - All builder apply/write tools return a standard UI envelope in addition to legacy fields:
156
+ `schema_version`, `operation`, `summary`, and `resources[]`.
157
+ - For UI cards or quick narration, read `resources[]` first. Each resource has `resource_type`, `operation`, `status`, `id`, `key`, `name`, typed `ids`, and `parent`.
158
+ - Use legacy fields such as `field_diff`, `views_diff`, `chart_results`, `created/updated/removed`, and `verification` only for compatibility and troubleshooting.
110
159
  - Treat post-write readback as the source of truth, not just write status codes.
111
160
  - `success` means write and verification completed; `partial_success` means the write landed but verification is incomplete.
112
161
  - For portals, distinguish clearly between:
113
162
  - base-info-only update with layout preserved
114
163
  - full sections replace
164
+ - For object apply tools, distinguish clearly between:
165
+ - `patch_*`: public partial parameter replacement; the tool hydrates current config and full-saves internally
166
+ - `upsert_*`: create or full target configuration; omitted fields may mean default/clear depending on that object
115
167
  - For charts, distinguish clearly between:
116
168
  - base / visibility change
117
169
  - real chart-config change
@@ -122,7 +174,7 @@ Treat these as the official surface. Do not default to `package_create`, `packag
122
174
  - Read one package: `package_get`
123
175
  - Create or update one package: `package_apply`
124
176
  - Resolve one app: `app_resolve`
125
- - Read one app summary: `app_get`
177
+ - Read one app map: `app_get`
126
178
  - Read fields only: `app_get_fields`
127
179
  - Read layout summary: `app_get_layout`
128
180
  - Read views summary: `app_get_views`
@@ -134,17 +186,84 @@ Treat these as the official surface. Do not default to `package_create`, `packag
134
186
  - Add/update/remove fields: `app_schema_apply`
135
187
  - Merge or replace layout: `app_layout_apply`
136
188
  - Replace workflow: `app_flow_apply`
137
- - Upsert/remove views: `app_views_apply`
138
- - Upsert/remove/reorder charts: `app_charts_apply`
189
+ - Upsert/patch/remove views: `app_views_apply`
190
+ - Upsert/patch/remove custom buttons and bind them to header/detail view positions: `app_custom_buttons_apply`
191
+ - Upsert/patch/remove app associated reports/views and per-view display: `app_associated_resources_apply`
192
+ - Upsert/patch/remove/reorder charts: `app_charts_apply`
139
193
  - Create or update portal pages: `portal_apply`
140
194
  - Release your own stale edit lock: `app_release_edit_lock_if_mine`
141
195
  - Final publish verification: `app_publish_verify`
142
196
 
197
+ ## Button Pattern
198
+
199
+ Use `app_custom_buttons_apply` for the whole custom-button path: button body, add-data mapping, default values, and placement.
200
+
201
+ ```json
202
+ {
203
+ "tool_name": "app_custom_buttons_apply",
204
+ "arguments": {
205
+ "app_key": "EMPLOYEE_APP",
206
+ "upsert_buttons": [
207
+ {
208
+ "client_key": "add_worklog",
209
+ "button_text": "快捷添加工时",
210
+ "style_preset": "primary_blue",
211
+ "button_icon": "ex-plus-circle",
212
+ "trigger_action": "addData",
213
+ "trigger_add_data_config": {
214
+ "target_app_key": "WORKLOG_APP",
215
+ "field_mappings": [
216
+ {"source_field": "数据ID", "target_field": "关联员工"},
217
+ {"source_field": "员工名称", "target_field": "员工姓名"},
218
+ {"source_field": "所属部门", "target_field": "部门名称"}
219
+ ],
220
+ "default_values": {
221
+ "工时类型": "日常工作",
222
+ "状态": "待提交"
223
+ }
224
+ }
225
+ }
226
+ ],
227
+ "remove_buttons": [],
228
+ "view_configs": [
229
+ {
230
+ "view_key": "EMPLOYEE_VIEW",
231
+ "mode": "merge",
232
+ "buttons": [
233
+ {"button_ref": "add_worklog", "placement": "detail", "primary": true}
234
+ ]
235
+ }
236
+ ]
237
+ }
238
+ }
239
+ ```
240
+
241
+ `button_ref` may be a same-call `client_key` or an existing `button_id`. Supported placements are `header`, `detail`, and `list`; `list` maps to the backend `INSIDE` row/list button position.
242
+
243
+ For add-data buttons that create a child record linked back to the current record, map `source_field: "数据ID"` to the target relation field. Do not use raw `que_relation` unless maintaining an existing backend config.
244
+
245
+ ## Playbook Selection
246
+
247
+ Before starting any build or create task, route to the right reference first:
248
+
249
+ | User intent | Signal phrases | Playbook |
250
+ |-------------|---------------|---------|
251
+ | Multi-app business system with cross-app relations, shared workflow, and a portal dashboard | "建一套系统"、"包含 N 个模块"、"这几个表单之间建立关联"、"完整的业务系统" | [build-complete-system.md](references/build-complete-system.md) |
252
+ | Single app end-to-end: rich schema, layout, flow, multiple view types, custom buttons, in-app charts, associated resources, and sample data | "建一个应用"、"帮我搭一个XX管理"、"从零开始建一个XX"、"我需要一个管理XX的应用" | [build-single-app.md](references/build-single-app.md) |
253
+ | Add one new app inside an existing package | "在这个包里加一个表单"、"现有系统里新增一个模块" | [create-app.md](references/create-app.md) |
254
+ | Update fields or schema only | "加一个字段"、"改字段名"、"删除字段" | [update-schema.md](references/update-schema.md) |
255
+ | Update layout only | "调整分组"、"改排列顺序" | [update-layout.md](references/update-layout.md) |
256
+ | Update workflow only | "修改审批流"、"加一个审批节点" | [update-flow.md](references/update-flow.md) |
257
+ | Update views only | "加一个看板"、"改筛选条件"、"新增视图" | [update-views.md](references/update-views.md) |
258
+
259
+ Decision rule: if the user mentions **multiple related apps or a portal**, use `build-complete-system.md`. If it is **one app** with a from-scratch setup request, use `build-single-app.md`. If the target app already exists and the user only wants to change one layer, use the matching single-layer playbook.
260
+
143
261
  ## Resources
144
262
 
145
- - Shared public-surface baseline: [public-surface-sync.md](../qingflow-app-user/references/public-surface-sync.md)
263
+ - Shared public-surface baseline: [public-surface-sync.md](references/public-surface-sync.md)
146
264
  - Environment switching: [references/environments.md](references/environments.md)
147
265
  - Tool choice and sequencing: [references/tool-selection.md](references/tool-selection.md)
266
+ - Field matching rules: [references/match-rules.md](references/match-rules.md)
148
267
  - Result semantics and gotchas: [references/gotchas.md](references/gotchas.md)
149
268
  - Create one app in an existing package: [references/create-app.md](references/create-app.md)
150
269
  - Update fields only: [references/update-schema.md](references/update-schema.md)
@@ -153,3 +272,5 @@ Treat these as the official surface. Do not default to `package_create`, `packag
153
272
  - Workflow assignees and node permissions: [references/flow-actors-and-permissions.md](references/flow-actors-and-permissions.md)
154
273
  - Update views only: [references/update-views.md](references/update-views.md)
155
274
  - Standard end-to-end builder sequences: [references/solution-playbooks.md](references/solution-playbooks.md)
275
+ - Build a complete multi-app system from scratch: [references/build-complete-system.md](references/build-complete-system.md)
276
+ - Build a single app end-to-end (schema + layout + flow + views + buttons + charts + associated resources + data): [references/build-single-app.md](references/build-single-app.md)