@stalfh233/omc-cli 0.6.6 → 0.6.10
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.
- package/README.md +2 -1
- package/dist/approval-flow.js +5 -3
- package/dist/bizservice-config.js +26 -10
- package/dist/call.js +47 -0
- package/dist/capability-overview.js +47 -24
- package/dist/cli-contract.js +90 -17
- package/dist/cli-output.js +41 -2
- package/dist/cli-stdout.js +74 -0
- package/dist/cli.js +162 -54
- package/dist/command-transport.js +8 -1
- package/dist/config-store.js +17 -2
- package/dist/discovery.js +62 -3
- package/dist/field-families.js +35 -6
- package/dist/goal-routes.js +118 -11
- package/dist/human-summary.js +11 -0
- package/dist/lanes/app-contract.js +12 -5
- package/dist/lanes/app.js +221 -134
- package/dist/lanes/apply-changes.js +2 -1
- package/dist/lanes/b2-registration-contract.js +3 -3
- package/dist/lanes/b2-registration.js +890 -693
- package/dist/lanes/businessrule.js +75 -51
- package/dist/lanes/custom-page.js +166 -115
- package/dist/lanes/datarule-contract.js +2 -2
- package/dist/lanes/datarule.js +238 -154
- package/dist/lanes/dictionary.js +253 -110
- package/dist/lanes/environment.js +57 -9
- package/dist/lanes/field.js +160 -107
- package/dist/lanes/folder-contract.js +7 -3
- package/dist/lanes/folder.js +249 -118
- package/dist/lanes/form-contract.js +1 -1
- package/dist/lanes/form.js +188 -64
- package/dist/lanes/list-view.js +405 -369
- package/dist/lanes/meta.js +31 -6
- package/dist/lanes/model-contract.js +1 -1
- package/dist/lanes/model.js +610 -512
- package/dist/lanes/online-js-contract.js +1 -1
- package/dist/lanes/online-js.js +160 -146
- package/dist/lanes/organization.js +905 -640
- package/dist/lanes/programming.js +274 -136
- package/dist/lanes/report-contract.js +2 -2
- package/dist/lanes/report.js +354 -198
- package/dist/lanes/rule-lifecycle.js +294 -140
- package/dist/lanes/runtime-data-contract.js +4 -4
- package/dist/lanes/runtime-data.js +869 -745
- package/dist/lanes/workflow-contract.js +3 -3
- package/dist/lanes/workflow.js +732 -605
- package/dist/model-scaffold.js +26 -5
- package/dist/path-projection.js +196 -14
- package/dist/plan.js +25 -0
- package/dist/programming-workflow.js +55 -3
- package/dist/read-shapes.js +146 -0
- package/dist/read.js +40 -9
- package/dist/recipes/businessrule.js +74 -24
- package/dist/recipes/dictionary.js +7 -4
- package/dist/recipes/form.js +46 -0
- package/dist/recipes/runtime-data.js +3 -2
- package/dist/refusal-guidance.js +29 -5
- package/dist/rule-support.js +14 -1
- package/dist/rule-when.js +2 -2
- package/dist/tool-types.js +9 -3
- package/dist/version-family-gaps.js +101 -0
- package/dist/write.js +2 -1
- package/package.json +6 -4
- package/skills/omc-glossary.md +13 -0
- package/skills/omc-refusals.md +13 -5
- package/skills/omc-start-here.md +2 -2
- package/skills/omc-tool-selection.md +2 -1
- package/skills/omc-write-hazards.md +5 -2
package/dist/model-scaffold.js
CHANGED
|
@@ -128,7 +128,15 @@ export async function cmdModelScaffold(args) {
|
|
|
128
128
|
});
|
|
129
129
|
const status = String(probe.result.status ?? "ok");
|
|
130
130
|
if (probe.exitCode !== 0 || (status !== "dry-run" && status !== "ok")) {
|
|
131
|
-
|
|
131
|
+
const result = probe.result;
|
|
132
|
+
// r28g F4:field.create 回执带 validation[],脚手架以前只压成一句
|
|
133
|
+
// 「按 validation 修正」却把数组丢掉——调用方不得不再逐字段 dry-run。
|
|
134
|
+
const validation = Array.isArray(result.validation) ? result.validation : undefined;
|
|
135
|
+
preflightProblems.push({
|
|
136
|
+
code: field.code,
|
|
137
|
+
message: refusalText(result),
|
|
138
|
+
...(validation ? { validation } : {}),
|
|
139
|
+
});
|
|
132
140
|
}
|
|
133
141
|
}
|
|
134
142
|
if (preflightProblems.length > 0) {
|
|
@@ -140,7 +148,7 @@ export async function cmdModelScaffold(args) {
|
|
|
140
148
|
failed: preflightProblems.length,
|
|
141
149
|
preflightProblems,
|
|
142
150
|
message: `有 ${preflightProblems.length} 个字段的预检没过,**尚未写入任何东西**。` +
|
|
143
|
-
`按 message 改 --fields 后重跑(常见:NUMERICAL 缺 options.scale
|
|
151
|
+
`按 preflightProblems[].validation / message 改 --fields 后重跑(常见:NUMERICAL 缺 options.scale、code 风格不合法、文本/数值字段用了自定义键名)。` +
|
|
144
152
|
`--fields 的每一项就是 field.create 的参数(形状见 omc describe field.create),子表列用 subFields(childFields 是别名)。`,
|
|
145
153
|
}, request, { code: "policy-refused", message: "字段形状预检未通过(未写入)", retryable: false }));
|
|
146
154
|
return CLI_EXIT_CODES.policyRefused;
|
|
@@ -290,15 +298,28 @@ export async function cmdModelScaffold(args) {
|
|
|
290
298
|
}
|
|
291
299
|
const failedSteps = steps.filter((step) => step.status === "failed");
|
|
292
300
|
const status = failedSteps.length > 0 ? "partial" : "ok";
|
|
301
|
+
const fieldCount = fields.reduce((sum, field) => sum + 1 + scaffoldChildrenOf(field).length, 0);
|
|
302
|
+
// NOTE: renamed locally to unblock tsx (parallel agent redeclared `created`
|
|
303
|
+
// in this same scope). Keep semantics identical.
|
|
304
|
+
const fieldsCreated = steps.filter((step) => step.step.endsWith("field.create") && step.status === "ok").length;
|
|
305
|
+
const publishActions = publishFieldSteps.filter((step) => step.status === "ok").length;
|
|
306
|
+
// r28g F5:旧键 publishedFields 实际是「发布动作次数/目标 schema 数」(常=1),
|
|
307
|
+
// 与 fieldCount 量级不符。保留旧键作兼容别名,同时给 publishActions + 字段计数。
|
|
308
|
+
const fieldsPublishedEstimate = publishingBlocked || fieldPublishFailed ? 0 : fieldsCreated;
|
|
293
309
|
const result = {
|
|
294
310
|
status,
|
|
295
311
|
model: code,
|
|
296
312
|
name,
|
|
297
|
-
fieldCount
|
|
298
|
-
created:
|
|
313
|
+
fieldCount,
|
|
314
|
+
created: fieldsCreated,
|
|
299
315
|
skipped: steps.filter((step) => step.step.endsWith("field.create") && step.status === "skipped").length,
|
|
300
316
|
failed: failedFields.length,
|
|
301
|
-
|
|
317
|
+
publishActions,
|
|
318
|
+
publishedFields: publishActions,
|
|
319
|
+
fieldsPublishedEstimate,
|
|
320
|
+
publishNote: publishActions > 0
|
|
321
|
+
? `已执行 ${publishActions} 次模型级 field.publish(覆盖本次创建的约 ${fieldsPublishedEstimate}/${fieldCount} 个字段;精确清单用 field.list --published)`
|
|
322
|
+
: "未发布字段(有失败或被跳过);精确状态用 field.list",
|
|
302
323
|
...(failedFields.length > 0 ? { failedFields } : {}),
|
|
303
324
|
...(failedSteps.length > 0 ? { failedSteps } : {}),
|
|
304
325
|
...(lastSummaries.length > 0 ? { summaries: lastSummaries } : {}),
|
package/dist/path-projection.js
CHANGED
|
@@ -1,6 +1,27 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import { readArtifact } from "./artifacts.js";
|
|
3
3
|
import { artifactRoot } from "./command-transport.js";
|
|
4
|
+
import { closestMatch } from "./closest-match.js";
|
|
5
|
+
/**
|
|
6
|
+
* r28f F2:投影 DSL 不是 jq。对象构造 `{a,b}` / 函数调用 `map(...)` 被逗号
|
|
7
|
+
* 拆路径后会伪装成两条 pathMiss(available=键列表),误导下一轮。先识别再
|
|
8
|
+
* 报「不支持」——别让调用方以为键不存在。
|
|
9
|
+
*/
|
|
10
|
+
export const UNSUPPORTED_JQ_OBJECT_HINT = "投影 DSL 不支持对象构造 `{a,b}`;单列请写 `.x[].a`,多列用逗号分隔(--jq \".x[].a,.x[].b\")或重复传 --jq";
|
|
11
|
+
export function detectUnsupportedJqSyntax(path) {
|
|
12
|
+
if (/[{}]/.test(path))
|
|
13
|
+
return UNSUPPORTED_JQ_OBJECT_HINT;
|
|
14
|
+
// r28g F20(真机 2026-11-05):jq 过滤表达式 `[?(@...)]` 会被当成路径段,
|
|
15
|
+
// 在 `rules[?(@` 处**截断**后报 pathMiss——读起来像"键不存在",实际是
|
|
16
|
+
// "这个 DSL 不支持过滤"。直接说清,并给出本 DSL 的等价写法。
|
|
17
|
+
if (/\[\s*\?/.test(path)) {
|
|
18
|
+
return "投影 DSL 不支持 jq 过滤表达式 `[?(@...)]`;等价写法是按字段选择 `rules[code=Create]`(返回第一个匹配项),或先 `--jq rules[]` 全取再由脚本筛";
|
|
19
|
+
}
|
|
20
|
+
// 函数调用:`map(` / `select(` 等(选择器 `[code=X]` 不含 `(`)。
|
|
21
|
+
if (/\w+\s*\(/.test(path))
|
|
22
|
+
return "投影 DSL 不支持函数调用;只做路径选择(点式 / [] 映射 / [key=value]),详见 omc help omc-refusals --section 10";
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
4
25
|
export function projectJsonPath(value, path) {
|
|
5
26
|
// r40 F9:`$.x`(jq 标准根写法)此前 pathMiss——agent/人都习惯带根前缀。
|
|
6
27
|
// 归一剥掉前导 `$.`/`$`,语义等价(本 DSL 根就是整份结果)。
|
|
@@ -8,6 +29,11 @@ export function projectJsonPath(value, path) {
|
|
|
8
29
|
path = path.slice(2);
|
|
9
30
|
else if (path === "$")
|
|
10
31
|
path = "";
|
|
32
|
+
// r28f F2:不支持语法优先于路径行走——避免 `{code` 被当成 miss 段。
|
|
33
|
+
const unsupported = detectUnsupportedJqSyntax(path);
|
|
34
|
+
if (unsupported !== undefined) {
|
|
35
|
+
return { missed: true, at: path, available: unsupported, unsupportedSyntax: true };
|
|
36
|
+
}
|
|
11
37
|
// 2026-11-01(r36 维护者实测):jq 切片 `a[0:6]`/`a[:n]`/`a[n:]` 此前
|
|
12
38
|
// pathMiss 且 hint 的 available 还把数组列成"可用键",误导下一步。数组
|
|
13
39
|
// 切片与 jq 语义一致(含头不含尾)。
|
|
@@ -145,8 +171,77 @@ function argvValues(args, name) {
|
|
|
145
171
|
}
|
|
146
172
|
return values;
|
|
147
173
|
}
|
|
148
|
-
/**
|
|
149
|
-
|
|
174
|
+
/**
|
|
175
|
+
* 路径选择器语法**唯一**真相源(HANDOFF §三.1-3)。此前同一套语法在五处各写一份
|
|
176
|
+
* (`projectResultExtract` 的 miss、`artifact get --summary --jq`、artifact 裸
|
|
177
|
+
* `--jq`、逗号多路径部分 miss、以及 `artifact get` 的 fetch 提示),改一处忘一处
|
|
178
|
+
* 就会出现"同一个错误两种说法"。现在语法句子只在这里定义,其余地方拼装。
|
|
179
|
+
*/
|
|
180
|
+
export const JQ_PATH_SYNTAX = "可用写法:点式 a.b、下标 a[0]、切片 a[0:6]、数组映射 items[].fields.x、按键选择 rules[code=Create] 或 data.activities[code=A1](数组与 index→条目对象都支持;键名可省 activityCode/nodeCode 等别名;返回第一个匹配项)。";
|
|
181
|
+
/** 非 jq 语言 + 一次取多列的写法。每一处 miss 提示都以它收尾。 */
|
|
182
|
+
export const JQ_PATH_RULES = "注意:本参数是路径选择器、不是 jq 语言(不支持 | 管道与对象构造);一次取多列用逗号分隔(--jq \"a.b,c.d\")或重复传 --jq。";
|
|
183
|
+
/**
|
|
184
|
+
* `call --extract/--jq` 专有的位置语义:路径相对 **result**,不是整个信封。
|
|
185
|
+
* r26b 读回体积审计实测:调用方写 `--jq result.total` → `pathMiss`(missedAt:"result"),
|
|
186
|
+
* 白付一整份大回执 + 一轮;改成 `--jq total` 立即只有 356 B。这条只属于 call 面
|
|
187
|
+
* (`artifact get --jq` 相对的是产物正文,不能套用)。
|
|
188
|
+
*/
|
|
189
|
+
export const RESULT_RELATIVE_PATH_NOTE = "路径相对 **result**(不要写 result. 前缀,如 --jq total 而不是 --jq result.total)。";
|
|
190
|
+
/** 语法不再只靠报错学:`omc help omc-refusals --section "10. 投影路径"` 是权威教程。 */
|
|
191
|
+
export const JQ_PATH_TOPIC = "omc-refusals";
|
|
192
|
+
export const JQ_PATH_SECTION = "10. 投影路径(--jq / --extract)";
|
|
193
|
+
export const JQ_PATH_HELP_POINTER = `语法详解:omc help ${JQ_PATH_TOPIC} --section 10(投影路径)。`;
|
|
194
|
+
/** Shared wording for every projection miss, so every face teaches the same syntax. */
|
|
195
|
+
export const PATH_MISS_HINT = `路径未命中(这一层没有该段)。${RESULT_RELATIVE_PATH_NOTE}${JQ_PATH_SYNTAX}available 是该层真实存在的键。${JQ_PATH_RULES}${JQ_PATH_HELP_POINTER}`;
|
|
196
|
+
/**
|
|
197
|
+
* `artifact get` 面:产物内容的投影。区别只在开头——它已经把 miss 落在哪一段
|
|
198
|
+
* (`missedAt`)和可用的键/范围一并返回,所以不必重复「这一层没有该段」。
|
|
199
|
+
*/
|
|
200
|
+
export function artifactPathMissHint(at) {
|
|
201
|
+
return `路径未命中${at !== undefined ? `段 "${at}"` : "(这一层没有该段)"}——available 是该层真实存在的键/范围;方括号下标(a[0])与点式(a.0)等价。${JQ_PATH_SYNTAX}${JQ_PATH_RULES}${JQ_PATH_HELP_POINTER}`;
|
|
202
|
+
}
|
|
203
|
+
/** 逗号多路径里只有部分 miss:给出定位方式 + 同一套语法。 */
|
|
204
|
+
export function partialPathMissHint() {
|
|
205
|
+
return `逗号多路径中部分路径未命中(见各 projection.pathMiss/missedAt/available)。${JQ_PATH_SYNTAX}${JQ_PATH_HELP_POINTER}`;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* 逐条 miss 的**短**提示。r27 真机(2026-11-05)实证:一段 695 B 的完整语法在
|
|
209
|
+
* 3 条路径 miss 时被复制 3 遍 ≈ 2 KB,而三条说的是同一件事。所以多路径时单条
|
|
210
|
+
* 只指出「在哪断的、这一层有什么」,语法全集由结果顶层的 `hint` 给一次。
|
|
211
|
+
*/
|
|
212
|
+
export function pathMissEntryHint(at, available) {
|
|
213
|
+
const keys = Array.isArray(available) ? available.slice(0, 12).map((k) => String(k)) : [];
|
|
214
|
+
const atPart = at !== undefined ? `在 "${at}" 断开` : "这一层没有该段";
|
|
215
|
+
const more = Array.isArray(available) && available.length > 12 ? " 等" : "";
|
|
216
|
+
return keys.length > 0 ? `${atPart};这一层有:${keys.join("、")}${more}` : atPart;
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* pathMiss 的 did-you-mean:对 miss 的最后一段在 available 键列表里做近似匹配。
|
|
220
|
+
* available 不是字符串数组(范围说明 / 空对象文案)或匹配不到时不加字段——
|
|
221
|
+
* 误导建议比没有更糟;过短输入由 closestMatch 自己静默。
|
|
222
|
+
*/
|
|
223
|
+
export function pathMissDidYouMean(at, available) {
|
|
224
|
+
if (at === undefined || at.length === 0)
|
|
225
|
+
return undefined;
|
|
226
|
+
if (!Array.isArray(available) || available.length === 0)
|
|
227
|
+
return undefined;
|
|
228
|
+
const keys = available.map((entry) => String(entry)).filter((entry) => entry.length > 0);
|
|
229
|
+
if (keys.length === 0)
|
|
230
|
+
return undefined;
|
|
231
|
+
// Missed segment may be `items[].workItemId` / `fields.repairNo` — match the
|
|
232
|
+
// last path piece against the keys at that layer.
|
|
233
|
+
const leaf = at.includes(".") || at.includes("/") || at.includes("[")
|
|
234
|
+
? at.replace(/^[.\/]+/, "").split(/[./\[]/).filter(Boolean).pop() ?? at
|
|
235
|
+
: at;
|
|
236
|
+
return closestMatch(leaf, keys);
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* 多路径一次投影时的**唯一一份**语法说明(放结果顶层,不再逐条复制)。
|
|
240
|
+
* 只给最要紧的三条:位置语义、映射写法、help 指针。
|
|
241
|
+
*/
|
|
242
|
+
export function multiPathMissHint() {
|
|
243
|
+
return `${RESULT_RELATIVE_PATH_NOTE}${JQ_PATH_SYNTAX}${JQ_PATH_HELP_POINTER}`;
|
|
244
|
+
}
|
|
150
245
|
/**
|
|
151
246
|
* T6(GitLab #17):`mapped` 只是"路径命中的条目数",值为 null 也算命中。
|
|
152
247
|
* 全 null + ok:true 曾被读成"取到了 N 个值/台账只有 1 行"(真实 42 行)。
|
|
@@ -154,12 +249,38 @@ const PATH_MISS_HINT = "路径未命中(这一层没有该段)。可用写
|
|
|
154
249
|
* 与字段本身为空。
|
|
155
250
|
*/
|
|
156
251
|
const ALL_NULL_HINT = `路径全部命中但值都是 null——注意 mapped 只是命中条目数,不代表有值。常见成因是列表投影没配置该列(data.list 只投影视图列,null 或整键省略;看同一次 data.list 的 projectionHint,或用 data.load 读单条权威值)。若 projectionHint 也指向该列,就不是"字段为空"。`;
|
|
252
|
+
/**
|
|
253
|
+
* `mapped`/`nullEntries` 的读法跟数据一起走(T6 / A6)。仅在确实出现 null 时加
|
|
254
|
+
* 提示,正常读取零噪音;全 null 用同一段文案,避免"同一事实三种说法"。
|
|
255
|
+
*/
|
|
256
|
+
export function nullProjectionHint(nullEntries, total) {
|
|
257
|
+
if (nullEntries === undefined || nullEntries === 0)
|
|
258
|
+
return undefined;
|
|
259
|
+
if (total === 0 || nullEntries !== total)
|
|
260
|
+
return `其中 ${nullEntries} 个命中值为 null(不区分"字段为空"与"列表未投影")。`;
|
|
261
|
+
return ALL_NULL_HINT;
|
|
262
|
+
}
|
|
157
263
|
/**
|
|
158
264
|
* `call --extract/--extract-keys`: resolve paths inside the result's own
|
|
159
265
|
* artifact (or inside the result object when the tool returned no artifact).
|
|
160
266
|
* Returns undefined when there is nothing to project so the caller's result is
|
|
161
267
|
* emitted unchanged.
|
|
162
268
|
*/
|
|
269
|
+
/**
|
|
270
|
+
* r28g F29(真机 2026-11-05):读面回执的**顶层**常带 `modelsCount` / `count`
|
|
271
|
+
* 这类只在 result 层有的键;`--jq modelsCount` 却去 artifact 副本里找,
|
|
272
|
+
* 于是 pathMiss,且 `available` 列的是 artifact 的键——调用方看到"键明明打印过
|
|
273
|
+
* 却说没有"。这里把事实补回去:该路径在 result 层存在时明确指路。
|
|
274
|
+
*/
|
|
275
|
+
function resultLayerNote(result, path) {
|
|
276
|
+
const head = path.replace(/^\$\.?/, "").split(/[.\[]/)[0];
|
|
277
|
+
if (head === "" || !(head in result))
|
|
278
|
+
return undefined;
|
|
279
|
+
return {
|
|
280
|
+
existsOnResult: head,
|
|
281
|
+
resultLayerHint: `「${head}」在 result 顶层(只是不在 artifact 副本里)。取它请不带 --jq 直接读回执,或改取 artifact 内的键(见 available)`,
|
|
282
|
+
};
|
|
283
|
+
}
|
|
163
284
|
export function projectResultExtract(result, paths, keysOnly) {
|
|
164
285
|
const artifactId = typeof result.detailArtifactId === "string" ? result.detailArtifactId : typeof result.artifactId === "string" ? result.artifactId : undefined;
|
|
165
286
|
// Tools hand an in-process artifact as `detailFile` (a local path);
|
|
@@ -212,35 +333,96 @@ export function projectResultExtract(result, paths, keysOnly) {
|
|
|
212
333
|
: [];
|
|
213
334
|
}
|
|
214
335
|
if (paths.length > 0) {
|
|
336
|
+
// r27 真机:多路径时每条 miss 都带 695 B 完整语法 → 3 条 2 KB 说同一件事。
|
|
337
|
+
// 单路径照旧带完整提示(只出现一次,最有用);多路径逐条只给定位,语法放顶层一次。
|
|
338
|
+
const multiPath = paths.length > 1;
|
|
339
|
+
const missHintFor = (at, available) => multiPath ? pathMissEntryHint(at, available) : PATH_MISS_HINT;
|
|
215
340
|
out.extract = paths.map((path) => {
|
|
341
|
+
// r28f F2:对象构造 / 函数调用不是 pathMiss——先识别,别伪装成键缺失。
|
|
342
|
+
const unsupported = detectUnsupportedJqSyntax(path);
|
|
343
|
+
if (unsupported !== undefined) {
|
|
344
|
+
return {
|
|
345
|
+
path,
|
|
346
|
+
content: null,
|
|
347
|
+
pathMiss: false,
|
|
348
|
+
unsupportedSyntax: true,
|
|
349
|
+
unanswered: true,
|
|
350
|
+
available: unsupported,
|
|
351
|
+
hint: unsupported,
|
|
352
|
+
};
|
|
353
|
+
}
|
|
216
354
|
// `items[].fields.planNo` maps over every row; a plain path stays a single
|
|
217
355
|
// value (issue 2026-10-25 §8). Both faces share one shape so a caller can
|
|
218
356
|
// switch between `--extract`/`--jq` without relearning the envelope.
|
|
219
357
|
if (path.includes("[]")) {
|
|
220
358
|
const fanout = projectJsonPathFanout(source, path);
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
359
|
+
if (fanout.missed) {
|
|
360
|
+
const didYouMean = pathMissDidYouMean(fanout.at, fanout.available);
|
|
361
|
+
return {
|
|
224
362
|
path,
|
|
225
|
-
content:
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
363
|
+
content: null,
|
|
364
|
+
pathMiss: true,
|
|
365
|
+
missedAt: fanout.at,
|
|
366
|
+
available: fanout.available,
|
|
367
|
+
missedEntries: fanout.missedEntries,
|
|
368
|
+
...(didYouMean !== undefined ? { didYouMean } : {}),
|
|
369
|
+
...(readFromArtifact ? resultLayerNote(result, path) ?? {} : {}),
|
|
370
|
+
hint: missHintFor(fanout.at, fanout.available),
|
|
230
371
|
};
|
|
372
|
+
}
|
|
373
|
+
return {
|
|
374
|
+
path,
|
|
375
|
+
content: fanout.values,
|
|
376
|
+
mapped: fanout.values.length,
|
|
377
|
+
...(fanout.nullEntries > 0 ? { nullEntries: fanout.nullEntries } : {}),
|
|
378
|
+
...(fanout.nullEntries > 0 && fanout.nullEntries === fanout.values.length ? { hint: ALL_NULL_HINT } : {}),
|
|
379
|
+
...(fanout.missedEntries > 0 ? { missedEntries: fanout.missedEntries, hint: `有 ${fanout.missedEntries} 条记录在该路径下缺失,values 只含命中项` } : {}),
|
|
380
|
+
};
|
|
231
381
|
}
|
|
232
382
|
const projected = projectJsonPath(source, path);
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
383
|
+
if (projected.missed) {
|
|
384
|
+
// projectJsonPath 也会拦 unsupportedSyntax;双保险统一出口。
|
|
385
|
+
if (projected.unsupportedSyntax === true) {
|
|
386
|
+
return {
|
|
387
|
+
path,
|
|
388
|
+
content: null,
|
|
389
|
+
pathMiss: false,
|
|
390
|
+
unsupportedSyntax: true,
|
|
391
|
+
unanswered: true,
|
|
392
|
+
available: projected.available,
|
|
393
|
+
hint: typeof projected.available === "string" ? projected.available : UNSUPPORTED_JQ_OBJECT_HINT,
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
const didYouMean = pathMissDidYouMean(projected.at, projected.available);
|
|
397
|
+
return {
|
|
398
|
+
path,
|
|
399
|
+
content: null,
|
|
400
|
+
pathMiss: true,
|
|
401
|
+
missedAt: projected.at,
|
|
402
|
+
available: projected.available,
|
|
403
|
+
...(didYouMean !== undefined ? { didYouMean } : {}),
|
|
404
|
+
...(readFromArtifact ? resultLayerNote(result, path) ?? {} : {}),
|
|
405
|
+
hint: missHintFor(projected.at, projected.available),
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
return { path, content: projected.value ?? null };
|
|
236
409
|
});
|
|
237
410
|
if (paths.length === 1)
|
|
238
411
|
out.content = out.extract[0]?.content ?? null;
|
|
239
412
|
// A projection that missed is not an answer. `status` stays `ok` (adding a
|
|
240
413
|
// new status value would fall through classifyResult to error/exit 15 and
|
|
241
414
|
// change the exit contract), but the envelope now says so out loud.
|
|
242
|
-
|
|
415
|
+
const extracts = out.extract;
|
|
416
|
+
if (extracts.some((entry) => entry.pathMiss === true || entry.unsupportedSyntax === true)) {
|
|
243
417
|
out.unanswered = true;
|
|
418
|
+
// 多路径:语法说明给一次,不再逐条复制。不支持语法优先于 pathMiss 语法课。
|
|
419
|
+
if (extracts.some((entry) => entry.unsupportedSyntax === true)) {
|
|
420
|
+
out.hint = UNSUPPORTED_JQ_OBJECT_HINT;
|
|
421
|
+
}
|
|
422
|
+
else if (multiPath) {
|
|
423
|
+
out.hint = `${RESULT_RELATIVE_PATH_NOTE}${JQ_PATH_SYNTAX}${JQ_PATH_HELP_POINTER}`;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
244
426
|
}
|
|
245
427
|
return out;
|
|
246
428
|
}
|
package/dist/plan.js
CHANGED
|
@@ -24,6 +24,28 @@ export function planFingerprint(plan) {
|
|
|
24
24
|
}))
|
|
25
25
|
.digest("hex");
|
|
26
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* r28g F8(真机 2026-11-05):dry-run 的 `*Summary` 只给键名 + sha256,把**最该
|
|
29
|
+
* 自查的那段**(三层转义后的条件串)整个遮住——校验通过的结论看得见,被校验的
|
|
30
|
+
* 内容看不见。这里补一个 `*Preview`:把**小**的字符串叶子原样列出(总长 ≤400B),
|
|
31
|
+
* 超了就不给(还是回到摘要)。目的是"可自查形状",不是把正文搬回来。
|
|
32
|
+
*/
|
|
33
|
+
function smallStringPreview(value) {
|
|
34
|
+
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
35
|
+
return undefined;
|
|
36
|
+
const out = {};
|
|
37
|
+
let budget = 400;
|
|
38
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
39
|
+
if (typeof entry !== "string")
|
|
40
|
+
continue;
|
|
41
|
+
const bytes = Buffer.byteLength(entry, "utf8");
|
|
42
|
+
if (bytes > budget)
|
|
43
|
+
continue;
|
|
44
|
+
budget -= bytes;
|
|
45
|
+
out[key] = entry;
|
|
46
|
+
}
|
|
47
|
+
return Object.keys(out).length > 0 ? out : undefined;
|
|
48
|
+
}
|
|
27
49
|
function summarizedArgs(tool, args) {
|
|
28
50
|
if (typeof args.summary === "string" && typeof args.fingerprint === "string")
|
|
29
51
|
return { ...args };
|
|
@@ -36,6 +58,9 @@ function summarizedArgs(tool, args) {
|
|
|
36
58
|
}
|
|
37
59
|
else if (Array.isArray(value) || (value && typeof value === "object") || typeof value === "string") {
|
|
38
60
|
summary[`${key}Summary`] = summarizeValue(value);
|
|
61
|
+
const preview = smallStringPreview(value);
|
|
62
|
+
if (preview !== undefined)
|
|
63
|
+
summary[`${key}Preview`] = preview;
|
|
39
64
|
}
|
|
40
65
|
else {
|
|
41
66
|
summary[key] = value;
|
|
@@ -50,6 +50,58 @@ export function readManifest(paths) {
|
|
|
50
50
|
return null;
|
|
51
51
|
return JSON.parse(readFileSync(paths.manifestFile, "utf8"));
|
|
52
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* r28j F13: existence used to mean "manifest.json present" only, so a user who
|
|
55
|
+
* hand-wrote `.omc-programming/tests/...` was told the whole workspace was
|
|
56
|
+
* absent. Distinguish "nothing here" / "tests-only" / "sources without
|
|
57
|
+
* manifest" so the refusal names the paths it checked.
|
|
58
|
+
*/
|
|
59
|
+
export function describeWorkspacePresence(paths) {
|
|
60
|
+
const hasRoot = existsSync(paths.root);
|
|
61
|
+
const hasManifest = existsSync(paths.manifestFile);
|
|
62
|
+
const hasSources = existsSync(paths.sourcesDir);
|
|
63
|
+
const hasTests = existsSync(paths.testsDir);
|
|
64
|
+
return {
|
|
65
|
+
present: hasManifest,
|
|
66
|
+
hasRoot,
|
|
67
|
+
hasManifest,
|
|
68
|
+
hasSources,
|
|
69
|
+
hasTests,
|
|
70
|
+
checked: [paths.manifestFile, paths.sourcesDir, paths.testsDir],
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
export function workspaceAbsentRefusal(paths) {
|
|
74
|
+
const presence = describeWorkspacePresence(paths);
|
|
75
|
+
if (!presence.hasRoot) {
|
|
76
|
+
return {
|
|
77
|
+
status: "refused",
|
|
78
|
+
blocker: "programming-workspace-absent",
|
|
79
|
+
message: `本地没有 .omc-programming/。先 omc programming pull --env <env> --app <appCode>。已检查:${presence.checked.join(" · ")}`,
|
|
80
|
+
checked: presence.checked,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
if (presence.hasTests && !presence.hasManifest) {
|
|
84
|
+
return {
|
|
85
|
+
status: "refused",
|
|
86
|
+
blocker: "programming-workspace-manifest-absent",
|
|
87
|
+
message: `已有 .omc-programming/tests/,但缺 manifest.json(以及通常还有 sources/)。tests 单独存在不算完整工作区——先 omc programming pull --env <env> --app <appCode> 生成 manifest+sources,再保留你的 tests。已检查:${presence.checked.join(" · ")}`,
|
|
88
|
+
checked: presence.checked,
|
|
89
|
+
hasTests: true,
|
|
90
|
+
hasManifest: false,
|
|
91
|
+
hasSources: presence.hasSources,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
status: "refused",
|
|
96
|
+
blocker: "programming-workspace-absent",
|
|
97
|
+
message: `本地 .omc-programming/ 不完整(缺 manifest.json)。先 omc programming pull --env <env> --app <appCode>。已检查:${presence.checked.join(" · ")}`,
|
|
98
|
+
checked: presence.checked,
|
|
99
|
+
hasRoot: true,
|
|
100
|
+
hasManifest: false,
|
|
101
|
+
hasSources: presence.hasSources,
|
|
102
|
+
hasTests: presence.hasTests,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
53
105
|
function writeManifest(paths, manifest) {
|
|
54
106
|
mkdirSync(paths.root, { recursive: true });
|
|
55
107
|
const ordered = {
|
|
@@ -198,7 +250,7 @@ export async function cmdProgrammingStatus(options) {
|
|
|
198
250
|
const session = await context.sessions.session(options.environment);
|
|
199
251
|
const manifest = readManifest(paths);
|
|
200
252
|
if (!manifest)
|
|
201
|
-
return
|
|
253
|
+
return workspaceAbsentRefusal(paths);
|
|
202
254
|
try {
|
|
203
255
|
assertManifestEnvironment(manifest, session.environmentName, session.profile.baseUrl);
|
|
204
256
|
}
|
|
@@ -429,7 +481,7 @@ export async function cmdProgrammingApply(options) {
|
|
|
429
481
|
const paths = workspacePaths(options.cwd);
|
|
430
482
|
const manifest = readManifest(paths);
|
|
431
483
|
if (!manifest)
|
|
432
|
-
return
|
|
484
|
+
return workspaceAbsentRefusal(paths);
|
|
433
485
|
const context = options.ctx ?? makeToolContext({ sessions: new SessionManager(new ConfigStore(defaultPaths())) });
|
|
434
486
|
const session = await context.sessions.session(options.environment);
|
|
435
487
|
try {
|
|
@@ -616,7 +668,7 @@ export async function cmdProgrammingTest(options) {
|
|
|
616
668
|
const paths = workspacePaths(options.cwd);
|
|
617
669
|
const manifest = readManifest(paths);
|
|
618
670
|
if (!manifest)
|
|
619
|
-
return
|
|
671
|
+
return workspaceAbsentRefusal(paths);
|
|
620
672
|
const context = options.ctx ?? makeToolContext({ sessions: new SessionManager(new ConfigStore(defaultPaths())) });
|
|
621
673
|
const session = await context.sessions.session(options.environment);
|
|
622
674
|
try {
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Explicit return-shape table for high-frequency read tools.
|
|
3
|
+
*
|
|
4
|
+
* Agents guess jq paths from input arg names (`controls`, `ruleCode`, `id`,
|
|
5
|
+
* bare business columns) and miss the real result keys. `describe` only listed
|
|
6
|
+
* inputs, so the only recovery was a pathMiss `available` list. This table is
|
|
7
|
+
* the cheap pre-call face: one tool's keys (+ one short trap note) on describe.
|
|
8
|
+
*
|
|
9
|
+
* Deliberately incomplete — undeclared tools omit `returns` entirely. Do not
|
|
10
|
+
* invent shapes from intuition.
|
|
11
|
+
*/
|
|
12
|
+
const READ_SHAPES = {
|
|
13
|
+
// r28k 治理面形状钉:截断≠空表;指纹有用途;doctor/get_status 分流。
|
|
14
|
+
"discover_environment": {
|
|
15
|
+
keys: ["applications:array<{code,name,groupId?,portalHidden?}>", "applicationsCount", "groups", "groupsCount", "foldersAndModels?", "foldersAndModelsCount?", "foldersAndModelsOmitted?", "models?", "modelsCount?", "modelsOmitted?", "tablesHint?", "applicationCode", "applicationResolution", "readiness", "environmentFingerprint", "fingerprintHint?", "detailArtifactId?"],
|
|
16
|
+
notes: [
|
|
17
|
+
"applications/groups 内联;foldersAndModels/models 外置时是 null + *Omitted:true(不是 [])",
|
|
18
|
+
"environmentFingerprint 用途见 fingerprintHint(写前漂移/计划绑定/读缓存键,非审批令牌)",
|
|
19
|
+
],
|
|
20
|
+
},
|
|
21
|
+
"environment.fingerprint": {
|
|
22
|
+
keys: ["environment", "fingerprint", "fingerprintHint"],
|
|
23
|
+
notes: ["fingerprint 随 env/origin/application/enterprise 变;写前漂移检测 + 计划/审计绑定"],
|
|
24
|
+
},
|
|
25
|
+
"get_status": {
|
|
26
|
+
keys: ["environment", "session", "coverage", "environmentScope", "unsupportedCapabilities?", "writeLease"],
|
|
27
|
+
notes: ["覆盖率/环境边界/租约用本工具;逐项中文 checks 用 doctor"],
|
|
28
|
+
},
|
|
29
|
+
"doctor": {
|
|
30
|
+
keys: ["checks", "anchor", "unsupportedCapabilities"],
|
|
31
|
+
notes: ["unsupportedCapabilities.coverageClaimMeaning:只列已知缺口;未列 ≠ 已验证可用"],
|
|
32
|
+
},
|
|
33
|
+
"coverage.manifest": {
|
|
34
|
+
keys: ["manifest:{version_family,anchor_version,items[],counts?,filter?,hint?}"],
|
|
35
|
+
notes: ["按 lane 索引(同 lane 可多行);答能力能不能用 → omc capability <id>"],
|
|
36
|
+
},
|
|
37
|
+
"apply_changes": {
|
|
38
|
+
keys: ["status", "steps?", "failedStep?", "progressFile?", "deliveryState?"],
|
|
39
|
+
notes: ["一次审批 N 变更、首败即停;单条调试用 omc call"],
|
|
40
|
+
},
|
|
41
|
+
"data.list": {
|
|
42
|
+
// items are {id, fields:{<code>: value}} — business columns live under fields.
|
|
43
|
+
keys: ["items:array<{id,fields:{<code>:value}}>", "total", "totalPages", "page", "size"],
|
|
44
|
+
// r28i F4: agents try rows[] / items[].sku first; pin the real shape here too.
|
|
45
|
+
notes: ["业务列在 items[].fields.<code>,不在 item 顶层,也不是 rows[](--jq items[].sku / rows[].sku 会 miss;正确:items[].fields.sku)"],
|
|
46
|
+
},
|
|
47
|
+
"folder.tree": {
|
|
48
|
+
keys: ["applicationCode", "applicationExists", "nodeCount", "folderCount", "modelCount", "nodes[]", "note"],
|
|
49
|
+
notes: ["计数是全应用聚合;共享环境请按 code 前缀过滤 nodes[],不要只比 nodeCount"],
|
|
50
|
+
},
|
|
51
|
+
"custompage.get": {
|
|
52
|
+
keys: ["code", "present", "page", "parentIdNote?", "contentHint?"],
|
|
53
|
+
notes: ["page.parentId 常为 null(/custom/get 不回 AppFunction 父节点);看 parentIdNote,落点用 folder.tree 按 code 核对"],
|
|
54
|
+
},
|
|
55
|
+
"data.load": {
|
|
56
|
+
keys: ["bizObjectId", "exists", "data:{...}"],
|
|
57
|
+
},
|
|
58
|
+
"model.list": {
|
|
59
|
+
// Compact diet may rewrite models to "code|name" pairs and add modelsCount.
|
|
60
|
+
keys: ["models:array<code|name>", "count", "modelsCount", "scopeHint"],
|
|
61
|
+
notes: [
|
|
62
|
+
"下游工具的 schemaCode = 本面 models 的 code(内联 diet 后是「code|name」里的 code)",
|
|
63
|
+
"modelsCount 是内联 result 键;若 --jq 走 artifact 副本可能 pathMiss——用 --jq count 或先看内联 result",
|
|
64
|
+
],
|
|
65
|
+
},
|
|
66
|
+
"model.inspect": {
|
|
67
|
+
// Slim/compact face (window diet): field inventory is `fieldCodes`, not fields[].
|
|
68
|
+
keys: ["schemaCode", "fieldCodes", "form", "listview", "ruleCodes", "datarules", "workflow", "hint"],
|
|
69
|
+
notes: ["slim 面键是 fieldCodes(不是 fields[].code)"],
|
|
70
|
+
},
|
|
71
|
+
"field.list": {
|
|
72
|
+
keys: ["fields:array<{code,name,type,...}>", "modelPresent?"],
|
|
73
|
+
notes: ["模型不存在时平台可能 refused(300004);存在性判据优先 model.inspect.modelPresent / rule.list.modelPresent"],
|
|
74
|
+
},
|
|
75
|
+
"workitem.list": {
|
|
76
|
+
// Flat rows — no fields bag. Wire id is projected as workItemId.
|
|
77
|
+
keys: ["items:array<{workItemId,activityCode,...}>", "total", "count"],
|
|
78
|
+
notes: ["待办 id 键是 workItemId,不是 id(平铺,不套 fields)"],
|
|
79
|
+
},
|
|
80
|
+
"rule.list": {
|
|
81
|
+
keys: ["rules:array<{code,name,enabled,status}>", "modelPresent", "source", "executableHint"],
|
|
82
|
+
notes: [
|
|
83
|
+
"入参/他面叫 ruleCode,本面返回 rules[].code",
|
|
84
|
+
"source=published-list:enabled/status 来自 PUBLISH 投影;可执行性看 rule.get / rule.enable.verified",
|
|
85
|
+
"modelPresent:false 时未发 list(避免幻影 schema 补建 7 条预置)",
|
|
86
|
+
],
|
|
87
|
+
},
|
|
88
|
+
"datarule.list": {
|
|
89
|
+
keys: ["rules:array<{id,propertyCode,dataRuleType,checkType,optionsKeys?}>", "rulesCount", "modelPresent", "rulesOmitted?"],
|
|
90
|
+
notes: [
|
|
91
|
+
"超阈值时内联 rules 是库存摘要(rulesOmitted:true),完整 options 在 detailArtifactId",
|
|
92
|
+
"模型不存在 → ok + modelPresent:false + rules:[](与 rule.list 同口径)",
|
|
93
|
+
],
|
|
94
|
+
},
|
|
95
|
+
"form.get": {
|
|
96
|
+
keys: ["present", "published", "publishedControlCodes", "draftControlCodes"],
|
|
97
|
+
notes: ["控件清单键是 publishedControlCodes/draftControlCodes,不是 controls"],
|
|
98
|
+
},
|
|
99
|
+
"listview.get": {
|
|
100
|
+
keys: ["present", "published", "lifecycle", "columns[]"],
|
|
101
|
+
},
|
|
102
|
+
"service.list": {
|
|
103
|
+
// r28f F1:行内带 adapterCode;顶栏 adapterDistribution 是选型聚合。
|
|
104
|
+
keys: ["services:array<{code,name,adapterCode}>", "codes", "adapterDistribution", "servicesCount"],
|
|
105
|
+
notes: ["选型看 adapterDistribution / services[].adapterCode;完整白名单(含 id/category)在 detailArtifactId"],
|
|
106
|
+
},
|
|
107
|
+
"method.get": {
|
|
108
|
+
// r28f F3:单条读面——参数面内联,不是集合读的 omitted 占位。
|
|
109
|
+
keys: ["serviceCode", "methodCode", "present", "method:{code,name,configJson,inputParameters,outputParameters}"],
|
|
110
|
+
notes: ["单条读:method.inputParameters/outputParameters/configJson 内联;其余大字段在 detailArtifactId"],
|
|
111
|
+
},
|
|
112
|
+
"service.adapter_config": {
|
|
113
|
+
// r28f F5:顶栏 adapterCodes 摘要;compact:true 只回 code/name/description。
|
|
114
|
+
keys: ["adapterCodes", "adapters:array<{code,name,config?...}>", "rawPresent"],
|
|
115
|
+
notes: ["选型先看 adapterCodes;完整 config 默认内联,compact:true 只回 code/name/description"],
|
|
116
|
+
},
|
|
117
|
+
"dictionary.list": {
|
|
118
|
+
keys: ["dictionaries:{totalElements,totalPages,content[]}"],
|
|
119
|
+
},
|
|
120
|
+
// r28h:组织读面——层级键/成员 id 是本面的全部价值,别猜成 code|name。
|
|
121
|
+
"org.department.list": {
|
|
122
|
+
keys: ["count", "departments:array<{id,name,code,parentId,corpId?}>"],
|
|
123
|
+
notes: ["层级看 departments[].parentId(compact 也保形,不会压成 id|name)"],
|
|
124
|
+
},
|
|
125
|
+
"org.role.list": {
|
|
126
|
+
keys: ["count", "roleCount", "groupCount", "roles:array<{id,name,code,group,groupId,groupCode}>", "groups:array<{id,name,code,parentId?,path,childCount}>"],
|
|
127
|
+
notes: ["roles[].group 是组名;写角色请用 roles[].groupId / groupCode(或 groups[].id/code)"],
|
|
128
|
+
},
|
|
129
|
+
"org.user.search": {
|
|
130
|
+
keys: ["keyword", "count", "total", "users:array<{id,name,username,departmentId}>", "departmentsScanned?"],
|
|
131
|
+
notes: ["≤50 人内联 {id,name,username,departmentId};更大才外置 artifact"],
|
|
132
|
+
},
|
|
133
|
+
"org.role.users": {
|
|
134
|
+
keys: ["roleId", "count", "total", "members:array<{id,name,username,departmentId,scopeCount}>"],
|
|
135
|
+
notes: ["roleId 与 roleCode 二者其一;members[].id 是用户 recId"],
|
|
136
|
+
},
|
|
137
|
+
"org.role.userScope": {
|
|
138
|
+
keys: ["roleId", "userId", "count", "scopes:array<{id,name,type,deptPath}>"],
|
|
139
|
+
notes: ["不是枚举档位:scopes 是部门节点列表;roleId 与 roleCode 二者其一"],
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
/** Lookup the authored return shape for a tool, if any. */
|
|
143
|
+
export function readShapeFor(toolName) {
|
|
144
|
+
return READ_SHAPES[toolName];
|
|
145
|
+
}
|
|
146
|
+
//# sourceMappingURL=read-shapes.js.map
|