@stalfh233/omc-cli 0.5.6 → 0.5.8

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 (228) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +359 -359
  3. package/coverage/m1-coverage-manifest-v1.json +1414 -1414
  4. package/dist/approval-flow.js +0 -0
  5. package/dist/args.js +0 -0
  6. package/dist/artifacts.js +0 -0
  7. package/dist/batch-call.js +0 -0
  8. package/dist/bench/call-face-eval.js +0 -0
  9. package/dist/bench/context-attribution.js +0 -0
  10. package/dist/bench/discovery-cost-eval.js +0 -0
  11. package/dist/bench/driver.js +0 -0
  12. package/dist/bench/evals.js +0 -0
  13. package/dist/bench/instrument.js +0 -0
  14. package/dist/bench/intent-face-eval.js +40 -40
  15. package/dist/bench/run.js +0 -0
  16. package/dist/bench/scenario.js +0 -0
  17. package/dist/bench/types.js +0 -0
  18. package/dist/bi-wire.js +0 -0
  19. package/dist/bizservice-config.js +0 -0
  20. package/dist/call.js +0 -0
  21. package/dist/capability-absences.js +0 -0
  22. package/dist/capability-overview.js +0 -0
  23. package/dist/capability-shape.js +0 -0
  24. package/dist/cli-contract.js +0 -0
  25. package/dist/cli-output.js +0 -0
  26. package/dist/cli.js +31 -1
  27. package/dist/closest-match.js +0 -0
  28. package/dist/code-rules.js +0 -0
  29. package/dist/command-transport.js +0 -0
  30. package/dist/compound-writes.js +0 -0
  31. package/dist/compute.js +0 -0
  32. package/dist/config-store.js +0 -0
  33. package/dist/context.js +0 -0
  34. package/dist/contract-consistency.js +0 -0
  35. package/dist/contract-resources.js +0 -0
  36. package/dist/coverage-consistency.js +0 -0
  37. package/dist/coverage-registry.js +0 -0
  38. package/dist/coverage.js +0 -0
  39. package/dist/data-list-filter.js +0 -0
  40. package/dist/describe-cache.js +0 -0
  41. package/dist/discovery.js +0 -0
  42. package/dist/endpoints.js +0 -0
  43. package/dist/environment-policy.js +0 -0
  44. package/dist/execution-metadata.js +0 -0
  45. package/dist/field-add.js +314 -0
  46. package/dist/field-change.js +0 -0
  47. package/dist/field-families.js +0 -0
  48. package/dist/form-default.js +0 -0
  49. package/dist/form-layout.js +0 -0
  50. package/dist/form-support.js +9 -0
  51. package/dist/formula-lint.js +0 -0
  52. package/dist/goal-routes.js +28 -16
  53. package/dist/governed-execution.js +0 -0
  54. package/dist/graph.js +50 -33
  55. package/dist/human-summary.js +1 -1
  56. package/dist/identity.js +0 -0
  57. package/dist/intent/baseline.js +0 -0
  58. package/dist/intent/capabilities/bizservice.js +0 -0
  59. package/dist/intent/capabilities/businessrule.js +0 -0
  60. package/dist/intent/capabilities/datarule.js +0 -0
  61. package/dist/intent/capabilities/field.js +0 -0
  62. package/dist/intent/capabilities/form.js +0 -0
  63. package/dist/intent/capabilities/index.js +0 -0
  64. package/dist/intent/capabilities/listview.js +0 -0
  65. package/dist/intent/capabilities/model.js +0 -0
  66. package/dist/intent/capabilities/onlinejs.js +0 -0
  67. package/dist/intent/capabilities/report.js +0 -0
  68. package/dist/intent/capabilities/workflow.js +0 -0
  69. package/dist/intent/capability.js +0 -0
  70. package/dist/intent/cli.js +0 -0
  71. package/dist/intent/compare.js +0 -0
  72. package/dist/intent/compiler.js +0 -0
  73. package/dist/intent/dsl.js +0 -0
  74. package/dist/intent/plan-apply.js +0 -0
  75. package/dist/intent/plan-file.js +0 -0
  76. package/dist/intent/readback.js +0 -0
  77. package/dist/intent/schema.js +0 -0
  78. package/dist/intent/validation.js +0 -0
  79. package/dist/intent/yaml.js +0 -0
  80. package/dist/journal.js +0 -0
  81. package/dist/json-column.js +0 -0
  82. package/dist/lanes/app-contract.js +0 -0
  83. package/dist/lanes/app-coverage.js +0 -0
  84. package/dist/lanes/app.js +0 -0
  85. package/dist/lanes/apply-changes.js +0 -0
  86. package/dist/lanes/b2-registration-contract.js +0 -0
  87. package/dist/lanes/b2-registration-coverage.js +0 -0
  88. package/dist/lanes/b2-registration.js +0 -0
  89. package/dist/lanes/businessrule-contract.js +0 -0
  90. package/dist/lanes/businessrule-coverage.js +0 -0
  91. package/dist/lanes/businessrule.js +11 -3
  92. package/dist/lanes/contract-support.js +0 -0
  93. package/dist/lanes/coverage-declaration.js +0 -0
  94. package/dist/lanes/custom-page.js +0 -0
  95. package/dist/lanes/datarule-contract.js +0 -0
  96. package/dist/lanes/datarule-coverage.js +0 -0
  97. package/dist/lanes/datarule-protocol.js +0 -0
  98. package/dist/lanes/datarule.js +6 -0
  99. package/dist/lanes/dictionary-contract.js +0 -0
  100. package/dist/lanes/dictionary-coverage.js +0 -0
  101. package/dist/lanes/dictionary.js +0 -0
  102. package/dist/lanes/environment-contract.js +0 -0
  103. package/dist/lanes/environment-coverage.js +0 -0
  104. package/dist/lanes/environment.js +0 -0
  105. package/dist/lanes/field-contract.js +0 -0
  106. package/dist/lanes/field-coverage.js +0 -0
  107. package/dist/lanes/field.js +0 -0
  108. package/dist/lanes/folder-contract.js +0 -0
  109. package/dist/lanes/folder-coverage.js +0 -0
  110. package/dist/lanes/folder.js +0 -0
  111. package/dist/lanes/form-contract.js +0 -0
  112. package/dist/lanes/form-coverage.js +0 -0
  113. package/dist/lanes/form.js +0 -0
  114. package/dist/lanes/lane-ids.js +0 -0
  115. package/dist/lanes/list-view-contract.js +0 -0
  116. package/dist/lanes/list-view-coverage.js +0 -0
  117. package/dist/lanes/list-view-shapes.js +0 -0
  118. package/dist/lanes/list-view.js +6 -0
  119. package/dist/lanes/meta-contract.js +0 -0
  120. package/dist/lanes/meta.js +0 -0
  121. package/dist/lanes/model-contract.js +0 -0
  122. package/dist/lanes/model-coverage.js +0 -0
  123. package/dist/lanes/model.js +0 -0
  124. package/dist/lanes/online-js-contract.js +0 -0
  125. package/dist/lanes/online-js-coverage.js +0 -0
  126. package/dist/lanes/online-js.js +0 -0
  127. package/dist/lanes/org-tree.js +0 -0
  128. package/dist/lanes/organization-coverage.js +27 -0
  129. package/dist/lanes/organization.js +10 -1
  130. package/dist/lanes/report-contract.js +0 -0
  131. package/dist/lanes/report-coverage.js +0 -0
  132. package/dist/lanes/report.js +0 -0
  133. package/dist/lanes/rule-graph.js +0 -0
  134. package/dist/lanes/rule-lifecycle-contract.js +0 -0
  135. package/dist/lanes/rule-lifecycle-coverage.js +0 -0
  136. package/dist/lanes/rule-lifecycle.js +0 -0
  137. package/dist/lanes/runtime-data-contract.js +0 -0
  138. package/dist/lanes/runtime-data-coverage.js +0 -0
  139. package/dist/lanes/runtime-data.js +24 -0
  140. package/dist/lanes/workflow-contract.js +0 -0
  141. package/dist/lanes/workflow-coverage.js +0 -0
  142. package/dist/lanes/workflow.js +7 -2
  143. package/dist/mcp-graph.js +0 -0
  144. package/dist/model-purge.js +0 -0
  145. package/dist/model-record.js +0 -0
  146. package/dist/model-scaffold.js +0 -0
  147. package/dist/omc-core-lazy.js +0 -0
  148. package/dist/online-js-layout.js +0 -0
  149. package/dist/online-js-source.js +28 -28
  150. package/dist/package-tool.js +0 -0
  151. package/dist/package.js +0 -0
  152. package/dist/path-projection.js +19 -1
  153. package/dist/plan.js +0 -0
  154. package/dist/read.js +0 -0
  155. package/dist/recipes/b2-registration.js +0 -0
  156. package/dist/recipes/businessrule.js +0 -0
  157. package/dist/recipes/datarule.js +0 -0
  158. package/dist/recipes/dictionary.js +0 -0
  159. package/dist/recipes/field.js +0 -0
  160. package/dist/recipes/form.js +0 -0
  161. package/dist/recipes/list-view.js +0 -0
  162. package/dist/recipes/model.js +0 -0
  163. package/dist/recipes/online-js.js +0 -0
  164. package/dist/recipes/runtime-data.js +0 -0
  165. package/dist/recipes/shared.js +0 -0
  166. package/dist/recipes/workflow.js +0 -0
  167. package/dist/redact.js +0 -0
  168. package/dist/refusal-guidance.js +0 -0
  169. package/dist/rollback-spec.js +0 -0
  170. package/dist/rollback.js +0 -0
  171. package/dist/rule-support.js +0 -0
  172. package/dist/rule-when.js +4 -3
  173. package/dist/runtime-support.js +0 -0
  174. package/dist/script.js +0 -0
  175. package/dist/service-doctor.js +0 -0
  176. package/dist/session-manager.js +0 -0
  177. package/dist/session.js +0 -0
  178. package/dist/skills.js +0 -0
  179. package/dist/sqlite-warning-filter.js +0 -0
  180. package/dist/support.js +0 -0
  181. package/dist/token-cache.js +0 -0
  182. package/dist/tool-types.js +0 -0
  183. package/dist/tools.js +0 -0
  184. package/dist/usage-log.js +0 -0
  185. package/dist/wire.js +0 -0
  186. package/dist/workflow-support.js +0 -0
  187. package/dist/write-lease.js +0 -0
  188. package/dist/write-lock.js +0 -0
  189. package/dist/write.js +0 -0
  190. package/dist/zip.js +0 -0
  191. package/docs/images/omc-nixi/README.md +41 -41
  192. package/docs/omc-introduction.md +851 -851
  193. package/docs/tool-surface-map.md +42 -42
  194. package/package.json +3 -3
  195. package/skills/omc-acceptance-criteria.md +50 -50
  196. package/skills/omc-business-configuration.md +336 -335
  197. package/skills/omc-capabilities.md +214 -214
  198. package/skills/omc-capability-scouting.md +55 -55
  199. package/skills/omc-config-draft-review.md +175 -175
  200. package/skills/omc-five-piece-flow.md +35 -35
  201. package/skills/omc-glossary.md +86 -86
  202. package/skills/omc-refusals.md +129 -129
  203. package/skills/omc-requirement-analysis.md +161 -161
  204. package/skills/omc-requirement-vocabulary.md +61 -61
  205. package/skills/omc-start-here.md +107 -106
  206. package/skills/omc-tool-selection.md +119 -119
  207. package/skills/omc-write-hazards.md +222 -200
  208. package/dist/approval-token.js +0 -102
  209. package/dist/bench/fake-http-server.js +0 -65
  210. package/dist/fake/app.js +0 -45
  211. package/dist/fake/b2-registration.js +0 -594
  212. package/dist/fake/businessrule.js +0 -242
  213. package/dist/fake/datarule.js +0 -72
  214. package/dist/fake/dictionary.js +0 -108
  215. package/dist/fake/environment.js +0 -30
  216. package/dist/fake/field.js +0 -167
  217. package/dist/fake/form.js +0 -101
  218. package/dist/fake/index.js +0 -121
  219. package/dist/fake/list-view.js +0 -289
  220. package/dist/fake/model.js +0 -136
  221. package/dist/fake/online-js.js +0 -18
  222. package/dist/fake/report.js +0 -253
  223. package/dist/fake/routes.js +0 -47
  224. package/dist/fake/rule-lifecycle.js +0 -37
  225. package/dist/fake/runtime-data.js +0 -367
  226. package/dist/fake/state.js +0 -67
  227. package/dist/fake/workflow.js +0 -364
  228. package/dist/server.js +0 -92
File without changes
package/dist/args.js CHANGED
File without changes
package/dist/artifacts.js CHANGED
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -122,24 +122,24 @@ function freshHome(url) {
122
122
  writeFileSync(join(home, "credentials"), JSON.stringify({ eval: { username: "tester", password: "eval-face-password" } }));
123
123
  return home;
124
124
  }
125
- const LADDER_INTENT = `intentVersion: 1
126
- models:
127
- - code: eval_m1
128
- name: 评测建模
129
- fields:
130
- - code: f_title
131
- name: 标题
132
- type: SHORT_TEXT
133
- - code: f_amount
134
- name: 金额
135
- type: NUMERICAL
136
- options:
137
- scale: 3
138
- - code: f_when
139
- name: 日期
140
- type: DATE
141
- form:
142
- publish: true
125
+ const LADDER_INTENT = `intentVersion: 1
126
+ models:
127
+ - code: eval_m1
128
+ name: 评测建模
129
+ fields:
130
+ - code: f_title
131
+ name: 标题
132
+ type: SHORT_TEXT
133
+ - code: f_amount
134
+ name: 金额
135
+ type: NUMERICAL
136
+ options:
137
+ scale: 3
138
+ - code: f_when
139
+ name: 日期
140
+ type: DATE
141
+ form:
142
+ publish: true
143
143
  `;
144
144
  export const INTENT_FACE_TASKS = [
145
145
  {
@@ -163,12 +163,12 @@ export const INTENT_FACE_TASKS = [
163
163
  id: "rule-backfill-ladder",
164
164
  description: "two models via intent + DYNAMIC backfill rule + enable + trigger row + verified target readback (mixed face: intent + escape hatch)",
165
165
  async run(agent, server) {
166
- const modelsIntent = agent.writeIntent("models.yaml", `intentVersion: 1
167
- models:
168
- - code: eval_rs
169
- name: 回填源
170
- - code: eval_rt
171
- name: 回填目标
166
+ const modelsIntent = agent.writeIntent("models.yaml", `intentVersion: 1
167
+ models:
168
+ - code: eval_rs
169
+ name: 回填源
170
+ - code: eval_rt
171
+ name: 回填目标
172
172
  `);
173
173
  await agent.intentValidate(modelsIntent);
174
174
  const { plan, planFile } = await agent.intentPlan(modelsIntent);
@@ -209,27 +209,27 @@ models:
209
209
  id: "input-error-recovery",
210
210
  description: "bad intent -> validate exit 10 with source location -> fix -> plan/apply -> idempotent re-plan/re-apply with zero duplicate wires",
211
211
  async run(agent, server) {
212
- const bad = agent.writeIntent("intent.yaml", `intentVersion: 1
213
- models:
214
- - code: eval_idem
215
- name: 幂等模型
216
- fields:
217
- - code: f_bad
218
- name: 坏类型
219
- type: HOLOGRAM
212
+ const bad = agent.writeIntent("intent.yaml", `intentVersion: 1
213
+ models:
214
+ - code: eval_idem
215
+ name: 幂等模型
216
+ fields:
217
+ - code: f_bad
218
+ name: 坏类型
219
+ type: HOLOGRAM
220
220
  `);
221
221
  const rejected = await agent.run(["intent", "validate", bad]);
222
222
  ok(rejected.code === 10 && rejected.envelope.error?.code === "invalid-input", `expected invalid-input exit 10, got ${String(rejected.code)}`);
223
223
  const first = rejected.envelope.result.errors[0];
224
224
  ok(first.code === "intent-field-type-unknown" && typeof first.line === "number", `located error: ${JSON.stringify(first).slice(0, 200)}`);
225
- const fixed = agent.writeIntent("intent.yaml", `intentVersion: 1
226
- models:
227
- - code: eval_idem
228
- name: 幂等模型
229
- fields:
230
- - code: f_bad
231
- name: 改正的字段
232
- type: SHORT_TEXT
225
+ const fixed = agent.writeIntent("intent.yaml", `intentVersion: 1
226
+ models:
227
+ - code: eval_idem
228
+ name: 幂等模型
229
+ fields:
230
+ - code: f_bad
231
+ name: 改正的字段
232
+ type: SHORT_TEXT
233
233
  `);
234
234
  const validated = await agent.intentValidate(fixed);
235
235
  ok(validated.valid === true, `fixed validate: ${JSON.stringify(validated).slice(0, 200)}`);
package/dist/bench/run.js CHANGED
File without changes
File without changes
File without changes
package/dist/bi-wire.js CHANGED
File without changes
File without changes
package/dist/call.js CHANGED
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
package/dist/cli.js CHANGED
@@ -13,6 +13,7 @@ import { redactText } from "./redact.js";
13
13
  import { readArgsFile, readStdinArgs, runToolCall, validateAgentPaths } from "./call.js";
14
14
  import { BATCH_MAX_ENTRIES, runBatchCall } from "./batch-call.js";
15
15
  import { cmdModelScaffold } from "./model-scaffold.js";
16
+ import { cmdFieldAdd } from "./field-add.js";
16
17
  import { cmdModelPurge } from "./model-purge.js";
17
18
  import { environmentWritable, requiresProductionConfirmation } from "./environment-policy.js";
18
19
  import { CLI_EXIT_CODES, classifyResult, envelope, errorEnvelope, installCodePageSafeStderr, refusalText, requestId } from "./cli-contract.js";
@@ -1733,6 +1734,35 @@ async function main() {
1733
1734
  const rest = ["form-default", ...(options.schema ? ["--schema", options.schema] : []), ...(options.schemaCode ? ["--schema-code", options.schemaCode] : []), ...(options.field ? ["--field", options.field] : []), ...(options.default !== undefined ? ["--default", options.default] : []), ...(options.env ? ["--env", options.env] : []), ...(options.environment ? ["--environment", options.environment] : []), ...(options.apply ? ["--apply"] : [])];
1734
1735
  commandExitCode = await cmdFormDefault(rest);
1735
1736
  });
1737
+ command
1738
+ .command("field-add")
1739
+ .option("--schema <schemaCode>", "存量模型 code")
1740
+ .option("--schema-code <schemaCode>")
1741
+ .option("--code <schemaCode>")
1742
+ .option("--fields <json>", "JSON 数组,每条是一个 field.create 参数(choices/字典 options 原样透传)")
1743
+ .option("--fields-json <json>")
1744
+ .option("--env <environment>")
1745
+ .option("--environment <environment>")
1746
+ .option("--no-form", "跳过表单重建")
1747
+ .option("--no-list", "跳过列表加列")
1748
+ .option("--force-publish", "有字段失败也发布(默认停在发布前)")
1749
+ .option("--apply")
1750
+ .addHelpText("after", `\n存量模型加字段一条命令:建字段 → 发布字段 → 重建表单 → 列表追加列(增量合并,保留既有列/条件/排序)。\n默认 dry-run 只列计划;--apply 执行。幂等:字段已存在记 skipped 继续。\n子表不属于本命令(存量挂子表走 field.create CHILD_TABLE + 子列,见 model-scaffold)。\n\nr30/r31 探针实证:同一条链手工要走 4 次写调用 + 各自回读。\n`)
1751
+ .action(async (options) => {
1752
+ const rest = ["field-add",
1753
+ ...(options.schema ? ["--schema", options.schema] : []),
1754
+ ...(options.schemaCode ? ["--schema-code", options.schemaCode] : []),
1755
+ ...(options.code ? ["--code", options.code] : []),
1756
+ ...(options.fields !== undefined ? ["--fields", options.fields] : []),
1757
+ ...(options.fieldsJson !== undefined ? ["--fields-json", options.fieldsJson] : []),
1758
+ ...(options.env ? ["--env", options.env] : []),
1759
+ ...(options.environment ? ["--environment", options.environment] : []),
1760
+ ...(options.form === false ? ["--no-form"] : []),
1761
+ ...(options.list === false ? ["--no-list"] : []),
1762
+ ...(options.forcePublish ? ["--force-publish"] : []),
1763
+ ...(options.apply ? ["--apply"] : [])];
1764
+ commandExitCode = await cmdFieldAdd(rest);
1765
+ });
1736
1766
  command.command("script [file]").option("--env <environment>", "行内无 env 时的默认环境").option("--continue", "某行失败后继续执行剩余行(默认停在首个失败)").description("NDJSON 多调脚本:每行 {\"tool\":\"<名称>\",\"args\":{...},\"env\":\"<环境>\",\"dryRun\":true}(# 注释/空行忽略;file 为 - 读 stdin);一个进程一次会话跑完全部,逐行输出 {index,tool,exitCode,result}").action(async (file, options) => {
1737
1767
  const { runScript, parseScript, readScriptInput } = await import("./script.js");
1738
1768
  let lines;
@@ -1906,7 +1936,7 @@ main().then((code) => {
1906
1936
  // envelope (or, for `env --json`, their own JSON) and were printing a second
1907
1937
  // empty envelope after it (live 2026-10-23: `omc rollback apply` answered two
1908
1938
  // lines, violating the one-envelope stdout contract).
1909
- const ownsStdout = ["call", "script", "artifact", "commands", "describe", "contract", "capabilities", "capability", "next", "usage", "intent", "help", "form-default", "compute", "rule-when", "approval-flow", "model-scaffold", "model-purge", "rollback", "env", "graph", "--version", "-V"];
1939
+ const ownsStdout = ["call", "script", "artifact", "commands", "describe", "contract", "capabilities", "capability", "next", "usage", "intent", "help", "form-default", "compute", "rule-when", "approval-flow", "model-scaffold", "field-add", "model-purge", "rollback", "env", "graph", "--version", "-V"];
1910
1940
  if (ownsStdout.includes(argv[2] ?? "")) {
1911
1941
  completeUsage(code);
1912
1942
  finish(code);
File without changes
File without changes
File without changes
File without changes
package/dist/compute.js CHANGED
File without changes
File without changes
package/dist/context.js CHANGED
File without changes
File without changes
File without changes
File without changes
File without changes
package/dist/coverage.js CHANGED
File without changes
File without changes
File without changes
package/dist/discovery.js CHANGED
File without changes
package/dist/endpoints.js CHANGED
File without changes
File without changes
File without changes
@@ -0,0 +1,314 @@
1
+ /**
2
+ * `omc field-add` — 存量模型加字段一条命令:建字段 → 发布字段 → 重建表单 → 列表加列。
3
+ *
4
+ * r30/r31 两轮探针实证同一条手工链(4 次写调用 + 各自回读):
5
+ * field.create → field.publish → form.publish(rebuild) → listview.configure。
6
+ * 「给既有模型加一个字段并显示到列表」是变更类需求的最高频动作,值得一条命令。
7
+ *
8
+ * 语义(与 model-scaffold 同族约定):
9
+ * - **幂等**:字段已存在(duplicate)→ 记 skipped 继续(补建场景不该整批失败)。
10
+ * - **不猜**:--fields 每项原样透传给 field.create(只补 schemaCode)。
11
+ * - **失败不发布**:有真失败默认停在发布前(--force-publish 强发)。
12
+ * - **列表增量**:listview.configure 默认合并语义(保留既有列/条件),只把新字段
13
+ * 追加进列集——不需要先读旧列(r31 实证 conditions 原样保留)。
14
+ * - 子表不属于本拼装器(存量模型挂子表有独立顺序),--fields 带 subFields 直接拒。
15
+ */
16
+ import { argvValue } from "./args.js";
17
+ import { runToolCall } from "./call.js";
18
+ import { CLI_EXIT_CODES, classifyResult, envelope, refusalText, requestId } from "./cli-contract.js";
19
+ import { writeEnvelope } from "./cli-output.js";
20
+ /**
21
+ * 字典简写展开(r30/r36 实证:字典字段要先 names 查 id、records 取记录、
22
+ * 再拼 optionsSet.dictionary 三段 4–5 调;`dictCode` 把它折进 field-add 一步):
23
+ * `{"code":"x","propertyType":"DROPDOWN_BOX","dictCode":"purchase_coop_status"}`
24
+ * → 内部解析 checkedDictionary(id)+checkedDictionaryCode+useDictionariesData(records)
25
+ * 按 dictionary-backed-single-select recipe 形状组装 options.optionsSet。
26
+ */
27
+ async function expandDictShorthand(fields, environment, contextForTest, request) {
28
+ const problems = [];
29
+ const expanded = [];
30
+ const call = async (tool, args) => {
31
+ const outcome = await runToolCall(tool, {
32
+ args,
33
+ requestId: request,
34
+ ...(environment !== undefined ? { environment } : {}),
35
+ ...(contextForTest !== undefined ? { context: contextForTest } : {}),
36
+ print: () => undefined,
37
+ });
38
+ return outcome.exitCode === 0 ? outcome.result : null;
39
+ };
40
+ const out = [];
41
+ for (const field of fields) {
42
+ const dictCode = field.dictCode;
43
+ if (typeof dictCode !== "string" || dictCode.length === 0) {
44
+ out.push({ ...field });
45
+ continue;
46
+ }
47
+ const rest = { ...field };
48
+ delete rest.dictCode;
49
+ // dictionary.list 是真假两平台都稳的清单面(names 的 getDictionaryIdAndNames
50
+ // 在 fake 上未实现);list 的回执可能是 {dictionaries:{content:[...]}} 分页
51
+ // 或裸数组,两种都吃。
52
+ const listResult = await call("dictionary.list", { limit: 200 });
53
+ const listPayload = listResult === null ? null : (listResult.dictionaries ?? listResult.names ?? listResult.content ?? listResult);
54
+ const names = Array.isArray(listPayload)
55
+ ? listPayload
56
+ : (Array.isArray(listPayload?.content) ? listPayload.content : []);
57
+ const hit = names.find((row) => String(row.code ?? row.dictionaryCode ?? "") === dictCode);
58
+ if (hit === undefined) {
59
+ problems.push(`${String(field.code)}: 字典 ${dictCode} 不存在(dictionary.names 未命中)——先 dictionary.save 或修正 dictCode`);
60
+ continue;
61
+ }
62
+ const dictId = String(hit.id ?? hit.dictionaryId ?? "");
63
+ const recordsResult = await call("dictionary.records", { code: dictCode, enabledOnly: true });
64
+ const recordsRaw = recordsResult === null ? [] : (recordsResult.records ?? {});
65
+ const recordRows = (Array.isArray(recordsRaw) ? recordsRaw : (recordsRaw.content ?? []));
66
+ if (recordRows.length === 0) {
67
+ problems.push(`${String(field.code)}: 字典 ${dictCode} 无启用记录——先补 dictionary.records`);
68
+ continue;
69
+ }
70
+ const useDictionariesData = recordRows.map((row) => ({ id: String(row.id ?? ""), code: String(row.code ?? ""), name: String(row.name ?? row.code ?? "") }));
71
+ out.push({
72
+ ...rest,
73
+ options: {
74
+ optionsSet: {
75
+ optionsType: "dictionary",
76
+ dictionary: { checkedDictionary: dictId, checkedDictionaryCode: dictCode, useDictionariesData },
77
+ },
78
+ },
79
+ });
80
+ expanded.push(String(field.code));
81
+ }
82
+ return { fields: out, expanded, problems };
83
+ }
84
+ export function validateFields(schemaCode, fields) {
85
+ const problems = [];
86
+ const seen = new Set();
87
+ for (const [index, field] of fields.entries()) {
88
+ const at = `fields[${index}]`;
89
+ const code = field.code;
90
+ if (typeof code !== "string" || code.length === 0)
91
+ problems.push(`${at}.code 必填`);
92
+ else if (seen.has(code))
93
+ problems.push(`${at}.code 与前面的字段重复(${code})`);
94
+ else
95
+ seen.add(code);
96
+ if (typeof field.name !== "string" || field.name.length === 0)
97
+ problems.push(`${at}.name 必填`);
98
+ if (typeof field.propertyType !== "string" || field.propertyType.length === 0)
99
+ problems.push(`${at}.propertyType 必填`);
100
+ if (field.subFields !== undefined || field.childFields !== undefined)
101
+ problems.push(`${at}: field-add 不处理子表(存量模型挂子表先 field.create CHILD_TABLE 再建子列,见 model-scaffold 的子表序)`);
102
+ }
103
+ if (problems.length === 0 && schemaCode.length === 0)
104
+ problems.push("--schema 必填");
105
+ return problems;
106
+ }
107
+ export async function cmdFieldAdd(args, contextForTest) {
108
+ const request = requestId();
109
+ const flag = (name) => argvValue(args, name);
110
+ const schemaCode = flag("--schema") ?? flag("--schema-code") ?? flag("--code");
111
+ const fieldsJson = flag("--fields") ?? flag("--fields-json");
112
+ const environment = flag("--env") ?? flag("--environment");
113
+ const apply = args.includes("--apply");
114
+ const publishForm = !args.includes("--no-form");
115
+ const addToList = !args.includes("--no-list");
116
+ const forcePublish = args.includes("--force-publish");
117
+ const usage = [
118
+ "用法:omc field-add --schema <模型code> --fields '<JSON 数组>' [--apply]",
119
+ " --fields 每项是一个 field.create 的参数:{\"code\",\"name\",\"propertyType\",...}(choices/字典 options 原样透传);",
120
+ " 字典简写:{\"dictCode\":\"<字典code>\"} 代替手拼 options.optionsSet(内部解析字典 id+启用记录,recipe 同形状)。",
121
+ " 默认 dry-run;--apply 依次执行 建字段 → 发布字段 → 重建表单 → 列表追加列。",
122
+ " 幂等:字段已存在记 skipped 继续;有真失败默认不发布/不重建(--force-publish 强发)。",
123
+ " --no-form 跳过表单重建;--no-list 跳过列表加列(默认两者都做)。",
124
+ ].join("\n");
125
+ const invalid = (message) => {
126
+ writeEnvelope(envelope("error", {}, request, { code: "invalid-input", message: `${message}\n${usage}`, retryable: false }));
127
+ return CLI_EXIT_CODES.invalidInput;
128
+ };
129
+ if (!schemaCode)
130
+ return invalid("--schema 必填(存量模型 code)");
131
+ if (fieldsJson === undefined)
132
+ return invalid("--fields 必填(JSON 数组)");
133
+ let fields;
134
+ try {
135
+ const parsed = JSON.parse(fieldsJson);
136
+ if (!Array.isArray(parsed) || parsed.length === 0)
137
+ throw new Error("--fields 必须是非空 JSON 数组");
138
+ fields = parsed;
139
+ }
140
+ catch (error) {
141
+ return invalid(`--fields 解析失败:${error instanceof Error ? error.message : String(error)}`);
142
+ }
143
+ const problems = validateFields(schemaCode, fields);
144
+ if (problems.length > 0)
145
+ return invalid(`--fields 有 ${problems.length} 处问题:\n - ${problems.join("\n - ")}`);
146
+ // 字典简写展开(dictCode → optionsSet.dictionary,含 id/记录解析)——展开失败
147
+ // 在任何写之前拒绝(名字对不上/无启用记录)。
148
+ let dictExpanded = 0;
149
+ if (fields.some((field) => typeof field.dictCode === "string")) {
150
+ const expandedResult = await expandDictShorthand(fields, environment, contextForTest, request);
151
+ if (expandedResult.problems.length > 0)
152
+ return invalid(`dictCode 展开失败(未写入):\n - ${expandedResult.problems.join("\n - ")}`);
153
+ fields = expandedResult.fields;
154
+ dictExpanded = expandedResult.expanded.length;
155
+ }
156
+ const options = {
157
+ schemaCode, fields, apply, publishForm, addToList, forcePublish,
158
+ ...(environment !== undefined ? { environment } : {}),
159
+ };
160
+ // 零 wire 形状预检(与 model-scaffold 同层防线):dry-run 走本地能力门。
161
+ const preflightProblems = [];
162
+ for (const field of fields) {
163
+ const probe = await runToolCall("field.create", {
164
+ args: { schemaCode, ...field },
165
+ requestId: request,
166
+ dryRun: true,
167
+ ...(environment !== undefined ? { environment } : {}),
168
+ ...(contextForTest !== undefined ? { context: contextForTest } : {}),
169
+ print: () => undefined,
170
+ });
171
+ const status = String(probe.result.status ?? "ok");
172
+ if (probe.exitCode !== 0 || (status !== "dry-run" && status !== "ok"))
173
+ preflightProblems.push({ code: String(field.code), message: refusalText(probe.result) });
174
+ }
175
+ if (preflightProblems.length > 0) {
176
+ writeEnvelope(envelope("error", {
177
+ status: "refused",
178
+ blocker: "field-add-preflight-failed",
179
+ schema: schemaCode,
180
+ written: false,
181
+ failed: preflightProblems.length,
182
+ preflightProblems,
183
+ message: `有 ${preflightProblems.length} 个字段的预检没过,**尚未写入任何东西**。` +
184
+ `按 message 改 --fields 后重跑(常见:NUMERICAL 缺 options.scale、自定义选项应用顶层 choices)。`,
185
+ }, request, { code: "policy-refused", message: "字段形状预检未通过(未写入)", retryable: false }));
186
+ return CLI_EXIT_CODES.policyRefused;
187
+ }
188
+ if (!apply) {
189
+ writeEnvelope(envelope("ok", {
190
+ status: "dry-run",
191
+ schema: schemaCode,
192
+ fields: fields.map((field) => String(field.code)),
193
+ ...(dictExpanded > 0 ? { dictExpanded } : {}),
194
+ steps: [
195
+ `field.create ×${fields.length}`,
196
+ "field.publish(发布失败/有字段失败则停)",
197
+ ...(publishForm ? [`form.publish ${schemaCode} --rebuild(重建让新字段进默认表单)`] : []),
198
+ ...(addToList ? [`listview.configure ${schemaCode} 增量追加列(保留既有列与条件)`] : []),
199
+ ],
200
+ preflight: { checked: fields.length, passed: true },
201
+ note: "dry-run 只列计划;已对每个字段跑过本地形状预检(零 wire)。--apply 依次执行全部步骤。",
202
+ }, request));
203
+ return CLI_EXIT_CODES.ok;
204
+ }
205
+ const steps = [];
206
+ const lastSummaries = [];
207
+ const call = async (tool, toolArgs) => {
208
+ const outcome = await runToolCall(tool, {
209
+ args: toolArgs,
210
+ requestId: request,
211
+ ...(environment !== undefined ? { environment } : {}),
212
+ ...(contextForTest !== undefined ? { context: contextForTest } : {}),
213
+ print: () => undefined,
214
+ });
215
+ const result = outcome.result;
216
+ if (outcome.exitCode === 0 && typeof result.summary === "string" && result.summary.length > 0)
217
+ lastSummaries.push(String(result.summary));
218
+ return { ok: outcome.exitCode === 0, result };
219
+ };
220
+ steps.push({ step: "preflight", status: "ok", detail: { checked: fields.length, wrote: false } });
221
+ // 1) 建字段:duplicate 记 skipped,其余失败继续跑完(一次看全)。
222
+ const failedFields = [];
223
+ const createdCodes = [];
224
+ for (const field of fields) {
225
+ const outcome = await call("field.create", { schemaCode, ...field });
226
+ const message = String(outcome.result.message ?? "");
227
+ if (outcome.ok) {
228
+ steps.push({ step: "field.create", status: "ok", detail: { schemaCode, code: String(field.code) } });
229
+ createdCodes.push(String(field.code));
230
+ }
231
+ else if (/已存在|duplicate/i.test(message)) {
232
+ steps.push({ step: "field.create", status: "skipped", detail: { schemaCode, code: String(field.code), reason: "already-present" } });
233
+ createdCodes.push(String(field.code)); // 已存在同样在发布面上,列表加列依然有意义
234
+ }
235
+ else {
236
+ const reason = refusalText(outcome.result);
237
+ failedFields.push({ code: String(field.code), message: reason });
238
+ steps.push({ step: "field.create", status: "failed", detail: { schemaCode, code: String(field.code), message: reason } });
239
+ }
240
+ }
241
+ // 2) 发布字段:半成品不发布。
242
+ const publishingBlocked = failedFields.length > 0 && !forcePublish;
243
+ let fieldPublishOk = false;
244
+ if (publishingBlocked) {
245
+ steps.push({
246
+ step: "field.publish",
247
+ status: "skipped",
248
+ detail: { reason: "有字段创建失败,默认不发布(用 --force-publish 强制)", failedFields: failedFields.map((entry) => entry.code) },
249
+ });
250
+ }
251
+ else {
252
+ const published = await call("field.publish", { schemaCode });
253
+ fieldPublishOk = published.ok;
254
+ steps.push({
255
+ step: "field.publish",
256
+ status: published.ok ? "ok" : "failed",
257
+ detail: published.ok ? { schemaCode } : { schemaCode, message: String(published.result.message ?? "") },
258
+ });
259
+ }
260
+ // 3) 表单重建:发布被挡/失败时同样不做(会把缺字段的布局烤进表单)。
261
+ if (publishForm && fieldPublishOk && !publishingBlocked) {
262
+ const published = await call("form.publish", { schemaCode, rebuild: true });
263
+ steps.push({
264
+ step: "form.publish",
265
+ status: published.ok ? "ok" : "failed",
266
+ detail: published.ok ? { schemaCode, rebuild: true } : { schemaCode, rebuild: true, message: String(published.result.message ?? "") },
267
+ });
268
+ }
269
+ else if (publishForm) {
270
+ steps.push({
271
+ step: "form.publish",
272
+ status: "skipped",
273
+ detail: { reason: publishingBlocked ? "有字段创建失败,默认不重建(--force-publish 可强发)" : "字段发布失败" },
274
+ });
275
+ }
276
+ // 4) 列表增量加列:默认合并语义保留既有列/条件/排序(r31 实证);只有字段全部
277
+ // 失败才跳过(部分成功时把成功的列加上去仍有意义)。
278
+ if (addToList && createdCodes.length > 0 && !publishingBlocked) {
279
+ const configured = await call("listview.configure", { schemaCode, fieldCodes: createdCodes });
280
+ steps.push({
281
+ step: "listview.configure",
282
+ status: configured.ok ? "ok" : "failed",
283
+ detail: configured.ok
284
+ ? { schemaCode, appended: createdCodes, semantics: "增量合并:保留既有列/条件/排序" }
285
+ : { schemaCode, fieldCodes: createdCodes, message: String(configured.result.message ?? "") },
286
+ });
287
+ }
288
+ else if (addToList) {
289
+ steps.push({
290
+ step: "listview.configure",
291
+ status: "skipped",
292
+ detail: { reason: createdCodes.length === 0 ? "没有字段落到发布面" : "有字段创建失败,默认不动列表(--force-publish 可强发)" },
293
+ });
294
+ }
295
+ const failedSteps = steps.filter((step) => step.status === "failed");
296
+ const status = failedSteps.length > 0 ? "partial" : "ok";
297
+ const result = {
298
+ status,
299
+ schema: schemaCode,
300
+ fieldCount: fields.length,
301
+ created: steps.filter((step) => step.step === "field.create" && step.status === "ok").length,
302
+ skipped: steps.filter((step) => step.step === "field.create" && step.status === "skipped").length,
303
+ failed: failedFields.length,
304
+ ...(failedFields.length > 0 ? { failedFields } : {}),
305
+ ...(failedSteps.length > 0 ? { failedSteps } : {}),
306
+ ...(lastSummaries.length > 0 ? { summaries: lastSummaries } : {}),
307
+ steps,
308
+ folded: "field.create → field.publish → form.publish(rebuild) → listview.configure(增量) 4 写调合成 1 条命令",
309
+ };
310
+ const classified = classifyResult(status === "partial" ? { ...result, requested: steps.length, succeeded: steps.length - failedSteps.length } : result);
311
+ writeEnvelope(envelope(classified.status, result, request, classified.error));
312
+ return status === "partial" ? 1 : 0;
313
+ }
314
+ //# sourceMappingURL=field-add.js.map
File without changes
File without changes
File without changes
File without changes
@@ -1147,6 +1147,15 @@ export async function executeFormDraftWrite(session, args) {
1147
1147
  }
1148
1148
  : {}),
1149
1149
  ...(args.rebuild === true && previousAttributes ? { rebuildPreservedControls } : {}),
1150
+ // r36(2026-11-01):rebuild 后草稿与重建前逐字节一致时明示——rebuild 的
1151
+ // 语义就是"按已发布字段重搭",一致通常意味着预期的新控件没进来(字段未
1152
+ // 发布/写错槽位),静默 no-op 会让"新增字段后刷新表单"看起来成功实则没变。
1153
+ ...(args.rebuild === true && previousAttributes !== undefined && typeof attributes === "string" && attributes.trim() === JSON.stringify(previousAttributes).trim()
1154
+ ? {
1155
+ rebuildUnchanged: true,
1156
+ rebuildNote: "rebuild 后草稿与重建前完全一致——若你预期新增控件(新字段上表单),检查:① 字段是否已 field.publish;② 写入槽位 sheetCode 是否正确(省略=模型 code 槽位);r36 实测同序列下重建静默 no-op。",
1157
+ }
1158
+ : {}),
1150
1159
  },
1151
1160
  };
1152
1161
  }
File without changes