openyida 2026.9.2 → 2026.9.3

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 (80) hide show
  1. package/README.md +9 -7
  2. package/lib/app/create-form/api-path.js +16 -2
  3. package/lib/app/create-form/args.js +14 -0
  4. package/lib/app/create-form/nav-icon-service.js +202 -0
  5. package/lib/app/create-form/nav-icon.js +267 -0
  6. package/lib/app/create-form.js +77 -3
  7. package/lib/app/nav-group.js +221 -53
  8. package/lib/app/page-template-guard.js +64 -0
  9. package/lib/app/publish.js +15 -1
  10. package/lib/app/services/canvas-page-schema-builder.js +1 -1
  11. package/lib/asset/ai-image.js +8 -9
  12. package/lib/asset/asset-cmd.js +3 -49
  13. package/lib/asset/asset-resolve.js +8 -10
  14. package/lib/asset/asset-status.js +7 -7
  15. package/lib/core/agent-capabilities.js +43 -1
  16. package/lib/core/cli-error.js +6 -1
  17. package/lib/core/command-contract.js +42 -1
  18. package/lib/core/command-manifest.js +73 -11
  19. package/lib/core/locales/en.js +5 -1
  20. package/lib/core/locales/zh.js +7 -3
  21. package/lib/core/query-data.js +126 -8
  22. package/lib/core/sample.js +51 -10
  23. package/lib/samples/openyida-scaffold/canvas-form-drawer.canvas.jsx +7 -7
  24. package/package.json +1 -1
  25. package/scripts/postinstall.js +5 -5
  26. package/yida-skills/SKILL.md +13 -13
  27. package/yida-skills/references/task-retrospective.md +4 -4
  28. package/yida-skills/skills/yida-app/SKILL.md +9 -8
  29. package/yida-skills/skills/yida-app/references/common-issues.md +1 -1
  30. package/yida-skills/skills/yida-app/workflow/step-2-design.md +42 -15
  31. package/yida-skills/skills/yida-app/workflow/step-3-create-or-reuse-app.md +8 -1
  32. package/yida-skills/skills/yida-app/workflow/step-4-forms-processes.md +13 -15
  33. package/yida-skills/skills/yida-app/workflow/step-7-page-code.md +4 -2
  34. package/yida-skills/skills/yida-app/workflow/step-8-publish-navigation.md +26 -11
  35. package/yida-skills/skills/yida-app/workflow/step-9-output-finish.md +22 -11
  36. package/yida-skills/skills/yida-canvas-custom-page/SKILL.md +8 -8
  37. package/yida-skills/skills/yida-canvas-custom-page/references/canvas-authoring-examples.md +1 -1
  38. package/yida-skills/skills/yida-canvas-custom-page/references/canvas-style-implementation-guide.md +16 -16
  39. package/yida-skills/skills/yida-canvas-custom-page/references/data-bridge-guide.md +1 -1
  40. package/yida-skills/skills/yida-canvas-custom-page/references/navigation-and-entry-guide.md +1 -1
  41. package/yida-skills/skills/yida-canvas-custom-page/references/page-generation-guide.md +6 -7
  42. package/yida-skills/skills/yida-canvas-table-form/SKILL.md +2 -2
  43. package/yida-skills/skills/yida-create-app/SKILL.md +13 -7
  44. package/yida-skills/skills/yida-create-form-page/SKILL.md +15 -17
  45. package/yida-skills/skills/yida-create-form-page/references/form-field-properties.md +1 -4
  46. package/yida-skills/skills/yida-custom-page/SKILL.md +5 -5
  47. package/yida-skills/skills/yida-custom-page/references/coding-guide.md +2 -6
  48. package/yida-skills/skills/yida-custom-page/references/design-system.md +4 -4
  49. package/yida-skills/skills/yida-data-management/SKILL.md +6 -4
  50. package/yida-skills/skills/yida-data-management/references/data-format-guide.md +13 -5
  51. package/yida-skills/skills/yida-design/SKILL.md +36 -39
  52. package/yida-skills/skills/yida-design/references/asset-workflow.md +32 -85
  53. package/yida-skills/skills/yida-design/references/page-quality-gates.md +3 -3
  54. package/yida-skills/skills/yida-design/references/style-design-selection.md +4 -4
  55. package/yida-skills/skills/yida-design/references/theme/app-custom-theme-template.css +15 -24
  56. package/yida-skills/skills/yida-design/references/theme/theme-token-presets.md +8 -8
  57. package/yida-skills/skills/yida-design/references/visual-decision-engine.md +1 -1
  58. package/yida-skills/skills/yida-design/references/visual-scaffold-recipes.md +2 -2
  59. package/yida-skills/skills/yida-design/sub_skill/page-design/SKILL.md +12 -13
  60. package/yida-skills/skills/yida-design/workflow/output-design.md +40 -26
  61. package/yida-skills/skills/yida-design/workflow/output-prd.md +8 -8
  62. package/yida-skills/skills/yida-design/workflow/step-1-read-brief.md +14 -0
  63. package/yida-skills/skills/yida-design/workflow/step-2-theme-system.md +23 -17
  64. package/yida-skills/skills/yida-design/workflow/step-4-wireframe-interaction.md +3 -3
  65. package/yida-skills/skills/yida-design/workflow/step-5-visual-states.md +5 -5
  66. package/yida-skills/skills/yida-design/workflow/step-6-handoff.md +22 -55
  67. package/yida-skills/skills/yida-nav-group/SKILL.md +3 -1
  68. package/yida-skills/skills/yida-page-config/SKILL.md +3 -1
  69. package/yida-skills/skills/yida-prd/SKILL.md +48 -0
  70. package/yida-skills/skills/yida-prd/references/app/blueprint.md +73 -0
  71. package/yida-skills/skills/yida-prd/references/app/navigation-patterns.md +43 -0
  72. package/yida-skills/skills/yida-prd/references/app/role-journey.md +30 -0
  73. package/yida-skills/skills/yida-prd/workflow/output-prd.md +188 -0
  74. package/yida-skills/skills/yida-prd/workflow/step-1-read-brief.md +15 -0
  75. package/yida-skills/skills/{yida-design/workflow/step-3-information-architecture.md → yida-prd/workflow/step-2-information-architecture.md} +2 -2
  76. package/yida-skills/skills/yida-publish-page/SKILL.md +1 -1
  77. package/yida-skills/skills/yida-requirement-analysis/SKILL.md +49 -0
  78. package/yida-skills/skills-index.json +36 -35
  79. package/yida-skills/skills/yida-design/workflow/step-1-positioning.md +0 -58
  80. package/yida-skills/skills/yida-form-detail/SKILL.md +0 -68
@@ -2,13 +2,12 @@
2
2
  /**
3
3
  * asset-cmd.js - `openyida asset` 素材能力命令
4
4
  *
5
- * P3 素材工具化的统一入口,把「检测素材能力 / 校验图片 URL / 解析回填 /
6
- * 素材来源引导」暴露为可脚本化命令,供本地智能体在生成官网/落地页前调用,
5
+ * 素材工具的统一入口,把「检测素材能力 / 解析回填 / 素材来源引导」
6
+ * 暴露为可脚本化命令,供本地智能体在生成官网/落地页前调用,
7
7
  * 从而做到:无 CDN 不谎称已上传、无可用素材不交付「最终版」。
8
8
  *
9
9
  * 子命令:
10
10
  * status 检测当前素材能力(CDN / 生成 / 推荐策略)
11
- * verify-url <url...> 校验一个或多个公开图片 URL 是否真实可达且为图片
12
11
  * resolve --hero <..> [--product <..>] [--upload-assets] 解析并回填一组素材,输出 materialStatus/缺口
13
12
  * generate 输出素材来源引导(免费素材库清单 + 纪律)
14
13
  *
@@ -18,7 +17,6 @@
18
17
  'use strict';
19
18
 
20
19
  const { getAssetStatus } = require('./asset-status');
21
- const { verifyImageUrls } = require('./url-verify');
22
20
  const { resolveAssets } = require('./asset-resolve');
23
21
  const {
24
22
  detectImageGenerator,
@@ -30,32 +28,28 @@ function printHelp() {
30
28
  console.log(`
31
29
  用法:
32
30
  openyida asset status [--offline] [--json]
33
- openyida asset verify-url <url> [<url> ...] [--min-bytes N] [--timeout ms] [--json]
34
31
  openyida asset resolve --hero <路径或URL> [--product <路径或URL> ...] [--require-hero] [--upload-assets] [--offline] [--json]
35
32
  openyida asset generate [--json]
36
33
 
37
34
  子命令:
38
35
  status 检测当前素材能力:是否配置 CDN、是否可生成、推荐素材策略
39
- verify-url 校验公开图片 URL 是否真实可达且确为图片(HEAD/GET,跟随重定向)
40
36
  resolve 把本地/外链素材解析为稳定 URL 并回填,输出 materialStatus(final|draft|none) 与缺口
41
37
  generate 输出素材来源引导(免费可商用素材库清单 + 使用纪律,供智能体检索真实图片)
42
38
 
43
39
  说明:
44
40
  openyida 无内置文生图能力;图片应由智能体自行生成或到免费素材库检索,
45
- 再经 verify-url 校验、(有 CDN 时)resolve 转存 CDN 后回填 spec。
41
+ 再通过 resolve 解析,并在配置 CDN 时转存后回填 spec。
46
42
  `);
47
43
  }
48
44
 
49
45
  function parseArgs(args) {
50
46
  const parsed = {
51
47
  subCommand: args[0],
52
- urls: [],
53
48
  hero: '',
54
49
  products: [],
55
50
  requireHero: false,
56
51
  offline: false,
57
52
  uploadAssets: false,
58
- minBytes: 0,
59
53
  timeout: 0,
60
54
  json: false,
61
55
  help: false,
@@ -78,12 +72,8 @@ function parseArgs(args) {
78
72
  } else if (arg === '--product') {
79
73
  const v = args[++i];
80
74
  if (v) {parsed.products.push(v);}
81
- } else if (arg === '--min-bytes') {
82
- parsed.minBytes = parseInt(args[++i], 10) || 0;
83
75
  } else if (arg === '--timeout') {
84
76
  parsed.timeout = parseInt(args[++i], 10) || 0;
85
- } else if (!arg.startsWith('--')) {
86
- parsed.urls.push(arg);
87
77
  }
88
78
  }
89
79
 
@@ -109,39 +99,6 @@ function runStatus(options) {
109
99
  status.reasons.forEach((r) => console.log(' - ' + r));
110
100
  }
111
101
 
112
- async function runVerifyUrl(options) {
113
- if (!options.urls.length) {
114
- throwCommandError('错误:请至少提供一个图片 URL。用法:openyida asset verify-url <url> [<url> ...]', {
115
- code: 'ASSET_VERIFY_URL_INVALID_ARGUMENTS',
116
- });
117
- }
118
- const results = await verifyImageUrls(options.urls, {
119
- minBytes: options.minBytes,
120
- timeout: options.timeout || undefined,
121
- });
122
- if (options.json) {
123
- console.log(JSON.stringify(results, null, 2));
124
- return;
125
- }
126
- let allOk = true;
127
- results.forEach((r) => {
128
- if (!r.ok) {allOk = false;}
129
- console.log((r.ok ? '[OK] ' : '[FAIL]') + ' ' + r.url);
130
- console.log(
131
- ' status=' + r.status +
132
- ' type=' + (r.contentType || '-') +
133
- ' bytes=' + (r.bytes || '-') +
134
- ' reason=' + r.reason
135
- );
136
- });
137
- if (!allOk) {
138
- throwCommandError('图片 URL 校验失败', {
139
- code: 'ASSET_VERIFY_URL_FAILED',
140
- details: { results },
141
- });
142
- }
143
- }
144
-
145
102
  async function runResolve(options) {
146
103
  const assets = {
147
104
  heroImage: options.hero,
@@ -210,9 +167,6 @@ async function run(args) {
210
167
  case 'status':
211
168
  runStatus(options);
212
169
  break;
213
- case 'verify-url':
214
- await runVerifyUrl(options);
215
- break;
216
170
  case 'resolve':
217
171
  await runResolve(options);
218
172
  break;
@@ -11,10 +11,8 @@
11
11
  * - 本地文件(存在于磁盘):
12
12
  * · 有 CDN → 转存 CDN,替换为稳定 CDN URL(source: cdn);
13
13
  * · 无 CDN → 无法交付浏览器可访问的稳定 URL → 记为缺口。
14
- * - http(s) 外链:
15
- * · 经 url-verify 校验可达且为图片 → 保留(source: verified-external);
16
- * 若有 CDN 且 options.mirror=true,可后续扩展为镜像转存;
17
- * · 校验失败 → 记为缺口(绝不带病写进页面)。
14
+ * - http(s) 外链:由 resolve 流程确认可用后保留(source: external);
15
+ * 有 CDN 且 options.mirror=true 时镜像转存;不可用时记为缺口。
18
16
  * - 空 / 占位:记为对应槽位缺口。
19
17
  *
20
18
  * materialStatus:
@@ -116,7 +114,7 @@ async function resolveOne(candidate, ctx) {
116
114
  return { resolved: false, url: '', source: 'empty', reason: 'EMPTY' };
117
115
  }
118
116
 
119
- // 外链 URL:先校验可达且为图片
117
+ // 外链素材:由 resolve 流程统一确认并按需镜像
120
118
  if (isHttpUrl(raw)) {
121
119
  const v = await (ctx.verifyFn || verifyImageUrl)(raw, { timeout: ctx.timeout });
122
120
  if (v.ok) {
@@ -148,11 +146,11 @@ async function resolveOne(candidate, ctx) {
148
146
  return {
149
147
  resolved: true,
150
148
  url: v.finalUrl || raw,
151
- source: 'verified-external',
149
+ source: 'external',
152
150
  reason: 'OK',
153
151
  };
154
152
  }
155
- return { resolved: false, url: raw, source: 'external', reason: v.reason || 'VERIFY_FAILED' };
153
+ return { resolved: false, url: raw, source: 'external', reason: v.reason || 'ASSET_UNAVAILABLE' };
156
154
  }
157
155
 
158
156
  // 本地文件:需要 CDN 才能转存为浏览器可访问的稳定 URL
@@ -184,7 +182,7 @@ async function resolveOne(candidate, ctx) {
184
182
  }
185
183
  }
186
184
 
187
- // 既不是可达 URL 也不是磁盘文件 → 视为未落地的占位引用
185
+ // 既不是外链也不是磁盘文件 → 视为未落地的占位引用
188
186
  return { resolved: false, url: raw, source: 'unknown', reason: 'NOT_FOUND' };
189
187
  }
190
188
 
@@ -193,8 +191,8 @@ async function resolveOne(candidate, ctx) {
193
191
  * @param {object} assets { heroImage, heroImageAlt, productImages, materialStrategy }
194
192
  * @param {object} [options]
195
193
  * @param {boolean} [options.requireHero] 页面是否必须有 Hero 图(官网/落地页通常 true)
196
- * @param {boolean} [options.online] 是否可联网校验外链(默认 true)
197
- * @param {number} [options.timeout] 单个 URL 校验超时
194
+ * @param {boolean} [options.online] 是否可联网处理外链(默认 true)
195
+ * @param {number} [options.timeout] 单个外链处理超时
198
196
  * @param {function} [options.uploadFn] 自定义上传函数(默认懒加载 cdn-upload.performUpload)
199
197
  * @returns {Promise<{
200
198
  * assets: object,
@@ -20,19 +20,19 @@ const { detectImageGenerator } = require('./ai-image');
20
20
  /**
21
21
  * 素材策略枚举
22
22
  * - cdn-upload : 有 CDN,可把本地/生成的图片转存为稳定 CDN URL
23
- * - verified-url : 无 CDN,但可使用「已验证可达」的公开图片 URL
23
+ * - external-url : 无 CDN,外链素材由 asset resolve 统一处理
24
24
  * - low-fidelity : 既无 CDN 也无可用素材 → 只能交付低保真草稿并标注素材缺口
25
25
  */
26
26
  const STRATEGY = {
27
27
  CDN_UPLOAD: 'cdn-upload',
28
- VERIFIED_URL: 'verified-url',
28
+ EXTERNAL_URL: 'external-url',
29
29
  LOW_FIDELITY: 'low-fidelity-draft',
30
30
  };
31
31
 
32
32
  /**
33
33
  * 检测当前素材能力
34
34
  * @param {object} [options]
35
- * @param {boolean} [options.online] 是否可联网(用于是否允许 verified-url 策略)
35
+ * @param {boolean} [options.online] 是否可联网(用于是否允许处理外链素材)
36
36
  * @returns {{
37
37
  * cdnConfigured: boolean,
38
38
  * canUpload: boolean,
@@ -43,7 +43,7 @@ const STRATEGY = {
43
43
  * }}
44
44
  */
45
45
  function getAssetStatus(options = {}) {
46
- const online = options.online !== false; // 默认认为可联网校验公开 URL
46
+ const online = options.online !== false; // 默认认为可联网处理外链素材
47
47
  const config = loadCdnConfig();
48
48
  const { valid, missing } = validateCdnConfig(config);
49
49
 
@@ -55,12 +55,12 @@ function getAssetStatus(options = {}) {
55
55
  recommendedStrategy = STRATEGY.CDN_UPLOAD;
56
56
  reasons.push('CDN 已配置:本地或 AI 生成图片可转存为稳定 CDN URL 并回填 spec。');
57
57
  } else if (online) {
58
- recommendedStrategy = STRATEGY.VERIFIED_URL;
59
- reasons.push('未配置 CDN:只能使用已通过 verify-url 校验的公开图片 URL,或交付草稿。');
58
+ recommendedStrategy = STRATEGY.EXTERNAL_URL;
59
+ reasons.push('未配置 CDN:外链素材交给 `openyida asset resolve` 统一处理,或交付草稿。');
60
60
  reasons.push('缺失 CDN 字段:' + (missing.join(', ') || '(未知)') + ',运行 `openyida cdn-config` 可开启上传能力。');
61
61
  } else {
62
62
  recommendedStrategy = STRATEGY.LOW_FIDELITY;
63
- reasons.push('未配置 CDN 且无法联网校验素材:只能交付低保真草稿并标注素材缺口。');
63
+ reasons.push('未配置 CDN 且无法处理外链素材:只能交付低保真草稿并标注素材缺口。');
64
64
  }
65
65
 
66
66
  if (!generator.available) {
@@ -142,6 +142,7 @@ const BUILDER_CORE_COMMAND_IDS = Object.freeze([
142
142
  'save-permission',
143
143
  'create-app',
144
144
  'create-form.create',
145
+ 'create-form.icons',
145
146
  'create-page',
146
147
  'publish',
147
148
  ]);
@@ -159,6 +160,8 @@ function commandBriefs(manifest, commandIds) {
159
160
  permission_mode: entry.permission && entry.permission.mode,
160
161
  side_effect_kind: entry.side_effect && entry.side_effect.kind,
161
162
  examples: entry.examples || [],
163
+ args: entry.args || [],
164
+ canonical: entry.canonical || null,
162
165
  }));
163
166
  }
164
167
 
@@ -443,6 +446,37 @@ function compactBuilderPath(builderPath) {
443
446
  };
444
447
  }
445
448
 
449
+ function buildFullAppArtifactRoute(manifest) {
450
+ const workflow = manifest.summary.core_workflows.full_app_build;
451
+ return {
452
+ orchestrator_skill_id: workflow.orchestrator_skill_id,
453
+ requirement_analysis_skill_id: workflow.requirement_analysis_skill_id,
454
+ requirement_brief_path: workflow.requirement_brief_path,
455
+ artifact_generation: workflow.artifact_generation,
456
+ };
457
+ }
458
+
459
+ function buildApplicationEntryPolicy(builderPath, env = process.env) {
460
+ const runtime = builderPath.runtime || {};
461
+ const managedRuntime = String(env.OPENYIDA_MANAGED_RUNTIME || '').trim().toLowerCase();
462
+ const managedCloudAgent = managedRuntime === 'cloud' ||
463
+ runtime.runtime === 'web_sandbox' ||
464
+ runtime.tool === 'mulerun';
465
+
466
+ return {
467
+ schema_version: 1,
468
+ environment: managedCloudAgent ? 'managed_cloud_agent' : 'non_cloud_agent',
469
+ delivery_unit: 'single_application_entry_group',
470
+ resource_delivery: 'summary_only',
471
+ internal_artifact_delivery: 'never',
472
+ entries: {
473
+ workbench: 'always',
474
+ custom: 'when_entry_mode_standalone_and_is_render_nav_false_readback',
475
+ admin: managedCloudAgent ? 'omit' : 'include',
476
+ },
477
+ };
478
+ }
479
+
446
480
  function canonicalize(value) {
447
481
  if (Array.isArray(value)) {
448
482
  return value.map(canonicalize);
@@ -484,6 +518,8 @@ function buildCommandManifestDigest(manifest) {
484
518
  side_effect_kind: entry.side_effect && entry.side_effect.kind,
485
519
  permission_mode: entry.permission && entry.permission.mode,
486
520
  permission_effect: entry.permission && entry.permission.effect,
521
+ args: entry.args || [],
522
+ canonical: entry.canonical || null,
487
523
  })),
488
524
  };
489
525
 
@@ -509,6 +545,7 @@ function buildAgentCapabilitiesSummary() {
509
545
  const builderPath = compactBuilderPath(
510
546
  buildBuilderPath(loginStatus, projectRoot, manifest, process.env, runtime)
511
547
  );
548
+ const applicationEntryPolicy = buildApplicationEntryPolicy(builderPath, process.env);
512
549
 
513
550
  return {
514
551
  schema_version: 1,
@@ -527,6 +564,8 @@ function buildAgentCapabilitiesSummary() {
527
564
  command_manifest_digest_algorithm: 'sha256',
528
565
  command_count: manifest.summary.command_count,
529
566
  full_capabilities_command: 'openyida agent-capabilities --json',
567
+ full_app_artifact_route: buildFullAppArtifactRoute(manifest),
568
+ application_entry_policy: applicationEntryPolicy,
530
569
  runtime,
531
570
  builder_path: builderPath,
532
571
  };
@@ -546,6 +585,7 @@ function buildAgentCapabilities() {
546
585
  const loginStatus = redactLogin(getAuthStatus({ projectRoot, includeSecrets: false }));
547
586
  const runtime = buildRuntimeSnapshot(projectRoot);
548
587
  const builderPath = buildBuilderPath(loginStatus, projectRoot, manifest, process.env, runtime);
588
+ const applicationEntryPolicy = buildApplicationEntryPolicy(builderPath, process.env);
549
589
 
550
590
  return {
551
591
  schema_version: 1,
@@ -559,6 +599,7 @@ function buildAgentCapabilities() {
559
599
  active: envSnapshot.active,
560
600
  runtime,
561
601
  login: loginStatus,
602
+ application_entry_policy: applicationEntryPolicy,
562
603
  recommended: {
563
604
  preflight_command: 'openyida agent-capabilities --summary-json',
564
605
  full_capabilities_command: 'openyida agent-capabilities --json',
@@ -602,7 +643,7 @@ function buildAgentCapabilities() {
602
643
  ],
603
644
  retry_policy: 'Do not repeat the same failed command without changing login state, organization, parameters, files, or field IDs.',
604
645
  completion_contracts: {
605
- full_app: 'Default full-app build is complete after creating or reusing the app, core forms, primary page, publishing it, and returning access URLs. If three or more resources/links are returned, present them in a Markdown table with type, purpose/name, URL, and status.',
646
+ full_app: 'Default full-app build is complete after creating or reusing the app, core forms, primary page, publishing it, and returning one named application entry group. Summarize business resources in prose; do not deliver one artifact or link card per resource.',
606
647
  },
607
648
  full_app_data_contract: 'Default full-app page code must not call this.dataSourceMap.* unless the same run created and bound a designer data source; use this.utils.yida.* or an entry-only page by default.',
608
649
  },
@@ -633,6 +674,7 @@ async function run(args = []) {
633
674
  module.exports = {
634
675
  buildAgentCapabilities,
635
676
  buildAgentCapabilitiesSummary,
677
+ buildApplicationEntryPolicy,
636
678
  compactBuilderPath,
637
679
  buildCommandManifestDigest,
638
680
  run,
@@ -50,6 +50,10 @@ function toErrorPayload(error) {
50
50
  'deleted',
51
51
  'alreadyAbsent',
52
52
  'mutationAccepted',
53
+ 'mutationPerformed',
54
+ 'changed',
55
+ 'alreadyApplied',
56
+ 'recoveredByReadback',
53
57
  'readbackVerified',
54
58
  'status',
55
59
  ].forEach((key) => {
@@ -86,7 +90,8 @@ function shouldUseStructuredErrorOutput(error, args = []) {
86
90
  && details.retrySafe === false
87
91
  && details.sideEffectState === 'unknown'
88
92
  );
89
- return ownedPartial || mutationOutcomeUnknown;
93
+ const navigationOrderFailure = /^NAV_ORDER_/.test(error.code || '');
94
+ return ownedPartial || mutationOutcomeUnknown || navigationOrderFailure;
90
95
  }
91
96
 
92
97
  module.exports = {
@@ -163,10 +163,19 @@ function quoteDisplayArg(value, force) {
163
163
 
164
164
  function normalizeEntryArgs(entry) {
165
165
  return (entry.args || [])
166
+ .filter(arg => arg.source !== 'option')
166
167
  .slice()
167
168
  .sort((left, right) => (left.position || 0) - (right.position || 0));
168
169
  }
169
170
 
171
+ function normalizeEntryOptions(entry) {
172
+ return (entry.args || []).filter(arg => arg.source === 'option');
173
+ }
174
+
175
+ function primaryBuilderOption(arg) {
176
+ return (arg.builder_options || [])[0] || `--${optionNameToken(arg.name)}`;
177
+ }
178
+
170
179
  function getCreateFormCreateCanonical(entry = getCreateFormCreateEntry()) {
171
180
  return cloneJson(entry.canonical, null);
172
181
  }
@@ -193,9 +202,25 @@ function getArgPlaceholder(entry, arg) {
193
202
 
194
203
  function buildCreateFormCreateCommand(params = {}, entry = getCreateFormCreateEntry()) {
195
204
  const args = normalizeEntryArgs(entry);
205
+ const optionArgv = [];
206
+ const optionDisplay = [];
207
+ normalizeEntryOptions(entry).forEach(arg => {
208
+ const value = params[arg.name];
209
+ if (value === undefined || value === null || value === false || value === '') {
210
+ return;
211
+ }
212
+ const option = primaryBuilderOption(arg);
213
+ optionArgv.push(option);
214
+ optionDisplay.push(option);
215
+ if (arg.type !== 'boolean') {
216
+ optionArgv.push(String(value));
217
+ optionDisplay.push(quoteDisplayArg(value, false));
218
+ }
219
+ });
196
220
  const argv = [
197
221
  ...canonicalPath(entry),
198
222
  ...args.map(arg => String(params[arg.name] || getArgPlaceholder(entry, arg))),
223
+ ...optionArgv,
199
224
  ];
200
225
  return {
201
226
  argv,
@@ -203,6 +228,7 @@ function buildCreateFormCreateCommand(params = {}, entry = getCreateFormCreateEn
203
228
  'openyida',
204
229
  ...canonicalPath(entry),
205
230
  ...args.map(arg => quoteDisplayArg(params[arg.name] || getArgPlaceholder(entry, arg), !!arg.display_quote)),
231
+ ...optionDisplay,
206
232
  ].join(' '),
207
233
  };
208
234
  }
@@ -317,11 +343,12 @@ function detectDeprecatedCreateFormInvocation(argv = [], options = {}) {
317
343
  function validateCreateFormCreateInvocation(tokens, entry = getCreateFormCreateEntry()) {
318
344
  const path = canonicalPath(entry);
319
345
  const args = normalizeEntryArgs(entry);
346
+ const parsed = parseOptionArgs(tokens.slice(path.length));
320
347
  const params = {};
321
348
  const missing = [];
322
349
 
323
350
  args.forEach(arg => {
324
- const value = tokens[path.length + (arg.position || 0)];
351
+ const value = parsed.positionals[arg.position || 0];
325
352
  if (value) {
326
353
  params[arg.name] = value;
327
354
  } else if (arg.required) {
@@ -329,6 +356,13 @@ function validateCreateFormCreateInvocation(tokens, entry = getCreateFormCreateE
329
356
  }
330
357
  });
331
358
 
359
+ normalizeEntryOptions(entry).forEach(arg => {
360
+ const source = readParam(parsed, arg, undefined);
361
+ if (source.value !== undefined) {
362
+ params[arg.name] = source.value;
363
+ }
364
+ });
365
+
332
366
  if (missing.length === 0) {
333
367
  const command = buildCreateFormCreateCommand(params, entry);
334
368
  return {
@@ -417,6 +451,13 @@ function buildCreateFormCreateFromArgs(args = [], entry = getCreateFormCreateEnt
417
451
  }
418
452
  });
419
453
 
454
+ normalizeEntryOptions(entry).forEach(arg => {
455
+ const source = readParam(parsed, arg, undefined);
456
+ if (source.value !== undefined) {
457
+ params[arg.name] = source.value;
458
+ }
459
+ });
460
+
420
461
  if (missing.length > 0) {
421
462
  return {
422
463
  ok: false,
@@ -365,6 +365,7 @@ const COMMAND_SIDE_EFFECTS = new Map([
365
365
  'agent-capabilities',
366
366
  'check-page',
367
367
  'commands',
368
+ 'create-form.icons',
368
369
  'create-form.validate-fields',
369
370
  'dingtalk-link',
370
371
  'formula.evaluate',
@@ -418,7 +419,7 @@ const COMMAND_SIDE_EFFECTS = new Map([
418
419
  ['asset', sideEffect('mixed', {
419
420
  mutates_yida: false,
420
421
  mutates_local: true,
421
- read_actions: ['status', 'verify-url', 'generate', 'resolve'],
422
+ read_actions: ['status', 'generate', 'resolve'],
422
423
  mutating_actions: ['resolve --upload-assets', 'resolve --mirror-external'],
423
424
  })],
424
425
 
@@ -626,6 +627,7 @@ const COMMAND_PERMISSIONS = new Map([
626
627
  'connector.list',
627
628
  'connector.list-actions',
628
629
  'connector.list-connections',
630
+ 'create-form.icons',
629
631
  'create-form.validate-fields',
630
632
  'dingtalk-link',
631
633
  'dws.contact-user-search',
@@ -1051,7 +1053,7 @@ const COMMAND_GROUPS = [
1051
1053
  id: 'form',
1052
1054
  titleKey: 'help.group_form',
1053
1055
  commands: [
1054
- command('create-form.create', ['create-form', 'create'], 'create-form create <appType> "<formTitle>" <fieldsJsonFile> [--locale zh_CN|en_US|ja_JP] [--open|--no-open]', 'help.cmd_create_form', {
1056
+ command('create-form.create', ['create-form', 'create'], 'create-form create <appType> "<formTitle>" <fieldsJsonFile> [--icon auto|<iconName>] [--locale zh_CN|en_US|ja_JP] [--open|--no-open]', 'help.cmd_create_form', {
1055
1057
  args: [
1056
1058
  {
1057
1059
  name: 'appType',
@@ -1082,13 +1084,23 @@ const COMMAND_GROUPS = [
1082
1084
  inline_json_rejected_options: ['--fields'],
1083
1085
  description: 'Path to a local JSON file containing field definitions.',
1084
1086
  },
1087
+ {
1088
+ name: 'icon',
1089
+ type: 'string',
1090
+ required: false,
1091
+ source: 'option',
1092
+ builder_options: ['--icon'],
1093
+ default: 'auto',
1094
+ value_catalog_command_id: 'create-form.icons',
1095
+ description: 'Form navigation icon name. Use auto for semantic selection or run create-form.icons for supported values.',
1096
+ },
1085
1097
  ],
1086
1098
  canonical: {
1087
1099
  command_id: 'create-form.create',
1088
1100
  path: ['create-form', 'create'],
1089
1101
  argv_template: ['create-form', 'create', '<appType>', '<formTitle>', '<fieldsJsonFile>'],
1090
1102
  display: 'openyida create-form create <appType> "<formTitle>" <fieldsJsonFile>',
1091
- builder: 'openyida commands build create-form.create --app-type <appType> --form-title "<formTitle>" --fields-json-file <fieldsJsonFile> --json',
1103
+ builder: 'openyida commands build create-form.create --app-type <appType> --form-title "<formTitle>" --fields-json-file <fieldsJsonFile> [--icon <iconName>] --json',
1092
1104
  },
1093
1105
  deprecatedPatterns: [
1094
1106
  {
@@ -1118,9 +1130,32 @@ const COMMAND_GROUPS = [
1118
1130
  ],
1119
1131
  examples: [
1120
1132
  'openyida create-form create APP_XXX "访客登记" .cache/openyida/visitor/fields.json',
1133
+ 'openyida create-form create APP_XXX "访客登记" .cache/openyida/visitor/fields.json --icon name-card',
1121
1134
  'openyida commands build create-form.create --app-type APP_XXX --form-title "访客登记" --fields-json-file .cache/openyida/visitor/fields.json --json',
1122
1135
  ],
1123
1136
  }),
1137
+ command('create-form.icons', ['create-form', 'icons'], 'create-form icons [--json]', 'help.cmd_list_form_icons', {
1138
+ requiresLogin: false,
1139
+ output: 'json',
1140
+ args: [
1141
+ {
1142
+ name: 'json',
1143
+ type: 'boolean',
1144
+ required: false,
1145
+ source: 'option',
1146
+ builder_options: ['--json'],
1147
+ default: false,
1148
+ description: 'Pretty-print the icon catalog as JSON.',
1149
+ },
1150
+ ],
1151
+ canonical: {
1152
+ command_id: 'create-form.icons',
1153
+ path: ['create-form', 'icons'],
1154
+ argv_template: ['create-form', 'icons', '[--json]'],
1155
+ display: 'openyida create-form icons [--json]',
1156
+ },
1157
+ examples: ['openyida create-form icons --json'],
1158
+ }),
1124
1159
  command('create-form.validate-fields', ['create-form', 'validate-fields'], 'create-form validate-fields <fieldsJsonOrFile> [--json]', 'help.cmd_validate_form', {
1125
1160
  requiresLogin: false,
1126
1161
  }),
@@ -1321,7 +1356,7 @@ const COMMAND_GROUPS = [
1321
1356
  command('ai', ['ai'], 'ai <text|image> [options]', 'help.cmd_ai', {
1322
1357
  output: 'text|json',
1323
1358
  }),
1324
- command('asset', ['asset'], 'asset <status|verify-url|resolve|generate> [options]', 'help.cmd_asset', {
1359
+ command('asset', ['asset'], 'asset <status|resolve|generate> [options]', 'help.cmd_asset', {
1325
1360
  requiresLogin: false,
1326
1361
  output: 'text|json',
1327
1362
  }),
@@ -1421,17 +1456,44 @@ function summarizeLocalizedCommands(commands) {
1421
1456
  core_workflows: {
1422
1457
  full_app_build: {
1423
1458
  mode: 'unified_build',
1459
+ orchestrator_skill_id: 'yida-app',
1424
1460
  trigger_phrases: ['默认方案', '不要追问', '直接创建', '尽快搭建'],
1425
- default_form_visual_guidance_skill_id: 'yida-form-detail',
1426
1461
  default_page_skill_id: 'yida-canvas-custom-page',
1427
1462
  default_ui_guidance_skill_id: 'yida-design',
1463
+ requirement_analysis_skill_id: 'yida-requirement-analysis',
1464
+ requirement_brief_path: '.cache/openyida/<project>/requirement-brief.json',
1465
+ artifact_generation: {
1466
+ mode: 'parallel',
1467
+ audience: 'internal',
1468
+ user_visible_delivery: false,
1469
+ tasks: [
1470
+ { skill_id: 'yida-prd', output_path: 'prd/<project>/prd.md' },
1471
+ { skill_id: 'yida-design', output_path: 'prd/<project>/design.md' },
1472
+ ],
1473
+ join_owner_skill_id: 'yida-app',
1474
+ },
1428
1475
  ordinary_jsx_skill_id: 'yida-custom-page',
1429
- form_visual_guidance_policy: 'Default form page development loads yida-form-detail as form visual guidance and merges Divider semantic grouping into field JSON. The runtime loads the same application custom theme CSS file in custom pages, forms, submissions, formDetail pages, and FormOpenContainer iframes so all contexts use aligned theme variables.',
1476
+ form_page_policy: 'Use yida-create-form-page for form structure. Never inject CSS, JavaScript, HTML, or theme code into native forms or formDetail pages; the platform renders those pages.',
1430
1477
  page_skill_policy: 'Use YidaCodeCanvas by default for new primary custom pages. Choose yida-custom-page only when the current target has been identified as existing .oyd.jsx/.oyb.jsx/renderJsx/platform Jsx component maintenance; do not switch to yida-custom-page merely because a page needs data, forms, uploads, this.utils.yida.*, or dataSourceMap-like capabilities.',
1431
- product_design_policy: 'Full app creation first resolves resource context, then uses yida-design for requirement analysis and product design before creating or filling missing app/form/process/page resources. yida-design outputs prd/<project>/prd.md with app information, app configuration, data structures, page functions, business rules, interaction states, resource blueprint, resource creation order, page implementation delivery order, navigation order, and acceptance criteria; it outputs prd/<project>/design.md with theme tokens, app custom theme CSS delivery, themeColor/navTheme, visual DNA, layout density, rounded rules, scene recipes, component rules, state rules, and visual acceptance checks.',
1432
- ui_guidance_policy: 'Default full-app primary page creation consumes yida-design prd.md for positioning, information architecture, page prototype, native form entry policy, material strategy, and business-specific checks; it consumes design.md for theme system, visual states, visualScaffold, surface material, rounded rules, density rules, components, and state styling. prd.md and design.md are the only design sources of truth. Page implementation may extract page-spec.json as a derived implementation handoff from prd.md + design.md; conflicts are resolved by regenerating the spec from prd.md/design.md. Demo data, screenshots, public sharing, data-source deep binding, and fine navigation grouping are optional after explicit user request or PRD acceptance criteria.',
1433
- default_nav_order_policy: 'After the primary page is successfully published, immediately run a lightweight navigation order update. If the PRD names a navigation order, call openyida nav-group order <appType> <items...>. If the PRD only gives broad groups or is missing navigation order, use openyida publish ... --auto-nav-order or openyida nav-group auto-order <appType> as a fallback with portal/home/workbench entry > business handling > data management > business analytics > system configuration.',
1434
- final_link_policy: 'Return exactly one primary user-facing link. For single-page create/update/publish intent, return the current page URL. For full app, form, process, permission, theme, navigation, or batch-resource work, return the app workbench URL: {base_url}/{appType}/workbench. Summarize other resources by type/name/ID/status only; do not output static asset, CDN build, locale JSON, admin, config, schema, or intermediate-file URLs unless explicitly requested.',
1478
+ product_design_policy: 'Full app creation first resolves resource context. yida-requirement-analysis writes one shared requirement brief. yida-prd and yida-design start in parallel from that brief and own prd/<project>/prd.md and prd/<project>/design.md independently; neither waits for or overwrites the other. yida-app joins both artifacts, validates their references, and only then creates or fills missing app/form/process/page resources.',
1479
+ ui_guidance_policy: 'Page implementation consumes yida-prd prd.md for positioning, information architecture, page prototype, native form entry policy, material strategy, business-specific checks, resource creation order, page implementation delivery order, navigation order, and acceptance criteria; it consumes yida-design design.md for app custom theme CSS delivery, themeColor/navTheme, visual states, visualScaffold, surface material, rounded rules, density rules, components, and state styling. prd.md and design.md are the only design sources of truth. Page implementation may extract page-spec.json as a derived implementation handoff from prd.md + design.md; conflicts are resolved by sending business conflicts to yida-prd and visual conflicts to yida-design before regenerating the spec. Demo data, screenshots, public sharing, data-source deep binding, and fine navigation grouping are optional after explicit user request or PRD acceptance criteria.',
1480
+ default_nav_order_policy: 'After the primary page is successfully published, perform exactly one navigation order operation. If the PRD names a navigation order, publish without --auto-nav-order and then call openyida nav-group order <appType> <items...>. If the PRD only gives broad groups or is missing navigation order, use openyida publish ... --auto-nav-order and do not call nav-group order or auto-order afterward. Explicit and automatic ordering are mutually exclusive; never generate per-item move loops. The fallback priority is portal/home/workbench entry > business handling > data management > business analytics > system configuration.',
1481
+ final_link_policy: 'Return exactly one user-visible application entry group, never one artifact or link card per form, process, report, schema, manifest, PRD, design file, or intermediate artifact. The group always contains the app workbench URL. Add the clean custom page URL only when PRD entryMode=standalone and get-form-config readback confirms isRenderNav=false. Add the admin URL only when agent-capabilities application_entry_policy.entries.admin=include. Summarize other business resources in prose unless the user explicitly asks for technical IDs.',
1482
+ application_entry_policy: {
1483
+ delivery_unit: 'single_application_entry_group',
1484
+ workbench: { include: 'always', url: '{base_url}/{appType}/workbench' },
1485
+ custom: {
1486
+ include: 'when_entry_mode_standalone_and_is_render_nav_false_readback',
1487
+ url: '{base_url}/{appType}/custom/{formUuid}',
1488
+ },
1489
+ admin: {
1490
+ include: 'follow_agent_capabilities_application_entry_policy',
1491
+ url: '{base_url}/{appType}/admin',
1492
+ },
1493
+ internal_artifacts: 'never_user_visible',
1494
+ business_resources: 'summary_only',
1495
+ },
1496
+ conditional_entry_command_ids: ['update-form-config', 'get-form-config'],
1435
1497
  required_command_ids: [
1436
1498
  'agent-capabilities',
1437
1499
  'create-app',
@@ -1461,7 +1523,7 @@ function summarizeLocalizedCommands(commands) {
1461
1523
  'yida-data-management',
1462
1524
  'yida-dashboard',
1463
1525
  ],
1464
- completion_contract: 'Resolve context, write the yida-design PRD, create or reuse app, core forms/processes, and primary page in PRD resource order, publish it, apply PRD navigation order or lightweight fallback navigation order, and return access URLs. If three or more resources/links are returned, present them in a Markdown table with type, purpose/name, URL, and status.',
1526
+ completion_contract: 'Resolve context, write the shared requirement brief, complete and join the internal parallel yida-prd and yida-design artifacts, create or reuse app, core forms/processes, and primary page in PRD resource order, publish it, apply PRD navigation order or lightweight fallback navigation order, and return one named application entry group. The group contains workbench, conditionally a verified standalone custom entry, and admin only for non-cloud agents. Business resources are summarized instead of emitted as individual delivery artifacts.',
1465
1527
  },
1466
1528
  },
1467
1529
  };
@@ -32,6 +32,7 @@ module.exports = {
32
32
  cmd_import: 'Import migration package, rebuild app',
33
33
  group_form: 'Forms & Pages',
34
34
  cmd_create_form: 'Create a form page',
35
+ cmd_list_form_icons: 'List available form navigation icons',
35
36
  cmd_validate_form: 'Validate form field JSON locally',
36
37
  cmd_update_form: 'Update a form page',
37
38
  cmd_list_forms: 'List forms/pages in an app',
@@ -112,7 +113,7 @@ module.exports = {
112
113
  cmd_batch: 'Run OpenYida commands in batch',
113
114
  cmd_flash_to_prd: 'Convert flash notes or meeting notes to a PRD prompt',
114
115
  cmd_ai: 'Call Yida AI text and image recognition APIs',
115
- cmd_asset: 'Detect asset capability / verify image URLs / resolve materials',
116
+ cmd_asset: 'Detect asset capability / resolve materials',
116
117
  cmd_cdn: 'CDN image management (config/upload/refresh)',
117
118
  cmd_cdn_config: 'Configure CDN / OSS upload',
118
119
  cmd_cdn_upload: 'Upload image to CDN',
@@ -1848,6 +1849,8 @@ Options:
1848
1849
  };
1849
1850
 
1850
1851
  Object.assign(module.exports.query_data || (module.exports.query_data = {}), {
1852
+ association_derived_field_read_only: 'Derived association-form query field {0} is read-only and cannot be saved or updated. Write the source association field without the _id suffix.',
1853
+ association_value_invalid: 'The write value for association-form field {0} is invalid. Use an array of objects where every item includes appType, formUuid, formType, instanceId, and title.',
1851
1854
  command_unsupported: 'Unsupported data command: {0} {1}. Run openyida commands --json to inspect the actual capability.',
1852
1855
  delete_confirmation_required: 'Before deleting a form instance, query it, show the target summary to the user, and add --confirm only after explicit approval. No delete was performed.',
1853
1856
  delete_preflight_failed: 'Could not read form instance {0} for delete preflight. No delete was performed.',
@@ -1856,6 +1859,7 @@ Object.assign(module.exports.query_data || (module.exports.query_data = {}), {
1856
1859
  delete_result_unknown: 'The delete outcome for form instance {0} is unknown. Perform read-only inspection and do not retry the delete automatically.',
1857
1860
  delete_target_mismatch: 'Form instance {0} does not belong to target form {1}. The operation stopped before delete.',
1858
1861
  delete_target_unverified: 'Could not verify the identity and ownership of form instance {0}. The operation stopped before delete.',
1862
+ field_reference_unknown: 'Field reference {0} is not a real fieldId or component alias in the target form. The operation stopped before mutation.',
1859
1863
  form_mode_unverified: 'Could not verify the type of form {0}. Creation stopped before any data write.',
1860
1864
  instance_target_mismatch: 'Instance {0} does not belong to expected business resource {1}. The operation stopped before mutation.',
1861
1865
  instance_target_unverified: 'Could not verify the business resource ownership of instance {0}. The operation stopped before mutation.',