@intentius/chant 0.124.15 → 0.124.17

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