@intentius/chant 0.124.14 → 0.124.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (178) hide show
  1. package/dist/build.d.ts +6 -0
  2. package/dist/build.d.ts.map +1 -1
  3. package/dist/build.js +1 -0
  4. package/dist/change-set.d.ts +24 -2
  5. package/dist/change-set.d.ts.map +1 -1
  6. package/dist/change-set.js +50 -1
  7. package/dist/ci-jobs.d.ts +227 -0
  8. package/dist/ci-jobs.d.ts.map +1 -0
  9. package/dist/ci-jobs.js +448 -0
  10. package/dist/cli/flush.d.ts +16 -0
  11. package/dist/cli/flush.d.ts.map +1 -0
  12. package/dist/cli/flush.js +42 -0
  13. package/dist/cli/handlers/approve.d.ts +98 -0
  14. package/dist/cli/handlers/approve.d.ts.map +1 -0
  15. package/dist/cli/handlers/approve.js +495 -0
  16. package/dist/cli/handlers/operator.d.ts +1 -95
  17. package/dist/cli/handlers/operator.d.ts.map +1 -1
  18. package/dist/cli/handlers/operator.js +4 -488
  19. package/dist/cli/handlers/override-approve.js +1 -1
  20. package/dist/cli/main.d.ts +4 -15
  21. package/dist/cli/main.d.ts.map +1 -1
  22. package/dist/cli/main.js +4 -1062
  23. package/dist/cli/parse-args.d.ts +6 -0
  24. package/dist/cli/parse-args.d.ts.map +1 -0
  25. package/dist/cli/parse-args.js +1028 -0
  26. package/dist/components/fan-out-record.d.ts +1 -1
  27. package/dist/components/fan-out-record.d.ts.map +1 -1
  28. package/dist/components/fan-out-record.js +3 -7
  29. package/dist/components/fan-out-run.d.ts +5 -3
  30. package/dist/components/fan-out-run.d.ts.map +1 -1
  31. package/dist/components/fan-out-run.js +7 -5
  32. package/dist/components/pr-pipeline.d.ts.map +1 -1
  33. package/dist/components/pr-pipeline.js +3 -2
  34. package/dist/composite.d.ts +10 -2
  35. package/dist/composite.d.ts.map +1 -1
  36. package/dist/composite.js +8 -0
  37. package/dist/discovery/collect.d.ts +2 -1
  38. package/dist/discovery/collect.d.ts.map +1 -1
  39. package/dist/discovery/collect.js +16 -1
  40. package/dist/discovery/fold-import.d.ts.map +1 -1
  41. package/dist/discovery/fold-import.js +2 -107
  42. package/dist/discovery/host-composite.d.ts +35 -0
  43. package/dist/discovery/host-composite.d.ts.map +1 -0
  44. package/dist/discovery/host-composite.js +93 -0
  45. package/dist/discovery/index.d.ts +9 -0
  46. package/dist/discovery/index.d.ts.map +1 -1
  47. package/dist/discovery/index.js +1 -1
  48. package/dist/gate-decision.d.ts +20 -9
  49. package/dist/gate-decision.d.ts.map +1 -1
  50. package/dist/gate-decision.js +72 -19
  51. package/dist/gated-waves.d.ts +25 -6
  52. package/dist/gated-waves.d.ts.map +1 -1
  53. package/dist/gated-waves.js +21 -6
  54. package/dist/lifecycle/gate-audit.d.ts +69 -2
  55. package/dist/lifecycle/gate-audit.d.ts.map +1 -1
  56. package/dist/lifecycle/gate-audit.js +76 -2
  57. package/dist/lifecycle/gate-ledger.d.ts +24 -0
  58. package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
  59. package/dist/lifecycle/gate-ledger.js +9 -1
  60. package/dist/op/activities/activity-contracts.d.ts +5 -5
  61. package/dist/op/activities/activity-contracts.js +1 -1
  62. package/dist/op/activities/change-set.d.ts +8 -3
  63. package/dist/op/activities/change-set.d.ts.map +1 -1
  64. package/dist/op/activities/change-set.js +5 -2
  65. package/dist/op/activities/reconcile.d.ts +28 -16
  66. package/dist/op/activities/reconcile.d.ts.map +1 -1
  67. package/dist/op/activities/reconcile.js +34 -20
  68. package/dist/op/builders.d.ts +1 -1
  69. package/dist/op/builders.d.ts.map +1 -1
  70. package/dist/op/builders.js +1 -1
  71. package/dist/op/op-waves-run.d.ts +2 -2
  72. package/dist/op/op-waves-run.d.ts.map +1 -1
  73. package/dist/op/op-waves-run.js +8 -9
  74. package/dist/op/op-waves.d.ts.map +1 -1
  75. package/dist/op/op-waves.js +4 -8
  76. package/dist/package-version.d.ts +2 -0
  77. package/dist/package-version.d.ts.map +1 -0
  78. package/dist/package-version.js +10 -0
  79. package/dist/wave-engine.d.ts +62 -0
  80. package/dist/wave-engine.d.ts.map +1 -0
  81. package/dist/wave-engine.js +59 -0
  82. package/dist/wave-layout.d.ts +29 -0
  83. package/dist/wave-layout.d.ts.map +1 -0
  84. package/dist/wave-layout.js +41 -0
  85. package/dist/wave-runtime.d.ts +29 -0
  86. package/dist/wave-runtime.d.ts.map +1 -0
  87. package/dist/wave-runtime.js +103 -0
  88. package/dist/wave-runtime.mjs +331 -0
  89. package/dist/workspace/__fixtures__/contract-repo.d.ts +7 -0
  90. package/dist/workspace/__fixtures__/contract-repo.d.ts.map +1 -1
  91. package/dist/workspace/__fixtures__/contract-repo.js +17 -1
  92. package/dist/workspace/__fixtures__/sessions.d.ts +1 -1
  93. package/dist/workspace/__fixtures__/sessions.d.ts.map +1 -1
  94. package/dist/workspace/__fixtures__/sessions.js +3 -3
  95. package/dist/workspace/ajv2020.d.ts +5 -0
  96. package/dist/workspace/ajv2020.d.ts.map +1 -0
  97. package/dist/workspace/ajv2020.js +14 -0
  98. package/dist/workspace/change-set.schema.json +5 -3
  99. package/dist/workspace/ci-green-workflow.d.ts +3 -1
  100. package/dist/workspace/ci-green-workflow.d.ts.map +1 -1
  101. package/dist/workspace/ci-green-workflow.js +5 -2
  102. package/dist/workspace/class-data.d.ts +84 -0
  103. package/dist/workspace/class-data.d.ts.map +1 -0
  104. package/dist/workspace/class-data.js +238 -0
  105. package/dist/workspace/declaration.d.ts.map +1 -1
  106. package/dist/workspace/declaration.js +4 -8
  107. package/dist/workspace/kinds.d.ts +3 -7
  108. package/dist/workspace/kinds.d.ts.map +1 -1
  109. package/dist/workspace/kinds.js +8 -45
  110. package/dist/workspace/points.d.ts.map +1 -1
  111. package/dist/workspace/points.js +2 -5
  112. package/dist/workspace/principal-classes.d.ts +4 -11
  113. package/dist/workspace/principal-classes.d.ts.map +1 -1
  114. package/dist/workspace/principal-classes.js +13 -99
  115. package/package.json +5 -3
  116. package/src/build.ts +8 -0
  117. package/src/change-set.test.ts +48 -0
  118. package/src/change-set.ts +52 -2
  119. package/src/ci-jobs.test.ts +49 -0
  120. package/src/ci-jobs.ts +515 -0
  121. package/src/cli/flush.ts +44 -0
  122. package/src/cli/handlers/approve.ts +618 -0
  123. package/src/cli/handlers/operator.ts +10 -608
  124. package/src/cli/handlers/override-approve.test.ts +1 -1
  125. package/src/cli/handlers/override-approve.ts +1 -1
  126. package/src/cli/main.ts +5 -816
  127. package/src/cli/parse-args.ts +781 -0
  128. package/src/components/fan-out-record.ts +3 -7
  129. package/src/components/fan-out-run.ts +7 -5
  130. package/src/components/pr-pipeline.ts +3 -2
  131. package/src/composite.ts +15 -3
  132. package/src/discovery/collect.ts +15 -1
  133. package/src/discovery/fold-build-parity.test.ts +71 -0
  134. package/src/discovery/fold-import.ts +2 -113
  135. package/src/discovery/fold-project-provenance.test.ts +21 -4
  136. package/src/discovery/host-composite.ts +101 -0
  137. package/src/discovery/index.ts +11 -1
  138. package/src/gate-decision-bundle.test.ts +6 -4
  139. package/src/gate-decision-identity.test.ts +59 -6
  140. package/src/gate-decision.ts +81 -21
  141. package/src/gated-waves.ts +28 -6
  142. package/src/lifecycle/gate-audit.test.ts +55 -1
  143. package/src/lifecycle/gate-audit.ts +116 -3
  144. package/src/lifecycle/gate-ledger.test.ts +20 -1
  145. package/src/lifecycle/gate-ledger.ts +33 -1
  146. package/src/op/activities/activity-contracts.ts +1 -1
  147. package/src/op/activities/change-set.ts +11 -3
  148. package/src/op/activities/lexicon-upgrade.test.ts +2 -0
  149. package/src/op/activities/reconcile.test.ts +62 -2
  150. package/src/op/activities/reconcile.ts +46 -22
  151. package/src/op/builders.ts +1 -1
  152. package/src/op/op-waves-run.ts +9 -9
  153. package/src/op/op-waves.test.ts +2 -0
  154. package/src/op/op-waves.ts +4 -8
  155. package/src/op/work-lease-run.test.ts +12 -4
  156. package/src/package-version.ts +11 -0
  157. package/src/wave-engine.test.ts +107 -0
  158. package/src/wave-engine.ts +98 -0
  159. package/src/wave-layout.ts +44 -0
  160. package/src/wave-runtime-bundle.test.ts +70 -0
  161. package/src/wave-runtime.e2e.test.ts +177 -0
  162. package/src/wave-runtime.ts +104 -0
  163. package/src/workspace/__fixtures__/contract-repo.ts +17 -1
  164. package/src/workspace/__fixtures__/sessions.ts +3 -3
  165. package/src/workspace/ajv2020.ts +17 -0
  166. package/src/workspace/change-set.schema.json +5 -3
  167. package/src/workspace/ci-green-workflow.test.ts +4 -4
  168. package/src/workspace/ci-green-workflow.ts +5 -2
  169. package/src/workspace/class-data.test.ts +64 -0
  170. package/src/workspace/class-data.ts +236 -0
  171. package/src/workspace/declaration.ts +4 -10
  172. package/src/workspace/kinds.ts +8 -40
  173. package/src/workspace/points.ts +2 -5
  174. package/src/workspace/principal-classes.ts +13 -99
  175. package/src/workspace/records-formats.test.ts +7 -2
  176. package/src/workspace/records-sessions-write.test.ts +3 -3
  177. package/src/workspace/status-contract.e2e.test.ts +37 -0
  178. package/src/workspace/status-contract.test.ts +23 -21
package/src/cli/main.ts CHANGED
@@ -14,8 +14,12 @@ import { armSandboxPolicyExecution } from "../lint/policy-import";
14
14
  import { ENV_VAR, unknownEnvError } from "../env";
15
15
  import { initRuntime } from "../runtime-adapter";
16
16
  import { installCompiledCli } from "../ts-loader";
17
- import { splitJoinedFlags, dispatchCommandGroup, collectCommandGroups, formatCommandGroupsHelp, type CommandGroup } from "./command-group";
17
+ import { dispatchCommandGroup, collectCommandGroups, formatCommandGroupsHelp, type CommandGroup } from "./command-group";
18
18
  import type { LexiconPlugin } from "../lexicon";
19
+ import { parseArgs } from "./parse-args";
20
+ import { flushAndExit, waitForStreamDrain } from "./flush";
21
+
22
+ export { parseArgs, waitForStreamDrain };
19
23
 
20
24
  /**
21
25
  * #3878 — each command's handler module loads when that command runs, not on
@@ -125,780 +129,6 @@ const loadPlugins: typeof import("./plugins").loadPlugins = async (...a) => (awa
125
129
  const resolveProjectLexicons: typeof import("./plugins").resolveProjectLexicons = async (...a) =>
126
130
  (await import("./plugins")).resolveProjectLexicons(...a);
127
131
 
128
- /**
129
- * Long-form flags that are pure booleans in {@link parseArgs} — their branch
130
- * below sets a field to `true` and never consumes a following array element.
131
- * Used only to reject a joined `--flag=value` form for these (chant #1127):
132
- * a boolean has no value to assign, and silently reinterpreting the joined
133
- * value as the next positional argument (path, component name, ...) would be
134
- * exactly the kind of silent misparse this issue exists to close. `--report`
135
- * is deliberately excluded — it's context-sensitive (bare boolean vs a SARIF
136
- * path, decided by lookahead), so a joined value for it is legitimate and
137
- * already handled correctly once split.
138
- */
139
- const BOOLEAN_FLAGS = new Set([
140
- "--help",
141
- "--open",
142
- "--version",
143
- "--agents",
144
- "--agent",
145
- "--all-projects",
146
- "--force",
147
- "--fix",
148
- "--watch",
149
- "--verbose",
150
- "--live",
151
- "--overlay",
152
- "--explain",
153
- "--owned",
154
- "--verbatim",
155
- "--apply-rewrites",
156
- "--write",
157
- "--strict",
158
- "--validate",
159
- "--use-composites",
160
- "--composites",
161
- "--stacks",
162
- "--components",
163
- "--up",
164
- "--down",
165
- "--include-dependents",
166
- "--local",
167
- "--json",
168
- "--progress-json",
169
- "--update-snapshot",
170
- "--update-baseline",
171
- "--run-examples",
172
- "--check",
173
- "--bump",
174
- "--no-release-record",
175
- "--fold",
176
- "--no-fold",
177
- "--no-cache",
178
- "--sandbox",
179
- "--yes",
180
- "--confirm-prod",
181
- "--once",
182
- "--expire",
183
- "--check-live",
184
- "--check-snapshot",
185
- "--fail-on-drift",
186
- "--durable-requests",
187
- "--skip-mcp",
188
- "--current",
189
- "--uncommitted",
190
- "--allow-code",
191
- "--root-only",
192
- "--generated",
193
- "--decide",
194
- "--record-plans",
195
- ]);
196
-
197
- /**
198
- * Parse command line arguments
199
- */
200
- export function parseArgs(args: string[]): ParsedArgs {
201
- // Local mutable copy — chant #1127's joined-`--flag=value` splitting below
202
- // rewrites the array in place (one token becomes two), so this must not
203
- // mutate whatever array the caller passed in (e.g. `process.argv.slice(2)`
204
- // is already a fresh copy, but callers shouldn't have to know that).
205
- args = args.slice();
206
-
207
- const result: ParsedArgs = {
208
- command: "",
209
- path: ".",
210
- extraPositional: undefined,
211
- extraPositional2: undefined,
212
- output: undefined,
213
- format: "",
214
- force: undefined,
215
- fix: false,
216
- lexicon: undefined,
217
- template: undefined,
218
- watch: false,
219
- verbose: false,
220
- help: false,
221
- param: undefined,
222
- paramsFile: undefined,
223
- report: undefined,
224
- local: undefined,
225
- profile: undefined,
226
- json: undefined,
227
- live: false,
228
- migrateFrom: undefined,
229
- migrateTo: undefined,
230
- emit: undefined,
231
- strict: false,
232
- validate: false,
233
- useComposites: false,
234
- reportFile: undefined,
235
- skill: undefined,
236
- skipMcp: undefined,
237
- src: undefined,
238
- env: undefined,
239
- };
240
-
241
- // chant #1127 — generic joined `--flag=value` support, factored out to
242
- // ./command-group.ts (chant #1078) so a lexicon's own mounted command can
243
- // apply the identical splitting discipline to its own flag vocabulary.
244
- // Every value-taking flag below is matched by an exact `arg === "--flag"`
245
- // check and then consumes the *next* array element (`args[++i]`) as its
246
- // value; a joined token like `--env=prod` never matches any of those,
247
- // doesn't match the trailing positional branch either (it starts with
248
- // `-`), and used to vanish with no error. Splitting the token at its FIRST
249
- // `=` and re-dispatching as two array elements makes every flag below see
250
- // the exact shape it already handles — including a flag like `--param`
251
- // whose own value legitimately contains `=` (`--param=tier=production`
252
- // splits to flag `--param`, value `tier=production`, not further split on
253
- // the second `=`).
254
- args = splitJoinedFlags(args, BOOLEAN_FLAGS);
255
-
256
- let i = 0;
257
- while (i < args.length) {
258
- const arg = args[i];
259
-
260
- if (arg === "--help" || arg === "-h") {
261
- result.help = true;
262
- } else if (arg === "--version" || arg === "-V") {
263
- result.version = true;
264
- } else if (arg === "--output" || arg === "-o") {
265
- result.output = args[++i];
266
- } else if (arg === "--format" || arg === "-f") {
267
- result.format = args[++i];
268
- } else if (arg === "--lexicon" || arg === "-d") {
269
- result.lexicon = args[++i];
270
- } else if (arg === "--template" || arg === "-t") {
271
- result.template = args[++i];
272
- } else if (arg === "--force") {
273
- result.force = true;
274
- } else if (arg === "--fix") {
275
- result.fix = true;
276
- } else if (arg === "--watch" || arg === "-w") {
277
- result.watch = true;
278
- } else if (arg === "--verbose" || arg === "-v") {
279
- result.verbose = true;
280
- } else if (arg === "--profile" || arg === "-p") {
281
- result.profile = args[++i];
282
- } else if (arg === "--report") {
283
- // --report alone is the boolean (used by `run`); --report <path> is
284
- // the migrate-command file path. Look ahead for a non-flag.
285
- const next = args[i + 1];
286
- if (next && !next.startsWith("-")) {
287
- result.reportFile = next;
288
- i++;
289
- } else {
290
- result.report = true;
291
- }
292
- } else if (arg === "--live") {
293
- result.live = true;
294
- } else if (arg === "--overlay") {
295
- result.overlay = true;
296
- } else if (arg === "--between") {
297
- result.betweenA = args[++i];
298
- result.betweenB = args[++i];
299
- if (!result.betweenA || !result.betweenB) throw new Error("--between needs two snapshot refs: --between <refA> <refB>");
300
- } else if (arg === "--traffic") {
301
- const v = args[++i];
302
- if (!v) throw new Error('--traffic needs a level, passed to the engine verbatim: --traffic "100 rps, p50"');
303
- result.traffic = v;
304
- } else if (arg === "--overlay-anchor") {
305
- const v = args[++i];
306
- if (v !== "source" && v !== "live") throw new Error(`--overlay-anchor must be 'source' or 'live', got '${v}'`);
307
- result.overlayAnchor = v;
308
- } else if (arg === "--from") {
309
- // Shared by `migrate --from <lexicon>`, `import --from <env>`,
310
- // `components promote --from <env>` and `init --from <repo>@<ref>`;
311
- // the commands never run together, so one field carries all four.
312
- result.migrateFrom = args[++i];
313
- } else if (arg === "--kustomize") {
314
- // `chant import --kustomize <dir>` (#1548): render the overlay, import
315
- // the output through the k8s template parser.
316
- result.kustomize = args[++i];
317
- } else if (arg === "--parser-option") {
318
- // `chant import --parser-option key[=value]` (#2994), repeatable: checked
319
- // against the lexicon's parserOptions() by the import command.
320
- const v = args[++i];
321
- if (!v || v.startsWith("-")) throw new Error("--parser-option needs key or key=value: --parser-option acceptLossyV1");
322
- (result.parserOption ??= []).push(v);
323
- } else if (arg === "--type") {
324
- result.selectType = args[++i];
325
- } else if (arg === "--name") {
326
- result.selectName = args[++i];
327
- } else if (arg === "--owned") {
328
- result.owned = true;
329
- } else if (arg === "--namespace") {
330
- // The live read's namespace default (#1629), not a kubectl `-n`: it fills
331
- // in for entities that declare no namespace and leaves the ones that do
332
- // alone. No `-n` short form — that spelling belongs to `chant kube`,
333
- // where it means kubectl's scope selector.
334
- result.namespace = args[++i];
335
- if (!result.namespace || result.namespace.startsWith("-")) throw new Error("--namespace needs a namespace: --namespace <ns>");
336
- } else if (arg === "--verbatim") {
337
- result.verbatim = true;
338
- } else if (arg === "--state") {
339
- result.statePath = args[++i];
340
- } else if (arg === "--select") {
341
- result.selectAddress = args[++i];
342
- } else if (arg === "--live-name") {
343
- result.liveName = args[++i];
344
- } else if (arg === "--apply-rewrites") {
345
- result.applyRewrites = true;
346
- } else if (arg === "--stack") {
347
- result.carveStack = args[++i];
348
- } else if (arg === "--write") {
349
- result.write = true;
350
- } else if (arg === "--write-source") {
351
- result.writeSource = true;
352
- } else if (arg === "--to") {
353
- result.migrateTo = args[++i];
354
- } else if (arg === "--emit") {
355
- result.emit = args[++i];
356
- } else if (arg === "--dry-run") {
357
- result.dryRun = true;
358
- } else if (arg === "--yes") {
359
- result.yes = true;
360
- } else if (arg === "--confirm-prod") {
361
- result.confirmProd = true;
362
- } else if (arg === "--strict") {
363
- result.strict = true;
364
- } else if (arg === "--validate") {
365
- result.validate = true;
366
- } else if (arg === "--use-composites") {
367
- result.useComposites = true;
368
- } else if (arg === "--skill") {
369
- result.skill = args[++i];
370
- } else if (arg === "--skip-mcp") {
371
- result.skipMcp = true;
372
- } else if (arg === "--src") {
373
- result.src = args[++i];
374
- } else if (arg === "--env") {
375
- result.env = args[++i];
376
- } else if (arg === "--port") {
377
- const value = args[++i];
378
- if (!value || value.startsWith("-")) throw new Error("--port needs a port: --port <container-port>=<host-port>");
379
- (result.port ??= []).push(value);
380
- } else if (arg === "--promote-to") {
381
- result.promoteTo = args[++i];
382
- if (!result.promoteTo || result.promoteTo.startsWith("-")) throw new Error("--promote-to needs an environment: --promote-to <env>");
383
- } else if (arg === "--tier") {
384
- result.tier = args[++i];
385
- } else if (arg === "--agents") {
386
- result.agents = true;
387
- } else if (arg === "--scope") {
388
- result.scope = args[++i];
389
- } else if (arg === "--all-projects") {
390
- result.allProjects = true;
391
- } else if (arg === "--runtime") {
392
- result.runtime = args[++i];
393
- } else if (arg === "--fail-on") {
394
- result.failOn = args[++i];
395
- } else if (arg === "--max-files") {
396
- // Validated by the audit handler, like `--limit` below.
397
- result.maxFiles = Number(args[++i]);
398
- } else if (arg === "--theme") {
399
- result.theme = args[++i];
400
- } else if (arg === "--stacks") {
401
- result.stacks = true;
402
- } else if (arg === "--components") {
403
- result.components = true;
404
- } else if (arg === "--generate") {
405
- result.generate = args[++i];
406
- } else if (arg === "--spec") {
407
- result.opsSpec = args[++i];
408
- } else if (arg === "--dump-outputs") {
409
- result.dumpOutputs = args[++i];
410
- } else if (arg === "--digest-file") {
411
- result.digestFile = args[++i];
412
- } else if (arg === "--seed-outputs") {
413
- (result.seedOutputs ??= []).push(args[++i]);
414
- } else if (arg === "--detail") {
415
- result.detail = Number(args[++i]);
416
- } else if (arg === "--lens") {
417
- result.lens = args[++i];
418
- } else if (arg === "--explain") {
419
- result.explain = true;
420
- } else if (arg === "--show") {
421
- result.show = args[++i];
422
- } else if (arg === "--up") {
423
- result.up = true;
424
- } else if (arg === "--down") {
425
- result.down = true;
426
- } else if (arg === "--node-sizes") {
427
- result.nodeSizes = args[++i];
428
- } else if (arg === "--layout-engine") {
429
- result.layoutEngine = args[++i];
430
- } else if (arg === "--base") {
431
- result.base = args[++i];
432
- } else if (arg === "--head") {
433
- result.head = args[++i];
434
- } else if (arg === "--include-dependents") {
435
- result.includeDependents = true;
436
- } else if (arg === "--from-affected") {
437
- result.fromAffected = args[++i];
438
- } else if (arg === "--gate") {
439
- result.gate = args[++i];
440
- } else if (arg === "--wave-gate") {
441
- result.waveGate = args[++i];
442
- if (!result.waveGate || result.waveGate.startsWith("-")) throw new Error("--wave-gate needs a gate name: --wave-gate <name>");
443
- } else if (arg === "--wave") {
444
- const raw = args[++i];
445
- const wave = Number(raw);
446
- if (!Number.isInteger(wave) || wave < 1) throw new Error(`--wave needs a wave number from 1: got "${raw ?? ""}"`);
447
- result.wave = wave;
448
- } else if (arg === "--decide") {
449
- result.decide = true;
450
- } else if (arg === "--record-plans") {
451
- result.recordPlans = true;
452
- } else if (arg === "--share") {
453
- const raw = args[++i];
454
- const share = Number(raw);
455
- if (!Number.isInteger(share) || share < 1) throw new Error(`--share needs a share number from 1: got "${raw ?? ""}"`);
456
- result.share = share;
457
- } else if (arg === "--canary") {
458
- const raw = args[++i];
459
- if (!raw || raw.startsWith("-")) throw new Error("--canary needs a component name: --canary <name>[,<name>...]");
460
- (result.canary ??= []).push(...raw.split(",").map((n) => n.trim()).filter(Boolean));
461
- } else if (arg === "--pr") {
462
- const raw = args[++i];
463
- const pr = Number(raw);
464
- if (!Number.isInteger(pr) || pr < 1) throw new Error(`--pr needs a pull or merge request number: got "${raw ?? ""}"`);
465
- result.pr = pr;
466
- } else if (arg === "--forge") {
467
- result.forge = args[++i];
468
- if (!result.forge || result.forge.startsWith("-")) throw new Error("--forge needs a forge: --forge github|gitlab|forgejo");
469
- } else if (arg === "--require-review") {
470
- result.requireReview = true;
471
- } else if (arg === "--pr-loop") {
472
- result.prLoop = true;
473
- } else if (arg === "--resume") {
474
- // `chant approve <op> <gate> --resume` (#3683) takes no value; the
475
- // fan-out and pull-request apply take the attempt file they resume.
476
- const next = args[i + 1];
477
- if (next === undefined || next.startsWith("-")) result.resumeRun = true;
478
- else result.resume = args[++i];
479
- } else if (arg === "--local") {
480
- result.local = true;
481
- } else if (arg === "--json") {
482
- result.json = true;
483
- } else if (arg === "--progress-json") {
484
- result.progressJson = true;
485
- } else if (arg === "--gated-exit") {
486
- // `chant run <op> --gated-exit <code>` (#2243) — remap only the gated
487
- // outcome's exit code. Parsed as a number here and range-checked in the
488
- // handler, where the refusal can name the flag alongside the run.
489
- result.gatedExit = Number(args[++i]);
490
- } else if (arg === "--durable-requests") {
491
- // `chant run approve ... --on <lexicon> --durable-requests` (#2126) —
492
- // resolve the gate on the hosting runtime's own request path rather
493
- // than by posting a follow-up prompt. Core only carries the flag; the
494
- // runtime decides whether it has such a path.
495
- result.durableRequests = true;
496
- } else if (arg === "--update-snapshot") {
497
- result.updateSnapshot = true;
498
- } else if (arg === "--update-baseline") {
499
- result.updateBaseline = true;
500
- } else if (arg === "--deep") {
501
- result.deep = true;
502
- } else if (arg === "--at") {
503
- result.at = args[++i];
504
- } else if (arg === "--kind") {
505
- // `chant workspace records|graph|check --kind <kind file>` (#2546, #2549)
506
- result.kind = args[++i];
507
- if (!result.kind || result.kind.startsWith("-")) throw new Error("--kind needs a kind file: --kind <path>");
508
- // Repeatable for `workspace graph --intent` (#2651); the others read the last one.
509
- (result.kinds ??= []).push(result.kind);
510
- } else if (arg === "--composites") {
511
- // `chant workspace graph --composites` (#2662)
512
- result.composites = true;
513
- } else if (arg === "--intent") {
514
- // `chant workspace graph --intent <path[:start-end]|path#symbol>` (#2651), or `--intent --record <id>` with no region.
515
- const value = args[i + 1];
516
- if (value !== undefined && !value.startsWith("-")) result.intent = args[++i];
517
- else if (args.includes("--record")) result.intent = "";
518
- else throw new Error("--intent needs a region: --intent <path[:start-end]|path#symbol>, or --intent --record <id>");
519
- } else if (arg === "--follow-squash") {
520
- // `chant workspace graph --intent ... --follow-squash` and `chant workspace runs --follow-squash` (#3035)
521
- result.followSquash = true;
522
- } else if (arg === "--record") {
523
- // `chant workspace graph --intent --record <id>`
524
- result.record = args[++i];
525
- if (!result.record || result.record.startsWith("-")) throw new Error("--record needs a record id: --record <id>");
526
- } else if (arg === "--path") {
527
- // `chant workspace patch <range> --path <p>`, repeatable
528
- const value = args[++i];
529
- if (!value || value.startsWith("-")) throw new Error("--path needs a path: --path <path>");
530
- (result.paths ??= []).push(value);
531
- } else if (arg === "--worktree") {
532
- // `chant workspace patch [<commit>] --worktree`
533
- result.worktree = true;
534
- } else if (arg === "--max-bytes") {
535
- // `chant workspace patch <range> --max-bytes <n>`
536
- const value = Number(args[++i]);
537
- if (!Number.isInteger(value) || value < 1) throw new Error("--max-bytes needs a whole number of bytes above 0");
538
- result.maxBytes = value;
539
- } else if (arg === "--changes") {
540
- // `chant workspace check --changes <base>..<head>` (#2773)
541
- result.changes = args[++i];
542
- if (!result.changes || result.changes.startsWith("-")) throw new Error("--changes needs a range: --changes <base>..<head>");
543
- } else if (arg === "--severity") {
544
- // `chant workspace check --changes <range> --severity off|warn|fail` (#2773)
545
- result.severity = args[++i];
546
- if (!result.severity || result.severity.startsWith("-")) throw new Error("--severity needs off, warn or fail");
547
- } else if (arg === "--current") {
548
- result.current = true;
549
- } else if (arg === "--uncommitted") {
550
- // `chant workspace records --uncommitted` (#3160)
551
- result.uncommitted = true;
552
- } else if (arg === "--set") {
553
- // `chant workspace records amend <id> --set <file|->` (#2670)
554
- result.set = args[++i];
555
- if (!result.set || (result.set.startsWith("-") && result.set !== "-")) throw new Error("--set needs a JSON file, or - for standard input: --set <file|->");
556
- } else if (arg === "--cover") {
557
- // `chant workspace box listing set <member> --cover <image>` (#3308)
558
- result.cover = args[++i];
559
- if (!result.cover || result.cover.startsWith("-")) throw new Error("--cover needs a PNG, JPEG or WebP file: --cover <image>");
560
- } else if (arg === "--cover-path") {
561
- // `chant workspace box listing set <member> --cover <image> --cover-path <path>` (#3308)
562
- result.coverPath = args[++i];
563
- if (!result.coverPath || result.coverPath.startsWith("-")) throw new Error("--cover-path needs a path from the workspace root: --cover-path <path>");
564
- } else if (arg === "--records") {
565
- // `chant workspace box publish <member> --records` (#3165): the records kept uncommitted, not a work item.
566
- result.records = true;
567
- } else if (arg === "--expect") {
568
- // `chant workspace records amend|review|close <id> --expect <digest>` (#3173): the record's digest the caller last read.
569
- result.expect = args[++i];
570
- if (!result.expect || result.expect.startsWith("-")) throw new Error("--expect needs the record's digest, as records --json prints it: --expect <digest>");
571
- } else if (arg === "--label") {
572
- // `chant workspace wip save --label <text>` (#3172)
573
- result.label = args[++i];
574
- if (!result.label || result.label.startsWith("-")) throw new Error("--label needs the snapshot's name, such as a turn id: --label <text>");
575
- } else if (arg === "--branch") {
576
- // `chant workspace wip --branch <branch>` (#3172)
577
- result.branch = args[++i];
578
- if (!result.branch || result.branch.startsWith("-")) throw new Error("--branch needs a branch name: --branch <branch>");
579
- } else if (arg === "--workflow") {
580
- // `chant ci workflow --workflow <name>`, repeatable (#3573)
581
- const name = args[++i];
582
- if (!name || name.startsWith("-")) throw new Error("--workflow needs a workflow's name: --workflow <name>");
583
- result.ciWorkflows = [...(result.ciWorkflows ?? []), name];
584
- } else if (arg === "--chant") {
585
- // `chant ci workflow --chant <command>` (#3573): what the tick step runs chant with.
586
- result.ciChant = args[++i];
587
- if (!result.ciChant || result.ciChant.startsWith("-")) throw new Error("--chant needs the command that runs chant, such as \"npx tsx packages/core/src/cli/main.ts\": --chant <command>");
588
- } else if (arg === "--install") {
589
- // `chant ci workflow --install <command>` (#3573): a step before the tick that installs what --chant runs.
590
- result.ciInstall = args[++i];
591
- if (!result.ciInstall || result.ciInstall.startsWith("-")) throw new Error("--install needs the install command, such as \"npm install\": --install <command>");
592
- } else if (arg === "--token-secret") {
593
- // `chant ci workflow --token-secret <NAME>` (#3573): the secret the tick's tag push authenticates with.
594
- result.ciTokenSecret = args[++i];
595
- if (!result.ciTokenSecret || result.ciTokenSecret.startsWith("-")) throw new Error("--token-secret needs a repository secret's name, such as CI_GREEN_TOKEN: --token-secret <NAME>");
596
- } else if (arg === "--app-id-var") {
597
- // `chant ci workflow --app-id-var <VAR> --app-key-secret <NAME>` (#3573): a GitHub App token pushes the tags.
598
- result.ciAppIdVar = args[++i];
599
- if (!result.ciAppIdVar || result.ciAppIdVar.startsWith("-")) throw new Error("--app-id-var needs the Actions variable holding the App's id: --app-id-var <VAR>");
600
- } else if (arg === "--app-key-secret") {
601
- result.ciAppKeySecret = args[++i];
602
- if (!result.ciAppKeySecret || result.ciAppKeySecret.startsWith("-")) throw new Error("--app-key-secret needs the secret holding the App's private key: --app-key-secret <NAME>");
603
- } else if (arg === "--verdict") {
604
- // `chant workspace records review <id> --verdict agree|dissent|abstain` (#2670)
605
- result.verdict = args[++i];
606
- if (!result.verdict || result.verdict.startsWith("-")) throw new Error("--verdict needs agree, dissent or abstain");
607
- } else if (arg === "--by") {
608
- // `chant workspace records review <id> --by <principal>` (#2670): whoever the caller says.
609
- result.by = args[++i];
610
- if (!result.by || result.by.startsWith("-")) throw new Error("--by needs the reviewer: --by <principal>");
611
- // Repeatable for `workspace points answer` (#2739): each person who answered.
612
- (result.bys ??= []).push(result.by);
613
- } else if (arg === "--open") {
614
- // `chant workspace points --open` (#2739): only the questions still open.
615
- result.open = true;
616
- } else if (arg === "--inputs") {
617
- // `chant workspace points ask <point> --inputs <file|-|json>` (#2739; JSON itself, #3403)
618
- result.inputs = args[++i];
619
- if (!result.inputs || (result.inputs.startsWith("-") && result.inputs !== "-")) throw new Error("--inputs needs a JSON file, - for standard input, or the JSON itself: --inputs <file|-|json>");
620
- } else if (arg === "--candidates") {
621
- // `chant workspace points ask <point> --candidates <file|-|json>` (#3403): an ad-hoc point's question and candidates.
622
- result.candidates = args[++i];
623
- if (!result.candidates || (result.candidates.startsWith("-") && result.candidates !== "-")) throw new Error("--candidates needs a JSON file, - for standard input, or the JSON itself: --candidates <file|-|json>");
624
- } else if (arg === "--response") {
625
- // `chant workspace points ask <point> --response <file>` (#2739): a POST /v1/systemone response the caller got.
626
- result.response = args[++i];
627
- if (!result.response || result.response.startsWith("-")) throw new Error("--response needs a JSON file: --response <file>");
628
- } else if (arg === "--subject") {
629
- // `chant workspace points ask <point> --subject <id>` (#2739): what the question is about.
630
- result.subject = args[++i];
631
- if (!result.subject || result.subject.startsWith("-")) throw new Error("--subject needs what the question is about, such as a work item id: --subject <id>");
632
- } else if (arg === "--store") {
633
- // `chant workspace points ask <point> --store ledger` (#3815): where a new question goes.
634
- const store = args[++i];
635
- if (store !== "tree" && store !== "ledger") throw new Error("--store is tree (the answer kind's directory in the working tree) or ledger (chant/lifecycle): --store <tree|ledger>");
636
- result.store = store;
637
- } else if (arg === "--answer") {
638
- // `chant workspace points answer <id> --answer <value>` (#2739)
639
- result.answer = args[++i];
640
- if (result.answer === undefined || result.answer === "") throw new Error("--answer needs one of the question's candidates: --answer <value>");
641
- } else if (arg === "--sign") {
642
- // `chant workspace records review <id> --sign [<key file>]` (#2687): seal the verdict.
643
- // `records new` and `records amend` take it too, to seal the record's author (#2688).
644
- // `chant approve <op> <gate> --sign [<key file>]` seals a gate approval (#3163).
645
- // With no key file, git's user.signingkey, as `git commit -S` reads it.
646
- const next = args[i + 1];
647
- result.sign = next !== undefined && !next.startsWith("-") ? args[++i] : true;
648
- } else if (arg === "--session") {
649
- // `chant workspace records review <id> --session <id>` (#2670)
650
- result.session = args[++i];
651
- if (!result.session || result.session.startsWith("-")) throw new Error("--session needs a session id: --session <id>");
652
- } else if (arg === "--prefix") {
653
- // `chant workspace records new <kind> --prefix <prefix>` (#2670): the id prefix to allocate under.
654
- result.prefix = args[++i];
655
- if (!result.prefix || result.prefix.startsWith("-")) throw new Error("--prefix needs an id prefix: --prefix <prefix>");
656
- } else if (arg === "--key" || arg === "--check-id" || arg === "--claim" || arg === "--environment" || arg === "--envelope" || arg === "--threshold") {
657
- // `chant workspace evidence sign|verify` and `chant workspace signers` (#2553)
658
- const value = args[++i];
659
- if (value === undefined || value.startsWith("-")) throw new Error(`${arg} needs a value`);
660
- const field = ({ "--key": "key", "--check-id": "checkId", "--claim": "claim", "--environment": "environment", "--envelope": "envelope", "--threshold": "threshold" } as const)[arg];
661
- result[field] = value;
662
- } else if (arg === "--require") {
663
- // `chant workspace records|verify --require attested` (#2547)
664
- result.require = args[++i];
665
- if (!result.require || result.require.startsWith("-")) throw new Error("--require needs a provenance level: --require attested");
666
- } else if (arg === "--ambient") {
667
- result.ambient = true;
668
- } else if (arg === "--check-live") {
669
- result.checkLive = true;
670
- } else if (arg === "--check-snapshot") {
671
- result.checkSnapshot = true;
672
- } else if (arg === "--fail-on-drift") {
673
- result.failOnDrift = true;
674
- } else if (arg === "--run-examples") {
675
- result.runExamples = true;
676
- } else if (arg === "--pinned-digest") {
677
- result.pinnedDigest = args[++i];
678
- } else if (arg === "--check") {
679
- result.check = true;
680
- } else if (arg === "--bump") {
681
- result.bump = true;
682
- } else if (arg === "--component") {
683
- result.component = args[++i];
684
- } else if (arg === "--digest") {
685
- result.digest = args[++i];
686
- if (result.digest !== undefined) (result.digests ??= []).push(result.digest);
687
- } else if (arg === "--release-plan") {
688
- result.releasePlanFile = args[++i];
689
- } else if (arg === "--git-sha") {
690
- result.gitSha = args[++i];
691
- } else if (arg === "--run-id") {
692
- result.runId = args[++i];
693
- } else if (arg === "--actor") {
694
- result.actor = args[++i];
695
- } else if (arg === "--approver") {
696
- result.approver = args[++i];
697
- } else if (arg === "--relayed-by") {
698
- // `chant approve <op> <gate> --relayed-by <principal>` and `workspace points answer <id> --relayed-by <principal>` (#3402):
699
- // who carried the approval or answer to chant for the person it names.
700
- result.relayedBy = args[++i];
701
- if (!result.relayedBy || result.relayedBy.startsWith("-")) throw new Error("--relayed-by needs the principal that relayed it: --relayed-by <principal>");
702
- } else if (arg === "--on") {
703
- // `chant run ... --on <lexicon>` (#2121) — which runtime hosts the run.
704
- result.on = args[++i];
705
- if (!result.on || result.on.startsWith("-")) throw new Error("--on needs a runtime name: --on <lexicon>");
706
- } else if (arg === "--compare-to") {
707
- result.compareTo = args[++i];
708
- } else if (arg === "--no-release-record") {
709
- result.noReleaseRecord = true;
710
- } else if (arg === "--fold") {
711
- result.fold = true;
712
- } else if (arg === "--no-cache") {
713
- // `chant workspace graph --no-cache` (#2876): read every member.
714
- result.noCache = true;
715
- } else if (arg === "--no-fold") {
716
- // chant #1134 — fold is the default build path; this is the explicit
717
- // opt-out, and like --fold it beats chant.config.ts's build.fold.
718
- result.fold = false;
719
- } else if (arg === "--sandbox") {
720
- result.sandbox = true;
721
- } else if (arg === "--fold-rank") {
722
- // chant #1083 — same context-sensitive shape as --report above:
723
- // `--fold-rank` alone prints the ranked-blocker report; `--fold-rank
724
- // <path>` ALSO writes the Brendan Gregg collapsed-format export there.
725
- const next = args[i + 1];
726
- if (next && !next.startsWith("-")) {
727
- result.foldRankCollapsedFile = next;
728
- i++;
729
- } else {
730
- result.foldRank = true;
731
- }
732
- } else if (arg === "--param") {
733
- // chant #1118/#1127 — `--param name=value` (space-separated) and
734
- // `--param=name=value` (joined, split above at its first `=` into flag
735
- // `--param` + value `name=value`) both land here and behave
736
- // identically; there is no separate joined-form error anymore (the
737
- // #1118 hard error this superseded only existed because the parser
738
- // didn't support joined forms at all — now that it does, the joined
739
- // form is just as valid as the space-separated one).
740
- (result.param ??= []).push(args[++i]);
741
- } else if (arg === "--lexicon-output") {
742
- // `--lexicon-output <lexicon>=<path>` (repeatable); the joined form
743
- // `--lexicon-output=otel=a.yml` arrives here already split at the first `=`.
744
- (result.lexiconOutput ??= []).push(args[++i]);
745
- } else if (arg === "--params-file") {
746
- result.paramsFile = args[++i];
747
- } else if (arg === "--projection") {
748
- result.projection = args[++i];
749
- } else if (arg === "--interval") {
750
- result.interval = args[++i];
751
- } else if (arg === "--lease-ttl") {
752
- result.leaseTtl = args[++i];
753
- } else if (arg === "--steward") {
754
- // #2731 — the name is optional: a project with one steward runs it
755
- // with a bare `--steward`.
756
- const next = args[i + 1];
757
- if (next !== undefined && !next.startsWith("-")) {
758
- result.steward = next;
759
- i++;
760
- } else {
761
- result.steward = "";
762
- }
763
- } else if (arg === "--once") {
764
- result.once = true;
765
- } else if (arg === "--work") {
766
- // `chant run <op> --work <id>` (#2748)
767
- const value = args[++i];
768
- if (!value || value.startsWith("-")) throw new Error("--work needs a work item id: --work <id>");
769
- result.work = value;
770
- } else if (arg === "--holder" || arg === "--ttl" || arg === "--token" || arg === "--outcome") {
771
- // `chant workspace work claim|renew|release <id>` (#2732)
772
- const value = args[++i];
773
- if (!value || value.startsWith("-")) throw new Error(`${arg} needs a value: ${arg} <${arg.slice(2)}>`);
774
- result[arg.slice(2) as "holder" | "ttl" | "token" | "outcome"] = value;
775
- } else if (arg === "--signer") {
776
- // `chant workspace runs statement <id> --signer <principal>` (#3192): the runner principal that will sign.
777
- const value = args[++i];
778
- if (!value || value.startsWith("-")) throw new Error("--signer needs a principal: --signer <principal>");
779
- result.signer = value;
780
- } else if (arg === "--unit" || arg === "--decision") {
781
- // `chant workspace runs --unit <id> | --decision <id>` (#3033): the runs on one work item, or for one decision.
782
- const value = args[++i];
783
- if (!value || value.startsWith("-")) throw new Error(`${arg} needs an id: ${arg} <id>`);
784
- result[arg.slice(2) as "unit" | "decision"] = value;
785
- } else if (arg === "--note") {
786
- result.note = args[++i];
787
- } else if (arg === "--reason") {
788
- // `chant approve policy-override <subject> --reason <text>` (#3760): why the denied plan goes out anyway.
789
- result.reason = args[++i];
790
- if (result.reason === undefined || result.reason.startsWith("-")) throw new Error("--reason needs the text of the reason: --reason <text>");
791
- } else if (arg === "--rule") {
792
- // `chant approve policy-override <subject> --rule <id>` (#3760), once per rule that denied the plan.
793
- const value = args[++i];
794
- if (!value || value.startsWith("-")) throw new Error("--rule needs a rule id: --rule <id>");
795
- (result.rules ??= []).push(value);
796
- } else if (arg === "--expire") {
797
- result.expire = true;
798
- } else if (arg === "--role") {
799
- // #2508 — a role the approver holds, for a gate whose quorum names
800
- // roles. Repeatable, and a comma list works too.
801
- const value = args[++i] ?? "";
802
- result.roles = [...(result.roles ?? []), ...value.split(",").map((r) => r.trim()).filter(Boolean)];
803
- } else if (arg === "--agent") {
804
- // #2508 — record the approval as an agent's rather than a person's.
805
- result.agent = true;
806
- } else if (arg === "--allow-code") {
807
- // #2550 — `chant workspace upgrade` runs a template's code migrations
808
- // only when asked to.
809
- result.allowCode = true;
810
- } else if (arg === "--source") {
811
- // #2551 — `chant workspace upgrade --source <repo>[#<member>]`.
812
- result.source = args[++i];
813
- if (!result.source || result.source.startsWith("-")) throw new Error("--source needs a template: --source <repo>[#<member>]");
814
- } else if (arg === "--tags") {
815
- // #2551 — `chant workspace adopt-lineage --tags <glob>`, also hash-index and versions.
816
- result.tags = args[++i];
817
- if (!result.tags || result.tags.startsWith("-")) throw new Error("--tags needs a tag glob: --tags 'v*'");
818
- } else if (arg === "--index") {
819
- // #2551 — `chant workspace adopt-lineage --index <file>`.
820
- result.index = args[++i];
821
- if (!result.index || result.index.startsWith("-")) throw new Error("--index needs a hash index file: --index <file>");
822
- } else if (arg === "--remove") {
823
- // #2552 — `chant workspace import --remove` removes the export member instead of writing it again.
824
- result.remove = true;
825
- } else if (arg === "--available") {
826
- // #2551 — `chant workspace versions --available` lists the template's version tags.
827
- result.available = true;
828
- } else if (arg === "--root-only") {
829
- // #2537 — `chant build` and `chant lint` at a declared workspace root
830
- // run on the root project alone instead of refusing with WSP000.
831
- result.rootOnly = true;
832
- } else if (arg === "--generated") {
833
- // #2641 — `chant workspace check --generated` runs each declared
834
- // generator and compares its output with the file in the tree.
835
- result.generated = true;
836
- } else if (arg === "--member") {
837
- // #2537 — `chant workspace build|lint|audit|graph --member <name>`.
838
- // Repeatable, and a comma list works too.
839
- const value = args[++i];
840
- if (!value || value.startsWith("-")) throw new Error("--member needs a member or group name: --member <name>");
841
- result.members = [...(result.members ?? []), ...value.split(",").map((m) => m.trim()).filter(Boolean)];
842
- } else if (arg === "--allow-same-origin") {
843
- // chant#2384 — record a resolution the same-origin rule would refuse,
844
- // deliberately. Flagged on the record, not just accepted quietly.
845
- result.allowSameOrigin = true;
846
- } else if (arg === "--plan") {
847
- result.plan = args[++i];
848
- } else if (arg === "--url") {
849
- result.url = args[++i];
850
- } else if (arg === "--op") {
851
- result.op = args[++i];
852
- } else if (arg === "--since") {
853
- result.since = args[++i];
854
- } else if (arg === "--limit") {
855
- // Parsed here, validated by the handler — `parseArgs` reports shape, not
856
- // policy, the same split every other value flag here uses.
857
- result.limit = Number(args[++i]);
858
- } else if (arg.startsWith("--")) {
859
- // chant #1127 — every recognized flag is matched above; anything left
860
- // starting with `--` is unrecognized, whether it arrived bare
861
- // (`--bogus`) or joined (`--bogus=value`, already split into
862
- // `--bogus` + `value` above). This used to fall through silently (the
863
- // "ignores unknown flags" case) — a typo'd or misremembered flag would
864
- // vanish with no diagnostic, exactly like the silent-drop this issue
865
- // closes for joined values. Point at --help rather than enumerating
866
- // every flag here: this parser's flag set is one flat list shared by
867
- // every command, not scoped per-command, so "the command's known
868
- // flags" isn't something this loop can name in isolation.
869
- throw new Error(`Unknown flag: ${arg}\nRun "chant --help" to see supported flags.`);
870
- } else if (!arg.startsWith("-")) {
871
- if (result.command) (result.positionals ??= []).push(arg);
872
- if (!result.command) {
873
- result.command = arg;
874
- } else if (result.path === ".") {
875
- result.path = arg;
876
- } else if (!result.extraPositional) {
877
- result.extraPositional = arg;
878
- } else {
879
- result.extraPositional2 = arg;
880
- }
881
- }
882
-
883
- i++;
884
- }
885
-
886
- // #2603 — `--promote-to` (#2575) adds a job to the pipeline that generate
887
- // mode synthesizes; no other command or mode reads it. Checked here, once
888
- // the whole command line is known, rather than in the build handler, so
889
- // `chant build --promote-to prod` and `chant run --promote-to prod` both
890
- // fail instead of running as if the flag were absent. A lexicon-mounted
891
- // command that takes its own `--promote-to` still gets it: `main` tries the
892
- // plugin commands before rethrowing a parse error.
893
- if (result.promoteTo && !(result.command === "build" && result.components && result.generate)) {
894
- throw new Error(
895
- "--promote-to needs build --components --generate <lexicon>: it adds a promote job to the generated CI pipeline, and only generate mode emits one.",
896
- );
897
- }
898
-
899
- return result;
900
- }
901
-
902
132
  /**
903
133
  * Print help message. `groups` — lexicon-contributed command groups
904
134
  * (chant #1078), best-effort loaded from the current project; composed in
@@ -2271,47 +1501,6 @@ async function main(): Promise<void> {
2271
1501
  await flushAndExit(await match.def.handler(ctx));
2272
1502
  }
2273
1503
 
2274
- /**
2275
- * Wait until a writable stream has flushed its buffer. `process.exit()` discards
2276
- * data still buffered for an async sink (a pipe or file), truncating large output
2277
- * at the ~64 KB pipe buffer — so `chant graph --format ir` piped into a consumer
2278
- * loses everything past 64 KB and its JSON won't parse. A TTY writes
2279
- * synchronously (`writableLength` stays 0), so this is a no-op there. Resolves on
2280
- * `error`/`close` too, so a reader that closes early (EPIPE) can't hang exit.
2281
- * Exported for testing.
2282
- */
2283
- export function waitForStreamDrain(stream: NodeJS.WriteStream): Promise<void> {
2284
- return new Promise((resolve) => {
2285
- const tick = (): void => {
2286
- if (stream.writableLength === 0 || stream.writableEnded || stream.destroyed) {
2287
- cleanup();
2288
- resolve();
2289
- return;
2290
- }
2291
- stream.once("drain", tick);
2292
- };
2293
- const stop = (): void => {
2294
- cleanup();
2295
- resolve();
2296
- };
2297
- const cleanup = (): void => {
2298
- stream.off("drain", tick);
2299
- stream.off("error", stop);
2300
- stream.off("close", stop);
2301
- };
2302
- stream.once("error", stop);
2303
- stream.once("close", stop);
2304
- tick();
2305
- });
2306
- }
2307
-
2308
- /** Flush stdout+stderr, then exit — so a large piped payload isn't truncated. */
2309
- async function flushAndExit(code: number): Promise<never> {
2310
- await waitForStreamDrain(process.stdout);
2311
- await waitForStreamDrain(process.stderr);
2312
- process.exit(code);
2313
- }
2314
-
2315
1504
  // Only run main when executed directly, not when imported. Robust to symlinked
2316
1505
  // invocation paths — see isEntryPoint (a raw string compare silently no-ops the
2317
1506
  // whole CLI through the npm .bin shim / a symlinked checkout).