@tangle-network/traces 0.11.4 → 0.11.5

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.
@@ -766,8 +766,8 @@ function serializeSpans(spans) {
766
766
  async function writeOtlpFile(spans, outPath) {
767
767
  const { mkdtemp: mkdtemp8, writeFile: writeFile10 } = await import("fs/promises");
768
768
  const { tmpdir: tmpdir9 } = await import("os");
769
- const { join: join33 } = await import("path");
770
- const path = outPath ?? join33(await mkdtemp8(join33(tmpdir9(), "traces-")), "spans.otlp.jsonl");
769
+ const { join: join34 } = await import("path");
770
+ const path = outPath ?? join34(await mkdtemp8(join34(tmpdir9(), "traces-")), "spans.otlp.jsonl");
771
771
  await writeFile10(path, serializeSpans(spans), "utf8");
772
772
  return path;
773
773
  }
@@ -7419,7 +7419,7 @@ function zaiChatCaller(options) {
7419
7419
  }
7420
7420
  };
7421
7421
  }
7422
- function clip(text2, limit) {
7422
+ function clipText(text2, limit) {
7423
7423
  if (text2.length <= limit) return text2;
7424
7424
  const half = Math.floor(limit / 2);
7425
7425
  return `${text2.slice(0, half)}
@@ -7428,11 +7428,11 @@ ${text2.slice(-half)}`;
7428
7428
  }
7429
7429
  function renderStep(step, marker) {
7430
7430
  const rc = parseRecordedReturncode(step.observation);
7431
- const output = clip(parseObservationOutput(step.observation).trim(), 1600);
7431
+ const output = clipText(parseObservationOutput(step.observation).trim(), 1600);
7432
7432
  return [
7433
7433
  `### step ${step.step_id}${marker}`,
7434
7434
  "```sh",
7435
- clip(step.action, 2e3),
7435
+ clipText(step.action, 2e3),
7436
7436
  "```",
7437
7437
  `returncode: ${rc ?? "none recorded"}`,
7438
7438
  output.length > 0 ? `output:
@@ -7441,13 +7441,25 @@ ${output}
7441
7441
  \`\`\`` : "output: (empty)"
7442
7442
  ].join("\n");
7443
7443
  }
7444
- function buildFixPrompt(input) {
7444
+ function promptBody(input) {
7445
7445
  const radius = input.contextRadius ?? 3;
7446
7446
  const target = input.steps.find((s) => s.step_id === input.k);
7447
7447
  if (!target) throw new Error(`replay-fix: no step with step_id ${input.k}`);
7448
7448
  const context = input.steps.filter(
7449
7449
  (s) => s.step_id !== input.k && Math.abs(s.step_id - input.k) <= radius
7450
7450
  );
7451
+ return [
7452
+ "## Task the agent was solving",
7453
+ input.taskStatement ? clipText(input.taskStatement, 4e3) : "(no task statement recorded)",
7454
+ "",
7455
+ "## Surrounding steps",
7456
+ ...context.map((s) => renderStep(s, "")),
7457
+ "",
7458
+ "## Failing step to correct",
7459
+ renderStep(target, " (INCORRECT \u2014 correct this one)")
7460
+ ];
7461
+ }
7462
+ function buildFixPrompt(input) {
7451
7463
  const system = [
7452
7464
  "You repair one failed shell command from a recorded coding-agent trajectory.",
7453
7465
  "The trajectory replays inside the original docker image; every command runs as a fresh /bin/sh subshell from a fixed working directory.",
@@ -7456,19 +7468,62 @@ function buildFixPrompt(input) {
7456
7468
  "The corrected command must accomplish the failing step's intent and exit 0. No prose outside the fenced block."
7457
7469
  ].join("\n");
7458
7470
  const user = [
7459
- "## Task the agent was solving",
7460
- input.taskStatement ? clip(input.taskStatement, 4e3) : "(no task statement recorded)",
7471
+ ...promptBody(input),
7461
7472
  "",
7462
- "## Surrounding steps",
7463
- ...context.map((s) => renderStep(s, "")),
7473
+ "Output the single corrected replacement for the failing step now."
7474
+ ].join("\n");
7475
+ return { system, user };
7476
+ }
7477
+ function renderFailedAttempt(prior) {
7478
+ if (prior.command === null) {
7479
+ return [
7480
+ `### attempt ${prior.attempt}`,
7481
+ `model call failed before producing a command: ${prior.llmError ?? "unknown error"}`
7482
+ ].join("\n");
7483
+ }
7484
+ const stdout = (prior.stdoutTail ?? "").trim();
7485
+ const stderr = (prior.stderrTail ?? "").trim();
7486
+ return [
7487
+ `### attempt ${prior.attempt}`,
7488
+ "```sh",
7489
+ clipText(prior.command, 2e3),
7490
+ "```",
7491
+ `exit code: ${prior.exitCode ?? "not executed"}`,
7492
+ stdout.length > 0 ? `stdout:
7493
+ \`\`\`
7494
+ ${stdout}
7495
+ \`\`\`` : "stdout: (empty)",
7496
+ stderr.length > 0 ? `stderr:
7497
+ \`\`\`
7498
+ ${stderr}
7499
+ \`\`\`` : "stderr: (empty)"
7500
+ ].join("\n");
7501
+ }
7502
+ function buildRetryFixPrompt(input, priorAttempts, maxScriptCommands = 5) {
7503
+ if (priorAttempts.length === 0) {
7504
+ throw new Error("replay-fix: buildRetryFixPrompt requires at least one prior attempt");
7505
+ }
7506
+ const system = [
7507
+ "You repair one failed shell command from a recorded coding-agent trajectory.",
7508
+ "The trajectory replays inside the original docker image; every command runs as a fresh /bin/sh subshell from a fixed working directory.",
7509
+ "Earlier corrected commands were executed for real and failed; their actual output is included below.",
7510
+ `Reply with a corrected fix inside a single \`\`\`sh fenced block: either one command, or a short script of at most ${maxScriptCommands} commands (one per line).`,
7511
+ "The whole block executes as ONE /bin/sh unit from the fixed working directory and must exit 0.",
7512
+ "Do not repeat a command that already failed. Keep reasoning brief. No prose outside the fenced block."
7513
+ ].join("\n");
7514
+ const user = [
7515
+ ...promptBody(input),
7464
7516
  "",
7465
- "## Failing step to correct",
7466
- renderStep(target, " (INCORRECT \u2014 correct this one)"),
7517
+ "## Previous fix attempts (executed for real \u2014 all failed)",
7518
+ ...priorAttempts.map((prior) => renderFailedAttempt(prior)),
7467
7519
  "",
7468
- "Output the single corrected replacement for the failing step now."
7520
+ `Output a corrected fix now \u2014 one \`\`\`sh block, at most ${maxScriptCommands} commands.`
7469
7521
  ].join("\n");
7470
7522
  return { system, user };
7471
7523
  }
7524
+ function countScriptCommands(script) {
7525
+ return script.split("\n").filter((line) => line.trim().length > 0 && !line.trim().startsWith("#")).length;
7526
+ }
7472
7527
  function extractFixCommand(content) {
7473
7528
  const blocks = [...content.matchAll(/```(?:sh|bash|shell)?\n([\s\S]*?)```/g)];
7474
7529
  const candidate = blocks.length > 0 ? blocks[blocks.length - 1][1] : content;
@@ -7484,12 +7539,167 @@ async function generateFixCommand(caller, input) {
7484
7539
  if (command === null) {
7485
7540
  return {
7486
7541
  succeeded: false,
7487
- error: `completion carried no usable command: ${clip(outcome.value.content, 300)}`
7542
+ error: `completion carried no usable command: ${clipText(outcome.value.content, 300)}`
7488
7543
  };
7489
7544
  }
7490
7545
  return { succeeded: true, value: { command, usage: outcome.value.usage } };
7491
7546
  }
7492
7547
 
7548
+ // src/replay-fix-loop.ts
7549
+ function toFailedAttempt(record2) {
7550
+ return {
7551
+ attempt: record2.attempt,
7552
+ command: record2.command,
7553
+ exitCode: record2.exitCode,
7554
+ stdoutTail: record2.stdoutTail,
7555
+ stderrTail: record2.stderrTail,
7556
+ llmError: record2.llmError ?? record2.armBError
7557
+ };
7558
+ }
7559
+ async function runFixLoop(caller, input, executor, options) {
7560
+ if (!Number.isInteger(options.maxAttempts) || options.maxAttempts < 1) {
7561
+ throw new Error(
7562
+ `replay-fix-loop: maxAttempts must be a positive integer, got ${options.maxAttempts}`
7563
+ );
7564
+ }
7565
+ const scriptCap = options.maxScriptCommands ?? 5;
7566
+ const tailChars = options.outputTailChars ?? 1600;
7567
+ const attempts = [];
7568
+ let llmCalls = 0;
7569
+ let llmFailures = 0;
7570
+ let promptTokens = 0;
7571
+ let completionTokens = 0;
7572
+ const unexecuted = (attempt, llmError, usage, command, started) => ({
7573
+ attempt,
7574
+ command,
7575
+ llmError,
7576
+ usage,
7577
+ executed: false,
7578
+ exitCode: null,
7579
+ prefixExecuted: null,
7580
+ prefixDivergences: null,
7581
+ failureVanished: null,
7582
+ stdoutTail: null,
7583
+ stderrTail: null,
7584
+ armBError: null,
7585
+ wallMs: Date.now() - started
7586
+ });
7587
+ for (let attempt = 1; attempt <= options.maxAttempts; attempt++) {
7588
+ const started = Date.now();
7589
+ const prompt = attempt === 1 ? buildFixPrompt(input) : buildRetryFixPrompt(input, attempts.map(toFailedAttempt), scriptCap);
7590
+ llmCalls += 1;
7591
+ const outcome = await caller.complete(prompt.system, prompt.user);
7592
+ if (!outcome.succeeded) {
7593
+ llmFailures += 1;
7594
+ attempts.push(unexecuted(attempt, outcome.error, null, null, started));
7595
+ options.onProgress?.(
7596
+ `fix-loop attempt ${attempt}: LLM failed \u2014 ${outcome.error.slice(0, 160)}`
7597
+ );
7598
+ continue;
7599
+ }
7600
+ const usage = outcome.value.usage;
7601
+ promptTokens += usage?.promptTokens ?? 0;
7602
+ completionTokens += usage?.completionTokens ?? 0;
7603
+ const command = extractFixCommand(outcome.value.content);
7604
+ if (command === null) {
7605
+ llmFailures += 1;
7606
+ attempts.push(
7607
+ unexecuted(
7608
+ attempt,
7609
+ `completion carried no usable command: ${clipText(outcome.value.content, 300)}`,
7610
+ usage,
7611
+ null,
7612
+ started
7613
+ )
7614
+ );
7615
+ continue;
7616
+ }
7617
+ if (attempt > 1) {
7618
+ const commandLines = countScriptCommands(command);
7619
+ if (commandLines > scriptCap) {
7620
+ llmFailures += 1;
7621
+ attempts.push(
7622
+ unexecuted(
7623
+ attempt,
7624
+ `script exceeds ${scriptCap} command lines (${commandLines})`,
7625
+ usage,
7626
+ null,
7627
+ started
7628
+ )
7629
+ );
7630
+ options.onProgress?.(
7631
+ `fix-loop attempt ${attempt}: rejected script with ${commandLines} command lines`
7632
+ );
7633
+ continue;
7634
+ }
7635
+ }
7636
+ let execution;
7637
+ try {
7638
+ execution = await executor(command, attempt);
7639
+ } catch (err) {
7640
+ const message = err instanceof Error ? err.message : String(err);
7641
+ attempts.push({
7642
+ ...unexecuted(attempt, "", usage, command, started),
7643
+ llmError: null,
7644
+ armBError: message.slice(0, 500)
7645
+ });
7646
+ options.onProgress?.(
7647
+ `fix-loop attempt ${attempt}: sandbox error \u2014 ${message.slice(0, 160)}`
7648
+ );
7649
+ return {
7650
+ flipped: false,
7651
+ flippedAtAttempt: null,
7652
+ attempts,
7653
+ aborted: true,
7654
+ llmCalls,
7655
+ llmFailures,
7656
+ promptTokens,
7657
+ completionTokens
7658
+ };
7659
+ }
7660
+ attempts.push({
7661
+ attempt,
7662
+ command,
7663
+ llmError: null,
7664
+ usage,
7665
+ executed: true,
7666
+ exitCode: execution.exitCode,
7667
+ prefixExecuted: execution.prefixExecuted,
7668
+ prefixDivergences: execution.prefixDivergences,
7669
+ failureVanished: execution.failureVanished,
7670
+ stdoutTail: clipText(execution.stdout, tailChars),
7671
+ stderrTail: clipText(execution.stderr, tailChars),
7672
+ armBError: null,
7673
+ wallMs: Date.now() - started
7674
+ });
7675
+ options.onProgress?.(
7676
+ `fix-loop attempt ${attempt}: exit=${execution.exitCode} failureVanished=${execution.failureVanished}`
7677
+ );
7678
+ if (execution.failureVanished) {
7679
+ return {
7680
+ flipped: true,
7681
+ flippedAtAttempt: attempt,
7682
+ attempts,
7683
+ aborted: false,
7684
+ llmCalls,
7685
+ llmFailures,
7686
+ promptTokens,
7687
+ completionTokens
7688
+ };
7689
+ }
7690
+ }
7691
+ return {
7692
+ flipped: false,
7693
+ flippedAtAttempt: null,
7694
+ attempts,
7695
+ aborted: false,
7696
+ llmCalls,
7697
+ llmFailures,
7698
+ promptTokens,
7699
+ completionTokens
7700
+ };
7701
+ }
7702
+
7493
7703
  // src/replay-batch.ts
7494
7704
  import { execFile } from "child_process";
7495
7705
  import { createHash as createHash8 } from "crypto";
@@ -7778,9 +7988,10 @@ async function runReplayBatch(options) {
7778
7988
  }
7779
7989
  }
7780
7990
  let llm = null;
7781
- if (options.fix === "generate") {
7991
+ if (options.fix !== "none") {
7782
7992
  const caller = options.fixCaller;
7783
- if (!caller) throw new Error("replay-verify-batch: fix=generate requires a fixCaller");
7993
+ if (!caller) throw new Error(`replay-verify-batch: fix=${options.fix} requires a fixCaller`);
7994
+ const fixAttempts = options.fixAttempts ?? 3;
7784
7995
  const maxFixCases = options.maxFixCases ?? 30;
7785
7996
  const seed = options.seed ?? 17;
7786
7997
  const eligible = rows.filter((r) => r.status === "ok" && r.replayed);
@@ -7809,20 +8020,115 @@ async function runReplayBatch(options) {
7809
8020
  armBPrefixExecuted: null,
7810
8021
  armBPrefixDivergences: null,
7811
8022
  failureVanished: null,
7812
- armBError: null
8023
+ armBError: null,
8024
+ attempts: null,
8025
+ flippedAtAttempt: null
7813
8026
  }
7814
8027
  };
7815
8028
  continue;
7816
8029
  }
7817
8030
  const replayCase = selected.find((c) => c.trajId === row.trajId && c.corpus === row.corpus);
7818
8031
  const label = `fix ${row.corpus}/${row.trajId}`;
7819
- onProgress(`${label}: generating corrected command`);
7820
- calls += 1;
7821
- const generated = await generateFixCommand(caller, {
8032
+ const signature = verdictByTraj.get(row.trajId)?.signature ?? null;
8033
+ const stepTimeoutMs = options.stepTimeoutMs ?? replayCase.recordedStepTimeoutMs ?? 12e4;
8034
+ const promptInput = {
7822
8035
  taskStatement: replayCase.taskStatement,
7823
8036
  steps: replayCase.steps,
7824
8037
  k: replayCase.k
7825
- });
8038
+ };
8039
+ const caseOut = join21(options.out, caseOutDirName(row));
8040
+ if (options.fix === "loop") {
8041
+ onProgress(`${label}: fix loop (budget ${fixAttempts} attempts)`);
8042
+ const result = await runFixLoop(
8043
+ caller,
8044
+ promptInput,
8045
+ async (command, attempt) => {
8046
+ onProgress(
8047
+ `${label}: attempt ${attempt} arm B \u2014 ${command.split("\n")[0].slice(0, 120)}`
8048
+ );
8049
+ const armB = await executeArmB(
8050
+ replayCase,
8051
+ command,
8052
+ backendFactory(row.derivedImage),
8053
+ signature,
8054
+ stepTimeoutMs,
8055
+ options.prefixLimit,
8056
+ (message) => onProgress(`${label}: attempt ${attempt}: ${message}`)
8057
+ );
8058
+ mkdirSync2(caseOut, { recursive: true });
8059
+ writeFileSync2(
8060
+ join21(caseOut, `armB-attempt${attempt}-result.json`),
8061
+ `${JSON.stringify({ command, ...armB }, null, 2)}
8062
+ `
8063
+ );
8064
+ writeFileSync2(
8065
+ join21(caseOut, "armB-result.json"),
8066
+ `${JSON.stringify({ command, attempt, ...armB }, null, 2)}
8067
+ `
8068
+ );
8069
+ return armB;
8070
+ },
8071
+ { maxAttempts: fixAttempts, onProgress: (message) => onProgress(`${label}: ${message}`) }
8072
+ );
8073
+ calls += result.llmCalls;
8074
+ failures += result.llmFailures;
8075
+ promptTokens += result.promptTokens;
8076
+ completionTokens += result.completionTokens;
8077
+ const usage = result.promptTokens + result.completionTokens > 0 ? { promptTokens: result.promptTokens, completionTokens: result.completionTokens } : null;
8078
+ const flippedRecord = result.flippedAtAttempt !== null ? result.attempts.find((a) => a.attempt === result.flippedAtAttempt) : null;
8079
+ const summary = flippedRecord ?? [...result.attempts].reverse().find((a) => a.executed) ?? null;
8080
+ const lastRecord = result.attempts.at(-1) ?? null;
8081
+ rows[index] = {
8082
+ ...row,
8083
+ fix: summary ? {
8084
+ attempted: true,
8085
+ sampledOut: false,
8086
+ command: summary.command,
8087
+ llmError: null,
8088
+ usage,
8089
+ armBExit: summary.exitCode,
8090
+ armBPrefixExecuted: summary.prefixExecuted,
8091
+ armBPrefixDivergences: summary.prefixDivergences,
8092
+ failureVanished: summary.failureVanished,
8093
+ armBError: null,
8094
+ attempts: result.attempts,
8095
+ flippedAtAttempt: result.flippedAtAttempt
8096
+ } : result.aborted ? {
8097
+ attempted: true,
8098
+ sampledOut: false,
8099
+ command: lastRecord?.command ?? null,
8100
+ llmError: null,
8101
+ usage,
8102
+ armBExit: null,
8103
+ armBPrefixExecuted: null,
8104
+ armBPrefixDivergences: null,
8105
+ failureVanished: null,
8106
+ armBError: lastRecord?.armBError ?? "sandbox error",
8107
+ attempts: result.attempts,
8108
+ flippedAtAttempt: null
8109
+ } : {
8110
+ attempted: true,
8111
+ sampledOut: false,
8112
+ command: null,
8113
+ llmError: lastRecord?.llmError ?? "no attempt produced a runnable fix",
8114
+ usage,
8115
+ armBExit: null,
8116
+ armBPrefixExecuted: null,
8117
+ armBPrefixDivergences: null,
8118
+ failureVanished: null,
8119
+ armBError: null,
8120
+ attempts: result.attempts,
8121
+ flippedAtAttempt: null
8122
+ }
8123
+ };
8124
+ onProgress(
8125
+ `${label}: loop done \u2014 flipped=${result.flipped}` + (result.flippedAtAttempt !== null ? ` at attempt ${result.flippedAtAttempt}` : "") + ` (${result.llmCalls} calls, ${result.attempts.filter((a) => a.executed).length} arms)`
8126
+ );
8127
+ continue;
8128
+ }
8129
+ onProgress(`${label}: generating corrected command`);
8130
+ calls += 1;
8131
+ const generated = await generateFixCommand(caller, promptInput);
7826
8132
  if (!generated.succeeded) {
7827
8133
  failures += 1;
7828
8134
  rows[index] = {
@@ -7837,7 +8143,9 @@ async function runReplayBatch(options) {
7837
8143
  armBPrefixExecuted: null,
7838
8144
  armBPrefixDivergences: null,
7839
8145
  failureVanished: null,
7840
- armBError: null
8146
+ armBError: null,
8147
+ attempts: null,
8148
+ flippedAtAttempt: null
7841
8149
  }
7842
8150
  };
7843
8151
  onProgress(`${label}: LLM failed \u2014 ${generated.error.slice(0, 200)}`);
@@ -7845,8 +8153,6 @@ async function runReplayBatch(options) {
7845
8153
  }
7846
8154
  promptTokens += generated.value.usage?.promptTokens ?? 0;
7847
8155
  completionTokens += generated.value.usage?.completionTokens ?? 0;
7848
- const signature = verdictByTraj.get(row.trajId)?.signature ?? null;
7849
- const stepTimeoutMs = options.stepTimeoutMs ?? replayCase.recordedStepTimeoutMs ?? 12e4;
7850
8156
  onProgress(`${label}: arm B \u2014 ${generated.value.command.split("\n")[0].slice(0, 120)}`);
7851
8157
  try {
7852
8158
  const armB = await executeArmB(
@@ -7870,10 +8176,11 @@ async function runReplayBatch(options) {
7870
8176
  armBPrefixExecuted: armB.prefixExecuted,
7871
8177
  armBPrefixDivergences: armB.prefixDivergences,
7872
8178
  failureVanished: armB.failureVanished,
7873
- armBError: null
8179
+ armBError: null,
8180
+ attempts: null,
8181
+ flippedAtAttempt: armB.failureVanished ? 1 : null
7874
8182
  }
7875
8183
  };
7876
- const caseOut = join21(options.out, caseOutDirName(row));
7877
8184
  mkdirSync2(caseOut, { recursive: true });
7878
8185
  writeFileSync2(
7879
8186
  join21(caseOut, "armB-result.json"),
@@ -7895,7 +8202,9 @@ async function runReplayBatch(options) {
7895
8202
  armBPrefixExecuted: null,
7896
8203
  armBPrefixDivergences: null,
7897
8204
  failureVanished: null,
7898
- armBError: message.slice(0, 500)
8205
+ armBError: message.slice(0, 500),
8206
+ attempts: null,
8207
+ flippedAtAttempt: null
7899
8208
  }
7900
8209
  };
7901
8210
  onProgress(`${label}: arm B error \u2014 ${message.slice(0, 200)}`);
@@ -7920,6 +8229,15 @@ async function runReplayBatch(options) {
7920
8229
  (r) => r.recordedReturncodeAtK !== null && r.recordedReturncodeAtK !== 0
7921
8230
  );
7922
8231
  const flippedNonzeroRc = armBNonzeroRc.filter((r) => r.fix.failureVanished === true);
8232
+ const attempt1Executed = rows.filter(
8233
+ (r) => r.fix?.attempts?.find((a) => a.attempt === 1)?.executed === true
8234
+ );
8235
+ const flippedAt1 = rows.filter((r) => r.fix?.flippedAtAttempt === 1);
8236
+ const flipsByAttempt = {};
8237
+ for (const row of rows) {
8238
+ const at = row.fix?.flippedAtAttempt;
8239
+ if (typeof at === "number") flipsByAttempt[String(at)] = (flipsByAttempt[String(at)] ?? 0) + 1;
8240
+ }
7923
8241
  const excludedByReason = {};
7924
8242
  for (const excluded of enumeration.excluded) {
7925
8243
  excludedByReason[excluded.reason] = (excludedByReason[excluded.reason] ?? 0) + 1;
@@ -7959,16 +8277,22 @@ async function runReplayBatch(options) {
7959
8277
  denominator: selected.length,
7960
8278
  value: rate(signatureStrict.length, selected.length)
7961
8279
  },
7962
- fixFlipRate: options.fix === "generate" ? {
8280
+ fixFlipRate: options.fix !== "none" ? {
7963
8281
  numerator: flipped.length,
7964
8282
  denominator: armBExecuted.length,
7965
8283
  value: rate(flipped.length, armBExecuted.length)
7966
8284
  } : null,
7967
- fixFlipRateNonzeroRc: options.fix === "generate" ? {
8285
+ fixFlipRateNonzeroRc: options.fix !== "none" ? {
7968
8286
  numerator: flippedNonzeroRc.length,
7969
8287
  denominator: armBNonzeroRc.length,
7970
8288
  value: rate(flippedNonzeroRc.length, armBNonzeroRc.length)
7971
- } : null
8289
+ } : null,
8290
+ fixFlipAttempt1: options.fix === "loop" ? {
8291
+ numerator: flippedAt1.length,
8292
+ denominator: attempt1Executed.length,
8293
+ value: rate(flippedAt1.length, attempt1Executed.length)
8294
+ } : null,
8295
+ flipsByAttempt: options.fix === "loop" ? flipsByAttempt : null
7972
8296
  },
7973
8297
  llm,
7974
8298
  excluded: enumeration.excluded,
@@ -8012,6 +8336,15 @@ function renderBatchReport(report) {
8012
8336
  `- Fix-flip rate on recorded-rc\u22600 cases: ${pct(headline.fixFlipRateNonzeroRc.value)} (${headline.fixFlipRateNonzeroRc.numerator}/${headline.fixFlipRateNonzeroRc.denominator}; real recorded failures \u2014 a gold step recorded with rc 0 flips vacuously).`
8013
8337
  );
8014
8338
  }
8339
+ if (headline.fixFlipAttempt1) {
8340
+ lines.push(
8341
+ `- Fix-flip@1: ${pct(headline.fixFlipAttempt1.value)} (${headline.fixFlipAttempt1.numerator}/${headline.fixFlipAttempt1.denominator} cases whose attempt 1 executed \u2014 the one-shot-comparable number).`
8342
+ );
8343
+ }
8344
+ if (headline.flipsByAttempt && Object.keys(headline.flipsByAttempt).length > 0) {
8345
+ const parts = Object.entries(headline.flipsByAttempt).sort((a, b) => Number(a[0]) - Number(b[0])).map(([attempt, count2]) => `attempt ${attempt}: ${count2}`);
8346
+ lines.push(`- Flips by attempt: ${parts.join(", ")}.`);
8347
+ }
8015
8348
  lines.push("");
8016
8349
  lines.push("## Enumeration");
8017
8350
  lines.push("");
@@ -8060,7 +8393,7 @@ function renderBatchReport(report) {
8060
8393
  );
8061
8394
  for (const row of report.cases) {
8062
8395
  const fix = row.fix;
8063
- const fixCell = !fix ? "\u2014" : fix.sampledOut ? "sampled-out" : fix.llmError ? "llm-failed" : fix.armBError ? "armB-error" : "generated";
8396
+ const fixCell = !fix ? "\u2014" : fix.sampledOut ? "sampled-out" : fix.llmError ? "llm-failed" : fix.armBError ? "armB-error" : fix.attempts ? fix.flippedAtAttempt !== null ? `flip@${fix.flippedAtAttempt}` : `exhausted(${fix.attempts.length})` : "generated";
8064
8397
  lines.push(
8065
8398
  [
8066
8399
  row.corpus,
@@ -8122,13 +8455,20 @@ function parseReplayBatchArgs(argv) {
8122
8455
  return n;
8123
8456
  };
8124
8457
  const fix = values.get("--fix") ?? "none";
8125
- if (fix !== "none" && fix !== "generate") {
8126
- throw new Error(`replay-verify-batch: --fix must be none or generate, got ${fix}`);
8458
+ if (fix !== "none" && fix !== "generate" && fix !== "loop") {
8459
+ throw new Error(`replay-verify-batch: --fix must be none, generate, or loop, got ${fix}`);
8460
+ }
8461
+ const fixAttempts = optionalNumber("--fix-attempts") ?? 3;
8462
+ if (!Number.isInteger(fixAttempts) || fixAttempts < 1) {
8463
+ throw new Error(
8464
+ `replay-verify-batch: --fix-attempts must be a positive integer, got ${values.get("--fix-attempts")}`
8465
+ );
8127
8466
  }
8128
8467
  return {
8129
8468
  corpora,
8130
8469
  out: out ?? ".",
8131
8470
  fix,
8471
+ fixAttempts,
8132
8472
  fixModel: values.get("--fix-model") ?? "glm-5.2",
8133
8473
  fixBaseUrl: values.get("--fix-base-url") ?? "https://api.z.ai/api/coding/paas/v4",
8134
8474
  fixApiKeyEnv: values.get("--fix-api-key-env") ?? "ZAI_GLM_API_KEY",
@@ -8150,7 +8490,7 @@ function replayBatchUsage() {
8150
8490
  Usage:
8151
8491
  traces replay-verify-batch \\
8152
8492
  --corpus NAME=<labels.json>::<prepared-dir> [--corpus ...] \\
8153
- --out DIR [--fix none|generate] [--enumerate-only] \\
8493
+ --out DIR [--fix none|generate|loop] [--fix-attempts 3] [--enumerate-only] \\
8154
8494
  [--fix-model glm-5.2] [--fix-base-url URL] [--fix-api-key-env ZAI_GLM_API_KEY] \\
8155
8495
  [--max-fix-cases 30] [--seed 17] [--step-timeout MS] [--prefix-limit N] \\
8156
8496
  [--case-filter SUBSTRING] [--case-limit N] \\
@@ -8169,6 +8509,13 @@ Usage:
8169
8509
  --max-fix-cases, seeded sample beyond it) and executes the corrected command
8170
8510
  as arm B in a fresh sandbox.
8171
8511
 
8512
+ --fix loop iterates: attempt 1 is the one-shot prompt; when an arm fails
8513
+ (nonzero exit or the failure signature persists) or the model call fails,
8514
+ the next prompt carries every prior command with its REAL executed
8515
+ stdout/stderr, up to --fix-attempts attempts (default 3). Retries may
8516
+ answer with a short script (<=5 commands) executed as one /bin/sh unit.
8517
+ Every attempt runs in its own fresh sandbox with the same replayed prefix.
8518
+
8172
8519
  Outputs batch-report.json, batch-report.md, cases.jsonl (incremental), and one
8173
8520
  directory per case with the full replay-verify artifacts.
8174
8521
  See docs/replay-verify.md for orchestrator setup and honest limits.
@@ -8213,7 +8560,7 @@ async function cmdReplayVerifyBatch(argv) {
8213
8560
  );
8214
8561
  }
8215
8562
  let fixCaller;
8216
- if (parsed.fix === "generate") {
8563
+ if (parsed.fix !== "none") {
8217
8564
  const fixApiKey = process.env[parsed.fixApiKeyEnv];
8218
8565
  if (!fixApiKey) {
8219
8566
  throw new Error(
@@ -8232,6 +8579,7 @@ async function cmdReplayVerifyBatch(argv) {
8232
8579
  baseUrl: parsed.baseUrl,
8233
8580
  apiKey,
8234
8581
  fix: parsed.fix,
8582
+ fixAttempts: parsed.fixAttempts,
8235
8583
  fixCaller,
8236
8584
  fixModelLabel: parsed.fixModel,
8237
8585
  maxFixCases: parsed.maxFixCases,
@@ -8251,6 +8599,573 @@ async function cmdReplayVerifyBatch(argv) {
8251
8599
  );
8252
8600
  }
8253
8601
 
8602
+ // src/replay-wire.ts
8603
+ var SUBJECT_PATTERN = /^incorrect-steps-(\d+)-(\d+)-(escaped|unescaped)-consequence-(\d+)$/;
8604
+ function parseIncorrectStepsSubject(subject) {
8605
+ const match = SUBJECT_PATTERN.exec(subject);
8606
+ if (!match) return null;
8607
+ return {
8608
+ firstStep: Number(match[1]),
8609
+ lastStep: Number(match[2]),
8610
+ escapeStatus: match[3],
8611
+ consequenceStep: Number(match[4])
8612
+ };
8613
+ }
8614
+ function resolveFindingInvocation(finding2, corpora) {
8615
+ const subject = parseIncorrectStepsSubject(finding2.subject);
8616
+ if (!subject) {
8617
+ throw new Error(
8618
+ `replay-wire: subject '${finding2.subject}' is not incorrect-steps-<first>-<last>-<escaped|unescaped>-consequence-<step>`
8619
+ );
8620
+ }
8621
+ const failures = [];
8622
+ for (const corpus of corpora) {
8623
+ const resolution = resolveCaseResources(corpus, finding2.trajId);
8624
+ if (resolution.resolved) {
8625
+ const at = subject.firstStep;
8626
+ const step = resolution.resources.steps.find((s) => s.step_id === at);
8627
+ if (!step) {
8628
+ throw new Error(
8629
+ `replay-wire: finding step ${at} is outside ${finding2.trajId} (${resolution.resources.steps.length} steps)`
8630
+ );
8631
+ }
8632
+ return { resources: resolution.resources, subject, at };
8633
+ }
8634
+ failures.push(`${corpus.name}: ${resolution.reason}`);
8635
+ }
8636
+ throw new Error(
8637
+ `replay-wire: trajectory ${finding2.trajId} is not replayable in any corpus \u2014 ${failures.join("; ")}`
8638
+ );
8639
+ }
8640
+ async function replayVerifyFinding(finding2, options) {
8641
+ if (options.fixCaller && options.fixCommand !== void 0) {
8642
+ throw new Error("replay-wire: pass fixCaller or fixCommand, not both");
8643
+ }
8644
+ const invocation = resolveFindingInvocation(finding2, options.corpora);
8645
+ let fixCommand = options.fixCommand ?? null;
8646
+ if (options.fixCaller) {
8647
+ const generated = await generateFixCommand(options.fixCaller, {
8648
+ taskStatement: invocation.resources.taskStatement,
8649
+ steps: invocation.resources.steps,
8650
+ k: invocation.at
8651
+ });
8652
+ if (!generated.succeeded) {
8653
+ throw new Error(`replay-wire: fix generation failed \u2014 ${generated.error}`);
8654
+ }
8655
+ fixCommand = generated.value.command;
8656
+ }
8657
+ const verdict = await replayVerify({
8658
+ stepsPath: invocation.resources.stepsPath,
8659
+ image: invocation.resources.image,
8660
+ at: invocation.at,
8661
+ fixCommand: fixCommand ?? void 0,
8662
+ cwd: invocation.resources.cwd,
8663
+ out: options.out,
8664
+ caseId: invocation.resources.trajId,
8665
+ stepTimeoutMs: options.stepTimeoutMs ?? invocation.resources.recordedStepTimeoutMs ?? void 0,
8666
+ prefixLimit: options.prefixLimit,
8667
+ backend: options.backend,
8668
+ apiKey: options.apiKey,
8669
+ baseUrl: options.baseUrl,
8670
+ onProgress: options.onProgress
8671
+ });
8672
+ return { invocation, fixCommand, verdict };
8673
+ }
8674
+
8675
+ // src/analyze-verify.ts
8676
+ import { mkdirSync as mkdirSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
8677
+ import { join as join22 } from "path";
8678
+ var SINGLE_STEP_SUBJECT = /^incorrect-step-(\d+)$/;
8679
+ function findingReplayStep(finding2) {
8680
+ const fromMetadata = finding2.metadata?.block_first_step;
8681
+ if (typeof fromMetadata === "number" && Number.isInteger(fromMetadata) && fromMetadata >= 1) {
8682
+ return fromMetadata;
8683
+ }
8684
+ const subject = finding2.subject ?? "";
8685
+ const wire = parseIncorrectStepsSubject(subject);
8686
+ if (wire) return wire.firstStep;
8687
+ const single = SINGLE_STEP_SUBJECT.exec(subject);
8688
+ if (single) return Number(single[1]);
8689
+ return null;
8690
+ }
8691
+ var TRACE_EVIDENCE_URI = /^trace:\/\/([^/]+)\//;
8692
+ function findingTrajectoryId(finding2) {
8693
+ for (const ref of finding2.evidence_refs ?? []) {
8694
+ if (typeof ref.uri !== "string") continue;
8695
+ const match = TRACE_EVIDENCE_URI.exec(ref.uri);
8696
+ if (match) return match[1];
8697
+ }
8698
+ return null;
8699
+ }
8700
+ function checkStep(steps, at) {
8701
+ const step = steps.find((s) => s.step_id === at);
8702
+ if (!step) {
8703
+ return { ok: false, reason: `step ${at} is outside the trajectory (${steps.length} steps)` };
8704
+ }
8705
+ if (isSubmitAction(step.action)) {
8706
+ return {
8707
+ ok: false,
8708
+ reason: `step ${at} is the submit action \u2014 a submit decision has no executable failure to replay`
8709
+ };
8710
+ }
8711
+ const recordedReturncode = parseRecordedReturncode(step.observation);
8712
+ if (recordedReturncode === null) {
8713
+ return {
8714
+ ok: false,
8715
+ reason: `step ${at} recorded no returncode \u2014 there is no executable failure signature to reproduce`
8716
+ };
8717
+ }
8718
+ return { ok: true, recordedReturncode };
8719
+ }
8720
+ function resolveFindingReplayability(finding2, source) {
8721
+ const at = findingReplayStep(finding2);
8722
+ if (at === null) {
8723
+ return {
8724
+ replayable: false,
8725
+ reason: `subject '${finding2.subject ?? "(none)"}' names no trajectory step (expected incorrect-step-<n>, incorrect-steps-<f>-<l>-\u2026, or metadata.block_first_step)`
8726
+ };
8727
+ }
8728
+ if (source.kind === "direct") {
8729
+ const trajId2 = findingTrajectoryId(finding2);
8730
+ if (trajId2 && source.caseId && trajId2 !== source.caseId) {
8731
+ return {
8732
+ replayable: false,
8733
+ reason: `finding cites trajectory '${trajId2}' but the supplied steps are case '${source.caseId}'`
8734
+ };
8735
+ }
8736
+ let steps;
8737
+ try {
8738
+ steps = JSON.parse(readFileSync4(source.stepsPath, "utf8"));
8739
+ } catch (err) {
8740
+ throw new Error(
8741
+ `verify-findings: cannot read steps file ${source.stepsPath} \u2014 ${err instanceof Error ? err.message : String(err)}`
8742
+ );
8743
+ }
8744
+ if (!Array.isArray(steps) || steps.length === 0) {
8745
+ throw new Error(`verify-findings: ${source.stepsPath} is not a non-empty steps array`);
8746
+ }
8747
+ const step = checkStep(steps, at);
8748
+ if (!step.ok) return { replayable: false, reason: step.reason };
8749
+ return {
8750
+ replayable: true,
8751
+ resolved: {
8752
+ caseId: source.caseId ?? trajId2 ?? source.stepsPath,
8753
+ stepsPath: source.stepsPath,
8754
+ image: source.image,
8755
+ cwd: source.cwd,
8756
+ at,
8757
+ recordedReturncode: step.recordedReturncode,
8758
+ recordedStepTimeoutMs: null
8759
+ }
8760
+ };
8761
+ }
8762
+ const trajId = findingTrajectoryId(finding2);
8763
+ if (!trajId) {
8764
+ return {
8765
+ replayable: false,
8766
+ reason: "finding carries no trace://<trajectory>/ evidence ref naming its trajectory"
8767
+ };
8768
+ }
8769
+ const failures = [];
8770
+ for (const corpus of source.corpora) {
8771
+ const resolution = resolveCaseResources(corpus, trajId);
8772
+ if (!resolution.resolved) {
8773
+ failures.push(`${corpus.name}: ${resolution.reason}${resolution.detail ? ` (${resolution.detail})` : ""}`);
8774
+ continue;
8775
+ }
8776
+ const step = checkStep(resolution.resources.steps, at);
8777
+ if (!step.ok) return { replayable: false, reason: step.reason };
8778
+ return {
8779
+ replayable: true,
8780
+ resolved: {
8781
+ caseId: trajId,
8782
+ stepsPath: resolution.resources.stepsPath,
8783
+ image: resolution.resources.image,
8784
+ cwd: resolution.resources.cwd,
8785
+ at,
8786
+ recordedReturncode: step.recordedReturncode,
8787
+ recordedStepTimeoutMs: resolution.resources.recordedStepTimeoutMs
8788
+ }
8789
+ };
8790
+ }
8791
+ return {
8792
+ replayable: false,
8793
+ reason: `trajectory ${trajId} is not replayable in any corpus \u2014 ${failures.join("; ")}`
8794
+ };
8795
+ }
8796
+ function classifyVerdict(verdict) {
8797
+ if (!verdict.armA.failureSignatureMatch) return "divergent";
8798
+ if (verdict.armB?.failureVanished) return "fix-flipped";
8799
+ return "reproduced";
8800
+ }
8801
+ var DEFAULT_SANDBOX_BASE_URL = "http://127.0.0.1:4097";
8802
+ async function assertSandboxReachable(baseUrl) {
8803
+ let response;
8804
+ try {
8805
+ response = await fetch(new URL("/health", baseUrl), { signal: AbortSignal.timeout(5e3) });
8806
+ } catch (err) {
8807
+ throw new Error(
8808
+ `verify-findings: sandbox API unreachable at ${baseUrl} \u2014 ${err instanceof Error ? err.message : String(err)}. Executing findings requires a running sandbox orchestrator + SDK adapter (see docs/replay-verify.md); start them or pass --base-url.`
8809
+ );
8810
+ }
8811
+ if (!response.ok) {
8812
+ throw new Error(
8813
+ `verify-findings: sandbox API at ${baseUrl} answered /health with HTTP ${response.status} \u2014 refusing to run proofs against degraded infrastructure`
8814
+ );
8815
+ }
8816
+ }
8817
+ function receiptExecution(verdict) {
8818
+ return {
8819
+ image: verdict.image,
8820
+ cwd: verdict.cwd,
8821
+ recordedReturncode: verdict.recordedReturncode,
8822
+ signature: verdict.signature,
8823
+ signatureBasis: verdict.signatureBasis,
8824
+ armA: {
8825
+ command: verdict.armA.command,
8826
+ exitCode: verdict.armA.exitCode,
8827
+ wallMs: verdict.armA.wallMs,
8828
+ failureSignatureMatch: verdict.armA.failureSignatureMatch
8829
+ },
8830
+ armB: verdict.armB ? {
8831
+ command: verdict.armB.command,
8832
+ exitCode: verdict.armB.exitCode,
8833
+ wallMs: verdict.armB.wallMs,
8834
+ failureVanished: verdict.armB.failureVanished
8835
+ } : null,
8836
+ prefixExecuted: verdict.prefixExecuted,
8837
+ prefixDivergences: verdict.prefixDivergences.length,
8838
+ totalMs: verdict.timings.totalMs
8839
+ };
8840
+ }
8841
+ function writeReceipt(receiptDir, finding2, verification, execution) {
8842
+ const receipt = {
8843
+ schema_version: "1.0.0",
8844
+ produced_at: (/* @__PURE__ */ new Date()).toISOString(),
8845
+ finding_id: verification.finding_id,
8846
+ analyst_id: finding2.analyst_id ?? null,
8847
+ subject: verification.subject,
8848
+ claim: typeof finding2.claim === "string" ? finding2.claim.slice(0, 600) : null,
8849
+ trajectory_id: verification.trajectory_id,
8850
+ step: verification.step,
8851
+ verified: verification.verified,
8852
+ reason: verification.reason,
8853
+ execution,
8854
+ verdict_path: verification.verdict_path,
8855
+ deduplicated_with: verification.deduplicated_with
8856
+ };
8857
+ writeFileSync3(join22(receiptDir, "receipt.json"), `${JSON.stringify(receipt, null, 2)}
8858
+ `);
8859
+ }
8860
+ function receiptDirName(index, finding2) {
8861
+ const id = typeof finding2.finding_id === "string" && finding2.finding_id.length > 0 ? finding2.finding_id.replace(/[^A-Za-z0-9_-]/g, "_") : "finding";
8862
+ return `${String(index + 1).padStart(3, "0")}-${id}`;
8863
+ }
8864
+ async function verifyFindings(findings, options) {
8865
+ if (findings.length === 0) throw new Error("verify-findings: no findings to verify");
8866
+ mkdirSync3(options.out, { recursive: true });
8867
+ const resolutions = findings.map((finding2) => resolveFindingReplayability(finding2, options.source));
8868
+ const baseUrl = options.baseUrl ?? DEFAULT_SANDBOX_BASE_URL;
8869
+ if (resolutions.some((resolution) => resolution.replayable) && !options.backend) {
8870
+ if (!options.apiKey) {
8871
+ throw new Error(
8872
+ "verify-findings: replayable findings need a sandbox API key \u2014 export SANDBOX_API_KEY or pass --api-key-env"
8873
+ );
8874
+ }
8875
+ await assertSandboxReachable(baseUrl);
8876
+ }
8877
+ const preparer = options.source.kind === "corpus" && !options.backend ? options.source.preparer ?? dockerImagePreparer() : null;
8878
+ const preparedImages = /* @__PURE__ */ new Map();
8879
+ const executedByKey = /* @__PURE__ */ new Map();
8880
+ const verifications = [];
8881
+ let executions = 0;
8882
+ for (let index = 0; index < findings.length; index++) {
8883
+ const finding2 = findings[index];
8884
+ const resolution = resolutions[index];
8885
+ const receiptDir = join22(options.out, receiptDirName(index, finding2));
8886
+ mkdirSync3(receiptDir, { recursive: true });
8887
+ const identity = {
8888
+ finding_id: finding2.finding_id ?? null,
8889
+ subject: finding2.subject ?? null,
8890
+ trajectory_id: findingTrajectoryId(finding2)
8891
+ };
8892
+ if (!resolution.replayable) {
8893
+ const verification2 = {
8894
+ ...identity,
8895
+ step: findingReplayStep(finding2),
8896
+ verified: "not-replayable",
8897
+ reason: resolution.reason,
8898
+ receipt: receiptDir,
8899
+ verdict_path: null,
8900
+ deduplicated_with: null
8901
+ };
8902
+ writeReceipt(receiptDir, finding2, verification2, null);
8903
+ verifications.push(verification2);
8904
+ options.onProgress?.(`${identity.finding_id ?? `finding ${index + 1}`}: not-replayable \u2014 ${resolution.reason}`);
8905
+ continue;
8906
+ }
8907
+ const resolved = resolution.resolved;
8908
+ const dedupeKey = `${resolved.caseId}::${resolved.at}::${options.fixCommand ?? ""}`;
8909
+ const prior = executedByKey.get(dedupeKey);
8910
+ if (prior) {
8911
+ const verification2 = {
8912
+ ...identity,
8913
+ step: resolved.at,
8914
+ verified: prior.verified,
8915
+ reason: prior.reason,
8916
+ receipt: receiptDir,
8917
+ verdict_path: prior.verdict_path,
8918
+ deduplicated_with: prior.receipt
8919
+ };
8920
+ const priorVerdict = prior.verdict_path ? JSON.parse(readFileSync4(prior.verdict_path, "utf8")) : null;
8921
+ writeReceipt(receiptDir, finding2, verification2, priorVerdict ? receiptExecution(priorVerdict) : null);
8922
+ verifications.push(verification2);
8923
+ options.onProgress?.(
8924
+ `${identity.finding_id ?? `finding ${index + 1}`}: ${prior.verified} (shares proof with ${prior.finding_id ?? prior.receipt})`
8925
+ );
8926
+ continue;
8927
+ }
8928
+ let image = resolved.image;
8929
+ if (preparer) {
8930
+ const preparationKey = `${resolved.image}::${resolved.cwd}`;
8931
+ let preparation = preparedImages.get(preparationKey);
8932
+ if (!preparation) {
8933
+ const ensured = await preparer.ensure(resolved.image, resolved.cwd);
8934
+ preparation = ensured.succeeded ? { succeeded: true, image: ensured.value.derivedImage } : { succeeded: false, error: ensured.error };
8935
+ preparedImages.set(preparationKey, preparation);
8936
+ }
8937
+ if (!preparation.succeeded) {
8938
+ const verification2 = {
8939
+ ...identity,
8940
+ step: resolved.at,
8941
+ verified: "not-replayable",
8942
+ reason: `replay image could not be prepared \u2014 ${preparation.error}`,
8943
+ receipt: receiptDir,
8944
+ verdict_path: null,
8945
+ deduplicated_with: null
8946
+ };
8947
+ writeReceipt(receiptDir, finding2, verification2, null);
8948
+ verifications.push(verification2);
8949
+ options.onProgress?.(`${identity.finding_id ?? `finding ${index + 1}`}: not-replayable \u2014 image preparation failed`);
8950
+ continue;
8951
+ }
8952
+ image = preparation.image;
8953
+ }
8954
+ options.onProgress?.(
8955
+ `${identity.finding_id ?? `finding ${index + 1}`}: executing arm A at step ${resolved.at} of ${resolved.caseId} on ${image}`
8956
+ );
8957
+ const verdict = await replayVerify({
8958
+ stepsPath: resolved.stepsPath,
8959
+ image,
8960
+ at: resolved.at,
8961
+ fixCommand: options.fixCommand,
8962
+ cwd: resolved.cwd,
8963
+ out: receiptDir,
8964
+ caseId: resolved.caseId,
8965
+ stepTimeoutMs: options.stepTimeoutMs ?? resolved.recordedStepTimeoutMs ?? void 0,
8966
+ prefixLimit: options.prefixLimit,
8967
+ backend: options.backend,
8968
+ apiKey: options.apiKey,
8969
+ baseUrl,
8970
+ maxLifetimeSeconds: options.maxLifetimeSeconds,
8971
+ onProgress: options.onProgress
8972
+ });
8973
+ executions += 1;
8974
+ const verification = {
8975
+ ...identity,
8976
+ step: resolved.at,
8977
+ verified: classifyVerdict(verdict),
8978
+ reason: null,
8979
+ receipt: receiptDir,
8980
+ verdict_path: join22(receiptDir, "replay-verdict.json"),
8981
+ deduplicated_with: null
8982
+ };
8983
+ executedByKey.set(dedupeKey, verification);
8984
+ writeReceipt(receiptDir, finding2, verification, receiptExecution(verdict));
8985
+ verifications.push(verification);
8986
+ options.onProgress?.(`${identity.finding_id ?? `finding ${index + 1}`}: ${verification.verified}`);
8987
+ }
8988
+ const counts = {
8989
+ reproduced: 0,
8990
+ "fix-flipped": 0,
8991
+ divergent: 0,
8992
+ "not-replayable": 0
8993
+ };
8994
+ for (const verification of verifications) counts[verification.verified] += 1;
8995
+ const run = { out: options.out, verifications, counts, executions };
8996
+ writeFileSync3(
8997
+ join22(options.out, "verifications.json"),
8998
+ `${JSON.stringify({ schema_version: "1.0.0", ...run }, null, 2)}
8999
+ `
9000
+ );
9001
+ return run;
9002
+ }
9003
+ function verdictCell(verification) {
9004
+ switch (verification.verified) {
9005
+ case "reproduced":
9006
+ return "**VERIFIED** \u2014 reproduced";
9007
+ case "fix-flipped":
9008
+ return "**VERIFIED** \u2014 fix-flipped";
9009
+ case "divergent":
9010
+ return "DIVERGENT \u2014 recorded failure did not reproduce";
9011
+ case "not-replayable":
9012
+ return `UNVERIFIABLE \u2014 ${verification.reason ?? "no reason recorded"}`;
9013
+ }
9014
+ }
9015
+ function renderVerifiedFindingsSection(run) {
9016
+ const lines = ["## Verified findings (executed replay)", ""];
9017
+ const total = run.verifications.length;
9018
+ lines.push(
9019
+ `${total} finding(s) \u2192 ${run.counts.reproduced} reproduced, ${run.counts["fix-flipped"]} fix-flipped, ${run.counts.divergent} divergent, ${run.counts["not-replayable"]} not replayable (${run.executions} sandbox execution(s); findings accusing the same step share one proof).`
9020
+ );
9021
+ lines.push("");
9022
+ lines.push("| Finding | Subject | Step | Verdict | Receipt |");
9023
+ lines.push("|---|---|---:|---|---|");
9024
+ for (const verification of run.verifications) {
9025
+ const shared = verification.deduplicated_with ? " (shared proof)" : "";
9026
+ lines.push(
9027
+ `| \`${verification.finding_id ?? "\u2014"}\` | \`${verification.subject ?? "\u2014"}\` | ${verification.step ?? "\u2014"} | ${verdictCell(verification)} | \`${verification.receipt}\`${shared} |`
9028
+ );
9029
+ }
9030
+ lines.push("");
9031
+ lines.push(
9032
+ "VERIFIED = the accused step was re-executed in a sandbox after replaying the trajectory prefix, and the recorded failure signature reproduced (fix-flipped: a corrected command additionally made it vanish). Each receipt directory carries receipt.json and, when executed, replay-verdict.json + report.md with real stdout/stderr."
9033
+ );
9034
+ lines.push("");
9035
+ return lines.join("\n");
9036
+ }
9037
+ function readFindingsFile(path) {
9038
+ const parsed = JSON.parse(readFileSync4(path, "utf8"));
9039
+ const array = Array.isArray(parsed) ? parsed : parsed && typeof parsed === "object" && Array.isArray(parsed.findings) ? parsed.findings : null;
9040
+ if (!array) {
9041
+ throw new Error(`${path} is neither a findings array nor an object with a findings array`);
9042
+ }
9043
+ for (const entry of array) {
9044
+ if (!entry || typeof entry !== "object") {
9045
+ throw new Error(`${path}: every finding must be an object, got ${JSON.stringify(entry)}`);
9046
+ }
9047
+ }
9048
+ return array;
9049
+ }
9050
+ function verifyFindingsUsage() {
9051
+ return `traces verify-findings \u2014 execute analyst findings as sandbox replay proofs
9052
+
9053
+ Usage:
9054
+ traces verify-findings --findings FINDINGS.json --out DIR \\
9055
+ ( --steps STEPS.json --image IMG --cwd DIR [--case ID]
9056
+ | --corpus name=<labels.json>::<preparedDir> [--corpus ...] ) \\
9057
+ [--fix-command CMD] [--step-timeout MS] [--prefix-limit N] \\
9058
+ [--base-url URL] [--api-key-env VAR] [--max-lifetime SECONDS]
9059
+
9060
+ --findings JSON array of analyst findings (or an object with a findings
9061
+ array) \u2014 the shape agent-eval analysts emit: subject
9062
+ incorrect-step-<n>, metadata.block_first_step, trace:// evidence
9063
+ --out receipt root; one directory per finding + verifications.json
9064
+ --steps/--image/--cwd
9065
+ verify against one trajectory; the image must be replay-ready
9066
+ (uid-1000 derived), exactly like traces replay-verify
9067
+ --case trajectory id of --steps; findings citing other trajectories
9068
+ are honestly marked not-replayable
9069
+ --corpus resolve each finding's trajectory in CodeTraceBench corpora;
9070
+ images are derived via the batch uid-1000 preparer (docker)
9071
+ --fix-command corrected step for arm B; a reproduced finding whose fix
9072
+ flips becomes fix-flipped
9073
+ --base-url sandbox API url (default $SANDBOX_API_URL or ${DEFAULT_SANDBOX_BASE_URL})
9074
+ --api-key-env env var holding the sandbox API key (default SANDBOX_API_KEY)
9075
+
9076
+ Every finding gets a verdict: reproduced | fix-flipped | divergent | not-replayable,
9077
+ with a receipt directory carrying the executed evidence or the precise reason.
9078
+ Requires a running sandbox orchestrator when any finding is replayable \u2014 infra
9079
+ absence is an error, never a silent skip. See docs/replay-verify.md for setup.
9080
+ `;
9081
+ }
9082
+ function parseVerifyFindingsArgs(argv) {
9083
+ const values = /* @__PURE__ */ new Map();
9084
+ const corpora = [];
9085
+ for (let i = 0; i < argv.length; i++) {
9086
+ const arg = argv[i];
9087
+ if (arg === "--help" || arg === "-h") return "help";
9088
+ if (!arg.startsWith("--")) throw new Error(`unexpected positional argument: ${arg}`);
9089
+ const value = argv[++i];
9090
+ if (value === void 0) throw new Error(`missing value for ${arg}`);
9091
+ if (arg === "--corpus") {
9092
+ corpora.push(parseCorpusFlag(value));
9093
+ continue;
9094
+ }
9095
+ values.set(arg, value);
9096
+ }
9097
+ const required = (flag) => {
9098
+ const v = values.get(flag);
9099
+ if (v === void 0) throw new Error(`verify-findings: ${flag} is required`);
9100
+ return v;
9101
+ };
9102
+ const optionalNumber = (flag) => {
9103
+ const v = values.get(flag);
9104
+ if (v === void 0) return void 0;
9105
+ const n = Number(v);
9106
+ if (!Number.isFinite(n)) throw new Error(`verify-findings: ${flag} must be a number, got ${v}`);
9107
+ return n;
9108
+ };
9109
+ const findingsPath = required("--findings");
9110
+ const out = required("--out");
9111
+ const direct = values.has("--steps") || values.has("--image") || values.has("--cwd");
9112
+ if (direct && corpora.length > 0) {
9113
+ throw new Error("verify-findings: pass --steps/--image/--cwd or --corpus, not both");
9114
+ }
9115
+ let source;
9116
+ if (direct) {
9117
+ source = {
9118
+ kind: "direct",
9119
+ stepsPath: required("--steps"),
9120
+ image: required("--image"),
9121
+ cwd: required("--cwd"),
9122
+ ...values.has("--case") ? { caseId: values.get("--case") } : {}
9123
+ };
9124
+ } else if (corpora.length > 0) {
9125
+ source = { kind: "corpus", corpora };
9126
+ } else {
9127
+ throw new Error("verify-findings: a replay source is required \u2014 --steps/--image/--cwd or --corpus");
9128
+ }
9129
+ return {
9130
+ findingsPath,
9131
+ source,
9132
+ out,
9133
+ fixCommand: values.get("--fix-command"),
9134
+ stepTimeoutMs: optionalNumber("--step-timeout"),
9135
+ prefixLimit: optionalNumber("--prefix-limit"),
9136
+ baseUrl: values.get("--base-url") ?? process.env.SANDBOX_API_URL ?? DEFAULT_SANDBOX_BASE_URL,
9137
+ apiKeyEnv: values.get("--api-key-env") ?? "SANDBOX_API_KEY",
9138
+ maxLifetimeSeconds: optionalNumber("--max-lifetime")
9139
+ };
9140
+ }
9141
+ async function cmdVerifyFindings(argv) {
9142
+ const parsed = parseVerifyFindingsArgs(argv);
9143
+ if (parsed === "help") {
9144
+ process.stdout.write(verifyFindingsUsage());
9145
+ return;
9146
+ }
9147
+ const findings = readFindingsFile(parsed.findingsPath);
9148
+ const apiKey = process.env[parsed.apiKeyEnv];
9149
+ const run = await verifyFindings(findings, {
9150
+ source: parsed.source,
9151
+ out: parsed.out,
9152
+ fixCommand: parsed.fixCommand,
9153
+ stepTimeoutMs: parsed.stepTimeoutMs,
9154
+ prefixLimit: parsed.prefixLimit,
9155
+ apiKey,
9156
+ baseUrl: parsed.baseUrl,
9157
+ maxLifetimeSeconds: parsed.maxLifetimeSeconds,
9158
+ onProgress: (message) => process.stderr.write(`${message}
9159
+ `)
9160
+ });
9161
+ process.stdout.write(`${JSON.stringify(run, null, 2)}
9162
+ `);
9163
+ process.stderr.write(
9164
+ `verify-findings: ${run.verifications.length} finding(s) \u2192 ${run.counts.reproduced} reproduced, ${run.counts["fix-flipped"]} fix-flipped, ${run.counts.divergent} divergent, ${run.counts["not-replayable"]} not-replayable (${run.executions} execution(s)) \u2192 ${run.out}
9165
+ `
9166
+ );
9167
+ }
9168
+
8254
9169
  // src/external.ts
8255
9170
  import { spawn } from "child_process";
8256
9171
  import { readdir as readdir14, readFile as readFile7 } from "fs/promises";
@@ -9015,18 +9930,18 @@ function commandRedactor(spec) {
9015
9930
  import { createHash as createHash9 } from "crypto";
9016
9931
  import { mkdir as mkdir3, mkdtemp as mkdtemp3, readFile as readFile8, readdir as readdir15, writeFile as writeFile4 } from "fs/promises";
9017
9932
  import { endianness, tmpdir as tmpdir4 } from "os";
9018
- import { join as join22, resolve as resolve6 } from "path";
9933
+ import { join as join23, resolve as resolve6 } from "path";
9019
9934
  var HODOSCOPE_VERSION = "0.2.4";
9020
9935
  async function writeHodoscopeInput(otlpPath, options = {}) {
9021
9936
  const explicitDirectory = options.directory ? resolve6(options.directory) : void 0;
9022
- const directory = explicitDirectory ?? await mkdtemp3(join22(tmpdir4(), "traces-hodoscope-input-"));
9937
+ const directory = explicitDirectory ?? await mkdtemp3(join23(tmpdir4(), "traces-hodoscope-input-"));
9023
9938
  if (explicitDirectory) {
9024
9939
  await mkdir3(directory, { recursive: true });
9025
9940
  if ((await readdir15(directory)).length > 0) {
9026
9941
  throw new Error(`Hodoscope explicit directory must be empty: ${directory}`);
9027
9942
  }
9028
9943
  }
9029
- const samples = join22(directory, "samples");
9944
+ const samples = join23(directory, "samples");
9030
9945
  await mkdir3(samples);
9031
9946
  const rows = [];
9032
9947
  for await (const value of readJsonl(otlpPath)) rows.push(parseOpenInferenceRow(value));
@@ -9052,7 +9967,7 @@ async function writeHodoscopeInput(otlpPath, options = {}) {
9052
9967
  }
9053
9968
  actionCount += actions;
9054
9969
  const file = `${createHash9("sha256").update(traceId).digest("hex").slice(0, 24)}.json`;
9055
- await writeFile4(join22(samples, file), `${JSON.stringify(trajectory, null, 2)}
9970
+ await writeFile4(join23(samples, file), `${JSON.stringify(trajectory, null, 2)}
9056
9971
  `, "utf8");
9057
9972
  }
9058
9973
  return {
@@ -9076,10 +9991,10 @@ function hodoscopeAnalyzer(options = {}) {
9076
9991
  async analyze(otlpPath, runOptions2 = {}) {
9077
9992
  const outputRoot = options.outputDir ? resolve6(options.outputDir) : tmpdir4();
9078
9993
  await mkdir3(outputRoot, { recursive: true });
9079
- const directory = await mkdtemp3(join22(outputRoot, "traces-hodoscope-"));
9080
- const input = join22(directory, "input");
9081
- const analysisFile = join22(directory, "analysis.hodoscope.json");
9082
- const samplesFile = join22(directory, "samples.json");
9994
+ const directory = await mkdtemp3(join23(outputRoot, "traces-hodoscope-"));
9995
+ const input = join23(directory, "input");
9996
+ const analysisFile = join23(directory, "analysis.hodoscope.json");
9997
+ const samplesFile = join23(directory, "samples.json");
9083
9998
  try {
9084
9999
  const prepared = await writeHodoscopeInput(otlpPath, {
9085
10000
  directory: input,
@@ -9791,7 +10706,7 @@ async function applyRedactor(spans, redactor) {
9791
10706
  import { createHash as createHash10 } from "crypto";
9792
10707
  import { mkdtemp as mkdtemp4, readFile as readFile9, writeFile as writeFile5 } from "fs/promises";
9793
10708
  import { tmpdir as tmpdir5 } from "os";
9794
- import { join as join23 } from "path";
10709
+ import { join as join24 } from "path";
9795
10710
  import {
9796
10711
  firstNumberAttr,
9797
10712
  LLM_CACHED_TOKEN_ATTR_KEYS,
@@ -10387,7 +11302,7 @@ async function exportTraceEvidenceFile(inputPath, opts = {}) {
10387
11302
  }
10388
11303
  async function writeTraceEvidenceExportFile(inputPath, outPath, opts = {}) {
10389
11304
  const result = await exportTraceEvidenceFile(inputPath, opts);
10390
- const path = outPath ?? join23(await mkdtemp4(join23(tmpdir5(), "traces-export-")), "spans.openinference.jsonl");
11305
+ const path = outPath ?? join24(await mkdtemp4(join24(tmpdir5(), "traces-export-")), "spans.openinference.jsonl");
10391
11306
  await writeFile5(path, serializeSpans(result.spans), "utf8");
10392
11307
  return { ...result, path };
10393
11308
  }
@@ -10395,7 +11310,7 @@ async function writeTraceEvidenceExportFile(inputPath, outPath, opts = {}) {
10395
11310
  // src/inspect.ts
10396
11311
  import { mkdtemp as mkdtemp5, readFile as readFile10, writeFile as writeFile6 } from "fs/promises";
10397
11312
  import { tmpdir as tmpdir6 } from "os";
10398
- import { basename as basename13, join as join24 } from "path";
11313
+ import { basename as basename13, join as join25 } from "path";
10399
11314
  var severityRank = {
10400
11315
  high: 0,
10401
11316
  medium: 1,
@@ -10684,14 +11599,14 @@ async function readSessionIndexFile(path) {
10684
11599
  return parsed;
10685
11600
  }
10686
11601
  async function writeInspectionReportFile(report, outPath) {
10687
- const path = outPath ?? join24(await mkdtemp5(join24(tmpdir6(), "traces-inspect-")), "inspection-report.md");
11602
+ const path = outPath ?? join25(await mkdtemp5(join25(tmpdir6(), "traces-inspect-")), "inspection-report.md");
10688
11603
  await writeFile6(path, renderInspectionReport(report), "utf8");
10689
11604
  return path;
10690
11605
  }
10691
11606
 
10692
11607
  // src/adoption.ts
10693
11608
  import { readFile as readFile11 } from "fs/promises";
10694
- import { join as join25 } from "path";
11609
+ import { join as join26 } from "path";
10695
11610
  function toolName2(s) {
10696
11611
  const n = s.attributes["tool.name"];
10697
11612
  if (typeof n === "string") return n;
@@ -10811,7 +11726,7 @@ async function readLoopRuns(cwds, sessionIds) {
10811
11726
  seenCwds.add(cwd);
10812
11727
  const firstSourceByRun = /* @__PURE__ */ new Map();
10813
11728
  for (const artifactDir of [".agent", ".evolve"]) {
10814
- const path = join25(cwd, artifactDir, "skill-runs.jsonl");
11729
+ const path = join26(cwd, artifactDir, "skill-runs.jsonl");
10815
11730
  let raw;
10816
11731
  try {
10817
11732
  raw = await readFile11(path, "utf8");
@@ -12223,7 +13138,7 @@ function renderAdoption(ar) {
12223
13138
  // src/improvement.ts
12224
13139
  import { access, mkdir as mkdir4, mkdtemp as mkdtemp6, writeFile as writeFile7 } from "fs/promises";
12225
13140
  import { tmpdir as tmpdir7 } from "os";
12226
- import { dirname as dirname7, join as join26, resolve as resolve7 } from "path";
13141
+ import { dirname as dirname7, join as join27, resolve as resolve7 } from "path";
12227
13142
  import { pathToFileURL } from "url";
12228
13143
  import {
12229
13144
  AnalystRegistry,
@@ -12730,13 +13645,13 @@ async function runTraceStoreInvestigation(opts) {
12730
13645
  };
12731
13646
  }
12732
13647
  async function writeTraceImprovementArtifacts(result, outDir) {
12733
- const directory = outDir ? resolve7(outDir) : await mkdtemp6(join26(tmpdir7(), "traces-improvement-"));
13648
+ const directory = outDir ? resolve7(outDir) : await mkdtemp6(join27(tmpdir7(), "traces-improvement-"));
12734
13649
  await mkdir4(directory, { recursive: true });
12735
13650
  const paths = {
12736
13651
  directory,
12737
- result: join26(directory, "result.json"),
12738
- evidence: join26(directory, "evidence.jsonl"),
12739
- report: join26(directory, "report.md"),
13652
+ result: join27(directory, "result.json"),
13653
+ evidence: join27(directory, "evidence.jsonl"),
13654
+ report: join27(directory, "report.md"),
12740
13655
  traces: result.otlpPath
12741
13656
  };
12742
13657
  const { report, ...machineResult } = result;
@@ -12748,11 +13663,11 @@ async function writeTraceImprovementArtifacts(result, outDir) {
12748
13663
  return paths;
12749
13664
  }
12750
13665
  async function runTraceImprovement(opts) {
12751
- const directory = opts.outDir ? resolve7(opts.outDir) : await mkdtemp6(join26(tmpdir7(), "traces-improvement-"));
13666
+ const directory = opts.outDir ? resolve7(opts.outDir) : await mkdtemp6(join27(tmpdir7(), "traces-improvement-"));
12752
13667
  await mkdir4(directory, { recursive: true });
12753
13668
  const investigation = await runTraceInvestigation({
12754
13669
  ...opts,
12755
- otlpOutPath: opts.otlpOutPath ?? join26(directory, "traces.otlp.jsonl")
13670
+ otlpOutPath: opts.otlpOutPath ?? join27(directory, "traces.otlp.jsonl")
12756
13671
  });
12757
13672
  const result = {
12758
13673
  ...investigation,
@@ -13195,7 +14110,7 @@ async function streamSessions(opts) {
13195
14110
 
13196
14111
  // src/supervisor-run-context.ts
13197
14112
  import { readFile as readFile12, stat as stat14 } from "fs/promises";
13198
- import { basename as basename14, join as join27 } from "path";
14113
+ import { basename as basename14, join as join28 } from "path";
13199
14114
  import { materializeTreeView } from "@tangle-network/agent-runtime/kernel";
13200
14115
  import {
13201
14116
  NO_SOURCE_LIMITS
@@ -13356,10 +14271,10 @@ function parseInstant(v) {
13356
14271
  return Number.isFinite(t) ? t : null;
13357
14272
  }
13358
14273
  async function readFileRunContext(runDir, opts = {}) {
13359
- const journalText = await readMaybe(join27(runDir, SPAWN_JOURNAL_FILE));
14274
+ const journalText = await readMaybe(join28(runDir, SPAWN_JOURNAL_FILE));
13360
14275
  const journal = readRunContextJournal(journalText);
13361
- const coordination = readCoordinationLog(await readMaybe(join27(runDir, COORDINATION_LOG_FILE)));
13362
- const result = await readMaybe(join27(runDir, RESULT_FILE));
14276
+ const coordination = readCoordinationLog(await readMaybe(join28(runDir, COORDINATION_LOG_FILE)));
14277
+ const result = await readMaybe(join28(runDir, RESULT_FILE));
13363
14278
  return {
13364
14279
  runRef: runDir,
13365
14280
  instanceId: opts.instanceId !== void 0 ? opts.instanceId : journal.root,
@@ -13382,7 +14297,7 @@ async function readFileRunContext(runDir, opts = {}) {
13382
14297
  harnessWorkerTokens: null,
13383
14298
  harnessMissingReason: "the file run context journals each child\u2019s own settled spend, so no external harness join is used",
13384
14299
  limits: sourceLimits(journal, coordination),
13385
- rootTranscriptRef: journal.present ? join27(runDir, SPAWN_JOURNAL_FILE) : null,
14300
+ rootTranscriptRef: journal.present ? join28(runDir, SPAWN_JOURNAL_FILE) : null,
13386
14301
  traceCommand: `traces watch ${runDir}`
13387
14302
  };
13388
14303
  }
@@ -13390,7 +14305,7 @@ function fileRunContextSupervisorRunReader(runDir, opts = {}) {
13390
14305
  return { runRef: runDir, read: () => readFileRunContext(runDir, opts) };
13391
14306
  }
13392
14307
  async function isFileRunContextDir(runDir) {
13393
- return stat14(join27(runDir, SPAWN_JOURNAL_FILE)).then((s) => s.isFile()).catch(() => false);
14308
+ return stat14(join28(runDir, SPAWN_JOURNAL_FILE)).then((s) => s.isFile()).catch(() => false);
13394
14309
  }
13395
14310
  function flattenJournal(journal) {
13396
14311
  return journal.events.map((event) => {
@@ -13536,9 +14451,9 @@ function mergeRolls(a, b) {
13536
14451
  };
13537
14452
  }
13538
14453
  async function readRunContextSnapshot(runDir) {
13539
- const journal = readRunContextJournal(await readMaybe(join27(runDir, SPAWN_JOURNAL_FILE)));
13540
- const coordination = readCoordinationLog(await readMaybe(join27(runDir, COORDINATION_LOG_FILE)));
13541
- const resultText = await readMaybe(join27(runDir, RESULT_FILE));
14454
+ const journal = readRunContextJournal(await readMaybe(join28(runDir, SPAWN_JOURNAL_FILE)));
14455
+ const coordination = readCoordinationLog(await readMaybe(join28(runDir, COORDINATION_LOG_FILE)));
14456
+ const resultText = await readMaybe(join28(runDir, RESULT_FILE));
13542
14457
  const result = parseResult(resultText);
13543
14458
  const view = safeTreeView(journal.events);
13544
14459
  const nodes = view === null ? [] : orderNodes(view, journal);
@@ -13721,7 +14636,7 @@ function wrap(text2, indent) {
13721
14636
 
13722
14637
  // src/run-span-tree.ts
13723
14638
  import { readdir as readdir16 } from "fs/promises";
13724
- import { join as join28 } from "path";
14639
+ import { join as join29 } from "path";
13725
14640
  import {
13726
14641
  firstNumberAttr as firstNumberAttr3,
13727
14642
  LLM_COST_ATTR_KEYS as LLM_COST_ATTR_KEYS2,
@@ -13994,7 +14909,7 @@ async function findSpanSourceFiles(dir) {
13994
14909
  const found = [];
13995
14910
  const entries = await readdir16(dir, { withFileTypes: true }).catch(() => []);
13996
14911
  for (const entry of entries) {
13997
- const child = join28(dir, entry.name);
14912
+ const child = join29(dir, entry.name);
13998
14913
  if (entry.isFile()) {
13999
14914
  if (SPAN_FILE_PATTERN.test(entry.name)) found.push(child);
14000
14915
  continue;
@@ -14002,7 +14917,7 @@ async function findSpanSourceFiles(dir) {
14002
14917
  if (!entry.isDirectory()) continue;
14003
14918
  const nested = await readdir16(child, { withFileTypes: true }).catch(() => []);
14004
14919
  for (const inner of nested) {
14005
- if (inner.isFile() && SPAN_FILE_PATTERN.test(inner.name)) found.push(join28(child, inner.name));
14920
+ if (inner.isFile() && SPAN_FILE_PATTERN.test(inner.name)) found.push(join29(child, inner.name));
14006
14921
  }
14007
14922
  }
14008
14923
  return found.sort();
@@ -14010,7 +14925,7 @@ async function findSpanSourceFiles(dir) {
14010
14925
 
14011
14926
  // src/supervisor-run-watch.ts
14012
14927
  import { readdir as readdir17 } from "fs/promises";
14013
- import { join as join29 } from "path";
14928
+ import { join as join30 } from "path";
14014
14929
  function budget(node) {
14015
14930
  const parts = [`${int(node.budget.maxIterations)}it`, `${int(node.budget.maxTokens)}tok`];
14016
14931
  if (node.budget.maxUsd !== void 0) parts.push(`$${node.budget.maxUsd.toFixed(2)}`);
@@ -14154,7 +15069,7 @@ async function resolveRunContextDir(dir) {
14154
15069
  const candidates = [];
14155
15070
  for (const entry of entries) {
14156
15071
  if (!entry.isDirectory()) continue;
14157
- const child = join29(dir, entry.name);
15072
+ const child = join30(dir, entry.name);
14158
15073
  if (await isFileRunContextDir(child)) candidates.push(child);
14159
15074
  }
14160
15075
  if (candidates.length === 1) return candidates[0];
@@ -14734,7 +15649,7 @@ async function collectSessionSelection(groups, options = {}) {
14734
15649
  // src/session-index.ts
14735
15650
  import { mkdtemp as mkdtemp7, readdir as readdir18, readFile as readFile13, stat as stat16, writeFile as writeFile8 } from "fs/promises";
14736
15651
  import { tmpdir as tmpdir8 } from "os";
14737
- import { dirname as dirname8, join as join30 } from "path";
15652
+ import { dirname as dirname8, join as join31 } from "path";
14738
15653
  function indexRow(record2) {
14739
15654
  return {
14740
15655
  session: record2.session,
@@ -14807,7 +15722,7 @@ async function findContextRoot(cwd) {
14807
15722
  if (!s) return null;
14808
15723
  if (!s.isDirectory()) current = dirname8(current);
14809
15724
  for (; ; ) {
14810
- if (await pathStat2(join30(current, ".git")) || await dirExists(join30(current, ".evolve")) || await fileExists(join30(current, "AGENTS.md")) || await fileExists(join30(current, "CLAUDE.md"))) {
15725
+ if (await pathStat2(join31(current, ".git")) || await dirExists(join31(current, ".evolve")) || await fileExists(join31(current, "AGENTS.md")) || await fileExists(join31(current, "CLAUDE.md"))) {
14811
15726
  return current;
14812
15727
  }
14813
15728
  const parent = dirname8(current);
@@ -14875,7 +15790,7 @@ async function walkMarkdown(root, kind, maxFiles = 100) {
14875
15790
  }
14876
15791
  for (const entry of entries) {
14877
15792
  if (out.length >= maxFiles) return;
14878
- const path = join30(dir, entry.name);
15793
+ const path = join31(dir, entry.name);
14879
15794
  if (entry.isDirectory()) {
14880
15795
  await walk(path);
14881
15796
  } else if (entry.isFile() && entry.name.endsWith(".md")) {
@@ -14890,20 +15805,20 @@ async function walkMarkdown(root, kind, maxFiles = 100) {
14890
15805
  async function collectContextRoot(root) {
14891
15806
  const files = [];
14892
15807
  for (const name of ["AGENTS.md", "CLAUDE.md", "GEMINI.md"]) {
14893
- const summary = await summarizeFile(join30(root, name), "instruction-doc");
15808
+ const summary = await summarizeFile(join31(root, name), "instruction-doc");
14894
15809
  if (summary) files.push(summary);
14895
15810
  }
14896
- const evolve = join30(root, ".evolve");
15811
+ const evolve = join31(root, ".evolve");
14897
15812
  for (const name of ["skill-runs.jsonl", "governor.jsonl", "experiments.jsonl"]) {
14898
- const summary = await summarizeFile(join30(evolve, name), "evolve-jsonl");
15813
+ const summary = await summarizeFile(join31(evolve, name), "evolve-jsonl");
14899
15814
  if (summary) files.push(summary);
14900
15815
  }
14901
15816
  for (const name of ["scorecard.json"]) {
14902
- const summary = await summarizeFile(join30(evolve, name), "evolve-json");
15817
+ const summary = await summarizeFile(join31(evolve, name), "evolve-json");
14903
15818
  if (summary) files.push(summary);
14904
15819
  }
14905
- files.push(...await walkMarkdown(join30(evolve, "reflections"), "reflection"));
14906
- files.push(...await walkMarkdown(join30(evolve, "handoffs"), "handoff"));
15820
+ files.push(...await walkMarkdown(join31(evolve, "reflections"), "reflection"));
15821
+ files.push(...await walkMarkdown(join31(evolve, "handoffs"), "handoff"));
14907
15822
  return { root, files: files.sort((a, b) => a.path.localeCompare(b.path)) };
14908
15823
  }
14909
15824
  async function collectContextIndex(records) {
@@ -14949,7 +15864,7 @@ function serializeSessionIndex(index) {
14949
15864
  `;
14950
15865
  }
14951
15866
  async function writeSessionIndexFile(index, outPath) {
14952
- const path = outPath ?? join30(await mkdtemp7(join30(tmpdir8(), "traces-index-")), "session-index.json");
15867
+ const path = outPath ?? join31(await mkdtemp7(join31(tmpdir8(), "traces-index-")), "session-index.json");
14953
15868
  await writeFile8(path, serializeSessionIndex(index), "utf8");
14954
15869
  return path;
14955
15870
  }
@@ -14957,11 +15872,11 @@ async function writeSessionIndexFile(index, outPath) {
14957
15872
  // src/upload-state.ts
14958
15873
  import { mkdir as mkdir5, readFile as readFile14, writeFile as writeFile9 } from "fs/promises";
14959
15874
  import { homedir as homedir12 } from "os";
14960
- import { dirname as dirname9, join as join31 } from "path";
15875
+ import { dirname as dirname9, join as join32 } from "path";
14961
15876
  import { contentHash } from "@tangle-network/agent-eval";
14962
15877
  function stateFile() {
14963
- const base = process.env.XDG_STATE_HOME ?? join31(homedir12(), ".local", "state");
14964
- return join31(base, "tangle-traces", "uploaded.json");
15878
+ const base = process.env.XDG_STATE_HOME ?? join32(homedir12(), ".local", "state");
15879
+ return join32(base, "tangle-traces", "uploaded.json");
14965
15880
  }
14966
15881
  function uploadKey(harness, sessionId) {
14967
15882
  return `${harness}:${sessionId}`;
@@ -14993,7 +15908,7 @@ function alreadyUploaded(state, harness, sessionId, hash2) {
14993
15908
  import { readFile as readFile15 } from "fs/promises";
14994
15909
  import { createRequire as createRequire2 } from "module";
14995
15910
  import { hostname as hostname2 } from "os";
14996
- import { basename as basename15, join as join32 } from "path";
15911
+ import { basename as basename15, join as join33 } from "path";
14997
15912
  import { hostedClientFromEnv } from "@tangle-network/agent-eval/hosted";
14998
15913
  import { REDACTION_VERSION } from "@tangle-network/agent-eval/traces";
14999
15914
  var require2 = createRequire2(import.meta.url);
@@ -15017,7 +15932,7 @@ async function planUpload(opts = {}) {
15017
15932
  async function gitBranch(cwd) {
15018
15933
  if (!cwd) return void 0;
15019
15934
  try {
15020
- const head = await readFile15(join32(cwd, ".git", "HEAD"), "utf8");
15935
+ const head = await readFile15(join33(cwd, ".git", "HEAD"), "utf8");
15021
15936
  const m = head.match(/^ref:\s*refs\/heads\/(.+)$/m);
15022
15937
  return m?.[1]?.trim();
15023
15938
  } catch {
@@ -15281,9 +16196,13 @@ export {
15281
16196
  resolveCaseResources,
15282
16197
  enumerateReplayableCases,
15283
16198
  zaiChatCaller,
16199
+ clipText,
15284
16200
  buildFixPrompt,
16201
+ buildRetryFixPrompt,
16202
+ countScriptCommands,
15285
16203
  extractFixCommand,
15286
16204
  generateFixCommand,
16205
+ runFixLoop,
15287
16206
  derivedImageTag,
15288
16207
  dockerImagePreparer,
15289
16208
  seededSample,
@@ -15292,6 +16211,21 @@ export {
15292
16211
  parseReplayBatchArgs,
15293
16212
  replayBatchUsage,
15294
16213
  cmdReplayVerifyBatch,
16214
+ parseIncorrectStepsSubject,
16215
+ resolveFindingInvocation,
16216
+ replayVerifyFinding,
16217
+ findingReplayStep,
16218
+ findingTrajectoryId,
16219
+ resolveFindingReplayability,
16220
+ classifyVerdict,
16221
+ DEFAULT_SANDBOX_BASE_URL,
16222
+ assertSandboxReachable,
16223
+ verifyFindings,
16224
+ renderVerifiedFindingsSection,
16225
+ readFindingsFile,
16226
+ verifyFindingsUsage,
16227
+ parseVerifyFindingsArgs,
16228
+ cmdVerifyFindings,
15295
16229
  runCommand,
15296
16230
  commandAnalyzer,
15297
16231
  haloAnalyzer,