@yagni-app/code-staging 1.2.7-staging.2035.1 → 1.2.7-staging.2056.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.
@@ -456,6 +456,13 @@ export async function runHeadlessGo(argv, deps = {}) {
456
456
  if (stream)
457
457
  emit({ type: "pipeline_log", event, data });
458
458
  };
459
+ // Each command the gate runs, as it starts and ends: a check runs for
460
+ // minutes with no turn of the model's, and the host's ticket went quiet
461
+ // through it (YAG-1031). The host shows the command and its verdict.
462
+ const onCheck = stream
463
+ ? (event) => logPipeline("verify_check", event)
464
+ : undefined;
465
+ const heard = onCheck ? { onCheck } : {};
459
466
  // A measurement before the build (the baseline, the plan's checks on the
460
467
  // base) gets at most a sixth of the run's time left when it starts, and
461
468
  // none below a minute: the build itself must still fit. No deadline, no cap.
@@ -480,7 +487,7 @@ export async function runHeadlessGo(argv, deps = {}) {
480
487
  }
481
488
  else if (dirs.length > 0) {
482
489
  try {
483
- const taken = await (deps.takeVerifyBaseline ?? makeTakeVerifyBaseline())(cwd, dirs, deps.signal, share.shareMs);
490
+ const taken = await (deps.takeVerifyBaseline ?? makeTakeVerifyBaseline(heard))(cwd, dirs, deps.signal, share.shareMs);
484
491
  logPipeline("verify_baseline", {
485
492
  ran: taken.ran,
486
493
  failures: taken.failures,
@@ -518,7 +525,7 @@ export async function runHeadlessGo(argv, deps = {}) {
518
525
  }
519
526
  else {
520
527
  try {
521
- const measured = await (deps.measureChecksOnBase ?? makeMeasureChecksOnBase())(cwd, measureFile, deps.signal, share.shareMs);
528
+ const measured = await (deps.measureChecksOnBase ?? makeMeasureChecksOnBase(heard))(cwd, measureFile, deps.signal, share.shareMs);
522
529
  logPipeline("verify_checks_on_base", {
523
530
  ran: measured.ran,
524
531
  passed: [...measured.passed],
@@ -558,6 +565,7 @@ export async function runHeadlessGo(argv, deps = {}) {
558
565
  ...(checksOnBase ? { checksOnBase } : {}),
559
566
  ...(scan ? { scanChange: scan } : {}),
560
567
  ...(cloud ? { unreadTestFailureIsFinding: true } : {}),
568
+ ...heard,
561
569
  });
562
570
  gate = made;
563
571
  runVerify = async (verifyCwd, signal, baselinePaths) => {
@@ -158,6 +158,38 @@ export interface VerifyCheckRun {
158
158
  */
159
159
  note?: string;
160
160
  }
161
+ /**
162
+ * How the note of an approved check this gate run did not run starts (its
163
+ * budget was spent, it was not reached, the build stopped first, the
164
+ * typecheck half failed first). The host's pull request body reads these
165
+ * words (`CHECK_NOT_RUN_NOTE` in @yagni/shared's attempts.ts); a change here
166
+ * is a change there.
167
+ */
168
+ export declare const CHECK_NOT_RUN_NOTE = "not run on this tree";
169
+ /**
170
+ * One command the gate starts or ends, for the session's stream (headlessGo's
171
+ * `verify_check` line), so the ticket says what runs instead of going quiet
172
+ * for minutes (YAG-1031). `base` is a run on the unchanged code before the
173
+ * build. An end that did not pass carries the last few hundred characters
174
+ * of its output, scrubbed: the host logs them and never shows them.
175
+ */
176
+ export type VerifyCheckEvent = {
177
+ phase: "start";
178
+ name: string;
179
+ command: string;
180
+ cwd: string;
181
+ base?: true;
182
+ } | {
183
+ phase: "end";
184
+ name: string;
185
+ command: string;
186
+ cwd: string;
187
+ exitCode: number | null;
188
+ durationMs: number;
189
+ baseline?: VerifyCheckRun["baseline"];
190
+ base?: true;
191
+ tail?: string;
192
+ };
161
193
  /**
162
194
  * Why a command threw instead of exiting, as its check record says it. `run`
163
195
  * is the run's own signal (a /stop or the run's end); `capped` is the one
@@ -535,6 +567,8 @@ export interface MakeRunVerifyOpts {
535
567
  * removes. Absent: no scan half, as before (the cloud session sets it).
536
568
  */
537
569
  scanChange?: (cwd: string, signal?: AbortSignal, baselinePaths?: string[]) => Promise<Finding[] | null>;
570
+ /** Told as each command starts and ends (the session's stream). Absent: nobody hears it. */
571
+ onCheck?: (event: VerifyCheckEvent) => void;
538
572
  }
539
573
  /**
540
574
  * One workstream as the scoped checker sees it: the name its findings are
@@ -636,6 +670,8 @@ export interface MakeTakeVerifyBaselineOpts {
636
670
  now?: () => number;
637
671
  treeStatus?: (cwd: string, signal?: AbortSignal) => Promise<string>;
638
672
  undoTreeWrites?: (cwd: string, entries: TreeEntry[]) => Promise<void>;
673
+ /** Told as each command starts and ends on the clean tree (the session's stream). */
674
+ onCheck?: (event: VerifyCheckEvent) => void;
639
675
  }
640
676
  /** What the baseline run found, for the gate and for the run's log. */
641
677
  export interface VerifyBaselineResult {
@@ -698,6 +734,8 @@ export interface MakeMeasureChecksOnBaseOpts {
698
734
  now?: () => number;
699
735
  treeStatus?: (cwd: string, signal?: AbortSignal) => Promise<string>;
700
736
  undoTreeWrites?: (cwd: string, entries: TreeEntry[]) => Promise<void>;
737
+ /** Told as each check starts and ends on the unchanged code (the session's stream). */
738
+ onCheck?: (event: VerifyCheckEvent) => void;
701
739
  }
702
740
  /**
703
741
  * Run each approved check `checks.json` marks `measureOnBase` once on the
@@ -195,6 +195,44 @@ export function changesGitState(command) {
195
195
  }
196
196
  return false;
197
197
  }
198
+ /**
199
+ * How the note of an approved check this gate run did not run starts (its
200
+ * budget was spent, it was not reached, the build stopped first, the
201
+ * typecheck half failed first). The host's pull request body reads these
202
+ * words (`CHECK_NOT_RUN_NOTE` in @yagni/shared's attempts.ts); a change here
203
+ * is a change there.
204
+ */
205
+ export const CHECK_NOT_RUN_NOTE = "not run on this tree";
206
+ /** How much of a failed command's output its end carries, for the host's own log. */
207
+ const CHECK_TAIL_CHARS = 400;
208
+ /** How a check went the last time it ran, after its not-run note; nothing when it never ran. */
209
+ function earlierRunWords(last) {
210
+ if (last === undefined)
211
+ return "";
212
+ if (last.exitCode === null)
213
+ return "; it gave no verdict on an earlier tree";
214
+ if (last.exitCode === 0)
215
+ return "; it passed on an earlier tree";
216
+ return `; it failed on an earlier tree (exit ${last.exitCode})`;
217
+ }
218
+ /** The end of a command's output, scrubbed, as a check's end carries it. */
219
+ function checkTail(output) {
220
+ return scrubSecrets(output).trimEnd().slice(-CHECK_TAIL_CHARS);
221
+ }
222
+ /** A recorded run's end, as the stream hears it: the output's tail only when it did not pass. */
223
+ function checkEnded(record, output) {
224
+ const tail = output && record.exitCode !== 0 ? checkTail(output) : "";
225
+ return {
226
+ phase: "end",
227
+ name: record.name,
228
+ command: record.command,
229
+ cwd: record.cwd,
230
+ exitCode: record.exitCode,
231
+ durationMs: record.durationMs,
232
+ baseline: record.baseline,
233
+ ...(tail ? { tail } : {}),
234
+ };
235
+ }
198
236
  /**
199
237
  * Why a command threw instead of exiting, as its check record says it. `run`
200
238
  * is the run's own signal (a /stop or the run's end); `capped` is the one
@@ -1401,6 +1439,54 @@ export function makeRunVerify(opts = {}) {
1401
1439
  // keys from the last call it failed in.
1402
1440
  let repoChecksSpentMs = 0;
1403
1441
  const lastCiFailure = new Map();
1442
+ // Each approved check's last run in this build, by command (trimmed).
1443
+ const lastApproved = new Map();
1444
+ /**
1445
+ * The approved checks as one call reports them, in `checks.json`'s order:
1446
+ * one the sandbox cannot run with no verdict (#2694), one this call ran
1447
+ * with its record, and any other with no verdict on this tree, why, and how
1448
+ * it went the last time it ran. The cloud session's report is the last
1449
+ * call's: one that ran none of them (the build's check budget spent, the
1450
+ * typecheck half failing first) used to drop every one, and a pull request
1451
+ * whose plan named six checks said the session reported none (YAG-1031,
1452
+ * production 2026-10-25). A check that changes the checkout's git state is
1453
+ * never run and never listed.
1454
+ */
1455
+ function approvedRecords(file, ran, why) {
1456
+ return file.checks.flatMap((check) => {
1457
+ if (check.status === "cannot_run") {
1458
+ return [
1459
+ {
1460
+ name: checkLabel(check.command),
1461
+ command: check.command,
1462
+ cwd: ".",
1463
+ exitCode: null,
1464
+ durationMs: 0,
1465
+ baseline: "cannot_run",
1466
+ note: `could not run in the sandbox${check.cause ? `: ${check.cause}` : ""}`,
1467
+ },
1468
+ ];
1469
+ }
1470
+ if (changesGitState(check.command))
1471
+ return [];
1472
+ const key = check.command.trim();
1473
+ const record = ran.get(key);
1474
+ if (record)
1475
+ return [record];
1476
+ const last = lastApproved.get(key);
1477
+ return [
1478
+ {
1479
+ name: checkLabel(check.command),
1480
+ command: check.command,
1481
+ cwd: ".",
1482
+ exitCode: null,
1483
+ durationMs: 0,
1484
+ baseline: last?.baseline ?? measuredBaseline(opts.checksOnBase, check),
1485
+ note: `${CHECK_NOT_RUN_NOTE}: ${why}${earlierRunWords(last)}`,
1486
+ },
1487
+ ];
1488
+ });
1489
+ }
1404
1490
  /**
1405
1491
  * The repository-checks half: the plan's approved checks, then the pull
1406
1492
  * request's CI steps its changed paths trigger. A failure becomes `critical`
@@ -1417,22 +1503,12 @@ export function makeRunVerify(opts = {}) {
1417
1503
  const labels = [];
1418
1504
  const cannotRun = [];
1419
1505
  // An approved check the sandbox cannot run is on the report as one with no
1420
- // verdict, so a session that never ran it is never read as all green
1421
- // (#2694): the host leaves its pull request a draft.
1422
- const unrunnable = [];
1506
+ // verdict (`approvedRecords`), so a session that never ran it is never
1507
+ // read as all green (#2694): the host leaves its pull request a draft.
1423
1508
  for (const check of file.checks) {
1424
1509
  if (check.status !== "cannot_run")
1425
1510
  continue;
1426
1511
  cannotRun.push(`\`${check.command}\`${check.cause ? ` (${check.cause})` : ""}`);
1427
- unrunnable.push({
1428
- name: checkLabel(check.command),
1429
- command: check.command,
1430
- cwd: ".",
1431
- exitCode: null,
1432
- durationMs: 0,
1433
- baseline: "cannot_run",
1434
- note: `could not run in the sandbox${check.cause ? `: ${check.cause}` : ""}`,
1435
- });
1436
1512
  }
1437
1513
  const paths = await changedPaths(cwd, signal, input.baselinePaths);
1438
1514
  // A job the pull request's CI reported failed runs whatever its paths say.
@@ -1479,12 +1555,23 @@ export function makeRunVerify(opts = {}) {
1479
1555
  const steps = jobs.flatMap((job) => job.steps.map((step) => ({ job, step })));
1480
1556
  if (approved.length === 0 && steps.length === 0) {
1481
1557
  report();
1482
- return { findings, reasons, labels, checks: unrunnable };
1558
+ // Every approved check here is one the sandbox cannot run: none is "not run".
1559
+ return {
1560
+ findings,
1561
+ reasons,
1562
+ labels,
1563
+ checks: approvedRecords(file, new Map(), "the sandbox cannot run it"),
1564
+ };
1483
1565
  }
1484
1566
  const buildLeft = (opts.repoChecksBuildBudgetMs ?? REPO_CHECKS_BUILD_BUDGET_MS) - repoChecksSpentMs;
1485
1567
  if (buildLeft < REPO_CHECK_MIN_START_MS) {
1486
1568
  reasons.push("approved checks and CI steps not run again: the build's check budget is spent; the host still runs the approved checks after the build");
1487
- return { findings, reasons, labels, checks: unrunnable };
1569
+ return {
1570
+ findings,
1571
+ reasons,
1572
+ labels,
1573
+ checks: approvedRecords(file, new Map(), "the build's check budget is spent"),
1574
+ };
1488
1575
  }
1489
1576
  const callBudget = Math.min(opts.repoChecksCallBudgetMs ?? REPO_CHECKS_CALL_BUDGET_MS, timeoutMs - (now() - input.callStartedAt), buildLeft);
1490
1577
  const halfStartedAt = now();
@@ -1500,6 +1587,7 @@ export function makeRunVerify(opts = {}) {
1500
1587
  failedJobs,
1501
1588
  approvedFailed: new Set(),
1502
1589
  checks: [],
1590
+ approvedRuns: new Map(),
1503
1591
  };
1504
1592
  const before = await treeStatus(cwd, signal).catch((err) => {
1505
1593
  const msg = err instanceof Error ? err.message : String(err);
@@ -1533,7 +1621,19 @@ export function makeRunVerify(opts = {}) {
1533
1621
  if (run.notReached > 0) {
1534
1622
  reasons.push(`${run.notReached} approved check(s) or CI step(s) not reached in the gate's time budget; the host still runs the approved checks after the build`);
1535
1623
  }
1536
- return { findings, reasons, labels, checks: [...unrunnable, ...run.checks] };
1624
+ const approvedRan = new Set(run.approvedRuns.values());
1625
+ const whyNotRun = signal?.aborted
1626
+ ? "the build stopped first"
1627
+ : "not reached in the gate's time budget";
1628
+ return {
1629
+ findings,
1630
+ reasons,
1631
+ labels,
1632
+ checks: [
1633
+ ...approvedRecords(file, run.approvedRuns, whyNotRun),
1634
+ ...run.checks.filter((record) => !approvedRan.has(record)),
1635
+ ],
1636
+ };
1537
1637
  }
1538
1638
  /**
1539
1639
  * One repository command's verdict: passed, a note (it could not run), or a
@@ -1543,6 +1643,8 @@ export function makeRunVerify(opts = {}) {
1543
1643
  * (`needs` says what). Only the session that measured knows it passed: a
1544
1644
  * resumed session's host carries forward what failed on the base, never
1545
1645
  * what passed, so there the same check is could not run, failing open.
1646
+ * The stream hears the command start, and its end here unless it failed:
1647
+ * a failure's caller tells its end once it decided whose the failure is.
1546
1648
  */
1547
1649
  async function runRepoCommand(run, command, dir, stepEnv, check) {
1548
1650
  const capMs = Math.min(opts.repoCheckTimeoutMs ?? REPO_CHECK_TIMEOUT_MS, run.left());
@@ -1550,6 +1652,8 @@ export function makeRunVerify(opts = {}) {
1550
1652
  const env = opts.repoChecksEnv
1551
1653
  ? { ...opts.repoChecksEnv, ...(stepEnv ?? {}) }
1552
1654
  : buildRepoCheckEnv(process.env, run.toolchainFile, stepEnv);
1655
+ const cwd = relative(run.cwd, dir) || ".";
1656
+ opts.onCheck?.({ phase: "start", name: check.name, command, cwd });
1553
1657
  const startedAt = now();
1554
1658
  try {
1555
1659
  const { code, output: rawOutput } = await exec(repoCheckArgv(command), dir, composed, env);
@@ -1557,7 +1661,7 @@ export function makeRunVerify(opts = {}) {
1557
1661
  const entry = {
1558
1662
  name: check.name,
1559
1663
  command,
1560
- cwd: relative(run.cwd, dir) || ".",
1664
+ cwd,
1561
1665
  exitCode: note ? null : code,
1562
1666
  durationMs: now() - startedAt,
1563
1667
  baseline,
@@ -1567,14 +1671,16 @@ export function makeRunVerify(opts = {}) {
1567
1671
  return entry;
1568
1672
  };
1569
1673
  if (code === 0) {
1570
- record(check.baseline);
1674
+ const entry = record(check.baseline);
1675
+ opts.onCheck?.(checkEnded(entry));
1571
1676
  return { kind: "passed" };
1572
1677
  }
1573
1678
  const output = scrubSecrets(rawOutput);
1574
1679
  const cls = classifyTestFailure(output, code, MAX_LOCATED_FINDINGS, env);
1575
1680
  if (cls.kind === "infra" && !(cls.needs && check.passedOnBase)) {
1576
1681
  const reason = `could not run: ${cls.reason}`;
1577
- record("cannot_run", `${reason}; ${exitSummary(check.name, code, output)}`);
1682
+ const entry = record("cannot_run", `${reason}; ${exitSummary(check.name, code, output)}`);
1683
+ opts.onCheck?.(checkEnded(entry, output));
1578
1684
  return { kind: "note", reason };
1579
1685
  }
1580
1686
  return {
@@ -1587,15 +1693,17 @@ export function makeRunVerify(opts = {}) {
1587
1693
  };
1588
1694
  }
1589
1695
  catch (err) {
1590
- run.checks.push({
1696
+ const entry = {
1591
1697
  name: check.name,
1592
1698
  command,
1593
- cwd: relative(run.cwd, dir) || ".",
1699
+ cwd,
1594
1700
  exitCode: null,
1595
1701
  durationMs: now() - startedAt,
1596
1702
  baseline: check.baseline,
1597
1703
  note: unexitedNote(err, run.signal, composed),
1598
- });
1704
+ };
1705
+ run.checks.push(entry);
1706
+ opts.onCheck?.(checkEnded(entry));
1599
1707
  const msg = err instanceof Error ? err.message : String(err);
1600
1708
  return { kind: "note", reason: `could not run: ${scrubSecrets(msg)}` };
1601
1709
  }
@@ -1628,11 +1736,18 @@ export function makeRunVerify(opts = {}) {
1628
1736
  // The failures it had there, when the session measured it failing.
1629
1737
  const failedOnBase = opts.checksOnBase?.failed.get(check.command.trim());
1630
1738
  const status = measuredBaseline(opts.checksOnBase, check);
1739
+ const recorded = run.checks.length;
1631
1740
  const verdict = await runRepoCommand(run, check.command, run.cwd, undefined, {
1632
1741
  name: label,
1633
1742
  baseline: status,
1634
1743
  passedOnBase: opts.checksOnBase?.passed.has(check.command.trim()) ?? false,
1635
1744
  });
1745
+ // `runRepoCommand` records every run once, whatever its verdict.
1746
+ const ran = run.checks[recorded];
1747
+ if (ran) {
1748
+ run.approvedRuns.set(check.command.trim(), ran);
1749
+ lastApproved.set(check.command.trim(), ran);
1750
+ }
1636
1751
  if (verdict.kind === "failed")
1637
1752
  run.approvedFailed.add(canonicalCheckCommand(check.command));
1638
1753
  if (verdict.kind === "failed" && verdict.needs) {
@@ -1668,6 +1783,8 @@ export function makeRunVerify(opts = {}) {
1668
1783
  run.findings.push(...found.slice(0, findingRoom(run)));
1669
1784
  }
1670
1785
  }
1786
+ if (verdict.kind === "failed")
1787
+ opts.onCheck?.(checkEnded(verdict.record, verdict.output));
1671
1788
  }
1672
1789
  }
1673
1790
  /**
@@ -1742,6 +1859,7 @@ export function makeRunVerify(opts = {}) {
1742
1859
  continue;
1743
1860
  }
1744
1861
  settle(job, "failed");
1862
+ opts.onCheck?.(checkEnded(verdict.record, verdict.output));
1745
1863
  const stepFindings = repoFailureFindings(run, verdict, step.name, where, dir);
1746
1864
  const keys = new Set(stepFindings.map((f) => f.message));
1747
1865
  const previous = lastCiFailure.get(key);
@@ -1791,15 +1909,27 @@ export function makeRunVerify(opts = {}) {
1791
1909
  const counts = opts.baseline?.get(verifyCommandKey(command));
1792
1910
  return counts && [...counts.values()].some((n) => n > 0) ? "fails_on_base" : "runnable";
1793
1911
  };
1794
- const recordCommand = (command, code, startedAt, baseline, note) => checks.push({
1912
+ /** A detected command starting, as the stream hears it. */
1913
+ const commandStarted = (command) => opts.onCheck?.({
1914
+ phase: "start",
1795
1915
  name: command.label,
1796
1916
  command: command.argv.join(" "),
1797
1917
  cwd: relative(cwd, command.cwd) || ".",
1798
- exitCode: code,
1799
- durationMs: now() - startedAt,
1800
- baseline,
1801
- ...(note ? { note } : {}),
1802
1918
  });
1919
+ /** A detected command's record, its end told to the stream with its output's tail. */
1920
+ const recordCommand = (command, code, startedAt, baseline, more = {}) => {
1921
+ const entry = {
1922
+ name: command.label,
1923
+ command: command.argv.join(" "),
1924
+ cwd: relative(cwd, command.cwd) || ".",
1925
+ exitCode: code,
1926
+ durationMs: now() - startedAt,
1927
+ baseline,
1928
+ ...(more.note ? { note: more.note } : {}),
1929
+ };
1930
+ checks.push(entry);
1931
+ opts.onCheck?.(checkEnded(entry, more.output));
1932
+ };
1803
1933
  // TYPECHECK HALF (frozen behavior, §0.2): run every changed package's build
1804
1934
  // check; union the findings. One package that cannot spawn fails OPEN (a
1805
1935
  // recorded reason, no finding) without sinking the verdict for the packages
@@ -1808,6 +1938,7 @@ export function makeRunVerify(opts = {}) {
1808
1938
  const typecheckEnv = buildGateVerifyEnv(process.env, toolchainFile);
1809
1939
  for (const command of commands) {
1810
1940
  const composed = composeAbortSignal(signal, AbortSignal.timeout(timeoutMs));
1941
+ commandStarted(command);
1811
1942
  const startedAt = now();
1812
1943
  try {
1813
1944
  const { code, output: rawOutput } = await exec(command.argv, command.cwd, composed, typecheckEnv);
@@ -1820,11 +1951,14 @@ export function makeRunVerify(opts = {}) {
1820
1951
  const infra = classifyVerifyInfraFailure(output, code);
1821
1952
  if (infra) {
1822
1953
  reasons.push(`verify (${command.label}) could not run: ${infra}`);
1823
- recordCommand(command, null, startedAt, "cannot_run", `could not run: ${infra}; ${exitSummary(command.label, code, output)}`);
1954
+ recordCommand(command, null, startedAt, "cannot_run", {
1955
+ note: `could not run: ${infra}; ${exitSummary(command.label, code, output)}`,
1956
+ output,
1957
+ });
1824
1958
  continue;
1825
1959
  }
1826
1960
  }
1827
- recordCommand(command, code, startedAt, standing(command));
1961
+ recordCommand(command, code, startedAt, standing(command), { output });
1828
1962
  anyRan = true;
1829
1963
  typecheckLabels.push(command.label);
1830
1964
  if (code !== 0) {
@@ -1835,7 +1969,9 @@ export function makeRunVerify(opts = {}) {
1835
1969
  }
1836
1970
  }
1837
1971
  catch (err) {
1838
- recordCommand(command, null, startedAt, standing(command), unexitedNote(err, signal, composed));
1972
+ recordCommand(command, null, startedAt, standing(command), {
1973
+ note: unexitedNote(err, signal, composed),
1974
+ });
1839
1975
  const msg = err instanceof Error ? err.message : String(err);
1840
1976
  reasons.push(`verify (${command.label}) could not run: ${scrubSecrets(msg)}`);
1841
1977
  }
@@ -1871,13 +2007,17 @@ export function makeRunVerify(opts = {}) {
1871
2007
  continue;
1872
2008
  }
1873
2009
  const composed = composeAbortSignal(signal, AbortSignal.timeout(testTimeoutMs));
2010
+ commandStarted(command);
1874
2011
  const startedAt = now();
1875
2012
  try {
1876
2013
  const { code, output: rawOutput } = await exec(command.argv, command.cwd, composed, testEnv);
1877
2014
  const output = scrubSecrets(rawOutput);
1878
2015
  const cls = classifyTestFailure(output, code, Number.POSITIVE_INFINITY, testEnv);
1879
2016
  if (cls.kind === "infra") {
1880
- recordCommand(command, null, startedAt, "cannot_run", `could not run: ${cls.reason}; ${exitSummary(command.label, code, output)}`);
2017
+ recordCommand(command, null, startedAt, "cannot_run", {
2018
+ note: `could not run: ${cls.reason}; ${exitSummary(command.label, code, output)}`,
2019
+ output,
2020
+ });
1881
2021
  reasons.push(`tests (${where}) could not run: ${cls.reason}; 'clean' reflects typecheck + review only`);
1882
2022
  continue;
1883
2023
  }
@@ -1887,19 +2027,22 @@ export function makeRunVerify(opts = {}) {
1887
2027
  testLabels.push(command.label);
1888
2028
  if (base === "fails_on_base") {
1889
2029
  const summary = exitSummary(command.label, code, output);
1890
- recordCommand(command, code, startedAt, base, `failed on the unchanged code too: ${summary}`);
2030
+ recordCommand(command, code, startedAt, base, {
2031
+ note: `failed on the unchanged code too: ${summary}`,
2032
+ output,
2033
+ });
1891
2034
  reasons.push(`tests (${where}) failed on the unchanged code too, so not a finding: ${summary}`);
1892
2035
  }
1893
2036
  else {
1894
2037
  // One finding per command, past the cap too: it is the only
1895
2038
  // word of this command's failure.
1896
- recordCommand(command, code, startedAt, base);
2039
+ recordCommand(command, code, startedAt, base, { output });
1897
2040
  testFindingCount += 1;
1898
2041
  findings.push(tailFinding(command.label, code, output));
1899
2042
  }
1900
2043
  continue;
1901
2044
  }
1902
- recordCommand(command, code, startedAt, base);
2045
+ recordCommand(command, code, startedAt, base, { output });
1903
2046
  if (cls.kind === "uncertain") {
1904
2047
  // Failing OPEN on an uninterpretable failure (§3d): an honest note,
1905
2048
  // never a guessed finding the fix stage cannot clear.
@@ -1918,7 +2061,9 @@ export function makeRunVerify(opts = {}) {
1918
2061
  }
1919
2062
  }
1920
2063
  catch (err) {
1921
- recordCommand(command, null, startedAt, standing(command), unexitedNote(err, signal, composed));
2064
+ recordCommand(command, null, startedAt, standing(command), {
2065
+ note: unexitedNote(err, signal, composed),
2066
+ });
1922
2067
  const msg = err instanceof Error ? err.message : String(err);
1923
2068
  reasons.push(`tests (${where}) could not run: ${scrubSecrets(msg)}`);
1924
2069
  }
@@ -1932,6 +2077,7 @@ export function makeRunVerify(opts = {}) {
1932
2077
  if (repoFile) {
1933
2078
  if (typecheckFailed) {
1934
2079
  reasons.push("approved checks and CI steps skipped: the typecheck half failed; a later round runs them");
2080
+ checks.push(...approvedRecords(repoFile, new Map(), "the typecheck half failed first"));
1935
2081
  }
1936
2082
  else {
1937
2083
  const repo = await runRepoChecks({
@@ -2056,7 +2202,8 @@ export function makeTakeVerifyBaseline(opts = {}) {
2056
2202
  for (const { command, test } of runs) {
2057
2203
  if (signal?.aborted)
2058
2204
  break;
2059
- const left = budgetMs - (now() - startedAt);
2205
+ const commandStartedAt = now();
2206
+ const left = budgetMs - (commandStartedAt - startedAt);
2060
2207
  if (left < REPO_CHECK_MIN_START_MS) {
2061
2208
  result.notes.push(`${command.label} (${command.cwd}): not reached in the baseline budget`);
2062
2209
  continue;
@@ -2065,8 +2212,23 @@ export function makeTakeVerifyBaseline(opts = {}) {
2065
2212
  ? (opts.testTimeoutMs ?? VERIFY_TEST_TIMEOUT_MS)
2066
2213
  : (opts.timeoutMs ?? VERIFY_TIMEOUT_MS), left);
2067
2214
  const composed = composeAbortSignal(signal, AbortSignal.timeout(cap));
2215
+ const heard = {
2216
+ name: command.label,
2217
+ command: command.argv.join(" "),
2218
+ cwd: relative(cwd, command.cwd) || ".",
2219
+ base: true,
2220
+ };
2221
+ opts.onCheck?.({ phase: "start", ...heard });
2222
+ const ended = (exitCode, output) => opts.onCheck?.({
2223
+ phase: "end",
2224
+ ...heard,
2225
+ exitCode,
2226
+ durationMs: now() - commandStartedAt,
2227
+ ...(output && exitCode !== 0 ? { tail: checkTail(output) } : {}),
2228
+ });
2068
2229
  try {
2069
2230
  const { code, output: rawOutput } = await exec(command.argv, command.cwd, composed, test ? testEnv : verifyEnv);
2231
+ ended(code, rawOutput);
2070
2232
  const output = scrubSecrets(rawOutput);
2071
2233
  let failed;
2072
2234
  if (test) {
@@ -2095,6 +2257,7 @@ export function makeTakeVerifyBaseline(opts = {}) {
2095
2257
  result.failures += n;
2096
2258
  }
2097
2259
  catch (err) {
2260
+ ended(null);
2098
2261
  const msg = err instanceof Error ? err.message : String(err);
2099
2262
  result.notes.push(`${command.label} (${command.cwd}): could not run: ${scrubSecrets(msg)}`);
2100
2263
  }
@@ -2170,14 +2333,25 @@ export function makeMeasureChecksOnBase(opts = {}) {
2170
2333
  break;
2171
2334
  const command = check.command.trim();
2172
2335
  const label = checkLabel(check.command);
2173
- const left = budgetMs - (now() - startedAt);
2336
+ const checkStartedAt = now();
2337
+ const left = budgetMs - (checkStartedAt - startedAt);
2174
2338
  if (left < REPO_CHECK_MIN_START_MS) {
2175
2339
  result.notes.push(`\`${label}\`: not reached in the budget`);
2176
2340
  continue;
2177
2341
  }
2178
2342
  const composed = composeAbortSignal(signal, AbortSignal.timeout(Math.min(opts.timeoutMs ?? REPO_CHECK_TIMEOUT_MS, left)));
2343
+ const heard = { name: label, command: check.command, cwd: ".", base: true };
2344
+ opts.onCheck?.({ phase: "start", ...heard });
2345
+ const ended = (exitCode, output) => opts.onCheck?.({
2346
+ phase: "end",
2347
+ ...heard,
2348
+ exitCode,
2349
+ durationMs: now() - checkStartedAt,
2350
+ ...(output && exitCode !== 0 ? { tail: checkTail(output) } : {}),
2351
+ });
2179
2352
  try {
2180
2353
  const { code, output: rawOutput } = await exec(repoCheckArgv(check.command), cwd, composed, env);
2354
+ ended(code, rawOutput);
2181
2355
  if (code === 0) {
2182
2356
  passed.add(command);
2183
2357
  result.ran.push(label);
@@ -2206,6 +2380,7 @@ export function makeMeasureChecksOnBase(opts = {}) {
2206
2380
  result.ran.push(label);
2207
2381
  }
2208
2382
  catch (err) {
2383
+ ended(null);
2209
2384
  result.notes.push(`\`${label}\`: ${unexitedNote(err, signal, composed)}`);
2210
2385
  }
2211
2386
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yagni-app/code-staging",
3
- "version": "1.2.7-staging.2035.1",
3
+ "version": "1.2.7-staging.2056.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": "83d927d6ece210d0734c46e6654c34bf89ecb565"
61
+ "yagniSourceSha": "76a36c6b94db739ea0fb0544cbe9a26279e6881c"
62
62
  }