@yagni-app/code-staging 1.2.6-staging.1982.1 → 1.2.6-staging.1992.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.
|
@@ -106,6 +106,17 @@ export interface VerifyOutcome {
|
|
|
106
106
|
/** Every command the gate ran, with how it exited (the cloud session's `result.json` checks). */
|
|
107
107
|
checks?: VerifyCheckRun[];
|
|
108
108
|
}
|
|
109
|
+
/** An approved check's name on the report: its command, cut at 80 characters. */
|
|
110
|
+
export declare function checkLabel(command: string): string;
|
|
111
|
+
/**
|
|
112
|
+
* True when a command changes the checkout's git state: a commit, a branch
|
|
113
|
+
* created or switched, a reset, a stash, a push. `git branch`, `git remote`
|
|
114
|
+
* and `git config` count as reads only in their listing forms. The same rule
|
|
115
|
+
* as `changesGitState` in the backend's attempts/inputs.ts, which leaves such
|
|
116
|
+
* a check out of the session's `checks.json`; both test files read one
|
|
117
|
+
* command corpus (test/fixtures/gitStateCommands.json), so they cannot drift.
|
|
118
|
+
*/
|
|
119
|
+
export declare function changesGitState(command: string): boolean;
|
|
109
120
|
/** One command the gate ran, and how it went. */
|
|
110
121
|
export interface VerifyCheckRun {
|
|
111
122
|
name: string;
|
|
@@ -107,6 +107,52 @@ export function exitSummary(label, code, output) {
|
|
|
107
107
|
}
|
|
108
108
|
/** The verify lens reuses `does_it_hold` (does the change build/test) to avoid widening ReviewLens. */
|
|
109
109
|
const VERIFY_LENS = "does_it_hold";
|
|
110
|
+
/** An approved check's name on the report: its command, cut at 80 characters. */
|
|
111
|
+
export function checkLabel(command) {
|
|
112
|
+
return command.length > 80 ? `${command.slice(0, 77)}...` : command;
|
|
113
|
+
}
|
|
114
|
+
/** Git subcommands that only read: a check may run them as often as it likes. */
|
|
115
|
+
const READ_ONLY_GIT = new Set("status diff log show ls-files ls-tree rev-parse rev-list check-ignore grep blame describe cat-file shortlog diff-tree merge-base for-each-ref show-ref name-rev count-objects var version help".split(" "));
|
|
116
|
+
/**
|
|
117
|
+
* One git call in a shell command: what precedes it (a separator, a quote, a
|
|
118
|
+
* `$(`), its global options (`-C dir`, `-c k=v`, `--git-dir=...`,
|
|
119
|
+
* `--no-pager`), its subcommand, and its arguments up to the next separator.
|
|
120
|
+
*/
|
|
121
|
+
const GIT_CALL =
|
|
122
|
+
// Each alternative is disjoint from the others (no backtracking blow-up).
|
|
123
|
+
/(?:^|[\s;&|(`!"'/])git((?:\s+(?:-[cC]\s+\S+|--[A-Za-z][\w-]*(?:=\S+)?|-[ABD-Zabd-z]))*)\s+([a-z][\w-]*)((?:[ \t][^;&|)`\n\r]*)?)/g;
|
|
124
|
+
/**
|
|
125
|
+
* True when a command changes the checkout's git state: a commit, a branch
|
|
126
|
+
* created or switched, a reset, a stash, a push. `git branch`, `git remote`
|
|
127
|
+
* and `git config` count as reads only in their listing forms. The same rule
|
|
128
|
+
* as `changesGitState` in the backend's attempts/inputs.ts, which leaves such
|
|
129
|
+
* a check out of the session's `checks.json`; both test files read one
|
|
130
|
+
* command corpus (test/fixtures/gitStateCommands.json), so they cannot drift.
|
|
131
|
+
*/
|
|
132
|
+
export function changesGitState(command) {
|
|
133
|
+
const calls = command.matchAll(GIT_CALL);
|
|
134
|
+
for (const call of calls) {
|
|
135
|
+
const sub = call[2] ?? "";
|
|
136
|
+
const args = (call[3] ?? "").replace(/["']/g, "").trim().split(/\s+/).filter(Boolean);
|
|
137
|
+
if (READ_ONLY_GIT.has(sub))
|
|
138
|
+
continue;
|
|
139
|
+
const listing = args.every((arg) => arg.startsWith("-") &&
|
|
140
|
+
!/^-[dDmMcC]$|^--(?:delete|move|copy|set-upstream|unset|add|replace-all|edit)/.test(arg));
|
|
141
|
+
if (sub === "branch" && listing)
|
|
142
|
+
continue;
|
|
143
|
+
if (sub === "remote" &&
|
|
144
|
+
(args.length === 0 || args.every((arg) => arg === "-v" || arg === "--verbose")))
|
|
145
|
+
continue;
|
|
146
|
+
if (sub === "config" &&
|
|
147
|
+
(args.includes("--get") ||
|
|
148
|
+
args.includes("--list") ||
|
|
149
|
+
args.includes("-l") ||
|
|
150
|
+
(args.length === 1 && !args[0]?.startsWith("-"))))
|
|
151
|
+
continue;
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
110
156
|
/**
|
|
111
157
|
* Why a command threw instead of exiting, as its check record says it. `run`
|
|
112
158
|
* is the run's own signal (a /stop or the run's end); `capped` is the one
|
|
@@ -434,6 +480,9 @@ const TEST_INFRA_PATTERNS = [
|
|
|
434
480
|
"missing environment variable",
|
|
435
481
|
],
|
|
436
482
|
[/\bprocess\.env\.[A-Z0-9_]+ is (?:undefined|not set)\b/, "missing environment variable"],
|
|
483
|
+
// Yarn Berry reading a `${TOKEN}` in .yarnrc.yml that the environment lacks
|
|
484
|
+
// (staging bench 2026-10-23: GITHUB_PACKAGES_TOKEN): the suite never started.
|
|
485
|
+
[/\bEnvironment variable not found \([A-Za-z0-9_]+\)/, "missing environment variable"],
|
|
437
486
|
[/\bSASL\b|\bSCRAM-SERVER\b/, "database auth failed (missing DB credentials?)"],
|
|
438
487
|
[/\bpassword authentication failed\b/i, "database auth failed"],
|
|
439
488
|
[/\brole "[^"]*" does not exist\b/i, "database role does not exist"],
|
|
@@ -1151,9 +1200,23 @@ export function makeRunVerify(opts = {}) {
|
|
|
1151
1200
|
const reasons = [];
|
|
1152
1201
|
const labels = [];
|
|
1153
1202
|
const cannotRun = [];
|
|
1203
|
+
// An approved check the sandbox cannot run is on the report as one with no
|
|
1204
|
+
// verdict, so a session that never ran it is never read as all green
|
|
1205
|
+
// (#2694): the host leaves its pull request a draft.
|
|
1206
|
+
const unrunnable = [];
|
|
1154
1207
|
for (const check of file.checks) {
|
|
1155
|
-
if (check.status
|
|
1156
|
-
|
|
1208
|
+
if (check.status !== "cannot_run")
|
|
1209
|
+
continue;
|
|
1210
|
+
cannotRun.push(`\`${check.command}\`${check.cause ? ` (${check.cause})` : ""}`);
|
|
1211
|
+
unrunnable.push({
|
|
1212
|
+
name: checkLabel(check.command),
|
|
1213
|
+
command: check.command,
|
|
1214
|
+
cwd: ".",
|
|
1215
|
+
exitCode: null,
|
|
1216
|
+
durationMs: 0,
|
|
1217
|
+
baseline: "cannot_run",
|
|
1218
|
+
note: `could not run in the sandbox${check.cause ? `: ${check.cause}` : ""}`,
|
|
1219
|
+
});
|
|
1157
1220
|
}
|
|
1158
1221
|
const paths = await changedPaths(cwd, signal, input.baselinePaths);
|
|
1159
1222
|
// A job the pull request's CI reported failed runs whatever its paths say.
|
|
@@ -1200,12 +1263,12 @@ export function makeRunVerify(opts = {}) {
|
|
|
1200
1263
|
const steps = jobs.flatMap((job) => job.steps.map((step) => ({ job, step })));
|
|
1201
1264
|
if (approved.length === 0 && steps.length === 0) {
|
|
1202
1265
|
report();
|
|
1203
|
-
return { findings, reasons, labels, checks:
|
|
1266
|
+
return { findings, reasons, labels, checks: unrunnable };
|
|
1204
1267
|
}
|
|
1205
1268
|
const buildLeft = (opts.repoChecksBuildBudgetMs ?? REPO_CHECKS_BUILD_BUDGET_MS) - repoChecksSpentMs;
|
|
1206
1269
|
if (buildLeft < REPO_CHECK_MIN_START_MS) {
|
|
1207
1270
|
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");
|
|
1208
|
-
return { findings, reasons, labels, checks:
|
|
1271
|
+
return { findings, reasons, labels, checks: unrunnable };
|
|
1209
1272
|
}
|
|
1210
1273
|
const callBudget = Math.min(opts.repoChecksCallBudgetMs ?? REPO_CHECKS_CALL_BUDGET_MS, timeoutMs - (now() - input.callStartedAt), buildLeft);
|
|
1211
1274
|
const halfStartedAt = now();
|
|
@@ -1254,7 +1317,7 @@ export function makeRunVerify(opts = {}) {
|
|
|
1254
1317
|
if (run.notReached > 0) {
|
|
1255
1318
|
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`);
|
|
1256
1319
|
}
|
|
1257
|
-
return { findings, reasons, labels, checks: run.checks };
|
|
1320
|
+
return { findings, reasons, labels, checks: [...unrunnable, ...run.checks] };
|
|
1258
1321
|
}
|
|
1259
1322
|
/** One repository command's verdict: passed, a note (it could not run), or a failure with its output. */
|
|
1260
1323
|
async function runRepoCommand(run, command, dir, stepEnv, check) {
|
|
@@ -1328,7 +1391,13 @@ export function makeRunVerify(opts = {}) {
|
|
|
1328
1391
|
run.notReached += 1;
|
|
1329
1392
|
continue;
|
|
1330
1393
|
}
|
|
1331
|
-
const label = check.command
|
|
1394
|
+
const label = checkLabel(check.command);
|
|
1395
|
+
if (changesGitState(check.command)) {
|
|
1396
|
+
// A check runs every round: one that commits, branches or resets would
|
|
1397
|
+
// move the session's HEAD under its own work. Never run, and said.
|
|
1398
|
+
run.reasons.push(`approved check \`${label}\` not run: it changes the checkout's git state (a branch, a commit or a reset), which a check must never do`);
|
|
1399
|
+
continue;
|
|
1400
|
+
}
|
|
1332
1401
|
const verdict = await runRepoCommand(run, check.command, run.cwd, undefined, {
|
|
1333
1402
|
name: label,
|
|
1334
1403
|
baseline: check.status,
|
|
@@ -1393,6 +1462,14 @@ export function makeRunVerify(opts = {}) {
|
|
|
1393
1462
|
continue;
|
|
1394
1463
|
}
|
|
1395
1464
|
const where = `CI ${job.name}, step "${step.name}"`;
|
|
1465
|
+
if (changesGitState(step.run)) {
|
|
1466
|
+
// The runner's checkout is its own; here it is the session's branch.
|
|
1467
|
+
const reason = "it changes the checkout's git state";
|
|
1468
|
+
run.reasons.push(`${where} not run: ${reason}`);
|
|
1469
|
+
stoppedJobs.add(job);
|
|
1470
|
+
settle(job, "cannot_run", reason);
|
|
1471
|
+
continue;
|
|
1472
|
+
}
|
|
1396
1473
|
const key = `${job.workflow}\u0000${job.job}\u0000${step.name}\u0000${step.run}`;
|
|
1397
1474
|
const dir = step.workingDirectory
|
|
1398
1475
|
? join(run.cwd, step.workingDirectory.replace(/^\.\/+/, ""))
|
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.1992.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": "a509d73f2a31808ff00aeefb6a4b7c360bd96956"
|
|
62
62
|
}
|