@yagni-app/code-staging 1.2.7-staging.2027.1 → 1.2.7-staging.2033.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.
|
@@ -496,8 +496,9 @@ export async function runHeadlessGo(argv, deps = {}) {
|
|
|
496
496
|
}
|
|
497
497
|
}
|
|
498
498
|
}
|
|
499
|
-
// The approved
|
|
500
|
-
//
|
|
499
|
+
// The approved checks the host has not seen pass on the unchanged code
|
|
500
|
+
// (`measureOnBase`: the plan's, and CI's when the repository's record has
|
|
501
|
+
// none of its own), each run once there before the build, on the same
|
|
501
502
|
// fresh tree the baseline above reads: a check that already failed there is
|
|
502
503
|
// not this change's to fix. A continuation or a fix round carries a change,
|
|
503
504
|
// so it measures nothing; its host hands it what an earlier session found.
|
|
@@ -23,7 +23,8 @@ export interface MissionCheck {
|
|
|
23
23
|
cause?: string;
|
|
24
24
|
/**
|
|
25
25
|
* The host has not seen it pass on the unchanged code (an approved plan's
|
|
26
|
-
* check
|
|
26
|
+
* check, or one CI runs that has no baseline): the session runs it there
|
|
27
|
+
* once before the build, and what that
|
|
27
28
|
* measures stands over `status` (`makeMeasureChecksOnBase` in verify.ts).
|
|
28
29
|
*/
|
|
29
30
|
measureOnBase?: boolean;
|
|
@@ -37,7 +37,10 @@
|
|
|
37
37
|
* anything UNCERTAIN fail OPEN with an honest note, never a never-clearable
|
|
38
38
|
* critical. A cloud session's gate (`unreadTestFailureIsFinding`) makes an
|
|
39
39
|
* UNCERTAIN failure a finding carrying the output tail, unless the clean tree
|
|
40
|
-
* failed the same command.
|
|
40
|
+
* failed the same command. On a build that named the repository's toolchain
|
|
41
|
+
* (`YAGNI_TOOLCHAIN_FILE`, a sandbox) both halves run on it, as the model's
|
|
42
|
+
* shell and the approved checks do ({@link buildGateVerifyEnv},
|
|
43
|
+
* {@link buildGateTestEnv}).
|
|
41
44
|
*
|
|
42
45
|
* Safe-by-default and FAIL-OPEN: if no scoped, non-fanned-out command is
|
|
43
46
|
* detectable, or the command cannot be spawned, or it times out, the gate
|
|
@@ -124,9 +127,10 @@ export declare function canonicalCheckCommand(command: string): string;
|
|
|
124
127
|
* True when a command changes the checkout's git state: a commit, a branch
|
|
125
128
|
* created or switched, a reset, a stash, a push. `git branch`, `git remote`
|
|
126
129
|
* and `git config` count as reads only in their listing forms. The same rule
|
|
127
|
-
* as `changesGitState` in the backend's
|
|
128
|
-
* a check out of the session's `checks.json`; both test files
|
|
129
|
-
* command corpus (test/fixtures/gitStateCommands.json), so they
|
|
130
|
+
* as `changesGitState` in the backend's repos/capabilities/repositoryChecks.ts,
|
|
131
|
+
* which leaves such a check out of the session's `checks.json`; both test files
|
|
132
|
+
* read one command corpus (test/fixtures/gitStateCommands.json), so they
|
|
133
|
+
* cannot drift.
|
|
130
134
|
*/
|
|
131
135
|
export declare function changesGitState(command: string): boolean;
|
|
132
136
|
/** One command the gate ran, and how it went. */
|
|
@@ -258,15 +262,36 @@ export declare function detectVerifyTestCommands(cwd: string, opts?: DetectOpts)
|
|
|
258
262
|
* verify failure.
|
|
259
263
|
*/
|
|
260
264
|
export declare function classifyVerifyInfraFailure(output: string, exitCode: number): string | null;
|
|
265
|
+
/**
|
|
266
|
+
* What a run could not start without, when its output says so in a way the
|
|
267
|
+
* gate recognises: a credential or variable the environment lacks, or a
|
|
268
|
+
* prerequisite the sandbox has not installed. Null for anything else, which
|
|
269
|
+
* stays a real failure. Pure.
|
|
270
|
+
*
|
|
271
|
+
* Narrow on purpose, since a match makes the run "could not run" rather than
|
|
272
|
+
* the change's failure:
|
|
273
|
+
* - a named variable counts only when `env` (the environment the command ran
|
|
274
|
+
* under) does not have it set: one it has is not why the run failed, and
|
|
275
|
+
* no unnamed phrase then stands in for it;
|
|
276
|
+
* - a line that quotes a test's expectation or a diff never counts, nor
|
|
277
|
+
* one a logger wrote below error level;
|
|
278
|
+
* - a repository's own "`A_B` is missing" counts only on an error's line.
|
|
279
|
+
*
|
|
280
|
+
* Without `env`, a named variable is taken as unset.
|
|
281
|
+
*/
|
|
282
|
+
export declare function classifyMissingPrerequisite(output: string, env?: NodeJS.ProcessEnv): string | null;
|
|
261
283
|
/** How the pure test classifier read one test run (spec §3d). */
|
|
262
284
|
export type TestFailureClassification = {
|
|
263
285
|
kind: "clean";
|
|
264
286
|
} | {
|
|
265
287
|
kind: "failures";
|
|
266
288
|
findings: Finding[];
|
|
267
|
-
}
|
|
289
|
+
}
|
|
290
|
+
/** `needs`: the credential or prerequisite the run lacked ({@link classifyMissingPrerequisite}). */
|
|
291
|
+
| {
|
|
268
292
|
kind: "infra";
|
|
269
293
|
reason: string;
|
|
294
|
+
needs?: string;
|
|
270
295
|
} | {
|
|
271
296
|
kind: "uncertain";
|
|
272
297
|
};
|
|
@@ -281,7 +306,9 @@ export type TestFailureClassification = {
|
|
|
281
306
|
* - infra/env shapes (command not found, module resolution, connection
|
|
282
307
|
* refused, missing env var, DB/auth, connect timeout) → `infra` with a short
|
|
283
308
|
* cause; checked FIRST so a suite whose assertions failed only because its
|
|
284
|
-
* DB was down fails open rather than blocking.
|
|
309
|
+
* DB was down fails open rather than blocking. A missing credential or
|
|
310
|
+
* prerequisite ({@link classifyMissingPrerequisite}, given the `env` the
|
|
311
|
+
* command ran under) is `infra` with `needs` and the cause "needs <it>".
|
|
285
312
|
* - recognisable failing-test lines (pnpm-wrapped vitest / jest / node:test
|
|
286
313
|
* TAP + spec reporters) → `failures` with located `critical` findings
|
|
287
314
|
* (capped at 25, deduped, locations attached when the runner printed them).
|
|
@@ -293,7 +320,7 @@ export type TestFailureClassification = {
|
|
|
293
320
|
* Callers scrub the output through {@link scrubSecrets} BEFORE classification
|
|
294
321
|
* so findings/notes never carry secrets (§0.9).
|
|
295
322
|
*/
|
|
296
|
-
export declare function classifyTestFailure(output: string, exitCode: number, limit?: number): TestFailureClassification;
|
|
323
|
+
export declare function classifyTestFailure(output: string, exitCode: number, limit?: number, env?: NodeJS.ProcessEnv): TestFailureClassification;
|
|
297
324
|
/**
|
|
298
325
|
* Turn a failing verify run into `critical` Findings. A clean (exit 0) run yields
|
|
299
326
|
* none. Located diagnostics (TypeScript first, then a generic file:line form, then
|
|
@@ -394,6 +421,22 @@ export declare function buildTestEnv(source?: NodeJS.ProcessEnv): NodeJS.Process
|
|
|
394
421
|
* own literal variables.
|
|
395
422
|
*/
|
|
396
423
|
export declare function buildRepoCheckEnv(source?: NodeJS.ProcessEnv, toolchain?: ToolchainFile | null, stepEnv?: Record<string, string>): NodeJS.ProcessEnv;
|
|
424
|
+
/**
|
|
425
|
+
* The environment the gate's own detected test script runs under. On a build
|
|
426
|
+
* that named a toolchain (a sandbox), an approved check's
|
|
427
|
+
* ({@link buildRepoCheckEnv}), so the suite runs on the repository's Node,
|
|
428
|
+
* Ruby or Go as the model's shell does, never the image's, and gives the same
|
|
429
|
+
* verdict it gives as an approved check. Without one (a developer's terminal),
|
|
430
|
+
* the user's own ({@link buildTestEnv}), as before.
|
|
431
|
+
*/
|
|
432
|
+
export declare function buildGateTestEnv(source?: NodeJS.ProcessEnv, toolchain?: ToolchainFile | null): NodeJS.ProcessEnv;
|
|
433
|
+
/**
|
|
434
|
+
* The environment the gate's own detected typecheck or lint script runs
|
|
435
|
+
* under: the scrubbed {@link buildVerifyEnv} on the build's toolchain, when it
|
|
436
|
+
* named one. Undefined without one, so the exec seam's default applies, as
|
|
437
|
+
* before.
|
|
438
|
+
*/
|
|
439
|
+
export declare function buildGateVerifyEnv(source?: NodeJS.ProcessEnv, toolchain?: ToolchainFile | null): NodeJS.ProcessEnv | undefined;
|
|
397
440
|
/** One porcelain line's path, and whether it is untracked. */
|
|
398
441
|
interface TreeEntry {
|
|
399
442
|
path: string;
|
|
@@ -431,7 +474,7 @@ export interface MakeRunVerifyOpts {
|
|
|
431
474
|
detectTests?: (cwd: string, changedDirs: string[]) => VerifyCommand[];
|
|
432
475
|
/** Per-package wall-clock cap for the test half (default VERIFY_TEST_TIMEOUT_MS). */
|
|
433
476
|
testTimeoutMs?: number;
|
|
434
|
-
/** Test-half env override for determinism in tests (default:
|
|
477
|
+
/** Test-half env override for determinism in tests (default: buildGateTestEnv() per run). */
|
|
435
478
|
testEnv?: NodeJS.ProcessEnv;
|
|
436
479
|
/**
|
|
437
480
|
* The mission's `checks.json` (the approved checks and the pull request's CI
|
|
@@ -451,7 +494,11 @@ export interface MakeRunVerifyOpts {
|
|
|
451
494
|
changedPaths?: (cwd: string, signal?: AbortSignal, baselinePaths?: string[]) => Promise<string[]>;
|
|
452
495
|
/** Repository-checks env override for determinism in tests (default: buildRepoCheckEnv() per run). */
|
|
453
496
|
repoChecksEnv?: NodeJS.ProcessEnv;
|
|
454
|
-
/**
|
|
497
|
+
/**
|
|
498
|
+
* The toolchain every command the gate runs is on: the repository checks,
|
|
499
|
+
* and its own detected typecheck and test scripts (default:
|
|
500
|
+
* `YAGNI_TOOLCHAIN_FILE`, when it reads).
|
|
501
|
+
*/
|
|
455
502
|
toolchain?: () => ToolchainFile | null;
|
|
456
503
|
repoChecksBuildBudgetMs?: number;
|
|
457
504
|
repoChecksCallBudgetMs?: number;
|
|
@@ -529,6 +576,11 @@ export interface MakeWorkstreamCheckOpts {
|
|
|
529
576
|
timeoutMs?: number;
|
|
530
577
|
/** What the clean tree already failed on (YAG-1003): those failures are not a workstream's. */
|
|
531
578
|
baseline?: VerifyBaseline | null;
|
|
579
|
+
/**
|
|
580
|
+
* The toolchain the checks run on, as the full gate's do (default:
|
|
581
|
+
* `YAGNI_TOOLCHAIN_FILE`, when it reads).
|
|
582
|
+
*/
|
|
583
|
+
toolchain?: () => ToolchainFile | null;
|
|
532
584
|
}
|
|
533
585
|
/**
|
|
534
586
|
* The per-workstream scoped check the implement diamond runs right after the fan
|
|
@@ -575,6 +627,11 @@ export interface MakeTakeVerifyBaselineOpts {
|
|
|
575
627
|
timeoutMs?: number;
|
|
576
628
|
testTimeoutMs?: number;
|
|
577
629
|
testEnv?: NodeJS.ProcessEnv;
|
|
630
|
+
/**
|
|
631
|
+
* The toolchain the commands run on, as the gate's do (default:
|
|
632
|
+
* `YAGNI_TOOLCHAIN_FILE`, when it reads).
|
|
633
|
+
*/
|
|
634
|
+
toolchain?: () => ToolchainFile | null;
|
|
578
635
|
budgetMs?: number;
|
|
579
636
|
now?: () => number;
|
|
580
637
|
treeStatus?: (cwd: string, signal?: AbortSignal) => Promise<string>;
|
|
@@ -625,7 +682,10 @@ export interface ChecksOnBaseResult extends ChecksOnBase {
|
|
|
625
682
|
*/
|
|
626
683
|
treeUndone: boolean;
|
|
627
684
|
}
|
|
628
|
-
/**
|
|
685
|
+
/**
|
|
686
|
+
* The whole measurement's cap, before the build starts: each check marked to
|
|
687
|
+
* measure, once, the plan's first.
|
|
688
|
+
*/
|
|
629
689
|
export declare const CHECKS_ON_BASE_BUDGET_MS: number;
|
|
630
690
|
export interface MakeMeasureChecksOnBaseOpts {
|
|
631
691
|
exec?: VerifyExec;
|
|
@@ -643,8 +703,9 @@ export interface MakeMeasureChecksOnBaseOpts {
|
|
|
643
703
|
* Run each approved check `checks.json` marks `measureOnBase` once on the
|
|
644
704
|
* unchanged code, before the build changes anything: a plan's check the
|
|
645
705
|
* host has not seen pass there (none of the record's, or a red one the
|
|
646
|
-
* record may hold from long ago)
|
|
647
|
-
*
|
|
706
|
+
* record may hold from long ago), and a command the repository's CI runs
|
|
707
|
+
* when the record names no check of its own. A check the plan wrote for the
|
|
708
|
+
* change often fails there by design (its test file is not written yet), and a
|
|
648
709
|
* failure that says only that (a file not found, no tests to run, output no
|
|
649
710
|
* parser reads) is no verdict: its failures all count after the build, as
|
|
650
711
|
* before. A failure the gate reads (failing tests, located diagnostics, a
|
|
@@ -37,7 +37,10 @@
|
|
|
37
37
|
* anything UNCERTAIN fail OPEN with an honest note, never a never-clearable
|
|
38
38
|
* critical. A cloud session's gate (`unreadTestFailureIsFinding`) makes an
|
|
39
39
|
* UNCERTAIN failure a finding carrying the output tail, unless the clean tree
|
|
40
|
-
* failed the same command.
|
|
40
|
+
* failed the same command. On a build that named the repository's toolchain
|
|
41
|
+
* (`YAGNI_TOOLCHAIN_FILE`, a sandbox) both halves run on it, as the model's
|
|
42
|
+
* shell and the approved checks do ({@link buildGateVerifyEnv},
|
|
43
|
+
* {@link buildGateTestEnv}).
|
|
41
44
|
*
|
|
42
45
|
* Safe-by-default and FAIL-OPEN: if no scoped, non-fanned-out command is
|
|
43
46
|
* detectable, or the command cannot be spawned, or it times out, the gate
|
|
@@ -163,9 +166,10 @@ const GIT_CALL =
|
|
|
163
166
|
* True when a command changes the checkout's git state: a commit, a branch
|
|
164
167
|
* created or switched, a reset, a stash, a push. `git branch`, `git remote`
|
|
165
168
|
* and `git config` count as reads only in their listing forms. The same rule
|
|
166
|
-
* as `changesGitState` in the backend's
|
|
167
|
-
* a check out of the session's `checks.json`; both test files
|
|
168
|
-
* command corpus (test/fixtures/gitStateCommands.json), so they
|
|
169
|
+
* as `changesGitState` in the backend's repos/capabilities/repositoryChecks.ts,
|
|
170
|
+
* which leaves such a check out of the session's `checks.json`; both test files
|
|
171
|
+
* read one command corpus (test/fixtures/gitStateCommands.json), so they
|
|
172
|
+
* cannot drift.
|
|
169
173
|
*/
|
|
170
174
|
export function changesGitState(command) {
|
|
171
175
|
const calls = command.matchAll(GIT_CALL);
|
|
@@ -509,18 +513,6 @@ const TEST_INFRA_PATTERNS = [
|
|
|
509
513
|
[/\bENOTFOUND\b|\bEAI_AGAIN\b/, "DNS/host resolution failed for a test dependency"],
|
|
510
514
|
[/\bEADDRINUSE\b/, "a port the tests need is already in use"],
|
|
511
515
|
[/\bETIMEDOUT\b|\bconnect(?:ion)? timed out\b/i, "connection to a test dependency timed out"],
|
|
512
|
-
[
|
|
513
|
-
/\bmissing (?:required )?env(?:ironment)? (?:var(?:iable)?s?)\b/i,
|
|
514
|
-
"missing environment variable",
|
|
515
|
-
],
|
|
516
|
-
[
|
|
517
|
-
/\benvironment variable\b[^\n]{0,80}\b(?:is )?(?:not set|missing|required|must be set)\b/i,
|
|
518
|
-
"missing environment variable",
|
|
519
|
-
],
|
|
520
|
-
[/\bprocess\.env\.[A-Z0-9_]+ is (?:undefined|not set)\b/, "missing environment variable"],
|
|
521
|
-
// Yarn Berry reading a `${TOKEN}` in .yarnrc.yml that the environment lacks
|
|
522
|
-
// (staging bench 2026-10-23: GITHUB_PACKAGES_TOKEN): the suite never started.
|
|
523
|
-
[/\bEnvironment variable not found \([A-Za-z0-9_]+\)/, "missing environment variable"],
|
|
524
516
|
[/\bSASL\b|\bSCRAM-SERVER\b/, "database auth failed (missing DB credentials?)"],
|
|
525
517
|
[/\bpassword authentication failed\b/i, "database auth failed"],
|
|
526
518
|
[/\brole "[^"]*" does not exist\b/i, "database role does not exist"],
|
|
@@ -537,6 +529,126 @@ const TEST_INFRA_PATTERNS = [
|
|
|
537
529
|
[/\bCannot connect to the Docker daemon\b/i, "Docker is not running"],
|
|
538
530
|
[/\bno space left on device\b/i, "the disk is full"],
|
|
539
531
|
];
|
|
532
|
+
/**
|
|
533
|
+
* A variable's name as an output names it: `SUPABASE_SECRET_KEY`, `PORT`.
|
|
534
|
+
* Two characters at least and upper case only, on purpose: a single letter or
|
|
535
|
+
* a lower-case word beside "is missing" is prose, and reading it as a
|
|
536
|
+
* variable would make a real failure "could not run".
|
|
537
|
+
*/
|
|
538
|
+
const VAR_NAME = "[A-Z][A-Z0-9_]*[A-Z0-9]";
|
|
539
|
+
/** Words a missing variable is said with after its name. */
|
|
540
|
+
const UNSET_WORDS = "(?:is |was )?(?:not set|not defined|missing|required|undefined|empty|must be set)";
|
|
541
|
+
const ENV_VARIABLE = "[Ee]nv(?:ironment)?[ _-]?[Vv]ar(?:iable)?";
|
|
542
|
+
/**
|
|
543
|
+
* The ways a run's output says it lacks an environment variable, each with the
|
|
544
|
+
* name in its first group. Each names the variable as one: a phrase about an
|
|
545
|
+
* environment variable around it, or `process.env.` before it.
|
|
546
|
+
*/
|
|
547
|
+
const NAMED_MISSING_VARIABLE = [
|
|
548
|
+
// "Missing required environment variable: SUPABASE_SECRET_KEY", "missing env var FOO"
|
|
549
|
+
new RegExp(`\\b[Mm]issing (?:[Rr]equired )?${ENV_VARIABLE}s?\\b[\\s:=\`'"([{]*(${VAR_NAME})\\b`, "g"),
|
|
550
|
+
// "Environment variable SUPABASE_URL is not set"
|
|
551
|
+
new RegExp(`\\b${ENV_VARIABLE} [\`'"]?(${VAR_NAME})[\`'"]? ${UNSET_WORDS}\\b`, "g"),
|
|
552
|
+
// "SUPABASE_URL environment variable is not set"
|
|
553
|
+
new RegExp(`\\b(${VAR_NAME}) ${ENV_VARIABLE} ${UNSET_WORDS}\\b`, "g"),
|
|
554
|
+
// "process.env.FOO is undefined"
|
|
555
|
+
new RegExp(`\\bprocess\\.env\\.(${VAR_NAME}) ${UNSET_WORDS}\\b`, "g"),
|
|
556
|
+
// Yarn Berry reading a `${TOKEN}` in .yarnrc.yml that the environment lacks
|
|
557
|
+
// (staging bench 2026-10-23: GITHUB_PACKAGES_TOKEN): the suite never started.
|
|
558
|
+
/\bEnvironment variable not found \(([A-Za-z_][A-Za-z0-9_]*)\)/g,
|
|
559
|
+
// django-environ: "ImproperlyConfigured: Set the SECRET_KEY environment variable"
|
|
560
|
+
new RegExp(`\\bSet the (${VAR_NAME}) environment variable\\b`, "g"),
|
|
561
|
+
];
|
|
562
|
+
/**
|
|
563
|
+
* A repository's own check of its environment, on a line that reads as the
|
|
564
|
+
* error the run stopped on (staging 2026-10-24, NearMyTown's e2e:
|
|
565
|
+
* "NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY is missing"). The name must look like
|
|
566
|
+
* a variable's (`A_B`), and the line an error's: a log line that a variable is
|
|
567
|
+
* missing is not why a run failed. Never "is not defined" or "is undefined":
|
|
568
|
+
* that is JavaScript's word for a name the code never declared.
|
|
569
|
+
*/
|
|
570
|
+
const BARE_MISSING_VARIABLE = /\b([A-Z][A-Z0-9]*(?:_[A-Z0-9]+)+) (?:is |was )?(?:missing|not set|must be set)\b/g;
|
|
571
|
+
const ERROR_LINE = /(?:Error|Exception)\b|^\s*(?:error|ERROR|fatal|FATAL)\b/;
|
|
572
|
+
/** At most this many missing variables are named in one cause. */
|
|
573
|
+
const MAX_NAMED_VARIABLES = 3;
|
|
574
|
+
/**
|
|
575
|
+
* A line that quotes what a test expected or received, or a diff of it: a
|
|
576
|
+
* test of an environment check, never the run's own missing variable.
|
|
577
|
+
*/
|
|
578
|
+
const QUOTED_LINE = /^\s*[-+]\s|\b(?:[Ee]xpected|[Rr]eceived|toThrow|[Aa]ssert)/;
|
|
579
|
+
/**
|
|
580
|
+
* A line a logger wrote below error level (`warn:`, `[info]`, `DEBUG`,
|
|
581
|
+
* Next.js's `⚠`): a variable it says is missing is one the run went on
|
|
582
|
+
* without, never why it failed.
|
|
583
|
+
*/
|
|
584
|
+
const LOG_LINE = /^\s*(?:⚠|[[(]?(?:warn(?:ing)?|info|debug|trace|notice)\b)/i;
|
|
585
|
+
/** Frameworks that say which variable they lack only by what it is for. */
|
|
586
|
+
const MISSING_FRAMEWORK_KEY = [
|
|
587
|
+
// Rails credentials: "Missing encryption key to decrypt file with. ... ENV["RAILS_MASTER_KEY"]"
|
|
588
|
+
[/\bMissing encryption key to decrypt file with\b/, "RAILS_MASTER_KEY"],
|
|
589
|
+
// Laravel's MissingAppKeyException
|
|
590
|
+
[/\bNo application encryption key has been specified\b/, "APP_KEY"],
|
|
591
|
+
];
|
|
592
|
+
/** Phrases that say a variable is missing without naming it. */
|
|
593
|
+
const UNNAMED_MISSING_VARIABLE = [
|
|
594
|
+
/\bmissing (?:required )?env(?:ironment)? (?:var(?:iable)?s?)\b/i,
|
|
595
|
+
/\benvironment variable\b[^\n]{0,80}\b(?:is )?(?:not set|missing|required|must be set)\b/i,
|
|
596
|
+
];
|
|
597
|
+
/**
|
|
598
|
+
* What a run could not start without, when its output says so in a way the
|
|
599
|
+
* gate recognises: a credential or variable the environment lacks, or a
|
|
600
|
+
* prerequisite the sandbox has not installed. Null for anything else, which
|
|
601
|
+
* stays a real failure. Pure.
|
|
602
|
+
*
|
|
603
|
+
* Narrow on purpose, since a match makes the run "could not run" rather than
|
|
604
|
+
* the change's failure:
|
|
605
|
+
* - a named variable counts only when `env` (the environment the command ran
|
|
606
|
+
* under) does not have it set: one it has is not why the run failed, and
|
|
607
|
+
* no unnamed phrase then stands in for it;
|
|
608
|
+
* - a line that quotes a test's expectation or a diff never counts, nor
|
|
609
|
+
* one a logger wrote below error level;
|
|
610
|
+
* - a repository's own "`A_B` is missing" counts only on an error's line.
|
|
611
|
+
*
|
|
612
|
+
* Without `env`, a named variable is taken as unset.
|
|
613
|
+
*/
|
|
614
|
+
export function classifyMissingPrerequisite(output, env) {
|
|
615
|
+
const unset = (name) => !env?.[name];
|
|
616
|
+
const lines = output
|
|
617
|
+
.split("\n")
|
|
618
|
+
.filter((line) => !QUOTED_LINE.test(line) && !LOG_LINE.test(line));
|
|
619
|
+
const named = new Set(lines.flatMap((line) => [
|
|
620
|
+
...NAMED_MISSING_VARIABLE.flatMap((re) => [...line.matchAll(re)]),
|
|
621
|
+
...(ERROR_LINE.test(line) ? [...line.matchAll(BARE_MISSING_VARIABLE)] : []),
|
|
622
|
+
].map((match) => match[1])));
|
|
623
|
+
const missing = [...named].filter(unset);
|
|
624
|
+
if (missing.length > 0) {
|
|
625
|
+
const more = missing.length - MAX_NAMED_VARIABLES;
|
|
626
|
+
return `${missing.slice(0, MAX_NAMED_VARIABLES).join(", ")}${more > 0 ? ` and ${more} more` : ""}`;
|
|
627
|
+
}
|
|
628
|
+
const text = lines.join("\n");
|
|
629
|
+
for (const [re, name] of MISSING_FRAMEWORK_KEY) {
|
|
630
|
+
if (re.test(text) && unset(name))
|
|
631
|
+
return name;
|
|
632
|
+
}
|
|
633
|
+
// supabase-js `createClient` with no URL or key: the variable it was read from is unset.
|
|
634
|
+
const supabase = text.match(/\bsupabase(Url|Key) is required\b/);
|
|
635
|
+
if (supabase) {
|
|
636
|
+
return `a Supabase ${supabase[1] === "Url" ? "URL" : "key"} (supabase${supabase[1]} is required)`;
|
|
637
|
+
}
|
|
638
|
+
// Playwright with no browser of its own version (a prepare installs one; #2758).
|
|
639
|
+
if (/\bExecutable doesn't exist at /.test(text) &&
|
|
640
|
+
/\bplaywright install\b|\bbrowserType\.launch\b|\bms-playwright\b/.test(text)) {
|
|
641
|
+
return "Playwright's browsers (npx playwright install)";
|
|
642
|
+
}
|
|
643
|
+
// A variable the output names that the environment has is not missing, and
|
|
644
|
+
// no unnamed phrase stands in for it. A log line is not read at all, so one
|
|
645
|
+
// that names a variable never stops an error's own unnamed phrase from
|
|
646
|
+
// counting: that error says the run lacked a variable, only not which.
|
|
647
|
+
if (named.size === 0 && UNNAMED_MISSING_VARIABLE.some((re) => re.test(text))) {
|
|
648
|
+
return "an environment variable the output does not name";
|
|
649
|
+
}
|
|
650
|
+
return null;
|
|
651
|
+
}
|
|
540
652
|
/** Strip a trailing `(4 ms)` / `(0.82ms)` duration off a failing-test name. */
|
|
541
653
|
function stripDuration(name) {
|
|
542
654
|
return name.replace(/\s*\(\d+(?:\.\d+)?\s*m?s\)\s*$/, "").trim();
|
|
@@ -568,7 +680,9 @@ const FAILURE_SUMMARY = [
|
|
|
568
680
|
* - infra/env shapes (command not found, module resolution, connection
|
|
569
681
|
* refused, missing env var, DB/auth, connect timeout) → `infra` with a short
|
|
570
682
|
* cause; checked FIRST so a suite whose assertions failed only because its
|
|
571
|
-
* DB was down fails open rather than blocking.
|
|
683
|
+
* DB was down fails open rather than blocking. A missing credential or
|
|
684
|
+
* prerequisite ({@link classifyMissingPrerequisite}, given the `env` the
|
|
685
|
+
* command ran under) is `infra` with `needs` and the cause "needs <it>".
|
|
572
686
|
* - recognisable failing-test lines (pnpm-wrapped vitest / jest / node:test
|
|
573
687
|
* TAP + spec reporters) → `failures` with located `critical` findings
|
|
574
688
|
* (capped at 25, deduped, locations attached when the runner printed them).
|
|
@@ -580,12 +694,15 @@ const FAILURE_SUMMARY = [
|
|
|
580
694
|
* Callers scrub the output through {@link scrubSecrets} BEFORE classification
|
|
581
695
|
* so findings/notes never carry secrets (§0.9).
|
|
582
696
|
*/
|
|
583
|
-
export function classifyTestFailure(output, exitCode, limit = MAX_LOCATED_FINDINGS) {
|
|
697
|
+
export function classifyTestFailure(output, exitCode, limit = MAX_LOCATED_FINDINGS, env) {
|
|
584
698
|
if (exitCode === 0)
|
|
585
699
|
return { kind: "clean" };
|
|
586
700
|
const spawnInfra = classifyVerifyInfraFailure(output, exitCode);
|
|
587
701
|
if (spawnInfra)
|
|
588
702
|
return { kind: "infra", reason: spawnInfra };
|
|
703
|
+
const needs = classifyMissingPrerequisite(output, env);
|
|
704
|
+
if (needs)
|
|
705
|
+
return { kind: "infra", reason: `needs ${needs}`, needs };
|
|
589
706
|
for (const [re, reason] of TEST_INFRA_PATTERNS) {
|
|
590
707
|
if (re.test(output))
|
|
591
708
|
return { kind: "infra", reason };
|
|
@@ -951,8 +1068,40 @@ export function buildRepoCheckEnv(source = process.env, toolchain = null, stepEn
|
|
|
951
1068
|
const env = buildTestEnv(source);
|
|
952
1069
|
for (const key of REPO_CHECK_SECRET_KEYS)
|
|
953
1070
|
delete env[key];
|
|
954
|
-
|
|
955
|
-
|
|
1071
|
+
return { ...onToolchain(env, toolchain), ...stepEnv, CI: "true" };
|
|
1072
|
+
}
|
|
1073
|
+
/**
|
|
1074
|
+
* `env` on the build's toolchain, as the model's shell gets it
|
|
1075
|
+
* (`makeToolchainSpawnHook`): the toolchain's PATH, and its other variables
|
|
1076
|
+
* beneath `env`'s own. No toolchain: `env` as it is.
|
|
1077
|
+
*/
|
|
1078
|
+
function onToolchain(env, toolchain) {
|
|
1079
|
+
return toolchain ? { ...toolchain.env, ...env, PATH: toolchain.path } : env;
|
|
1080
|
+
}
|
|
1081
|
+
/**
|
|
1082
|
+
* The environment the gate's own detected test script runs under. On a build
|
|
1083
|
+
* that named a toolchain (a sandbox), an approved check's
|
|
1084
|
+
* ({@link buildRepoCheckEnv}), so the suite runs on the repository's Node,
|
|
1085
|
+
* Ruby or Go as the model's shell does, never the image's, and gives the same
|
|
1086
|
+
* verdict it gives as an approved check. Without one (a developer's terminal),
|
|
1087
|
+
* the user's own ({@link buildTestEnv}), as before.
|
|
1088
|
+
*/
|
|
1089
|
+
export function buildGateTestEnv(source = process.env, toolchain = null) {
|
|
1090
|
+
return toolchain ? buildRepoCheckEnv(source, toolchain) : buildTestEnv(source);
|
|
1091
|
+
}
|
|
1092
|
+
/**
|
|
1093
|
+
* The environment the gate's own detected typecheck or lint script runs
|
|
1094
|
+
* under: the scrubbed {@link buildVerifyEnv} on the build's toolchain, when it
|
|
1095
|
+
* named one. Undefined without one, so the exec seam's default applies, as
|
|
1096
|
+
* before.
|
|
1097
|
+
*/
|
|
1098
|
+
export function buildGateVerifyEnv(source = process.env, toolchain = null) {
|
|
1099
|
+
return toolchain ? onToolchain(buildVerifyEnv(source), toolchain) : undefined;
|
|
1100
|
+
}
|
|
1101
|
+
/** The toolchain the session's harness named (`YAGNI_TOOLCHAIN_FILE`), when it reads. */
|
|
1102
|
+
function sessionToolchain() {
|
|
1103
|
+
const path = process.env.YAGNI_TOOLCHAIN_FILE?.trim();
|
|
1104
|
+
return path ? readToolchainFile(path) : null;
|
|
956
1105
|
}
|
|
957
1106
|
/** One repository command as the gate runs it: through bash, as Actions runs a `run:` step. */
|
|
958
1107
|
function repoCheckArgv(run) {
|
|
@@ -1111,6 +1260,7 @@ export function makeWorkstreamCheck(opts = {}) {
|
|
|
1111
1260
|
// command, untagged, by whichever target reaches the command first.
|
|
1112
1261
|
const uncoveredEmitted = new Set();
|
|
1113
1262
|
const results = [];
|
|
1263
|
+
const env = buildGateVerifyEnv(process.env, (opts.toolchain ?? sessionToolchain)());
|
|
1114
1264
|
for (const target of targets) {
|
|
1115
1265
|
const dirs = claimDirs(cwd, target.files);
|
|
1116
1266
|
const commands = dirs.length > 0 ? detect(cwd, dirs).filter((c) => dirs.includes(c.cwd)) : [];
|
|
@@ -1134,7 +1284,7 @@ export function makeWorkstreamCheck(opts = {}) {
|
|
|
1134
1284
|
if (!run) {
|
|
1135
1285
|
const composed = composeAbortSignal(signal, AbortSignal.timeout(timeoutMs));
|
|
1136
1286
|
try {
|
|
1137
|
-
const { code, output } = await exec(command.argv, command.cwd, composed);
|
|
1287
|
+
const { code, output } = await exec(command.argv, command.cwd, composed, env);
|
|
1138
1288
|
run = { code, output: scrubSecrets(output) };
|
|
1139
1289
|
}
|
|
1140
1290
|
catch (err) {
|
|
@@ -1185,7 +1335,7 @@ export function makeWorkstreamCheck(opts = {}) {
|
|
|
1185
1335
|
*/
|
|
1186
1336
|
function repoCommandFindings(failure, at) {
|
|
1187
1337
|
const limit = at.limit ?? MAX_LOCATED_FINDINGS;
|
|
1188
|
-
const cls = classifyTestFailure(failure.output, failure.code, limit);
|
|
1338
|
+
const cls = classifyTestFailure(failure.output, failure.code, limit, failure.env);
|
|
1189
1339
|
const parsed = cls.kind === "failures"
|
|
1190
1340
|
? cls.findings
|
|
1191
1341
|
: parseVerifyFailures(failure.output, failure.code, at.label, limit);
|
|
@@ -1243,11 +1393,7 @@ export function makeRunVerify(opts = {}) {
|
|
|
1243
1393
|
const baseline = new Set(baselinePaths ?? []);
|
|
1244
1394
|
return parseChangedPaths(snapshot.status).filter((p) => !baseline.has(p));
|
|
1245
1395
|
});
|
|
1246
|
-
const toolchain = opts.toolchain ??
|
|
1247
|
-
(() => {
|
|
1248
|
-
const path = process.env.YAGNI_TOOLCHAIN_FILE?.trim();
|
|
1249
|
-
return path ? readToolchainFile(path) : null;
|
|
1250
|
-
});
|
|
1396
|
+
const toolchain = opts.toolchain ?? sessionToolchain;
|
|
1251
1397
|
const treeStatus = opts.treeStatus ??
|
|
1252
1398
|
(async (cwd, signal) => (await snapshotWorkspace(cwd, signal)).status);
|
|
1253
1399
|
const undoTreeWrites = opts.undoTreeWrites ?? defaultUndoTreeWrites;
|
|
@@ -1346,7 +1492,7 @@ export function makeRunVerify(opts = {}) {
|
|
|
1346
1492
|
cwd,
|
|
1347
1493
|
signal,
|
|
1348
1494
|
left: () => callBudget - (now() - halfStartedAt),
|
|
1349
|
-
toolchainFile:
|
|
1495
|
+
toolchainFile: input.toolchainFile,
|
|
1350
1496
|
findings,
|
|
1351
1497
|
reasons,
|
|
1352
1498
|
labels,
|
|
@@ -1389,7 +1535,15 @@ export function makeRunVerify(opts = {}) {
|
|
|
1389
1535
|
}
|
|
1390
1536
|
return { findings, reasons, labels, checks: [...unrunnable, ...run.checks] };
|
|
1391
1537
|
}
|
|
1392
|
-
/**
|
|
1538
|
+
/**
|
|
1539
|
+
* One repository command's verdict: passed, a note (it could not run), or a
|
|
1540
|
+
* failure with its output. A check that passed on the unchanged code
|
|
1541
|
+
* (`passedOnBase`, measured in this environment) and now says it lacks a
|
|
1542
|
+
* credential failed: the change made it need one, and that is the change's
|
|
1543
|
+
* (`needs` says what). Only the session that measured knows it passed: a
|
|
1544
|
+
* resumed session's host carries forward what failed on the base, never
|
|
1545
|
+
* what passed, so there the same check is could not run, failing open.
|
|
1546
|
+
*/
|
|
1393
1547
|
async function runRepoCommand(run, command, dir, stepEnv, check) {
|
|
1394
1548
|
const capMs = Math.min(opts.repoCheckTimeoutMs ?? REPO_CHECK_TIMEOUT_MS, run.left());
|
|
1395
1549
|
const composed = composeAbortSignal(run.signal, AbortSignal.timeout(Math.max(1, capMs)));
|
|
@@ -1417,13 +1571,20 @@ export function makeRunVerify(opts = {}) {
|
|
|
1417
1571
|
return { kind: "passed" };
|
|
1418
1572
|
}
|
|
1419
1573
|
const output = scrubSecrets(rawOutput);
|
|
1420
|
-
const cls = classifyTestFailure(output, code);
|
|
1421
|
-
if (cls.kind === "infra") {
|
|
1574
|
+
const cls = classifyTestFailure(output, code, MAX_LOCATED_FINDINGS, env);
|
|
1575
|
+
if (cls.kind === "infra" && !(cls.needs && check.passedOnBase)) {
|
|
1422
1576
|
const reason = `could not run: ${cls.reason}`;
|
|
1423
1577
|
record("cannot_run", `${reason}; ${exitSummary(check.name, code, output)}`);
|
|
1424
1578
|
return { kind: "note", reason };
|
|
1425
1579
|
}
|
|
1426
|
-
return {
|
|
1580
|
+
return {
|
|
1581
|
+
kind: "failed",
|
|
1582
|
+
output,
|
|
1583
|
+
code,
|
|
1584
|
+
env,
|
|
1585
|
+
record: record(check.baseline),
|
|
1586
|
+
...(cls.kind === "infra" && cls.needs ? { needs: cls.needs } : {}),
|
|
1587
|
+
};
|
|
1427
1588
|
}
|
|
1428
1589
|
catch (err) {
|
|
1429
1590
|
run.checks.push({
|
|
@@ -1470,9 +1631,13 @@ export function makeRunVerify(opts = {}) {
|
|
|
1470
1631
|
const verdict = await runRepoCommand(run, check.command, run.cwd, undefined, {
|
|
1471
1632
|
name: label,
|
|
1472
1633
|
baseline: status,
|
|
1634
|
+
passedOnBase: opts.checksOnBase?.passed.has(check.command.trim()) ?? false,
|
|
1473
1635
|
});
|
|
1474
1636
|
if (verdict.kind === "failed")
|
|
1475
1637
|
run.approvedFailed.add(canonicalCheckCommand(check.command));
|
|
1638
|
+
if (verdict.kind === "failed" && verdict.needs) {
|
|
1639
|
+
run.reasons.push(`approved check \`${label}\` passed on the unchanged code and now needs ${verdict.needs}: the change's failure`);
|
|
1640
|
+
}
|
|
1476
1641
|
if (verdict.kind === "note") {
|
|
1477
1642
|
run.reasons.push(`approved check \`${label}\` ${verdict.reason}`);
|
|
1478
1643
|
}
|
|
@@ -1612,6 +1777,8 @@ export function makeRunVerify(opts = {}) {
|
|
|
1612
1777
|
const findings = [];
|
|
1613
1778
|
const reasons = [];
|
|
1614
1779
|
let anyRan = false;
|
|
1780
|
+
// Every half runs on the build's toolchain, as the model's shell does.
|
|
1781
|
+
const toolchainFile = toolchain();
|
|
1615
1782
|
// Failures the clean tree already had, by command label (YAG-1003).
|
|
1616
1783
|
const preexisting = new Map();
|
|
1617
1784
|
const countPreexisting = (label, n) => {
|
|
@@ -1638,11 +1805,12 @@ export function makeRunVerify(opts = {}) {
|
|
|
1638
1805
|
// recorded reason, no finding) without sinking the verdict for the packages
|
|
1639
1806
|
// that did run. Output is scrubbed before it can enter a finding (§0.9).
|
|
1640
1807
|
const typecheckLabels = [];
|
|
1808
|
+
const typecheckEnv = buildGateVerifyEnv(process.env, toolchainFile);
|
|
1641
1809
|
for (const command of commands) {
|
|
1642
1810
|
const composed = composeAbortSignal(signal, AbortSignal.timeout(timeoutMs));
|
|
1643
1811
|
const startedAt = now();
|
|
1644
1812
|
try {
|
|
1645
|
-
const { code, output: rawOutput } = await exec(command.argv, command.cwd, composed);
|
|
1813
|
+
const { code, output: rawOutput } = await exec(command.argv, command.cwd, composed, typecheckEnv);
|
|
1646
1814
|
const output = scrubSecrets(rawOutput);
|
|
1647
1815
|
if (code !== 0) {
|
|
1648
1816
|
// Infra/env failure (command not found in a fresh worktree, Node loader
|
|
@@ -1686,7 +1854,7 @@ export function makeRunVerify(opts = {}) {
|
|
|
1686
1854
|
reasons.push("tests skipped: the typecheck half failed; 'clean' still requires a later round's tests");
|
|
1687
1855
|
}
|
|
1688
1856
|
else {
|
|
1689
|
-
const testEnv = opts.testEnv ??
|
|
1857
|
+
const testEnv = opts.testEnv ?? buildGateTestEnv(process.env, toolchainFile);
|
|
1690
1858
|
let testFindingCount = 0;
|
|
1691
1859
|
// A root test script an approved check runs too (`yarn run test`
|
|
1692
1860
|
// beside `yarn test`) runs once, as that check, on the repository's
|
|
@@ -1707,7 +1875,7 @@ export function makeRunVerify(opts = {}) {
|
|
|
1707
1875
|
try {
|
|
1708
1876
|
const { code, output: rawOutput } = await exec(command.argv, command.cwd, composed, testEnv);
|
|
1709
1877
|
const output = scrubSecrets(rawOutput);
|
|
1710
|
-
const cls = classifyTestFailure(output, code, Number.POSITIVE_INFINITY);
|
|
1878
|
+
const cls = classifyTestFailure(output, code, Number.POSITIVE_INFINITY, testEnv);
|
|
1711
1879
|
if (cls.kind === "infra") {
|
|
1712
1880
|
recordCommand(command, null, startedAt, "cannot_run", `could not run: ${cls.reason}; ${exitSummary(command.label, code, output)}`);
|
|
1713
1881
|
reasons.push(`tests (${where}) could not run: ${cls.reason}; 'clean' reflects typecheck + review only`);
|
|
@@ -1772,6 +1940,7 @@ export function makeRunVerify(opts = {}) {
|
|
|
1772
1940
|
signal,
|
|
1773
1941
|
baselinePaths,
|
|
1774
1942
|
callStartedAt,
|
|
1943
|
+
toolchainFile,
|
|
1775
1944
|
});
|
|
1776
1945
|
reasons.push(...repo.reasons);
|
|
1777
1946
|
repoLabels.push(...repo.labels);
|
|
@@ -1879,6 +2048,10 @@ export function makeTakeVerifyBaseline(opts = {}) {
|
|
|
1879
2048
|
...commands.map((command) => ({ command, test: false })),
|
|
1880
2049
|
...tests.map((command) => ({ command, test: true })),
|
|
1881
2050
|
];
|
|
2051
|
+
// The gate's environments, so a failure keys the same on the clean tree and the build's.
|
|
2052
|
+
const toolchainFile = (opts.toolchain ?? sessionToolchain)();
|
|
2053
|
+
const verifyEnv = buildGateVerifyEnv(process.env, toolchainFile);
|
|
2054
|
+
const testEnv = opts.testEnv ?? buildGateTestEnv(process.env, toolchainFile);
|
|
1882
2055
|
try {
|
|
1883
2056
|
for (const { command, test } of runs) {
|
|
1884
2057
|
if (signal?.aborted)
|
|
@@ -1893,13 +2066,11 @@ export function makeTakeVerifyBaseline(opts = {}) {
|
|
|
1893
2066
|
: (opts.timeoutMs ?? VERIFY_TIMEOUT_MS), left);
|
|
1894
2067
|
const composed = composeAbortSignal(signal, AbortSignal.timeout(cap));
|
|
1895
2068
|
try {
|
|
1896
|
-
const { code, output: rawOutput } = test
|
|
1897
|
-
? await exec(command.argv, command.cwd, composed, opts.testEnv ?? buildTestEnv())
|
|
1898
|
-
: await exec(command.argv, command.cwd, composed);
|
|
2069
|
+
const { code, output: rawOutput } = await exec(command.argv, command.cwd, composed, test ? testEnv : verifyEnv);
|
|
1899
2070
|
const output = scrubSecrets(rawOutput);
|
|
1900
2071
|
let failed;
|
|
1901
2072
|
if (test) {
|
|
1902
|
-
const cls = classifyTestFailure(output, code, Number.POSITIVE_INFINITY);
|
|
2073
|
+
const cls = classifyTestFailure(output, code, Number.POSITIVE_INFINITY, testEnv);
|
|
1903
2074
|
if (cls.kind === "infra" || cls.kind === "uncertain") {
|
|
1904
2075
|
result.notes.push(`${command.label} (${command.cwd}): ${cls.kind === "infra" ? cls.reason : "output not interpretable"}`);
|
|
1905
2076
|
if (cls.kind === "uncertain") {
|
|
@@ -1945,14 +2116,18 @@ export function makeTakeVerifyBaseline(opts = {}) {
|
|
|
1945
2116
|
return result;
|
|
1946
2117
|
};
|
|
1947
2118
|
}
|
|
1948
|
-
/**
|
|
2119
|
+
/**
|
|
2120
|
+
* The whole measurement's cap, before the build starts: each check marked to
|
|
2121
|
+
* measure, once, the plan's first.
|
|
2122
|
+
*/
|
|
1949
2123
|
export const CHECKS_ON_BASE_BUDGET_MS = 10 * 60_000;
|
|
1950
2124
|
/**
|
|
1951
2125
|
* Run each approved check `checks.json` marks `measureOnBase` once on the
|
|
1952
2126
|
* unchanged code, before the build changes anything: a plan's check the
|
|
1953
2127
|
* host has not seen pass there (none of the record's, or a red one the
|
|
1954
|
-
* record may hold from long ago)
|
|
1955
|
-
*
|
|
2128
|
+
* record may hold from long ago), and a command the repository's CI runs
|
|
2129
|
+
* when the record names no check of its own. A check the plan wrote for the
|
|
2130
|
+
* change often fails there by design (its test file is not written yet), and a
|
|
1956
2131
|
* failure that says only that (a file not found, no tests to run, output no
|
|
1957
2132
|
* parser reads) is no verdict: its failures all count after the build, as
|
|
1958
2133
|
* before. A failure the gate reads (failing tests, located diagnostics, a
|
|
@@ -1967,11 +2142,7 @@ export const CHECKS_ON_BASE_BUDGET_MS = 10 * 60_000;
|
|
|
1967
2142
|
export function makeMeasureChecksOnBase(opts = {}) {
|
|
1968
2143
|
const exec = opts.exec ?? defaultExec;
|
|
1969
2144
|
const now = opts.now ?? Date.now;
|
|
1970
|
-
const toolchain = opts.toolchain ??
|
|
1971
|
-
(() => {
|
|
1972
|
-
const path = process.env.YAGNI_TOOLCHAIN_FILE?.trim();
|
|
1973
|
-
return path ? readToolchainFile(path) : null;
|
|
1974
|
-
});
|
|
2145
|
+
const toolchain = opts.toolchain ?? sessionToolchain;
|
|
1975
2146
|
const treeStatus = opts.treeStatus ??
|
|
1976
2147
|
(async (cwd, signal) => (await snapshotWorkspace(cwd, signal)).status);
|
|
1977
2148
|
const undoTreeWrites = opts.undoTreeWrites ?? defaultUndoTreeWrites;
|
|
@@ -2013,12 +2184,12 @@ export function makeMeasureChecksOnBase(opts = {}) {
|
|
|
2013
2184
|
continue;
|
|
2014
2185
|
}
|
|
2015
2186
|
const output = scrubSecrets(rawOutput);
|
|
2016
|
-
const cls = classifyTestFailure(output, code, Number.POSITIVE_INFINITY);
|
|
2187
|
+
const cls = classifyTestFailure(output, code, Number.POSITIVE_INFINITY, env);
|
|
2017
2188
|
if (cls.kind === "infra") {
|
|
2018
2189
|
result.notes.push(`\`${label}\`: could not run: ${cls.reason}`);
|
|
2019
2190
|
continue;
|
|
2020
2191
|
}
|
|
2021
|
-
const found = repoCommandFindings({ output, code }, {
|
|
2192
|
+
const found = repoCommandFindings({ output, code, env }, {
|
|
2022
2193
|
label,
|
|
2023
2194
|
prefix: approvedPrefix(label),
|
|
2024
2195
|
repoRoot: cwd,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yagni-app/code-staging",
|
|
3
|
-
"version": "1.2.7-staging.
|
|
3
|
+
"version": "1.2.7-staging.2033.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": "67b28e4d87817ddc9be0c0cb8ef367a50e8ce928"
|
|
62
62
|
}
|