Files
gbrain/src/core/eval/cycle-default.ts
T
488f89e0dc v0.42.15.0 fix: decouple CLI primary output from process.stdout.isTTY (#1784) (#1806)
* feat(eval): cycle-default — single source of truth for TTY/non-TTY cycle count (#1784)

* fix(jobs): decouple 'jobs watch' format (--json) from loop (--follow); non-TTY prints one human snapshot (#1784)

* fix(reindex): human cost-refusal unless --json; spend guardrail unchanged (#1784)

* fix(eval): annotate non-interactive cycle/budget defaults; runner core TTY-agnostic (#1784)

* chore: bump version and changelog (v0.42.15.0)

Decouple CLI primary output from process.stdout.isTTY (#1784): human by
default, JSON only with --json; jobs watch non-TTY one-shots; eval banners
annotate non-interactive defaults; reindex-code refusal is human unless --json.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs: CLAUDE.md Key Files notes for v0.42.15.0 isTTY-output decoupling (#1784)

Annotate jobs.ts (jobs watch format/loop split + resolveWatchMode + the new
cycle-default.ts), reindex-code.ts (human cost-refusal), and eval-cross-modal.ts
(cycle/budget banner annotations). Regenerate llms-full.txt.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 07:09:20 -07:00

66 lines
2.8 KiB
TypeScript

/**
* v0.42.11.0 (#1784) — single source of truth for the eval cycle-count default.
*
* Several eval commands (`eval cross-modal`, `eval takes-quality run/regress`)
* and the takes-quality runner core resolved their cycle default as
* `process.stdout.isTTY ? 3 : 1`. The non-TTY value of 1 is a deliberate
* cost-conservative default (each cycle calls frontier models), but the split
* was SILENT — a subagent / pipe / cron run got 1 with nothing explaining why,
* which is the surprise issue #1784 names.
*
* The fix is NOT a new stderr notice line — those commands already print the
* resolved cycle count in their existing banner. Instead, callers ANNOTATE that
* existing banner via `cycleDefaultSuffix` when the value came from the non-TTY
* default, so the operator sees `cycles: 1 (non-interactive default; --cycles N
* for more)` instead of a bare `cycles: 1`.
*
* The runner CORE (`takes-quality-eval/runner.ts`) consumes only
* `DEFAULT_CYCLES_NONTTY` — library code stays TTY-agnostic; the CLI layer owns
* the TTY=3 upgrade + the banner annotation.
*
* Deliberately NOT shared with `resolveWorkersWithClamp` (sync-concurrency.ts):
* different domain, no engine, no per-process dedup. Sharing would be premature.
*/
/** Interactive (TTY) default: deeper eval, more model calls. */
export const DEFAULT_CYCLES_TTY = 3;
/** Non-interactive (pipe / cron / subagent) default: cost-conservative. */
export const DEFAULT_CYCLES_NONTTY = 1;
export interface CycleResolution {
/** The effective cycle count. */
cycles: number;
/**
* True ONLY when no explicit value was given AND we are non-TTY — i.e. the
* caller fell through to `DEFAULT_CYCLES_NONTTY`. This is the case worth
* annotating in the banner so the 1-vs-3 difference isn't silent.
*/
usedNonTtyDefault: boolean;
}
/**
* Resolve the cycle count from an explicit value + TTY-ness.
*
* explicit set → {explicit, false} (caller asked; no annotation)
* undefined+TTY → {3, false} (interactive default; visible live)
* undefined+!TTY→ {1, true} (cost-safe default; ANNOTATE)
*/
export function resolveCycleDefault(
explicit: number | undefined,
isTty: boolean,
): CycleResolution {
if (explicit !== undefined) return { cycles: explicit, usedNonTtyDefault: false };
if (isTty) return { cycles: DEFAULT_CYCLES_TTY, usedNonTtyDefault: false };
return { cycles: DEFAULT_CYCLES_NONTTY, usedNonTtyDefault: true };
}
/**
* Banner suffix to append to an EXISTING stderr line that already prints the
* cycle count. Empty string unless the non-TTY default was applied, so the
* common (TTY or explicit) cases get no extra text.
*/
export function cycleDefaultSuffix(r: CycleResolution): string {
return r.usedNonTtyDefault ? ' (non-interactive default; --cycles N for more)' : '';
}