@yagni-app/code-staging 1.2.6-staging.1960.1 → 1.2.6-staging.1973.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.
@@ -507,6 +507,7 @@ export async function runHeadlessGo(argv, deps = {}) {
507
507
  ...gateOpts,
508
508
  ...(baseline ? { baseline } : {}),
509
509
  ...(scan ? { scanChange: scan } : {}),
510
+ ...(cloud ? { unreadTestFailureIsFinding: true } : {}),
510
511
  });
511
512
  gate = made;
512
513
  runVerify = async (verifyCwd, signal, baselinePaths) => {
@@ -35,7 +35,9 @@
35
35
  * become `critical` findings through the same review->fix loop; infra/env
36
36
  * failures (connection refused, missing env, DB/auth, command not found) and
37
37
  * anything UNCERTAIN fail OPEN with an honest note, never a never-clearable
38
- * critical.
38
+ * critical. A cloud session's gate (`unreadTestFailureIsFinding`) makes an
39
+ * UNCERTAIN failure a finding carrying the output tail, unless the clean tree
40
+ * failed the same command.
39
41
  *
40
42
  * Safe-by-default and FAIL-OPEN: if no scoped, non-fanned-out command is
41
43
  * detectable, or the command cannot be spawned, or it times out, the gate
@@ -68,6 +70,11 @@ export declare const REPO_CHECKS_BUILD_BUDGET_MS: number;
68
70
  export declare const REPO_CHECKS_CALL_BUDGET_MS: number;
69
71
  /** One approved check's or CI step's cap. */
70
72
  export declare const REPO_CHECK_TIMEOUT_MS: number;
73
+ /**
74
+ * A command's exit as a note says it: what ran, its exit code and the first
75
+ * line of the tail a finding would carry. Pure.
76
+ */
77
+ export declare function exitSummary(label: string, code: number, output: string): string;
71
78
  /** A resolved verify command plus how it was found. */
72
79
  export interface VerifyCommand {
73
80
  /** Shell-free command + args, e.g. ["pnpm", "run", "typecheck"]. */
@@ -105,7 +112,10 @@ export interface VerifyCheckRun {
105
112
  command: string;
106
113
  /** Where it ran, repo-relative ("." for the root). */
107
114
  cwd: string;
108
- /** Null when the command never exited: it timed out, was aborted, or was not found. */
115
+ /**
116
+ * Null when the command gave no verdict: it timed out, was aborted, was not
117
+ * found, or exited on an environment that could not run it (its note says how).
118
+ */
109
119
  exitCode: number | null;
110
120
  durationMs: number;
111
121
  /**
@@ -114,7 +124,11 @@ export interface VerifyCheckRun {
114
124
  * neither.
115
125
  */
116
126
  baseline: "runnable" | "fails_on_base" | "cannot_run";
117
- /** Why it has no exit code: "timed out", "aborted", "not found" or "could not run". */
127
+ /**
128
+ * Why it has no exit code ("timed out", "aborted", "not found", "could not
129
+ * run", or the cause with the exit and the first line of its output tail),
130
+ * or beside one, that the unchanged code failed it too.
131
+ */
118
132
  note?: string;
119
133
  }
120
134
  /**
@@ -430,6 +444,13 @@ export interface MakeRunVerifyOpts {
430
444
  * findings. Absent: every failure is a finding, as before.
431
445
  */
432
446
  baseline?: VerifyBaseline | null;
447
+ /**
448
+ * The cloud session's rule (spec decision 3): a test run that exits
449
+ * non-zero with output no parser reads is a finding carrying its output
450
+ * tail, unless the baseline saw the clean tree fail the same command, which
451
+ * is a note. Absent (mission mode): it fails open with a note, as before.
452
+ */
453
+ unreadTestFailureIsFinding?: boolean;
433
454
  /**
434
455
  * The host's secret and binary scans over what this run added
435
456
  * (changeScan.ts, YAG-1000), given the tree's pre-run dirt to leave out;
@@ -35,7 +35,9 @@
35
35
  * become `critical` findings through the same review->fix loop; infra/env
36
36
  * failures (connection refused, missing env, DB/auth, command not found) and
37
37
  * anything UNCERTAIN fail OPEN with an honest note, never a never-clearable
38
- * critical.
38
+ * critical. A cloud session's gate (`unreadTestFailureIsFinding`) makes an
39
+ * UNCERTAIN failure a finding carrying the output tail, unless the clean tree
40
+ * failed the same command.
39
41
  *
40
42
  * Safe-by-default and FAIL-OPEN: if no scoped, non-fanned-out command is
41
43
  * detectable, or the command cannot be spawned, or it times out, the gate
@@ -87,6 +89,22 @@ const MAX_NAMED_CANNOT_RUN = 10;
87
89
  /** Cap on located findings parsed from one failing run, and on the opaque-tail message. */
88
90
  const MAX_LOCATED_FINDINGS = 25;
89
91
  const MAX_TAIL_CHARS = 1000;
92
+ /** The last MAX_TAIL_CHARS of a command's output, trimmed: what an unlocated finding carries. */
93
+ function outputTail(output) {
94
+ return (output.length > MAX_TAIL_CHARS ? output.slice(-MAX_TAIL_CHARS) : output).trim();
95
+ }
96
+ /**
97
+ * A command's exit as a note says it: what ran, its exit code and the first
98
+ * line of the tail a finding would carry. Pure.
99
+ */
100
+ export function exitSummary(label, code, output) {
101
+ const first = outputTail(output)
102
+ .split("\n")
103
+ .map((line) => line.trim())
104
+ .find(Boolean);
105
+ const line = first && first.length > 200 ? `${first.slice(0, 197)}...` : first;
106
+ return `\`${label}\` exited ${code}${line ? `; its output tail begins: ${line}` : " with no output"}`;
107
+ }
90
108
  /** The verify lens reuses `does_it_hold` (does the change build/test) to avoid widening ReviewLens. */
91
109
  const VERIFY_LENS = "does_it_hold";
92
110
  /**
@@ -557,17 +575,7 @@ export function classifyTestFailure(output, exitCode, limit = MAX_LOCATED_FINDIN
557
575
  };
558
576
  }
559
577
  if (FAILURE_SUMMARY.some((re) => re.test(output))) {
560
- const tail = output.length > MAX_TAIL_CHARS ? output.slice(output.length - MAX_TAIL_CHARS) : output;
561
- return {
562
- kind: "failures",
563
- findings: [
564
- {
565
- severity: "critical",
566
- lens: VERIFY_LENS,
567
- message: `verify (test) failed (exit ${exitCode}). Output tail:\n${tail.trim()}`,
568
- },
569
- ],
570
- };
578
+ return { kind: "failures", findings: [tailFinding("test", exitCode, output)] };
571
579
  }
572
580
  return { kind: "uncertain" };
573
581
  }
@@ -633,14 +641,15 @@ export function parseVerifyFailures(output, exitCode, label, limit = MAX_LOCATED
633
641
  }
634
642
  if (located.length > 0)
635
643
  return located;
636
- const tail = output.length > MAX_TAIL_CHARS ? output.slice(output.length - MAX_TAIL_CHARS) : output;
637
- return [
638
- {
639
- severity: "critical",
640
- lens: VERIFY_LENS,
641
- message: `verify (${label}) failed (exit ${exitCode}). Output tail:\n${tail.trim()}`,
642
- },
643
- ];
644
+ return [tailFinding(label, exitCode, output)];
645
+ }
646
+ /** One unlocated finding carrying the output's tail. */
647
+ function tailFinding(label, exitCode, output) {
648
+ return {
649
+ severity: "critical",
650
+ lens: VERIFY_LENS,
651
+ message: `verify (${label}) failed (exit ${exitCode}). Output tail:\n${outputTail(output)}`,
652
+ };
644
653
  }
645
654
  /** Trim a trailing slash and normalise separators for the path math below. */
646
655
  const normDir = (p) => p.replace(/\\/g, "/").replace(/\/+$/, "");
@@ -685,6 +694,12 @@ function liftFinding(finding, repoRoot, commandCwd) {
685
694
  const file = toRepoRelativePath(finding.file, repoRoot, commandCwd);
686
695
  return file === finding.file ? finding : { ...finding, file };
687
696
  }
697
+ /**
698
+ * A test command's baseline entry when the clean tree failed it with output
699
+ * no parser reads: it already failed there. It matches no failure's key, so
700
+ * it never excuses a finding.
701
+ */
702
+ const UNREAD_FAILURE_KEY = "\u0000unread failure";
688
703
  /** One command's identity across the baseline run and the build's runs: where it runs and what it runs. */
689
704
  export function verifyCommandKey(command) {
690
705
  return `${command.cwd}\u0000${command.argv.join(" ")}`;
@@ -1251,13 +1266,14 @@ export function makeRunVerify(opts = {}) {
1251
1266
  const startedAt = now();
1252
1267
  try {
1253
1268
  const { code, output: rawOutput } = await exec(repoCheckArgv(command), dir, composed, env);
1254
- const record = (baseline) => run.checks.push({
1269
+ const record = (baseline, note) => run.checks.push({
1255
1270
  name: check.name,
1256
1271
  command,
1257
1272
  cwd: relative(run.cwd, dir) || ".",
1258
- exitCode: code,
1273
+ exitCode: note ? null : code,
1259
1274
  durationMs: now() - startedAt,
1260
1275
  baseline,
1276
+ ...(note ? { note } : {}),
1261
1277
  });
1262
1278
  if (code === 0) {
1263
1279
  record(check.baseline);
@@ -1265,9 +1281,12 @@ export function makeRunVerify(opts = {}) {
1265
1281
  }
1266
1282
  const output = scrubSecrets(rawOutput);
1267
1283
  const cls = classifyTestFailure(output, code);
1268
- record(cls.kind === "infra" ? "cannot_run" : check.baseline);
1269
- if (cls.kind === "infra")
1270
- return { kind: "note", reason: `could not run: ${cls.reason}` };
1284
+ if (cls.kind === "infra") {
1285
+ const reason = `could not run: ${cls.reason}`;
1286
+ record("cannot_run", `${reason}; ${exitSummary(check.name, code, output)}`);
1287
+ return { kind: "note", reason };
1288
+ }
1289
+ record(check.baseline);
1271
1290
  return { kind: "failed", output, code };
1272
1291
  }
1273
1292
  catch (err) {
@@ -1470,7 +1489,7 @@ export function makeRunVerify(opts = {}) {
1470
1489
  const infra = classifyVerifyInfraFailure(output, code);
1471
1490
  if (infra) {
1472
1491
  reasons.push(`verify (${command.label}) could not run: ${infra}`);
1473
- recordCommand(command, code, startedAt, "cannot_run");
1492
+ recordCommand(command, null, startedAt, "cannot_run", `could not run: ${infra}; ${exitSummary(command.label, code, output)}`);
1474
1493
  continue;
1475
1494
  }
1476
1495
  }
@@ -1514,11 +1533,30 @@ export function makeRunVerify(opts = {}) {
1514
1533
  const { code, output: rawOutput } = await exec(command.argv, command.cwd, composed, testEnv);
1515
1534
  const output = scrubSecrets(rawOutput);
1516
1535
  const cls = classifyTestFailure(output, code, Number.POSITIVE_INFINITY);
1517
- recordCommand(command, code, startedAt, cls.kind === "infra" ? "cannot_run" : standing(command));
1518
1536
  if (cls.kind === "infra") {
1537
+ recordCommand(command, null, startedAt, "cannot_run", `could not run: ${cls.reason}; ${exitSummary(command.label, code, output)}`);
1519
1538
  reasons.push(`tests (${where}) could not run: ${cls.reason}; 'clean' reflects typecheck + review only`);
1520
1539
  continue;
1521
1540
  }
1541
+ const base = standing(command);
1542
+ if (cls.kind === "uncertain" && opts.unreadTestFailureIsFinding) {
1543
+ anyRan = true;
1544
+ testLabels.push(command.label);
1545
+ if (base === "fails_on_base") {
1546
+ const summary = exitSummary(command.label, code, output);
1547
+ recordCommand(command, code, startedAt, base, `failed on the unchanged code too: ${summary}`);
1548
+ reasons.push(`tests (${where}) failed on the unchanged code too, so not a finding: ${summary}`);
1549
+ }
1550
+ else {
1551
+ // One finding per command, past the cap too: it is the only
1552
+ // word of this command's failure.
1553
+ recordCommand(command, code, startedAt, base);
1554
+ testFindingCount += 1;
1555
+ findings.push(tailFinding(command.label, code, output));
1556
+ }
1557
+ continue;
1558
+ }
1559
+ recordCommand(command, code, startedAt, base);
1522
1560
  if (cls.kind === "uncertain") {
1523
1561
  // Failing OPEN on an uninterpretable failure (§3d): an honest note,
1524
1562
  // never a guessed finding the fix stage cannot clear.
@@ -1689,6 +1727,9 @@ export function makeTakeVerifyBaseline(opts = {}) {
1689
1727
  const cls = classifyTestFailure(output, code, Number.POSITIVE_INFINITY);
1690
1728
  if (cls.kind === "infra" || cls.kind === "uncertain") {
1691
1729
  result.notes.push(`${command.label} (${command.cwd}): ${cls.kind === "infra" ? cls.reason : "output not interpretable"}`);
1730
+ if (cls.kind === "uncertain") {
1731
+ baseline.set(verifyCommandKey(command), new Map([[UNREAD_FAILURE_KEY, 1]]));
1732
+ }
1692
1733
  continue;
1693
1734
  }
1694
1735
  failed = cls.kind === "failures" ? cls.findings : [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yagni-app/code-staging",
3
- "version": "1.2.6-staging.1960.1",
3
+ "version": "1.2.6-staging.1973.1",
4
4
  "description": "YAGNI Code: a terminal coding agent that already knows your company. One YAGNI login routes the model and grounds the agent in your team's context.",
5
5
  "license": "SEE LICENSE IN LICENSE.md",
6
6
  "author": "YAGNI, Inc. <jack@yagni.app> (https://yagni.app)",
@@ -58,5 +58,5 @@
58
58
  "turndown": "^7.2.4",
59
59
  "typebox": "^1.3.15"
60
60
  },
61
- "yagniSourceSha": "6525a69ea8f8955a94acebf1b574974f0dc41fd0"
61
+ "yagniSourceSha": "0cc9459eb25b5a357e700f90b803ff0adf194a0e"
62
62
  }