@brainbase-labs/cli 0.25.0-eng1209.8 → 0.25.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.
Files changed (3) hide show
  1. package/README.md +14 -7
  2. package/dist/index.js +195 -73
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -72,21 +72,28 @@ synchronously. Schema v1 supports:
72
72
  - `workspace_assertion`: file existence, absence, SHA-256, or content checks.
73
73
  - `sandbox_command`: a bounded argv command rooted in the workspace or hidden
74
74
  tests directory. Exit zero passes; any other exit code is a valid failed
75
- verdict. Schema v1 allows one command evaluator and runs it after all
76
- read-only assertions so evaluator mutations cannot change candidate verdicts.
75
+ verdict. Schema v1 allows up to 20 command evaluators and runs them after all
76
+ read-only assertions. Each command receives either the verified live workspace
77
+ for legacy read-only evaluation or its own isolated frozen workspace copy.
77
78
 
78
79
  Valid failed verdicts still produce a successful evaluation phase. Invalid
79
- specs, digest/path violations, missing environment, launch failures, timeouts,
80
- and output-budget violations fail the phase. Results are written atomically
80
+ specs, digest/path violations, missing environment, phase-budget exhaustion,
81
+ and output-budget violations fail the phase. Individual command launch failures,
82
+ timeouts, and forced terminations are recorded as errored evaluator results so
83
+ the remaining evaluators can still run. Results are written atomically
81
84
  with mode `0600`, include the raw spec digest and checksummed evidence/log
82
85
  input/output references with explicit `staging`, `workspace`, `tests`, or
83
86
  `logs` roots, redact declared secret values from command logs, require the
84
87
  result path to be `<logs_root>/result.json`, and treat a matching successful
85
88
  phase result as authoritative on replay.
86
89
 
87
- On any phase-level execution failure, MAS must tear down the task sandbox. The
88
- CLI kills the command process group and descendants it can observe, but the
89
- runtime lifecycle remains the final cleanup boundary for daemonized processes.
90
+ On any phase-level execution failure, MAS must tear down the task sandbox.
91
+ Failed and timed-out commands clean up the process group and descendants the
92
+ CLI can observe. Successful evaluator commands receive the same cleanup so one
93
+ check cannot contaminate the next. Successful hydration setup commands may
94
+ intentionally leave services running for the agent turn; the runtime lifecycle
95
+ remains their final cleanup boundary. Isolated evaluator workspace copies omit
96
+ `.git` and unsafe absolute or escaping symlinks.
90
97
 
91
98
  ## Development
92
99
 
package/dist/index.js CHANGED
@@ -36008,7 +36008,7 @@ function padStart(s, n) {
36008
36008
  // package.json
36009
36009
  var package_default = {
36010
36010
  name: "@brainbase-labs/cli",
36011
- version: "0.25.0-eng1209.8",
36011
+ version: "0.25.0",
36012
36012
  description: "Pack, share, and install agent templates across harnesses (Claude Code, Codex, ...).",
36013
36013
  type: "module",
36014
36014
  bin: {
@@ -78485,9 +78485,12 @@ var MAX_TRAJECTORY_BYTES = 100 * 1024 * 1024;
78485
78485
  var MAX_REMOTE_INPUT_BYTES = 2 * 1024 * 1024 * 1024;
78486
78486
  var MAX_ARCHIVE_EXTRACTED_BYTES = 2 * 1024 * 1024 * 1024;
78487
78487
  var MAX_ARCHIVE_SCAN_BYTES = 2 * 1024 * 1024 * 1024;
78488
+ var MAX_HYDRATE_COMMANDS = 150;
78489
+ var MAX_PHASE_RESULT_BYTES = 32 * 1024 * 1024;
78488
78490
  var MAX_SANDBOX_COMMANDS = 20;
78489
78491
  var MAX_CRITERIA_PER_EVALUATOR = 200;
78490
- var MAX_CRITERIA_RESULT_BYTES = 1024 * 1024;
78492
+ var MAX_CRITERIA_RESULT_BYTES = 24 * 1024 * 1024;
78493
+ var MAX_CRITERIA_RESULT_PAYLOAD_BYTES = 20 * 1024 * 1024;
78491
78494
  var MAX_CRITERION_EXPLANATION_LENGTH = 16384;
78492
78495
  var MAX_CRITERION_EVIDENCE_IDS = 100;
78493
78496
  var MAX_CRITERION_EVIDENCE_ID_LENGTH = 256;
@@ -78495,6 +78498,7 @@ var MAX_ALLOWED_EVIDENCE_IDS = 1e4;
78495
78498
  var COMMAND_PROCESS_MARKER_ENV = "BRAINBASE_BENCHMARK_COMMAND_MARKER";
78496
78499
  var COMMAND_PROCESS_POLL_MS = 10;
78497
78500
  var COMMAND_PROCESS_CLEANUP_MS = 500;
78501
+ var JUDGE_ERROR_PREFIX = "BRAINBASE_BENCHMARK_JUDGE_ERROR_V1:";
78498
78502
  var RESERVED_WORKSPACE_PATHS = new Set([
78499
78503
  ".brainbase",
78500
78504
  ".git",
@@ -78661,7 +78665,7 @@ var TrajectoryEvidenceSchema = EvidenceFileSchema.extend({
78661
78665
  var HydrateSpecSchema = BaseSpecSchema.extend({
78662
78666
  phase: exports_external.literal("hydrate"),
78663
78667
  materials: exports_external.array(MaterialSchema).max(1e4).default([]),
78664
- setup_commands: exports_external.array(CommandSchema).max(128).default([])
78668
+ setup_commands: exports_external.array(CommandSchema).max(MAX_HYDRATE_COMMANDS).default([])
78665
78669
  }).strict();
78666
78670
  var CandidateOutputSchema = exports_external.object({
78667
78671
  id: IdSchema,
@@ -78802,12 +78806,12 @@ var BenchmarkSpecSchema = exports_external.discriminatedUnion("phase", [
78802
78806
  { items: value.candidate_outputs, path: "candidate_outputs" }
78803
78807
  ];
78804
78808
  for (const { items, path: issuePath } of uniqueLists) {
78805
- const ids = items.map((item) => item.id);
78809
+ const ids = items.map((item) => issuePath === "candidate_outputs" ? item.id.toLowerCase() : item.id);
78806
78810
  if (new Set(ids).size !== ids.length) {
78807
78811
  context.addIssue({
78808
78812
  code: exports_external.ZodIssueCode.custom,
78809
78813
  path: [issuePath],
78810
- message: `${issuePath} ids must be unique`
78814
+ message: `${issuePath} ids must be unique${issuePath === "candidate_outputs" ? " ignoring case" : ""}`
78811
78815
  });
78812
78816
  }
78813
78817
  }
@@ -78870,6 +78874,7 @@ var BENCHMARK_CAPABILITIES = {
78870
78874
  "remote_input_references_v1",
78871
78875
  "archive_file_materials_v1",
78872
78876
  "structured_criterion_results_v1",
78877
+ "structured_judge_errors_v1",
78873
78878
  "multiple_sandbox_commands_v1",
78874
78879
  "sandbox_command_workspace_modes_v1",
78875
78880
  "candidate_outputs_v1",
@@ -78883,6 +78888,8 @@ var BENCHMARK_CAPABILITIES = {
78883
78888
  ],
78884
78889
  limits: {
78885
78890
  max_secret_bindings: 100,
78891
+ max_hydrate_commands: MAX_HYDRATE_COMMANDS,
78892
+ max_phase_result_bytes: MAX_PHASE_RESULT_BYTES,
78886
78893
  max_evaluators: 1000,
78887
78894
  max_sandbox_commands: MAX_SANDBOX_COMMANDS,
78888
78895
  max_criteria_per_evaluator: MAX_CRITERIA_PER_EVALUATOR
@@ -80052,6 +80059,18 @@ function markedProcessPids(marker) {
80052
80059
  return [];
80053
80060
  }
80054
80061
  function processExists(pid) {
80062
+ if (process.platform === "linux") {
80063
+ try {
80064
+ const stat = fs81.readFileSync(path88.join("/proc", String(pid), "stat"), "utf8");
80065
+ const commandEnd = stat.lastIndexOf(")");
80066
+ const state = commandEnd >= 0 ? stat.slice(commandEnd + 2, commandEnd + 3) : "";
80067
+ if (state === "Z" || state === "X")
80068
+ return false;
80069
+ } catch (error2) {
80070
+ if (error2.code === "ENOENT")
80071
+ return false;
80072
+ }
80073
+ }
80055
80074
  try {
80056
80075
  process.kill(pid, 0);
80057
80076
  return true;
@@ -80083,7 +80102,7 @@ async function terminateCommandProcesses(child, marker, observedDescendants) {
80083
80102
  await new Promise((resolve) => setTimeout(resolve, COMMAND_PROCESS_POLL_MS));
80084
80103
  }
80085
80104
  }
80086
- async function runCommand(command, root, spec, context, additions = {}) {
80105
+ async function runCommand(command, root, spec, context, options = {}) {
80087
80106
  const cwdRel = normalizedRootRelative(command.cwd);
80088
80107
  assertNoSymlinkTraversal(root, cwdRel);
80089
80108
  const cwd2 = path88.resolve(root, cwdRel);
@@ -80107,7 +80126,7 @@ async function runCommand(command, root, spec, context, additions = {}) {
80107
80126
  const child = spawn4(command.argv[0], command.argv.slice(1), {
80108
80127
  cwd: cwd2,
80109
80128
  env: buildEnvironment(spec, command.secret_env, {
80110
- ...additions,
80129
+ ...options.additions,
80111
80130
  [COMMAND_PROCESS_MARKER_ENV]: commandMarker
80112
80131
  }),
80113
80132
  stdio: ["ignore", "pipe", "pipe"],
@@ -80163,13 +80182,23 @@ async function runCommand(command, root, spec, context, additions = {}) {
80163
80182
  }
80164
80183
  target.push(chunk2);
80165
80184
  };
80185
+ const finish = (code, signal) => {
80186
+ if (observer)
80187
+ clearInterval(observer);
80188
+ if (code === null) {
80189
+ reject2(new BenchmarkCommandExecutionError("command_terminated", `command was terminated by ${signal ?? "an unknown signal"}: ${command.id}`, sanitizedOutput(stdout), sanitizedOutput(stderr), Date.now() - started));
80190
+ return;
80191
+ }
80192
+ resolve({
80193
+ exitCode: code,
80194
+ stdout: sanitizedOutput(stdout),
80195
+ stderr: sanitizedOutput(stderr),
80196
+ durationMs: Date.now() - started,
80197
+ redactions: [commandMarker]
80198
+ });
80199
+ };
80166
80200
  child.stdout?.on("data", (chunk2) => capture(stdout, chunk2));
80167
80201
  child.stderr?.on("data", (chunk2) => capture(stderr, chunk2));
80168
- child.once("exit", () => {
80169
- if (timer)
80170
- clearTimeout(timer);
80171
- cleanupProcesses();
80172
- });
80173
80202
  child.on("error", (error2) => {
80174
80203
  fail(new BenchmarkCommandExecutionError("command_start_failed", `failed to start ${command.id}: ${error2.message}`, Buffer.concat(stdout), Buffer.concat(stderr), Date.now() - started));
80175
80204
  });
@@ -80180,24 +80209,21 @@ async function runCommand(command, root, spec, context, additions = {}) {
80180
80209
  }
80181
80210
  fail(new BenchmarkCommandExecutionError("command_timeout", `command timed out: ${command.id}`, Buffer.concat(stdout), Buffer.concat(stderr), Date.now() - started));
80182
80211
  }, timeoutMs2);
80183
- child.on("close", (code, signal) => {
80212
+ child.on("exit", (code, signal) => {
80184
80213
  if (settled)
80185
80214
  return;
80186
80215
  settled = true;
80187
80216
  clearTimeout(timer);
80188
- cleanupProcesses().then(() => {
80189
- if (code === null) {
80190
- reject2(new BenchmarkCommandExecutionError("command_terminated", `command was terminated by ${signal ?? "an unknown signal"}: ${command.id}`, sanitizedOutput(stdout), sanitizedOutput(stderr), Date.now() - started));
80191
- return;
80192
- }
80193
- resolve({
80194
- exitCode: code,
80195
- stdout: sanitizedOutput(stdout),
80196
- stderr: sanitizedOutput(stderr),
80197
- durationMs: Date.now() - started,
80198
- redactions: [commandMarker]
80199
- });
80200
- }, reject2);
80217
+ if (options.descendantCleanup === "always" || code === null || code !== 0) {
80218
+ cleanupProcesses().then(() => finish(code, signal), reject2);
80219
+ } else {
80220
+ if (observer)
80221
+ clearInterval(observer);
80222
+ child.unref();
80223
+ child.stdout?.unref?.();
80224
+ child.stderr?.unref?.();
80225
+ finish(code, signal);
80226
+ }
80201
80227
  });
80202
80228
  });
80203
80229
  }
@@ -80275,7 +80301,9 @@ async function executeHydrate(spec, context) {
80275
80301
  assertBudget(context);
80276
80302
  let result2;
80277
80303
  try {
80278
- result2 = await runCommand(command, spec.workspace_root, spec, context);
80304
+ result2 = await runCommand(command, spec.workspace_root, spec, context, {
80305
+ descendantCleanup: "failure_only"
80306
+ });
80279
80307
  } catch (error2) {
80280
80308
  context.steps.push({
80281
80309
  id: command.id,
@@ -80463,6 +80491,8 @@ function manifestDirectories(manifest, context) {
80463
80491
  const directories = new Set;
80464
80492
  for (const entry of manifest) {
80465
80493
  assertBudget(context);
80494
+ if (entry.kind === "symlink")
80495
+ continue;
80466
80496
  let current = path88.posix.dirname(entry.path);
80467
80497
  while (current !== ".") {
80468
80498
  assertBudget(context);
@@ -80480,9 +80510,8 @@ function candidateOutputFiles(output, manifest, context) {
80480
80510
  assertBudget(context);
80481
80511
  if (!matcher.test(entry.path))
80482
80512
  continue;
80483
- if (entry.kind === "symlink") {
80484
- throw new BenchmarkPhaseError("unsafe_path", `candidate output ${output.id} matches a symlink`);
80485
- }
80513
+ if (entry.kind === "symlink")
80514
+ continue;
80486
80515
  matched.push(entry);
80487
80516
  }
80488
80517
  return {
@@ -80490,12 +80519,6 @@ function candidateOutputFiles(output, manifest, context) {
80490
80519
  files: matched
80491
80520
  };
80492
80521
  }
80493
- for (const entry of manifest) {
80494
- assertBudget(context);
80495
- if (entry.kind === "symlink" && matcher.test(entry.path)) {
80496
- throw new BenchmarkPhaseError("unsafe_path", `candidate output ${output.id} matches a symlink`);
80497
- }
80498
- }
80499
80522
  const directories = [];
80500
80523
  for (const directory of manifestDirectories(manifest, context)) {
80501
80524
  assertBudget(context);
@@ -80510,9 +80533,8 @@ function candidateOutputFiles(output, manifest, context) {
80510
80533
  assertBudget(context);
80511
80534
  if (!entry.path.startsWith(prefix))
80512
80535
  continue;
80513
- if (entry.kind === "symlink") {
80514
- throw new BenchmarkPhaseError("unsafe_path", `candidate output ${output.id} contains a symlink`);
80515
- }
80536
+ if (entry.kind === "symlink")
80537
+ continue;
80516
80538
  selected.set(entry.path, entry);
80517
80539
  }
80518
80540
  }
@@ -80558,7 +80580,7 @@ async function copyCandidateOutput(output, manifest, spec, context) {
80558
80580
  }
80559
80581
  return copied;
80560
80582
  }
80561
- async function copyFrozenWorkspace(manifest, spec, context, sourceRoot = spec.workspace_root, preserveUnsafeSymlinks = false) {
80583
+ async function copyFrozenWorkspace(manifest, spec, context, sourceRoot = spec.workspace_root, symlinkPolicy = "preserve") {
80562
80584
  const destinationRoot = fs81.mkdtempSync(path88.join(os16.tmpdir(), "brainbase-benchmark-workspace-"));
80563
80585
  fs81.chmodSync(destinationRoot, 448);
80564
80586
  context.temporaryRoots.add(destinationRoot);
@@ -80572,12 +80594,9 @@ async function copyFrozenWorkspace(manifest, spec, context, sourceRoot = spec.wo
80572
80594
  if (target === null || Buffer.byteLength(target) !== frozenFile.size || sha256(target) !== frozenFile.sha256) {
80573
80595
  throw new BenchmarkPhaseError("evidence_tampered", `workspace changed after it was frozen: ${frozenFile.path}`);
80574
80596
  }
80575
- if (!preserveUnsafeSymlinks && path88.isAbsolute(target)) {
80576
- throw new BenchmarkPhaseError("unsafe_workspace_symlink", `sandbox evaluator workspace cannot reproduce an absolute symlink: ${frozenFile.path}`);
80577
- }
80578
80597
  const resolvedTarget = path88.resolve(path88.dirname(source), target);
80579
- if (!preserveUnsafeSymlinks && !isWithin(sourceRoot, resolvedTarget)) {
80580
- throw new BenchmarkPhaseError("unsafe_workspace_symlink", `sandbox evaluator workspace symlink escapes the workspace: ${frozenFile.path}`);
80598
+ if (symlinkPolicy === "contained_relative_only" && (path88.isAbsolute(target) || !isWithin(sourceRoot, resolvedTarget))) {
80599
+ continue;
80581
80600
  }
80582
80601
  fs81.mkdirSync(path88.dirname(destination), { recursive: true, mode: 448 });
80583
80602
  fs81.symlinkSync(target, destination);
@@ -80591,11 +80610,15 @@ async function copyFrozenWorkspace(manifest, spec, context, sourceRoot = spec.wo
80591
80610
  }
80592
80611
  return destinationRoot;
80593
80612
  }
80594
- async function copyEvaluatorTests(spec, context) {
80613
+ async function copyEvaluatorTests(spec, evaluator, context) {
80595
80614
  const destinationRoot = fs81.mkdtempSync(path88.join(os16.tmpdir(), "brainbase-benchmark-tests-"));
80596
80615
  fs81.chmodSync(destinationRoot, 448);
80597
80616
  context.temporaryRoots.add(destinationRoot);
80598
- const stack = [{ source: spec.tests_root, destination: destinationRoot }];
80617
+ const sourceRoot = evaluatorTestsPath(evaluator, spec.tests_root);
80618
+ const relativeRoot = evaluator.tests_path ? normalizedRootRelative(evaluator.tests_path) : ".";
80619
+ const destinationStart = relativeRoot === "." ? destinationRoot : path88.resolve(destinationRoot, relativeRoot);
80620
+ fs81.mkdirSync(destinationStart, { recursive: true, mode: 448 });
80621
+ const stack = [{ source: sourceRoot, destination: destinationStart }];
80599
80622
  while (stack.length > 0) {
80600
80623
  assertBudget(context);
80601
80624
  const current = stack.pop();
@@ -80657,7 +80680,38 @@ var CriterionResultSchema = exports_external.object({
80657
80680
  });
80658
80681
  var CriteriaResultFileSchema = exports_external.object({
80659
80682
  criteria: exports_external.array(CriterionResultSchema).max(MAX_CRITERIA_PER_EVALUATOR)
80683
+ }).strict().superRefine((value, context) => {
80684
+ if (Buffer.byteLength(JSON.stringify(value)) > MAX_CRITERIA_RESULT_PAYLOAD_BYTES) {
80685
+ context.addIssue({
80686
+ code: exports_external.ZodIssueCode.custom,
80687
+ path: ["criteria"],
80688
+ message: "criterion result exceeds the 20 MiB aggregate payload limit"
80689
+ });
80690
+ }
80691
+ });
80692
+ var JudgeErrorSchema = exports_external.object({
80693
+ code: exports_external.enum([
80694
+ "judge_provider_quota_exhausted",
80695
+ "judge_rate_limited",
80696
+ "judge_provider_authentication_failed",
80697
+ "judge_provider_unavailable",
80698
+ "judge_request_failed"
80699
+ ]),
80700
+ message: exports_external.string().min(1).max(500)
80660
80701
  }).strict();
80702
+ function structuredJudgeError(stderr) {
80703
+ for (const line of stderr.toString("utf8").split(/\r?\n/).reverse()) {
80704
+ const marker = line.indexOf(JUDGE_ERROR_PREFIX);
80705
+ if (marker < 0)
80706
+ continue;
80707
+ try {
80708
+ const parsed = JudgeErrorSchema.safeParse(JSON.parse(line.slice(marker + JUDGE_ERROR_PREFIX.length)));
80709
+ if (parsed.success)
80710
+ return parsed.data;
80711
+ } catch {}
80712
+ }
80713
+ return null;
80714
+ }
80661
80715
  function readCriterionResults(resultPath, criterionKeys, allowedEvidenceIds, spec, additionalSecrets = []) {
80662
80716
  let opened;
80663
80717
  try {
@@ -80670,7 +80724,7 @@ function readCriterionResults(resultPath, criterionKeys, allowedEvidenceIds, spe
80670
80724
  }
80671
80725
  try {
80672
80726
  if (opened.stat.size > MAX_CRITERIA_RESULT_BYTES) {
80673
- throw new BenchmarkPhaseError("invalid_criterion_result", "criterion result exceeds the 1 MiB limit");
80727
+ throw new BenchmarkPhaseError("invalid_criterion_result", "criterion result exceeds the 24 MiB file limit");
80674
80728
  }
80675
80729
  let raw;
80676
80730
  try {
@@ -80766,9 +80820,12 @@ async function evaluateOne(evaluator, spec, manifest, frozenWorkspaceRoot, final
80766
80820
  cwd: ".",
80767
80821
  secret_env: []
80768
80822
  }, spec.tests_root, spec, context, {
80769
- BB_REGEX_INPUT: frozenEvidence.finalOutputPath,
80770
- BB_REGEX_PATTERN_B64: Buffer.from(assertion.pattern).toString("base64"),
80771
- BB_REGEX_FLAGS: assertion.flags
80823
+ additions: {
80824
+ BB_REGEX_INPUT: frozenEvidence.finalOutputPath,
80825
+ BB_REGEX_PATTERN_B64: Buffer.from(assertion.pattern).toString("base64"),
80826
+ BB_REGEX_FLAGS: assertion.flags
80827
+ },
80828
+ descendantCleanup: "always"
80772
80829
  });
80773
80830
  if (regexResult.exitCode === 2) {
80774
80831
  throw new BenchmarkPhaseError("invalid_evaluator", `invalid regex in evaluator: ${evaluator.id}`);
@@ -80867,12 +80924,12 @@ async function evaluateOne(evaluator, spec, manifest, frozenWorkspaceRoot, final
80867
80924
  let privateResultRoot;
80868
80925
  const usesLiveWorkspace = evaluator.workspace_mode === "read_only";
80869
80926
  try {
80870
- const evaluatorWorkspace = usesLiveWorkspace ? spec.workspace_root : await copyFrozenWorkspace(manifest, spec, context, frozenWorkspaceRoot);
80927
+ const evaluatorWorkspace = usesLiveWorkspace ? spec.workspace_root : await copyFrozenWorkspace(manifest, spec, context, frozenWorkspaceRoot, "contained_relative_only");
80871
80928
  if (!usesLiveWorkspace)
80872
80929
  isolatedWorkspace = evaluatorWorkspace;
80873
80930
  else
80874
80931
  await verifyWorkspaceManifestUnchanged(manifest, spec, context);
80875
- isolatedTestsRoot = await copyEvaluatorTests(spec, context);
80932
+ isolatedTestsRoot = await copyEvaluatorTests(spec, evaluator, context);
80876
80933
  const testsPath = evaluatorTestsPath(evaluator, isolatedTestsRoot);
80877
80934
  const commandRoot = evaluator.root === "workspace" ? evaluatorWorkspace : isolatedTestsRoot;
80878
80935
  const command = { id: evaluator.id, ...evaluator.command };
@@ -80891,14 +80948,23 @@ async function evaluateOne(evaluator, spec, manifest, frozenWorkspaceRoot, final
80891
80948
  criterionResultPath = path88.join(privateResultRoot, "result.json");
80892
80949
  environment.BRAINBASE_BENCHMARK_CRITERIA_RESULT = criterionResultPath;
80893
80950
  }
80894
- const result2 = await runCommand(command, commandRoot, spec, context, environment);
80951
+ const result2 = await runCommand(command, commandRoot, spec, context, {
80952
+ additions: environment,
80953
+ descendantCleanup: "always"
80954
+ });
80895
80955
  const stdout = await writeLog(spec.logs_root, `${evaluator.id}.stdout.log`, result2.stdout, spec);
80896
80956
  const stderr = await writeLog(spec.logs_root, `${evaluator.id}.stderr.log`, result2.stderr, spec);
80897
80957
  let criterionResults;
80898
80958
  try {
80899
80959
  criterionResults = evaluator.criterion_keys && criterionResultPath ? readCriterionResults(criterionResultPath, evaluator.criterion_keys, evaluator.allowed_evidence_ids ?? [], spec, result2.redactions) : undefined;
80900
80960
  } catch (error2) {
80901
- const normalized = stableError(error2);
80961
+ let normalized = stableError(error2);
80962
+ if (normalized?.code === "missing_criterion_result" && result2.exitCode !== 0) {
80963
+ normalized = structuredJudgeError(result2.stderr) ?? {
80964
+ code: "command_terminated",
80965
+ message: "sandbox evaluator terminated before writing its criterion result"
80966
+ };
80967
+ }
80902
80968
  return {
80903
80969
  ...base2,
80904
80970
  status: "errored",
@@ -81034,7 +81100,7 @@ async function executeEvaluate(spec, context) {
81034
81100
  assertEvaluateOutputBudget(spec, context);
81035
81101
  }
81036
81102
  await verifyRecordsUnchanged(manifest, spec);
81037
- const frozenWorkspaceRoot = await copyFrozenWorkspace(manifest, spec, context, spec.workspace_root, true);
81103
+ const frozenWorkspaceRoot = await copyFrozenWorkspace(manifest, spec, context, spec.workspace_root);
81038
81104
  for (const reference of spec.references) {
81039
81105
  const referenceOutputs = await copyMaterial(reference, spec.staging_root, spec.tests_root, "tests", false, context);
81040
81106
  outputs.push(...referenceOutputs);
@@ -81134,6 +81200,67 @@ function stableError(error2) {
81134
81200
  message: error2 instanceof Error ? error2.message : "unknown benchmark phase failure"
81135
81201
  };
81136
81202
  }
81203
+ function compactOversizedResult(result2) {
81204
+ const actualBytes = Buffer.byteLength(`${JSON.stringify(result2, null, 2)}
81205
+ `);
81206
+ if (actualBytes <= MAX_PHASE_RESULT_BYTES)
81207
+ return result2;
81208
+ return {
81209
+ schema_version: result2.schema_version,
81210
+ cli_version: result2.cli_version,
81211
+ phase: result2.phase,
81212
+ attempt_id: result2.attempt_id,
81213
+ phase_id: result2.phase_id,
81214
+ spec_digest: result2.spec_digest,
81215
+ status: "failed",
81216
+ started_at: result2.started_at,
81217
+ completed_at: result2.completed_at,
81218
+ duration_ms: result2.duration_ms,
81219
+ steps: [],
81220
+ inputs: [],
81221
+ outputs: [],
81222
+ error: {
81223
+ code: "result_too_large",
81224
+ message: "benchmark phase result exceeds the 32 MiB contract limit",
81225
+ details: {
81226
+ actual_bytes: actualBytes,
81227
+ max_bytes: MAX_PHASE_RESULT_BYTES,
81228
+ step_count: result2.steps.length,
81229
+ input_count: result2.inputs.length,
81230
+ output_count: result2.outputs.length,
81231
+ evaluator_count: result2.evaluators?.length ?? 0
81232
+ }
81233
+ }
81234
+ };
81235
+ }
81236
+ function readCachedPhaseResult(resultPath) {
81237
+ let opened;
81238
+ try {
81239
+ opened = openRegularFileNoFollow(resultPath, "cached benchmark phase result");
81240
+ } catch {
81241
+ return;
81242
+ }
81243
+ try {
81244
+ if (opened.stat.size > MAX_PHASE_RESULT_BYTES)
81245
+ return;
81246
+ const chunks = [];
81247
+ let offset = 0;
81248
+ while (offset <= MAX_PHASE_RESULT_BYTES) {
81249
+ const buffer = Buffer.alloc(Math.min(64 * 1024, MAX_PHASE_RESULT_BYTES + 1 - offset));
81250
+ const bytesRead = fs81.readSync(opened.fd, buffer, 0, buffer.length, offset);
81251
+ if (bytesRead === 0) {
81252
+ return JSON.parse(Buffer.concat(chunks, offset).toString("utf8"));
81253
+ }
81254
+ chunks.push(buffer.subarray(0, bytesRead));
81255
+ offset += bytesRead;
81256
+ }
81257
+ return;
81258
+ } catch {
81259
+ return;
81260
+ } finally {
81261
+ fs81.closeSync(opened.fd);
81262
+ }
81263
+ }
81137
81264
  function rawIdentity(value) {
81138
81265
  if (!value || typeof value !== "object") {
81139
81266
  return { phase: "unknown", attemptId: null, phaseId: null };
@@ -81206,13 +81333,9 @@ function prepareBenchmarkPhaseInvocation(specPathInput, resultPathInput, expecte
81206
81333
  timeout_ms: spec.budget.timeout_ms
81207
81334
  };
81208
81335
  let cachedResult;
81209
- if (fs81.existsSync(resultPath)) {
81210
- try {
81211
- const cached2 = JSON.parse(fs81.readFileSync(resultPath, "utf8"));
81212
- if (cached2.schema_version === SCHEMA_VERSION && cached2.phase === spec.phase && cached2.attempt_id === spec.attempt_id && cached2.phase_id === spec.phase_id && cached2.spec_digest === digest && cached2.status === "succeeded") {
81213
- cachedResult = cached2;
81214
- }
81215
- } catch {}
81336
+ const cached2 = readCachedPhaseResult(resultPath);
81337
+ if (cached2?.schema_version === SCHEMA_VERSION && cached2.phase === spec.phase && cached2.attempt_id === spec.attempt_id && cached2.phase_id === spec.phase_id && cached2.spec_digest === digest && cached2.status === "succeeded") {
81338
+ cachedResult = cached2;
81216
81339
  }
81217
81340
  if (cachedResult) {
81218
81341
  if (spec.phase === "hydrate") {
@@ -81229,7 +81352,7 @@ function prepareBenchmarkPhaseInvocation(specPathInput, resultPathInput, expecte
81229
81352
  } catch (error2) {
81230
81353
  return {
81231
81354
  ok: false,
81232
- result: {
81355
+ result: compactOversizedResult({
81233
81356
  schema_version: SCHEMA_VERSION,
81234
81357
  cli_version: VERSION,
81235
81358
  phase: identity2.phase === "unknown" ? expectedPhase : identity2.phase,
@@ -81244,7 +81367,7 @@ function prepareBenchmarkPhaseInvocation(specPathInput, resultPathInput, expecte
81244
81367
  inputs: [],
81245
81368
  outputs: [],
81246
81369
  error: stableError(error2)
81247
- }
81370
+ })
81248
81371
  };
81249
81372
  }
81250
81373
  }
@@ -81324,13 +81447,9 @@ async function runBenchmarkPhase(specPathInput, resultPathInput, expectedPhase,
81324
81447
  }
81325
81448
  resultPathValidated = true;
81326
81449
  let cachedResult;
81327
- if (fs81.existsSync(resultPath)) {
81328
- try {
81329
- const cached2 = JSON.parse(fs81.readFileSync(resultPath, "utf8"));
81330
- if (cached2.schema_version === SCHEMA_VERSION && cached2.phase === spec.phase && cached2.attempt_id === spec.attempt_id && cached2.phase_id === spec.phase_id && cached2.spec_digest === digest && cached2.status === "succeeded") {
81331
- cachedResult = cached2;
81332
- }
81333
- } catch {}
81450
+ const cached2 = readCachedPhaseResult(resultPath);
81451
+ if (cached2?.schema_version === SCHEMA_VERSION && cached2.phase === spec.phase && cached2.attempt_id === spec.attempt_id && cached2.phase_id === spec.phase_id && cached2.spec_digest === digest && cached2.status === "succeeded") {
81452
+ cachedResult = cached2;
81334
81453
  }
81335
81454
  if (cachedResult) {
81336
81455
  if (spec.phase === "hydrate") {
@@ -81373,7 +81492,7 @@ async function runBenchmarkPhase(specPathInput, resultPathInput, expectedPhase,
81373
81492
  fs81.rmSync(temporary, { recursive: true, force: true });
81374
81493
  }
81375
81494
  }
81376
- const result2 = {
81495
+ let result2 = {
81377
81496
  schema_version: SCHEMA_VERSION,
81378
81497
  cli_version: VERSION,
81379
81498
  phase: identity2.phase === "unknown" && expectedPhase ? expectedPhase : identity2.phase,
@@ -81391,6 +81510,7 @@ async function runBenchmarkPhase(specPathInput, resultPathInput, expectedPhase,
81391
81510
  ...error2 ? { error: error2 } : {}
81392
81511
  };
81393
81512
  if (!resultPathValidated || !context?.logsOwned) {
81513
+ result2 = compactOversizedResult(result2);
81394
81514
  return { exitCode: status === "succeeded" ? 0 : 1, result: result2 };
81395
81515
  }
81396
81516
  if (status === "succeeded" && validatedSpec?.phase === "evaluate") {
@@ -81404,6 +81524,8 @@ async function runBenchmarkPhase(specPathInput, resultPathInput, expectedPhase,
81404
81524
  result2.error = stableError(budgetError);
81405
81525
  }
81406
81526
  }
81527
+ result2 = compactOversizedResult(result2);
81528
+ status = result2.status;
81407
81529
  try {
81408
81530
  writeJsonAtomic(resultPath, result2);
81409
81531
  } catch (writeError) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brainbase-labs/cli",
3
- "version": "0.25.0-eng1209.8",
3
+ "version": "0.25.0",
4
4
  "description": "Pack, share, and install agent templates across harnesses (Claude Code, Codex, ...).",
5
5
  "type": "module",
6
6
  "bin": {