@pushpalsdev/cli 1.1.21 → 1.1.23
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/dist/pushpals-cli.js +25 -1
- package/package.json +1 -1
- package/runtime/sandbox/apps/workerpals/src/backends/openai_codex/openai_codex_executor.py +288 -31
- package/runtime/sandbox/apps/workerpals/src/backends/openai_codex/test_openai_codex_runtime_config.py +505 -0
- package/runtime/sandbox/apps/workerpals/src/common/types.ts +69 -0
- package/runtime/sandbox/apps/workerpals/src/docker_executor.ts +75 -16
- package/runtime/sandbox/apps/workerpals/src/execute_job.ts +334 -19
- package/runtime/sandbox/apps/workerpals/src/job_runner.ts +3 -0
- package/runtime/sandbox/apps/workerpals/src/workerpals_main.ts +131 -3
|
@@ -32,7 +32,14 @@ import {
|
|
|
32
32
|
type ToolRequirement,
|
|
33
33
|
} from "shared";
|
|
34
34
|
import { resolveExecutor, type WorkerpalsRuntimeConfig } from "./common/executor_backend.js";
|
|
35
|
-
import type {
|
|
35
|
+
import type {
|
|
36
|
+
JobDiagnostics,
|
|
37
|
+
JobPatchSnapshotDiagnostics,
|
|
38
|
+
JobPublishBlockedInfo,
|
|
39
|
+
JobResult,
|
|
40
|
+
JobTerminalDiagnostics,
|
|
41
|
+
JobValidationRunDiagnostics,
|
|
42
|
+
} from "./common/types.js";
|
|
36
43
|
import {
|
|
37
44
|
compactJobOutput,
|
|
38
45
|
truncate,
|
|
@@ -191,6 +198,8 @@ export interface QualityGatePolicy {
|
|
|
191
198
|
|
|
192
199
|
const BROWSER_VALIDATION_MAX_AUTO_REVISIONS = 3;
|
|
193
200
|
const CRITIC_COMPACT_RETRY_MIN_REDUCTION_RATIO = 0.25;
|
|
201
|
+
const MAX_DIAGNOSTIC_PATH_SAMPLES = 50;
|
|
202
|
+
const MAX_DIAGNOSTIC_TEXT_CHARS = 8_000;
|
|
194
203
|
|
|
195
204
|
export function qualityRevisionLoopUpperBound(policy: {
|
|
196
205
|
maxAutoRevisions: number;
|
|
@@ -233,6 +242,58 @@ export function qualityRevisionBudgetDecision(opts: {
|
|
|
233
242
|
};
|
|
234
243
|
}
|
|
235
244
|
|
|
245
|
+
const MERGE_CONFLICT_RETRY_EXECUTION_BUDGET_MS = 300_000;
|
|
246
|
+
const MERGE_CONFLICT_RETRY_FINALIZATION_BUDGET_MS = 60_000;
|
|
247
|
+
const MERGE_CONFLICT_MIN_RETRY_EXECUTION_BUDGET_MS = 120_000;
|
|
248
|
+
|
|
249
|
+
export function mergeConflictResolverRetryBudgetDecision(opts: {
|
|
250
|
+
jobElapsedMs: number;
|
|
251
|
+
executionBudgetMs: number;
|
|
252
|
+
finalizationBudgetMs: number;
|
|
253
|
+
}): {
|
|
254
|
+
shouldStart: boolean;
|
|
255
|
+
executionBudgetMs: number;
|
|
256
|
+
finalizationBudgetMs: number;
|
|
257
|
+
remainingTotalBudgetMs: number;
|
|
258
|
+
minimumExecutionBudgetMs: number;
|
|
259
|
+
} {
|
|
260
|
+
const configuredExecutionBudgetMs = Number(opts.executionBudgetMs);
|
|
261
|
+
if (!Number.isFinite(configuredExecutionBudgetMs) || configuredExecutionBudgetMs <= 0) {
|
|
262
|
+
return {
|
|
263
|
+
shouldStart: true,
|
|
264
|
+
executionBudgetMs: MERGE_CONFLICT_RETRY_EXECUTION_BUDGET_MS,
|
|
265
|
+
finalizationBudgetMs: MERGE_CONFLICT_RETRY_FINALIZATION_BUDGET_MS,
|
|
266
|
+
remainingTotalBudgetMs: Number.POSITIVE_INFINITY,
|
|
267
|
+
minimumExecutionBudgetMs: MERGE_CONFLICT_MIN_RETRY_EXECUTION_BUDGET_MS,
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const configuredFinalizationBudgetMs = Math.max(0, Number(opts.finalizationBudgetMs) || 0);
|
|
272
|
+
const elapsedMs = Math.max(0, Number(opts.jobElapsedMs) || 0);
|
|
273
|
+
const remainingTotalBudgetMs = Math.max(
|
|
274
|
+
0,
|
|
275
|
+
Math.floor(configuredExecutionBudgetMs + configuredFinalizationBudgetMs - elapsedMs),
|
|
276
|
+
);
|
|
277
|
+
const finalizationBudgetMs = Math.min(
|
|
278
|
+
MERGE_CONFLICT_RETRY_FINALIZATION_BUDGET_MS,
|
|
279
|
+
configuredFinalizationBudgetMs,
|
|
280
|
+
remainingTotalBudgetMs,
|
|
281
|
+
);
|
|
282
|
+
const availableExecutionBudgetMs = Math.max(0, remainingTotalBudgetMs - finalizationBudgetMs);
|
|
283
|
+
const executionBudgetMs = Math.min(
|
|
284
|
+
MERGE_CONFLICT_RETRY_EXECUTION_BUDGET_MS,
|
|
285
|
+
Math.floor(availableExecutionBudgetMs),
|
|
286
|
+
);
|
|
287
|
+
|
|
288
|
+
return {
|
|
289
|
+
shouldStart: executionBudgetMs >= MERGE_CONFLICT_MIN_RETRY_EXECUTION_BUDGET_MS,
|
|
290
|
+
executionBudgetMs: Math.max(10_000, executionBudgetMs),
|
|
291
|
+
finalizationBudgetMs,
|
|
292
|
+
remainingTotalBudgetMs,
|
|
293
|
+
minimumExecutionBudgetMs: MERGE_CONFLICT_MIN_RETRY_EXECUTION_BUDGET_MS,
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
|
|
236
297
|
export function shouldRetryCriticTimeoutWithCompact(opts: {
|
|
237
298
|
timeoutBehavior: string;
|
|
238
299
|
qualityOk: boolean;
|
|
@@ -530,6 +591,162 @@ export function publishableChangedPaths(changedPaths: string[]): string[] {
|
|
|
530
591
|
return changedPaths.filter((path) => !isNonPublishableArtifactPath(path));
|
|
531
592
|
}
|
|
532
593
|
|
|
594
|
+
function compactDiagnosticText(value: unknown, maxChars = MAX_DIAGNOSTIC_TEXT_CHARS): string | null {
|
|
595
|
+
const text = String(value ?? "").replace(/\s+$/g, "");
|
|
596
|
+
if (!text.trim()) return null;
|
|
597
|
+
return text.length <= maxChars ? text : text.slice(Math.max(0, text.length - maxChars));
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
function diagnosticPathSample(paths: string[], limit = MAX_DIAGNOSTIC_PATH_SAMPLES): string[] {
|
|
601
|
+
const out: string[] = [];
|
|
602
|
+
const seen = new Set<string>();
|
|
603
|
+
for (const raw of paths) {
|
|
604
|
+
const path = String(raw ?? "").replace(/\\/g, "/").replace(/^\.\/+/, "").trim();
|
|
605
|
+
if (!path || seen.has(path)) continue;
|
|
606
|
+
seen.add(path);
|
|
607
|
+
out.push(path);
|
|
608
|
+
if (out.length >= limit) break;
|
|
609
|
+
}
|
|
610
|
+
return out;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
function diagnosticTopLevelDirs(paths: string[]): string[] {
|
|
614
|
+
const seen = new Set<string>();
|
|
615
|
+
for (const path of paths) {
|
|
616
|
+
const normalized = String(path ?? "").replace(/\\/g, "/").replace(/^\.\/+/, "").trim();
|
|
617
|
+
if (!normalized) continue;
|
|
618
|
+
const top = normalized.includes("/") ? normalized.split("/", 1)[0] : normalized;
|
|
619
|
+
if (top) seen.add(top);
|
|
620
|
+
if (seen.size >= 20) break;
|
|
621
|
+
}
|
|
622
|
+
return [...seen];
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
function buildPatchSnapshotDiagnostics(
|
|
626
|
+
changedPaths: string[],
|
|
627
|
+
attempt: number,
|
|
628
|
+
phase: string,
|
|
629
|
+
): JobPatchSnapshotDiagnostics {
|
|
630
|
+
const publishable = publishableChangedPaths(changedPaths);
|
|
631
|
+
const artifactOnly = changedPaths.filter((path) => isNonPublishableArtifactPath(path));
|
|
632
|
+
return {
|
|
633
|
+
attempt,
|
|
634
|
+
phase,
|
|
635
|
+
publishableFileCount: publishable.length,
|
|
636
|
+
artifactOnlyPathCount: artifactOnly.length,
|
|
637
|
+
changedPathSample: diagnosticPathSample(changedPaths),
|
|
638
|
+
topLevelDirs: diagnosticTopLevelDirs(publishable.length > 0 ? publishable : changedPaths),
|
|
639
|
+
capturedAt: new Date().toISOString(),
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
function classifyValidationRunFailure(run: ValidationExecutionResult): string | null {
|
|
644
|
+
if (run.ok) return null;
|
|
645
|
+
const combined = `${run.command}\n${run.stdout}\n${run.stderr}`.toLowerCase();
|
|
646
|
+
if (run.exitCode === 124 || combined.includes("timed out") || combined.includes("timeout")) {
|
|
647
|
+
return "timeout";
|
|
648
|
+
}
|
|
649
|
+
if (run.exitCode === 127 || combined.includes("missing tool") || combined.includes("not found")) {
|
|
650
|
+
return "missing_tool";
|
|
651
|
+
}
|
|
652
|
+
if (/browser|playwright|cypress|locator|page\.|screenshot|web:e2e/.test(combined)) {
|
|
653
|
+
return "browser_validation";
|
|
654
|
+
}
|
|
655
|
+
if (/cannot find module|import error|does not provide an export|no exported member|mock/.test(combined)) {
|
|
656
|
+
return "test_harness";
|
|
657
|
+
}
|
|
658
|
+
return "nonzero_exit";
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
function buildValidationRunDiagnostics(
|
|
662
|
+
runs: ValidationExecutionResult[],
|
|
663
|
+
attempt: number,
|
|
664
|
+
): JobValidationRunDiagnostics[] {
|
|
665
|
+
return runs.slice(0, 20).map((run) => ({
|
|
666
|
+
attempt,
|
|
667
|
+
command: run.command,
|
|
668
|
+
exitCode: run.exitCode,
|
|
669
|
+
durationMs: run.elapsedMs,
|
|
670
|
+
passed: run.ok,
|
|
671
|
+
failureClass: classifyValidationRunFailure(run),
|
|
672
|
+
stdoutTail: compactDiagnosticText(run.stdout),
|
|
673
|
+
stderrTail: compactDiagnosticText(run.stderr),
|
|
674
|
+
}));
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
function inferTerminalFailureClass(result: JobResult, changedPaths: string[]): string {
|
|
678
|
+
if (result.ok) return "success";
|
|
679
|
+
const text = `${result.summary ?? ""}\n${result.stderr ?? ""}\n${result.stdout ?? ""}`.toLowerCase();
|
|
680
|
+
const publishableCount = publishableChangedPaths(changedPaths).length;
|
|
681
|
+
if (changedPaths.length > 0 && publishableCount === 0) return "artifact_only_no_publishable_patch";
|
|
682
|
+
if (result.exitCode === 124 || text.includes("timed out") || text.includes("timeout")) return "timeout";
|
|
683
|
+
if (text.includes("validationgate") || text.includes("validation")) return "validation";
|
|
684
|
+
if (text.includes("scopegate") || text.includes("scope")) return "scope";
|
|
685
|
+
if (text.includes("criticgate") || text.includes("critic")) return "critic";
|
|
686
|
+
if (text.includes("publish")) return "publish";
|
|
687
|
+
if (text.includes("shell-wrapper") || text.includes("command-router")) return "command_policy";
|
|
688
|
+
return "executor_failure";
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
function inferTerminalStage(result: JobResult, fallback: string): string {
|
|
692
|
+
const text = `${result.summary ?? ""}\n${result.stderr ?? ""}`.toLowerCase();
|
|
693
|
+
if (text.includes("validationgate") || text.includes("validation")) return "validation";
|
|
694
|
+
if (text.includes("scopegate") || text.includes("scope")) return "scope";
|
|
695
|
+
if (text.includes("criticgate") || text.includes("critic")) return "critic";
|
|
696
|
+
if (text.includes("publish")) return "publish";
|
|
697
|
+
if (text.includes("quality gate")) return "quality";
|
|
698
|
+
if (text.includes("codex") || text.includes("executor")) return "executor";
|
|
699
|
+
return fallback;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
function mergeJobDiagnostics(base: JobDiagnostics | undefined, extra: JobDiagnostics): JobDiagnostics {
|
|
703
|
+
return {
|
|
704
|
+
...(base ?? {}),
|
|
705
|
+
...extra,
|
|
706
|
+
attempts: [...(base?.attempts ?? []), ...(extra.attempts ?? [])],
|
|
707
|
+
phaseSpans: [...(base?.phaseSpans ?? []), ...(extra.phaseSpans ?? [])],
|
|
708
|
+
validationRuns: [...(base?.validationRuns ?? []), ...(extra.validationRuns ?? [])],
|
|
709
|
+
patchSnapshots: [...(base?.patchSnapshots ?? []), ...(extra.patchSnapshots ?? [])],
|
|
710
|
+
terminal: extra.terminal ?? base?.terminal,
|
|
711
|
+
metadata: {
|
|
712
|
+
...(base?.metadata ?? {}),
|
|
713
|
+
...(extra.metadata ?? {}),
|
|
714
|
+
},
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
function withJobDiagnostics(result: JobResult, diagnostics: JobDiagnostics): JobResult {
|
|
719
|
+
return {
|
|
720
|
+
...result,
|
|
721
|
+
diagnostics: mergeJobDiagnostics(result.diagnostics, diagnostics),
|
|
722
|
+
};
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
function buildTerminalDiagnostics(args: {
|
|
726
|
+
result: JobResult;
|
|
727
|
+
executor: string;
|
|
728
|
+
changedPaths: string[];
|
|
729
|
+
terminalStage: string;
|
|
730
|
+
timeoutMs?: number | null;
|
|
731
|
+
metadata?: Record<string, unknown>;
|
|
732
|
+
}): JobTerminalDiagnostics {
|
|
733
|
+
const publishable = publishableChangedPaths(args.changedPaths);
|
|
734
|
+
const artifactOnly = args.changedPaths.filter((path) => isNonPublishableArtifactPath(path));
|
|
735
|
+
const text = `${args.result.summary ?? ""}\n${args.result.stderr ?? ""}\n${args.result.stdout ?? ""}`;
|
|
736
|
+
return {
|
|
737
|
+
failureClass: inferTerminalFailureClass(args.result, args.changedPaths),
|
|
738
|
+
terminalStage: inferTerminalStage(args.result, args.terminalStage),
|
|
739
|
+
executorBackend: args.executor,
|
|
740
|
+
summary: compactDiagnosticText(args.result.summary, 1_000),
|
|
741
|
+
watchdogFired: /watchdog|rollout coach/i.test(text),
|
|
742
|
+
timeoutMs: args.timeoutMs ?? null,
|
|
743
|
+
publishableFileCount: publishable.length,
|
|
744
|
+
artifactOnlyPathCount: artifactOnly.length,
|
|
745
|
+
changedPathSample: diagnosticPathSample(args.changedPaths),
|
|
746
|
+
metadata: args.metadata,
|
|
747
|
+
};
|
|
748
|
+
}
|
|
749
|
+
|
|
533
750
|
function collectPlanningText(planning: TaskExecutePlanning): string {
|
|
534
751
|
return [
|
|
535
752
|
planning.intent,
|
|
@@ -7379,6 +7596,8 @@ export async function executeJob(
|
|
|
7379
7596
|
const jobStartedAt = Date.now();
|
|
7380
7597
|
const previousValidationFailureDigests = new Map<string, string>();
|
|
7381
7598
|
const failureJobFamily = buildTaskFailureJobFamily(normalizedParams);
|
|
7599
|
+
const diagnosticValidationRuns: JobValidationRunDiagnostics[] = [];
|
|
7600
|
+
const diagnosticPatchSnapshots: JobPatchSnapshotDiagnostics[] = [];
|
|
7382
7601
|
while (revisionAttempt <= qualityRevisionLoopMax) {
|
|
7383
7602
|
const attemptStartedAt = Date.now();
|
|
7384
7603
|
const attemptParams: Record<string, unknown> = { ...normalizedParams };
|
|
@@ -7388,7 +7607,7 @@ export async function executeJob(
|
|
|
7388
7607
|
}
|
|
7389
7608
|
|
|
7390
7609
|
const executor = resolveExecutor(runtimeConfig);
|
|
7391
|
-
const
|
|
7610
|
+
const defaultExecuteBudgets = { executionBudgetMs, finalizationBudgetMs };
|
|
7392
7611
|
const runExecutor = getBackendTaskExecutor(executor);
|
|
7393
7612
|
if (!runExecutor) {
|
|
7394
7613
|
return {
|
|
@@ -7400,14 +7619,17 @@ export async function executeJob(
|
|
|
7400
7619
|
let result: Awaited<ReturnType<typeof runExecutor>> | null = null;
|
|
7401
7620
|
let mergeConflictPass = 0;
|
|
7402
7621
|
let executorElapsedMs = 0;
|
|
7622
|
+
let nextMergeConflictExecuteBudgets: typeof defaultExecuteBudgets | null = null;
|
|
7403
7623
|
while (true) {
|
|
7624
|
+
const currentExecuteBudgets = nextMergeConflictExecuteBudgets ?? defaultExecuteBudgets;
|
|
7625
|
+
nextMergeConflictExecuteBudgets = null;
|
|
7404
7626
|
const currentResult = await runExecutor(
|
|
7405
7627
|
kind,
|
|
7406
7628
|
attemptParams,
|
|
7407
7629
|
repo,
|
|
7408
7630
|
runtimeConfig,
|
|
7409
7631
|
onLog,
|
|
7410
|
-
|
|
7632
|
+
currentExecuteBudgets,
|
|
7411
7633
|
);
|
|
7412
7634
|
if (!currentResult.ok) return currentResult;
|
|
7413
7635
|
result = currentResult;
|
|
@@ -7441,19 +7663,42 @@ export async function executeJob(
|
|
|
7441
7663
|
exitCode: 4,
|
|
7442
7664
|
};
|
|
7443
7665
|
}
|
|
7666
|
+
const retryBudget = mergeConflictResolverRetryBudgetDecision({
|
|
7667
|
+
jobElapsedMs: Date.now() - attemptStartedAt,
|
|
7668
|
+
executionBudgetMs,
|
|
7669
|
+
finalizationBudgetMs,
|
|
7670
|
+
});
|
|
7671
|
+
if (!retryBudget.shouldStart) {
|
|
7672
|
+
const detail =
|
|
7673
|
+
"Merge-conflict rebase advanced into another conflicted commit, but remaining job budget " +
|
|
7674
|
+
`is ${retryBudget.remainingTotalBudgetMs}ms (< ${retryBudget.minimumExecutionBudgetMs}ms execution).`;
|
|
7675
|
+
onLog?.("stderr", `[MergeConflict] ${detail}`);
|
|
7676
|
+
return {
|
|
7677
|
+
ok: false,
|
|
7678
|
+
summary: detail,
|
|
7679
|
+
stdout: currentResult.stdout,
|
|
7680
|
+
stderr: [currentResult.stderr ?? "", resume.detail ?? detail].filter(Boolean).join("\n"),
|
|
7681
|
+
exitCode: 4,
|
|
7682
|
+
};
|
|
7683
|
+
}
|
|
7684
|
+
nextMergeConflictExecuteBudgets = {
|
|
7685
|
+
executionBudgetMs: retryBudget.executionBudgetMs,
|
|
7686
|
+
finalizationBudgetMs: retryBudget.finalizationBudgetMs,
|
|
7687
|
+
};
|
|
7444
7688
|
onLog?.(
|
|
7445
7689
|
"stdout",
|
|
7446
7690
|
`[MergeConflict] Rebase surfaced another conflicted commit after auto-continue; rerunning resolver pass ${
|
|
7447
7691
|
mergeConflictPass + 1
|
|
7448
|
-
}.`,
|
|
7692
|
+
} with a capped completion budget (${retryBudget.executionBudgetMs}ms execution).`,
|
|
7449
7693
|
);
|
|
7450
7694
|
continue;
|
|
7451
7695
|
}
|
|
7452
7696
|
if (sequencer === "rebase" && !resume.resumed) {
|
|
7453
7697
|
mergeConflictPass += 1;
|
|
7454
|
-
const budget =
|
|
7698
|
+
const budget = mergeConflictResolverRetryBudgetDecision({
|
|
7455
7699
|
jobElapsedMs: Date.now() - attemptStartedAt,
|
|
7456
7700
|
executionBudgetMs,
|
|
7701
|
+
finalizationBudgetMs,
|
|
7457
7702
|
});
|
|
7458
7703
|
if (mergeConflictPass < MAX_MERGE_CONFLICT_RESOLUTION_PASSES && budget.shouldStart) {
|
|
7459
7704
|
const retryDetail =
|
|
@@ -7470,18 +7715,22 @@ export async function executeJob(
|
|
|
7470
7715
|
]
|
|
7471
7716
|
.filter(Boolean)
|
|
7472
7717
|
.join("\n\n");
|
|
7718
|
+
nextMergeConflictExecuteBudgets = {
|
|
7719
|
+
executionBudgetMs: budget.executionBudgetMs,
|
|
7720
|
+
finalizationBudgetMs: budget.finalizationBudgetMs,
|
|
7721
|
+
};
|
|
7473
7722
|
onLog?.(
|
|
7474
7723
|
"stdout",
|
|
7475
7724
|
`[MergeConflict] ${retryDetail}; rerunning resolver pass ${
|
|
7476
7725
|
mergeConflictPass + 1
|
|
7477
|
-
} with focused rebase-completion guidance.`,
|
|
7726
|
+
} with focused rebase-completion guidance and capped budget (${budget.executionBudgetMs}ms execution).`,
|
|
7478
7727
|
);
|
|
7479
7728
|
continue;
|
|
7480
7729
|
}
|
|
7481
7730
|
if (!budget.shouldStart) {
|
|
7482
7731
|
onLog?.(
|
|
7483
7732
|
"stderr",
|
|
7484
|
-
`[MergeConflict] Not rerunning unfinished rebase resolver: remaining
|
|
7733
|
+
`[MergeConflict] Not rerunning unfinished rebase resolver: remaining total budget is ${budget.remainingTotalBudgetMs}ms (< ${budget.minimumExecutionBudgetMs}ms execution).`,
|
|
7485
7734
|
);
|
|
7486
7735
|
}
|
|
7487
7736
|
}
|
|
@@ -7511,6 +7760,11 @@ export async function executeJob(
|
|
|
7511
7760
|
? parseChangedPathsFromStatus(preQualityStatus.stdout)
|
|
7512
7761
|
: [];
|
|
7513
7762
|
const preQualityPublishablePaths = publishableChangedPaths(preQualityChangedPaths);
|
|
7763
|
+
if (preQualityChangedPaths.length > 0) {
|
|
7764
|
+
diagnosticPatchSnapshots.push(
|
|
7765
|
+
buildPatchSnapshotDiagnostics(preQualityChangedPaths, revisionAttempt, "executor"),
|
|
7766
|
+
);
|
|
7767
|
+
}
|
|
7514
7768
|
const executorText = `${result.summary ?? ""}\n${result.stdout ?? ""}\n${result.stderr ?? ""}`;
|
|
7515
7769
|
const shellWrapperReturn =
|
|
7516
7770
|
/shell-wrapper command rejections|command-router shell-wrapper|command policy rejection/i.test(
|
|
@@ -7524,13 +7778,24 @@ export async function executeJob(
|
|
|
7524
7778
|
"stderr",
|
|
7525
7779
|
`[QualityGate] ${detail} Skipping ValidationGate/CriticGate because there is no PR-worthy patch to validate.`,
|
|
7526
7780
|
);
|
|
7527
|
-
|
|
7781
|
+
const failure: JobResult = {
|
|
7528
7782
|
ok: false,
|
|
7529
7783
|
summary: `Executor produced no publishable code changes (${detail})`,
|
|
7530
7784
|
stdout: result.stdout,
|
|
7531
7785
|
stderr: [result.stderr ?? "", detail].filter(Boolean).join("\n"),
|
|
7532
7786
|
exitCode: 4,
|
|
7533
7787
|
};
|
|
7788
|
+
return withJobDiagnostics(failure, {
|
|
7789
|
+
terminal: buildTerminalDiagnostics({
|
|
7790
|
+
result: failure,
|
|
7791
|
+
executor,
|
|
7792
|
+
changedPaths: preQualityChangedPaths,
|
|
7793
|
+
terminalStage: "executor",
|
|
7794
|
+
timeoutMs: executionBudgetMs,
|
|
7795
|
+
metadata: { revisionAttempt, executorElapsedMs },
|
|
7796
|
+
}),
|
|
7797
|
+
patchSnapshots: [...diagnosticPatchSnapshots],
|
|
7798
|
+
});
|
|
7534
7799
|
}
|
|
7535
7800
|
if (
|
|
7536
7801
|
preQualityPublishablePaths.length === 0 &&
|
|
@@ -7544,13 +7809,24 @@ export async function executeJob(
|
|
|
7544
7809
|
"stderr",
|
|
7545
7810
|
`[QualityGate] ${reason} Skipping ValidationGate/CriticGate and failing fast.`,
|
|
7546
7811
|
);
|
|
7547
|
-
|
|
7812
|
+
const failure: JobResult = {
|
|
7548
7813
|
ok: false,
|
|
7549
7814
|
summary: reason,
|
|
7550
7815
|
stdout: result.stdout,
|
|
7551
7816
|
stderr: [result.stderr ?? "", reason].filter(Boolean).join("\n"),
|
|
7552
7817
|
exitCode: 4,
|
|
7553
7818
|
};
|
|
7819
|
+
return withJobDiagnostics(failure, {
|
|
7820
|
+
terminal: buildTerminalDiagnostics({
|
|
7821
|
+
result: failure,
|
|
7822
|
+
executor,
|
|
7823
|
+
changedPaths: preQualityChangedPaths,
|
|
7824
|
+
terminalStage: "executor",
|
|
7825
|
+
timeoutMs: executionBudgetMs,
|
|
7826
|
+
metadata: { revisionAttempt, executorElapsedMs, shellWrapperReturn },
|
|
7827
|
+
}),
|
|
7828
|
+
patchSnapshots: [...diagnosticPatchSnapshots],
|
|
7829
|
+
});
|
|
7554
7830
|
}
|
|
7555
7831
|
|
|
7556
7832
|
const qualityStartedAt = Date.now();
|
|
@@ -7566,6 +7842,12 @@ export async function executeJob(
|
|
|
7566
7842
|
},
|
|
7567
7843
|
);
|
|
7568
7844
|
const qualityElapsedMs = Date.now() - qualityStartedAt;
|
|
7845
|
+
diagnosticPatchSnapshots.push(
|
|
7846
|
+
buildPatchSnapshotDiagnostics(quality.changedPaths, revisionAttempt, "quality"),
|
|
7847
|
+
);
|
|
7848
|
+
diagnosticValidationRuns.push(
|
|
7849
|
+
...buildValidationRunDiagnostics(quality.validationRuns, revisionAttempt),
|
|
7850
|
+
);
|
|
7569
7851
|
const validationCommandElapsedMs = quality.validationRuns.reduce(
|
|
7570
7852
|
(total, run) => total + Math.max(0, Number(run.elapsedMs) || 0),
|
|
7571
7853
|
0,
|
|
@@ -7626,6 +7908,30 @@ export async function executeJob(
|
|
|
7626
7908
|
: executor === "openai_codex"
|
|
7627
7909
|
? await runCodexCriticReview(repo, attemptParams, qualityForCritic, runtimeConfig, onLog)
|
|
7628
7910
|
: await runTaskCriticReview(repo, attemptParams, qualityForCritic, runtimeConfig, onLog);
|
|
7911
|
+
const annotateTerminalResult = (
|
|
7912
|
+
terminalResult: JobResult,
|
|
7913
|
+
terminalStage: string,
|
|
7914
|
+
changedPaths: string[] = quality.changedPaths,
|
|
7915
|
+
): JobResult =>
|
|
7916
|
+
withJobDiagnostics(terminalResult, {
|
|
7917
|
+
terminal: buildTerminalDiagnostics({
|
|
7918
|
+
result: terminalResult,
|
|
7919
|
+
executor,
|
|
7920
|
+
changedPaths,
|
|
7921
|
+
terminalStage,
|
|
7922
|
+
timeoutMs: executionBudgetMs,
|
|
7923
|
+
metadata: {
|
|
7924
|
+
revisionAttempt,
|
|
7925
|
+
executorElapsedMs,
|
|
7926
|
+
qualityElapsedMs,
|
|
7927
|
+
validationFailureScope: quality.validationFailureScope,
|
|
7928
|
+
validationRuns: quality.validationRuns.length,
|
|
7929
|
+
criticScore: critic?.score ?? null,
|
|
7930
|
+
},
|
|
7931
|
+
}),
|
|
7932
|
+
validationRuns: [...diagnosticValidationRuns],
|
|
7933
|
+
patchSnapshots: [...diagnosticPatchSnapshots],
|
|
7934
|
+
});
|
|
7629
7935
|
if (!qualityGatePolicy.criticGateEnabled) {
|
|
7630
7936
|
onLog?.("stdout", "[CriticGate] Disabled by workerpals.quality_critic_gate_enabled=false.");
|
|
7631
7937
|
} else if (skipCriticAfterExecutorTimeout) {
|
|
@@ -7685,7 +7991,7 @@ export async function executeJob(
|
|
|
7685
7991
|
"stderr",
|
|
7686
7992
|
"[PublishGate] Disabled by workerpals.quality_publish_gate_enabled=false; returning worker result despite gate failures.",
|
|
7687
7993
|
);
|
|
7688
|
-
|
|
7994
|
+
const advisoryResult: JobResult = {
|
|
7689
7995
|
...result,
|
|
7690
7996
|
summary: `${result.summary} (publish gate disabled; quality gate findings were advisory)`,
|
|
7691
7997
|
stderr: truncate(
|
|
@@ -7700,6 +8006,7 @@ export async function executeJob(
|
|
|
7700
8006
|
),
|
|
7701
8007
|
exitCode: typeof result.exitCode === "number" ? result.exitCode : 0,
|
|
7702
8008
|
};
|
|
8009
|
+
return annotateTerminalResult(advisoryResult, "quality");
|
|
7703
8010
|
}
|
|
7704
8011
|
|
|
7705
8012
|
if (!deterministicRequiresRevision && !criticRequiresRevision) {
|
|
@@ -7718,13 +8025,14 @@ export async function executeJob(
|
|
|
7718
8025
|
outputPolicyForRuntime(runtimeConfig),
|
|
7719
8026
|
);
|
|
7720
8027
|
onLog?.("stderr", `[QualityGate] ${requiredSummary}`);
|
|
7721
|
-
|
|
8028
|
+
const failure: JobResult = {
|
|
7722
8029
|
ok: false,
|
|
7723
8030
|
summary: requiredSummary,
|
|
7724
8031
|
stdout: result.stdout,
|
|
7725
8032
|
stderr: diagnostics,
|
|
7726
8033
|
exitCode: 4,
|
|
7727
8034
|
};
|
|
8035
|
+
return annotateTerminalResult(failure, "validation");
|
|
7728
8036
|
}
|
|
7729
8037
|
if (critic) {
|
|
7730
8038
|
onLog?.(
|
|
@@ -7732,7 +8040,7 @@ export async function executeJob(
|
|
|
7732
8040
|
`[CriticGate] review score ${critic.score.toFixed(1)}/10 (threshold ${qualityCriticMinScore}).`,
|
|
7733
8041
|
);
|
|
7734
8042
|
}
|
|
7735
|
-
return result;
|
|
8043
|
+
return annotateTerminalResult(result, "completed");
|
|
7736
8044
|
}
|
|
7737
8045
|
|
|
7738
8046
|
const blockerIssue = quality.blocker
|
|
@@ -7792,13 +8100,14 @@ export async function executeJob(
|
|
|
7792
8100
|
} else if (quality.requiredValidationFailures.length > 0) {
|
|
7793
8101
|
const requiredSummary = `Required vision.md validation blocked publishing: ${quality.requiredValidationFailures.join("; ")}`;
|
|
7794
8102
|
onLog?.("stderr", `[QualityGate] ${requiredSummary}`);
|
|
7795
|
-
|
|
8103
|
+
const failure: JobResult = {
|
|
7796
8104
|
ok: false,
|
|
7797
8105
|
summary: requiredSummary,
|
|
7798
8106
|
stdout: result.stdout,
|
|
7799
8107
|
stderr: blockerDiagnostics,
|
|
7800
8108
|
exitCode: 4,
|
|
7801
8109
|
};
|
|
8110
|
+
return annotateTerminalResult(failure, "validation");
|
|
7802
8111
|
} else if (shouldSoftPassValidationBlocker(qualityGatePolicy, quality.blocker)) {
|
|
7803
8112
|
onLog?.(
|
|
7804
8113
|
"stderr",
|
|
@@ -7807,7 +8116,7 @@ export async function executeJob(
|
|
|
7807
8116
|
260,
|
|
7808
8117
|
)}`,
|
|
7809
8118
|
);
|
|
7810
|
-
|
|
8119
|
+
const softPass: JobResult = {
|
|
7811
8120
|
...result,
|
|
7812
8121
|
summary:
|
|
7813
8122
|
`${result.summary} ` +
|
|
@@ -7815,15 +8124,17 @@ export async function executeJob(
|
|
|
7815
8124
|
stderr: blockerDiagnostics,
|
|
7816
8125
|
exitCode: typeof result.exitCode === "number" ? result.exitCode : 0,
|
|
7817
8126
|
};
|
|
8127
|
+
return annotateTerminalResult(softPass, "quality");
|
|
7818
8128
|
} else {
|
|
7819
8129
|
onLog?.("stderr", `[QualityGate] ${blockerSummary}`);
|
|
7820
|
-
|
|
8130
|
+
const failure: JobResult = {
|
|
7821
8131
|
ok: false,
|
|
7822
8132
|
summary: blockerSummary,
|
|
7823
8133
|
stdout: result.stdout,
|
|
7824
8134
|
stderr: blockerDiagnostics,
|
|
7825
8135
|
exitCode: 4,
|
|
7826
8136
|
};
|
|
8137
|
+
return annotateTerminalResult(failure, "quality");
|
|
7827
8138
|
}
|
|
7828
8139
|
}
|
|
7829
8140
|
if (revisionAttempt >= activeMaxAutoRevisions) {
|
|
@@ -7840,13 +8151,14 @@ export async function executeJob(
|
|
|
7840
8151
|
);
|
|
7841
8152
|
const requiredSummary = `Required vision.md validation failed after ${revisionAttempt} auto-revision attempt(s): ${quality.requiredValidationFailures.join("; ")}`;
|
|
7842
8153
|
onLog?.("stderr", `[QualityGate] ${requiredSummary}`);
|
|
7843
|
-
|
|
8154
|
+
const failure: JobResult = {
|
|
7844
8155
|
ok: false,
|
|
7845
8156
|
summary: requiredSummary,
|
|
7846
8157
|
stdout: result.stdout,
|
|
7847
8158
|
stderr: diagnostics,
|
|
7848
8159
|
exitCode: 4,
|
|
7849
8160
|
};
|
|
8161
|
+
return annotateTerminalResult(failure, "validation");
|
|
7850
8162
|
}
|
|
7851
8163
|
if (qualitySoftPassOnExhausted) {
|
|
7852
8164
|
const diagnostics = truncate(
|
|
@@ -7862,14 +8174,15 @@ export async function executeJob(
|
|
|
7862
8174
|
260,
|
|
7863
8175
|
)}`,
|
|
7864
8176
|
);
|
|
7865
|
-
|
|
8177
|
+
const softPass: JobResult = {
|
|
7866
8178
|
...result,
|
|
7867
8179
|
summary: `${result.summary} (quality gate soft-pass after ${revisionAttempt} auto-revision attempt(s))`,
|
|
7868
8180
|
stderr: diagnostics,
|
|
7869
8181
|
exitCode: typeof result.exitCode === "number" ? result.exitCode : 0,
|
|
7870
8182
|
};
|
|
8183
|
+
return annotateTerminalResult(softPass, "quality");
|
|
7871
8184
|
}
|
|
7872
|
-
|
|
8185
|
+
const failure: JobResult = {
|
|
7873
8186
|
ok: false,
|
|
7874
8187
|
summary: `Quality gate failed after ${revisionAttempt} auto-revision attempt(s): ${toSingleLine(
|
|
7875
8188
|
issueSummary,
|
|
@@ -7884,6 +8197,7 @@ export async function executeJob(
|
|
|
7884
8197
|
),
|
|
7885
8198
|
exitCode: 4,
|
|
7886
8199
|
};
|
|
8200
|
+
return annotateTerminalResult(failure, "quality");
|
|
7887
8201
|
}
|
|
7888
8202
|
|
|
7889
8203
|
const revisionBudget = qualityRevisionBudgetDecision({
|
|
@@ -7900,7 +8214,7 @@ export async function executeJob(
|
|
|
7900
8214
|
220,
|
|
7901
8215
|
)}`;
|
|
7902
8216
|
onLog?.("stderr", `[QualityGate] ${budgetSummary}`);
|
|
7903
|
-
|
|
8217
|
+
const failure: JobResult = {
|
|
7904
8218
|
ok: false,
|
|
7905
8219
|
summary: budgetSummary,
|
|
7906
8220
|
stdout: result.stdout,
|
|
@@ -7916,6 +8230,7 @@ export async function executeJob(
|
|
|
7916
8230
|
),
|
|
7917
8231
|
exitCode: 4,
|
|
7918
8232
|
};
|
|
8233
|
+
return annotateTerminalResult(failure, "quality");
|
|
7919
8234
|
}
|
|
7920
8235
|
|
|
7921
8236
|
revisionAttempt += 1;
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
import { executeJob, shouldCommit, createJobCommit } from "./execute_job.js";
|
|
20
20
|
import { loadPushPalsConfig } from "shared";
|
|
21
21
|
import { writeFileSync } from "fs";
|
|
22
|
+
import type { JobDiagnostics } from "./common/types.js";
|
|
22
23
|
import {
|
|
23
24
|
applyMergeConflictExecutionHints,
|
|
24
25
|
isMergeConflictResolutionParams,
|
|
@@ -55,6 +56,7 @@ interface JobResult {
|
|
|
55
56
|
sha: string;
|
|
56
57
|
stage: "sync" | "push";
|
|
57
58
|
};
|
|
59
|
+
diagnostics?: JobDiagnostics;
|
|
58
60
|
}
|
|
59
61
|
|
|
60
62
|
// ─── Logging helpers ────────────────────────────────────────────────────────
|
|
@@ -183,6 +185,7 @@ async function main(): Promise<void> {
|
|
|
183
185
|
stdout: result.stdout,
|
|
184
186
|
stderr: result.stderr,
|
|
185
187
|
exitCode: result.exitCode,
|
|
188
|
+
diagnostics: result.diagnostics,
|
|
186
189
|
};
|
|
187
190
|
// Create commit for file-modifying jobs
|
|
188
191
|
if (result.ok && shouldCommit(spec.kind, CONFIG)) {
|