@brainbase-labs/cli 0.25.0-eng1209.4 → 0.25.0-eng1209.6

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 (2) hide show
  1. package/dist/index.js +145 -24
  2. package/package.json +1 -1
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.4",
36011
+ version: "0.25.0-eng1209.6",
36012
36012
  description: "Pack, share, and install agent templates across harnesses (Claude Code, Codex, ...).",
36013
36013
  type: "module",
36014
36014
  bin: {
@@ -78882,6 +78882,19 @@ class BenchmarkPhaseError extends Error {
78882
78882
  this.name = "BenchmarkPhaseError";
78883
78883
  }
78884
78884
  }
78885
+
78886
+ class BenchmarkCommandExecutionError extends BenchmarkPhaseError {
78887
+ stdout;
78888
+ stderr;
78889
+ durationMs;
78890
+ constructor(code, message, stdout, stderr, durationMs) {
78891
+ super(code, message);
78892
+ this.stdout = stdout;
78893
+ this.stderr = stderr;
78894
+ this.durationMs = durationMs;
78895
+ this.name = "BenchmarkCommandExecutionError";
78896
+ }
78897
+ }
78885
78898
  var ZIP_EOCD_SIGNATURE = 101010256;
78886
78899
  var ZIP_CENTRAL_SIGNATURE = 33639248;
78887
78900
  var ZIP_LOCAL_SIGNATURE = 67324752;
@@ -79985,7 +79998,8 @@ async function runCommand(command, root, spec, context, additions = {}) {
79985
79998
  const remainingMs = context.deadline - Date.now();
79986
79999
  if (remainingMs <= 0)
79987
80000
  throw new BenchmarkPhaseError("phase_timeout", "phase budget expired");
79988
- const timeoutMs2 = Math.min(command.timeout_ms ?? remainingMs, remainingMs);
80001
+ const reachesPhaseDeadline = command.timeout_ms === undefined || command.timeout_ms >= remainingMs;
80002
+ const timeoutMs2 = reachesPhaseDeadline ? remainingMs : command.timeout_ms;
79989
80003
  const started = Date.now();
79990
80004
  return await new Promise((resolve, reject2) => {
79991
80005
  const child = spawn4(command.argv[0], command.argv.slice(1), {
@@ -80022,10 +80036,14 @@ async function runCommand(command, root, spec, context, additions = {}) {
80022
80036
  child.stdout?.on("data", (chunk2) => capture(stdout, chunk2));
80023
80037
  child.stderr?.on("data", (chunk2) => capture(stderr, chunk2));
80024
80038
  child.on("error", (error2) => {
80025
- fail(new BenchmarkPhaseError("command_start_failed", `failed to start ${command.id}: ${error2.message}`));
80039
+ fail(new BenchmarkCommandExecutionError("command_start_failed", `failed to start ${command.id}: ${error2.message}`, Buffer.concat(stdout), Buffer.concat(stderr), Date.now() - started));
80026
80040
  });
80027
80041
  timer = setTimeout(() => {
80028
- fail(new BenchmarkPhaseError("command_timeout", `command timed out: ${command.id}`));
80042
+ if (reachesPhaseDeadline) {
80043
+ fail(new BenchmarkPhaseError("phase_timeout", "phase budget expired"));
80044
+ return;
80045
+ }
80046
+ fail(new BenchmarkCommandExecutionError("command_timeout", `command timed out: ${command.id}`, Buffer.concat(stdout), Buffer.concat(stderr), Date.now() - started));
80029
80047
  }, timeoutMs2);
80030
80048
  child.on("close", (code, signal) => {
80031
80049
  if (settled)
@@ -80033,7 +80051,7 @@ async function runCommand(command, root, spec, context, additions = {}) {
80033
80051
  settled = true;
80034
80052
  clearTimeout(timer);
80035
80053
  if (code === null) {
80036
- reject2(new BenchmarkPhaseError("command_terminated", `command was terminated by ${signal ?? "an unknown signal"}: ${command.id}`));
80054
+ reject2(new BenchmarkCommandExecutionError("command_terminated", `command was terminated by ${signal ?? "an unknown signal"}: ${command.id}`, Buffer.concat(stdout), Buffer.concat(stderr), Date.now() - started));
80037
80055
  return;
80038
80056
  }
80039
80057
  resolve({
@@ -80234,6 +80252,42 @@ async function workspaceManifest(spec, context) {
80234
80252
  }
80235
80253
  return records.sort((a3, b4) => a3.path.localeCompare(b4.path));
80236
80254
  }
80255
+ function treeBytes(root, context) {
80256
+ let totalBytes = 0;
80257
+ const stack = [path88.resolve(root)];
80258
+ while (stack.length > 0) {
80259
+ const directory = stack.pop();
80260
+ const entries = fs81.readdirSync(directory, { withFileTypes: true });
80261
+ for (const entry of entries) {
80262
+ assertBudget(context);
80263
+ const candidate = path88.join(directory, entry.name);
80264
+ const stat = fs81.lstatSync(candidate);
80265
+ if (stat.isDirectory()) {
80266
+ stack.push(candidate);
80267
+ continue;
80268
+ }
80269
+ if (stat.isFile()) {
80270
+ totalBytes += stat.size;
80271
+ continue;
80272
+ }
80273
+ if (stat.isSymbolicLink()) {
80274
+ totalBytes += Buffer.byteLength(fs81.readlinkSync(candidate));
80275
+ continue;
80276
+ }
80277
+ throw new BenchmarkPhaseError("unsafe_path", `evaluate output contains an unsupported filesystem entry: ${path88.relative(root, candidate)}`);
80278
+ }
80279
+ }
80280
+ return totalBytes;
80281
+ }
80282
+ function assertEvaluateOutputBudget(spec, context, additionalBytes = 0) {
80283
+ const actual = treeBytes(spec.logs_root, context) + additionalBytes;
80284
+ if (actual > spec.workspace_limits.max_total_bytes) {
80285
+ throw new BenchmarkPhaseError("workspace_limit_exceeded", "evaluate output bytes exceed the workspace byte limit", {
80286
+ actual,
80287
+ max_total_bytes: spec.workspace_limits.max_total_bytes
80288
+ });
80289
+ }
80290
+ }
80237
80291
  function candidateGlob(pattern) {
80238
80292
  let source = "^";
80239
80293
  for (let index = 0;index < pattern.length; index += 1) {
@@ -80392,13 +80446,48 @@ async function copyFrozenWorkspace(manifest, spec, context) {
80392
80446
  }
80393
80447
  return destinationRoot;
80394
80448
  }
80395
- function evaluatorTestsPath(evaluator, spec) {
80449
+ async function copyEvaluatorTests(spec, context) {
80450
+ const destinationRoot = fs81.mkdtempSync(path88.join(os16.tmpdir(), "brainbase-benchmark-tests-"));
80451
+ fs81.chmodSync(destinationRoot, 448);
80452
+ context.temporaryRoots.add(destinationRoot);
80453
+ const stack = [{ source: spec.tests_root, destination: destinationRoot }];
80454
+ while (stack.length > 0) {
80455
+ assertBudget(context);
80456
+ const current = stack.pop();
80457
+ const entries = fs81.readdirSync(current.source, { withFileTypes: true }).sort((left, right) => left.name.localeCompare(right.name));
80458
+ for (const entry of entries) {
80459
+ assertBudget(context);
80460
+ const source = path88.join(current.source, entry.name);
80461
+ const destination = path88.join(current.destination, entry.name);
80462
+ const stat = fs81.lstatSync(source);
80463
+ if (stat.isSymbolicLink()) {
80464
+ throw new BenchmarkPhaseError("unsafe_path", `evaluator reference tree contains a symlink: ${path88.relative(spec.tests_root, source)}`);
80465
+ }
80466
+ if (stat.isDirectory()) {
80467
+ fs81.mkdirSync(destination, { recursive: true, mode: stat.mode & 511 });
80468
+ stack.push({ source, destination });
80469
+ continue;
80470
+ }
80471
+ if (!stat.isFile()) {
80472
+ throw new BenchmarkPhaseError("unsafe_path", `evaluator reference tree contains an unsupported filesystem entry: ${path88.relative(spec.tests_root, source)}`);
80473
+ }
80474
+ await atomicCopy(source, destination, stat.mode & 511, spec.tests_root);
80475
+ const sourceRecord = await recordFile(spec.tests_root, source, "tests");
80476
+ const copiedRecord = await recordFile(destinationRoot, destination, "tests");
80477
+ if (copiedRecord.path !== sourceRecord.path || copiedRecord.sha256 !== sourceRecord.sha256 || copiedRecord.size !== sourceRecord.size || copiedRecord.mode !== sourceRecord.mode) {
80478
+ throw new BenchmarkPhaseError("evidence_tampered", `evaluator reference changed while it was copied: ${sourceRecord.path}`);
80479
+ }
80480
+ }
80481
+ }
80482
+ return destinationRoot;
80483
+ }
80484
+ function evaluatorTestsPath(evaluator, testsRoot) {
80396
80485
  if (!evaluator.tests_path)
80397
- return spec.tests_root;
80486
+ return testsRoot;
80398
80487
  const relative = normalizedRootRelative(evaluator.tests_path);
80399
- assertNoSymlinkTraversal(spec.tests_root, relative);
80400
- const candidate = path88.resolve(spec.tests_root, relative);
80401
- if (!isWithin(spec.tests_root, candidate) || !fs81.existsSync(candidate)) {
80488
+ assertNoSymlinkTraversal(testsRoot, relative);
80489
+ const candidate = path88.resolve(testsRoot, relative);
80490
+ if (!isWithin(testsRoot, candidate) || !fs81.existsSync(candidate)) {
80402
80491
  throw new BenchmarkPhaseError("invalid_tests_path", `evaluator tests_path does not exist: ${evaluator.tests_path}`);
80403
80492
  }
80404
80493
  const stat = fs81.lstatSync(candidate);
@@ -80623,12 +80712,14 @@ async function evaluateOne(evaluator, spec, manifest, finalOutput, trajectory, f
80623
80712
  };
80624
80713
  }
80625
80714
  let isolatedWorkspace;
80715
+ let isolatedTestsRoot;
80626
80716
  let privateResultRoot;
80627
80717
  try {
80628
80718
  const evaluatorWorkspace = await copyFrozenWorkspace(manifest, spec, context);
80629
80719
  isolatedWorkspace = evaluatorWorkspace;
80630
- const testsPath = evaluatorTestsPath(evaluator, spec);
80631
- const commandRoot = evaluator.root === "workspace" ? evaluatorWorkspace : spec.tests_root;
80720
+ isolatedTestsRoot = await copyEvaluatorTests(spec, context);
80721
+ const testsPath = evaluatorTestsPath(evaluator, isolatedTestsRoot);
80722
+ const commandRoot = evaluator.root === "workspace" ? evaluatorWorkspace : isolatedTestsRoot;
80632
80723
  const command = { id: evaluator.id, ...evaluator.command };
80633
80724
  const environment = {
80634
80725
  BRAINBASE_BENCHMARK_WORKSPACE: evaluatorWorkspace,
@@ -80679,7 +80770,7 @@ async function evaluateOne(evaluator, spec, manifest, finalOutput, trajectory, f
80679
80770
  ...criterionResults ? { criterion_results: criterionResults } : {}
80680
80771
  };
80681
80772
  } finally {
80682
- for (const temporary of [privateResultRoot, isolatedWorkspace]) {
80773
+ for (const temporary of [privateResultRoot, isolatedTestsRoot, isolatedWorkspace]) {
80683
80774
  if (!temporary)
80684
80775
  continue;
80685
80776
  fs81.rmSync(temporary, { recursive: true, force: true });
@@ -80728,6 +80819,7 @@ async function executeEvaluate(spec, context) {
80728
80819
  ];
80729
80820
  outputs.push(...frozenEvidenceRecords);
80730
80821
  context.outputs.push(...frozenEvidenceRecords);
80822
+ assertEvaluateOutputBudget(spec, context);
80731
80823
  assertBudget(context);
80732
80824
  const manifest = await workspaceManifest(spec, context);
80733
80825
  const manifestPath2 = path88.join(spec.logs_root, "candidate-workspace-manifest.json");
@@ -80740,6 +80832,7 @@ async function executeEvaluate(spec, context) {
80740
80832
  const manifestRecord = await recordFile(spec.logs_root, manifestPath2, "logs");
80741
80833
  outputs.push(manifestRecord);
80742
80834
  context.outputs.push(manifestRecord);
80835
+ assertEvaluateOutputBudget(spec, context);
80743
80836
  for (const artifactRelInput of spec.candidate_artifacts) {
80744
80837
  const artifactRel = workspaceRel(artifactRelInput);
80745
80838
  assertNoSymlinkTraversal(spec.workspace_root, artifactRel);
@@ -80757,11 +80850,13 @@ async function executeEvaluate(spec, context) {
80757
80850
  outputs.push(artifact);
80758
80851
  context.outputs.push(artifact);
80759
80852
  }
80853
+ assertEvaluateOutputBudget(spec, context);
80760
80854
  for (const candidateOutput of spec.candidate_outputs) {
80761
80855
  const copied = await copyCandidateOutput(candidateOutput, manifest, spec, context);
80762
80856
  outputs.push(...copied);
80763
80857
  context.outputs.push(...copied);
80764
80858
  }
80859
+ assertEvaluateOutputBudget(spec, context);
80765
80860
  if (spec.capture_workspace_archive) {
80766
80861
  const regularFiles = manifest.filter((entry) => entry.kind !== "symlink").map((entry) => entry.path);
80767
80862
  const archive = path88.join(spec.logs_root, "candidate-workspace.tar.gz");
@@ -80775,6 +80870,7 @@ async function executeEvaluate(spec, context) {
80775
80870
  const archiveRecord = await recordFile(spec.logs_root, archive, "logs");
80776
80871
  outputs.push(archiveRecord);
80777
80872
  context.outputs.push(archiveRecord);
80873
+ assertEvaluateOutputBudget(spec, context);
80778
80874
  }
80779
80875
  await verifyRecordsUnchanged(manifest, spec);
80780
80876
  for (const reference of spec.references) {
@@ -80789,7 +80885,6 @@ async function executeEvaluate(spec, context) {
80789
80885
  for (const evaluator of executionOrder) {
80790
80886
  assertBudget(context);
80791
80887
  const started = Date.now();
80792
- let requiredResultError;
80793
80888
  try {
80794
80889
  const evaluated = await evaluateOne(evaluator, spec, manifest, finalOutput.buffer.toString("utf8"), trajectory, { finalOutputPath, trajectoryPath }, context);
80795
80890
  assertBudget(context);
@@ -80803,13 +80898,14 @@ async function executeEvaluate(spec, context) {
80803
80898
  outputs.push(evaluated.stderr);
80804
80899
  context.outputs.push(evaluated.stderr);
80805
80900
  }
80806
- if (evaluated.status === "errored" && evaluator.required) {
80807
- const errorCode = typeof evaluated.details?.error_code === "string" ? evaluated.details.error_code : "evaluator_execution_failed";
80808
- const errorMessage2 = typeof evaluated.details?.error_message === "string" ? evaluated.details.error_message : "required evaluator execution failed";
80809
- requiredResultError = new BenchmarkPhaseError(errorCode, errorMessage2);
80810
- }
80811
80901
  } catch (error2) {
80812
80902
  const normalized = stableError(error2);
80903
+ let stdout;
80904
+ let stderr;
80905
+ if (error2 instanceof BenchmarkCommandExecutionError) {
80906
+ stdout = await writeLog(spec.logs_root, `${evaluator.id}.stdout.log`, error2.stdout, spec);
80907
+ stderr = await writeLog(spec.logs_root, `${evaluator.id}.stderr.log`, error2.stderr, spec);
80908
+ }
80813
80909
  const errored = {
80814
80910
  id: evaluator.id,
80815
80911
  type: evaluator.type,
@@ -80819,26 +80915,38 @@ async function executeEvaluate(spec, context) {
80819
80915
  verdict: null,
80820
80916
  engine: "brainbase-cli",
80821
80917
  engine_version: VERSION,
80822
- duration_ms: Date.now() - started,
80918
+ duration_ms: error2 instanceof BenchmarkCommandExecutionError ? error2.durationMs : Date.now() - started,
80823
80919
  details: {
80824
80920
  error_code: normalized?.code ?? "phase_failed",
80825
80921
  error_message: normalized?.message ?? "evaluator execution failed"
80826
- }
80922
+ },
80923
+ ...stdout ? { stdout } : {},
80924
+ ...stderr ? { stderr } : {}
80827
80925
  };
80828
80926
  evaluators.push(errored);
80829
80927
  context.evaluators.push(errored);
80830
- if (evaluator.required)
80928
+ if (stdout) {
80929
+ outputs.push(stdout);
80930
+ context.outputs.push(stdout);
80931
+ }
80932
+ if (stderr) {
80933
+ outputs.push(stderr);
80934
+ context.outputs.push(stderr);
80935
+ }
80936
+ if (!(error2 instanceof BenchmarkCommandExecutionError))
80831
80937
  throw error2;
80832
80938
  assertBudget(context);
80833
80939
  }
80834
- if (requiredResultError)
80835
- throw requiredResultError;
80940
+ if (evaluator.type === "sandbox_command") {
80941
+ assertEvaluateOutputBudget(spec, context);
80942
+ }
80836
80943
  }
80837
80944
  await verifyRecordsUnchanged([...context.inputs, ...context.outputs.slice(0, frozenOutputCount)], spec);
80838
80945
  if (!verifyOwnedDirectory(spec.tests_root, "tests", spec) || !verifyOwnedDirectory(spec.logs_root, "logs", spec)) {
80839
80946
  throw new BenchmarkPhaseError("evidence_tampered", "benchmark evaluator changed an owned phase directory marker");
80840
80947
  }
80841
80948
  assertBudget(context);
80949
+ assertEvaluateOutputBudget(spec, context);
80842
80950
  evaluators.sort((left, right) => evaluatorOrder.get(left.id) - evaluatorOrder.get(right.id));
80843
80951
  return { outputs, evaluators };
80844
80952
  }
@@ -81024,6 +81132,7 @@ async function runBenchmarkPhase(specPathInput, resultPathInput, expectedPhase,
81024
81132
  let status = "failed";
81025
81133
  let error2;
81026
81134
  let context;
81135
+ let validatedSpec;
81027
81136
  let resultPathValidated = false;
81028
81137
  try {
81029
81138
  let bytes;
@@ -81043,6 +81152,7 @@ async function runBenchmarkPhase(specPathInput, resultPathInput, expectedPhase,
81043
81152
  }
81044
81153
  identity2 = rawIdentity(raw);
81045
81154
  const spec = BenchmarkSpecSchema.parse(raw);
81155
+ validatedSpec = spec;
81046
81156
  if (expectedPhase && spec.phase !== expectedPhase) {
81047
81157
  throw new BenchmarkPhaseError("phase_mismatch", `the ${expectedPhase} command cannot execute a ${spec.phase} spec`);
81048
81158
  }
@@ -81122,6 +81232,17 @@ async function runBenchmarkPhase(specPathInput, resultPathInput, expectedPhase,
81122
81232
  if (!resultPathValidated || !context?.logsOwned) {
81123
81233
  return { exitCode: status === "succeeded" ? 0 : 1, result: result2 };
81124
81234
  }
81235
+ if (status === "succeeded" && validatedSpec?.phase === "evaluate") {
81236
+ try {
81237
+ const resultBytes = Buffer.byteLength(`${JSON.stringify(result2, null, 2)}
81238
+ `);
81239
+ assertEvaluateOutputBudget(validatedSpec, context, resultBytes);
81240
+ } catch (budgetError) {
81241
+ status = "failed";
81242
+ result2.status = "failed";
81243
+ result2.error = stableError(budgetError);
81244
+ }
81245
+ }
81125
81246
  try {
81126
81247
  writeJsonAtomic(resultPath, result2);
81127
81248
  } catch (writeError) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brainbase-labs/cli",
3
- "version": "0.25.0-eng1209.4",
3
+ "version": "0.25.0-eng1209.6",
4
4
  "description": "Pack, share, and install agent templates across harnesses (Claude Code, Codex, ...).",
5
5
  "type": "module",
6
6
  "bin": {