@smartergpt/lexrunner 1.4.0 → 1.5.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.
- package/CHANGELOG.md +116 -0
- package/README.md +29 -23
- package/dist/{audit-5ZUBPZZX.js → audit-SKMMC2U4.js} +1 -1
- package/dist/{autopilot-JXO4MZ6H.js → autopilot-62Y2M2ZO.js} +1 -1
- package/dist/{chunk-R4DWK6FE.js → chunk-6PZDSWCG.js} +13 -10
- package/dist/{chunk-WFN4JTZI.js → chunk-LQSRQ6Z3.js} +5 -5
- package/dist/{chunk-WSJEPY7T.js → chunk-TIMHM7VL.js} +386 -80
- package/dist/cli.cjs +15467 -2652
- package/dist/cli.d.ts +1279 -10
- package/dist/cli.js +14699 -2226
- package/dist/{constraints-V4VWCWR5.js → constraints-2ZK5FJFA.js} +1 -1
- package/package.json +3 -2
- package/schemas/gate-execution-receipt.schema.json +161 -0
|
@@ -2100,7 +2100,7 @@ function validateGateInput(gateName, input) {
|
|
|
2100
2100
|
const schema = loadGateSchema(gateName);
|
|
2101
2101
|
if (!schema) {
|
|
2102
2102
|
console.warn(
|
|
2103
|
-
"[
|
|
2103
|
+
"[lexrunner] validator: missing schema for gate; validation skipped (will be required in v1.0.0)"
|
|
2104
2104
|
);
|
|
2105
2105
|
return;
|
|
2106
2106
|
}
|
|
@@ -2956,10 +2956,10 @@ function prompt(question) {
|
|
|
2956
2956
|
input: process.stdin,
|
|
2957
2957
|
output: process.stdout
|
|
2958
2958
|
});
|
|
2959
|
-
return new Promise((
|
|
2959
|
+
return new Promise((resolve6) => {
|
|
2960
2960
|
rl.question(question, (answer) => {
|
|
2961
2961
|
rl.close();
|
|
2962
|
-
|
|
2962
|
+
resolve6(answer.trim());
|
|
2963
2963
|
});
|
|
2964
2964
|
});
|
|
2965
2965
|
}
|
|
@@ -3160,7 +3160,7 @@ async function terminateProcessTree(child, options = {}) {
|
|
|
3160
3160
|
return { method: "taskkill", forceKilled: true, descendantsReaped: reaped };
|
|
3161
3161
|
}
|
|
3162
3162
|
const killGroup = options.killGroup ?? process.kill.bind(process);
|
|
3163
|
-
const wait = options.wait ?? ((durationMs) => new Promise((
|
|
3163
|
+
const wait = options.wait ?? ((durationMs) => new Promise((resolve6) => setTimeout(resolve6, durationMs)));
|
|
3164
3164
|
const graceMs = options.graceMs ?? 500;
|
|
3165
3165
|
let method = "process-group";
|
|
3166
3166
|
try {
|
|
@@ -3193,17 +3193,178 @@ function groupExists(pid, killGroup) {
|
|
|
3193
3193
|
}
|
|
3194
3194
|
}
|
|
3195
3195
|
function runTaskkill(pid) {
|
|
3196
|
-
return new Promise((
|
|
3196
|
+
return new Promise((resolve6) => {
|
|
3197
3197
|
const killer = spawn("taskkill", ["/PID", String(pid), "/T", "/F"], {
|
|
3198
3198
|
stdio: "ignore",
|
|
3199
3199
|
windowsHide: true
|
|
3200
3200
|
});
|
|
3201
|
-
killer.once("error", () =>
|
|
3202
|
-
killer.once("close", (code) =>
|
|
3201
|
+
killer.once("error", () => resolve6(false));
|
|
3202
|
+
killer.once("close", (code) => resolve6(code === 0 || code === 128));
|
|
3203
3203
|
});
|
|
3204
3204
|
}
|
|
3205
3205
|
|
|
3206
|
+
// src/gates/execution-receipt.ts
|
|
3207
|
+
import { randomUUID } from "crypto";
|
|
3208
|
+
import {
|
|
3209
|
+
copyFileSync as copyFileSync2,
|
|
3210
|
+
existsSync as existsSync9,
|
|
3211
|
+
mkdirSync as mkdirSync5,
|
|
3212
|
+
readFileSync as readFileSync10,
|
|
3213
|
+
realpathSync,
|
|
3214
|
+
renameSync as renameSync2,
|
|
3215
|
+
statSync as statSync2,
|
|
3216
|
+
writeFileSync as writeFileSync5
|
|
3217
|
+
} from "fs";
|
|
3218
|
+
import { basename as basename2, delimiter, dirname, extname, isAbsolute as isAbsolute2, join as join9, resolve as resolve5 } from "path";
|
|
3219
|
+
var GATE_EXECUTION_RECEIPT_SCHEMA_VERSION = "lexrunner-gate-execution-receipt/v1";
|
|
3220
|
+
var MAX_GATE_RECEIPT_OUTPUT_BYTES = 64 * 1024;
|
|
3221
|
+
function resolveSpawnExecutable(command, environment, workingDirectory, platform2 = process.platform) {
|
|
3222
|
+
if (isAbsolute2(command) || /[\\/]/u.test(command)) {
|
|
3223
|
+
return requireExecutable(resolve5(workingDirectory, command));
|
|
3224
|
+
}
|
|
3225
|
+
const pathValue = environmentValue(environment, "PATH", platform2) ?? "";
|
|
3226
|
+
const extensions = platform2 === "win32" ? executableExtensions(command, environmentValue(environment, "PATHEXT", platform2)) : [""];
|
|
3227
|
+
for (const directory of pathValue.split(delimiter).filter(Boolean)) {
|
|
3228
|
+
for (const extension of extensions) {
|
|
3229
|
+
const candidate = resolve5(directory, `${command}${extension}`);
|
|
3230
|
+
if (isExecutableFile(candidate)) return realpathSync(candidate);
|
|
3231
|
+
}
|
|
3232
|
+
}
|
|
3233
|
+
throw new Error(`Unable to resolve local gate shell executable '${command}'.`);
|
|
3234
|
+
}
|
|
3235
|
+
function fileIdentity(filePath) {
|
|
3236
|
+
try {
|
|
3237
|
+
const stats = statSync2(filePath);
|
|
3238
|
+
if (!stats.isFile()) return null;
|
|
3239
|
+
const realPath = realpathSync(filePath);
|
|
3240
|
+
const content = readFileSync10(realPath);
|
|
3241
|
+
return {
|
|
3242
|
+
path: resolve5(filePath),
|
|
3243
|
+
realPath,
|
|
3244
|
+
bytes: content.byteLength,
|
|
3245
|
+
mtimeMs: stats.mtimeMs,
|
|
3246
|
+
sha256: `sha256:${sha256(content)}`
|
|
3247
|
+
};
|
|
3248
|
+
} catch {
|
|
3249
|
+
return null;
|
|
3250
|
+
}
|
|
3251
|
+
}
|
|
3252
|
+
function captureDeclaredArtifactBaselines(declaredPaths, workingDirectory) {
|
|
3253
|
+
return declaredPaths.map((declaredPath) => {
|
|
3254
|
+
const resolvedPath = resolve5(workingDirectory, declaredPath);
|
|
3255
|
+
return { declaredPath, resolvedPath, before: fileIdentity(resolvedPath) };
|
|
3256
|
+
});
|
|
3257
|
+
}
|
|
3258
|
+
function collectFreshGateArtifacts(baselines, gateArtifactDirectory) {
|
|
3259
|
+
mkdirSync5(gateArtifactDirectory, { recursive: true });
|
|
3260
|
+
const paths = [];
|
|
3261
|
+
const usedNames = /* @__PURE__ */ new Set();
|
|
3262
|
+
const identities = baselines.map((baseline, index) => {
|
|
3263
|
+
const source = fileIdentity(baseline.resolvedPath);
|
|
3264
|
+
if (!existsSync9(baseline.resolvedPath)) {
|
|
3265
|
+
return { ...baseline, status: "missing", source: null };
|
|
3266
|
+
}
|
|
3267
|
+
if (!source) {
|
|
3268
|
+
return { ...baseline, status: "unsupported", source: null };
|
|
3269
|
+
}
|
|
3270
|
+
if (baseline.before?.sha256 === source.sha256) {
|
|
3271
|
+
return { ...baseline, status: "stale", source };
|
|
3272
|
+
}
|
|
3273
|
+
let retainedName = basename2(baseline.resolvedPath);
|
|
3274
|
+
if (usedNames.has(retainedName)) retainedName = `${index + 1}-${retainedName}`;
|
|
3275
|
+
usedNames.add(retainedName);
|
|
3276
|
+
const retainedPath = join9(gateArtifactDirectory, retainedName);
|
|
3277
|
+
try {
|
|
3278
|
+
copyFileSync2(baseline.resolvedPath, retainedPath);
|
|
3279
|
+
const retained = fileIdentity(retainedPath);
|
|
3280
|
+
if (!retained || retained.sha256 !== source.sha256 || retained.bytes !== source.bytes) {
|
|
3281
|
+
return {
|
|
3282
|
+
...baseline,
|
|
3283
|
+
status: "collection_error",
|
|
3284
|
+
source,
|
|
3285
|
+
retainedPath,
|
|
3286
|
+
error: "Retained artifact identity does not match its source."
|
|
3287
|
+
};
|
|
3288
|
+
}
|
|
3289
|
+
paths.push(retainedPath);
|
|
3290
|
+
return { ...baseline, status: "collected", source, retainedPath, retained };
|
|
3291
|
+
} catch (error) {
|
|
3292
|
+
return {
|
|
3293
|
+
...baseline,
|
|
3294
|
+
status: "collection_error",
|
|
3295
|
+
source,
|
|
3296
|
+
retainedPath,
|
|
3297
|
+
error: boundedError(error)
|
|
3298
|
+
};
|
|
3299
|
+
}
|
|
3300
|
+
});
|
|
3301
|
+
return {
|
|
3302
|
+
paths,
|
|
3303
|
+
identities,
|
|
3304
|
+
complete: identities.every(({ status }) => status === "collected")
|
|
3305
|
+
};
|
|
3306
|
+
}
|
|
3307
|
+
function gateOutputEvidence(value) {
|
|
3308
|
+
const bytes = Buffer.isBuffer(value) ? value : Buffer.from(value, "utf8");
|
|
3309
|
+
return {
|
|
3310
|
+
bytes: bytes.byteLength,
|
|
3311
|
+
sha256: `sha256:${sha256(bytes)}`,
|
|
3312
|
+
truncated: bytes.byteLength > MAX_GATE_RECEIPT_OUTPUT_BYTES,
|
|
3313
|
+
content: utf8Prefix(bytes, MAX_GATE_RECEIPT_OUTPUT_BYTES)
|
|
3314
|
+
};
|
|
3315
|
+
}
|
|
3316
|
+
function writeLocalGateExecutionReceipt(artifactDirectory, receipt) {
|
|
3317
|
+
mkdirSync5(artifactDirectory, { recursive: true });
|
|
3318
|
+
const receiptPath = join9(
|
|
3319
|
+
artifactDirectory,
|
|
3320
|
+
`gate-execution-receipt.attempt-${receipt.attempt}.json`
|
|
3321
|
+
);
|
|
3322
|
+
const temporaryPath = join9(
|
|
3323
|
+
dirname(receiptPath),
|
|
3324
|
+
`.${basename2(receiptPath)}.${process.pid}.${randomUUID()}.tmp`
|
|
3325
|
+
);
|
|
3326
|
+
writeFileSync5(temporaryPath, canonicalJSONStringify(receipt), "utf8");
|
|
3327
|
+
renameSync2(temporaryPath, receiptPath);
|
|
3328
|
+
return receiptPath;
|
|
3329
|
+
}
|
|
3330
|
+
function requireExecutable(candidate) {
|
|
3331
|
+
if (!isExecutableFile(candidate)) {
|
|
3332
|
+
throw new Error(`Local gate shell executable does not exist at '${candidate}'.`);
|
|
3333
|
+
}
|
|
3334
|
+
return realpathSync(candidate);
|
|
3335
|
+
}
|
|
3336
|
+
function isExecutableFile(candidate) {
|
|
3337
|
+
try {
|
|
3338
|
+
return statSync2(candidate).isFile();
|
|
3339
|
+
} catch {
|
|
3340
|
+
return false;
|
|
3341
|
+
}
|
|
3342
|
+
}
|
|
3343
|
+
function environmentValue(environment, name, platform2) {
|
|
3344
|
+
if (platform2 !== "win32") return environment[name];
|
|
3345
|
+
const match = Object.keys(environment).find((key) => key.toUpperCase() === name);
|
|
3346
|
+
return match ? environment[match] : void 0;
|
|
3347
|
+
}
|
|
3348
|
+
function executableExtensions(command, pathExt) {
|
|
3349
|
+
if (extname(command)) return [""];
|
|
3350
|
+
return (pathExt || ".COM;.EXE;.BAT;.CMD").split(";").filter(Boolean).map((extension) => extension.toLowerCase());
|
|
3351
|
+
}
|
|
3352
|
+
function utf8Prefix(value, maxBytes) {
|
|
3353
|
+
if (value.byteLength <= maxBytes) return value.toString("utf8");
|
|
3354
|
+
return value.subarray(0, maxBytes).toString("utf8");
|
|
3355
|
+
}
|
|
3356
|
+
function boundedError(error) {
|
|
3357
|
+
const value = error instanceof Error ? error.message : String(error);
|
|
3358
|
+
return value.length <= 1024 ? value : `${value.slice(0, 1024)}\u2026`;
|
|
3359
|
+
}
|
|
3360
|
+
|
|
3206
3361
|
// src/gates.ts
|
|
3362
|
+
function resolveLocalGateShell(command, platform2 = process.platform) {
|
|
3363
|
+
return platform2 === "win32" ? {
|
|
3364
|
+
command: "pwsh",
|
|
3365
|
+
arguments: ["-NoLogo", "-NoProfile", "-NonInteractive", "-Command", command]
|
|
3366
|
+
} : { command: "bash", arguments: ["-c", command] };
|
|
3367
|
+
}
|
|
3207
3368
|
async function executeGate(gate, policy, artifactDir, timeoutMs = 3e4, itemName, skipValidation = false, repoRoot, turnCostTracker, suppressStdout = false) {
|
|
3208
3369
|
if (!skipValidation && gate.input) {
|
|
3209
3370
|
try {
|
|
@@ -3234,7 +3395,7 @@ async function executeGate(gate, policy, artifactDir, timeoutMs = 3e4, itemName,
|
|
|
3234
3395
|
`\u23F3 Retrying gate '${gate.name}' (attempt ${attempt}/${retryConfig.maxAttempts}) after ${retryConfig.backoffSeconds}s delay...`
|
|
3235
3396
|
);
|
|
3236
3397
|
}
|
|
3237
|
-
await new Promise((
|
|
3398
|
+
await new Promise((resolve6) => setTimeout(resolve6, delayMs));
|
|
3238
3399
|
}
|
|
3239
3400
|
const result = await executeGateAttempt(gate, artifactDir, attempt, timeoutMs, repoRoot);
|
|
3240
3401
|
lastResult = result;
|
|
@@ -3375,6 +3536,55 @@ async function executeGateAttempt(gate, artifactDir, attempt, timeoutMs, repoRoo
|
|
|
3375
3536
|
}
|
|
3376
3537
|
}
|
|
3377
3538
|
async function executeLocalGate(gate, artifactDir, attempt, startedAt, startTime, timeoutMs, repoRoot) {
|
|
3539
|
+
const workingDirectory = path9.resolve(gate.cwd || repoRoot || process.cwd());
|
|
3540
|
+
const gateArtifactDirectory = path9.join(artifactDir, gate.name);
|
|
3541
|
+
const environment = { ...process.env, ...gate.env };
|
|
3542
|
+
const shell = resolveLocalGateShell(gate.run);
|
|
3543
|
+
const artifactBaselines = captureDeclaredArtifactBaselines(
|
|
3544
|
+
gate.artifacts ?? [],
|
|
3545
|
+
workingDirectory
|
|
3546
|
+
);
|
|
3547
|
+
let shellExecutable;
|
|
3548
|
+
let shellIdentityBefore = null;
|
|
3549
|
+
try {
|
|
3550
|
+
shellExecutable = resolveSpawnExecutable(
|
|
3551
|
+
shell.command,
|
|
3552
|
+
environment,
|
|
3553
|
+
workingDirectory,
|
|
3554
|
+
process.platform
|
|
3555
|
+
);
|
|
3556
|
+
shellIdentityBefore = fileIdentity(shellExecutable);
|
|
3557
|
+
if (!shellIdentityBefore) throw new Error("Resolved shell is not an evidence-bindable file.");
|
|
3558
|
+
} catch (error) {
|
|
3559
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
3560
|
+
return finalizeLocalGateAttempt({
|
|
3561
|
+
gate,
|
|
3562
|
+
artifactBaselines,
|
|
3563
|
+
gateArtifactDirectory,
|
|
3564
|
+
workingDirectory,
|
|
3565
|
+
attempt,
|
|
3566
|
+
startedAt,
|
|
3567
|
+
startTime,
|
|
3568
|
+
shell,
|
|
3569
|
+
shellExecutable: null,
|
|
3570
|
+
shellIdentityBefore: null,
|
|
3571
|
+
spawned: false,
|
|
3572
|
+
stdout: Buffer.alloc(0),
|
|
3573
|
+
stderr: Buffer.from(errorMessage, "utf8"),
|
|
3574
|
+
result: {
|
|
3575
|
+
gate: gate.name,
|
|
3576
|
+
status: "fail",
|
|
3577
|
+
exitCode: 1,
|
|
3578
|
+
duration: Date.now() - startTime,
|
|
3579
|
+
stdout: "",
|
|
3580
|
+
stderr: errorMessage,
|
|
3581
|
+
failureKind: "spawn_error",
|
|
3582
|
+
artifacts: [],
|
|
3583
|
+
attempts: attempt,
|
|
3584
|
+
lastAttempt: startedAt
|
|
3585
|
+
}
|
|
3586
|
+
});
|
|
3587
|
+
}
|
|
3378
3588
|
try {
|
|
3379
3589
|
const { getCommandValidator } = await import("./commandValidator-LVEMJTZP.js");
|
|
3380
3590
|
const validator = getCommandValidator();
|
|
@@ -3382,34 +3592,50 @@ async function executeLocalGate(gate, artifactDir, attempt, startedAt, startTime
|
|
|
3382
3592
|
} catch (validationError) {
|
|
3383
3593
|
const duration = Date.now() - startTime;
|
|
3384
3594
|
const errorMessage = validationError instanceof Error ? validationError.message : String(validationError);
|
|
3385
|
-
return {
|
|
3386
|
-
gate
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3595
|
+
return finalizeLocalGateAttempt({
|
|
3596
|
+
gate,
|
|
3597
|
+
artifactBaselines,
|
|
3598
|
+
gateArtifactDirectory,
|
|
3599
|
+
workingDirectory,
|
|
3600
|
+
attempt,
|
|
3601
|
+
startedAt,
|
|
3602
|
+
startTime,
|
|
3603
|
+
shell,
|
|
3604
|
+
shellExecutable,
|
|
3605
|
+
shellIdentityBefore,
|
|
3606
|
+
spawned: false,
|
|
3607
|
+
stdout: Buffer.alloc(0),
|
|
3608
|
+
stderr: Buffer.from(`Command validation failed: ${errorMessage}`, "utf8"),
|
|
3609
|
+
result: {
|
|
3610
|
+
gate: gate.name,
|
|
3611
|
+
status: "fail",
|
|
3612
|
+
exitCode: 1,
|
|
3613
|
+
duration,
|
|
3614
|
+
stdout: "",
|
|
3615
|
+
stderr: `Command validation failed: ${errorMessage}`,
|
|
3616
|
+
artifacts: [],
|
|
3617
|
+
attempts: attempt,
|
|
3618
|
+
lastAttempt: startedAt
|
|
3619
|
+
}
|
|
3620
|
+
});
|
|
3396
3621
|
}
|
|
3397
|
-
return new Promise((
|
|
3622
|
+
return new Promise((resolve6) => {
|
|
3398
3623
|
let timedOut = false;
|
|
3399
|
-
|
|
3400
|
-
const childProcess = spawn2(
|
|
3624
|
+
let settled = false;
|
|
3625
|
+
const childProcess = spawn2(shellExecutable, shell.arguments, {
|
|
3401
3626
|
cwd: workingDirectory,
|
|
3402
|
-
env:
|
|
3627
|
+
env: environment,
|
|
3403
3628
|
stdio: ["pipe", "pipe", "pipe"],
|
|
3404
|
-
detached: process.platform !== "win32"
|
|
3629
|
+
detached: process.platform !== "win32",
|
|
3630
|
+
windowsHide: true
|
|
3405
3631
|
});
|
|
3406
|
-
|
|
3407
|
-
|
|
3632
|
+
const stdoutChunks = [];
|
|
3633
|
+
const stderrChunks = [];
|
|
3408
3634
|
childProcess.stdout?.on("data", (data) => {
|
|
3409
|
-
|
|
3635
|
+
stdoutChunks.push(Buffer.isBuffer(data) ? data : Buffer.from(data));
|
|
3410
3636
|
});
|
|
3411
3637
|
childProcess.stderr?.on("data", (data) => {
|
|
3412
|
-
|
|
3638
|
+
stderrChunks.push(Buffer.isBuffer(data) ? data : Buffer.from(data));
|
|
3413
3639
|
});
|
|
3414
3640
|
let timeoutCleanup;
|
|
3415
3641
|
const timeout = setTimeout(() => {
|
|
@@ -3417,44 +3643,146 @@ async function executeLocalGate(gate, artifactDir, attempt, startedAt, startTime
|
|
|
3417
3643
|
timeoutCleanup = terminateProcessTree(childProcess);
|
|
3418
3644
|
}, timeoutMs);
|
|
3419
3645
|
childProcess.on("close", async (exitCode) => {
|
|
3646
|
+
if (settled) return;
|
|
3647
|
+
settled = true;
|
|
3420
3648
|
clearTimeout(timeout);
|
|
3421
3649
|
const cleanup = timeoutCleanup ? await timeoutCleanup : void 0;
|
|
3422
3650
|
const duration = Date.now() - startTime;
|
|
3423
|
-
const
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3651
|
+
const stdout = Buffer.concat(stdoutChunks);
|
|
3652
|
+
const stderr = Buffer.concat(stderrChunks);
|
|
3653
|
+
const stdoutText = stdout.toString("utf8");
|
|
3654
|
+
const stderrText = stderr.toString("utf8");
|
|
3655
|
+
const projectedStderr = timedOut ? `GATE_TIMEOUT: exceeded ${timeoutMs}ms; descendantsReaped=${cleanup?.descendantsReaped ?? false}` : stderrText.trim();
|
|
3656
|
+
resolve6(
|
|
3657
|
+
finalizeLocalGateAttempt({
|
|
3658
|
+
gate,
|
|
3659
|
+
artifactBaselines,
|
|
3660
|
+
gateArtifactDirectory,
|
|
3661
|
+
workingDirectory,
|
|
3662
|
+
attempt,
|
|
3663
|
+
startedAt,
|
|
3664
|
+
startTime,
|
|
3665
|
+
shell,
|
|
3666
|
+
shellExecutable,
|
|
3667
|
+
shellIdentityBefore,
|
|
3668
|
+
spawned: true,
|
|
3669
|
+
stdout,
|
|
3670
|
+
stderr,
|
|
3671
|
+
result: {
|
|
3672
|
+
gate: gate.name,
|
|
3673
|
+
status: exitCode === 0 && !timedOut ? "pass" : "fail",
|
|
3674
|
+
exitCode: timedOut ? 124 : exitCode ?? 1,
|
|
3675
|
+
duration,
|
|
3676
|
+
stdout: stdoutText.trim(),
|
|
3677
|
+
stderr: projectedStderr,
|
|
3678
|
+
failureKind: timedOut ? "timeout" : exitCode === 0 ? void 0 : "nonzero_exit",
|
|
3679
|
+
...cleanup ? { timeoutCleanup: cleanup } : {},
|
|
3680
|
+
artifacts: [],
|
|
3681
|
+
attempts: attempt,
|
|
3682
|
+
lastAttempt: startedAt
|
|
3683
|
+
}
|
|
3684
|
+
})
|
|
3685
|
+
);
|
|
3437
3686
|
});
|
|
3438
3687
|
childProcess.on("error", (error) => {
|
|
3688
|
+
if (settled) return;
|
|
3689
|
+
settled = true;
|
|
3439
3690
|
clearTimeout(timeout);
|
|
3440
3691
|
const duration = Date.now() - startTime;
|
|
3692
|
+
const stdout = Buffer.concat(stdoutChunks);
|
|
3441
3693
|
const classified = classifyError(error, `Gate '${gate.name}' process error`);
|
|
3442
3694
|
console.error(formatErrorForUser(classified));
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3695
|
+
const projectedStderr = `${classified.context}: ${error.message}`;
|
|
3696
|
+
resolve6(
|
|
3697
|
+
finalizeLocalGateAttempt({
|
|
3698
|
+
gate,
|
|
3699
|
+
artifactBaselines,
|
|
3700
|
+
gateArtifactDirectory,
|
|
3701
|
+
workingDirectory,
|
|
3702
|
+
attempt,
|
|
3703
|
+
startedAt,
|
|
3704
|
+
startTime,
|
|
3705
|
+
shell,
|
|
3706
|
+
shellExecutable,
|
|
3707
|
+
shellIdentityBefore,
|
|
3708
|
+
spawned: false,
|
|
3709
|
+
stdout,
|
|
3710
|
+
stderr: Buffer.from(projectedStderr, "utf8"),
|
|
3711
|
+
result: {
|
|
3712
|
+
gate: gate.name,
|
|
3713
|
+
status: "fail",
|
|
3714
|
+
exitCode: 1,
|
|
3715
|
+
duration,
|
|
3716
|
+
stdout: stdout.toString("utf8").trim(),
|
|
3717
|
+
stderr: projectedStderr,
|
|
3718
|
+
failureKind: "spawn_error",
|
|
3719
|
+
artifacts: [],
|
|
3720
|
+
attempts: attempt,
|
|
3721
|
+
lastAttempt: startedAt
|
|
3722
|
+
}
|
|
3723
|
+
})
|
|
3724
|
+
);
|
|
3455
3725
|
});
|
|
3456
3726
|
});
|
|
3457
3727
|
}
|
|
3728
|
+
function finalizeLocalGateAttempt(input) {
|
|
3729
|
+
const collected = collectFreshGateArtifacts(input.artifactBaselines, input.gateArtifactDirectory);
|
|
3730
|
+
const shellIdentityAfter = input.shellExecutable ? fileIdentity(input.shellExecutable) : null;
|
|
3731
|
+
const shellUnchanged = input.shellIdentityBefore !== null && shellIdentityAfter !== null && input.shellIdentityBefore.sha256 === shellIdentityAfter.sha256 && input.shellIdentityBefore.bytes === shellIdentityAfter.bytes;
|
|
3732
|
+
const evidenceComplete = collected.complete && (!input.spawned || input.shellIdentityBefore !== null && shellUnchanged);
|
|
3733
|
+
const result = { ...input.result };
|
|
3734
|
+
if (result.status === "pass" && !evidenceComplete) {
|
|
3735
|
+
result.status = "fail";
|
|
3736
|
+
result.exitCode = 1;
|
|
3737
|
+
result.failureKind = "evidence_error";
|
|
3738
|
+
result.stderr = [
|
|
3739
|
+
result.stderr,
|
|
3740
|
+
"GATE_EVIDENCE_INVALID: declared output or shell identity failed"
|
|
3741
|
+
].filter(Boolean).join("\n");
|
|
3742
|
+
}
|
|
3743
|
+
const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3744
|
+
const duration = Date.now() - input.startTime;
|
|
3745
|
+
result.duration = duration;
|
|
3746
|
+
const receiptPath = writeLocalGateExecutionReceipt(input.gateArtifactDirectory, {
|
|
3747
|
+
schemaVersion: GATE_EXECUTION_RECEIPT_SCHEMA_VERSION,
|
|
3748
|
+
attempt: input.attempt,
|
|
3749
|
+
declaredGate: {
|
|
3750
|
+
name: input.gate.name,
|
|
3751
|
+
run: input.gate.run,
|
|
3752
|
+
cwd: input.gate.cwd ?? null,
|
|
3753
|
+
runtime: input.gate.runtime,
|
|
3754
|
+
artifacts: [...input.gate.artifacts ?? []]
|
|
3755
|
+
},
|
|
3756
|
+
execution: {
|
|
3757
|
+
cwd: input.workingDirectory,
|
|
3758
|
+
startedAt: input.startedAt,
|
|
3759
|
+
finishedAt,
|
|
3760
|
+
durationMs: duration,
|
|
3761
|
+
shell: {
|
|
3762
|
+
command: input.shell.command,
|
|
3763
|
+
executable: input.shellIdentityBefore,
|
|
3764
|
+
argv: [...input.shell.arguments],
|
|
3765
|
+
identityAfter: shellIdentityAfter,
|
|
3766
|
+
unchanged: shellUnchanged,
|
|
3767
|
+
spawned: input.spawned
|
|
3768
|
+
}
|
|
3769
|
+
},
|
|
3770
|
+
outcome: {
|
|
3771
|
+
status: result.status,
|
|
3772
|
+
exitCode: result.exitCode ?? null,
|
|
3773
|
+
failureKind: result.failureKind ?? null,
|
|
3774
|
+
timeoutCleanup: result.timeoutCleanup ?? null,
|
|
3775
|
+
evidenceComplete
|
|
3776
|
+
},
|
|
3777
|
+
output: {
|
|
3778
|
+
stdout: gateOutputEvidence(input.stdout),
|
|
3779
|
+
stderr: gateOutputEvidence(input.stderr)
|
|
3780
|
+
},
|
|
3781
|
+
artifacts: collected.identities
|
|
3782
|
+
});
|
|
3783
|
+
result.artifacts = [...collected.paths, receiptPath];
|
|
3784
|
+
return result;
|
|
3785
|
+
}
|
|
3458
3786
|
async function executeContainerGate(gate, artifactDir, attempt, startedAt, startTime, timeoutMs, repoRoot) {
|
|
3459
3787
|
console.warn(
|
|
3460
3788
|
`\u26A0\uFE0F Container runtime for gate '${gate.name}' not yet implemented, falling back to local execution`
|
|
@@ -3476,28 +3804,6 @@ async function executeCiServiceGate(gate, artifactDir, attempt, startedAt, start
|
|
|
3476
3804
|
lastAttempt: startedAt
|
|
3477
3805
|
};
|
|
3478
3806
|
}
|
|
3479
|
-
function collectArtifacts(gate, artifactDir) {
|
|
3480
|
-
if (!gate.artifacts || gate.artifacts.length === 0) {
|
|
3481
|
-
return [];
|
|
3482
|
-
}
|
|
3483
|
-
const collected = [];
|
|
3484
|
-
const gateArtifactDir = path9.join(artifactDir, gate.name);
|
|
3485
|
-
if (!fs9.existsSync(gateArtifactDir)) {
|
|
3486
|
-
fs9.mkdirSync(gateArtifactDir, { recursive: true });
|
|
3487
|
-
}
|
|
3488
|
-
for (const artifactPath of gate.artifacts) {
|
|
3489
|
-
try {
|
|
3490
|
-
if (fs9.existsSync(artifactPath)) {
|
|
3491
|
-
const destPath = path9.join(gateArtifactDir, path9.basename(artifactPath));
|
|
3492
|
-
fs9.copyFileSync(artifactPath, destPath);
|
|
3493
|
-
collected.push(destPath);
|
|
3494
|
-
}
|
|
3495
|
-
} catch (error) {
|
|
3496
|
-
console.warn(`Failed to collect artifact ${artifactPath}:`, error);
|
|
3497
|
-
}
|
|
3498
|
-
}
|
|
3499
|
-
return collected;
|
|
3500
|
-
}
|
|
3501
3807
|
function checkVulnGate(artifactDir, policy) {
|
|
3502
3808
|
const startTime = Date.now();
|
|
3503
3809
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -3823,13 +4129,13 @@ async function executeGatesWithPolicy(plan, executionState, artifactDir, timeout
|
|
|
3823
4129
|
if (promises.length > 0) {
|
|
3824
4130
|
await Promise.race(promises);
|
|
3825
4131
|
} else if (executing.size > 0) {
|
|
3826
|
-
await new Promise((
|
|
4132
|
+
await new Promise((resolve6) => setTimeout(resolve6, 50));
|
|
3827
4133
|
} else {
|
|
3828
4134
|
break;
|
|
3829
4135
|
}
|
|
3830
4136
|
}
|
|
3831
4137
|
while (executing.size > 0) {
|
|
3832
|
-
await new Promise((
|
|
4138
|
+
await new Promise((resolve6) => setTimeout(resolve6, 50));
|
|
3833
4139
|
}
|
|
3834
4140
|
}
|
|
3835
4141
|
function buildExecutionOrder(plan) {
|
|
@@ -4642,8 +4948,8 @@ var FileAnalyzer = class {
|
|
|
4642
4948
|
* Extract module name from test file
|
|
4643
4949
|
*/
|
|
4644
4950
|
getTestedModule(testFilename) {
|
|
4645
|
-
const
|
|
4646
|
-
return
|
|
4951
|
+
const basename3 = testFilename.split("/").pop() || "";
|
|
4952
|
+
return basename3.replace(/\.(test|spec)\.(ts|js|tsx|jsx)$/, "");
|
|
4647
4953
|
}
|
|
4648
4954
|
/**
|
|
4649
4955
|
* Perform complete file analysis on a set of PRs
|