@stalfh233/omc-cli 0.5.11 → 0.5.12

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.
@@ -256,7 +256,7 @@ export function classifyResult(result) {
256
256
  const suffix = candidates.length > 0 ? `;did you mean: ${candidates.join("、")}` : ";用 `omc commands --json --compact --lane <lane>` 或 `omc next` 定位工具";
257
257
  return { status: "error", error: withNext({ code: "invalid-input", message: `unknown tool ${String(result.tool ?? "")}${suffix}`, retryable: false }), exitCode: CLI_EXIT_CODES.invalidInput };
258
258
  }
259
- if (result.blocker === "tool-input-invalid")
259
+ if (result.blocker === "tool-input-invalid" || result.blocker === "tool-input-unknown-keys")
260
260
  return {
261
261
  status: "error",
262
262
  error: { ...withNext({ code: "invalid-input", message: String(result.message ?? "tool input is invalid"), retryable: false }), nextCommand: `omc describe ${String(result.tool ?? "<tool>")}` },
package/dist/cli.js CHANGED
@@ -42,7 +42,7 @@ import { absencePointer } from "./capability-absences.js";
42
42
  import { intentConfigRoot, readIntentFile, validateIntentText } from "./intent/cli.js";
43
43
  import { intentSchemaBlock, intentExample, intentExampleIds } from "./intent/schema.js";
44
44
  import { planIntent, readbackIntent, applyIntent, intentWarnings } from "./intent/plan-apply.js";
45
- import { listSkills, readSkill, readSkillOutline, readSkillSection, searchSkills } from "./skills.js";
45
+ import { listSkills, readSkill, readSkillOutline, readSkillSection, searchSkills, suggestHelpTopics } from "./skills.js";
46
46
  import { parseToolInput, ToolInputError } from "./tool-types.js";
47
47
  import { WireError, wireCall } from "./wire.js";
48
48
  import { DEFAULT_PULL_CONCURRENCY, GraphIndexAbsentError, GraphPullLockHeldError, MAX_PULL_CONCURRENCY, impactGraph, publishGraphPull, queryGraphReferences, readGraphStatus, searchGraphRules, } from "./graph.js";
@@ -204,7 +204,7 @@ async function cmdDoctor(store, environment, serviceCode) {
204
204
  const errcode = error instanceof WireError ? error.errcode : undefined;
205
205
  if (errcode === 109001 || /许可证|license/i.test(error.message)) {
206
206
  ok = false;
207
- steps.push(`许可证 FAIL 已过期(平台拒绝 apppackage/list:${String(errcode ?? "")})——续期或更换环境;否则几乎全部读面与所有写面都会被 109001 拒绝`);
207
+ steps.push(`许可证 FAIL 已过期(平台拒绝 apppackage/list:${String(errcode ?? "")})——续期或更换环境。注意:109001 是**分面拦截**不是全环境不可用(r40 实证:报表/集成/部分读面被拒,模型/字段/数据面仍可交付)——不要因本项 FAIL 一刀切停步,按需逐面探测并用最小写验证`);
208
208
  }
209
209
  else {
210
210
  ok = false;
@@ -1361,7 +1361,8 @@ function cmdHelpSearch(topic, query, transport, searchMode, limit) {
1361
1361
  if (singleSection !== undefined) {
1362
1362
  const sliced = readSkillSection(topic, singleSection);
1363
1363
  if (sliced === undefined) {
1364
- const message = `未知 topic "${topic}";可用 topic:${topics.map((entry) => entry.name).join("、")}(命令清单见 omc help commands)${commandPointer}`;
1364
+ const suggestions = suggestHelpTopics(topic);
1365
+ const message = `未知 topic "${topic}";可用 topic:${topics.map((entry) => entry.name).join("、")}(命令清单见 omc help commands)${commandPointer}${suggestions.length > 0 ? `;你可能要找:${suggestions.join("、")}` : ""}`;
1365
1366
  console.error(`help: ${message}`);
1366
1367
  return emitInvalid(transport, { message });
1367
1368
  }
@@ -1382,7 +1383,8 @@ function cmdHelpSearch(topic, query, transport, searchMode, limit) {
1382
1383
  }
1383
1384
  const document = readSkill(topic);
1384
1385
  if (document === undefined) {
1385
- const message = `未知 topic "${topic}";可用 topic:${topics.map((entry) => entry.name).join("、")}(命令清单见 omc help commands)${commandPointer}`;
1386
+ const suggestions = suggestHelpTopics(topic);
1387
+ const message = `未知 topic "${topic}";可用 topic:${topics.map((entry) => entry.name).join("、")}(命令清单见 omc help commands)${commandPointer}${suggestions.length > 0 ? `;你可能要找:${suggestions.join("、")}` : ""}`;
1386
1388
  console.error(`help: ${message}`);
1387
1389
  return emitInvalid(transport, { message });
1388
1390
  }
@@ -1423,6 +1423,23 @@ async function refuseRequiredOnChildTableFields(session, schemaCode, template, t
1423
1423
  * same edge). This is the "read back, then send the delta" path — without it a
1424
1424
  * subset silently REPLACED the whole activity list and dropped nodes.
1425
1425
  */
1426
+ function refusePartialBtnConfigList(activities) {
1427
+ // r32 实证(backlog 清算批):btnConfigList 局部行=整行替换——只传 {code, remark}
1428
+ // 会把 enabled 静默置 false、commentPermission 置 null(驳回意见必填丢失)。
1429
+ // fail-closed:按钮行不带显式 enabled 一律拒绝,指路 hazards 的整行回写法。
1430
+ for (const activity of activities) {
1431
+ const rows = activity.btnConfigList;
1432
+ if (!Array.isArray(rows))
1433
+ continue;
1434
+ const partial = rows.filter((row) => row !== null && typeof row === "object" && !("enabled" in row));
1435
+ if (partial.length > 0)
1436
+ throw new ToolRefusalError({
1437
+ status: "refused",
1438
+ blocker: "workflow-btnconfig-partial-row",
1439
+ message: `workflow.update: 节点 ${String(activity.activityCode ?? activity.id ?? "")} 的 btnConfigList 存在不带 enabled 的按钮行(${partial.map((row) => String(row.code ?? "?")).join("、")})——该平台按**整行替换**处理,缺省键会被静默重置(enabled→false、commentPermission→null)。先 workflow.get_published/get_draft 取该按钮整行,改目标属性后整条回写(见 omc help omc-write-hazards --section 按钮)。`,
1440
+ });
1441
+ }
1442
+ }
1426
1443
  function mergeCallerDelta(template, draft, options = {}) {
1427
1444
  const { routes: routesRaw, rules: rulesRaw, activities: _raw, edges: edgesRaw, ...rest } = template;
1428
1445
  // `routes` and `edges` are the recipe spellings; `rules` is the platform DTO
@@ -1486,6 +1503,7 @@ function mergeCallerDelta(template, draft, options = {}) {
1486
1503
  return { ...activity, ...override };
1487
1504
  });
1488
1505
  const newActivities = [...callerByOriginal.values()];
1506
+ refusePartialBtnConfigList([...mergedActivities, ...newActivities]);
1489
1507
  // A caller-only code that arrived WITHOUT a type has no draft row to inherit
1490
1508
  // from — refuse it here (the lint allows type-less permission-shape rows
1491
1509
  // precisely because they are expected to match an existing node).
@@ -1595,6 +1613,8 @@ function normalizeWorkflowTemplate(template, draft, options = {}) {
1595
1613
  // comment); give every changed/new node a fresh code, and remap caller routes
1596
1614
  // so they follow.
1597
1615
  const reconciled = rawCaller ? reconcileCallerActivityCodes(rawCaller, draftActivities, options) : undefined;
1616
+ if (rawCaller !== undefined)
1617
+ refusePartialBtnConfigList(rawCaller);
1598
1618
  const activities = reconciled?.activities;
1599
1619
  const codeMap = reconciled?.codeMap ?? new Map();
1600
1620
  const remapCode = (value) => codeMap.get(String(value ?? "")) ?? value;
package/dist/skills.js CHANGED
@@ -48,6 +48,57 @@ export function listSkills() {
48
48
  return { name, description };
49
49
  });
50
50
  }
51
+ /**
52
+ * r43 后 backlog:`omc help 编号` 曾零命中(高频中文业务词不是 topic 名)。
53
+ * 双路建议:①精选同义词表(高频业务词→topic,r24–r43 探针实测检索失败样本)
54
+ * ②全 topic 节标题子串扫描(自维护——节名改了建议跟着变)。
55
+ */
56
+ const HELP_TOPIC_SYNONYMS = Object.freeze({
57
+ 编号: ["omc-capabilities"],
58
+ 序列号: ["omc-capabilities"],
59
+ 单号: ["omc-capabilities"],
60
+ 流水号: ["omc-capabilities"],
61
+ 自动编号: ["omc-capabilities"],
62
+ 审批: ["omc-five-piece-flow"],
63
+ 流程: ["omc-five-piece-flow"],
64
+ 回写: ["omc-business-configuration"],
65
+ 跨模型: ["omc-business-configuration"],
66
+ 规则: ["omc-business-configuration"],
67
+ 字典: ["omc-business-configuration"],
68
+ 权限: ["omc-business-configuration"],
69
+ 视图: ["omc-write-hazards"],
70
+ 过滤: ["omc-write-hazards"],
71
+ 条件: ["omc-write-hazards"],
72
+ 删除: ["omc-write-hazards"],
73
+ 清理: ["omc-write-hazards"],
74
+ 子表: ["omc-write-hazards"],
75
+ 表单: ["omc-write-hazards"],
76
+ 验收: ["omc-acceptance-criteria"],
77
+ 入门: ["omc-start-here"],
78
+ 拒绝: ["omc-refusals"],
79
+ });
80
+ export function suggestHelpTopics(query) {
81
+ if (query === undefined || query.length === 0)
82
+ return [];
83
+ const hits = new Set(HELP_TOPIC_SYNONYMS[query] ?? []);
84
+ for (const skill of listSkills()) {
85
+ if (skill.name.includes(query) || (skill.description ?? "").includes(query))
86
+ hits.add(skill.name);
87
+ }
88
+ // 节标题扫描:`omc help <topic> --section "<heading>"` 直达
89
+ for (const name of listSkills().map((skill) => skill.name)) {
90
+ const outline = readSkillOutline(name);
91
+ if (outline === undefined)
92
+ continue;
93
+ for (const section of outline.sections) {
94
+ if (section.heading.includes(query)) {
95
+ hits.add(`omc help ${name} --section "${section.heading}"`);
96
+ break;
97
+ }
98
+ }
99
+ }
100
+ return [...hits].slice(0, 5);
101
+ }
51
102
  /** Read one methodology topic for `omc help <topic>`. */
52
103
  export function readSkill(name) {
53
104
  if (!/^[a-z0-9-]+$/.test(name))
@@ -91,6 +91,9 @@ function applyInputAliases(tool, input) {
91
91
  continue;
92
92
  normalized ??= { ...input };
93
93
  normalized[canonical] = input[hit];
94
+ // 同义词键就地删除:归一后的输入只留规范键——写面未知键 fail-closed
95
+ // (r43 后)不把已消费的同义词误报成未知键。
96
+ delete normalized[hit];
94
97
  }
95
98
  return normalized ?? input;
96
99
  }
@@ -111,8 +114,53 @@ function keyRenameHint(issuePath, input, tool) {
111
114
  // "你传的是 schemaCode", which pointed at the wrong key.
112
115
  return `(该对象在本工具里的键是 ${key},你传的是 ${synonyms.join("/")};键名对照见 omc-tool-selection 的 Key-name table)`;
113
116
  }
117
+ /** Cheap closest-key suggestion: exact→prefix→shared-start bigrams, threshold keeps it honest. */
118
+ function closestSchemaKey(key, schemaKeys) {
119
+ if (schemaKeys.has(key))
120
+ return key;
121
+ let best;
122
+ let bestScore = 0;
123
+ for (const candidate of schemaKeys) {
124
+ const lower = key.toLowerCase();
125
+ const candLower = candidate.toLowerCase();
126
+ let score = 0;
127
+ if (candLower.startsWith(lower) || lower.startsWith(candLower))
128
+ score = 0.9;
129
+ else if (candLower.includes(lower) || lower.includes(candLower))
130
+ score = 0.7;
131
+ else {
132
+ let shared = 0;
133
+ for (let i = 0; i + 2 <= lower.length; i++)
134
+ if (candLower.includes(lower.slice(i, i + 2)))
135
+ shared++;
136
+ score = lower.length > 2 ? shared / (lower.length - 1) : 0;
137
+ }
138
+ if (score > bestScore) {
139
+ bestScore = score;
140
+ best = candidate;
141
+ }
142
+ }
143
+ return bestScore >= 0.4 ? best : undefined;
144
+ }
114
145
  export function parseToolInput(tool, input) {
115
146
  const normalized = applyInputAliases(tool, input);
147
+ // r30 引出、r42/r43 再证:静默丢弃错键是头号返工源(dictionary.save items
148
+ // 未知键、跨模型 filterCondition 错键同族)。写面 fail-closed——未知顶层键
149
+ // 当场拒绝并给最接近的合法键提示;读面保持宽松(探查性调用常见无害附加)。
150
+ if (tool.kind === "write") {
151
+ const schemaKeys = new Set(Object.keys(tool.inputSchema));
152
+ const unknown = Object.keys(normalized).filter((key) => !schemaKeys.has(key));
153
+ if (unknown.length > 0) {
154
+ const suggestions = unknown.map((key) => `${key}→${closestSchemaKey(key, schemaKeys) ?? "(无相近键,omc describe <tool> 看参数表)"}`);
155
+ throw new ToolInputError({
156
+ status: "error",
157
+ blocker: "tool-input-unknown-keys",
158
+ tool: tool.name,
159
+ message: `写面工具不接受未知键(会被静默丢弃):${unknown.join("、")}。最接近的合法键:${suggestions.join(";")}。参数表:omc describe ${tool.name}`,
160
+ issues: unknown.map((key) => ({ path: [key], code: "unrecognized_key", message: `unknown key; closest: ${closestSchemaKey(key, schemaKeys) ?? "none"}` })),
161
+ });
162
+ }
163
+ }
116
164
  const result = z.object(tool.inputSchema).safeParse(normalized);
117
165
  if (result.success)
118
166
  return result.data;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stalfh233/omc-cli",
3
- "version": "0.5.11",
3
+ "version": "0.5.12",
4
4
  "description": "CloudPivot 8.6 configuration capability CLI — governed, fail-closed, coverage-manifest bounded.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -52,9 +52,9 @@
52
52
  "access": "public"
53
53
  },
54
54
  "dependencies": {
55
- "@stalfh233/omc-fake-platform": "0.5.11",
55
+ "@stalfh233/omc-fake-platform": "0.5.12",
56
56
  "commander": "14.0.3",
57
- "omc-core": "0.5.11",
57
+ "omc-core": "0.5.12",
58
58
  "proper-lockfile": "4.1.2",
59
59
  "write-file-atomic": "6.0.0",
60
60
  "zod": "^3.23.8",