@yagni-app/code-staging 1.2.6-staging.1918.1 → 1.2.6-staging.1932.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.
|
@@ -244,7 +244,9 @@ function pickScript(pkgDir, readFile, pm) {
|
|
|
244
244
|
*/
|
|
245
245
|
export function detectVerifyCommands(cwd, opts = {}) {
|
|
246
246
|
const readFile = opts.readFile ?? ((p) => readFileSync(p, "utf8"));
|
|
247
|
-
const override = opts.allowRepoOverride
|
|
247
|
+
const override = opts.allowRepoOverride
|
|
248
|
+
? tryRead(readFile, join(cwd, ".yagni-code", "verify"))
|
|
249
|
+
: null;
|
|
248
250
|
if (override) {
|
|
249
251
|
for (const raw of override.split("\n")) {
|
|
250
252
|
const line = raw.trim();
|
|
@@ -380,13 +382,22 @@ export function classifyVerifyInfraFailure(output, exitCode) {
|
|
|
380
382
|
* the failure parsers and, past those, to fail-open UNCERTAIN.
|
|
381
383
|
*/
|
|
382
384
|
const TEST_INFRA_PATTERNS = [
|
|
383
|
-
[
|
|
385
|
+
[
|
|
386
|
+
/\bECONNREFUSED\b|\bconnection refused\b/i,
|
|
387
|
+
"connection refused (is the test's DB/service running?)",
|
|
388
|
+
],
|
|
384
389
|
[/\bECONNRESET\b/, "connection reset by the test's DB/service"],
|
|
385
390
|
[/\bENOTFOUND\b|\bEAI_AGAIN\b/, "DNS/host resolution failed for a test dependency"],
|
|
386
391
|
[/\bEADDRINUSE\b/, "a port the tests need is already in use"],
|
|
387
392
|
[/\bETIMEDOUT\b|\bconnect(?:ion)? timed out\b/i, "connection to a test dependency timed out"],
|
|
388
|
-
[
|
|
389
|
-
|
|
393
|
+
[
|
|
394
|
+
/\bmissing (?:required )?env(?:ironment)? (?:var(?:iable)?s?)\b/i,
|
|
395
|
+
"missing environment variable",
|
|
396
|
+
],
|
|
397
|
+
[
|
|
398
|
+
/\benvironment variable\b[^\n]{0,80}\b(?:is )?(?:not set|missing|required|must be set)\b/i,
|
|
399
|
+
"missing environment variable",
|
|
400
|
+
],
|
|
390
401
|
[/\bprocess\.env\.[A-Z0-9_]+ is (?:undefined|not set)\b/, "missing environment variable"],
|
|
391
402
|
[/\bSASL\b|\bSCRAM-SERVER\b/, "database auth failed (missing DB credentials?)"],
|
|
392
403
|
[/\bpassword authentication failed\b/i, "database auth failed"],
|
|
@@ -395,6 +406,14 @@ const TEST_INFRA_PATTERNS = [
|
|
|
395
406
|
[/\brelation "[^"]*" does not exist\b/i, "database schema missing (migrations not run?)"],
|
|
396
407
|
[/\bauthentication (?:failed|error)\b/i, "authentication against a test dependency failed"],
|
|
397
408
|
[/\b401 Unauthorized\b|\b403 Forbidden\b/i, "authorization against a test dependency failed"],
|
|
409
|
+
// A container image that would not come down is the registry, not the
|
|
410
|
+
// change (NMT-71: ghcr.io answered EOF to supabase start).
|
|
411
|
+
[
|
|
412
|
+
/\bfailed to pull (?:docker )?image\b|\bError response from daemon: (?:Get|Head|pull|manifest)\b|\bpull access denied\b|\btoomanyrequests\b/i,
|
|
413
|
+
"a container image could not be pulled (registry unreachable or rate-limited)",
|
|
414
|
+
],
|
|
415
|
+
[/\bCannot connect to the Docker daemon\b/i, "Docker is not running"],
|
|
416
|
+
[/\bno space left on device\b/i, "the disk is full"],
|
|
398
417
|
];
|
|
399
418
|
/** Strip a trailing `(4 ms)` / `(0.82ms)` duration off a failing-test name. */
|
|
400
419
|
function stripDuration(name) {
|
|
@@ -507,7 +526,11 @@ export function classifyTestFailure(output, exitCode) {
|
|
|
507
526
|
return {
|
|
508
527
|
kind: "failures",
|
|
509
528
|
findings: failures.map((f) => {
|
|
510
|
-
const finding = {
|
|
529
|
+
const finding = {
|
|
530
|
+
severity: "critical",
|
|
531
|
+
lens: VERIFY_LENS,
|
|
532
|
+
message: `verify: test failed: ${f.message}`,
|
|
533
|
+
};
|
|
511
534
|
if (f.file)
|
|
512
535
|
finding.file = f.file;
|
|
513
536
|
if (f.line)
|
|
@@ -564,7 +587,13 @@ export function parseVerifyFailures(output, exitCode, label) {
|
|
|
564
587
|
if (seen.has(key))
|
|
565
588
|
continue;
|
|
566
589
|
seen.add(key);
|
|
567
|
-
located.push({
|
|
590
|
+
located.push({
|
|
591
|
+
severity: "critical",
|
|
592
|
+
lens: VERIFY_LENS,
|
|
593
|
+
message: `verify: ${message}`,
|
|
594
|
+
file,
|
|
595
|
+
line: lineNo,
|
|
596
|
+
});
|
|
568
597
|
if (located.length >= MAX_LOCATED_FINDINGS)
|
|
569
598
|
break;
|
|
570
599
|
}
|
|
@@ -720,7 +749,7 @@ export function newTreeEntries(before, after) {
|
|
|
720
749
|
}
|
|
721
750
|
function runGitQuiet(args, cwd) {
|
|
722
751
|
return new Promise((resolve, reject) => {
|
|
723
|
-
execFile("git", args, { cwd, maxBuffer: 1024 * 1024 }, (err) =>
|
|
752
|
+
execFile("git", args, { cwd, maxBuffer: 1024 * 1024 }, (err) => err ? reject(err) : resolve());
|
|
724
753
|
});
|
|
725
754
|
}
|
|
726
755
|
/** Undo what the repository checks wrote: an untracked path is removed, a tracked one restored. */
|
|
@@ -797,7 +826,10 @@ const defaultExec = makeDefaultExec();
|
|
|
797
826
|
*/
|
|
798
827
|
export function resolveClaimDirs(repoRoot, claims, readFile) {
|
|
799
828
|
const probes = claims
|
|
800
|
-
.map((c) => c
|
|
829
|
+
.map((c) => c
|
|
830
|
+
.trim()
|
|
831
|
+
.replace(/^\.\/+/, "")
|
|
832
|
+
.replace(/\/+$/, ""))
|
|
801
833
|
.filter((c) => c.length > 0 && c !== ".")
|
|
802
834
|
.map((c) => `${c}/.claim`);
|
|
803
835
|
return resolvePackageDirs(repoRoot, probes, readFile);
|
|
@@ -832,8 +864,10 @@ function tagFinding(finding, name) {
|
|
|
832
864
|
* and produces no findings.
|
|
833
865
|
*/
|
|
834
866
|
export function makeWorkstreamCheck(opts = {}) {
|
|
835
|
-
const claimDirs = opts.claimDirs ??
|
|
836
|
-
|
|
867
|
+
const claimDirs = opts.claimDirs ??
|
|
868
|
+
((cwd, claims) => resolveClaimDirs(cwd, claims, (p) => readFileSync(p, "utf8")));
|
|
869
|
+
const detect = opts.detect ??
|
|
870
|
+
((cwd, dirs) => detectVerifyCommands(cwd, { changedDirs: dirs }));
|
|
837
871
|
const exec = opts.exec ?? defaultExec;
|
|
838
872
|
const timeoutMs = opts.timeoutMs ?? VERIFY_TIMEOUT_MS;
|
|
839
873
|
return async function runWorkstreamCheck(cwd, targets, signal) {
|
|
@@ -923,7 +957,8 @@ export function makeRunVerify(opts = {}) {
|
|
|
923
957
|
allowRepoOverride: process.env.YAGNI_CODE_ALLOW_VERIFY_OVERRIDE === "1",
|
|
924
958
|
changedDirs,
|
|
925
959
|
}));
|
|
926
|
-
const detectTests = opts.detectTests ??
|
|
960
|
+
const detectTests = opts.detectTests ??
|
|
961
|
+
((cwd, changedDirs) => detectVerifyTestCommands(cwd, { changedDirs }));
|
|
927
962
|
const changedDirs = opts.changedDirs ??
|
|
928
963
|
(async (cwd, signal, baselinePaths) => {
|
|
929
964
|
const snapshot = await snapshotWorkspace(cwd, signal);
|
|
@@ -951,7 +986,8 @@ export function makeRunVerify(opts = {}) {
|
|
|
951
986
|
const path = process.env.YAGNI_TOOLCHAIN_FILE?.trim();
|
|
952
987
|
return path ? readToolchainFile(path) : null;
|
|
953
988
|
});
|
|
954
|
-
const treeStatus = opts.treeStatus ??
|
|
989
|
+
const treeStatus = opts.treeStatus ??
|
|
990
|
+
(async (cwd, signal) => (await snapshotWorkspace(cwd, signal)).status);
|
|
955
991
|
const undoTreeWrites = opts.undoTreeWrites ?? defaultUndoTreeWrites;
|
|
956
992
|
// Across the whole build: the half's time spent, and each CI step's failure
|
|
957
993
|
// keys from the last call it failed in.
|
|
@@ -992,19 +1028,29 @@ export function makeRunVerify(opts = {}) {
|
|
|
992
1028
|
continue;
|
|
993
1029
|
failedJobs.set(job, job.steps.length > 0
|
|
994
1030
|
? { name: job.failedCheck.name, status: "not_reached" }
|
|
995
|
-
: {
|
|
1031
|
+
: {
|
|
1032
|
+
name: job.failedCheck.name,
|
|
1033
|
+
status: "cannot_run",
|
|
1034
|
+
reason: job.cannotRun[0]?.reason ?? "none of its steps can run in the sandbox",
|
|
1035
|
+
});
|
|
996
1036
|
}
|
|
997
1037
|
const report = () => {
|
|
998
1038
|
if (failedJobs.size === 0 && !file.failedChecksCannotRun?.length)
|
|
999
1039
|
return;
|
|
1000
1040
|
opts.onFailedChecks?.([
|
|
1001
1041
|
...failedJobs.values(),
|
|
1002
|
-
...(file.failedChecksCannotRun ?? []).map((failed) => ({
|
|
1042
|
+
...(file.failedChecksCannotRun ?? []).map((failed) => ({
|
|
1043
|
+
name: failed.name,
|
|
1044
|
+
status: "cannot_run",
|
|
1045
|
+
reason: failed.reason,
|
|
1046
|
+
})),
|
|
1003
1047
|
]);
|
|
1004
1048
|
};
|
|
1005
1049
|
if (cannotRun.length > 0) {
|
|
1006
1050
|
const named = cannotRun.slice(0, MAX_NAMED_CANNOT_RUN).join("; ");
|
|
1007
|
-
const more = cannotRun.length > MAX_NAMED_CANNOT_RUN
|
|
1051
|
+
const more = cannotRun.length > MAX_NAMED_CANNOT_RUN
|
|
1052
|
+
? `; and ${cannotRun.length - MAX_NAMED_CANNOT_RUN} more`
|
|
1053
|
+
: "";
|
|
1008
1054
|
reasons.push(`could not run in the sandbox: ${named}${more}`);
|
|
1009
1055
|
}
|
|
1010
1056
|
const approved = file.checks.filter((check) => check.status !== "cannot_run");
|
|
@@ -1091,8 +1137,13 @@ export function makeRunVerify(opts = {}) {
|
|
|
1091
1137
|
/** A failure's findings, located from the folder the command ran in and prefixed with what ran. */
|
|
1092
1138
|
function repoFailureFindings(run, failure, label, prefix, dir) {
|
|
1093
1139
|
const cls = classifyTestFailure(failure.output, failure.code);
|
|
1094
|
-
const parsed = cls.kind === "failures"
|
|
1095
|
-
|
|
1140
|
+
const parsed = cls.kind === "failures"
|
|
1141
|
+
? cls.findings
|
|
1142
|
+
: parseVerifyFailures(failure.output, failure.code, label);
|
|
1143
|
+
return parsed.map((f) => ({
|
|
1144
|
+
...liftFinding(f, run.cwd, dir),
|
|
1145
|
+
message: `${prefix}: ${f.message}`,
|
|
1146
|
+
}));
|
|
1096
1147
|
}
|
|
1097
1148
|
const findingRoom = (run) => Math.max(0, MAX_LOCATED_FINDINGS - run.findings.length);
|
|
1098
1149
|
/**
|
|
@@ -1171,7 +1222,9 @@ export function makeRunVerify(opts = {}) {
|
|
|
1171
1222
|
}
|
|
1172
1223
|
const where = `CI ${job.name}, step "${step.name}"`;
|
|
1173
1224
|
const key = `${job.workflow}\u0000${job.job}\u0000${step.name}\u0000${step.run}`;
|
|
1174
|
-
const dir = step.workingDirectory
|
|
1225
|
+
const dir = step.workingDirectory
|
|
1226
|
+
? join(run.cwd, step.workingDirectory.replace(/^\.\/+/, ""))
|
|
1227
|
+
: run.cwd;
|
|
1175
1228
|
const verdict = await runRepoCommand(run, step.run, dir, step.env);
|
|
1176
1229
|
if (verdict.kind === "passed") {
|
|
1177
1230
|
lastCiFailure.delete(key);
|
|
@@ -1211,7 +1264,12 @@ export function makeRunVerify(opts = {}) {
|
|
|
1211
1264
|
// choice of how to verify, so an override never takes them out of the loop.
|
|
1212
1265
|
const repoFile = opts.repoChecks?.() ?? null;
|
|
1213
1266
|
if (commands.length === 0 && testCommands.length === 0 && !repoFile) {
|
|
1214
|
-
return {
|
|
1267
|
+
return {
|
|
1268
|
+
ran: false,
|
|
1269
|
+
ok: true,
|
|
1270
|
+
reason: "verify skipped: no verify command detected",
|
|
1271
|
+
findings: [],
|
|
1272
|
+
};
|
|
1215
1273
|
}
|
|
1216
1274
|
const findings = [];
|
|
1217
1275
|
const reasons = [];
|
|
@@ -1306,7 +1364,13 @@ export function makeRunVerify(opts = {}) {
|
|
|
1306
1364
|
reasons.push("approved checks and CI steps skipped: the typecheck half failed; a later round runs them");
|
|
1307
1365
|
}
|
|
1308
1366
|
else {
|
|
1309
|
-
const repo = await runRepoChecks({
|
|
1367
|
+
const repo = await runRepoChecks({
|
|
1368
|
+
file: repoFile,
|
|
1369
|
+
cwd,
|
|
1370
|
+
signal,
|
|
1371
|
+
baselinePaths,
|
|
1372
|
+
callStartedAt,
|
|
1373
|
+
});
|
|
1310
1374
|
reasons.push(...repo.reasons);
|
|
1311
1375
|
repoLabels.push(...repo.labels);
|
|
1312
1376
|
if (repo.labels.length > 0)
|
|
@@ -1317,9 +1381,19 @@ export function makeRunVerify(opts = {}) {
|
|
|
1317
1381
|
// The command labels list only what produced a verdict — on every half —
|
|
1318
1382
|
// so the FINISH trailer's `passed (<cmds>)` never claims a check that was
|
|
1319
1383
|
// infra-skipped or threw (invariant §0.1).
|
|
1320
|
-
const command = [
|
|
1384
|
+
const command = [
|
|
1385
|
+
...new Set(typecheckLabels),
|
|
1386
|
+
...new Set(testLabels),
|
|
1387
|
+
...new Set(repoLabels),
|
|
1388
|
+
].join(", ");
|
|
1321
1389
|
if (!anyRan) {
|
|
1322
|
-
return {
|
|
1390
|
+
return {
|
|
1391
|
+
ran: false,
|
|
1392
|
+
ok: true,
|
|
1393
|
+
command,
|
|
1394
|
+
reason: reasons.join("; ") || "verify skipped",
|
|
1395
|
+
findings: [],
|
|
1396
|
+
};
|
|
1323
1397
|
}
|
|
1324
1398
|
return {
|
|
1325
1399
|
ran: true,
|
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.1932.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": "15297691a28134646f9295adea6f7744d70e392f"
|
|
62
62
|
}
|