@stalfh233/omc-cli 0.0.0-stage → 0.4.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/LICENSE +21 -0
- package/README.md +293 -3
- package/coverage/m1-coverage-manifest-v1.json +1278 -0
- package/dist/approval-token.js +102 -0
- package/dist/args.js +25 -0
- package/dist/artifacts.js +119 -0
- package/dist/bench/call-face-eval.js +256 -0
- package/dist/bench/context-attribution.js +151 -0
- package/dist/bench/discovery-cost-eval.js +230 -0
- package/dist/bench/driver.js +81 -0
- package/dist/bench/evals.js +236 -0
- package/dist/bench/fake-http-server.js +65 -0
- package/dist/bench/instrument.js +87 -0
- package/dist/bench/intent-face-eval.js +343 -0
- package/dist/bench/run.js +166 -0
- package/dist/bench/scenario.js +343 -0
- package/dist/bench/types.js +76 -0
- package/dist/bi-wire.js +41 -0
- package/dist/bizservice-config.js +581 -0
- package/dist/call.js +153 -0
- package/dist/capability-absences.js +23 -0
- package/dist/capability-overview.js +492 -0
- package/dist/capability-shape.js +154 -0
- package/dist/cli-contract.js +70 -0
- package/dist/cli-output.js +73 -0
- package/dist/cli.js +1418 -0
- package/dist/code-rules.js +69 -0
- package/dist/command-transport.js +155 -0
- package/dist/config-store.js +195 -0
- package/dist/context.js +21 -0
- package/dist/contract-consistency.js +66 -0
- package/dist/contract-resources.js +62 -0
- package/dist/coverage-consistency.js +62 -0
- package/dist/coverage-registry.js +76 -0
- package/dist/coverage.js +130 -0
- package/dist/data-list-filter.js +114 -0
- package/dist/discovery.js +390 -0
- package/dist/endpoints.js +154 -0
- package/dist/environment-policy.js +26 -0
- package/dist/execution-metadata.js +1092 -0
- package/dist/fake/app.js +45 -0
- package/dist/fake/b2-registration.js +594 -0
- package/dist/fake/businessrule.js +242 -0
- package/dist/fake/datarule.js +72 -0
- package/dist/fake/dictionary.js +108 -0
- package/dist/fake/environment.js +30 -0
- package/dist/fake/field.js +167 -0
- package/dist/fake/form.js +101 -0
- package/dist/fake/index.js +121 -0
- package/dist/fake/list-view.js +289 -0
- package/dist/fake/model.js +136 -0
- package/dist/fake/online-js.js +18 -0
- package/dist/fake/report.js +253 -0
- package/dist/fake/routes.js +47 -0
- package/dist/fake/rule-lifecycle.js +37 -0
- package/dist/fake/runtime-data.js +367 -0
- package/dist/fake/state.js +67 -0
- package/dist/fake/workflow.js +364 -0
- package/dist/field-change.js +200 -0
- package/dist/field-families.js +896 -0
- package/dist/form-layout.js +111 -0
- package/dist/form-support.js +821 -0
- package/dist/goal-routes.js +468 -0
- package/dist/governed-execution.js +87 -0
- package/dist/human-summary.js +212 -0
- package/dist/identity.js +62 -0
- package/dist/intent/baseline.js +57 -0
- package/dist/intent/capabilities/bizservice.js +274 -0
- package/dist/intent/capabilities/businessrule.js +493 -0
- package/dist/intent/capabilities/datarule.js +187 -0
- package/dist/intent/capabilities/field.js +545 -0
- package/dist/intent/capabilities/form.js +136 -0
- package/dist/intent/capabilities/index.js +64 -0
- package/dist/intent/capabilities/listview.js +157 -0
- package/dist/intent/capabilities/model.js +100 -0
- package/dist/intent/capabilities/onlinejs.js +108 -0
- package/dist/intent/capabilities/report.js +355 -0
- package/dist/intent/capabilities/workflow.js +458 -0
- package/dist/intent/capability.js +6 -0
- package/dist/intent/cli.js +91 -0
- package/dist/intent/compare.js +56 -0
- package/dist/intent/compiler.js +79 -0
- package/dist/intent/dsl.js +129 -0
- package/dist/intent/plan-file.js +63 -0
- package/dist/intent/readback.js +65 -0
- package/dist/intent/schema.js +158 -0
- package/dist/intent/validation.js +30 -0
- package/dist/intent/yaml.js +315 -0
- package/dist/json-column.js +68 -0
- package/dist/lanes/app-contract.js +95 -0
- package/dist/lanes/app-coverage.js +16 -0
- package/dist/lanes/app.js +174 -0
- package/dist/lanes/apply-changes.js +231 -0
- package/dist/lanes/b2-registration-contract.js +292 -0
- package/dist/lanes/b2-registration-coverage.js +48 -0
- package/dist/lanes/b2-registration.js +1187 -0
- package/dist/lanes/businessrule-contract.js +232 -0
- package/dist/lanes/businessrule-coverage.js +16 -0
- package/dist/lanes/businessrule.js +221 -0
- package/dist/lanes/contract-support.js +65 -0
- package/dist/lanes/coverage-declaration.js +9 -0
- package/dist/lanes/datarule-contract.js +155 -0
- package/dist/lanes/datarule-coverage.js +19 -0
- package/dist/lanes/datarule-protocol.js +308 -0
- package/dist/lanes/datarule.js +818 -0
- package/dist/lanes/dictionary-contract.js +78 -0
- package/dist/lanes/dictionary-coverage.js +24 -0
- package/dist/lanes/dictionary.js +235 -0
- package/dist/lanes/environment-contract.js +70 -0
- package/dist/lanes/environment-coverage.js +14 -0
- package/dist/lanes/environment.js +163 -0
- package/dist/lanes/field-contract.js +223 -0
- package/dist/lanes/field-coverage.js +27 -0
- package/dist/lanes/field.js +374 -0
- package/dist/lanes/form-contract.js +97 -0
- package/dist/lanes/form-coverage.js +16 -0
- package/dist/lanes/form.js +185 -0
- package/dist/lanes/lane-ids.js +34 -0
- package/dist/lanes/list-view-contract.js +175 -0
- package/dist/lanes/list-view-coverage.js +20 -0
- package/dist/lanes/list-view-shapes.js +1207 -0
- package/dist/lanes/list-view.js +578 -0
- package/dist/lanes/meta-contract.js +85 -0
- package/dist/lanes/meta.js +255 -0
- package/dist/lanes/model-contract.js +168 -0
- package/dist/lanes/model-coverage.js +20 -0
- package/dist/lanes/model.js +986 -0
- package/dist/lanes/online-js-contract.js +99 -0
- package/dist/lanes/online-js-coverage.js +28 -0
- package/dist/lanes/online-js.js +127 -0
- package/dist/lanes/report-contract.js +144 -0
- package/dist/lanes/report-coverage.js +21 -0
- package/dist/lanes/report.js +476 -0
- package/dist/lanes/rule-graph.js +1846 -0
- package/dist/lanes/rule-lifecycle-contract.js +92 -0
- package/dist/lanes/rule-lifecycle-coverage.js +20 -0
- package/dist/lanes/rule-lifecycle.js +176 -0
- package/dist/lanes/runtime-data-contract.js +264 -0
- package/dist/lanes/runtime-data-coverage.js +25 -0
- package/dist/lanes/runtime-data.js +1054 -0
- package/dist/lanes/workflow-contract.js +223 -0
- package/dist/lanes/workflow-coverage.js +40 -0
- package/dist/lanes/workflow.js +1813 -0
- package/dist/online-js-layout.js +58 -0
- package/dist/online-js-source.js +276 -0
- package/dist/package-tool.js +51 -0
- package/dist/package.js +73 -0
- package/dist/plan.js +73 -0
- package/dist/read.js +144 -0
- package/dist/redact.js +28 -0
- package/dist/rule-support.js +587 -0
- package/dist/runtime-support.js +134 -0
- package/dist/server.js +92 -0
- package/dist/session-manager.js +30 -0
- package/dist/session.js +149 -0
- package/dist/skills.js +112 -0
- package/dist/support.js +98 -0
- package/dist/tool-types.js +127 -0
- package/dist/tools.js +59 -0
- package/dist/usage-log.js +197 -0
- package/dist/wire.js +287 -0
- package/dist/workflow-support.js +99 -0
- package/dist/write-lease.js +26 -0
- package/dist/write-lock.js +109 -0
- package/dist/write.js +176 -0
- package/dist/zip.js +156 -0
- package/docs/tool-surface-map.md +42 -0
- package/package.json +71 -6
- package/skills/omc-acceptance-criteria.md +47 -0
- package/skills/omc-business-configuration.md +257 -0
- package/skills/omc-capabilities.md +197 -0
- package/skills/omc-capability-scouting.md +55 -0
- package/skills/omc-config-draft-review.md +171 -0
- package/skills/omc-five-piece-flow.md +35 -0
- package/skills/omc-glossary.md +86 -0
- package/skills/omc-refusals.md +110 -0
- package/skills/omc-requirement-analysis.md +161 -0
- package/skills/omc-requirement-vocabulary.md +51 -0
- package/skills/omc-start-here.md +82 -0
- package/skills/omc-tool-selection.md +119 -0
- package/skills/omc-write-hazards.md +87 -0
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
import { boundaryOf, capabilityRouteIndex, CAPABILITY_CUES, DISCOVERY_AUTHORITY } from "./capability-overview.js";
|
|
2
|
+
import { listSkills, readSkillSection } from "./skills.js";
|
|
3
|
+
/**
|
|
4
|
+
* Mode cues. `next` used to answer every goal with the BUILD ladder, so
|
|
5
|
+
* "只读查看 X 的字段定义" was routed to field.create/form etc. (write:true) and
|
|
6
|
+
* a natural whole-app ask ("做一个 CRM 客户管理系统") matched no capability cue
|
|
7
|
+
* and fell through to `confident:false`. The mode is decided FIRST from verbs,
|
|
8
|
+
* then the capability ladder is shaped for it.
|
|
9
|
+
*
|
|
10
|
+
* Markers "能/可以/怎么" make scout win before a build verb ("怎么写在线 JS"
|
|
11
|
+
* asks whether/how, it does not by itself authorize a write).
|
|
12
|
+
*/
|
|
13
|
+
const SCOUT_CUES = ["能不能", "能不能够", "能否", "是否支持", "支持吗", "支持不支持", "可不可以", "有没有办法", "有没有可能", "怎么实现", "如何实现", "怎么写", "怎么配置"];
|
|
14
|
+
const MODIFY_CUES = ["修改", "调整", "更新", "重命名", "改名", "编辑", "删除", "去掉", "移除", "改为", "改成"];
|
|
15
|
+
const READ_CUES = ["查看", "读回", "盘点", "现状", "列出", "有哪些", "有没有", "检查", "审计", "导出", "只读", "看看", "看下", "查一下", "查询一下", "查一查", "梳理", "清点", "给我一份", "哪些", "是什么", "是否", "是否正确", "现有", "已有"];
|
|
16
|
+
const BUILD_CUES = ["新建", "创建", "搭建", "从零", "从无到有", "做", "建", "加", "实现", "生成", "新增", "保存", "启用", "录入", "添加"];
|
|
17
|
+
/** Whole-application asks — a single-model atom is the wrong shape for these. */
|
|
18
|
+
const WHOLE_APP_CUES = ["应用", "系统", "整套", "从零", "从无到有", "端到端", "一条龙", "一整套", "业务应用", "平台"];
|
|
19
|
+
function looksWholePackage(goal, capabilityCount) {
|
|
20
|
+
if (capabilityCount >= 4)
|
|
21
|
+
return true;
|
|
22
|
+
return WHOLE_APP_CUES.some((cue) => goal.includes(cue));
|
|
23
|
+
}
|
|
24
|
+
function detectMode(goal) {
|
|
25
|
+
if (SCOUT_CUES.some((cue) => goal.includes(cue)))
|
|
26
|
+
return "scout";
|
|
27
|
+
const modify = MODIFY_CUES.some((cue) => goal.includes(cue));
|
|
28
|
+
const build = BUILD_CUES.some((cue) => goal.includes(cue));
|
|
29
|
+
if (modify)
|
|
30
|
+
return "modify";
|
|
31
|
+
if (build)
|
|
32
|
+
return "build";
|
|
33
|
+
if (READ_CUES.some((cue) => goal.includes(cue)))
|
|
34
|
+
return "read";
|
|
35
|
+
// No verb: keep the historical build ladder (it is the safe default shape).
|
|
36
|
+
return "build";
|
|
37
|
+
}
|
|
38
|
+
/** The read tools of a lane (kind:read), for read/query mode. */
|
|
39
|
+
function readToolsInLane(registry, lane) {
|
|
40
|
+
return registry.filter((tool) => tool.lane === lane && tool.kind === "read").map((tool) => tool.name);
|
|
41
|
+
}
|
|
42
|
+
/** The declarative (intent) path a whole-application build should prefer. */
|
|
43
|
+
function intentGuidance() {
|
|
44
|
+
return {
|
|
45
|
+
path: "declarative",
|
|
46
|
+
why: "整包需求:用一份声明式意图文件描述 models/fields/form/listview/rules/workflow/onlinejs/services,由编译器合成平台原生结构并按依赖顺序执行;比逐能力拼装更省上下文、更不易漂移。",
|
|
47
|
+
commands: [
|
|
48
|
+
"omc intent schema [--example <id>]:离线看 intent YAML 的键位/操作符/可运行示例(写 intent 前先看,省去猜键名与试错)",
|
|
49
|
+
"omc intent plan <file> --env <env>:离线先校验(形体错在接触环境前返回,故 validate 可选);大 plan 自动外置,回 planExternalized:true + planFile,直接 `omc intent apply <planFile>`;小 plan 才内联在 result.plan",
|
|
50
|
+
"omc intent apply <planFile|plan.json>(默认最终读回等价校验;--no-verify 关闭)",
|
|
51
|
+
"omc intent readback <file> --env <env>;validate [--show-intent](默认只回 digest)",
|
|
52
|
+
],
|
|
53
|
+
coverageNote: "intent 只覆盖部分能力(capabilities 的 intent: partial);未覆盖的能力(应用/字典/业务集成/运行数据等)仍用下面的阶梯或 omc call 补齐。",
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function firstSentence(text) {
|
|
57
|
+
const collapsed = text.replace(/\s+/g, " ").trim();
|
|
58
|
+
const stop = collapsed.search(/[。.;;!!??]/);
|
|
59
|
+
return stop >= 0 ? collapsed.slice(0, stop + 1) : collapsed;
|
|
60
|
+
}
|
|
61
|
+
/** CJK bigrams — used only to rank a capability's recipes, never to route. */
|
|
62
|
+
function bigrams(text) {
|
|
63
|
+
const run = text.toLowerCase().match(/[\u4e00-\u9fff]+/g)?.join("") ?? "";
|
|
64
|
+
const grams = new Set();
|
|
65
|
+
for (let index = 0; index < run.length - 1; index += 1)
|
|
66
|
+
grams.add(run.slice(index, index + 2));
|
|
67
|
+
return grams;
|
|
68
|
+
}
|
|
69
|
+
function overlap(left, right) {
|
|
70
|
+
const a = bigrams(left);
|
|
71
|
+
const b = bigrams(right);
|
|
72
|
+
let count = 0;
|
|
73
|
+
for (const gram of a)
|
|
74
|
+
if (b.has(gram))
|
|
75
|
+
count += 1;
|
|
76
|
+
return count;
|
|
77
|
+
}
|
|
78
|
+
/** Capability scores from the authored cues, plus title/alias matches. */
|
|
79
|
+
function capabilityScores(registry, goal) {
|
|
80
|
+
const lower = goal.toLowerCase();
|
|
81
|
+
const scores = new Map();
|
|
82
|
+
for (const route of capabilityRouteIndex(registry).routes) {
|
|
83
|
+
let score = 0;
|
|
84
|
+
for (const cue of CAPABILITY_CUES[route.id] ?? [])
|
|
85
|
+
if (lower.includes(cue.toLowerCase()))
|
|
86
|
+
score += cue.length;
|
|
87
|
+
for (const alias of route.aliases)
|
|
88
|
+
if (lower.includes(alias.toLowerCase()))
|
|
89
|
+
score += alias.length;
|
|
90
|
+
if (lower.includes(route.id.toLowerCase()))
|
|
91
|
+
score += route.id.length;
|
|
92
|
+
if (score > 0)
|
|
93
|
+
scores.set(route.id, score);
|
|
94
|
+
}
|
|
95
|
+
return scores;
|
|
96
|
+
}
|
|
97
|
+
function bestRecipe(registry, capabilityId, lane, goal, minScore = 3) {
|
|
98
|
+
let best;
|
|
99
|
+
for (const tool of registry) {
|
|
100
|
+
if (tool.lane !== lane)
|
|
101
|
+
continue;
|
|
102
|
+
for (const recipe of tool.executionMetadata?.recipes ?? []) {
|
|
103
|
+
const score = overlap(goal, `${recipe.title} ${recipe.id}`);
|
|
104
|
+
if (score >= minScore && (best === undefined || score > best.score))
|
|
105
|
+
best = { tool: tool.name, recipe: recipe.id, title: recipe.title, score };
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return best;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* The write tools a capability step will actually call, as a compact
|
|
112
|
+
* `{ toolName: [requiredArg] }` map. `omc next` inlines this so an agent stops
|
|
113
|
+
* paying a separate `commands --json --compact --lane <lane>` (1–5 KB) per
|
|
114
|
+
* capability just to learn the tool names and required args — bench19 spent 8
|
|
115
|
+
* such calls (31 KB) on a two-table task. Names + required keys are all the
|
|
116
|
+
* lane bundle's actionable payload; the arg *shapes* still come from the
|
|
117
|
+
* recipe/describe. The map form (not an array of `{tool,required}`) keeps the
|
|
118
|
+
* ladder inside its byte budget.
|
|
119
|
+
*/
|
|
120
|
+
function stepTools(registry, lane) {
|
|
121
|
+
const tools = {};
|
|
122
|
+
for (const tool of registry) {
|
|
123
|
+
if (tool.lane !== lane || tool.kind !== "write")
|
|
124
|
+
continue;
|
|
125
|
+
tools[tool.name] = Object.entries(tool.inputSchema)
|
|
126
|
+
.filter(([name, schema]) => name !== "environment" && name !== "capability" && name !== "confirmProduction" && name !== "requestId" && !schema.isOptional())
|
|
127
|
+
.map(([name]) => name);
|
|
128
|
+
}
|
|
129
|
+
return tools;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Round 13 — methodology by section, not by whole topic.
|
|
133
|
+
*
|
|
134
|
+
* `next` used to answer a build ask with `readTopics` — whole topic names such
|
|
135
|
+
* as `omc-business-configuration` (13.5 KB) and `omc-config-draft-review`
|
|
136
|
+
* (10.3 KB). An agent reads them once and pays every later turn; the eight
|
|
137
|
+
* mandatory topics are 54,674 B (~13.7k tokens) and alone exceed the 50k task
|
|
138
|
+
* budget. The section read surface already exists (`omc help <topic>
|
|
139
|
+
* --section`), and a targeted section is 400–3,400 B. This registry maps each
|
|
140
|
+
* capability to the sections it actually needs and the route returns one
|
|
141
|
+
* runnable `--section` command per pointer, so the cheap face is the default.
|
|
142
|
+
*
|
|
143
|
+
* Precision matters: a section title that does not resolve would push the
|
|
144
|
+
* agent back to the whole topic. `test/goal-sections.test.ts` asserts every
|
|
145
|
+
* authored section resolves.
|
|
146
|
+
*/
|
|
147
|
+
export const CAPABILITY_SECTIONS = Object.freeze({
|
|
148
|
+
environment: [
|
|
149
|
+
{ topic: "omc-start-here", sections: ["最小路径(先读这段就能开始)"] },
|
|
150
|
+
{ topic: "omc-write-hazards", sections: ["固定验收顺序"] },
|
|
151
|
+
],
|
|
152
|
+
app: [
|
|
153
|
+
{ topic: "omc-business-configuration", sections: ["标准顺序", "运行验收与清理"] },
|
|
154
|
+
{ topic: "omc-write-hazards", sections: ["固定验收顺序"] },
|
|
155
|
+
],
|
|
156
|
+
dictionary: [
|
|
157
|
+
{ topic: "omc-business-configuration", sections: ["模型与字段"] },
|
|
158
|
+
{ topic: "omc-tool-selection", sections: ["High layer — orchestrated CLI calls"] },
|
|
159
|
+
],
|
|
160
|
+
model: [
|
|
161
|
+
{ topic: "omc-config-draft-review", sections: ["3. Draft template", "8. Canonical configuration order(规范配置顺序)"] },
|
|
162
|
+
{ topic: "omc-business-configuration", sections: ["标准顺序", "模型与字段"] },
|
|
163
|
+
{ topic: "omc-write-hazards", sections: ["模型字段与关联"] },
|
|
164
|
+
],
|
|
165
|
+
field: [
|
|
166
|
+
{ topic: "omc-business-configuration", sections: ["模型与字段", "子表"] },
|
|
167
|
+
{ topic: "omc-write-hazards", sections: ["模型字段与关联", "子表"] },
|
|
168
|
+
],
|
|
169
|
+
form: [
|
|
170
|
+
{ topic: "omc-business-configuration", sections: ["表单与视图"] },
|
|
171
|
+
{ topic: "omc-write-hazards", sections: ["表单与 Sheet201"] },
|
|
172
|
+
],
|
|
173
|
+
listview: [
|
|
174
|
+
{ topic: "omc-business-configuration", sections: ["表单与视图"] },
|
|
175
|
+
{ topic: "omc-write-hazards", sections: ["视图"] },
|
|
176
|
+
],
|
|
177
|
+
datarule: [
|
|
178
|
+
{ topic: "omc-business-configuration", sections: ["业务规则", "条件式(必填/只读/显示)怎么写"] },
|
|
179
|
+
{ topic: "omc-refusals", sections: ["3. Semantic preflight — the payload is malformed"] },
|
|
180
|
+
],
|
|
181
|
+
businessrule: [
|
|
182
|
+
{ topic: "omc-business-configuration", sections: ["业务规则", "规则图的触发条件(关键)"] },
|
|
183
|
+
{ topic: "omc-refusals", sections: ["3. Semantic preflight — the payload is malformed"] },
|
|
184
|
+
],
|
|
185
|
+
"rule-lifecycle": [
|
|
186
|
+
{ topic: "omc-business-configuration", sections: ["业务规则"] },
|
|
187
|
+
{ topic: "omc-refusals", sections: ["5. Dependency & async convergence"] },
|
|
188
|
+
],
|
|
189
|
+
workflow: [
|
|
190
|
+
{ topic: "omc-business-configuration", sections: ["流程字段权限"] },
|
|
191
|
+
{ topic: "omc-refusals", sections: ["5. Dependency & async convergence"] },
|
|
192
|
+
],
|
|
193
|
+
onlinejs: [
|
|
194
|
+
{ topic: "omc-business-configuration", sections: ["按钮与在线 JS"] },
|
|
195
|
+
{ topic: "omc-write-hazards", sections: ["在线 JS"] },
|
|
196
|
+
],
|
|
197
|
+
bizservice: [
|
|
198
|
+
{ topic: "omc-business-configuration", sections: ["业务集成"] },
|
|
199
|
+
{ topic: "omc-write-hazards", sections: ["规则与业务集成"] },
|
|
200
|
+
],
|
|
201
|
+
data: [
|
|
202
|
+
{ topic: "omc-business-configuration", sections: ["运行验收与清理"] },
|
|
203
|
+
{ topic: "omc-refusals", sections: ["4. Readback mismatch — the write landed but does not match", "6. Idempotency & replay"] },
|
|
204
|
+
],
|
|
205
|
+
});
|
|
206
|
+
function sectionPointersFor(ids) {
|
|
207
|
+
const seen = new Set();
|
|
208
|
+
const pointers = [];
|
|
209
|
+
for (const id of ids)
|
|
210
|
+
for (const entry of CAPABILITY_SECTIONS[id] ?? []) {
|
|
211
|
+
for (const section of entry.sections) {
|
|
212
|
+
const key = `${entry.topic}\u0000${section}`;
|
|
213
|
+
if (seen.has(key))
|
|
214
|
+
continue;
|
|
215
|
+
seen.add(key);
|
|
216
|
+
pointers.push({ topic: entry.topic, section });
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return pointers;
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Ready-to-run methodology reads, one per topic, that fetch ALL of that topic's
|
|
223
|
+
* relevant sections in a single call. Round 15: bench60-62 fetched each
|
|
224
|
+
* `readSections` entry separately (~18 round-trips); `--section` is repeatable
|
|
225
|
+
* and comma-separated, so grouping by topic collapses them to ~3 without adding
|
|
226
|
+
* any bytes (the outputs land in context either way — the saving is turns).
|
|
227
|
+
*/
|
|
228
|
+
function batchReadCommands(pointers) {
|
|
229
|
+
const byTopic = new Map();
|
|
230
|
+
for (const pointer of pointers)
|
|
231
|
+
byTopic.set(pointer.topic, [...(byTopic.get(pointer.topic) ?? []), pointer.section]);
|
|
232
|
+
return [...byTopic.entries()].map(([topic, sections]) => `omc help ${topic} ${sections.map((section) => `--section "${section}"`).join(" ")}`);
|
|
233
|
+
}
|
|
234
|
+
/** The route's methodology-read surface: pointers (+ per-section bytes) + ready-to-run batched commands + hint. */
|
|
235
|
+
function routeReads(readSections) {
|
|
236
|
+
// Per-section UTF-8 bytes ride each pointer (one field, not a parallel array)
|
|
237
|
+
// so a caller can budget which sections to fetch — dogfood R1 found the
|
|
238
|
+
// batched readCommands could exceed a window with no way to see sizes.
|
|
239
|
+
const budgeted = readSections.map((pointer) => ({ ...pointer, bytes: sectionBytes(pointer) }));
|
|
240
|
+
return { readSections: budgeted, readCommands: batchReadCommands(readSections), readHint: READ_HINT };
|
|
241
|
+
}
|
|
242
|
+
/** UTF-8 byte length of one methodology section (best-effort; 0 when unreadable). */
|
|
243
|
+
function sectionBytes(pointer) {
|
|
244
|
+
const resolved = readSkillSection(pointer.topic, pointer.section);
|
|
245
|
+
return resolved ? Buffer.byteLength(resolved.section, "utf8") : 0;
|
|
246
|
+
}
|
|
247
|
+
const READ_HINT = '按 readCommands 一次读完(每条命令用可重复的 `--section` 取该 topic 的所有相关节,等价于逐节 `omc help <topic> --section "<section>"` 但只花一次往返);单节字节数见 readSectionBudget,按预算自选。除非确需全貌,不要 `omc help <topic>` 读整篇(5–14KB,读一次就留在后续每一轮)。';
|
|
248
|
+
function orderedCapabilities(ids, index) {
|
|
249
|
+
const selected = new Set(ids);
|
|
250
|
+
const nodes = [...index.nodeById.values()];
|
|
251
|
+
const order = new Map(nodes.map((node, position) => [node.id, position]));
|
|
252
|
+
const visiting = new Set();
|
|
253
|
+
const visited = new Set();
|
|
254
|
+
const result = [];
|
|
255
|
+
const visit = (id) => {
|
|
256
|
+
if (!selected.has(id) || visited.has(id) || visiting.has(id))
|
|
257
|
+
return;
|
|
258
|
+
visiting.add(id);
|
|
259
|
+
for (const required of index.nodeById.get(id)?.requires ?? [])
|
|
260
|
+
visit(required);
|
|
261
|
+
visiting.delete(id);
|
|
262
|
+
visited.add(id);
|
|
263
|
+
result.push(id);
|
|
264
|
+
};
|
|
265
|
+
[...selected].sort((left, right) => (order.get(left) ?? Number.MAX_SAFE_INTEGER) - (order.get(right) ?? Number.MAX_SAFE_INTEGER)).forEach(visit);
|
|
266
|
+
return result;
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* A whole-application BUILD ask that names no capability ("做一个 CRM 系统")
|
|
270
|
+
* must not fall through as `confident:false` — that is the most natural way to
|
|
271
|
+
* ask for the declarative path. Route it to the declarative path as the PRIMARY
|
|
272
|
+
* route, with the atomic ladder demoted to a supplement.
|
|
273
|
+
*
|
|
274
|
+
* It used to return `route.ladder` AND `intent` as two co-equal paths, and the
|
|
275
|
+
* caller had to choose (bench40: agents wandered between them). The declarative
|
|
276
|
+
* path is the intended shape for a cross-object write, so it is now advertised
|
|
277
|
+
* via `route.primary:"intent"`, and the atomic tools move under
|
|
278
|
+
* `route.atomicSupplement` — what intent does not cover (delete/lifecycle/
|
|
279
|
+
* runtime/exact patches), not a second main path.
|
|
280
|
+
*/
|
|
281
|
+
const WHOLE_PACKAGE_CAPABILITIES = ["model", "field", "form", "listview", "datarule", "businessrule", "workflow", "onlinejs"];
|
|
282
|
+
/** A whole-package ask (build OR modify) is a declarative-intent candidate. */
|
|
283
|
+
function isWholePackageWrite(mode, goal, capabilityCount) {
|
|
284
|
+
return (mode === "build" || mode === "modify") && looksWholePackage(goal, capabilityCount);
|
|
285
|
+
}
|
|
286
|
+
function wholePackageRoute(registry, goal, index) {
|
|
287
|
+
const routeById = new Map(index.routes.map((route) => [route.id, route]));
|
|
288
|
+
const ordered = orderedCapabilities([...WHOLE_PACKAGE_CAPABILITIES], index).filter((id) => routeById.has(id));
|
|
289
|
+
// Round 16b (dogfood R1): the supplement used to repeat every capability's
|
|
290
|
+
// full tool map (model.create/delete/…, field.create/…), ~60% of a 2.8KB
|
|
291
|
+
// response the caller never used — intent already covers these. Keep it a
|
|
292
|
+
// POINTER (capability + lane + one command), not a second ladder.
|
|
293
|
+
const ladder = ordered.map((id) => {
|
|
294
|
+
const capability = routeById.get(id);
|
|
295
|
+
return { capability: id, lane: capability.lane, firstCommand: `omc commands --json --compact --lane ${capability.lane}` };
|
|
296
|
+
});
|
|
297
|
+
return {
|
|
298
|
+
status: "ok",
|
|
299
|
+
goal,
|
|
300
|
+
confident: true,
|
|
301
|
+
mode: "whole-package",
|
|
302
|
+
match: { kind: "whole-package", ref: ordered.join("→"), capabilities: ordered },
|
|
303
|
+
route: {
|
|
304
|
+
primary: "intent",
|
|
305
|
+
atomicSupplement: { note: "intent 已覆盖下列能力的创建;这些原子工具只用于 intent 未覆盖面(删除/生命周期/运行数据/精确 patch):", ladder },
|
|
306
|
+
...routeReads(sectionPointersFor(ordered)),
|
|
307
|
+
firstCommands: [DISCOVERY_AUTHORITY.readiness],
|
|
308
|
+
write: true,
|
|
309
|
+
},
|
|
310
|
+
write: true,
|
|
311
|
+
intent: intentGuidance(),
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
/** A configuration read-back ask that names no capability ("给我一份现状说明"). */
|
|
315
|
+
const OVERVIEW_READ_CAPABILITIES = ["environment", "model", "field", "form", "listview", "datarule", "businessrule", "workflow", "data"];
|
|
316
|
+
function readOverviewRoute(registry, goal, index) {
|
|
317
|
+
const routeById = new Map(index.routes.map((route) => [route.id, route]));
|
|
318
|
+
const ordered = orderedCapabilities([...OVERVIEW_READ_CAPABILITIES], index).filter((id) => routeById.has(id));
|
|
319
|
+
const ladder = ordered.map((id) => ({ capability: id, lane: routeById.get(id).lane, readTools: readToolsInLane(registry, routeById.get(id).lane), firstCommand: `omc capability ${id}` }));
|
|
320
|
+
return {
|
|
321
|
+
status: "ok",
|
|
322
|
+
goal,
|
|
323
|
+
confident: true,
|
|
324
|
+
mode: "read",
|
|
325
|
+
match: { kind: "read-overview", ref: ordered.join("→"), capabilities: ordered },
|
|
326
|
+
route: { ladder, ...routeReads(sectionPointersFor(["environment"])), firstCommands: [DISCOVERY_AUTHORITY.readiness], write: false, readback: true },
|
|
327
|
+
write: false,
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
export function goalRoute(registry, goal) {
|
|
331
|
+
const index = capabilityRouteIndex(registry);
|
|
332
|
+
const routeById = new Map(index.routes.map((route) => [route.id, route]));
|
|
333
|
+
const registryTools = new Map(registry.map((tool) => [tool.name, tool]));
|
|
334
|
+
const availableTopics = new Set(listSkills().map((topic) => topic.name));
|
|
335
|
+
const mode = detectMode(goal);
|
|
336
|
+
const readish = mode === "read" || mode === "scout";
|
|
337
|
+
// Boundary FIRST. An ask that names an out-of-coverage face (OCR, release/
|
|
338
|
+
// prod, inline coding, nested child, cross-model aggregation) must not be
|
|
339
|
+
// matched to a nearby capability and handed the write ladder — that is the
|
|
340
|
+
// "invent a capability for a blocked face" anti-pattern. Return a boundary
|
|
341
|
+
// result pointing at capability-scouting + the (read-only) coverage proof.
|
|
342
|
+
const boundary = boundaryOf(goal);
|
|
343
|
+
if (boundary) {
|
|
344
|
+
return {
|
|
345
|
+
status: "ok",
|
|
346
|
+
goal,
|
|
347
|
+
confident: true,
|
|
348
|
+
mode: "scout",
|
|
349
|
+
write: false,
|
|
350
|
+
match: { kind: "boundary", ref: boundary.id, status: boundary.status, cue: boundary.cue },
|
|
351
|
+
boundary: {
|
|
352
|
+
id: boundary.id,
|
|
353
|
+
status: boundary.status,
|
|
354
|
+
note: boundary.note,
|
|
355
|
+
verify: "omc capabilities / omc capability <id>(运行时清单才是权威边界;能力表只是阅读辅助)",
|
|
356
|
+
topics: ["omc-requirement-analysis", "omc-capability-scouting"].filter((topic) => availableTopics.has(topic)),
|
|
357
|
+
},
|
|
358
|
+
route: { write: false, readback: false, firstCommands: ["omc capabilities", "omc capability <id>", "omc help omc-capability-scouting"] },
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
const scores = [...capabilityScores(registry, goal).entries()].sort((left, right) => right[1] - left[1]);
|
|
362
|
+
const fallback = {
|
|
363
|
+
status: "ok",
|
|
364
|
+
goal,
|
|
365
|
+
confident: false,
|
|
366
|
+
route: null,
|
|
367
|
+
fallback: "omc-requirement-analysis",
|
|
368
|
+
topics: [...availableTopics],
|
|
369
|
+
write: false,
|
|
370
|
+
mode,
|
|
371
|
+
};
|
|
372
|
+
const best = scores[0];
|
|
373
|
+
if (best === undefined) {
|
|
374
|
+
if (isWholePackageWrite(mode, goal, 0))
|
|
375
|
+
return wholePackageRoute(registry, goal, index);
|
|
376
|
+
if (readish)
|
|
377
|
+
return readOverviewRoute(registry, goal, index);
|
|
378
|
+
return fallback;
|
|
379
|
+
}
|
|
380
|
+
const ladderIds = scores.filter((entry) => entry[1] >= 2).map((entry) => entry[0]);
|
|
381
|
+
if (ladderIds.length >= 2) {
|
|
382
|
+
const ordered = orderedCapabilities(ladderIds, index);
|
|
383
|
+
if (ordered.length !== ladderIds.length) {
|
|
384
|
+
return { ...fallback, fallback: "目标包含多个无法排序的能力,请拆成独立目标", match: { kind: "ambiguous-ladder", capabilities: ladderIds } };
|
|
385
|
+
}
|
|
386
|
+
// Each step inlines its capability's write tools + required arg names, so an
|
|
387
|
+
// agent can go `next` → `call` without the per-capability `commands --lane`
|
|
388
|
+
// inventory crawl (bench19: 8 such calls, 31 KB). The matched recipe (when
|
|
389
|
+
// one exists) gives the arg SHAPE in ~1–2 KB; the lane bundle remains the
|
|
390
|
+
// fallback for tools with no recipe. In read/scout mode the steps list the
|
|
391
|
+
// READ tools instead — a read-back ask must not be answered with field.create.
|
|
392
|
+
const ladder = ordered.map((id) => {
|
|
393
|
+
const capability = routeById.get(id);
|
|
394
|
+
if (readish) {
|
|
395
|
+
return { capability: id, lane: capability.lane, readTools: readToolsInLane(registry, capability.lane), firstCommand: `omc capability ${id}` };
|
|
396
|
+
}
|
|
397
|
+
const recipe = bestRecipe(registry, id, capability.lane, goal);
|
|
398
|
+
return {
|
|
399
|
+
capability: id,
|
|
400
|
+
lane: capability.lane,
|
|
401
|
+
tools: stepTools(registry, capability.lane),
|
|
402
|
+
...(recipe
|
|
403
|
+
? { recipe: { tool: recipe.tool, id: recipe.recipe, command: `omc describe ${recipe.tool} --example ${recipe.recipe}` } }
|
|
404
|
+
: { firstCommand: `omc commands --json --compact --lane ${capability.lane}` }),
|
|
405
|
+
};
|
|
406
|
+
});
|
|
407
|
+
const readSections = sectionPointersFor(ordered);
|
|
408
|
+
const write = readish ? false : ordered.some((id) => routeById.get(id)?.tools.some((name) => registryTools.get(name)?.kind === "write"));
|
|
409
|
+
const whole = isWholePackageWrite(mode, goal, ordered.length);
|
|
410
|
+
// When intent is the primary path, the atomic ladder is only a POINTER to the
|
|
411
|
+
// capabilities (dogfood R1: repeating every tool map was ~60% unused bytes).
|
|
412
|
+
const atomicSupplement = { note: "intent 已覆盖这些能力的创建;原子工具只用于 intent 未覆盖面:", ladder: ladder.map((step) => ({ capability: step.capability, lane: step.lane, firstCommand: step.firstCommand ?? `omc commands --json --compact --lane ${step.lane}` })) };
|
|
413
|
+
return {
|
|
414
|
+
status: "ok",
|
|
415
|
+
goal,
|
|
416
|
+
confident: true,
|
|
417
|
+
mode: whole ? "whole-package" : mode,
|
|
418
|
+
match: { kind: readish ? "read-ladder" : "ladder", ref: ordered.join("→"), capabilities: ordered },
|
|
419
|
+
route: { ...(whole ? { primary: "intent", atomicSupplement } : { ladder }), ...routeReads(readSections), firstCommands: [DISCOVERY_AUTHORITY.readiness], write, ...(readish ? { readback: true } : {}) },
|
|
420
|
+
write,
|
|
421
|
+
...(whole ? { intent: intentGuidance() } : {}),
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
// Single-capability mode: when several capabilities tie, a matching recipe
|
|
425
|
+
// breaks the tie; an unresolved tie falls back instead of guessing.
|
|
426
|
+
const capabilityId = best[0];
|
|
427
|
+
const route = routeById.get(capabilityId);
|
|
428
|
+
if (!route)
|
|
429
|
+
return fallback;
|
|
430
|
+
const write = readish ? false : route.tools.some((name) => registryTools.get(name)?.kind === "write");
|
|
431
|
+
const recipe = readish ? undefined : bestRecipe(registry, capabilityId, route.lane, goal);
|
|
432
|
+
const firstCommands = [DISCOVERY_AUTHORITY.readiness];
|
|
433
|
+
if (recipe)
|
|
434
|
+
firstCommands.push(`omc describe ${recipe.tool} --example ${recipe.recipe}`);
|
|
435
|
+
if (readish)
|
|
436
|
+
firstCommands.push(`omc capability ${capabilityId}`);
|
|
437
|
+
else if (route.tools.length > 1)
|
|
438
|
+
firstCommands.push(`omc commands --json --compact --lane ${route.lane}`);
|
|
439
|
+
else
|
|
440
|
+
firstCommands.push(`omc capability ${capabilityId}`);
|
|
441
|
+
const whole = isWholePackageWrite(mode, goal, 1);
|
|
442
|
+
return {
|
|
443
|
+
status: "ok",
|
|
444
|
+
goal,
|
|
445
|
+
confident: true,
|
|
446
|
+
mode: whole ? "whole-package" : mode,
|
|
447
|
+
match: {
|
|
448
|
+
kind: readish ? "read-capability" : recipe ? "recipe" : "capability",
|
|
449
|
+
ref: recipe ? recipe.recipe : capabilityId,
|
|
450
|
+
title: firstSentence(recipe ? recipe.title : route.title),
|
|
451
|
+
capability: capabilityId,
|
|
452
|
+
...(recipe ? { tool: recipe.tool, recipe: recipe.recipe } : {}),
|
|
453
|
+
},
|
|
454
|
+
route: {
|
|
455
|
+
capability: capabilityId,
|
|
456
|
+
lane: route.lane,
|
|
457
|
+
...(whole ? { primary: "intent" } : {}),
|
|
458
|
+
...(readish ? { readTools: readToolsInLane(registry, route.lane) } : { tools: route.tools }),
|
|
459
|
+
...routeReads(sectionPointersFor([capabilityId])),
|
|
460
|
+
firstCommands,
|
|
461
|
+
write,
|
|
462
|
+
...(readish ? { readback: true } : {}),
|
|
463
|
+
},
|
|
464
|
+
write,
|
|
465
|
+
...(whole ? { intent: intentGuidance() } : {}),
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
//# sourceMappingURL=goal-routes.js.map
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { assertWithinCoverage, versionFamilyNotice } from "./coverage.js";
|
|
2
|
+
import { planFingerprint } from "./plan.js";
|
|
3
|
+
import { environmentNotice, environmentWritable, requiresProductionConfirmation } from "./environment-policy.js";
|
|
4
|
+
import { refusalFromCoverage } from "./support.js";
|
|
5
|
+
const settled = (result) => ({ kind: "settled", result });
|
|
6
|
+
/** Run the shared gate sequence; settle with a refusal or open an admission. */
|
|
7
|
+
export async function admitGovernedWrite(ctx, request) {
|
|
8
|
+
const claims = Array.isArray(request.coverage) ? request.coverage : [request.coverage];
|
|
9
|
+
try {
|
|
10
|
+
for (const claim of claims)
|
|
11
|
+
assertWithinCoverage(ctx.manifest, claim.lane, claim.scope);
|
|
12
|
+
}
|
|
13
|
+
catch (error) {
|
|
14
|
+
return settled(refusalFromCoverage(error));
|
|
15
|
+
}
|
|
16
|
+
const resolved = ctx.sessions.config().resolveEnvironment(request.environment);
|
|
17
|
+
if (!environmentWritable(resolved.profile))
|
|
18
|
+
return settled({
|
|
19
|
+
status: "refused",
|
|
20
|
+
blocker: "environment-write-disabled",
|
|
21
|
+
environment: resolved.name,
|
|
22
|
+
production: resolved.profile.production === true,
|
|
23
|
+
message: `${environmentNotice(resolved.profile)} 请在 omc setup 中开启“允许写入此环境”。`,
|
|
24
|
+
});
|
|
25
|
+
let session;
|
|
26
|
+
try {
|
|
27
|
+
session = await ctx.sessions.session(request.environment);
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
return settled({
|
|
31
|
+
status: "uncertain",
|
|
32
|
+
frozen: true,
|
|
33
|
+
reason: "wire-transport:session",
|
|
34
|
+
detail: error instanceof Error ? error.message : String(error),
|
|
35
|
+
message: "写入前会话/登录传输失败,结果未写入且已冻结;恢复后先重新发现再重试。",
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
const version = session.detectedVersion();
|
|
39
|
+
if (!version)
|
|
40
|
+
return settled({
|
|
41
|
+
status: "refused",
|
|
42
|
+
blocker: "version-family-evidence-missing",
|
|
43
|
+
message: "writes fail closed when the CloudPivot version family cannot be established",
|
|
44
|
+
});
|
|
45
|
+
// Version family is advisory now: OMC is built on 8.6, but a deployment on a
|
|
46
|
+
// different version (or product line, e.g. AI-Platform) is accepted in
|
|
47
|
+
// compatibility mode. Missing evidence still fails closed above.
|
|
48
|
+
const versionNotice = versionFamilyNotice(ctx.manifest, version);
|
|
49
|
+
// Bind the plan to THIS environment (fingerprint + notice) so the preview and
|
|
50
|
+
// every downstream artifact agree.
|
|
51
|
+
const plan = {
|
|
52
|
+
...(await request.plan(session)),
|
|
53
|
+
environment: session.environmentName,
|
|
54
|
+
environmentFingerprint: session.environmentFingerprint(),
|
|
55
|
+
environmentNotice: environmentNotice(resolved.profile),
|
|
56
|
+
...(versionNotice ? { versionNotice } : {}),
|
|
57
|
+
};
|
|
58
|
+
if (request.dryRun === true)
|
|
59
|
+
return settled((await request.dryRunResult?.(plan, session)) ?? { status: "dry-run", wouldChange: true, plan, planFingerprint: planFingerprint(plan) });
|
|
60
|
+
const replayed = request.replay?.(plan, session);
|
|
61
|
+
if (replayed !== undefined)
|
|
62
|
+
return settled(replayed);
|
|
63
|
+
// Production is a risk label, not a gate: the write is permitted, but only
|
|
64
|
+
// with an explicit per-write acknowledgement that lands in the audit trail.
|
|
65
|
+
// Missing confirmation refuses with zero wire.
|
|
66
|
+
const productionConfirmed = requiresProductionConfirmation(resolved.profile);
|
|
67
|
+
if (productionConfirmed && request.confirmProduction !== true)
|
|
68
|
+
return settled({
|
|
69
|
+
status: "refused",
|
|
70
|
+
blocker: "production-confirmation-required",
|
|
71
|
+
environment: resolved.name,
|
|
72
|
+
production: true,
|
|
73
|
+
plan,
|
|
74
|
+
message: "当前是生产环境:每次写入必须显式携带 confirmProduction:true;这是高危确认,会记入审计工件。",
|
|
75
|
+
});
|
|
76
|
+
return {
|
|
77
|
+
kind: "admitted",
|
|
78
|
+
admission: {
|
|
79
|
+
session,
|
|
80
|
+
environmentName: session.environmentName,
|
|
81
|
+
profile: resolved.profile,
|
|
82
|
+
plan,
|
|
83
|
+
...(productionConfirmed ? { productionConfirmed: true } : {}),
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=governed-execution.js.map
|