@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/CHANGELOG.md +15 -0
- package/README.md +248 -21
- package/dist/cli.js +3273 -1646
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +120 -11
- package/dist/index.js +1210 -394
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
- package/src/critic/presets/reviewer.md +7 -0
- package/src/tester/presets/tester.md +29 -0
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
|
-
|
|
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,
|
|
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
|