@robmclarty/volley 0.11.1 → 0.12.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/index.d.ts CHANGED
@@ -1,8 +1,16 @@
1
- import { Engine, FinishReason, StreamChunk, Throughput, Tool, ToolApprovalHandler, TrajectoryLogger, UsageTotals } from "fascicle";
1
+ import { EffortLevel, Engine, FinishReason, StreamChunk, Throughput, Tool, ToolApprovalHandler, TrajectoryLogger, UsageTotals } from "fascicle";
2
2
  import { z } from "zod";
3
3
  //#region src/types.d.ts
4
4
  type Verdict = 'approved' | 'changes_requested';
5
+ /** One rung of the effort ladder, and the critic's effort: fascicle's
6
+ * per-call `effort` levels less `none`, which on `claude_cli` sets nothing and
7
+ * leaves Claude Code at its own default, possibly above `low`, so it can't
8
+ * stand on a ladder that climbs. */
9
+ type EffortRung = Exclude<EffortLevel, 'none'>;
5
10
  type CriticPreset = 'reviewer' | 'optimizer' | 'researcher';
11
+ /** `--tester`: whether a tester writes a failing test from the criteria
12
+ * before the first build. */
13
+ type TesterSetting = 'on' | 'off';
6
14
  /** Transport for the critic role. The critic is read-only + schema-constrained,
7
15
  * so it has always been able to run on a local model. `openrouter` reaches a
8
16
  * hosted model over one API key and takes the local critic's tools and
@@ -33,6 +41,10 @@ type RunStatus = 'running' | 'success' | 'budget_exhausted' | 'cost_cap_reached'
33
41
  * the run halts on the spot rather than iterating, because a check the builder
34
42
  * can rewrite is not evidence the work is done. */
35
43
  'gate_edit_blocked' |
44
+ /** The check passed right after the tester wrote its test, before any
45
+ * build: the test is vacuous or the work is already done, and either way no
46
+ * builder is paid to find out (move 1's D6, D17). */
47
+ 'tester_passed_early' |
36
48
  /** A critique's other outcome: the critic requested changes, or the check
37
49
  * failed, on the one change it reviewed. A run never ends this way. */
38
50
  'rejected' | 'interrupted' | 'error';
@@ -52,10 +64,14 @@ type VolleyConfig = {
52
64
  builder_max_tool_calls?: number;
53
65
  builder_max_output_tokens?: number;
54
66
  builder_trim_history?: number;
67
+ effort_ladder?: EffortRung[];
55
68
  allow_unsandboxed_builder?: boolean;
56
69
  critic_model?: string;
57
70
  critic_provider?: CriticProvider;
58
71
  critic_url?: string;
72
+ critic_effort?: EffortRung;
73
+ tester?: TesterSetting;
74
+ tester_model?: string;
59
75
  builder_permission_mode?: BuilderPermissionMode;
60
76
  critic?: CriticPreset | (string & {});
61
77
  max_iterations?: number;
@@ -120,6 +136,12 @@ type ResolvedConfig = {
120
136
  * every turn whole. Null by default, until a long local session is shown
121
137
  * to run faster with it on. Ignored by the `claude_cli` builder. */
122
138
  builder_trim_history: number | null;
139
+ /** The efforts the builder climbs (`--effort-ladder`): each build runs one
140
+ * rung further up than the last, and the top rung repeats. Null, the
141
+ * default, passes no effort at all, so the builder's call is today's. On
142
+ * `ollama` and `lmstudio`, whose transports drop effort, every rung is the
143
+ * same and the ladder is flat. */
144
+ effort_ladder: EffortRung[] | null;
123
145
  /** Operator opt-out permitting a non-`claude_cli` builder to run without a
124
146
  * sandbox — it gets a real host bash. Refused by default. */
125
147
  allow_unsandboxed_builder: boolean;
@@ -128,6 +150,18 @@ type ResolvedConfig = {
128
150
  /** The critic seat's own model server base URL (`--critic-url`), as given;
129
151
  * resolved like `builder_url`, and null never falls back to the builder's. */
130
152
  critic_url: string | null;
153
+ /** The critic's effort on every call (`--critic-effort`), apart from the
154
+ * builder's ladder: `high` when a ladder is set and nothing names one, since
155
+ * the critic is what makes a cheap build safe, else null, which passes no
156
+ * effort, as before there was a ladder. */
157
+ critic_effort: EffortRung | null;
158
+ /** Whether a tester runs before the first build (`--tester on`), writing a
159
+ * failing test from the criteria under the test and fixture gate paths. Off
160
+ * by default. It takes the critic's provider, URL and effort. */
161
+ tester: boolean;
162
+ /** The tester's model: `--tester-model`, else the critic's. Null with the
163
+ * tester off. */
164
+ tester_model: string | null;
131
165
  builder_permission_mode: BuilderPermissionMode;
132
166
  critic_preset: CriticPreset | 'custom';
133
167
  critic_prompt_path: string | null;
@@ -231,6 +265,9 @@ type ChangeSet = {
231
265
  };
232
266
  /** Per-phase record for iteration summaries. */
233
267
  type PhaseRecord = {
268
+ /** What the critic found wrong with the tester's test, when it found
269
+ * anything. Critic-only. Additive. */
270
+ test_findings?: string[];
234
271
  provider: string;
235
272
  model: string;
236
273
  session_id: string | null;
@@ -276,6 +313,11 @@ type PhaseRecord = {
276
313
  critic_degraded?: boolean;
277
314
  /** Why the verdict degraded, set with `critic_degraded`. Additive. */
278
315
  degrade_cause?: DegradeCause;
316
+ /** The seat asked for an effort its provider drops (ollama, lmstudio), so
317
+ * the call ran at the provider's own and a ladder there is flat. Set on a
318
+ * builder record that ran at a rung and a critic record that ran at
319
+ * `critic_effort`, when either was ignored; absent otherwise. Additive. */
320
+ effort_ignored?: true;
279
321
  /** Output tokens per second over the model's own turns (tool execution and
280
322
  * retry backoff excluded), from fascicle's per-step timing. `basis` is
281
323
  * `decode` when every turn streamed, `blended` when any round trip counted
@@ -326,7 +368,10 @@ type BuilderStepRecord = {
326
368
  output_chars: number;
327
369
  }>;
328
370
  };
329
- type HaltReason = 'cost_cap' | 'gate_edit' | null;
371
+ type HaltReason = 'cost_cap' | 'gate_edit' | 'tester_passed_early' | null;
372
+ /** Why a build ran one rung up the ladder from the build before it: that
373
+ * build's check failed, or its check passed and the critic sent it back. */
374
+ type ClimbReason = 'check_failed' | 'critic_rejected';
330
375
  /** Carry-state threaded through the fascicle loop. */
331
376
  type LoopState = {
332
377
  iteration: number;
@@ -341,14 +386,39 @@ type LoopState = {
341
386
  changes: ChangeSet | null;
342
387
  builder: PhaseRecord | null;
343
388
  critic: PhaseRecord | null;
389
+ /** This iteration's tester session, or null: the tester runs in iteration 1
390
+ * only, before its build. */
391
+ tester: PhaseRecord | null;
392
+ /** The check run right after the tester, which must fail; null in an
393
+ * iteration the tester didn't open. Apart from `check`, which is the check
394
+ * after the build. */
395
+ tester_check: CheckResult | null;
396
+ /** What the tester wrote, measured as the builder's changes are; null in an
397
+ * iteration the tester didn't open, or outside a git repository. */
398
+ tester_changes: ChangeSet | null;
399
+ /** Each file the tester wrote, by its hash right after the test phase. A
400
+ * build that leaves one at that hash didn't edit the gate there (move 1's
401
+ * D22). Kept for the whole run, and read back on resume. */
402
+ tester_hashes: Record<string, string>;
344
403
  total_usage: UsageTotals;
345
404
  total_cost_usd: number;
346
405
  builder_cost_usd: number;
347
406
  critic_cost_usd: number;
407
+ /** The tester's spend, apart from the other two seats' (D21). */
408
+ tester_cost_usd: number;
348
409
  check_duration_ms: number;
349
410
  /** How many of the run's builder sessions ended on the step limit
350
411
  * (`max_steps`) rather than calling finish. */
351
412
  builder_max_steps_hits: number;
413
+ /** Builder sessions the run has started, this iteration's included: build
414
+ * *k* runs at `effort_ladder[min(k, len) - 1]`. A resumed run reads it back
415
+ * from the last archived iteration. */
416
+ builds: number;
417
+ /** The effort this iteration's build ran at, or null with no ladder. */
418
+ rung: EffortRung | null;
419
+ /** Why this iteration's build ran a rung above the last one's, or null when
420
+ * it didn't: the first build, the top rung repeating, or no ladder. */
421
+ climbed: ClimbReason | null;
352
422
  iteration_cost_usd: number;
353
423
  halt: HaltReason;
354
424
  cost_warned: boolean;
@@ -364,6 +434,8 @@ type RunResult = {
364
434
  total_cost_usd: number;
365
435
  builder_cost_usd: number;
366
436
  critic_cost_usd: number;
437
+ /** The tester's spend; 0 with the tester off. Additive. */
438
+ tester_cost_usd: number;
367
439
  check_duration_ms: number;
368
440
  /** How many of the run's builder sessions hit the step limit before they
369
441
  * called finish: work handed to the check and critic unverified. The
@@ -415,6 +487,12 @@ type ResolveOptions = {
415
487
  * SDK and the CLI resolve `auto` and a command check the same way. */
416
488
  export declare function resolve_config(raw: VolleyConfig, options?: ResolveOptions): ResolvedConfig;
417
489
  //#endregion
490
+ //#region src/workspace_tools.d.ts
491
+ /** Which tool set a seat holds, so its prompt names the tools it has: volley's
492
+ * own (`'volley'`), or fascicle's workspace tools under `volley acp`
493
+ * (`'acp'`). */
494
+ type ToolSet = 'volley' | 'acp';
495
+ //#endregion
418
496
  //#region src/builder/tools.d.ts
419
497
  /** The raw outcome of running one bash command, before the model-facing
420
498
  * truncation + timeout marker the `bash` tool applies. `status` is the exit code
@@ -438,12 +516,6 @@ type BashExecutor = (command: string, options: {
438
516
  max_capture_bytes: number;
439
517
  }) => BashOutcome;
440
518
  //#endregion
441
- //#region src/workspace_tools.d.ts
442
- /** Which tool set a seat holds, so its prompt names the tools it has: volley's
443
- * own (`'volley'`), or fascicle's workspace tools under `volley acp`
444
- * (`'acp'`). */
445
- type ToolSet = 'volley' | 'acp';
446
- //#endregion
447
519
  //#region src/builder.d.ts
448
520
  type BuilderDeps = {
449
521
  engine: Engine;
@@ -480,6 +552,7 @@ export declare const verdict_schema: z.ZodObject<{
480
552
  }>;
481
553
  feedback: z.ZodPipe<z.ZodNullable<z.ZodString>, z.ZodTransform<string, string | null>>;
482
554
  unmet_criteria: z.ZodPipe<z.ZodNullable<z.ZodArray<z.ZodString>>, z.ZodTransform<string[], string[] | null>>;
555
+ test_findings: z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodString>>>;
483
556
  }, z.core.$strip>;
484
557
  type VerdictOutput = z.infer<typeof verdict_schema>;
485
558
  type CriticDeps = {
@@ -497,15 +570,22 @@ type CriticDeps = {
497
570
  };
498
571
  //#endregion
499
572
  //#region src/render/format.d.ts
500
- type Role = 'builder' | 'critic' | 'check';
573
+ type Role = 'builder' | 'critic' | 'tester' | 'check';
501
574
  //#endregion
502
575
  //#region src/render/renderer.d.ts
503
- type RenderMode = 'default' | 'verbose' | 'quiet' | 'json';
576
+ /** `protocol` is `volley run --protocol runciter.run/1`: the quiet mode's
577
+ * human lines, with runciter's typed lines (`runciter.progress` for each
578
+ * phase, `runciter.usage` for each iteration) in place of the phase and cost
579
+ * lines. */
580
+ type RenderMode = 'default' | 'verbose' | 'quiet' | 'json' | 'protocol';
504
581
  type RendererOptions = {
505
582
  mode: RenderMode;
506
583
  show_thinking: boolean;
507
584
  color: boolean;
508
585
  max_cost_usd: number | null;
586
+ /** The run's iteration cap, the `total` of each `runciter.progress` line in
587
+ * protocol mode. */
588
+ max_iterations?: number;
509
589
  /** Where the run's trajectory is written, named when verbose output is cut
510
590
  * short (default: "the run's trajectory.jsonl"). */
511
591
  trajectory_path?: string;
@@ -514,8 +594,12 @@ type RendererOptions = {
514
594
  type Renderer = {
515
595
  builder_chunk: (chunk: StreamChunk) => void;
516
596
  critic_chunk: (chunk: StreamChunk) => void;
597
+ tester_chunk: (chunk: StreamChunk) => void;
517
598
  phase_start: (iteration: number, phase: Role) => void;
518
599
  phase_end: (iteration: number, phase: Role, ok: boolean, detail?: string) => void;
600
+ /** The effort this iteration's build runs at, one line per build when a
601
+ * ladder is set. */
602
+ rung: (iteration: number, rung: EffortRung, climbed: ClimbReason | null) => void;
519
603
  cost_line: (iteration: number, phase: Role, phase_cost: number | null, run_total: number) => void;
520
604
  info: (message: string) => void;
521
605
  warn: (message: string) => void;
@@ -524,6 +608,23 @@ type Renderer = {
524
608
  };
525
609
  export declare function create_renderer(options: RendererOptions): Renderer;
526
610
  //#endregion
611
+ //#region src/tester/run.d.ts
612
+ type TesterDeps = {
613
+ engine: Engine;
614
+ config: ResolvedConfig;
615
+ on_chunk: (chunk: StreamChunk) => void;
616
+ /** Tools in place of `tester_tools`, as the critic takes them. A
617
+ * `claude_cli` tester uses the CLI's own and never takes these. */
618
+ tools?: ReadonlyArray<Tool>;
619
+ /** Which set `tools` is, so the system prompt names what the tester holds;
620
+ * absent, volley's own. */
621
+ tool_set?: ToolSet;
622
+ /** Answers each call to a tool that asks first, as under `volley acp`,
623
+ * where the tester's writes ask as the builder's do. Absent, such a call
624
+ * fails closed. */
625
+ on_tool_approval?: ToolApprovalHandler;
626
+ };
627
+ //#endregion
527
628
  //#region src/phases.d.ts
528
629
  /** How the check runs when the caller sets it, as `volley acp` does. Left
529
630
  * out, the check runs unasked with volley's environment less the seats' keys,
@@ -548,6 +649,11 @@ type BuilderSeat = Pick<BuilderDeps, 'engine' | 'tools' | 'tool_set' | 'on_tool_
548
649
  * reads and the name of their set. It takes no approval handler, since its
549
650
  * reads never ask. */
550
651
  type CriticSeat = Pick<CriticDeps, 'engine' | 'tools' | 'tool_set'>;
652
+ /** The tester seat: its engine and, when injected, the tools that replace
653
+ * volley's tester tools, the name of their set, and the handler that answers
654
+ * their approval requests. Absent, it rides the critic's engine, whose
655
+ * provider and URL it takes. */
656
+ type TesterSeat = Pick<TesterDeps, 'engine' | 'tools' | 'tool_set' | 'on_tool_approval'>;
551
657
  //#endregion
552
658
  //#region src/orchestrator.d.ts
553
659
  type OrchestratorDeps = {
@@ -563,6 +669,8 @@ type OrchestratorDeps = {
563
669
  seats?: {
564
670
  builder?: Omit<BuilderSeat, 'engine'>;
565
671
  critic?: Omit<CriticSeat, 'engine'>;
672
+ /** The tester's, which rides the critic's engine. */
673
+ tester?: Omit<TesterSeat, 'engine'>;
566
674
  };
567
675
  /** A second sink for the run's trajectory, beside the run's
568
676
  * `trajectory.jsonl`. With one, the flow runs through `run.stream`, so the
@@ -599,7 +707,8 @@ export declare function run_critique(config: ResolvedConfig, deps: CritiqueDeps)
599
707
  //#region src/loop_state.d.ts
600
708
  export declare function initial_state(): LoopState;
601
709
  /** All stopping conditions live here: acceptance, cost cap, the gate
602
- * edit refusal, and — implicitly via `max_rounds` — the iteration budget.
710
+ * edit refusal, a tester's test passing before any build, and — implicitly
711
+ * via `max_rounds` — the iteration budget.
603
712
  * Success wins when it and the cap land on the same iteration.
604
713
  *
605
714
  * A gate edit is the one condition that *beats* success rather than losing to