@rulvar/cli 1.61.0 → 1.62.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { r as runCli, t as processIo } from "./io-Dp8Qkr9V.js";
2
+ import { r as runCli, t as processIo } from "./io-DQqS6oAt.js";
3
3
  import { sanitizeTerminalText } from "@rulvar/core";
4
4
  import { inspect } from "node:util";
5
5
  //#region src/cli.ts
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { CreateEngineOptions, Engine, JournalStore, KeyDeriver, LeasableStore, ModelRef, RunHandle, RunMeta, RunOutcome, Usage, Workflow, WorkflowEvent, WorkflowRegistry } from "@rulvar/core";
1
+ import { CreateEngineOptions, Engine, JournalStore, KeyDeriver, LeasableStore, ModelRef, PreflightInput, RunHandle, RunMeta, RunOutcome, Usage, Workflow, WorkflowEvent, WorkflowRegistry } from "@rulvar/core";
2
2
 
3
3
  //#region src/io.d.ts
4
4
  interface CliIo {
@@ -42,12 +42,38 @@ declare function inspectCommand(argv: string[], context: CommandContext): Promis
42
42
  * assembled price table, the same numbers rulvar inspect reports.
43
43
  */
44
44
  declare function invoiceCommand(argv: string[], context: CommandContext): Promise<number>;
45
+ /**
46
+ * rulvar preflight (the experiment-review P2.2; grammar in grammar.ts):
47
+ * the effective-config linter and dry-run estimator. Loads the SAME
48
+ * config, module, and run-profile merge `rulvar run` would assemble,
49
+ * but constructs no engine, opens no store, and dispatches nothing:
50
+ * the report is computed by preflightEstimate over options alone, so
51
+ * the command cannot pay for a single provider token by construction.
52
+ * The declared spawn wave comes from the `preflight` export of the
53
+ * config or workflow module (module wins), and --spawns JSON overrides
54
+ * it from the command line. --json prints the machine-readable report.
55
+ * Exit 1 when any finding has severity 'error' (the linter contract:
56
+ * green preflight means the run can at least start), 0 otherwise.
57
+ */
58
+ declare function preflightCommand(argv: string[], context: CommandContext): Promise<number>;
45
59
  //#endregion
46
60
  //#region src/config.d.ts
61
+ /**
62
+ * The preflight declaration a config or workflow module may export
63
+ * (the experiment-review P2.2): the declared spawn wave, the
64
+ * orchestrator spec, and the quota rule set behind the configured
65
+ * limiter, exactly the PreflightInput slices the estimator cannot
66
+ * derive from engineOptions alone. `rulvar preflight` merges the
67
+ * workflow module's declaration over the config file's, and --spawns
68
+ * overrides the spawn wave from the command line.
69
+ */
70
+ type PreflightDeclaration = Pick<PreflightInput, "spawns" | "orchestrator" | "quotaRules">;
47
71
  /** The shape both the config module and a workflow module may export. */
48
72
  interface CliConfig {
49
73
  engineOptions?: Partial<CreateEngineOptions>;
50
74
  workflows?: WorkflowRegistry;
75
+ /** rulvar preflight declaration (P2.2). */
76
+ preflight?: PreflightDeclaration;
51
77
  /** rulvar kb sweep configuration (M11-T05). */
52
78
  kbSweep?: KbSweepCliConfig;
53
79
  }
@@ -118,6 +144,7 @@ interface LoadedWorkflowModule {
118
144
  workflow?: Workflow<never, unknown>;
119
145
  engineOptions?: Partial<CreateEngineOptions>;
120
146
  workflows?: WorkflowRegistry;
147
+ preflight?: PreflightDeclaration;
121
148
  }
122
149
  /** Imports a workflow module given on the command line. */
123
150
  declare function loadWorkflowModule(file: string, cwd: string): Promise<LoadedWorkflowModule>;
@@ -386,4 +413,4 @@ declare function toOtel(run: {
386
413
  result: Promise<RunOutcome<unknown>>;
387
414
  }, tracer: TracerLike, options?: ToOtelOptions): Promise<number>;
388
415
  //#endregion
389
- export { type AssembledCli, type CliConfig, type CliIo, type CommandContext, type CreateServerOptions, type CreateWorkerOptions, DEFAULT_MAX_PENDING_EVENTS_PER_CLIENT, DEFAULT_STORE_DIR, DEFAULT_WORKER_TTL_MS, HELP, type KbSweepCliConfig, type LoadedWorkflowModule, type OtelContextApi, type RulvarServer, type SpanLike, type ToOtelOptions, type TracerLike, type Worker, assembleEngine, attachProgress, createServer, createWorker, driveRun, inspectCommand, invoiceCommand, loadCliConfig, loadWorkflowModule, looksLikeFile, processIo, renderEventLine, reportOutcome, resumeCommand, runCli, runCommand, runsLsCommand, strictExitCode, toOtel };
416
+ export { type AssembledCli, type CliConfig, type CliIo, type CommandContext, type CreateServerOptions, type CreateWorkerOptions, DEFAULT_MAX_PENDING_EVENTS_PER_CLIENT, DEFAULT_STORE_DIR, DEFAULT_WORKER_TTL_MS, HELP, type KbSweepCliConfig, type LoadedWorkflowModule, type OtelContextApi, type PreflightDeclaration, type RulvarServer, type SpanLike, type ToOtelOptions, type TracerLike, type Worker, assembleEngine, attachProgress, createServer, createWorker, driveRun, inspectCommand, invoiceCommand, loadCliConfig, loadWorkflowModule, looksLikeFile, preflightCommand, processIo, renderEventLine, reportOutcome, resumeCommand, runCli, runCommand, runsLsCommand, strictExitCode, toOtel };
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { _ as loadWorkflowModule, a as invoiceCommand, c as runsLsCommand, d as strictExitCode, f as attachProgress, g as loadCliConfig, h as assembleEngine, i as inspectCommand, l as driveRun, m as DEFAULT_STORE_DIR, n as HELP, o as resumeCommand, p as renderEventLine, r as runCli, s as runCommand, t as processIo, u as reportOutcome, v as looksLikeFile } from "./io-Dp8Qkr9V.js";
1
+ import { _ as loadCliConfig, a as invoiceCommand, c as runCommand, d as reportOutcome, f as strictExitCode, g as assembleEngine, h as DEFAULT_STORE_DIR, i as inspectCommand, l as runsLsCommand, m as renderEventLine, n as HELP, o as preflightCommand, p as attachProgress, r as runCli, s as resumeCommand, t as processIo, u as driveRun, v as loadWorkflowModule, y as looksLikeFile } from "./io-DQqS6oAt.js";
2
2
  import { ConfigError, InvalidResolutionError, JournalCompatibilityError, LeaseHeldError, Replayer, RulvarError, buildDeriverRegistry, compileSecretMasker, costReportFromJournal, maskSecrets, normalizeEntry, readRunMeta, scanJournalCompatibility, validateSchemaSpec } from "@rulvar/core";
3
3
  //#region src/server.ts
4
4
  /**
@@ -961,4 +961,4 @@ async function toOtel(run, tracer, options = {}) {
961
961
  return created;
962
962
  }
963
963
  //#endregion
964
- export { DEFAULT_MAX_PENDING_EVENTS_PER_CLIENT, DEFAULT_STORE_DIR, DEFAULT_WORKER_TTL_MS, HELP, assembleEngine, attachProgress, createServer, createWorker, driveRun, inspectCommand, invoiceCommand, loadCliConfig, loadWorkflowModule, looksLikeFile, processIo, renderEventLine, reportOutcome, resumeCommand, runCli, runCommand, runsLsCommand, strictExitCode, toOtel };
964
+ export { DEFAULT_MAX_PENDING_EVENTS_PER_CLIENT, DEFAULT_STORE_DIR, DEFAULT_WORKER_TTL_MS, HELP, assembleEngine, attachProgress, createServer, createWorker, driveRun, inspectCommand, invoiceCommand, loadCliConfig, loadWorkflowModule, looksLikeFile, preflightCommand, processIo, renderEventLine, reportOutcome, resumeCommand, runCli, runCommand, runsLsCommand, strictExitCode, toOtel };
@@ -1,4 +1,4 @@
1
- import { ConfigError, FileModelKnowledgeStore, INBOX_PROPOSAL_TTL_DAYS, JsonlFileStore, LeaseHeldError, auditRuns, claimExpired, claimExpiry, compilePermissionPreset, costReportFromJournal, createEngine, hashRunArgs, hashRunOutput, invoiceFromJournal, lastRunSettle, parseModelRef, priceUsdOf, proposalStatement, readRunMeta, reconcileRunMeta, remeasureQueue, resolvePricing, runProfile, sanitizeTerminalText } from "@rulvar/core";
1
+ import { ConfigError, FileModelKnowledgeStore, INBOX_PROPOSAL_TTL_DAYS, JsonlFileStore, LeaseHeldError, auditRuns, claimExpired, claimExpiry, compilePermissionPreset, costReportFromJournal, createEngine, hashRunArgs, hashRunOutput, invoiceFromJournal, lastRunSettle, parseModelRef, preflightEstimate, priceUsdOf, proposalStatement, readRunMeta, reconcileRunMeta, remeasureQueue, resolvePricing, runProfile, sanitizeTerminalText } from "@rulvar/core";
2
2
  import { join, resolve } from "node:path";
3
3
  import { existsSync, statSync } from "node:fs";
4
4
  import { pathToFileURL } from "node:url";
@@ -43,7 +43,8 @@ async function loadCliConfig(cwd) {
43
43
  return {
44
44
  ...config ?? {},
45
45
  ...mod.engineOptions === void 0 ? {} : { engineOptions: mod.engineOptions },
46
- ...mod.workflows === void 0 ? {} : { workflows: mod.workflows }
46
+ ...mod.workflows === void 0 ? {} : { workflows: mod.workflows },
47
+ ...mod.preflight === void 0 ? {} : { preflight: mod.preflight }
47
48
  };
48
49
  }
49
50
  return {};
@@ -58,6 +59,7 @@ async function loadWorkflowModule(file, cwd) {
58
59
  if (candidate !== void 0 && isWorkflowValue(candidate)) loaded.workflow = candidate;
59
60
  if (mod.engineOptions !== void 0) loaded.engineOptions = mod.engineOptions;
60
61
  if (mod.workflows !== void 0) loaded.workflows = mod.workflows;
62
+ if (mod.preflight !== void 0) loaded.preflight = mod.preflight;
61
63
  return loaded;
62
64
  }
63
65
  /** True when the `run` target names a file rather than a registry entry. */
@@ -495,6 +497,25 @@ const GRAMMAR = {
495
497
  { name: "dry-run" }
496
498
  ]
497
499
  },
500
+ preflight: {
501
+ command: "preflight",
502
+ positionals: ["<file|name>"],
503
+ flags: [
504
+ {
505
+ name: "budget-usd",
506
+ placeholder: "N"
507
+ },
508
+ {
509
+ name: "profile",
510
+ placeholder: "NAME"
511
+ },
512
+ {
513
+ name: "spawns",
514
+ placeholder: "JSON"
515
+ },
516
+ { name: "json" }
517
+ ]
518
+ },
498
519
  "kb list": {
499
520
  command: "kb list",
500
521
  positionals: [],
@@ -591,7 +612,8 @@ function helpCommandLines() {
591
612
  GRAMMAR["runs audit"],
592
613
  GRAMMAR.inspect,
593
614
  GRAMMAR.invoice,
594
- GRAMMAR.plan
615
+ GRAMMAR.plan,
616
+ GRAMMAR.preflight
595
617
  ];
596
618
  const heads = top.map((grammar) => [
597
619
  "rulvar",
@@ -1130,6 +1152,102 @@ async function invoiceCommand(argv, context) {
1130
1152
  for (const item of invoice.unpriced) context.io.out(`unpriced: ${item.model} (${item.usage.inputTokens + item.usage.outputTokens} tok)`);
1131
1153
  return 0;
1132
1154
  }
1155
+ /** Formats an optional USD number for the preflight text rows. */
1156
+ function usdOf(value) {
1157
+ return value === void 0 ? "n/a" : `$${value.toFixed(4)}`;
1158
+ }
1159
+ function renderPreflight(report, io) {
1160
+ io.out("preflight: effective limits and admission projection (zero provider dispatches)");
1161
+ const perProvider = report.concurrency.perProvider;
1162
+ io.out(`concurrency: perRun=${report.concurrency.perRun}` + (perProvider === void 0 ? "" : ` perProvider={${Object.entries(perProvider).map(([id, cap]) => `${id}:${cap}`).join(", ")}}`));
1163
+ const budget = report.budget;
1164
+ io.out(`budget: ceiling=${usdOf(budget.ceilingUsd)} flatReserve=${usdOf(budget.flatReserveUsd)} lifetimeSpawnCap=${budget.lifetimeSpawnCap} childFraction=${budget.childBudgetFraction} maxDepth=${budget.maxDepth}`);
1165
+ if (budget.orchestrator !== void 0) {
1166
+ const orch = budget.orchestrator;
1167
+ io.out(`orchestrator: effectiveCap=${usdOf(orch.effectiveCapUsd)} finalizeReserve=${usdOf(orch.finalizeReserveUsd)} over ${orch.finalizeTurns} turns (${orch.reserveCommitted ? "committed against the run root" : "not committed: no plan extension"})`);
1168
+ }
1169
+ io.out(`quota: ${report.quota.configured ? "configured" : "none"}` + (report.quota.tenant === void 0 ? "" : ` tenant=${report.quota.tenant}`) + (report.quota.rules === void 0 ? "" : ` rules=${report.quota.rules}`));
1170
+ io.out(`run limits: ${JSON.stringify(report.runLimits)}`);
1171
+ for (const spawn of report.spawns) {
1172
+ io.out(`spawn '${spawn.label}' role=${spawn.role} x${spawn.count} servedBy=${spawn.servedBy ?? "UNROUTED"}${spawn.unpriced === true ? " (unpriced)" : ""} reserve=${usdOf(spawn.admissionReserveUsd)} (${spawn.reserveSource})` + (spawn.maxOutputTokensPerTurn === void 0 ? "" : ` maxOutput=${spawn.maxOutputTokensPerTurn}`) + (spawn.turnFloorUsd === void 0 ? "" : ` turnFloor=${usdOf(spawn.turnFloorUsd)}`) + ` toolCallCeiling=${spawn.executedToolCallCeiling ?? "unlimited"}`);
1173
+ io.out(` limits: ${JSON.stringify(spawn.limits)}`);
1174
+ for (const row of spawn.toolCeilings) {
1175
+ if (row.tool === "(any)" && row.ceiling === null) continue;
1176
+ io.out(` tool ${row.tool}: ceiling=${row.ceiling ?? "unlimited"}` + (row.boundBy === void 0 ? "" : ` (${row.boundBy})`));
1177
+ }
1178
+ }
1179
+ const admission = report.admission;
1180
+ if (admission.wave.length > 0) {
1181
+ io.out(`admission: ${admission.admitted} of ${admission.wave.length} admitted` + (admission.ceilingUsd === void 0 ? "" : ` under ceiling=${usdOf(admission.ceilingUsd)}`) + (admission.reservedForFinalizationUsd === 0 ? "" : ` (finalization holds ${usdOf(admission.reservedForFinalizationUsd)})`));
1182
+ for (const row of admission.wave) io.out(` ${row.admitted ? "admit" : "DENY "} ${row.label} reserve=${usdOf(row.reserveUsd)}` + (row.deniedBy === void 0 ? "" : ` [${row.deniedBy}]`));
1183
+ }
1184
+ io.out(`exposure: maxInFlight=${report.exposure.maxInFlight}` + (report.exposure.overshootOneTurnFloorUsd === void 0 ? "" : ` overshootOneTurnFloor=${usdOf(report.exposure.overshootOneTurnFloorUsd)}`));
1185
+ for (const [provider, row] of Object.entries(report.exposure.perProvider)) io.out(` provider ${provider}: inFlight=${row.inFlight} requestsPerWave=${row.requestsPerWave} tokensPerWaveFloor=${row.tokensPerWaveFloor}`);
1186
+ io.out(`findings: ${report.findings.length}`);
1187
+ for (const finding of report.findings) io.out(` ${finding.severity} ${finding.code}: ${finding.message}` + (finding.spawn === void 0 ? "" : ` [spawn ${finding.spawn}]`));
1188
+ }
1189
+ /**
1190
+ * rulvar preflight (the experiment-review P2.2; grammar in grammar.ts):
1191
+ * the effective-config linter and dry-run estimator. Loads the SAME
1192
+ * config, module, and run-profile merge `rulvar run` would assemble,
1193
+ * but constructs no engine, opens no store, and dispatches nothing:
1194
+ * the report is computed by preflightEstimate over options alone, so
1195
+ * the command cannot pay for a single provider token by construction.
1196
+ * The declared spawn wave comes from the `preflight` export of the
1197
+ * config or workflow module (module wins), and --spawns JSON overrides
1198
+ * it from the command line. --json prints the machine-readable report.
1199
+ * Exit 1 when any finding has severity 'error' (the linter contract:
1200
+ * green preflight means the run can at least start), 0 otherwise.
1201
+ */
1202
+ async function preflightCommand(argv, context) {
1203
+ const parsed = parseCommand(GRAMMAR.preflight, argv);
1204
+ const target = parsed.positionals[0];
1205
+ const profile = parsed.values.profile;
1206
+ const json = parsed.values.json === true;
1207
+ const budgetUsd = parsed.values["budget-usd"] === void 0 ? void 0 : parseBudgetValue("budget-usd", parsed.values["budget-usd"]);
1208
+ const config = await loadCliConfig(context.cwd);
1209
+ const module = looksLikeFile(target) ? await loadWorkflowModule(target, context.cwd) : void 0;
1210
+ const workflows = {
1211
+ ...config.workflows,
1212
+ ...module?.workflows
1213
+ };
1214
+ if ((module?.workflow ?? workflows[target]) === void 0) throw new ConfigError(looksLikeFile(target) ? `${target} exports no workflow (default export or named 'workflow')` : `no workflow named '${target}' in the registry; register it in rulvar.config.mjs`);
1215
+ let engineOptions = {
1216
+ ...config.engineOptions,
1217
+ ...module?.engineOptions
1218
+ };
1219
+ if (profile !== void 0) {
1220
+ const preset = runProfile(profile);
1221
+ if (preset === void 0) throw new ConfigError(`unknown run profile '${profile}'; shipped: fast, standard, deep, ultra`);
1222
+ engineOptions = applyRunProfile(preset, engineOptions);
1223
+ }
1224
+ const declaration = {
1225
+ ...config.preflight,
1226
+ ...module?.preflight
1227
+ };
1228
+ let spawns = declaration.spawns;
1229
+ const spawnsJson = parsed.values.spawns;
1230
+ if (spawnsJson !== void 0) {
1231
+ let parsedSpawns;
1232
+ try {
1233
+ parsedSpawns = JSON.parse(spawnsJson);
1234
+ } catch (error) {
1235
+ throw new ConfigError(`--spawns is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
1236
+ }
1237
+ if (!Array.isArray(parsedSpawns)) throw new ConfigError("--spawns must be a JSON array of spawn specs");
1238
+ spawns = parsedSpawns;
1239
+ }
1240
+ const report = preflightEstimate({
1241
+ engine: engineOptions,
1242
+ run: { ...budgetUsd === void 0 ? {} : { budgetUsd } },
1243
+ ...declaration.orchestrator === void 0 ? {} : { orchestrator: declaration.orchestrator },
1244
+ ...spawns === void 0 ? {} : { spawns },
1245
+ ...declaration.quotaRules === void 0 ? {} : { quotaRules: declaration.quotaRules }
1246
+ });
1247
+ if (json) context.io.out(JSON.stringify(report, null, 2));
1248
+ else renderPreflight(report, context.io);
1249
+ return report.findings.some((finding) => finding.severity === "error") ? 1 : 0;
1250
+ }
1133
1251
  /**
1134
1252
  * rulvar plan (M6-T11; grammar in grammar.ts): plans a workflow script
1135
1253
  * through @rulvar/planner (loaded dynamically: the CLI's static
@@ -1598,6 +1716,16 @@ billable provider call (failed and retried attempts included) with the
1598
1716
  provider's response id when the adapter surfaced one, plus the
1599
1717
  gross/net ledger totals (gross includes abandoned subtrees, exactly
1600
1718
  what the provider bills); --json emits the machine-readable form.
1719
+ preflight is the effective-config linter and dry-run estimator: it
1720
+ assembles the SAME options rulvar run would (config, module exports,
1721
+ --profile, --budget-usd) but constructs no engine and dispatches
1722
+ nothing, then prints the effective merged limits per declared spawn,
1723
+ the layer-1 admission projection over the declared wave (which spawns
1724
+ admit, which are denied and by what), the per-tool and weighted-unit
1725
+ bottleneck ordering, the concurrency and quota exposure floors, and
1726
+ the linter findings. The declared wave comes from the preflight export
1727
+ of the config or workflow module, or --spawns JSON; --json emits the
1728
+ machine-readable report; exit 1 when any finding is severity error.
1601
1729
  plan asks the planner model (role plan) to write a workflow script,
1602
1730
  lints and self-repairs it, then runs it in the worker sandbox; --dry-run
1603
1731
  prints the accepted script without running. Both stages are paid runs
@@ -1636,6 +1764,7 @@ async function runCli(argv, options) {
1636
1764
  case "inspect": return await inspectCommand(rest, context);
1637
1765
  case "invoice": return await invoiceCommand(rest, context);
1638
1766
  case "plan": return await planCommand(rest, context);
1767
+ case "preflight": return await preflightCommand(rest, context);
1639
1768
  case "kb": return await kbCommand(rest, context);
1640
1769
  case void 0:
1641
1770
  case "help":
@@ -1685,4 +1814,4 @@ function processIo() {
1685
1814
  };
1686
1815
  }
1687
1816
  //#endregion
1688
- export { loadWorkflowModule as _, invoiceCommand as a, runsLsCommand as c, strictExitCode as d, attachProgress as f, loadCliConfig as g, assembleEngine as h, inspectCommand as i, driveRun as l, DEFAULT_STORE_DIR as m, HELP as n, resumeCommand as o, renderEventLine as p, runCli as r, runCommand as s, processIo as t, reportOutcome as u, looksLikeFile as v };
1817
+ export { loadCliConfig as _, invoiceCommand as a, runCommand as c, reportOutcome as d, strictExitCode as f, assembleEngine as g, DEFAULT_STORE_DIR as h, inspectCommand as i, runsLsCommand as l, renderEventLine as m, HELP as n, preflightCommand as o, attachProgress as p, runCli as r, resumeCommand as s, processIo as t, driveRun as u, loadWorkflowModule as v, looksLikeFile as y };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rulvar/cli",
3
- "version": "1.61.0",
3
+ "version": "1.62.0",
4
4
  "description": "Rulvar shell: run/resume/runs/inspect/plan/kb commands, TUI progress, createServer, createWorker, OTel exporter.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -22,17 +22,17 @@
22
22
  "access": "public"
23
23
  },
24
24
  "dependencies": {
25
- "@rulvar/core": "1.61.0"
25
+ "@rulvar/core": "1.62.0"
26
26
  },
27
27
  "devDependencies": {
28
28
  "@types/node": "^22.20.0",
29
29
  "tsdown": "^0.22.3",
30
30
  "typescript": "~6.0.3",
31
- "@rulvar/testing": "1.61.0",
32
- "@rulvar/planner": "1.61.0",
33
- "@rulvar/store-sqlite": "1.61.0",
34
- "@rulvar/plan": "1.61.0",
35
- "@rulvar/evals": "1.61.0"
31
+ "@rulvar/planner": "1.62.0",
32
+ "@rulvar/testing": "1.62.0",
33
+ "@rulvar/plan": "1.62.0",
34
+ "@rulvar/store-sqlite": "1.62.0",
35
+ "@rulvar/evals": "1.62.0"
36
36
  },
37
37
  "bin": {
38
38
  "rulvar": "./dist/cli.js"