(
opts: FailureGetOptions,
deps: TestDeps = {},
)
| 4002 | * remediation text per §5.4. |
| 4003 | */ |
| 4004 | export async function runFailureGet( |
| 4005 | opts: FailureGetOptions, |
| 4006 | deps: TestDeps = {}, |
| 4007 | ): Promise<FailureGetResult> { |
| 4008 | const out = makeOutput(opts.output, deps); |
| 4009 | const client = makeClient(opts, deps); |
| 4010 | // We resolve the output dir BEFORE the network call so a missing / |
| 4011 | // empty path surfaces as VALIDATION_ERROR (exit 5) without spending |
| 4012 | // an API call. `writeBundle` re-validates internally; this is the |
| 4013 | // fast-fail. |
| 4014 | const requestedDir = opts.out; |
| 4015 | |
| 4016 | const context = await client.get<CliFailureContext>( |
| 4017 | `/tests/${encodeURIComponent(opts.testId)}/failure`, |
| 4018 | ); |
| 4019 | |
| 4020 | // Run the §3 atomicity invariants on every path — even when --out is |
| 4021 | // absent. An agent piping the JSON envelope into a vision-LLM |
| 4022 | // consumer would otherwise be handed stitched data the contract |
| 4023 | // guarantees never reaches it. The bundle writer re-runs the check |
| 4024 | // internally; this call is the cheap upfront trap. |
| 4025 | assertContextIntegrity(context, 'local'); |
| 4026 | |
| 4027 | if (requestedDir !== undefined) { |
| 4028 | // Dry-run: do NOT call writeBundle (which would mkdir, fetch |
| 4029 | // presigned URLs, and write files). Print the would-be bundle layout |
| 4030 | // to stderr and emit the wire envelope to stdout so the agent sees |
| 4031 | // the shape it would parse from disk. |
| 4032 | if (opts.dryRun) { |
| 4033 | const stderr = deps.stderr ?? ((line: string) => process.stderr.write(`${line}\n`)); |
| 4034 | const fileNames = plannedBundleFiles(context, opts.failedOnly); |
| 4035 | stderr( |
| 4036 | `[dry-run] would write bundle to ${requestedDir} (${fileNames.length} files; meta.json renames last)`, |
| 4037 | ); |
| 4038 | for (const f of fileNames) stderr(`[dry-run] ${f}`); |
| 4039 | if (opts.output === 'json') { |
| 4040 | out.print({ ok: true, dir: requestedDir, dryRun: true, context }); |
| 4041 | } else { |
| 4042 | // Use a dry-run-specific renderer: the real success renderer |
| 4043 | // says "Bundle written to ..." which would be a lie here. Stdout |
| 4044 | // is the success contract automation may parse, so it must not |
| 4045 | // imply the bundle was created. |
| 4046 | out.print( |
| 4047 | { dir: requestedDir, files: fileNames.length, snapshotId: context.snapshotId }, |
| 4048 | data => |
| 4049 | renderBundleDryRunText(data as { dir: string; files: number; snapshotId: string }), |
| 4050 | ); |
| 4051 | } |
| 4052 | return { context }; |
| 4053 | } |
| 4054 | |
| 4055 | const bundle = await writeBundle(context, { |
| 4056 | dir: requestedDir, |
| 4057 | failedOnly: opts.failedOnly, |
| 4058 | fetchImpl: deps.fetchImpl, |
| 4059 | }); |
| 4060 | if (opts.output === 'json') { |
| 4061 | out.print({ ok: true, dir: bundle.dir, meta: bundle.meta, files: bundle.files }); |
no test coverage detected