@robmclarty/volley 0.5.4 → 0.6.1

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/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/config.ts
2
- import { accessSync, constants, existsSync, readFileSync, statSync } from "fs";
2
+ import { accessSync, constants, existsSync as existsSync2, readFileSync as readFileSync2, statSync } from "fs";
3
3
  import { isAbsolute, resolve } from "path";
4
4
  import { pathToFileURL } from "url";
5
5
  import { randomUUID } from "crypto";
@@ -133,6 +133,31 @@ function collect_changes(options) {
133
133
  };
134
134
  }
135
135
 
136
+ // src/check/detect.ts
137
+ import { existsSync, readFileSync } from "fs";
138
+ import { join } from "path";
139
+ function package_check_script_is_checkride(workspace) {
140
+ const path = join(workspace, "package.json");
141
+ if (!existsSync(path)) return false;
142
+ try {
143
+ const pkg = JSON.parse(readFileSync(path, "utf8"));
144
+ const script = pkg.scripts?.["check"];
145
+ return typeof script === "string" && /(^|\s)checkride(\s|$)/.test(script);
146
+ } catch {
147
+ return false;
148
+ }
149
+ }
150
+ function checkride_detected(workspace) {
151
+ return existsSync(join(workspace, "checkride.config.json")) || package_check_script_is_checkride(workspace) || existsSync(join(workspace, "node_modules", ".bin", "checkride"));
152
+ }
153
+ function resolve_check_runner(check, workspace) {
154
+ if (check === "none") return "none";
155
+ if (check === "auto") {
156
+ return checkride_detected(workspace) ? "checkride" : "none";
157
+ }
158
+ return /(^|\s)checkride(\s|$)/.test(check.trim()) ? "checkride" : "command";
159
+ }
160
+
136
161
  // src/types.ts
137
162
  function config_error(message) {
138
163
  return Object.assign(new Error(message), { kind: "config_error" });
@@ -192,17 +217,17 @@ var PERMISSION_MODES = [
192
217
  function expand_at_file(value, cwd) {
193
218
  if (!value.startsWith("@")) return value;
194
219
  const path = resolve(cwd, value.slice(1));
195
- if (!existsSync(path)) {
220
+ if (!existsSync2(path)) {
196
221
  throw config_error(`file not found: ${path} (from ${value})`);
197
222
  }
198
- return readFileSync(path, "utf8");
223
+ return readFileSync2(path, "utf8");
199
224
  }
200
225
  function is_critic_preset(value) {
201
226
  return CRITIC_PRESETS.includes(value);
202
227
  }
203
228
  async function load_config_file(path) {
204
229
  const abs = resolve(path);
205
- if (!existsSync(abs)) {
230
+ if (!existsSync2(abs)) {
206
231
  throw config_error(`config file not found: ${abs}`);
207
232
  }
208
233
  let loaded;
@@ -220,7 +245,7 @@ async function load_config_file(path) {
220
245
  }
221
246
  function validate_workspace(workspace) {
222
247
  const abs = isAbsolute(workspace) ? workspace : resolve(workspace);
223
- if (!existsSync(abs)) {
248
+ if (!existsSync2(abs)) {
224
249
  throw config_error(`workspace not found: ${abs}`);
225
250
  }
226
251
  if (!statSync(abs).isDirectory()) {
@@ -238,7 +263,7 @@ function resolve_critic(critic, cwd) {
238
263
  return { critic_preset: critic, critic_prompt_path: null };
239
264
  }
240
265
  const path = resolve(cwd, critic);
241
- if (!existsSync(path)) {
266
+ if (!existsSync2(path)) {
242
267
  throw config_error(
243
268
  `--critic must be one of ${CRITIC_PRESETS.join(", ")} or a path to a prompt file; got: ${critic}`
244
269
  );
@@ -282,6 +307,16 @@ function detect_containment(env) {
282
307
  const value = env["VOLLEY_CONTAINED"];
283
308
  return value === "1" || value === "true";
284
309
  }
310
+ var RUN_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_-]*$/;
311
+ function resolve_run_id(run_id) {
312
+ if (run_id === void 0) return randomUUID();
313
+ if (!RUN_ID_PATTERN.test(run_id)) {
314
+ throw config_error(
315
+ `--run-id must be letters, digits, '-', and '_', starting with a letter or digit; got: ${run_id}`
316
+ );
317
+ }
318
+ return run_id;
319
+ }
285
320
  function resolve_config(raw, options = {}) {
286
321
  const cwd = options.cwd ?? process.cwd();
287
322
  const env = options.env ?? process.env;
@@ -347,11 +382,11 @@ function resolve_config(raw, options = {}) {
347
382
  );
348
383
  }
349
384
  }
350
- if (raw.git_checkpoints === true && !existsSync(resolve(workspace, ".git"))) {
385
+ if (raw.git_checkpoints === true && !existsSync2(resolve(workspace, ".git"))) {
351
386
  throw config_error(`--git requires the workspace to be a git repository: ${workspace}`);
352
387
  }
353
388
  const worktree = raw.worktree ?? false;
354
- if (worktree && !existsSync(resolve(workspace, ".git"))) {
389
+ if (worktree && !existsSync2(resolve(workspace, ".git"))) {
355
390
  throw config_error(`--worktree requires the workspace to be a git repository: ${workspace}`);
356
391
  }
357
392
  const discard_worktree = raw.discard_worktree ?? false;
@@ -365,14 +400,16 @@ function resolve_config(raw, options = {}) {
365
400
  throw config_error("gate_paths must be an array of glob strings");
366
401
  }
367
402
  const sandbox_image = resolve_sandbox_image(raw, env);
403
+ const run_id = resolve_run_id(options.run_id);
404
+ const check = raw.check ?? DEFAULT_CHECK;
368
405
  return {
369
406
  version: 2,
370
- run_id: options.run_id ?? randomUUID(),
407
+ run_id,
371
408
  started_at: options.started_at ?? (/* @__PURE__ */ new Date()).toISOString(),
372
409
  prompt,
373
410
  criteria,
374
- check: raw.check ?? DEFAULT_CHECK,
375
- check_resolved: "none",
411
+ check,
412
+ check_resolved: resolve_check_runner(check, workspace),
376
413
  builder_model: raw.builder_model ?? DEFAULT_BUILDER_MODEL,
377
414
  builder_provider,
378
415
  builder_max_steps,
@@ -399,12 +436,12 @@ function resolve_config(raw, options = {}) {
399
436
  }
400
437
 
401
438
  // src/orchestrator.ts
402
- import { existsSync as existsSync10, readFileSync as readFileSync10 } from "fs";
439
+ import { existsSync as existsSync11, readFileSync as readFileSync11 } from "fs";
403
440
  import { run } from "fascicle";
404
441
  import { filesystem_logger } from "fascicle/adapters";
405
442
 
406
443
  // src/engine.ts
407
- import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
444
+ import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
408
445
  import { create_engine } from "fascicle";
409
446
  var DEFAULT_OLLAMA_URL = "http://localhost:11434";
410
447
  var DEFAULT_LMSTUDIO_URL = "http://localhost:1234/v1";
@@ -428,11 +465,11 @@ function resolve_ollama_base_url(env) {
428
465
  return cross_to_host_gateway(normalized, env);
429
466
  }
430
467
  function load_pricing_overrides(path) {
431
- if (!existsSync2(path)) {
468
+ if (!existsSync3(path)) {
432
469
  throw config_error(`VOLLEY_PRICING_PATH not found: ${path}`);
433
470
  }
434
471
  try {
435
- return JSON.parse(readFileSync2(path, "utf8"));
472
+ return JSON.parse(readFileSync3(path, "utf8"));
436
473
  } catch (err) {
437
474
  const detail = err instanceof Error ? err.message : String(err);
438
475
  throw config_error(`VOLLEY_PRICING_PATH is not valid JSON: ${detail}`);
@@ -483,8 +520,8 @@ function create_volley_engine(options) {
483
520
  import { branch, fallback, loop, pipe, retry, sequence, step } from "fascicle";
484
521
 
485
522
  // src/builder.ts
486
- import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
487
- import { dirname as dirname2, join as join4 } from "path";
523
+ import { existsSync as existsSync6, readFileSync as readFileSync6 } from "fs";
524
+ import { dirname as dirname2, join as join5 } from "path";
488
525
  import { fileURLToPath } from "url";
489
526
  import { model_call } from "fascicle";
490
527
 
@@ -528,7 +565,7 @@ async function warn_small_local_context(provider, model, base_url, warn, abort)
528
565
  // src/builder/tools.ts
529
566
  import { spawnSync as spawnSync2 } from "child_process";
530
567
  import { lookup as dns_lookup } from "dns";
531
- import { mkdirSync, readFileSync as readFileSync4, writeFileSync } from "fs";
568
+ import { mkdirSync, readFileSync as readFileSync5, writeFileSync } from "fs";
532
569
  import { dirname } from "path";
533
570
  import { Readability } from "@mozilla/readability";
534
571
  import ipaddr from "ipaddr.js";
@@ -538,8 +575,8 @@ import { Agent, buildConnector, interceptors, request } from "undici";
538
575
  import { z as z2 } from "zod";
539
576
 
540
577
  // src/workspace_tools.ts
541
- import { readFileSync as readFileSync3, readdirSync, statSync as statSync2 } from "fs";
542
- import { isAbsolute as isAbsolute2, join, relative, resolve as resolve2, sep } from "path";
578
+ import { readFileSync as readFileSync4, readdirSync, statSync as statSync2 } from "fs";
579
+ import { isAbsolute as isAbsolute2, join as join2, relative, resolve as resolve2, sep } from "path";
543
580
  import { z } from "zod";
544
581
  var READ_FILE_MAX_BYTES = 2e5;
545
582
  var SEARCH_MAX_MATCHES = 200;
@@ -573,7 +610,7 @@ function walk(root, start, limit) {
573
610
  continue;
574
611
  }
575
612
  for (const name of entries) {
576
- const abs = join(dir, name);
613
+ const abs = join2(dir, name);
577
614
  let is_dir;
578
615
  try {
579
616
  is_dir = statSync2(abs).isDirectory();
@@ -618,7 +655,7 @@ function read_only_tools(workspace) {
618
655
  execute: (raw) => {
619
656
  const input = read_file_input.parse(raw);
620
657
  const abs = contain(workspace, input.path);
621
- const content = readFileSync3(abs, "utf8");
658
+ const content = readFileSync4(abs, "utf8");
622
659
  return content.length > READ_FILE_MAX_BYTES ? `${content.slice(0, READ_FILE_MAX_BYTES)}
623
660
  \u2026 [truncated at ${String(READ_FILE_MAX_BYTES)} bytes]` : content;
624
661
  }
@@ -637,7 +674,7 @@ function read_only_tools(workspace) {
637
674
  if (hits.length >= SEARCH_MAX_MATCHES) break;
638
675
  let content;
639
676
  try {
640
- content = readFileSync3(file.abs, "utf8");
677
+ content = readFileSync4(file.abs, "utf8");
641
678
  } catch {
642
679
  continue;
643
680
  }
@@ -745,9 +782,9 @@ function is_forbidden_address(address) {
745
782
  var default_resolver = (hostname, options, callback) => {
746
783
  dns_lookup(hostname, options, callback);
747
784
  };
748
- function guarded_lookup(is_forbidden, resolve4) {
785
+ function guarded_lookup(is_forbidden, resolve5) {
749
786
  return (hostname, options, callback) => {
750
- resolve4(hostname, options, (err, address, family) => {
787
+ resolve5(hostname, options, (err, address, family) => {
751
788
  if (err !== null) {
752
789
  callback(err, "", 0);
753
790
  return;
@@ -769,8 +806,8 @@ function ssrf_error(address, hostname) {
769
806
  err.code = "ERR_SSRF_BLOCKED";
770
807
  return err;
771
808
  }
772
- function create_ssrf_agent(is_forbidden, resolve4) {
773
- const base = buildConnector({ lookup: guarded_lookup(is_forbidden, resolve4), timeout: FETCH_TIMEOUT_MS });
809
+ function create_ssrf_agent(is_forbidden, resolve5) {
810
+ const base = buildConnector({ lookup: guarded_lookup(is_forbidden, resolve5), timeout: FETCH_TIMEOUT_MS });
774
811
  const connector = (options, callback) => {
775
812
  base(options, (err, socket) => {
776
813
  if (err !== null) {
@@ -980,7 +1017,7 @@ function builder_tools(workspace, options = {}) {
980
1017
  execute: (raw) => {
981
1018
  const input = edit_file_input.parse(raw);
982
1019
  const abs = contain(workspace, input.path);
983
- const content = readFileSync4(abs, "utf8");
1020
+ const content = readFileSync5(abs, "utf8");
984
1021
  const matches = count_occurrences(content, input.old_str);
985
1022
  if (matches === 0) {
986
1023
  return `error: old_str not found in ${input.path} (0 matches). No change made. Read the file and retry with an exact excerpt of its current content.`;
@@ -1120,16 +1157,16 @@ async function prewarm_ollama_model(base_url, model, abort) {
1120
1157
  }
1121
1158
 
1122
1159
  // src/worktree.ts
1123
- import { existsSync as existsSync4, renameSync as renameSync2, rmSync, symlinkSync } from "fs";
1124
- import { join as join3, resolve as resolve3 } from "path";
1160
+ import { existsSync as existsSync5, renameSync as renameSync2, rmSync, symlinkSync } from "fs";
1161
+ import { join as join4, resolve as resolve3 } from "path";
1125
1162
  import { spawnSync as spawnSync4 } from "child_process";
1126
1163
 
1127
1164
  // src/workspace.ts
1128
- import { existsSync as existsSync3, mkdirSync as mkdirSync2, renameSync, writeFileSync as writeFileSync2 } from "fs";
1129
- import { join as join2 } from "path";
1165
+ import { existsSync as existsSync4, mkdirSync as mkdirSync2, renameSync, writeFileSync as writeFileSync2 } from "fs";
1166
+ import { join as join3 } from "path";
1130
1167
  import { spawnSync as spawnSync3 } from "child_process";
1131
1168
  function volley_path(workspace, ...parts) {
1132
- return join2(workspace, ".volley", ...parts);
1169
+ return join3(workspace, ".volley", ...parts);
1133
1170
  }
1134
1171
  function iteration_dir(workspace, iteration) {
1135
1172
  return volley_path(workspace, "iterations", String(iteration).padStart(3, "0"));
@@ -1139,8 +1176,8 @@ function backup_stamp(date = /* @__PURE__ */ new Date()) {
1139
1176
  }
1140
1177
  function initialize_workspace(workspace, options = {}) {
1141
1178
  const dir = volley_path(workspace);
1142
- if (existsSync3(dir) && options.preserve !== true) {
1143
- renameSync(dir, join2(workspace, `.volley.bak.${backup_stamp()}`));
1179
+ if (existsSync4(dir) && options.preserve !== true) {
1180
+ renameSync(dir, join3(workspace, `.volley.bak.${backup_stamp()}`));
1144
1181
  }
1145
1182
  mkdirSync2(volley_path(workspace, "iterations"), { recursive: true });
1146
1183
  }
@@ -1178,7 +1215,7 @@ function write_resolved_config(config) {
1178
1215
  );
1179
1216
  }
1180
1217
  function git_checkpoint(root, message) {
1181
- if (!existsSync3(join2(root, ".git"))) {
1218
+ if (!existsSync4(join3(root, ".git"))) {
1182
1219
  throw config_error(`git checkpoints require a git repository: ${root}`);
1183
1220
  }
1184
1221
  const add = spawnSync3("git", ["add", "-A"], { cwd: root });
@@ -1197,7 +1234,7 @@ function git_checkpoint(root, message) {
1197
1234
  }
1198
1235
  }
1199
1236
  function integrate_worktree(workspace, branch2, message) {
1200
- if (!existsSync3(join2(workspace, ".git"))) {
1237
+ if (!existsSync4(join3(workspace, ".git"))) {
1201
1238
  throw config_error(`--worktree integration requires a git repository: ${workspace}`);
1202
1239
  }
1203
1240
  const squash = spawnSync3("git", ["merge", "--squash", branch2], {
@@ -1299,14 +1336,14 @@ function branch_exists(repo, branch2) {
1299
1336
  }
1300
1337
  function create_worktree(options) {
1301
1338
  const repo = resolve3(options.workspace);
1302
- if (!existsSync4(join3(repo, ".git"))) {
1339
+ if (!existsSync5(join4(repo, ".git"))) {
1303
1340
  throw config_error(`--worktree requires the workspace to be a git repository: ${repo}`);
1304
1341
  }
1305
1342
  const path = worktree_path(repo);
1306
1343
  const base = options.base ?? "HEAD";
1307
1344
  const log = options.log ?? (() => {
1308
1345
  });
1309
- if (existsSync4(path)) {
1346
+ if (existsSync5(path)) {
1310
1347
  const backup = `${path}.bak.${backup_stamp()}`;
1311
1348
  renameSync2(path, backup);
1312
1349
  git2(repo, ["worktree", "prune"]);
@@ -1318,9 +1355,9 @@ function create_worktree(options) {
1318
1355
  log(`worktree: rotated existing branch ${options.branch} aside to ${backup_branch}`);
1319
1356
  }
1320
1357
  git_ok(repo, ["worktree", "add", "-b", options.branch, path, base]);
1321
- const modules = join3(repo, "node_modules");
1322
- if (existsSync4(modules) && !existsSync4(join3(path, "node_modules"))) {
1323
- symlinkSync(modules, join3(path, "node_modules"), "dir");
1358
+ const modules = join4(repo, "node_modules");
1359
+ if (existsSync5(modules) && !existsSync5(join4(path, "node_modules"))) {
1360
+ symlinkSync(modules, join4(path, "node_modules"), "dir");
1324
1361
  log(`worktree: linked node_modules from ${repo}`);
1325
1362
  }
1326
1363
  return { path, branch: options.branch };
@@ -1335,7 +1372,7 @@ function teardown_worktree(options) {
1335
1372
  git_quiet(repo, ["branch", "-D", options.branch]);
1336
1373
  }
1337
1374
  git_quiet(repo, ["worktree", "prune"]);
1338
- if (existsSync4(path)) {
1375
+ if (existsSync5(path)) {
1339
1376
  try {
1340
1377
  rmSync(path, { recursive: true, force: true });
1341
1378
  } catch {
@@ -1344,7 +1381,7 @@ function teardown_worktree(options) {
1344
1381
  return outcome;
1345
1382
  }
1346
1383
  function salvage_worktree(repo, path, branch2) {
1347
- if (existsSync4(path)) {
1384
+ if (existsSync5(path)) {
1348
1385
  git_quiet(path, ["add", "-A"]);
1349
1386
  git_quiet(path, [
1350
1387
  "commit",
@@ -1417,8 +1454,8 @@ function compose_builder_system() {
1417
1454
  function presets_dir() {
1418
1455
  let dir = dirname2(fileURLToPath(import.meta.url));
1419
1456
  for (let depth = 0; depth < 6; depth += 1) {
1420
- const candidate = join4(dir, "src", "builder", "presets");
1421
- if (existsSync5(candidate)) return candidate;
1457
+ const candidate = join5(dir, "src", "builder", "presets");
1458
+ if (existsSync6(candidate)) return candidate;
1422
1459
  dir = dirname2(dir);
1423
1460
  }
1424
1461
  throw config_error("builder presets directory not found");
@@ -1429,7 +1466,7 @@ function compose_builder_system_local() {
1429
1466
  "iterating on a workspace until it satisfies a task and its acceptance",
1430
1467
  "criteria."
1431
1468
  ].join("\n");
1432
- const append = readFileSync5(join4(presets_dir(), "harness_append_local.md"), "utf8");
1469
+ const append = readFileSync6(join5(presets_dir(), "harness_append_local.md"), "utf8");
1433
1470
  return `${identity}
1434
1471
 
1435
1472
  ${append.trimEnd()}`;
@@ -1552,24 +1589,24 @@ import { z as z3 } from "zod";
1552
1589
  import { model_call as model_call2 } from "fascicle";
1553
1590
 
1554
1591
  // src/critic/prompt.ts
1555
- import { existsSync as existsSync6, readFileSync as readFileSync6 } from "fs";
1556
- import { dirname as dirname3, join as join5 } from "path";
1592
+ import { existsSync as existsSync7, readFileSync as readFileSync7 } from "fs";
1593
+ import { dirname as dirname3, join as join6 } from "path";
1557
1594
  import { fileURLToPath as fileURLToPath2 } from "url";
1558
1595
  function presets_dir2() {
1559
1596
  let dir = dirname3(fileURLToPath2(import.meta.url));
1560
1597
  for (let depth = 0; depth < 6; depth += 1) {
1561
- const candidate = join5(dir, "src", "critic", "presets");
1562
- if (existsSync6(candidate)) return candidate;
1563
- const local = join5(dir, "presets");
1564
- if (existsSync6(local)) return local;
1598
+ const candidate = join6(dir, "src", "critic", "presets");
1599
+ if (existsSync7(candidate)) return candidate;
1600
+ const local = join6(dir, "presets");
1601
+ if (existsSync7(local)) return local;
1565
1602
  dir = dirname3(dir);
1566
1603
  }
1567
1604
  throw config_error("critic presets directory not found");
1568
1605
  }
1569
1606
  function resolve_critic_prompt(config) {
1570
- const base = config.critic_prompt_path !== null ? readFileSync6(config.critic_prompt_path, "utf8") : readFileSync6(join5(presets_dir2(), `${config.critic_preset}.md`), "utf8");
1607
+ const base = config.critic_prompt_path !== null ? readFileSync7(config.critic_prompt_path, "utf8") : readFileSync7(join6(presets_dir2(), `${config.critic_preset}.md`), "utf8");
1571
1608
  const append_file = config.critic_provider === "claude_cli" ? "harness_append.md" : "harness_append_local.md";
1572
- const append = readFileSync6(join5(presets_dir2(), append_file), "utf8");
1609
+ const append = readFileSync7(join6(presets_dir2(), append_file), "utf8");
1573
1610
  return `${base.trimEnd()}
1574
1611
 
1575
1612
  ${append.trimEnd()}`;
@@ -1869,8 +1906,8 @@ function status_of(value, converged) {
1869
1906
  }
1870
1907
 
1871
1908
  // src/check/checkride.ts
1872
- import { existsSync as existsSync7, readFileSync as readFileSync7 } from "fs";
1873
- import { join as join6 } from "path";
1909
+ import { existsSync as existsSync8, readFileSync as readFileSync8 } from "fs";
1910
+ import { join as join7 } from "path";
1874
1911
  import { spawn } from "child_process";
1875
1912
  import { aborted_error } from "fascicle";
1876
1913
  var CHECK_ARTIFACT_MAX_CHARS = 8e3;
@@ -1922,9 +1959,9 @@ function parse_summary_with_fallback(stdout, workspace) {
1922
1959
  try {
1923
1960
  return parse_checkride_summary(stdout);
1924
1961
  } catch (err) {
1925
- const file = join6(workspace, ".check", "summary.json");
1926
- if (!existsSync7(file)) throw err;
1927
- return parse_checkride_summary(readFileSync7(file, "utf8"));
1962
+ const file = join7(workspace, ".check", "summary.json");
1963
+ if (!existsSync8(file)) throw err;
1964
+ return parse_checkride_summary(readFileSync8(file, "utf8"));
1928
1965
  }
1929
1966
  }
1930
1967
  function failing_checks(summary) {
@@ -1933,11 +1970,11 @@ function failing_checks(summary) {
1933
1970
  function read_failing_artifacts(workspace, failing) {
1934
1971
  return failing.map((check) => {
1935
1972
  const file = check.output_file ?? `${check.name}.stdout.txt`;
1936
- const path = join6(workspace, ".check", file);
1937
- if (!existsSync7(path)) {
1973
+ const path = join7(workspace, ".check", file);
1974
+ if (!existsSync8(path)) {
1938
1975
  return { slot: check.name, path: null, content: "(no artifact found)", truncated: false };
1939
1976
  }
1940
- const raw = readFileSync7(path, "utf8");
1977
+ const raw = readFileSync8(path, "utf8");
1941
1978
  const truncated = raw.length > CHECK_ARTIFACT_MAX_CHARS;
1942
1979
  return {
1943
1980
  slot: check.name,
@@ -2009,14 +2046,14 @@ function skipped_check(reason) {
2009
2046
 
2010
2047
  // src/iteration.ts
2011
2048
  import {
2012
- existsSync as existsSync8,
2049
+ existsSync as existsSync9,
2013
2050
  mkdirSync as mkdirSync3,
2014
- readFileSync as readFileSync8,
2051
+ readFileSync as readFileSync9,
2015
2052
  readdirSync as readdirSync2,
2016
2053
  rmSync as rmSync2,
2017
2054
  writeFileSync as writeFileSync3
2018
2055
  } from "fs";
2019
- import { basename, join as join7 } from "path";
2056
+ import { basename, join as join8 } from "path";
2020
2057
  function write_json(path, value) {
2021
2058
  writeFileSync3(path, `${JSON.stringify(value, null, 2)}
2022
2059
  `);
@@ -2060,32 +2097,32 @@ function phase_summary(record) {
2060
2097
  }
2061
2098
  function archive_iteration(config, state) {
2062
2099
  const dir = iteration_dir(config.workspace, state.iteration);
2063
- mkdirSync3(join7(dir, "check"), { recursive: true });
2100
+ mkdirSync3(join8(dir, "check"), { recursive: true });
2064
2101
  if (state.verdict !== null && state.feedback !== null) {
2065
2102
  persist_verdict_files(config.workspace, state.feedback, state.verdict);
2066
- writeFileSync3(join7(dir, "feedback.md"), readFileSync8(volley_path(config.workspace, "feedback.md")));
2067
- writeFileSync3(join7(dir, "verdict"), `${state.verdict}
2103
+ writeFileSync3(join8(dir, "feedback.md"), readFileSync9(volley_path(config.workspace, "feedback.md")));
2104
+ writeFileSync3(join8(dir, "verdict"), `${state.verdict}
2068
2105
  `);
2069
2106
  }
2070
2107
  const check = state.check;
2071
2108
  if (check !== null && check.ran) {
2072
2109
  if (check.runner === "checkride") {
2073
2110
  if (check.summary !== void 0) {
2074
- write_json(join7(dir, "check", "summary.json"), check.summary);
2111
+ write_json(join8(dir, "check", "summary.json"), check.summary);
2075
2112
  }
2076
2113
  for (const artifact of check.detail) {
2077
- if (artifact.path !== null && existsSync8(artifact.path)) {
2078
- writeFileSync3(join7(dir, "check", basename(artifact.path)), readFileSync8(artifact.path));
2114
+ if (artifact.path !== null && existsSync9(artifact.path)) {
2115
+ writeFileSync3(join8(dir, "check", basename(artifact.path)), readFileSync9(artifact.path));
2079
2116
  }
2080
2117
  }
2081
2118
  } else {
2082
- writeFileSync3(join7(dir, "check", "check.log"), check.log ?? "");
2083
- writeFileSync3(join7(dir, "check", "check.exit"), `${String(check.exit_code)}
2119
+ writeFileSync3(join8(dir, "check", "check.log"), check.log ?? "");
2120
+ writeFileSync3(join8(dir, "check", "check.exit"), `${String(check.exit_code)}
2084
2121
  `);
2085
2122
  }
2086
2123
  }
2087
2124
  const completed_at = (/* @__PURE__ */ new Date()).toISOString();
2088
- write_json(join7(dir, "summary.json"), {
2125
+ write_json(join8(dir, "summary.json"), {
2089
2126
  iteration: state.iteration,
2090
2127
  started_at: state.iteration_started_at,
2091
2128
  completed_at,
@@ -2135,20 +2172,20 @@ function run_result_from_state(config, state, status, completed_at) {
2135
2172
  }
2136
2173
 
2137
2174
  // src/summary.ts
2138
- import { existsSync as existsSync9, readFileSync as readFileSync9, readdirSync as readdirSync3, writeFileSync as writeFileSync4 } from "fs";
2175
+ import { existsSync as existsSync10, readFileSync as readFileSync10, readdirSync as readdirSync3, writeFileSync as writeFileSync4 } from "fs";
2139
2176
  function transport_of(provider) {
2140
2177
  return provider === "claude_cli" ? "claude_cli" : "ai_sdk";
2141
2178
  }
2142
2179
  function read_iteration_archives(workspace) {
2143
2180
  const root = volley_path(workspace, "iterations");
2144
- if (!existsSync9(root)) return [];
2181
+ if (!existsSync10(root)) return [];
2145
2182
  const dirs = readdirSync3(root).filter((dir) => /^\d{3}$/.test(dir)).toSorted();
2146
2183
  const archives = [];
2147
2184
  for (const dir of dirs) {
2148
2185
  const path = volley_path(workspace, "iterations", dir, "summary.json");
2149
- if (!existsSync9(path)) continue;
2186
+ if (!existsSync10(path)) continue;
2150
2187
  try {
2151
- archives.push(JSON.parse(readFileSync9(path, "utf8")));
2188
+ archives.push(JSON.parse(readFileSync10(path, "utf8")));
2152
2189
  } catch {
2153
2190
  }
2154
2191
  }
@@ -2467,7 +2504,7 @@ async function run_volley(config, deps, resume_from = null) {
2467
2504
  async function run_loop(config, deps, resume_from, bash_executor, baseline) {
2468
2505
  const { renderer } = deps;
2469
2506
  const engine = deps.engine ?? create_volley_engine({
2470
- workspace: config.workspace,
2507
+ workspace: build_root(config.workspace, config.worktree),
2471
2508
  builder_provider: config.builder_provider,
2472
2509
  critic_provider: config.critic_provider
2473
2510
  });
@@ -2501,7 +2538,7 @@ function finalize_failed_run(config, err) {
2501
2538
  const status = kind === "aborted_error" || cause === "aborted_error" ? "interrupted" : "error";
2502
2539
  try {
2503
2540
  const summary_path = volley_path(config.workspace, "summary.json");
2504
- const prior = existsSync10(summary_path) ? JSON.parse(readFileSync10(summary_path, "utf8")) : run_result_from_state(config, initial_state(), status, null);
2541
+ const prior = existsSync11(summary_path) ? JSON.parse(readFileSync11(summary_path, "utf8")) : run_result_from_state(config, initial_state(), status, null);
2505
2542
  write_run_summary(config, {
2506
2543
  ...prior,
2507
2544
  status,
@@ -2679,6 +2716,82 @@ function summarize_output(output) {
2679
2716
  }
2680
2717
  }
2681
2718
 
2719
+ // src/sandbox.ts
2720
+ import { resolve as resolve4 } from "path";
2721
+ var SANDBOX_WORKDIR = "/workspace";
2722
+ var SANDBOX_HOME = "/home/node";
2723
+ var HOST_GATEWAY_HOST = "host.docker.internal";
2724
+ var SANDBOX_NETWORK_NAME = "volley-sandbox-net";
2725
+ var SANDBOX_NETWORK_SUBNET = "172.31.99.0/24";
2726
+ function network_run_args(network, network_name) {
2727
+ if (network === "none") return ["--network", "none"];
2728
+ return ["--network", network_name, "--add-host", `${HOST_GATEWAY_HOST}:host-gateway`];
2729
+ }
2730
+ function sandbox_run_args(options) {
2731
+ if (options.uid === 0) {
2732
+ throw config_error("refusing to start the builder sandbox as root (uid 0)");
2733
+ }
2734
+ const user = options.uid !== null && options.gid !== null ? ["--user", `${String(options.uid)}:${String(options.gid)}`] : [];
2735
+ const name = options.name !== void 0 && options.name !== null ? ["--name", options.name] : [];
2736
+ const entrypoint = options.entrypoint !== void 0 && options.entrypoint !== null ? ["--entrypoint", options.entrypoint] : [];
2737
+ const env = (options.env ?? []).flatMap(([key, value]) => ["-e", `${key}=${value}`]);
2738
+ const labels = (options.labels ?? []).flatMap(([key, value]) => ["--label", `${key}=${value}`]);
2739
+ const store = options.store_volume !== void 0 && options.store_volume !== null ? ["-v", `${options.store_volume}:${SANDBOX_HOME}/.local/share/pnpm/store`] : [];
2740
+ return [
2741
+ "run",
2742
+ // One-shot, foreground container: the whole run *is* this container, so
2743
+ // remove it when volley exits.
2744
+ "--rm",
2745
+ ...name,
2746
+ ...labels,
2747
+ ...user,
2748
+ // Network policy: default-deny egress. `'none'` gives the container
2749
+ // no interface; `'allowlist'` puts it on the user-defined bridge and collapses
2750
+ // the allowlist targets onto the host gateway.
2751
+ ...network_run_args(options.network, options.network_name),
2752
+ // Resource caps: contain a runaway / fork-bomb / OOM without starving
2753
+ // pnpm/tsc (pids 1024, not 100; nofile high enough for esbuild/watchers).
2754
+ "--memory=4g",
2755
+ "--memory-swap=4g",
2756
+ "--memory-swappiness=0",
2757
+ "--cpus=2",
2758
+ "--pids-limit=1024",
2759
+ "--ulimit",
2760
+ "nofile=8192:16384",
2761
+ "--ulimit",
2762
+ "core=0",
2763
+ // Privilege caps: drop every capability, forbid privilege
2764
+ // escalation, keep the default seccomp profile, and run tini (PID 1) to reap
2765
+ // zombies. Never `--privileged`.
2766
+ "--cap-drop=ALL",
2767
+ "--security-opt",
2768
+ "no-new-privileges",
2769
+ "--init",
2770
+ // Read-only rootfs with tmpfs for the few writable paths the toolchain
2771
+ // needs; a pnpm store, when mounted, persists on a named volume.
2772
+ "--read-only",
2773
+ "--tmpfs",
2774
+ "/tmp",
2775
+ "--tmpfs",
2776
+ "/run",
2777
+ "--tmpfs",
2778
+ `${SANDBOX_HOME}/.cache`,
2779
+ "-e",
2780
+ `HOME=${SANDBOX_HOME}`,
2781
+ ...env,
2782
+ // The bind-mounted worktree (writable — a bind mount is exempt from
2783
+ // `--read-only`) and, when one is named, the persistent pnpm store.
2784
+ "-v",
2785
+ `${resolve4(options.build_root)}:${SANDBOX_WORKDIR}`,
2786
+ ...store,
2787
+ "-w",
2788
+ SANDBOX_WORKDIR,
2789
+ ...entrypoint,
2790
+ options.image,
2791
+ ...options.command ?? []
2792
+ ];
2793
+ }
2794
+
2682
2795
  // src/exit_codes.ts
2683
2796
  var EXIT_SUCCESS = 0;
2684
2797
  var EXIT_BUDGET_EXHAUSTED = 2;
@@ -2745,6 +2858,8 @@ export {
2745
2858
  EXIT_CRITIC_ERROR,
2746
2859
  EXIT_INTERRUPTED,
2747
2860
  EXIT_SUCCESS,
2861
+ SANDBOX_NETWORK_NAME,
2862
+ SANDBOX_NETWORK_SUBNET,
2748
2863
  create_renderer,
2749
2864
  exit_code_for_error,
2750
2865
  exit_code_for_status,
@@ -2752,8 +2867,10 @@ export {
2752
2867
  gate,
2753
2868
  initial_state,
2754
2869
  load_config_file,
2870
+ network_run_args,
2755
2871
  resolve_config,
2756
2872
  run_volley,
2873
+ sandbox_run_args,
2757
2874
  status_of,
2758
2875
  verdict_schema
2759
2876
  };