@robmclarty/volley 0.5.3 → 0.5.4

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 CHANGED
@@ -1,5 +1,16 @@
1
1
  # Changelog
2
2
 
3
+ ## v0.5.4 — 2026-09-25
4
+
5
+ ### Added
6
+ - **A diagram of the loop, drawn from the code.** The README's new "The flow" section shows the real composition: the branch that skips verification once the builder alone crosses the cost cap, the critic's retry-then-tool-less fallback for local models, and the guard that stops the loop. fascicle's `describe.diagram` renders it from `src/flow.ts`, and `pnpm diagram` prints it from a checkout.
7
+
8
+ ### Changed
9
+ - **The trajectory's flow structure includes the builder and the critic.** They now appear as children of `build` and `critique` in the tree a run records at its start, so the fascicle viewer draws both model calls, and the critic's retry and fallback, before the run reaches them. Live output, verdicts, and exit codes are unchanged.
10
+
11
+ ### Internal
12
+ - Built on fascicle 0.12.13. A test holds the `src/flow.ts` header diagram and the README's copy to `describe.diagram`, so neither can drift from the code.
13
+
3
14
  ## v0.5.3 — 2026-09-24
4
15
 
5
16
  ### Fixed
package/README.md CHANGED
@@ -88,6 +88,38 @@ workspace filesystem and the critic's feedback. Swap the critic to change what
88
88
  the loop optimizes for: `--critic reviewer` (default), `optimizer`,
89
89
  `researcher`, or a path to your own system prompt.
90
90
 
91
+ ### The flow
92
+
93
+ Under the hood the loop is one [fascicle](https://www.npmjs.com/package/fascicle)
94
+ composition in [`src/flow.ts`](./src/flow.ts), and fascicle's `describe.diagram`
95
+ draws it straight from the code. It shows what the sketch above leaves out: the
96
+ branch that skips verification once the builder alone crosses the cost cap, the
97
+ critic's retry-then-tool-less fallback for local models whose streams die, and
98
+ the guard that decides when the loop stops.
99
+
100
+ <!-- flow diagram: pnpm diagram -->
101
+ ```text
102
+ volley loop: one round per iteration, up to max_iterations
103
+ ├─ iteration sequence
104
+ │ ├─ build open the iteration, run the builder, measure the change
105
+ │ │ └─ builder model call: one agentic session in the build root
106
+ │ ├─ verify branch: did the build alone cross the cost cap?
107
+ │ │ ├─ then skip_verify record the check as skipped and call no critic
108
+ │ │ └─ else sequence
109
+ │ │ ├─ check the deterministic gate: checkride, a command, or none
110
+ │ │ └─ critique the read-only critic's verdict and feedback
111
+ │ │ └─ critic fallback: judge without tools if a local critic's stream keeps dying
112
+ │ │ ├─ retry one more try after a stream death, local providers only
113
+ │ │ │ └─ critic_tools model call with read-only workspace tools
114
+ │ │ └─ pipe mark the verdict degraded
115
+ │ │ └─ critic_toolless model call judging from the check output and a file list
116
+ │ └─ record archive the iteration and rewrite the run summary
117
+ └─ guard gate stop on approval over a green check, the cost cap, or a gate edit
118
+ ```
119
+
120
+ `pnpm diagram` prints the same tree from a checkout. A test holds this copy and
121
+ the one in the `src/flow.ts` header to what the code builds, so neither drifts.
122
+
91
123
  ## Install
92
124
 
93
125
  Requires Node ≥ 24, pnpm, and the [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code)
@@ -290,7 +322,7 @@ Providers and their setup:
290
322
  Install the peer for your provider in the project running volley (e.g.
291
323
  `pnpm add ai-sdk-ollama@^4`); fascicle loads it lazily only when the local
292
324
  critic actually runs. The major matters: it must satisfy fascicle's declared
293
- peer range (`^4` for fascicle 0.12.10, which tracks the AI SDK v7 line; a bare
325
+ peer range (`^4` for fascicle 0.12.13, which tracks the AI SDK v7 line; a bare
294
326
  `pnpm add ai-sdk-ollama` installs v4, which is exactly what this major wants).
295
327
  The Ollama base URL is the **server root** — `ai-sdk-ollama` adds the `/api`
296
328
  prefix itself (volley strips a trailing `/api` from `VOLLEY_OLLAMA_URL` for
@@ -485,6 +517,7 @@ pnpm test # vitest
485
517
  pnpm typecheck # tsc --noEmit
486
518
  pnpm check # checkride — volley dogfoods its own definition of done
487
519
  pnpm build # tsup → dist/
520
+ pnpm diagram # draw the flow from src/flow.ts with describe.diagram
488
521
  ```
489
522
 
490
523
  Live smoke tests against the real `claude` CLI and real checkride are opt-in:
package/dist/cli.js CHANGED
@@ -1804,6 +1804,7 @@ function make_builder_step(deps) {
1804
1804
  return model_call({
1805
1805
  engine: deps.engine,
1806
1806
  id: "builder",
1807
+ description: "model call: one agentic session in the build root",
1807
1808
  provider: config.builder_provider,
1808
1809
  model: config.builder_model,
1809
1810
  system: resolve_builder_system(config),
@@ -2045,11 +2046,16 @@ function make_critic_tools_step(deps) {
2045
2046
  return model_call2({
2046
2047
  ...critic_call_config(deps),
2047
2048
  id: "critic_tools",
2049
+ description: "model call with read-only workspace tools",
2048
2050
  ...critic_tool_options(deps.config)
2049
2051
  });
2050
2052
  }
2051
2053
  function make_critic_toolless_step(deps) {
2052
- return model_call2({ ...critic_call_config(deps), id: "critic_toolless" });
2054
+ return model_call2({
2055
+ ...critic_call_config(deps),
2056
+ id: "critic_toolless",
2057
+ description: "model call judging from the check output and a file list"
2058
+ });
2053
2059
  }
2054
2060
  function critic_retryable(config) {
2055
2061
  return (err) => is_retryable_critic_error(config, err);
@@ -2546,14 +2552,18 @@ function build_critic(deps) {
2546
2552
  const retryable = critic_retryable(deps.config);
2547
2553
  return fallback(
2548
2554
  retry(make_critic_tools_step(deps), {
2555
+ description: "one more try after a stream death, local providers only",
2549
2556
  max_attempts: MAX_CRITIC_RETRIES + 1,
2550
2557
  backoff_ms: 0,
2551
2558
  when: retryable,
2552
2559
  project: with_retries
2553
2560
  }),
2554
- pipe(make_critic_toolless_step(deps), as_degraded),
2561
+ pipe(make_critic_toolless_step(deps), as_degraded, {
2562
+ description: "mark the verdict degraded"
2563
+ }),
2555
2564
  {
2556
2565
  name: "critic",
2566
+ description: "judge without tools if a local critic's stream keeps dying",
2557
2567
  when: retryable,
2558
2568
  handoff: (prompt) => toolless_critic_prompt(deps.config, prompt),
2559
2569
  project: with_fallback_cause
@@ -2569,13 +2579,27 @@ function build_flow(deps, resume_from) {
2569
2579
  bash_executor: deps.bash_executor
2570
2580
  });
2571
2581
  const critic = build_critic({ engine: deps.engine, config, on_chunk: renderer.critic_chunk });
2572
- const build = step("build", (s, ctx) => build_phase(deps, builder, s, ctx));
2573
- const skip_verify = step("skip_verify", (s) => skip_verify_phase(deps, s));
2574
- const check = step("check", (s, ctx) => check_phase(deps, s, ctx));
2575
- const critique = step("critique", (s, ctx) => critique_phase(deps, critic, s, ctx));
2576
- const record = step("record", (s) => record_phase(deps, s));
2582
+ const build = step("build", (s, ctx) => build_phase(deps, builder, s, ctx), {
2583
+ description: "open the iteration, run the builder, measure the change",
2584
+ arm: builder
2585
+ });
2586
+ const skip_verify = step("skip_verify", (s) => skip_verify_phase(deps, s), {
2587
+ description: "record the check as skipped and call no critic"
2588
+ });
2589
+ const check = step("check", (s, ctx) => check_phase(deps, s, ctx), {
2590
+ description: "the deterministic gate: checkride, a command, or none"
2591
+ });
2592
+ const critique = step(
2593
+ "critique",
2594
+ (s, ctx) => critique_phase(deps, critic, s, ctx),
2595
+ { description: "the read-only critic's verdict and feedback", arm: critic }
2596
+ );
2597
+ const record = step("record", (s) => record_phase(deps, s), {
2598
+ description: "archive the iteration and rewrite the run summary"
2599
+ });
2577
2600
  const verify = branch({
2578
2601
  name: "verify",
2602
+ description: "did the build alone cross the cost cap?",
2579
2603
  when: (s) => cost_cap_hit(config, s),
2580
2604
  // fascicle names this arm `then`. It holds a Step object, never a function,
2581
2605
  // so nothing can mistake the config for a thenable.
@@ -2588,9 +2612,12 @@ function build_flow(deps, resume_from) {
2588
2612
  });
2589
2613
  return loop({
2590
2614
  name: "volley",
2615
+ description: "one round per iteration, up to max_iterations",
2591
2616
  init: (input) => input.resume_from ?? initial_state(),
2592
2617
  body: iteration,
2593
- guard: step("gate", (s) => gate(config, s)),
2618
+ guard: step("gate", (s) => gate(config, s), {
2619
+ description: "stop on approval over a green check, the cost cap, or a gate edit"
2620
+ }),
2594
2621
  finish: (s, { converged }) => ({ value: s, converged }),
2595
2622
  max_rounds: remaining_iterations(config, resume_from)
2596
2623
  });