@yagni-app/code-staging 1.2.6-staging.1951.1 → 1.2.6-staging.1952.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.
|
@@ -45,7 +45,8 @@
|
|
|
45
45
|
* - Always, as the last line: the result object
|
|
46
46
|
* `{type:"pipeline_result", ok, stopReason, rounds, findings, stages:[{stage,
|
|
47
47
|
* tier, usage}], …}` — or `{type:"pipeline_error", …}` when a build stage
|
|
48
|
-
* threw.
|
|
48
|
+
* threw. `preexistingFailures` (absent at 0) counts the gate's own
|
|
49
|
+
* typecheck, lint and test failures the clean tree already had (YAG-1003).
|
|
49
50
|
* - Exit codes: 0 ONLY on a verified candidate (a `clean` stop), 1 for any
|
|
50
51
|
* other outcome or a thrown pipeline error, 2 for a usage/input problem.
|
|
51
52
|
*
|
|
@@ -60,7 +61,7 @@
|
|
|
60
61
|
*/
|
|
61
62
|
import { type TreeSnapshotDeps } from "./treeSnapshot.js";
|
|
62
63
|
import { runPipeline as defaultRunPipeline } from "./orchestrator.js";
|
|
63
|
-
import { makeRunVerify } from "./verify.js";
|
|
64
|
+
import { makeRunVerify, type VerifyBaselineResult } from "./verify.js";
|
|
64
65
|
import { type RunBudget } from "./budget.js";
|
|
65
66
|
import type { Finding, JsonEvent, ModelTier, PipelineStage, StageId, StageTag, StageUsage, StopReason } from "./types.js";
|
|
66
67
|
/** One-line usage copy, shared by every argument error. */
|
|
@@ -129,6 +130,11 @@ export interface HeadlessGoResult {
|
|
|
129
130
|
/** Honest note when the deterministic verify gate gave no verdict. */
|
|
130
131
|
verifyNote?: string;
|
|
131
132
|
verifyCommand?: string;
|
|
133
|
+
/**
|
|
134
|
+
* Failures the gate's own typecheck, lint and tests already had on the clean
|
|
135
|
+
* tree, in its last verdict (YAG-1003): a note, never findings. Absent at 0.
|
|
136
|
+
*/
|
|
137
|
+
preexistingFailures?: number;
|
|
132
138
|
}
|
|
133
139
|
export interface HeadlessGoOutcome {
|
|
134
140
|
exitCode: number;
|
|
@@ -160,6 +166,8 @@ export interface HeadlessGoDeps {
|
|
|
160
166
|
* there is none). Defaults to the real gate; injected in tests.
|
|
161
167
|
*/
|
|
162
168
|
makeRunVerify?: typeof makeRunVerify;
|
|
169
|
+
/** The verify gate's clean-tree baseline run (YAG-1003). Defaults to the real one; injected in tests. */
|
|
170
|
+
takeVerifyBaseline?: (cwd: string, dirs: string[], signal?: AbortSignal, budgetMs?: number) => Promise<VerifyBaselineResult>;
|
|
163
171
|
/** stdout sink; one call per NDJSON line, newline added by the caller. */
|
|
164
172
|
write?: (line: string) => void;
|
|
165
173
|
/** stderr sink for human-readable problems. */
|
|
@@ -45,7 +45,8 @@
|
|
|
45
45
|
* - Always, as the last line: the result object
|
|
46
46
|
* `{type:"pipeline_result", ok, stopReason, rounds, findings, stages:[{stage,
|
|
47
47
|
* tier, usage}], …}` — or `{type:"pipeline_error", …}` when a build stage
|
|
48
|
-
* threw.
|
|
48
|
+
* threw. `preexistingFailures` (absent at 0) counts the gate's own
|
|
49
|
+
* typecheck, lint and test failures the clean tree already had (YAG-1003).
|
|
49
50
|
* - Exit codes: 0 ONLY on a verified candidate (a `clean` stop), 1 for any
|
|
50
51
|
* other outcome or a thrown pipeline error, 2 for a usage/input problem.
|
|
51
52
|
*
|
|
@@ -69,7 +70,7 @@ import { normalizeMission } from "./mission.js";
|
|
|
69
70
|
import { MISSION_CHECKS_FILE_NAME, MISSION_CI_RESULTS_FILE_NAME, readMissionChecksFile, renderCiResults, } from "./missionChecks.js";
|
|
70
71
|
import { startTreeSnapshots } from "./treeSnapshot.js";
|
|
71
72
|
import { runPipeline as defaultRunPipeline } from "./orchestrator.js";
|
|
72
|
-
import { makeRunVerify } from "./verify.js";
|
|
73
|
+
import { baselineDirs, makeRunVerify, makeTakeVerifyBaseline, makeWorkstreamCheck, planPaths, } from "./verify.js";
|
|
73
74
|
import { loadGroundingEnabled } from "../grounding.js";
|
|
74
75
|
import { codeStateHome } from "../stateHome.js";
|
|
75
76
|
import { parseTierCap, TIER_CAP_ENV } from "./tierCap.js";
|
|
@@ -82,6 +83,9 @@ export const HEADLESS_GO_USAGE = "Usage: yagni go --headless --ticket-file <path
|
|
|
82
83
|
* can never read as success to a script, a smoke gate, or CI.
|
|
83
84
|
*/
|
|
84
85
|
export const HEADLESS_GO_EXIT = { verified: 0, unverified: 1, usage: 2 };
|
|
86
|
+
/** The verify baseline's share of a deadline run's time left, and the least it is worth starting with. */
|
|
87
|
+
const BASELINE_SHARE_OF_TIME_LEFT = 6;
|
|
88
|
+
const MIN_BASELINE_BUDGET_MS = 60_000;
|
|
85
89
|
const VALUE_FLAGS = {
|
|
86
90
|
"--ticket-file": "ticketFile",
|
|
87
91
|
"--plan-file": "planFile",
|
|
@@ -204,6 +208,7 @@ function buildResult(result, ctx) {
|
|
|
204
208
|
...(finish?.prUrl ? { prUrl: finish.prUrl } : {}),
|
|
205
209
|
...(result.verifyNote ? { verifyNote: result.verifyNote } : {}),
|
|
206
210
|
...(result.verifyCommand ? { verifyCommand: result.verifyCommand } : {}),
|
|
211
|
+
...(ctx.preexistingFailures ? { preexistingFailures: ctx.preexistingFailures } : {}),
|
|
207
212
|
};
|
|
208
213
|
}
|
|
209
214
|
/** Attribution rider for a streamed child event (never overwrites pi's own keys). */
|
|
@@ -327,7 +332,7 @@ export async function runHeadlessGo(argv, deps = {}) {
|
|
|
327
332
|
// A mission's approved checks and its pull request's CI jobs, written by the
|
|
328
333
|
// host beside the ticket: the verify gate runs them inside the loop. No file
|
|
329
334
|
// (a host that predates it) leaves the gate as it was.
|
|
330
|
-
let
|
|
335
|
+
let gateOpts;
|
|
331
336
|
if (mission && args.ticketFile) {
|
|
332
337
|
const checksPath = join(dirname(args.ticketFile), MISSION_CHECKS_FILE_NAME);
|
|
333
338
|
const read = readMissionChecksFile(checksPath, readFile);
|
|
@@ -349,7 +354,7 @@ export async function runHeadlessGo(argv, deps = {}) {
|
|
|
349
354
|
writeErr(`Could not write ${resultsPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
350
355
|
}
|
|
351
356
|
};
|
|
352
|
-
|
|
357
|
+
gateOpts = { repoChecks: () => checks, onFailedChecks };
|
|
353
358
|
}
|
|
354
359
|
else if (read.problem !== "missing") {
|
|
355
360
|
const why = read.problem === "malformed" ? "not a version 1 checks file" : "it could not be read";
|
|
@@ -371,6 +376,51 @@ export async function runHeadlessGo(argv, deps = {}) {
|
|
|
371
376
|
writeErr(`Ignoring ${fixPath}: ${why}. The fix round runs the full pipeline.`);
|
|
372
377
|
}
|
|
373
378
|
}
|
|
379
|
+
// The gate's own checks, once on the clean tree before the build (YAG-1003),
|
|
380
|
+
// in the packages the approved plan names: what already failed there is a
|
|
381
|
+
// note, never a finding. Only a fresh build's tree is the clean base; a
|
|
382
|
+
// continuation or a fix round already carries a change, so it takes none.
|
|
383
|
+
let baseline;
|
|
384
|
+
const logBaseline = (data) => {
|
|
385
|
+
deps.logger?.("verify_baseline", data);
|
|
386
|
+
if (stream)
|
|
387
|
+
emit({ type: "pipeline_log", event: "verify_baseline", data });
|
|
388
|
+
};
|
|
389
|
+
// With a deadline, the baseline gets at most a sixth of the run's time left,
|
|
390
|
+
// and none below a minute: the build itself must still fit.
|
|
391
|
+
const deadlineLeftMs = budget?.deadlineMs === undefined ? undefined : budget.deadlineMs - (deps.now ?? Date.now)();
|
|
392
|
+
const baselineBudgetMs = deadlineLeftMs === undefined ? undefined : Math.floor(deadlineLeftMs / BASELINE_SHARE_OF_TIME_LEFT);
|
|
393
|
+
if (mission?.plan && !continuation && !fixRound && !deps.signal?.aborted) {
|
|
394
|
+
const dirs = baselineDirs(cwd, planPaths(mission.plan), readFile);
|
|
395
|
+
if (dirs.length > 0 && baselineBudgetMs !== undefined && baselineBudgetMs < MIN_BASELINE_BUDGET_MS) {
|
|
396
|
+
logBaseline({ skipped: "too little of the run's time left", deadlineLeftMs, baselineBudgetMs });
|
|
397
|
+
}
|
|
398
|
+
else if (dirs.length > 0) {
|
|
399
|
+
try {
|
|
400
|
+
const taken = await (deps.takeVerifyBaseline ?? makeTakeVerifyBaseline())(cwd, dirs, deps.signal, baselineBudgetMs);
|
|
401
|
+
logBaseline({ ran: taken.ran, failures: taken.failures, notes: taken.notes });
|
|
402
|
+
if (taken.baseline.size > 0)
|
|
403
|
+
baseline = taken.baseline;
|
|
404
|
+
}
|
|
405
|
+
catch (err) {
|
|
406
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
407
|
+
logBaseline({ error: message });
|
|
408
|
+
writeErr(`The verify baseline could not run (every failure counts): ${message}`);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
// The last gate verdict's count of failures the clean tree already had, for the result.
|
|
413
|
+
let preexistingFailures = 0;
|
|
414
|
+
let runVerify;
|
|
415
|
+
if (gateOpts || baseline) {
|
|
416
|
+
const gate = (deps.makeRunVerify ?? makeRunVerify)({ ...gateOpts, ...(baseline ? { baseline } : {}) });
|
|
417
|
+
runVerify = async (verifyCwd, signal, baselinePaths) => {
|
|
418
|
+
const outcome = await gate(verifyCwd, signal, baselinePaths);
|
|
419
|
+
if (outcome.ran)
|
|
420
|
+
preexistingFailures = outcome.preexisting ?? 0;
|
|
421
|
+
return outcome;
|
|
422
|
+
};
|
|
423
|
+
}
|
|
374
424
|
const pipelineDeps = {
|
|
375
425
|
cwd,
|
|
376
426
|
...(fixRound ? { fixRound } : {}),
|
|
@@ -391,6 +441,7 @@ export async function runHeadlessGo(argv, deps = {}) {
|
|
|
391
441
|
grounded: deps.grounded ?? grounding,
|
|
392
442
|
...(mission ? { mission } : {}),
|
|
393
443
|
...(runVerify ? { runVerify } : {}),
|
|
444
|
+
...(baseline ? { runWorkstreamCheck: makeWorkstreamCheck({ baseline }) } : {}),
|
|
394
445
|
...(continuation ? { continuation: { stageAtStop: continuation.stageAtStop } } : {}),
|
|
395
446
|
...(resumeFrom ? { resumeFrom } : {}),
|
|
396
447
|
onEvent: (ev, tag) => {
|
|
@@ -437,6 +488,7 @@ export async function runHeadlessGo(argv, deps = {}) {
|
|
|
437
488
|
...(args.runId ? { runId: args.runId } : {}),
|
|
438
489
|
...(tierCap ? { tierCap } : {}),
|
|
439
490
|
...(mission ? { missionMode: true } : {}),
|
|
491
|
+
preexistingFailures,
|
|
440
492
|
});
|
|
441
493
|
emit({ type: "pipeline_result", ...headless });
|
|
442
494
|
return {
|
|
@@ -94,6 +94,8 @@ export interface VerifyOutcome {
|
|
|
94
94
|
reason?: string;
|
|
95
95
|
/** Critical findings parsed from a failing run; empty otherwise. */
|
|
96
96
|
findings: Finding[];
|
|
97
|
+
/** Failures the clean tree already had (YAG-1003): named in `reason`, never findings. */
|
|
98
|
+
preexisting?: number;
|
|
97
99
|
}
|
|
98
100
|
/** Injectable fs seam so detection is unit-testable without a real repo. */
|
|
99
101
|
export interface DetectOpts {
|
|
@@ -228,15 +230,16 @@ export type TestFailureClassification = {
|
|
|
228
230
|
* Callers scrub the output through {@link scrubSecrets} BEFORE classification
|
|
229
231
|
* so findings/notes never carry secrets (§0.9).
|
|
230
232
|
*/
|
|
231
|
-
export declare function classifyTestFailure(output: string, exitCode: number): TestFailureClassification;
|
|
233
|
+
export declare function classifyTestFailure(output: string, exitCode: number, limit?: number): TestFailureClassification;
|
|
232
234
|
/**
|
|
233
235
|
* Turn a failing verify run into `critical` Findings. A clean (exit 0) run yields
|
|
234
|
-
* none. Located diagnostics (TypeScript first, then a generic file:line form
|
|
235
|
-
*
|
|
236
|
+
* none. Located diagnostics (TypeScript first, then a generic file:line form, then
|
|
237
|
+
* ESLint's stylish error rows under their file header) are parsed into per-error
|
|
238
|
+
* findings, at most `limit` of them; if none parse, a single capped finding carries
|
|
236
239
|
* the tail of the output so the fix stage still gets actionable signal. Never
|
|
237
240
|
* throws; always emits at least one finding on a non-zero exit.
|
|
238
241
|
*/
|
|
239
|
-
export declare function parseVerifyFailures(output: string, exitCode: number, label: string): Finding[];
|
|
242
|
+
export declare function parseVerifyFailures(output: string, exitCode: number, label: string, limit?: number): Finding[];
|
|
240
243
|
/**
|
|
241
244
|
* One finding's file as the REPO ROOT sees it.
|
|
242
245
|
*
|
|
@@ -253,6 +256,49 @@ export declare function parseVerifyFailures(output: string, exitCode: number, la
|
|
|
253
256
|
* so a finding that passes through twice is not double-prefixed.
|
|
254
257
|
*/
|
|
255
258
|
export declare function toRepoRelativePath(file: string, repoRoot: string, commandCwd: string): string;
|
|
259
|
+
/**
|
|
260
|
+
* What the gate's own checks already failed on before the build changed
|
|
261
|
+
* anything (YAG-1003): per command, how many times each failure key was seen
|
|
262
|
+
* on the clean tree. A command with no entry was not run there (or could not
|
|
263
|
+
* run), and its failures all count, as before.
|
|
264
|
+
*/
|
|
265
|
+
export type VerifyBaseline = ReadonlyMap<string, ReadonlyMap<string, number>>;
|
|
266
|
+
/** One command's identity across the baseline run and the build's runs: where it runs and what it runs. */
|
|
267
|
+
export declare function verifyCommandKey(command: Pick<VerifyCommand, "cwd" | "argv">): string;
|
|
268
|
+
/**
|
|
269
|
+
* One failure's key, the shape #2601's `failureKeyOf` gives a CI failure: the
|
|
270
|
+
* repo-relative file and the message, never the line, since a change above a
|
|
271
|
+
* failure moves it. A failure that is only the output's tail names nothing
|
|
272
|
+
* stable, so it has no key and is never excused. Pure.
|
|
273
|
+
*/
|
|
274
|
+
export declare function verifyFailureKey(finding: Finding): string | null;
|
|
275
|
+
/** How many times each failure key occurs. Pure. */
|
|
276
|
+
export declare function failureKeyCounts(findings: readonly Finding[]): Map<string, number>;
|
|
277
|
+
/**
|
|
278
|
+
* The failures this change caused: each failure key is excused at most as many
|
|
279
|
+
* times as the clean tree had it, so a second copy of an old error is new. A
|
|
280
|
+
* failure in a file the build did not touch still counts when the baseline did
|
|
281
|
+
* not have it, since a change can break another file's types. No baseline for
|
|
282
|
+
* the command leaves every failure in place. Pure.
|
|
283
|
+
*/
|
|
284
|
+
export declare function subtractBaseline(findings: readonly Finding[], baseline: ReadonlyMap<string, number> | undefined): {
|
|
285
|
+
findings: Finding[];
|
|
286
|
+
excused: number;
|
|
287
|
+
};
|
|
288
|
+
/** The note for failures the clean tree already had, by command label. Pure. */
|
|
289
|
+
export declare function preexistingNote(byLabel: ReadonlyMap<string, number>): string | null;
|
|
290
|
+
/**
|
|
291
|
+
* The repo-relative paths an approved plan names (`src/flows/a.tsx`,
|
|
292
|
+
* `packages/web/`), which say which packages the build will change before it
|
|
293
|
+
* has changed anything. URLs and paths that leave the tree are dropped. Pure.
|
|
294
|
+
*/
|
|
295
|
+
export declare function planPaths(plan: string): string[];
|
|
296
|
+
/**
|
|
297
|
+
* The package dirs the baseline runs in: those the plan's paths fall in. The
|
|
298
|
+
* repo root drops out when a package below it was named, since its check is
|
|
299
|
+
* not one the build's gate would run then. Pure (the `readFile` seam).
|
|
300
|
+
*/
|
|
301
|
+
export declare function baselineDirs(repoRoot: string, paths: string[], readFile: (path: string) => string): string[];
|
|
256
302
|
/** Result of actually executing the resolved command. */
|
|
257
303
|
export interface VerifyExecResult {
|
|
258
304
|
code: number;
|
|
@@ -352,6 +398,12 @@ export interface MakeRunVerifyOpts {
|
|
|
352
398
|
treeStatus?: (cwd: string, signal?: AbortSignal) => Promise<string>;
|
|
353
399
|
/** Undo what the half wrote into the tree (default: `git clean` / `git checkout` of exactly those paths). */
|
|
354
400
|
undoTreeWrites?: (cwd: string, entries: TreeEntry[]) => Promise<void>;
|
|
401
|
+
/**
|
|
402
|
+
* What the typecheck and test halves already failed on before the build
|
|
403
|
+
* (YAG-1003, {@link makeTakeVerifyBaseline}). Those failures are a note, not
|
|
404
|
+
* findings. Absent: every failure is a finding, as before.
|
|
405
|
+
*/
|
|
406
|
+
baseline?: VerifyBaseline | null;
|
|
355
407
|
}
|
|
356
408
|
/**
|
|
357
409
|
* One workstream as the scoped checker sees it: the name its findings are
|
|
@@ -391,6 +443,8 @@ export interface MakeWorkstreamCheckOpts {
|
|
|
391
443
|
detect?: (cwd: string, claimDirs: string[]) => VerifyCommand[];
|
|
392
444
|
exec?: VerifyExec;
|
|
393
445
|
timeoutMs?: number;
|
|
446
|
+
/** What the clean tree already failed on (YAG-1003): those failures are not a workstream's. */
|
|
447
|
+
baseline?: VerifyBaseline | null;
|
|
394
448
|
}
|
|
395
449
|
/**
|
|
396
450
|
* The per-workstream scoped check the implement diamond runs right after the fan
|
|
@@ -428,5 +482,42 @@ export declare function makeWorkstreamCheck(opts?: MakeWorkstreamCheckOpts): (cw
|
|
|
428
482
|
* blocks a real change on its own failure.
|
|
429
483
|
*/
|
|
430
484
|
export declare function makeRunVerify(opts?: MakeRunVerifyOpts): (cwd: string, signal?: AbortSignal, baselinePaths?: string[]) => Promise<VerifyOutcome>;
|
|
485
|
+
/** The whole baseline run's cap, before the build starts: it costs minutes at most. */
|
|
486
|
+
export declare const VERIFY_BASELINE_BUDGET_MS: number;
|
|
487
|
+
export interface MakeTakeVerifyBaselineOpts {
|
|
488
|
+
detect?: (cwd: string, dirs: string[]) => VerifyCommand[];
|
|
489
|
+
detectTests?: (cwd: string, dirs: string[]) => VerifyCommand[];
|
|
490
|
+
exec?: VerifyExec;
|
|
491
|
+
timeoutMs?: number;
|
|
492
|
+
testTimeoutMs?: number;
|
|
493
|
+
testEnv?: NodeJS.ProcessEnv;
|
|
494
|
+
budgetMs?: number;
|
|
495
|
+
now?: () => number;
|
|
496
|
+
treeStatus?: (cwd: string, signal?: AbortSignal) => Promise<string>;
|
|
497
|
+
undoTreeWrites?: (cwd: string, entries: TreeEntry[]) => Promise<void>;
|
|
498
|
+
}
|
|
499
|
+
/** What the baseline run found, for the gate and for the run's log. */
|
|
500
|
+
export interface VerifyBaselineResult {
|
|
501
|
+
baseline: VerifyBaseline;
|
|
502
|
+
/** Commands that gave a verdict on the clean tree, by label. */
|
|
503
|
+
ran: string[];
|
|
504
|
+
/** Keyed failures the clean tree already had, across those commands. */
|
|
505
|
+
failures: number;
|
|
506
|
+
/** Commands that gave no verdict, and why: their failures all count later. */
|
|
507
|
+
notes: string[];
|
|
508
|
+
}
|
|
509
|
+
/**
|
|
510
|
+
* Run the gate's own typecheck and test commands ONCE on the clean tree,
|
|
511
|
+
* before the build changes anything (YAG-1003), in the package dirs the
|
|
512
|
+
* approved plan names, and keep each command's failure keys. The build's gate
|
|
513
|
+
* then counts only the failures this change added ({@link subtractBaseline}).
|
|
514
|
+
*
|
|
515
|
+
* The same detection, exec seam and environments as {@link makeRunVerify}, so
|
|
516
|
+
* a command's key and its failures' keys match the build's runs. Fail-open per
|
|
517
|
+
* command: one that cannot run, times out, or is not reached in the budget
|
|
518
|
+
* has no baseline, and its failures all count, as before. What the commands
|
|
519
|
+
* write into the tree is undone, so none of it reaches the candidate.
|
|
520
|
+
*/
|
|
521
|
+
export declare function makeTakeVerifyBaseline(opts?: MakeTakeVerifyBaselineOpts): (cwd: string, dirs: string[], signal?: AbortSignal, budgetOverrideMs?: number) => Promise<VerifyBaselineResult>;
|
|
431
522
|
export {};
|
|
432
523
|
//# sourceMappingURL=verify.d.ts.map
|
|
@@ -458,7 +458,7 @@ const FAILURE_SUMMARY = [
|
|
|
458
458
|
* Callers scrub the output through {@link scrubSecrets} BEFORE classification
|
|
459
459
|
* so findings/notes never carry secrets (§0.9).
|
|
460
460
|
*/
|
|
461
|
-
export function classifyTestFailure(output, exitCode) {
|
|
461
|
+
export function classifyTestFailure(output, exitCode, limit = MAX_LOCATED_FINDINGS) {
|
|
462
462
|
if (exitCode === 0)
|
|
463
463
|
return { kind: "clean" };
|
|
464
464
|
const spawnInfra = classifyVerifyInfraFailure(output, exitCode);
|
|
@@ -521,7 +521,7 @@ export function classifyTestFailure(output, exitCode) {
|
|
|
521
521
|
else if (!existing.file && parsed.file)
|
|
522
522
|
byMessage.set(parsed.message, parsed);
|
|
523
523
|
}
|
|
524
|
-
const failures = [...byMessage.values()].slice(0,
|
|
524
|
+
const failures = [...byMessage.values()].slice(0, limit);
|
|
525
525
|
if (failures.length > 0) {
|
|
526
526
|
return {
|
|
527
527
|
kind: "failures",
|
|
@@ -558,28 +558,45 @@ export function classifyTestFailure(output, exitCode) {
|
|
|
558
558
|
const TS_DIAGNOSTIC = /^(.+?)\((\d+),\d+\):\s*error\s+TS\d+:\s*(.+)$/;
|
|
559
559
|
/** Match a generic `path:line:col: error message` (eslint/tsc-pretty-ish). */
|
|
560
560
|
const GENERIC_DIAGNOSTIC = /^(.+?):(\d+):\d+:?\s*(?:error\b[:\s]*)?(.+)$/;
|
|
561
|
+
/** An ESLint stylish file header: one path with an extension, nothing else on the line. */
|
|
562
|
+
const STYLISH_FILE = /^(?:\.{0,2}\/)?[^\s:]+\.[A-Za-z0-9]+$/;
|
|
563
|
+
/** An ESLint stylish error row under that header: `12:5 error message rule`. */
|
|
564
|
+
const STYLISH_ERROR = /^(\d+):\d+\s+(?:error|Error:)\s+(.+)$/;
|
|
561
565
|
/**
|
|
562
566
|
* Turn a failing verify run into `critical` Findings. A clean (exit 0) run yields
|
|
563
|
-
* none. Located diagnostics (TypeScript first, then a generic file:line form
|
|
564
|
-
*
|
|
567
|
+
* none. Located diagnostics (TypeScript first, then a generic file:line form, then
|
|
568
|
+
* ESLint's stylish error rows under their file header) are parsed into per-error
|
|
569
|
+
* findings, at most `limit` of them; if none parse, a single capped finding carries
|
|
565
570
|
* the tail of the output so the fix stage still gets actionable signal. Never
|
|
566
571
|
* throws; always emits at least one finding on a non-zero exit.
|
|
567
572
|
*/
|
|
568
|
-
export function parseVerifyFailures(output, exitCode, label) {
|
|
573
|
+
export function parseVerifyFailures(output, exitCode, label, limit = MAX_LOCATED_FINDINGS) {
|
|
569
574
|
if (exitCode === 0)
|
|
570
575
|
return [];
|
|
571
576
|
const located = [];
|
|
572
577
|
const seen = new Set();
|
|
578
|
+
// ESLint's default (stylish) and `next lint` output name the file once, on
|
|
579
|
+
// its own line, above that file's `line:col error message rule` rows.
|
|
580
|
+
let stylishFile;
|
|
573
581
|
for (const raw of output.split("\n")) {
|
|
574
582
|
const line = raw.trim();
|
|
575
583
|
if (!line)
|
|
576
584
|
continue;
|
|
577
585
|
const m = line.match(TS_DIAGNOSTIC) ?? line.match(GENERIC_DIAGNOSTIC);
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
586
|
+
let parts = m ? [m[1], m[2], m[3]] : null;
|
|
587
|
+
if (!parts) {
|
|
588
|
+
if (STYLISH_FILE.test(line)) {
|
|
589
|
+
stylishFile = line;
|
|
590
|
+
continue;
|
|
591
|
+
}
|
|
592
|
+
const row = stylishFile ? line.match(STYLISH_ERROR) : null;
|
|
593
|
+
if (!row || !stylishFile)
|
|
594
|
+
continue;
|
|
595
|
+
parts = [stylishFile, row[1], row[2].replace(/\s{2,}/g, " ")];
|
|
596
|
+
}
|
|
597
|
+
const file = parts[0].trim();
|
|
598
|
+
const lineNo = Number(parts[1]);
|
|
599
|
+
const message = parts[2].trim();
|
|
583
600
|
// Guard the generic matcher against noise: require a plausible file token.
|
|
584
601
|
if (!file || /\s/.test(file) || !Number.isFinite(lineNo))
|
|
585
602
|
continue;
|
|
@@ -594,7 +611,7 @@ export function parseVerifyFailures(output, exitCode, label) {
|
|
|
594
611
|
file,
|
|
595
612
|
line: lineNo,
|
|
596
613
|
});
|
|
597
|
-
if (located.length >=
|
|
614
|
+
if (located.length >= limit)
|
|
598
615
|
break;
|
|
599
616
|
}
|
|
600
617
|
if (located.length > 0)
|
|
@@ -651,6 +668,98 @@ function liftFinding(finding, repoRoot, commandCwd) {
|
|
|
651
668
|
const file = toRepoRelativePath(finding.file, repoRoot, commandCwd);
|
|
652
669
|
return file === finding.file ? finding : { ...finding, file };
|
|
653
670
|
}
|
|
671
|
+
/** One command's identity across the baseline run and the build's runs: where it runs and what it runs. */
|
|
672
|
+
export function verifyCommandKey(command) {
|
|
673
|
+
return `${command.cwd}\u0000${command.argv.join(" ")}`;
|
|
674
|
+
}
|
|
675
|
+
/**
|
|
676
|
+
* One failure's key, the shape #2601's `failureKeyOf` gives a CI failure: the
|
|
677
|
+
* repo-relative file and the message, never the line, since a change above a
|
|
678
|
+
* failure moves it. A failure that is only the output's tail names nothing
|
|
679
|
+
* stable, so it has no key and is never excused. Pure.
|
|
680
|
+
*/
|
|
681
|
+
export function verifyFailureKey(finding) {
|
|
682
|
+
if (finding.message.includes("Output tail:"))
|
|
683
|
+
return null;
|
|
684
|
+
return `${finding.file ?? ""}\u0000${finding.message}`;
|
|
685
|
+
}
|
|
686
|
+
/** How many times each failure key occurs. Pure. */
|
|
687
|
+
export function failureKeyCounts(findings) {
|
|
688
|
+
const counts = new Map();
|
|
689
|
+
for (const finding of findings) {
|
|
690
|
+
const key = verifyFailureKey(finding);
|
|
691
|
+
if (key !== null)
|
|
692
|
+
counts.set(key, (counts.get(key) ?? 0) + 1);
|
|
693
|
+
}
|
|
694
|
+
return counts;
|
|
695
|
+
}
|
|
696
|
+
/**
|
|
697
|
+
* The failures this change caused: each failure key is excused at most as many
|
|
698
|
+
* times as the clean tree had it, so a second copy of an old error is new. A
|
|
699
|
+
* failure in a file the build did not touch still counts when the baseline did
|
|
700
|
+
* not have it, since a change can break another file's types. No baseline for
|
|
701
|
+
* the command leaves every failure in place. Pure.
|
|
702
|
+
*/
|
|
703
|
+
export function subtractBaseline(findings, baseline) {
|
|
704
|
+
if (!baseline)
|
|
705
|
+
return { findings: [...findings], excused: 0 };
|
|
706
|
+
const left = new Map(baseline);
|
|
707
|
+
const fresh = [];
|
|
708
|
+
let excused = 0;
|
|
709
|
+
for (const finding of findings) {
|
|
710
|
+
const key = verifyFailureKey(finding);
|
|
711
|
+
const count = key === null ? 0 : (left.get(key) ?? 0);
|
|
712
|
+
if (key !== null && count > 0) {
|
|
713
|
+
left.set(key, count - 1);
|
|
714
|
+
excused += 1;
|
|
715
|
+
}
|
|
716
|
+
else {
|
|
717
|
+
fresh.push(finding);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
return { findings: fresh, excused };
|
|
721
|
+
}
|
|
722
|
+
/** The note for failures the clean tree already had, by command label. Pure. */
|
|
723
|
+
export function preexistingNote(byLabel) {
|
|
724
|
+
const total = [...byLabel.values()].reduce((sum, n) => sum + n, 0);
|
|
725
|
+
if (total === 0)
|
|
726
|
+
return null;
|
|
727
|
+
const parts = [...byLabel].filter(([, n]) => n > 0).map(([label, n]) => `${label} ${n}`);
|
|
728
|
+
return `already failed before this change: ${total} (${parts.join(", ")}); not findings`;
|
|
729
|
+
}
|
|
730
|
+
/** Cap on the paths read off a plan, and on the packages the baseline runs in. */
|
|
731
|
+
const MAX_PLAN_PATHS = 200;
|
|
732
|
+
const MAX_BASELINE_PACKAGES = 10;
|
|
733
|
+
/** A path-shaped token in plan prose: at least one `/`, no scheme. */
|
|
734
|
+
const PLAN_PATH = /(?:^|[\s`'"(<[])(\.?\/?[\w@.+-]+(?:\/[\w@.+[\]-]+)+\/?)/g;
|
|
735
|
+
/**
|
|
736
|
+
* The repo-relative paths an approved plan names (`src/flows/a.tsx`,
|
|
737
|
+
* `packages/web/`), which say which packages the build will change before it
|
|
738
|
+
* has changed anything. URLs and paths that leave the tree are dropped. Pure.
|
|
739
|
+
*/
|
|
740
|
+
export function planPaths(plan) {
|
|
741
|
+
const paths = new Set();
|
|
742
|
+
for (const match of plan.matchAll(PLAN_PATH)) {
|
|
743
|
+
const raw = match[1].replace(/[.,:;]+$/, "").replace(/^\.\//, "");
|
|
744
|
+
if (!raw || raw.startsWith("/") || raw.startsWith("..") || raw.includes("://"))
|
|
745
|
+
continue;
|
|
746
|
+
paths.add(raw);
|
|
747
|
+
if (paths.size >= MAX_PLAN_PATHS)
|
|
748
|
+
break;
|
|
749
|
+
}
|
|
750
|
+
return [...paths];
|
|
751
|
+
}
|
|
752
|
+
/**
|
|
753
|
+
* The package dirs the baseline runs in: those the plan's paths fall in. The
|
|
754
|
+
* repo root drops out when a package below it was named, since its check is
|
|
755
|
+
* not one the build's gate would run then. Pure (the `readFile` seam).
|
|
756
|
+
*/
|
|
757
|
+
export function baselineDirs(repoRoot, paths, readFile) {
|
|
758
|
+
const dirs = resolveClaimDirs(repoRoot, paths, readFile);
|
|
759
|
+
const root = normDir(repoRoot);
|
|
760
|
+
const below = dirs.filter((dir) => normDir(dir) !== root);
|
|
761
|
+
return (below.length > 0 ? below : dirs).slice(0, MAX_BASELINE_PACKAGES);
|
|
762
|
+
}
|
|
654
763
|
const SAFE_ENV_KEYS = [
|
|
655
764
|
"PATH",
|
|
656
765
|
"HOME",
|
|
@@ -815,6 +924,13 @@ export function makeDefaultExec(opts = {}) {
|
|
|
815
924
|
});
|
|
816
925
|
}
|
|
817
926
|
const defaultExec = makeDefaultExec();
|
|
927
|
+
/** The gate's typecheck-half detection, shared by the build's runs and the baseline run. */
|
|
928
|
+
const defaultDetect = (cwd, changedDirs) => detectVerifyCommands(cwd, {
|
|
929
|
+
allowRepoOverride: process.env.YAGNI_CODE_ALLOW_VERIFY_OVERRIDE === "1",
|
|
930
|
+
changedDirs,
|
|
931
|
+
});
|
|
932
|
+
/** The gate's test-half detection, shared the same way. */
|
|
933
|
+
const defaultDetectTests = (cwd, changedDirs) => detectVerifyTestCommands(cwd, { changedDirs });
|
|
818
934
|
/**
|
|
819
935
|
* A workstream's claims resolved to package dirs. Claims are PREFIXES and a claim
|
|
820
936
|
* is very often a directory (`packages/backend/src/routes`), while
|
|
@@ -921,7 +1037,7 @@ export function makeWorkstreamCheck(opts = {}) {
|
|
|
921
1037
|
anyRan = true;
|
|
922
1038
|
labels.push(command.label);
|
|
923
1039
|
if (run.code !== 0) {
|
|
924
|
-
const parsed = parseVerifyFailures(run.output, run.code, command.label).map((f) => liftFinding(f, cwd, command.cwd));
|
|
1040
|
+
const parsed = subtractBaseline(parseVerifyFailures(run.output, run.code, command.label, Number.POSITIVE_INFINITY).map((f) => liftFinding(f, cwd, command.cwd)), opts.baseline?.get(verifyCommandKey(command))).findings.slice(0, MAX_LOCATED_FINDINGS);
|
|
925
1041
|
const covered = parsed.filter((f) => f.file != null && target.files.some((claim) => claimCovers(claim, f.file)));
|
|
926
1042
|
findings.push(...covered.map((f) => tagFinding(f, target.name)));
|
|
927
1043
|
if (!uncoveredEmitted.has(memoKey)) {
|
|
@@ -952,13 +1068,8 @@ export function makeWorkstreamCheck(opts = {}) {
|
|
|
952
1068
|
* blocks a real change on its own failure.
|
|
953
1069
|
*/
|
|
954
1070
|
export function makeRunVerify(opts = {}) {
|
|
955
|
-
const detect = opts.detect ??
|
|
956
|
-
|
|
957
|
-
allowRepoOverride: process.env.YAGNI_CODE_ALLOW_VERIFY_OVERRIDE === "1",
|
|
958
|
-
changedDirs,
|
|
959
|
-
}));
|
|
960
|
-
const detectTests = opts.detectTests ??
|
|
961
|
-
((cwd, changedDirs) => detectVerifyTestCommands(cwd, { changedDirs }));
|
|
1071
|
+
const detect = opts.detect ?? defaultDetect;
|
|
1072
|
+
const detectTests = opts.detectTests ?? defaultDetectTests;
|
|
962
1073
|
const changedDirs = opts.changedDirs ??
|
|
963
1074
|
(async (cwd, signal, baselinePaths) => {
|
|
964
1075
|
const snapshot = await snapshotWorkspace(cwd, signal);
|
|
@@ -1274,6 +1385,12 @@ export function makeRunVerify(opts = {}) {
|
|
|
1274
1385
|
const findings = [];
|
|
1275
1386
|
const reasons = [];
|
|
1276
1387
|
let anyRan = false;
|
|
1388
|
+
// Failures the clean tree already had, by command label (YAG-1003).
|
|
1389
|
+
const preexisting = new Map();
|
|
1390
|
+
const countPreexisting = (label, n) => {
|
|
1391
|
+
if (n > 0)
|
|
1392
|
+
preexisting.set(label, (preexisting.get(label) ?? 0) + n);
|
|
1393
|
+
};
|
|
1277
1394
|
// TYPECHECK HALF (frozen behavior, §0.2): run every changed package's build
|
|
1278
1395
|
// check; union the findings. One package that cannot spawn fails OPEN (a
|
|
1279
1396
|
// recorded reason, no finding) without sinking the verdict for the packages
|
|
@@ -1298,7 +1415,10 @@ export function makeRunVerify(opts = {}) {
|
|
|
1298
1415
|
anyRan = true;
|
|
1299
1416
|
typecheckLabels.push(command.label);
|
|
1300
1417
|
if (code !== 0) {
|
|
1301
|
-
|
|
1418
|
+
const parsed = parseVerifyFailures(output, code, command.label, Number.POSITIVE_INFINITY).map((f) => liftFinding(f, cwd, command.cwd));
|
|
1419
|
+
const fresh = subtractBaseline(parsed, opts.baseline?.get(verifyCommandKey(command)));
|
|
1420
|
+
countPreexisting(command.label, fresh.excused);
|
|
1421
|
+
findings.push(...fresh.findings.slice(0, MAX_LOCATED_FINDINGS));
|
|
1302
1422
|
}
|
|
1303
1423
|
}
|
|
1304
1424
|
catch (err) {
|
|
@@ -1328,7 +1448,7 @@ export function makeRunVerify(opts = {}) {
|
|
|
1328
1448
|
try {
|
|
1329
1449
|
const { code, output: rawOutput } = await exec(command.argv, command.cwd, composed, testEnv);
|
|
1330
1450
|
const output = scrubSecrets(rawOutput);
|
|
1331
|
-
const cls = classifyTestFailure(output, code);
|
|
1451
|
+
const cls = classifyTestFailure(output, code, Number.POSITIVE_INFINITY);
|
|
1332
1452
|
if (cls.kind === "infra") {
|
|
1333
1453
|
reasons.push(`tests (${where}) could not run: ${cls.reason}; 'clean' reflects typecheck + review only`);
|
|
1334
1454
|
continue;
|
|
@@ -1343,7 +1463,9 @@ export function makeRunVerify(opts = {}) {
|
|
|
1343
1463
|
testLabels.push(command.label);
|
|
1344
1464
|
if (cls.kind === "failures") {
|
|
1345
1465
|
const room = Math.max(0, MAX_LOCATED_FINDINGS - testFindingCount);
|
|
1346
|
-
const
|
|
1466
|
+
const fresh = subtractBaseline(cls.findings.map((f) => liftFinding(f, cwd, command.cwd)), opts.baseline?.get(verifyCommandKey(command)));
|
|
1467
|
+
countPreexisting(command.label, fresh.excused);
|
|
1468
|
+
const take = fresh.findings.slice(0, room);
|
|
1347
1469
|
testFindingCount += take.length;
|
|
1348
1470
|
findings.push(...take);
|
|
1349
1471
|
}
|
|
@@ -1378,6 +1500,10 @@ export function makeRunVerify(opts = {}) {
|
|
|
1378
1500
|
findings.push(...repo.findings);
|
|
1379
1501
|
}
|
|
1380
1502
|
}
|
|
1503
|
+
const note = preexistingNote(preexisting);
|
|
1504
|
+
if (note)
|
|
1505
|
+
reasons.push(note);
|
|
1506
|
+
const preexistingTotal = [...preexisting.values()].reduce((sum, n) => sum + n, 0);
|
|
1381
1507
|
// The command labels list only what produced a verdict — on every half —
|
|
1382
1508
|
// so the FINISH trailer's `passed (<cmds>)` never claims a check that was
|
|
1383
1509
|
// infra-skipped or threw (invariant §0.1).
|
|
@@ -1401,7 +1527,114 @@ export function makeRunVerify(opts = {}) {
|
|
|
1401
1527
|
command,
|
|
1402
1528
|
findings,
|
|
1403
1529
|
...(reasons.length ? { reason: reasons.join("; ") } : {}),
|
|
1530
|
+
...(preexistingTotal > 0 ? { preexisting: preexistingTotal } : {}),
|
|
1404
1531
|
};
|
|
1405
1532
|
};
|
|
1406
1533
|
}
|
|
1534
|
+
/** The whole baseline run's cap, before the build starts: it costs minutes at most. */
|
|
1535
|
+
export const VERIFY_BASELINE_BUDGET_MS = 10 * 60_000;
|
|
1536
|
+
/**
|
|
1537
|
+
* Run the gate's own typecheck and test commands ONCE on the clean tree,
|
|
1538
|
+
* before the build changes anything (YAG-1003), in the package dirs the
|
|
1539
|
+
* approved plan names, and keep each command's failure keys. The build's gate
|
|
1540
|
+
* then counts only the failures this change added ({@link subtractBaseline}).
|
|
1541
|
+
*
|
|
1542
|
+
* The same detection, exec seam and environments as {@link makeRunVerify}, so
|
|
1543
|
+
* a command's key and its failures' keys match the build's runs. Fail-open per
|
|
1544
|
+
* command: one that cannot run, times out, or is not reached in the budget
|
|
1545
|
+
* has no baseline, and its failures all count, as before. What the commands
|
|
1546
|
+
* write into the tree is undone, so none of it reaches the candidate.
|
|
1547
|
+
*/
|
|
1548
|
+
export function makeTakeVerifyBaseline(opts = {}) {
|
|
1549
|
+
const detect = opts.detect ?? defaultDetect;
|
|
1550
|
+
const detectTests = opts.detectTests ?? defaultDetectTests;
|
|
1551
|
+
const exec = opts.exec ?? defaultExec;
|
|
1552
|
+
const now = opts.now ?? Date.now;
|
|
1553
|
+
const treeStatus = opts.treeStatus ??
|
|
1554
|
+
(async (cwd, signal) => (await snapshotWorkspace(cwd, signal)).status);
|
|
1555
|
+
const undoTreeWrites = opts.undoTreeWrites ?? defaultUndoTreeWrites;
|
|
1556
|
+
return async function takeVerifyBaseline(cwd, dirs, signal, budgetOverrideMs) {
|
|
1557
|
+
const baseline = new Map();
|
|
1558
|
+
const result = { baseline, ran: [], failures: 0, notes: [] };
|
|
1559
|
+
if (dirs.length === 0)
|
|
1560
|
+
return result;
|
|
1561
|
+
const commands = detect(cwd, dirs);
|
|
1562
|
+
const overridden = commands.some((c) => c.source === "override");
|
|
1563
|
+
const tests = overridden ? [] : detectTests(cwd, dirs);
|
|
1564
|
+
if (commands.length === 0 && tests.length === 0)
|
|
1565
|
+
return result;
|
|
1566
|
+
const startedAt = now();
|
|
1567
|
+
const budgetMs = Math.min(opts.budgetMs ?? VERIFY_BASELINE_BUDGET_MS, budgetOverrideMs ?? Number.POSITIVE_INFINITY);
|
|
1568
|
+
const before = await treeStatus(cwd, signal).catch((err) => {
|
|
1569
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1570
|
+
result.notes.push(`could not read the tree before the baseline, so what it writes is not undone: ${scrubSecrets(msg)}`);
|
|
1571
|
+
return null;
|
|
1572
|
+
});
|
|
1573
|
+
const runs = [
|
|
1574
|
+
...commands.map((command) => ({ command, test: false })),
|
|
1575
|
+
...tests.map((command) => ({ command, test: true })),
|
|
1576
|
+
];
|
|
1577
|
+
try {
|
|
1578
|
+
for (const { command, test } of runs) {
|
|
1579
|
+
if (signal?.aborted)
|
|
1580
|
+
break;
|
|
1581
|
+
const left = budgetMs - (now() - startedAt);
|
|
1582
|
+
if (left < REPO_CHECK_MIN_START_MS) {
|
|
1583
|
+
result.notes.push(`${command.label} (${command.cwd}): not reached in the baseline budget`);
|
|
1584
|
+
continue;
|
|
1585
|
+
}
|
|
1586
|
+
const cap = Math.min(test
|
|
1587
|
+
? (opts.testTimeoutMs ?? VERIFY_TEST_TIMEOUT_MS)
|
|
1588
|
+
: (opts.timeoutMs ?? VERIFY_TIMEOUT_MS), left);
|
|
1589
|
+
const composed = composeAbortSignal(signal, AbortSignal.timeout(cap));
|
|
1590
|
+
try {
|
|
1591
|
+
const { code, output: rawOutput } = test
|
|
1592
|
+
? await exec(command.argv, command.cwd, composed, opts.testEnv ?? buildTestEnv())
|
|
1593
|
+
: await exec(command.argv, command.cwd, composed);
|
|
1594
|
+
const output = scrubSecrets(rawOutput);
|
|
1595
|
+
let failed;
|
|
1596
|
+
if (test) {
|
|
1597
|
+
const cls = classifyTestFailure(output, code, Number.POSITIVE_INFINITY);
|
|
1598
|
+
if (cls.kind === "infra" || cls.kind === "uncertain") {
|
|
1599
|
+
result.notes.push(`${command.label} (${command.cwd}): ${cls.kind === "infra" ? cls.reason : "output not interpretable"}`);
|
|
1600
|
+
continue;
|
|
1601
|
+
}
|
|
1602
|
+
failed = cls.kind === "failures" ? cls.findings : [];
|
|
1603
|
+
}
|
|
1604
|
+
else {
|
|
1605
|
+
const infra = code === 0 ? null : classifyVerifyInfraFailure(output, code);
|
|
1606
|
+
if (infra) {
|
|
1607
|
+
result.notes.push(`${command.label} (${command.cwd}): ${infra}`);
|
|
1608
|
+
continue;
|
|
1609
|
+
}
|
|
1610
|
+
failed = parseVerifyFailures(output, code, command.label, Number.POSITIVE_INFINITY);
|
|
1611
|
+
}
|
|
1612
|
+
const counts = failureKeyCounts(failed.map((f) => liftFinding(f, cwd, command.cwd)));
|
|
1613
|
+
baseline.set(verifyCommandKey(command), counts);
|
|
1614
|
+
result.ran.push(command.label);
|
|
1615
|
+
for (const n of counts.values())
|
|
1616
|
+
result.failures += n;
|
|
1617
|
+
}
|
|
1618
|
+
catch (err) {
|
|
1619
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1620
|
+
result.notes.push(`${command.label} (${command.cwd}): could not run: ${scrubSecrets(msg)}`);
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1623
|
+
}
|
|
1624
|
+
finally {
|
|
1625
|
+
if (before !== null) {
|
|
1626
|
+
try {
|
|
1627
|
+
const written = newTreeEntries(before, await treeStatus(cwd));
|
|
1628
|
+
if (written.length > 0)
|
|
1629
|
+
await undoTreeWrites(cwd, written);
|
|
1630
|
+
}
|
|
1631
|
+
catch (err) {
|
|
1632
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1633
|
+
result.notes.push(`could not undo what the baseline wrote into the tree: ${scrubSecrets(msg)}`);
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
return result;
|
|
1638
|
+
};
|
|
1639
|
+
}
|
|
1407
1640
|
//# sourceMappingURL=verify.js.map
|
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.1952.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": "b25d4f380aa71b29777808fe149c71d8d0e3c4ce"
|
|
62
62
|
}
|