@pushpalsdev/cli 1.0.53 → 1.0.55
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/package.json
CHANGED
|
@@ -64,6 +64,11 @@ interface ValidationExecutionResult {
|
|
|
64
64
|
elapsedMs: number;
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
interface ValidationBlocker {
|
|
68
|
+
category: "repo" | "environment";
|
|
69
|
+
detail: string;
|
|
70
|
+
}
|
|
71
|
+
|
|
67
72
|
interface DeterministicQualityResult {
|
|
68
73
|
ok: boolean;
|
|
69
74
|
skipped: boolean;
|
|
@@ -71,6 +76,7 @@ interface DeterministicQualityResult {
|
|
|
71
76
|
changedPaths: string[];
|
|
72
77
|
changedTestPaths: string[];
|
|
73
78
|
validationRuns: ValidationExecutionResult[];
|
|
79
|
+
blocker: ValidationBlocker | null;
|
|
74
80
|
}
|
|
75
81
|
|
|
76
82
|
interface CriticReview {
|
|
@@ -198,6 +204,30 @@ export function buildQualityGateRevisionIssues(
|
|
|
198
204
|
return [...new Set(merged)];
|
|
199
205
|
}
|
|
200
206
|
|
|
207
|
+
const TEST_ASSERTION_BALANCE_ISSUE =
|
|
208
|
+
"Changed test files do not show both positive and negative assertion coverage (expected both).";
|
|
209
|
+
|
|
210
|
+
export function relaxAdvisoryQualityIssues(
|
|
211
|
+
qualityIssues: string[],
|
|
212
|
+
validationRuns: Array<{ ok: boolean }>,
|
|
213
|
+
critic: CriticReview | null,
|
|
214
|
+
qualityCriticMinScore: number,
|
|
215
|
+
): string[] {
|
|
216
|
+
const normalizedQualityIssues = qualityIssues
|
|
217
|
+
.map((entry) => String(entry ?? "").trim())
|
|
218
|
+
.filter(Boolean);
|
|
219
|
+
if (normalizedQualityIssues.length === 0) return [];
|
|
220
|
+
|
|
221
|
+
const hasPassingValidation = validationRuns.some((run) => Boolean(run?.ok));
|
|
222
|
+
const criticPasses = !critic || critic.score >= qualityCriticMinScore;
|
|
223
|
+
if (!hasPassingValidation || !criticPasses) {
|
|
224
|
+
return normalizedQualityIssues;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const relaxed = normalizedQualityIssues.filter((issue) => issue !== TEST_ASSERTION_BALANCE_ISSUE);
|
|
228
|
+
return relaxed;
|
|
229
|
+
}
|
|
230
|
+
|
|
201
231
|
export function resolveReviewFixCompletionBranch(
|
|
202
232
|
value: unknown,
|
|
203
233
|
fallbackBranch: string,
|
|
@@ -323,7 +353,7 @@ export function deriveQualityGatePolicy(
|
|
|
323
353
|
return {
|
|
324
354
|
mode: "merge_conflict",
|
|
325
355
|
maxAutoRevisions: baseMaxAutoRevisions,
|
|
326
|
-
softPassOnExhausted:
|
|
356
|
+
softPassOnExhausted: baseSoftPassOnExhausted,
|
|
327
357
|
criticMinScore: baseCriticMinScore,
|
|
328
358
|
};
|
|
329
359
|
}
|
|
@@ -341,7 +371,7 @@ export function deriveQualityGatePolicy(
|
|
|
341
371
|
return {
|
|
342
372
|
mode: "review_fix",
|
|
343
373
|
maxAutoRevisions: Math.max(baseMaxAutoRevisions, 2),
|
|
344
|
-
softPassOnExhausted:
|
|
374
|
+
softPassOnExhausted: baseSoftPassOnExhausted,
|
|
345
375
|
criticMinScore: tightenedCriticMinScore,
|
|
346
376
|
};
|
|
347
377
|
}
|
|
@@ -533,6 +563,60 @@ async function runValidationCommand(
|
|
|
533
563
|
};
|
|
534
564
|
}
|
|
535
565
|
|
|
566
|
+
function extractPreparedMergeConflictPaths(params: Record<string, unknown>): string[] {
|
|
567
|
+
const reviewAgent =
|
|
568
|
+
params.reviewAgent && typeof params.reviewAgent === "object" && !Array.isArray(params.reviewAgent)
|
|
569
|
+
? (params.reviewAgent as Record<string, unknown>)
|
|
570
|
+
: null;
|
|
571
|
+
const preparedPaths = Array.isArray(reviewAgent?.preparedConflictPaths)
|
|
572
|
+
? reviewAgent.preparedConflictPaths
|
|
573
|
+
: [];
|
|
574
|
+
return preparedPaths
|
|
575
|
+
.map((entry) => String(entry ?? "").trim().replace(/\\/g, "/"))
|
|
576
|
+
.filter(Boolean);
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
function detectValidationBlocker(runs: ValidationExecutionResult[]): ValidationBlocker | null {
|
|
580
|
+
const combined = runs
|
|
581
|
+
.flatMap((run) => [run.stdout, run.stderr])
|
|
582
|
+
.filter(Boolean)
|
|
583
|
+
.join("\n")
|
|
584
|
+
.toLowerCase();
|
|
585
|
+
if (!combined) return null;
|
|
586
|
+
|
|
587
|
+
if (
|
|
588
|
+
combined.includes("cannot find module") ||
|
|
589
|
+
combined.includes("module not found") ||
|
|
590
|
+
combined.includes("failed to resolve import") ||
|
|
591
|
+
combined.includes("could not resolve") ||
|
|
592
|
+
combined.includes("no such file or directory") ||
|
|
593
|
+
combined.includes("package not found")
|
|
594
|
+
) {
|
|
595
|
+
return {
|
|
596
|
+
category: "repo",
|
|
597
|
+
detail:
|
|
598
|
+
"Validation is blocked by missing repo dependencies or imported files. Fix the repository test/runtime setup before retrying this job.",
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
if (
|
|
603
|
+
combined.includes("read-only file system") ||
|
|
604
|
+
combined.includes("permission denied") ||
|
|
605
|
+
combined.includes("network access") ||
|
|
606
|
+
combined.includes("connection refused") ||
|
|
607
|
+
combined.includes("getaddrinfo") ||
|
|
608
|
+
combined.includes("eacces")
|
|
609
|
+
) {
|
|
610
|
+
return {
|
|
611
|
+
category: "environment",
|
|
612
|
+
detail:
|
|
613
|
+
"Validation is blocked by sandbox environment restrictions (filesystem, permissions, or network). Retry only after the worker environment is fixed.",
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
return null;
|
|
618
|
+
}
|
|
619
|
+
|
|
536
620
|
function stripAnsiControlSequences(value: string): string {
|
|
537
621
|
return value.replace(/\u001b\[[0-9;?]*[ -/]*[@-~]/g, "");
|
|
538
622
|
}
|
|
@@ -722,7 +806,7 @@ function isTestFocusedTask(
|
|
|
722
806
|
|
|
723
807
|
function hasBalancedPositiveNegativeAssertions(paths: string[], repo: string): boolean {
|
|
724
808
|
const negativeSignal =
|
|
725
|
-
|
|
809
|
+
/(\.not\b|\b(invalid|negative|error|throw|reject|null|undefined|non[- ]?existent|toThrow|toBeNull|toBeUndefined|without|missing|absent|unchanged|same|remains?|stays?|prevent|avoid|zero|none)\b|<\s*0|<=\s*0)/i;
|
|
726
810
|
let positiveAssertions = 0;
|
|
727
811
|
let negativeAssertions = 0;
|
|
728
812
|
|
|
@@ -762,12 +846,18 @@ async function runDeterministicQualityGate(
|
|
|
762
846
|
changedPaths: [],
|
|
763
847
|
changedTestPaths: [],
|
|
764
848
|
validationRuns: [],
|
|
849
|
+
blocker: null,
|
|
765
850
|
};
|
|
766
851
|
}
|
|
767
852
|
|
|
768
853
|
const statusResult = await git(repo, ["status", "--porcelain"]);
|
|
769
854
|
const changedPaths = statusResult.ok ? parseChangedPathsFromStatus(statusResult.stdout) : [];
|
|
770
|
-
const
|
|
855
|
+
const preparedMergeConflictPaths = extractPreparedMergeConflictPaths(params);
|
|
856
|
+
const changedTestPaths = Array.from(
|
|
857
|
+
new Set(
|
|
858
|
+
[...changedPaths, ...preparedMergeConflictPaths].filter((path) => isLikelyTestPath(path)),
|
|
859
|
+
),
|
|
860
|
+
);
|
|
771
861
|
const issues: string[] = [];
|
|
772
862
|
if (changedTestPaths.length === 0) {
|
|
773
863
|
issues.push("No relevant test file was modified for this test-focused task.");
|
|
@@ -848,14 +938,16 @@ async function runDeterministicQualityGate(
|
|
|
848
938
|
issues.push("Validation steps did not execute a recognizable test command.");
|
|
849
939
|
}
|
|
850
940
|
}
|
|
941
|
+
const blocker = detectValidationBlocker(validationRuns);
|
|
851
942
|
|
|
852
943
|
return {
|
|
853
|
-
ok: issues.length === 0,
|
|
944
|
+
ok: issues.length === 0 && blocker === null,
|
|
854
945
|
skipped: false,
|
|
855
946
|
issues,
|
|
856
947
|
changedPaths,
|
|
857
948
|
changedTestPaths,
|
|
858
949
|
validationRuns,
|
|
950
|
+
blocker,
|
|
859
951
|
};
|
|
860
952
|
}
|
|
861
953
|
|
|
@@ -3491,12 +3583,12 @@ export async function executeJob(
|
|
|
3491
3583
|
: qualityCriticMinScore.toFixed(1);
|
|
3492
3584
|
onLog?.(
|
|
3493
3585
|
"stdout",
|
|
3494
|
-
`[QualityGate] review_fix
|
|
3586
|
+
`[QualityGate] review_fix policy active: prior_score=${priorScore}, target_threshold=${threshold}, soft_pass_on_exhausted=${qualitySoftPassOnExhausted ? "true" : "false"}; repo/environment blockers still fail hard.`,
|
|
3495
3587
|
);
|
|
3496
3588
|
} else if (qualityGatePolicy.mode === "merge_conflict") {
|
|
3497
3589
|
onLog?.(
|
|
3498
3590
|
"stdout",
|
|
3499
|
-
|
|
3591
|
+
`[QualityGate] merge_conflict policy active: soft_pass_on_exhausted=${qualitySoftPassOnExhausted ? "true" : "false"}; unfinished rebases and repo/environment blockers still fail hard.`,
|
|
3500
3592
|
);
|
|
3501
3593
|
}
|
|
3502
3594
|
|
|
@@ -3567,7 +3659,20 @@ export async function executeJob(
|
|
|
3567
3659
|
: executor === "openai_codex"
|
|
3568
3660
|
? await runCodexCriticReview(repo, attemptParams, quality, runtimeConfig, onLog)
|
|
3569
3661
|
: await runTaskCriticReview(repo, attemptParams, quality, runtimeConfig, onLog);
|
|
3570
|
-
const
|
|
3662
|
+
const effectiveQualityIssues = relaxAdvisoryQualityIssues(
|
|
3663
|
+
quality.issues,
|
|
3664
|
+
quality.validationRuns,
|
|
3665
|
+
critic,
|
|
3666
|
+
qualityCriticMinScore,
|
|
3667
|
+
);
|
|
3668
|
+
if (effectiveQualityIssues.length !== quality.issues.length) {
|
|
3669
|
+
onLog?.(
|
|
3670
|
+
"stdout",
|
|
3671
|
+
"[QualityGate] Assertion-balance heuristic downgraded to advisory because validation passed and critic score met threshold.",
|
|
3672
|
+
);
|
|
3673
|
+
}
|
|
3674
|
+
const deterministicRequiresRevision =
|
|
3675
|
+
effectiveQualityIssues.length > 0 || quality.blocker !== null;
|
|
3571
3676
|
const criticRequiresRevision = Boolean(critic && critic.score < qualityCriticMinScore);
|
|
3572
3677
|
|
|
3573
3678
|
if (!deterministicRequiresRevision && !criticRequiresRevision) {
|
|
@@ -3580,8 +3685,29 @@ export async function executeJob(
|
|
|
3580
3685
|
return result;
|
|
3581
3686
|
}
|
|
3582
3687
|
|
|
3583
|
-
const issues = buildQualityGateRevisionIssues(
|
|
3688
|
+
const issues = buildQualityGateRevisionIssues(
|
|
3689
|
+
effectiveQualityIssues,
|
|
3690
|
+
critic,
|
|
3691
|
+
qualityCriticMinScore,
|
|
3692
|
+
);
|
|
3584
3693
|
const issueSummary = issues.map((entry) => toSingleLine(entry, 180)).join(" | ");
|
|
3694
|
+
if (quality.blocker) {
|
|
3695
|
+
const blockerSummary = `Quality gate blocked by ${quality.blocker.category} issue: ${quality.blocker.detail}`;
|
|
3696
|
+
onLog?.("stderr", `[QualityGate] ${blockerSummary}`);
|
|
3697
|
+
return {
|
|
3698
|
+
ok: false,
|
|
3699
|
+
summary: blockerSummary,
|
|
3700
|
+
stdout: result.stdout,
|
|
3701
|
+
stderr: truncate(
|
|
3702
|
+
[
|
|
3703
|
+
result.stderr ?? "",
|
|
3704
|
+
...quality.validationRuns.flatMap((run) => [run.stdout, run.stderr]).filter(Boolean),
|
|
3705
|
+
].join("\n"),
|
|
3706
|
+
outputPolicyForRuntime(runtimeConfig),
|
|
3707
|
+
),
|
|
3708
|
+
exitCode: 4,
|
|
3709
|
+
};
|
|
3710
|
+
}
|
|
3585
3711
|
if (revisionAttempt >= qualityMaxAutoRevisions) {
|
|
3586
3712
|
if (qualitySoftPassOnExhausted) {
|
|
3587
3713
|
const diagnostics = truncate(
|