@intentius/chant 0.49.0 → 0.50.0
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/dist/audit/catalog.d.ts +13 -3
- package/dist/audit/catalog.d.ts.map +1 -1
- package/dist/audit/core.d.ts +9 -0
- package/dist/audit/core.d.ts.map +1 -1
- package/dist/audit/discover.d.ts +6 -0
- package/dist/audit/discover.d.ts.map +1 -1
- package/dist/audit/fetch.d.ts.map +1 -1
- package/dist/audit/report-html.d.ts.map +1 -1
- package/dist/audit/report-model.d.ts +6 -0
- package/dist/audit/report-model.d.ts.map +1 -1
- package/dist/audit/report.d.ts.map +1 -1
- package/dist/audit/rules-doc.d.ts.map +1 -1
- package/dist/audit/secrets.d.ts +95 -0
- package/dist/audit/secrets.d.ts.map +1 -0
- package/dist/audit/wrangler.d.ts +33 -0
- package/dist/audit/wrangler.d.ts.map +1 -0
- package/dist/build.d.ts.map +1 -1
- package/dist/cli/commands/audit.d.ts +7 -0
- package/dist/cli/commands/audit.d.ts.map +1 -1
- package/dist/cli/commands/build.d.ts +23 -0
- package/dist/cli/commands/build.d.ts.map +1 -1
- package/dist/cli/handlers/build.d.ts.map +1 -1
- package/dist/cli/handlers/components.d.ts +31 -0
- package/dist/cli/handlers/components.d.ts.map +1 -1
- package/dist/cli/handlers/lifecycle.d.ts +11 -0
- package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
- package/dist/cli/handlers/operator.d.ts +32 -0
- package/dist/cli/handlers/operator.d.ts.map +1 -0
- package/dist/cli/handlers/scenario.d.ts +39 -0
- package/dist/cli/handlers/scenario.d.ts.map +1 -0
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/mcp/server.d.ts +35 -2
- package/dist/cli/mcp/server.d.ts.map +1 -1
- package/dist/cli/mcp/types.d.ts +29 -1
- package/dist/cli/mcp/types.d.ts.map +1 -1
- package/dist/cli/registry.d.ts +14 -2
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/codegen/docs-rule-scanning.d.ts.map +1 -1
- package/dist/components/capability.d.ts +17 -2
- package/dist/components/capability.d.ts.map +1 -1
- package/dist/components/cli-support.d.ts +7 -0
- package/dist/components/cli-support.d.ts.map +1 -1
- package/dist/components/component.d.ts +15 -0
- package/dist/components/component.d.ts.map +1 -1
- package/dist/components/driver.d.ts.map +1 -1
- package/dist/components/verbs/index.d.ts +6 -1
- package/dist/components/verbs/index.d.ts.map +1 -1
- package/dist/components/verbs/run-agent.d.ts +499 -0
- package/dist/components/verbs/run-agent.d.ts.map +1 -0
- package/dist/components/verbs/sign.d.ts +30 -0
- package/dist/components/verbs/sign.d.ts.map +1 -1
- package/dist/composite.d.ts +6 -1
- package/dist/composite.d.ts.map +1 -1
- package/dist/discovery/collect.d.ts.map +1 -1
- package/dist/discovery/fold-import.d.ts +15 -1
- package/dist/discovery/fold-import.d.ts.map +1 -1
- package/dist/discovery/fold-rank.d.ts +66 -0
- package/dist/discovery/fold-rank.d.ts.map +1 -0
- package/dist/discovery/index.d.ts +15 -0
- package/dist/discovery/index.d.ts.map +1 -1
- package/dist/discovery/param-deps.d.ts +17 -0
- package/dist/discovery/param-deps.d.ts.map +1 -0
- package/dist/fold/fold.d.ts +55 -2
- package/dist/fold/fold.d.ts.map +1 -1
- package/dist/fold/subset.d.ts +21 -14
- package/dist/fold/subset.d.ts.map +1 -1
- package/dist/lexicon-schema.d.ts +2 -0
- package/dist/lexicon-schema.d.ts.map +1 -1
- package/dist/lexicon.d.ts +93 -0
- package/dist/lexicon.d.ts.map +1 -1
- package/dist/lifecycle/converge-ledger.d.ts +90 -0
- package/dist/lifecycle/converge-ledger.d.ts.map +1 -0
- package/dist/lifecycle/deep-diff.d.ts +18 -0
- package/dist/lifecycle/deep-diff.d.ts.map +1 -1
- package/dist/lifecycle/deep-observe.d.ts +9 -1
- package/dist/lifecycle/deep-observe.d.ts.map +1 -1
- package/dist/lifecycle/gate-ledger.d.ts +33 -0
- package/dist/lifecycle/gate-ledger.d.ts.map +1 -0
- package/dist/lifecycle/git.d.ts +145 -21
- package/dist/lifecycle/git.d.ts.map +1 -1
- package/dist/lifecycle/index.d.ts +4 -0
- package/dist/lifecycle/index.d.ts.map +1 -1
- package/dist/lifecycle/lease.d.ts +113 -0
- package/dist/lifecycle/lease.d.ts.map +1 -0
- package/dist/lifecycle/scenario-eval.d.ts +42 -0
- package/dist/lifecycle/scenario-eval.d.ts.map +1 -0
- package/dist/lifecycle/scenario.d.ts +163 -0
- package/dist/lifecycle/scenario.d.ts.map +1 -0
- package/dist/lifecycle/symptoms.d.ts +63 -0
- package/dist/lifecycle/symptoms.d.ts.map +1 -0
- package/dist/lint/output-docs.d.ts +94 -0
- package/dist/lint/output-docs.d.ts.map +1 -0
- package/dist/lint/post-synth.d.ts +29 -0
- package/dist/lint/post-synth.d.ts.map +1 -1
- package/dist/lint/rules/__fixtures__/comp/comp003/pass/agent-turn.component.d.ts +11 -0
- package/dist/lint/rules/__fixtures__/comp/comp003/pass/agent-turn.component.d.ts.map +1 -0
- package/dist/lsp/lexicon-providers.d.ts +7 -0
- package/dist/lsp/lexicon-providers.d.ts.map +1 -1
- package/dist/op/activity-contract.d.ts +139 -0
- package/dist/op/activity-contract.d.ts.map +1 -0
- package/dist/op/builders.d.ts +42 -2
- package/dist/op/builders.d.ts.map +1 -1
- package/dist/op/converge-rule.d.ts +161 -0
- package/dist/op/converge-rule.d.ts.map +1 -0
- package/dist/op/generate-pipeline.d.ts +39 -0
- package/dist/op/generate-pipeline.d.ts.map +1 -0
- package/dist/op/index.d.ts +14 -0
- package/dist/op/index.d.ts.map +1 -1
- package/dist/op/local-executor.d.ts.map +1 -1
- package/dist/op/op-verb-class.d.ts +42 -0
- package/dist/op/op-verb-class.d.ts.map +1 -0
- package/dist/op/operator.d.ts +128 -0
- package/dist/op/operator.d.ts.map +1 -0
- package/dist/op/step-output-ref.d.ts +187 -0
- package/dist/op/step-output-ref.d.ts.map +1 -0
- package/dist/op/types.d.ts +18 -1
- package/dist/op/types.d.ts.map +1 -1
- package/dist/provenance.d.ts +73 -3
- package/dist/provenance.d.ts.map +1 -1
- package/dist/runtime-adapter.d.ts +7 -1
- package/dist/runtime-adapter.d.ts.map +1 -1
- package/dist/serializer.d.ts +18 -0
- package/dist/serializer.d.ts.map +1 -1
- package/dist/toml.d.ts +40 -5
- package/dist/toml.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/audit/catalog.test.ts +1 -1
- package/src/audit/catalog.ts +75 -3
- package/src/audit/core.ts +9 -0
- package/src/audit/discover.ts +29 -2
- package/src/audit/fetch.test.ts +216 -3
- package/src/audit/fetch.ts +270 -59
- package/src/audit/report-html.ts +5 -2
- package/src/audit/report-model.ts +9 -0
- package/src/audit/report.test.ts +22 -0
- package/src/audit/report.ts +3 -2
- package/src/audit/rules-doc.ts +2 -0
- package/src/audit/secrets.test.ts +303 -0
- package/src/audit/secrets.ts +406 -0
- package/src/audit/wrangler.test.ts +230 -0
- package/src/audit/wrangler.ts +290 -0
- package/src/build.ts +8 -3
- package/src/cli/command-group.ts +1 -1
- package/src/cli/commands/__fixtures__/schemas/sarif-2.1.0.schema.json +2882 -0
- package/src/cli/commands/audit.test.ts +215 -1
- package/src/cli/commands/audit.ts +86 -17
- package/src/cli/commands/build.test.ts +167 -2
- package/src/cli/commands/build.ts +114 -23
- package/src/cli/handlers/build.ts +2 -0
- package/src/cli/handlers/components.test.ts +199 -1
- package/src/cli/handlers/components.ts +160 -3
- package/src/cli/handlers/graph.test.ts +20 -0
- package/src/cli/handlers/graph.ts +10 -1
- package/src/cli/handlers/lifecycle.ts +12 -4
- package/src/cli/handlers/operator.test.ts +255 -0
- package/src/cli/handlers/operator.ts +240 -0
- package/src/cli/handlers/scenario.test.ts +456 -0
- package/src/cli/handlers/scenario.ts +330 -0
- package/src/cli/main.test.ts +23 -0
- package/src/cli/main.ts +72 -1
- package/src/cli/mcp/server.test.ts +265 -2
- package/src/cli/mcp/server.ts +84 -7
- package/src/cli/mcp/types.ts +27 -1
- package/src/cli/registry.ts +14 -2
- package/src/codegen/docs-rule-scanning.test.ts +42 -0
- package/src/codegen/docs-rule-scanning.ts +25 -2
- package/src/components/README.md +7 -0
- package/src/components/capability.ts +17 -2
- package/src/components/cli-support.test.ts +17 -0
- package/src/components/cli-support.ts +13 -1
- package/src/components/component-schema.test.ts +32 -0
- package/src/components/component.schema.json +6 -0
- package/src/components/component.test.ts +21 -0
- package/src/components/component.ts +15 -0
- package/src/components/driver.ts +12 -4
- package/src/components/verbs/index.ts +6 -1
- package/src/components/verbs/run-agent.test.ts +683 -0
- package/src/components/verbs/run-agent.ts +786 -0
- package/src/components/verbs/sign.test.ts +19 -0
- package/src/components/verbs/sign.ts +34 -2
- package/src/composite.ts +31 -2
- package/src/discovery/collect.ts +11 -2
- package/src/discovery/fold-import.test.ts +54 -0
- package/src/discovery/fold-import.ts +178 -38
- package/src/discovery/fold-rank.test.ts +197 -0
- package/src/discovery/fold-rank.ts +346 -0
- package/src/discovery/index.ts +16 -1
- package/src/discovery/param-deps.test.ts +118 -0
- package/src/discovery/param-deps.ts +170 -0
- package/src/fold/fold.test.ts +6 -2
- package/src/fold/fold.ts +184 -3
- package/src/fold/subset.test.ts +82 -19
- package/src/fold/subset.ts +79 -41
- package/src/lexicon-schema.ts +3 -0
- package/src/lexicon.ts +103 -2
- package/src/lifecycle/converge-ledger.test.ts +199 -0
- package/src/lifecycle/converge-ledger.ts +179 -0
- package/src/lifecycle/deep-diff.test.ts +79 -1
- package/src/lifecycle/deep-diff.ts +23 -0
- package/src/lifecycle/deep-observe.ts +13 -2
- package/src/lifecycle/gate-ledger.test.ts +103 -0
- package/src/lifecycle/gate-ledger.ts +140 -0
- package/src/lifecycle/git.test.ts +430 -0
- package/src/lifecycle/git.ts +446 -84
- package/src/lifecycle/index.ts +4 -0
- package/src/lifecycle/lease.test.ts +343 -0
- package/src/lifecycle/lease.ts +270 -0
- package/src/lifecycle/scenario-eval.test.ts +199 -0
- package/src/lifecycle/scenario-eval.ts +158 -0
- package/src/lifecycle/scenario.test.ts +195 -0
- package/src/lifecycle/scenario.ts +321 -0
- package/src/lifecycle/symptoms.test.ts +116 -0
- package/src/lifecycle/symptoms.ts +126 -0
- package/src/lint/output-docs.test.ts +220 -0
- package/src/lint/output-docs.ts +204 -0
- package/src/lint/post-synth.test.ts +97 -0
- package/src/lint/post-synth.ts +45 -0
- package/src/lint/rules/__fixtures__/comp/comp003/pass/agent-turn.component.ts +26 -0
- package/src/lint/rules/comp/comp.test.ts +49 -1
- package/src/lint/rules/evl001-non-literal-expression.test.ts +8 -3
- package/src/lint/rules/evl001-non-literal-expression.ts +6 -6
- package/src/lsp/lexicon-providers.test.ts +44 -0
- package/src/lsp/lexicon-providers.ts +11 -1
- package/src/op/activity-contract.test.ts +180 -0
- package/src/op/activity-contract.ts +278 -0
- package/src/op/builders-exports.test.ts +17 -1
- package/src/op/builders.ts +59 -5
- package/src/op/converge-rule.test.ts +179 -0
- package/src/op/converge-rule.ts +311 -0
- package/src/op/generate-pipeline.test.ts +53 -0
- package/src/op/generate-pipeline.ts +99 -0
- package/src/op/index.ts +30 -0
- package/src/op/local-executor.test.ts +92 -0
- package/src/op/local-executor.ts +45 -9
- package/src/op/op-verb-class.test.ts +126 -0
- package/src/op/op-verb-class.ts +115 -0
- package/src/op/operator.test.ts +346 -0
- package/src/op/operator.ts +213 -0
- package/src/op/step-output-ref.test.ts +334 -0
- package/src/op/step-output-ref.ts +453 -0
- package/src/op/types.ts +18 -1
- package/src/provenance.test.ts +151 -4
- package/src/provenance.ts +118 -4
- package/src/runtime-adapter.ts +31 -10
- package/src/serializer.ts +18 -0
- package/src/toml.test.ts +157 -384
- package/src/toml.ts +371 -5
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { commandBuildParams } from "../build-params-cli";
|
|
4
|
+
import { build, type BuildResult } from "../../build";
|
|
5
|
+
import { readEnvironmentSnapshots, fetchLifecycle } from "../../lifecycle/git";
|
|
6
|
+
import { buildChangeSet, type ChangeSet, type ChangeSetEntry } from "../../lifecycle/change-set";
|
|
7
|
+
import {
|
|
8
|
+
planReceipts,
|
|
9
|
+
mergeReceiptEntries,
|
|
10
|
+
observedValueResolver,
|
|
11
|
+
readReceiptValue,
|
|
12
|
+
type ReceiptReading,
|
|
13
|
+
} from "../../lifecycle/receipt-plan";
|
|
14
|
+
import { collectEffectReceipts, isEffectReceipt, type EffectReceiptDeclaration } from "../../effect-receipt";
|
|
15
|
+
import { evaluateScenario, type ScenarioVerdict } from "../../lifecycle/scenario-eval";
|
|
16
|
+
import { collectScenarios, type ScenarioDeclaration, type ScenarioGiven } from "../../lifecycle/scenario";
|
|
17
|
+
import { isResourceDeclarable } from "../../declarable";
|
|
18
|
+
import { loadChantConfig } from "../../config";
|
|
19
|
+
import { unknownEnvError } from "../../env";
|
|
20
|
+
import { collectBuildRootContributors } from "../plugins";
|
|
21
|
+
import { resolveBuildRoot } from "./lifecycle";
|
|
22
|
+
import { formatError, formatSuccess, formatBold } from "../format";
|
|
23
|
+
import type { CommandContext } from "../registry";
|
|
24
|
+
import type { LifecycleSnapshot } from "../../lifecycle/types";
|
|
25
|
+
import type { UnobservedEntity } from "../../observation";
|
|
26
|
+
import type { ResourceMetadata } from "../../lexicon";
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* `chant scenario check` (#1292) — evaluate every declared `Scenario`
|
|
30
|
+
* offline, against a fixture snapshot standing in for live observation.
|
|
31
|
+
*
|
|
32
|
+
* Mirrors `runLifecyclePlan` (./lifecycle.ts): build the project, walk every
|
|
33
|
+
* lexicon's declared entities, produce a `ChangeSet`. The one substitution is
|
|
34
|
+
* the whole point — a scenario's `given` fixture stands in for
|
|
35
|
+
* `observeLexicon`'s live read, so no plugin ever makes a network call and no
|
|
36
|
+
* credentials are ever asked for. `evaluateScenario` (../../lifecycle/scenario-eval.ts)
|
|
37
|
+
* checks the resulting change set against the scenario's `expect`.
|
|
38
|
+
*
|
|
39
|
+
* A scenario has exactly one fixture, not a before-and-after pair, so the
|
|
40
|
+
* change set it produces never has an `observedThen` to diff against — the
|
|
41
|
+
* `update` count is therefore always 0. That is a real, not a placeholder,
|
|
42
|
+
* limitation of what a single-snapshot scenario can assert: presence
|
|
43
|
+
* (`create`/`delete`/`noop`) and ownership are fully expressive here; drift
|
|
44
|
+
* since a prior read is not, because there is no prior read in this model,
|
|
45
|
+
* only the one fixture.
|
|
46
|
+
*
|
|
47
|
+
* Effect receipts (#1832) get the same replacement `runLifecyclePlan` gives
|
|
48
|
+
* them: `collectEffectReceipts` pulls every declared receipt out of the
|
|
49
|
+
* generic axis, `evaluateOneScenario` builds a {@link ReceiptReading} per
|
|
50
|
+
* receipt from the SAME fixture data that stands in for `observed.resources`/
|
|
51
|
+
* `observed.unobserved` elsewhere in this function, and `planReceipts` +
|
|
52
|
+
* `mergeReceiptEntries` (../../lifecycle/receipt-plan.ts) replace whatever the
|
|
53
|
+
* generic classification proposed for a receipt with its real `effect`
|
|
54
|
+
* classification — never a bare create/noop. A receipt whose reading can't be
|
|
55
|
+
* derived from the fixture (its lexicon has no fixture data, or a reference
|
|
56
|
+
* input the receipt depends on isn't among the fixture's recorded attributes)
|
|
57
|
+
* is classified exactly the way `runLifecyclePlan` classifies it: an
|
|
58
|
+
* unreadable receipt lands `unobserved`, loudly; a receipt whose reference
|
|
59
|
+
* input can't resolve still proposes the fire, with an "unresolved input"
|
|
60
|
+
* note, never a guessed digest.
|
|
61
|
+
*/
|
|
62
|
+
export async function runScenarioCheck(ctx: CommandContext): Promise<number> {
|
|
63
|
+
const { args, plugins, serializers } = ctx;
|
|
64
|
+
|
|
65
|
+
const { config } = await loadChantConfig(resolve("."));
|
|
66
|
+
const declaredParams = await commandBuildParams(config.buildParams, args);
|
|
67
|
+
if (!declaredParams) return 1;
|
|
68
|
+
|
|
69
|
+
const buildResult = await build(resolveBuildRoot(args, config), serializers, undefined, {
|
|
70
|
+
buildParams: declaredParams,
|
|
71
|
+
buildRoots: collectBuildRootContributors(plugins, config as unknown as Record<string, unknown>, resolve(".")),
|
|
72
|
+
});
|
|
73
|
+
if (buildResult.errors.length > 0) {
|
|
74
|
+
console.error(formatError({ message: "Build failed — fix errors before checking scenarios" }));
|
|
75
|
+
return 1;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const scenarios = collectScenarios(buildResult.entities);
|
|
79
|
+
if (scenarios.size === 0) {
|
|
80
|
+
console.log("No scenarios declared — nothing to check.");
|
|
81
|
+
return 0;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Same read surface `runLifecyclePlan` uses (lifecycle.ts) — computed once,
|
|
85
|
+
// ahead of the per-scenario loop, since it's a property of the build, not
|
|
86
|
+
// of any one scenario's fixture.
|
|
87
|
+
const receipts = collectEffectReceipts(buildResult.entities);
|
|
88
|
+
|
|
89
|
+
// `snapshot(env)` reads the chant/lifecycle orphan branch — fetch once,
|
|
90
|
+
// up front, the same pre-read `runLifecyclePlan` does, rather than once per
|
|
91
|
+
// scenario that needs it.
|
|
92
|
+
if ([...scenarios.values()].some((s) => s.given.kind === "env")) {
|
|
93
|
+
await fetchLifecycle();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const results: Array<{ entityName: string; scenario: ScenarioDeclaration; verdict: ScenarioVerdict; env: string }> = [];
|
|
97
|
+
for (const [entityName, scenario] of scenarios) {
|
|
98
|
+
if (scenario.given.kind === "env") {
|
|
99
|
+
const envErr = unknownEnvError(scenario.given.env, config.environments);
|
|
100
|
+
if (envErr) {
|
|
101
|
+
results.push({
|
|
102
|
+
entityName,
|
|
103
|
+
scenario,
|
|
104
|
+
env: scenario.given.env,
|
|
105
|
+
verdict: { pass: false, checks: [{ clause: "given", pass: false, detail: envErr }] },
|
|
106
|
+
});
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
const { verdict, env } = await evaluateOneScenario(scenario, buildResult, receipts);
|
|
111
|
+
results.push({ entityName, scenario, verdict, env });
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (args.json) {
|
|
115
|
+
console.log(
|
|
116
|
+
JSON.stringify(
|
|
117
|
+
results.map((r) => ({ name: r.scenario.name, entity: r.entityName, env: r.env, ...r.verdict })),
|
|
118
|
+
null,
|
|
119
|
+
2,
|
|
120
|
+
),
|
|
121
|
+
);
|
|
122
|
+
} else {
|
|
123
|
+
for (const r of results) printResult(r.entityName, r.scenario, r.env, r.verdict);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const failed = results.filter((r) => !r.verdict.pass).length;
|
|
127
|
+
const total = results.length;
|
|
128
|
+
if (failed > 0) {
|
|
129
|
+
console.error(formatError({ message: `${failed}/${total} scenario(s) failed` }));
|
|
130
|
+
return 1;
|
|
131
|
+
}
|
|
132
|
+
console.log(formatSuccess(`${total}/${total} scenario(s) passed`));
|
|
133
|
+
return 0;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function printResult(entityName: string, scenario: ScenarioDeclaration, env: string, verdict: ScenarioVerdict): void {
|
|
137
|
+
const status = verdict.pass ? formatSuccess("PASS") : "FAIL";
|
|
138
|
+
console.log(`\n${formatBold(scenario.name)} [${entityName}] — given ${describeGiven(scenario.given)}${env ? `, env ${env}` : ""}: ${status}`);
|
|
139
|
+
for (const check of verdict.checks) {
|
|
140
|
+
if (check.pass) continue;
|
|
141
|
+
console.log(` ${check.clause}: ${check.detail ?? "failed"}`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Human-readable rendering of a scenario's `given`, for output and error messages. */
|
|
146
|
+
function describeGiven(given: ScenarioGiven): string {
|
|
147
|
+
return given.kind === "file" ? given.path : `env "${given.env}"`;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
interface GivenResolution {
|
|
151
|
+
env: string;
|
|
152
|
+
/** Fixture data per lexicon it covers. Empty when nothing could be read. */
|
|
153
|
+
perLexicon: Map<string, LifecycleSnapshot>;
|
|
154
|
+
/** Set when the fixture itself could not be resolved — the scenario fails on this alone. */
|
|
155
|
+
error?: string;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** Read `given`'s fixture data. Offline: a file read for `snapshot(path)`, a
|
|
159
|
+
* git-plumbing read of the already-fetched orphan branch for `snapshot(env)` —
|
|
160
|
+
* never a live provider call. */
|
|
161
|
+
async function resolveGiven(given: ScenarioGiven): Promise<GivenResolution> {
|
|
162
|
+
if (given.kind === "file") {
|
|
163
|
+
const abs = resolve(given.path);
|
|
164
|
+
let raw: string;
|
|
165
|
+
try {
|
|
166
|
+
raw = await readFile(abs, "utf8");
|
|
167
|
+
} catch {
|
|
168
|
+
return { env: "", perLexicon: new Map(), error: `fixture not found: ${given.path}` };
|
|
169
|
+
}
|
|
170
|
+
let snap: LifecycleSnapshot;
|
|
171
|
+
try {
|
|
172
|
+
snap = JSON.parse(raw) as LifecycleSnapshot;
|
|
173
|
+
} catch {
|
|
174
|
+
return { env: "", perLexicon: new Map(), error: `fixture is not valid JSON: ${given.path}` };
|
|
175
|
+
}
|
|
176
|
+
if (typeof snap.lexicon !== "string" || typeof snap.environment !== "string" || typeof snap.resources !== "object") {
|
|
177
|
+
return {
|
|
178
|
+
env: typeof snap.environment === "string" ? snap.environment : "",
|
|
179
|
+
perLexicon: new Map(),
|
|
180
|
+
error: `${given.path} is not a LifecycleSnapshot — missing lexicon/environment/resources`,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
return { env: snap.environment, perLexicon: new Map([[snap.lexicon, snap]]) };
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const stored = await readEnvironmentSnapshots(given.env);
|
|
187
|
+
if (stored.size === 0) {
|
|
188
|
+
return {
|
|
189
|
+
env: given.env,
|
|
190
|
+
perLexicon: new Map(),
|
|
191
|
+
error: `no recorded snapshot for environment "${given.env}" on chant/lifecycle — record one with \`chant lifecycle snapshot ${given.env}\``,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
const perLexicon = new Map<string, LifecycleSnapshot>();
|
|
195
|
+
for (const [key, content] of stored) {
|
|
196
|
+
const snap = JSON.parse(content) as LifecycleSnapshot;
|
|
197
|
+
perLexicon.set(snap.lexicon ?? key, snap);
|
|
198
|
+
}
|
|
199
|
+
return { env: given.env, perLexicon };
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Build the merged change set for one scenario and evaluate it. A lexicon the
|
|
204
|
+
* fixture has no data for is never silently read as "nothing declared, all
|
|
205
|
+
* absent" — every entity it declares is marked `unobserved` (#1089's own
|
|
206
|
+
* discipline: a hole the fixture cannot fill is a hole, not a guess).
|
|
207
|
+
*
|
|
208
|
+
* Effect receipts (#1832) get the same treatment `runLifecyclePlan` gives
|
|
209
|
+
* them (lifecycle.ts:1145,1184,1213-1242,1278-1280): a receipt joins the
|
|
210
|
+
* declared axis alongside ordinary resources, a {@link ReceiptReading} is
|
|
211
|
+
* built per receipt from the SAME fixture data (`observedNow`/`unobserved`)
|
|
212
|
+
* every other entity in this loop reads, and `planReceipts` +
|
|
213
|
+
* `mergeReceiptEntries` replace whatever the generic classification proposed
|
|
214
|
+
* for the receipt with the real `effect` classification before the change set
|
|
215
|
+
* ever reaches `evaluateScenario` — an unfired receipt never reads as a bare
|
|
216
|
+
* `create`, and a stale one never reads as a clean `noop`.
|
|
217
|
+
*/
|
|
218
|
+
async function evaluateOneScenario(
|
|
219
|
+
scenario: ScenarioDeclaration,
|
|
220
|
+
buildResult: BuildResult,
|
|
221
|
+
receipts: ReadonlyMap<string, EffectReceiptDeclaration>,
|
|
222
|
+
): Promise<{ verdict: ScenarioVerdict; env: string }> {
|
|
223
|
+
const resolved = await resolveGiven(scenario.given);
|
|
224
|
+
if (resolved.error) {
|
|
225
|
+
return {
|
|
226
|
+
env: resolved.env,
|
|
227
|
+
verdict: { pass: false, checks: [{ clause: "given", pass: false, detail: resolved.error }] },
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const declaredByLexicon = new Map<string, Set<string>>();
|
|
232
|
+
for (const [name, entity] of buildResult.entities) {
|
|
233
|
+
// A receipt has no `props` payload of its own but is declared, diffed,
|
|
234
|
+
// and observed like any resource (#1832) — it joins the declared axis so
|
|
235
|
+
// its lexicon's fixture data can confirm presence, absence, or a hole,
|
|
236
|
+
// the same as `runLifecyclePlan` (lifecycle.ts:1184).
|
|
237
|
+
if (!isResourceDeclarable(entity) && !isEffectReceipt(entity)) continue;
|
|
238
|
+
if (!declaredByLexicon.has(entity.lexicon)) declaredByLexicon.set(entity.lexicon, new Set());
|
|
239
|
+
declaredByLexicon.get(entity.lexicon)!.add(name);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Every fixture lexicon's `resources` merged, for resolving a receipt's
|
|
243
|
+
// reference inputs against the fixture the same way `runLifecyclePlan`
|
|
244
|
+
// resolves them against its merged live observation (lifecycle.ts:1149,1213).
|
|
245
|
+
const allObservedResources: Record<string, ResourceMetadata> = {};
|
|
246
|
+
const receiptReadings = new Map<string, ReceiptReading>();
|
|
247
|
+
|
|
248
|
+
const lexicons = new Set<string>([...declaredByLexicon.keys(), ...resolved.perLexicon.keys()]);
|
|
249
|
+
const entries: ChangeSetEntry[] = [];
|
|
250
|
+
for (const lexiconName of lexicons) {
|
|
251
|
+
const declared = declaredByLexicon.get(lexiconName) ?? new Set<string>();
|
|
252
|
+
const fixture = resolved.perLexicon.get(lexiconName);
|
|
253
|
+
const observedNow = fixture?.resources ?? {};
|
|
254
|
+
const unobserved: Record<string, UnobservedEntity> = { ...(fixture?.unobserved ?? {}) };
|
|
255
|
+
if (!fixture) {
|
|
256
|
+
for (const name of declared) {
|
|
257
|
+
if (!(name in unobserved)) {
|
|
258
|
+
unobserved[name] = {
|
|
259
|
+
reason: "filtered",
|
|
260
|
+
detail: `given ${describeGiven(scenario.given)} has no recorded data for lexicon "${lexiconName}"`,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
Object.assign(allObservedResources, observedNow);
|
|
267
|
+
for (const [receiptName, receipt] of receipts) {
|
|
268
|
+
if (receipt.lexicon !== lexiconName) continue;
|
|
269
|
+
const live = observedNow[receiptName];
|
|
270
|
+
const hole = unobserved[receiptName];
|
|
271
|
+
if (live) {
|
|
272
|
+
receiptReadings.set(receiptName, {
|
|
273
|
+
observed: true,
|
|
274
|
+
present: true,
|
|
275
|
+
value: readReceiptValue(live.attributes),
|
|
276
|
+
type: live.type,
|
|
277
|
+
...(live.physicalId ? { physicalId: live.physicalId } : {}),
|
|
278
|
+
lexicon: lexiconName,
|
|
279
|
+
});
|
|
280
|
+
} else if (hole) {
|
|
281
|
+
receiptReadings.set(receiptName, {
|
|
282
|
+
observed: false,
|
|
283
|
+
present: false,
|
|
284
|
+
lexicon: lexiconName,
|
|
285
|
+
...(hole.type ? { type: hole.type } : {}),
|
|
286
|
+
unobservedReason: hole.reason,
|
|
287
|
+
...(hole.detail ? { unobservedDetail: hole.detail } : {}),
|
|
288
|
+
});
|
|
289
|
+
} else {
|
|
290
|
+
// Neither in the fixture's resources nor named unobserved: the
|
|
291
|
+
// fixture stands in for a live read that confirmed the receipt
|
|
292
|
+
// absent — the same claim `buildChangeSet` reads off a bare
|
|
293
|
+
// observation (#1089).
|
|
294
|
+
receiptReadings.set(receiptName, { observed: true, present: false, lexicon: lexiconName });
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// No `observedThen`: a scenario has one fixture, standing in for live
|
|
299
|
+
// observation only — never a second, prior read to diff against. See this
|
|
300
|
+
// module's top comment on what that means for `update`.
|
|
301
|
+
const cs = buildChangeSet(
|
|
302
|
+
resolved.env,
|
|
303
|
+
{ declared, observedNow, observedThen: undefined, unobserved },
|
|
304
|
+
{ lexicon: lexiconName },
|
|
305
|
+
);
|
|
306
|
+
entries.push(...cs.entries);
|
|
307
|
+
}
|
|
308
|
+
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
309
|
+
|
|
310
|
+
const merged: ChangeSet = { env: resolved.env, entries };
|
|
311
|
+
// Receipt classification (#1832): replace whatever the generic pass above
|
|
312
|
+
// proposed for a receipt — a `create` for one confirmed absent, a `noop`
|
|
313
|
+
// for one present but stale — with the real `effect` classification, the
|
|
314
|
+
// same replacement `runLifecyclePlan` performs (lifecycle.ts:1278-1280).
|
|
315
|
+
if (receipts.size > 0) {
|
|
316
|
+
const receiptEntries = planReceipts(receipts, receiptReadings, observedValueResolver(allObservedResources));
|
|
317
|
+
mergeReceiptEntries(merged, receipts, receiptEntries);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return { env: resolved.env, verdict: evaluateScenario(merged, scenario.expect) };
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/** Fallback for `chant scenario <unknown subcommand>` — mirrors `runLifecycleUnknown`. */
|
|
324
|
+
export async function runScenarioUnknown(ctx: CommandContext): Promise<number> {
|
|
325
|
+
console.error(formatError({
|
|
326
|
+
message: `Unknown scenario subcommand: ${ctx.args.extraPositional ?? ctx.args.path}`,
|
|
327
|
+
hint: "Available: chant scenario check",
|
|
328
|
+
}));
|
|
329
|
+
return 1;
|
|
330
|
+
}
|
package/src/cli/main.test.ts
CHANGED
|
@@ -383,6 +383,29 @@ describe("parseArgs", () => {
|
|
|
383
383
|
expect(() => parseArgs(["run", "myop", "--json=1"])).toThrow(/--json is a boolean flag/);
|
|
384
384
|
});
|
|
385
385
|
|
|
386
|
+
test("--fold-rank has the same context-sensitive bare-vs-path behavior as --report (#1083)", () => {
|
|
387
|
+
// Bare --fold-rank is the boolean (print the ranked report); --fold-rank
|
|
388
|
+
// <path> is the collapsed-format export destination.
|
|
389
|
+
const bare = parseArgs(["build", "src", "--fold", "--fold-rank"]);
|
|
390
|
+
expect(bare.foldRank).toBe(true);
|
|
391
|
+
expect(bare.foldRankCollapsedFile).toBeUndefined();
|
|
392
|
+
|
|
393
|
+
const withPath = parseArgs(["build", "src", "--fold", "--fold-rank", "out.collapsed"]);
|
|
394
|
+
expect(withPath.foldRankCollapsedFile).toBe("out.collapsed");
|
|
395
|
+
expect(withPath.foldRank).toBeUndefined();
|
|
396
|
+
|
|
397
|
+
// A following flag (not a path) keeps the bare-boolean reading.
|
|
398
|
+
const beforeFlag = parseArgs(["build", "src", "--fold-rank", "--verbose"]);
|
|
399
|
+
expect(beforeFlag.foldRank).toBe(true);
|
|
400
|
+
expect(beforeFlag.verbose).toBe(true);
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
test("--fold-rank=<path> (joined form) resolves the same as the space-separated one", () => {
|
|
404
|
+
const result = parseArgs(["build", "src", "--fold-rank=out.collapsed"]);
|
|
405
|
+
expect(result.foldRankCollapsedFile).toBe("out.collapsed");
|
|
406
|
+
expect(result.foldRank).toBeUndefined();
|
|
407
|
+
});
|
|
408
|
+
|
|
386
409
|
test("--report keeps its context-sensitive bare-vs-value behavior when joined", () => {
|
|
387
410
|
// --report is deliberately not in the boolean-reject set: bare --report is
|
|
388
411
|
// a boolean (`run`), but --report <path> is a SARIF destination (migrate).
|
package/src/cli/main.ts
CHANGED
|
@@ -24,11 +24,13 @@ import { runCarveEmit } from "./handlers/carve-emit";
|
|
|
24
24
|
import { runCarveBridge } from "./handlers/carve-bridge";
|
|
25
25
|
import { runCarveApply } from "./handlers/carve-apply";
|
|
26
26
|
import { runLifecycleSnapshot, runLifecycleShow, runLifecycleDiff, runLifecycleRollback, runLifecyclePlan, runLifecycleAffected, runLifecycleLog, runLifecycleTeardown, runLifecycleUnknown } from "./handlers/lifecycle";
|
|
27
|
-
import { runComponentsStatus, runComponentsReleaseRecord, runComponentsUnknown } from "./handlers/components";
|
|
27
|
+
import { runComponentsStatus, runComponentsReleaseRecord, runComponentsExport, runComponentsUnknown } from "./handlers/components";
|
|
28
|
+
import { runScenarioCheck, runScenarioUnknown } from "./handlers/scenario";
|
|
28
29
|
import { runGraph } from "./handlers/graph";
|
|
29
30
|
import { runExplain } from "./handlers/explain";
|
|
30
31
|
import { runSearch } from "./handlers/search";
|
|
31
32
|
import { runOp, runOpList, runOpStatus, runOpSignal, runOpCancel, runOpLog } from "./handlers/run";
|
|
33
|
+
import { runOperator, runOperatorStatus, runApprove } from "./handlers/operator";
|
|
32
34
|
import { runEmulator } from "./handlers/emulator";
|
|
33
35
|
import { splitJoinedFlags, dispatchCommandGroup, collectCommandGroups, formatCommandGroupsHelp, type CommandGroup } from "./command-group";
|
|
34
36
|
import type { LexiconPlugin } from "../lexicon";
|
|
@@ -80,6 +82,7 @@ const BOOLEAN_FLAGS = new Set([
|
|
|
80
82
|
"--sandbox",
|
|
81
83
|
"--yes",
|
|
82
84
|
"--confirm-prod",
|
|
85
|
+
"--once",
|
|
83
86
|
]);
|
|
84
87
|
|
|
85
88
|
/**
|
|
@@ -334,6 +337,17 @@ export function parseArgs(args: string[]): ParsedArgs {
|
|
|
334
337
|
result.fold = false;
|
|
335
338
|
} else if (arg === "--sandbox") {
|
|
336
339
|
result.sandbox = true;
|
|
340
|
+
} else if (arg === "--fold-rank") {
|
|
341
|
+
// chant #1083 — same context-sensitive shape as --report above:
|
|
342
|
+
// `--fold-rank` alone prints the ranked-blocker report; `--fold-rank
|
|
343
|
+
// <path>` ALSO writes the Brendan Gregg collapsed-format export there.
|
|
344
|
+
const next = args[i + 1];
|
|
345
|
+
if (next && !next.startsWith("-")) {
|
|
346
|
+
result.foldRankCollapsedFile = next;
|
|
347
|
+
i++;
|
|
348
|
+
} else {
|
|
349
|
+
result.foldRank = true;
|
|
350
|
+
}
|
|
337
351
|
} else if (arg === "--param") {
|
|
338
352
|
// chant #1118/#1127 — `--param name=value` (space-separated) and
|
|
339
353
|
// `--param=name=value` (joined, split above at its first `=` into flag
|
|
@@ -347,6 +361,14 @@ export function parseArgs(args: string[]): ParsedArgs {
|
|
|
347
361
|
result.paramsFile = args[++i];
|
|
348
362
|
} else if (arg === "--projection") {
|
|
349
363
|
result.projection = args[++i];
|
|
364
|
+
} else if (arg === "--interval") {
|
|
365
|
+
result.interval = args[++i];
|
|
366
|
+
} else if (arg === "--lease-ttl") {
|
|
367
|
+
result.leaseTtl = args[++i];
|
|
368
|
+
} else if (arg === "--once") {
|
|
369
|
+
result.once = true;
|
|
370
|
+
} else if (arg === "--note") {
|
|
371
|
+
result.note = args[++i];
|
|
350
372
|
} else if (arg.startsWith("--")) {
|
|
351
373
|
// chant #1127 — every recognized flag is matched above; anything left
|
|
352
374
|
// starting with `--` is unrecognized, whether it arrived bare
|
|
@@ -462,6 +484,24 @@ Ops:
|
|
|
462
484
|
consumer to render live wave/component/phase/step
|
|
463
485
|
progress instead of tailing raw logs (additive; run
|
|
464
486
|
semantics/exit code unchanged)
|
|
487
|
+
operator Run scheduled ticks for discovered ConvergeOps
|
|
488
|
+
locally, no Temporal (#1485): acquire/renew a per-op
|
|
489
|
+
lease (git ref CAS), tick on --interval, record every
|
|
490
|
+
result as a ledger fact. --env <env> scopes to one
|
|
491
|
+
environment; --interval <dur> (default 60s) and
|
|
492
|
+
--lease-ttl <dur> (default 5m) tune cadence; --once
|
|
493
|
+
runs a single round and exits (cron/systemd-timer/
|
|
494
|
+
CronJob invokers use this instead of the daemon)
|
|
495
|
+
operator status Last tick, outcomes, and pending gates per
|
|
496
|
+
ConvergeOp, read from the chant/lifecycle orphan
|
|
497
|
+
branch alone — no daemon needs to be running
|
|
498
|
+
(--env <env>, --json)
|
|
499
|
+
approve <op> <gate> Record a gate's out-of-band resolution fact
|
|
500
|
+
(--actor <name>, --note <text>) — the durable
|
|
501
|
+
counterpart to a converge tick's gate-as-fact
|
|
502
|
+
outcome; see the pending-gates list in operator
|
|
503
|
+
status. Does not itself unblock the gated op's local
|
|
504
|
+
dispatch (re-run --temporal, or merge its PR)
|
|
465
505
|
|
|
466
506
|
graph Show Op dependency graph (--stacks for cross-stack order,
|
|
467
507
|
--format ir|mermaid|dot|layout for the lint-gated graph IR,
|
|
@@ -496,6 +536,12 @@ Lifecycle (alias: lc):
|
|
|
496
536
|
--confirm-prod, or an interactive confirmation)
|
|
497
537
|
lifecycle log [env] History of lifecycle snapshots
|
|
498
538
|
|
|
539
|
+
Plan scenarios (#1292):
|
|
540
|
+
scenario check Evaluate every declared Scenario's expect clause
|
|
541
|
+
offline, against its given fixture (no cloud, no
|
|
542
|
+
credentials); --json: emit verdicts as JSON.
|
|
543
|
+
Nonzero exit on any failing scenario.
|
|
544
|
+
|
|
499
545
|
Component release ledger + status:
|
|
500
546
|
components status [env] What's built vs what's deployed where, joined by
|
|
501
547
|
digest (--live: reconcile against live+ownership;
|
|
@@ -505,6 +551,11 @@ Component release ledger + status:
|
|
|
505
551
|
components release <env> Append one immutable release record
|
|
506
552
|
(--component <name> --digest <sha256:...>
|
|
507
553
|
[--git-sha <sha>] [--run-id <id>] [--actor <name>])
|
|
554
|
+
components export <env> Materialize a persisted build archive manifest to
|
|
555
|
+
a portable directory (--component <name>
|
|
556
|
+
[--digest <manifestDigest>] -o <dir> [--json]);
|
|
557
|
+
copies every image/template/asset/sbom entry
|
|
558
|
+
byte-for-byte plus a self-describing manifest.json
|
|
508
559
|
|
|
509
560
|
Lexicon development:
|
|
510
561
|
dev generate Generate lexicon artifacts (+ validate + coverage)
|
|
@@ -590,6 +641,14 @@ Options:
|
|
|
590
641
|
--no-fold (build) Opt out of folding for this invocation: every
|
|
591
642
|
source module is imported and run, the pre-#1134
|
|
592
643
|
behavior. Beats chant.config.ts's build.fold.
|
|
644
|
+
--fold-rank [<path>] (build, with --fold) Rank run-mode files by dominator
|
|
645
|
+
retained-count over the forward import-failure graph
|
|
646
|
+
(#1083) — fixing the top blocker unblocks the most
|
|
647
|
+
files. Files held back only by the reverse rule
|
|
648
|
+
(#1044) are reported separately, never folded into
|
|
649
|
+
the ranking. With a path argument, also writes a
|
|
650
|
+
Brendan Gregg collapsed-format export (weighted by
|
|
651
|
+
retained count) there, for any flame/icicle viewer.
|
|
593
652
|
--sandbox (build) Run run-fallback source files (or every
|
|
594
653
|
file, without --fold) together, isolated, in one
|
|
595
654
|
sandboxed child process instead of in-process
|
|
@@ -620,6 +679,7 @@ Examples:
|
|
|
620
679
|
chant build ./infra/ --components --generate gitlab --output .gitlab-ci.yml
|
|
621
680
|
chant run --components search-service --env staging
|
|
622
681
|
chant run --components all --env production
|
|
682
|
+
chant components export prod --component search-service -o ./dist/search-service
|
|
623
683
|
chant import template.json --output ./infra/
|
|
624
684
|
chant import --from prod --name my-bucket --output src/
|
|
625
685
|
chant lint ./infra/
|
|
@@ -780,6 +840,10 @@ const registry: CommandDef[] = [
|
|
|
780
840
|
{ name: "run log", handler: runOpLog },
|
|
781
841
|
{ name: "run", handler: runOp },
|
|
782
842
|
|
|
843
|
+
{ name: "operator status", handler: runOperatorStatus },
|
|
844
|
+
{ name: "operator", handler: runOperator },
|
|
845
|
+
{ name: "approve", handler: runApprove },
|
|
846
|
+
|
|
783
847
|
{ name: "graph", handler: runGraph },
|
|
784
848
|
{ name: "vendor", handler: runVendor },
|
|
785
849
|
|
|
@@ -793,9 +857,15 @@ const registry: CommandDef[] = [
|
|
|
793
857
|
{ name: "lifecycle teardown", requiresPlugins: true, handler: runLifecycleTeardown },
|
|
794
858
|
{ name: "lifecycle log", handler: runLifecycleLog },
|
|
795
859
|
|
|
860
|
+
// Plan scenarios (#1292) — fully offline, no plugin network calls, but
|
|
861
|
+
// requiresPlugins:true so the build step has serializers to partition
|
|
862
|
+
// against, same as every other build-then-something verb.
|
|
863
|
+
{ name: "scenario check", requiresPlugins: true, handler: runScenarioCheck },
|
|
864
|
+
|
|
796
865
|
// Component release ledger + status surface (#568, epic #551)
|
|
797
866
|
{ name: "components status", requiresPlugins: true, handler: runComponentsStatus },
|
|
798
867
|
{ name: "components release", handler: runComponentsReleaseRecord },
|
|
868
|
+
{ name: "components export", handler: runComponentsExport },
|
|
799
869
|
|
|
800
870
|
// Local emulators of configured lexicons (#920). Compound so the action word
|
|
801
871
|
// lands in args.path (not consumed as a project dir) and projectPath is forced ".".
|
|
@@ -811,6 +881,7 @@ const registry: CommandDef[] = [
|
|
|
811
881
|
{ name: "carve", handler: runCarveUnknown },
|
|
812
882
|
{ name: "emulator", requiresPlugins: true, handler: runEmulator },
|
|
813
883
|
{ name: "lifecycle", handler: runLifecycleUnknown },
|
|
884
|
+
{ name: "scenario", handler: runScenarioUnknown },
|
|
814
885
|
{ name: "dev", handler: runDevUnknown },
|
|
815
886
|
{ name: "serve", handler: runServeUnknown },
|
|
816
887
|
{ name: "components", handler: runComponentsUnknown },
|