@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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
|
|
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
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
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
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
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,
|
|
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.
|
|
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": "
|
|
61
|
+
"yagniSourceSha": "0cc9459eb25b5a357e700f90b803ff0adf194a0e"
|
|
62
62
|
}
|