@miraland-labs/conduit-bridge 0.16.147 → 0.16.148

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.
@@ -208,7 +208,7 @@ export async function captureVerificationFailure(input) {
208
208
  // Same rule as the pre-delivery gate: this is the diagnosis a repair brief is written from, so
209
209
  // it must keep the end. It carried its own head-truncating copy of that logic until the two were
210
210
  // shown to be the same bug.
211
- const text = boundedFailureDetail(command, result.stdout.trim() ? result.stdout.trim().split("\n") : [], result.stderr.trim() ? result.stderr.trim().split("\n") : [], result.code, DIAGNOSIS_DETAIL_MAX_CHARS);
211
+ const text = boundedFailureDetail(command, outputLines(result), result.code, DIAGNOSIS_DETAIL_MAX_CHARS);
212
212
  if (text.trim())
213
213
  return { command, detail: text };
214
214
  }
@@ -263,6 +263,14 @@ export function boundedTail(text, maxChars) {
263
263
  return text.slice(text.length - maxChars);
264
264
  return `${text.slice(0, head)}${marker}${text.slice(text.length - tail)}`;
265
265
  }
266
+ /**
267
+ * The lines of a run in the order the command wrote them. A run without the combined output (a
268
+ * test's fake runner) reads as stdout followed by stderr.
269
+ */
270
+ function outputLines(result) {
271
+ const split = (text) => (text.trim() ? text.trim().split("\n") : []);
272
+ return result.output === undefined ? [...split(result.stdout), ...split(result.stderr)] : split(result.output);
273
+ }
266
274
  /**
267
275
  * A failure detail that keeps the part which explains the failure.
268
276
  *
@@ -275,10 +283,13 @@ export function boundedTail(text, maxChars) {
275
283
  * So: the command line for orientation, a few opening lines, then the **tail**, and the exit code
276
284
  * always. What was dropped is stated rather than silently vanishing, because a reader must be able
277
285
  * to tell a short failure from a truncated one.
286
+ *
287
+ * The lines are in the order the command wrote them. Joining all stdout before all stderr made the
288
+ * tail of a failed `cargo test` its stderr `Running …` lines and elided the panic on stdout.
278
289
  */
279
- export function boundedFailureDetail(command, stdoutLines, stderrLines, code, maxChars = FAILURE_DETAIL_MAX_CHARS) {
290
+ export function boundedFailureDetail(command, lines, code, maxChars = FAILURE_DETAIL_MAX_CHARS) {
280
291
  const cap = (line) => (line.length > 500 ? `${line.slice(0, 500)}…` : line);
281
- const body = [...stdoutLines, ...stderrLines].map(cap);
292
+ const body = lines.map(cap);
282
293
  const first = `$ ${command}`;
283
294
  const last = `exit ${code}`;
284
295
  const marker = (dropped) => `… ${dropped} line${dropped === 1 ? "" : "s"} omitted …`;
@@ -322,20 +333,36 @@ export function boundedFailureDetail(command, stdoutLines, stderrLines, code, ma
322
333
  *
323
334
  * A gate the timeout stopped did not exit: `timedOut` says so, and stderr ends with the limit it
324
335
  * reached. Read as exit 1, a gate that hung on the untouched base was a red base on the author.
336
+ *
337
+ * `output` is stdout and stderr together, in the order the lines arrived: a failure detail reads its
338
+ * tail from it.
325
339
  */
326
340
  export async function runBoundedVerificationCommand(command, workspace, timeoutMs = VERIFICATION_TIMEOUT_MS) {
327
341
  const argv = argvForBoundedCommand(command);
328
342
  const bin = argv[0];
329
343
  if (!bin)
330
344
  throw new Error("Verification command is empty");
345
+ // Chunks are not whole lines. Each stream keeps its partial last line until the line ends, so a
346
+ // stderr write between two stdout chunks cannot split a stdout line.
347
+ const lines = [];
348
+ const partial = { stdout: "", stderr: "" };
349
+ const collect = (stream) => (chunk) => {
350
+ const parts = `${partial[stream]}${String(chunk)}`.split("\n");
351
+ partial[stream] = parts.pop() ?? "";
352
+ lines.push(...parts);
353
+ };
354
+ const output = () => [...lines, ...[partial.stdout, partial.stderr].filter(Boolean)].join("\n");
331
355
  try {
332
- const { stdout, stderr } = await execFileAsync(bin, argv.slice(1), {
356
+ const pending = execFileAsync(bin, argv.slice(1), {
333
357
  cwd: workspace,
334
358
  timeout: timeoutMs,
335
359
  maxBuffer: VERIFICATION_MAX_BUFFER_BYTES,
336
360
  env: process.env,
337
361
  });
338
- return { stdout: String(stdout), stderr: String(stderr), code: 0 };
362
+ pending.child.stdout?.on("data", collect("stdout"));
363
+ pending.child.stderr?.on("data", collect("stderr"));
364
+ const { stdout, stderr } = await pending;
365
+ return { stdout: String(stdout), stderr: String(stderr), output: output(), code: 0 };
339
366
  }
340
367
  catch (error) {
341
368
  const err = error;
@@ -347,9 +374,11 @@ export async function runBoundedVerificationCommand(command, workspace, timeoutM
347
374
  if (typeof err.code === "string")
348
375
  throw error;
349
376
  if (err.killed === true) {
377
+ const stopped = `Bridge stopped the gate: it did not exit within ${Math.round(timeoutMs / 1_000)} seconds.`;
350
378
  return {
351
379
  stdout: String(err.stdout ?? ""),
352
- stderr: `${String(err.stderr ?? "")}\nBridge stopped the gate: it did not exit within ${Math.round(timeoutMs / 1_000)} seconds.`.trimStart(),
380
+ stderr: `${String(err.stderr ?? "")}\n${stopped}`.trimStart(),
381
+ output: `${output()}\n${stopped}`.trimStart(),
353
382
  code: 1,
354
383
  timedOut: true,
355
384
  };
@@ -358,6 +387,7 @@ export async function runBoundedVerificationCommand(command, workspace, timeoutM
358
387
  return {
359
388
  stdout: String(err.stdout ?? ""),
360
389
  stderr,
390
+ output: output() || stderr,
361
391
  code: typeof err.code === "number" ? err.code : 1,
362
392
  };
363
393
  }
@@ -536,12 +566,12 @@ export async function ensureTestEvidence(input) {
536
566
  // and would otherwise be witnessed as a passing test run that tested nothing.
537
567
  const ranNothing = detectGateRanNothing(command, result.stdout, result.stderr, result.code);
538
568
  if (ranNothing) {
539
- const detail = boundedFailureDetail(command, stdoutLines, stderrLines, result.code);
569
+ const detail = boundedFailureDetail(command, outputLines(result), result.code);
540
570
  throw new VerificationGateRanNothingError(command, result.code, ranNothing, detail);
541
571
  }
542
572
  if (result.code !== 0) {
543
573
  // Prefix must match FINALIZE_CONTRACT_PATTERN ("Agent report") — not Bridge-fault laundering.
544
- const detail = boundedFailureDetail(command, stdoutLines, stderrLines, result.code);
574
+ const detail = boundedFailureDetail(command, outputLines(result), result.code);
545
575
  // One line an operator can grep when a stored failure looks head-truncated. Reading the shape
546
576
  // back out of the database could not distinguish "the fix did not run" from "something
547
577
  // downstream re-truncated it"; this says which, at the moment it is produced.
package/dist/execution.js CHANGED
@@ -526,7 +526,9 @@ export function buildDiagnosticPrompt(input) {
526
526
  ...(decisions.length ? ["", `OWNER DECISIONS\n${decisions.map((item) => `- ${item.question}: ${item.decision}`).join("\n")}`] : []),
527
527
  ...(input.verificationCommands.length ? ["", `BOUNDED VERIFICATION COMMANDS\n${input.verificationCommands.map((item) => `- ${item}`).join("\n")}`] : []),
528
528
  ...(gate ? ["", `GATE — Bridge re-runs \`${gate}\` after the next run; the agent cannot change or replace it.`
529
- + " If the fix is not in the agent's hands, answer confident:false and name the gate in root_cause."] : []),
529
+ + " A fix that needs a file outside the APPROVED CHANGE SCOPE is still a grounded diagnosis."
530
+ + " Answer confident:true and write the full repository path of each such file in fix_direction. Conductor makes it a scope decision."
531
+ + " Answer confident:false only when no file change can fix the cause (the gate itself, the environment), and name the gate in root_cause."] : []),
530
532
  "",
531
533
  "WITNESSED FAILURE OUTPUT",
532
534
  redactSecrets(input.failureOutput),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@miraland-labs/conduit-bridge",
3
- "version": "0.16.147",
3
+ "version": "0.16.148",
4
4
  "description": "Conduit Bridge CLI \u2014 join, connect, disconnect, multi-driver lanes, and run Claude Code / Codex / Cursor / OpenCode / Pi / Kiro / Antigravity / Grok Build agents for a Conduit organization",
5
5
  "type": "module",
6
6
  "bin": {