@bilalimamoglu/sift 0.3.3 → 0.4.0
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/README.md +74 -36
- package/dist/cli.js +699 -167
- package/dist/index.d.ts +5 -1
- package/dist/index.js +629 -141
- package/package.json +3 -1
package/dist/index.js
CHANGED
|
@@ -61,7 +61,125 @@ function evaluateGate(args) {
|
|
|
61
61
|
|
|
62
62
|
// src/core/testStatusDecision.ts
|
|
63
63
|
import { z } from "zod";
|
|
64
|
-
|
|
64
|
+
|
|
65
|
+
// src/core/testStatusTargets.ts
|
|
66
|
+
function unique(values) {
|
|
67
|
+
return [...new Set(values)];
|
|
68
|
+
}
|
|
69
|
+
function normalizeTestId(value) {
|
|
70
|
+
return value.replace(/\\/g, "/").replace(/\s+/g, " ").trim();
|
|
71
|
+
}
|
|
72
|
+
function stripMatcherProse(value) {
|
|
73
|
+
return value.replace(/\s+-\s+.*$/, "").trim();
|
|
74
|
+
}
|
|
75
|
+
function extractJsFile(value) {
|
|
76
|
+
const match = value.match(/([A-Za-z0-9_./-]+\.(?:test|spec)\.[cm]?[jt]sx?)/i);
|
|
77
|
+
return match ? normalizeTestId(match[1]) : null;
|
|
78
|
+
}
|
|
79
|
+
function normalizeFailingTarget(label, runner) {
|
|
80
|
+
const normalized = normalizeTestId(label).replace(/^['"]|['"]$/g, "");
|
|
81
|
+
if (runner === "pytest") {
|
|
82
|
+
return stripMatcherProse(normalized);
|
|
83
|
+
}
|
|
84
|
+
if (runner === "vitest" || runner === "jest") {
|
|
85
|
+
const compact = normalized.replace(/^FAIL\s+/i, "").replace(/^[❯×]\s*/, "").replace(/\s+\[[^\]]+\]\s*$/, "").trim();
|
|
86
|
+
const file = extractJsFile(compact);
|
|
87
|
+
if (!file) {
|
|
88
|
+
return stripMatcherProse(compact);
|
|
89
|
+
}
|
|
90
|
+
const fileIndex = compact.indexOf(file);
|
|
91
|
+
const suffix = compact.slice(fileIndex + file.length).trim();
|
|
92
|
+
if (!suffix) {
|
|
93
|
+
return file;
|
|
94
|
+
}
|
|
95
|
+
if (suffix.startsWith(">")) {
|
|
96
|
+
const testName = stripMatcherProse(suffix.replace(/^>\s*/, ""));
|
|
97
|
+
return testName.length > 0 ? `${file} > ${testName}` : file;
|
|
98
|
+
}
|
|
99
|
+
return file;
|
|
100
|
+
}
|
|
101
|
+
return normalized;
|
|
102
|
+
}
|
|
103
|
+
function extractFamilyPrefix(value) {
|
|
104
|
+
const normalized = normalizeTestId(value);
|
|
105
|
+
const filePart = normalized.split("::")[0]?.split(" > ")[0]?.trim() ?? normalized;
|
|
106
|
+
const workflowMatch = filePart.match(/^(\.github\/workflows\/)/);
|
|
107
|
+
if (workflowMatch) {
|
|
108
|
+
return workflowMatch[1];
|
|
109
|
+
}
|
|
110
|
+
const testsMatch = filePart.match(/^((?:test|tests)\/[^/]+\/)/);
|
|
111
|
+
if (testsMatch) {
|
|
112
|
+
return testsMatch[1];
|
|
113
|
+
}
|
|
114
|
+
const srcMatch = filePart.match(/^(src\/[^/]+\/)/);
|
|
115
|
+
if (srcMatch) {
|
|
116
|
+
return srcMatch[1];
|
|
117
|
+
}
|
|
118
|
+
const configMatch = filePart.match(
|
|
119
|
+
/^((?:[^/]+\/)*(?:package\.json|pytest\.ini|pyproject\.toml|tox\.ini|conftest\.py|(?:vitest|jest)\.config\.[^/]+|tsconfig(?:\.[^/]+)?\.json|[^/]*config[^/]*\.(?:json|ya?ml)))$/i
|
|
120
|
+
);
|
|
121
|
+
if (configMatch) {
|
|
122
|
+
return configMatch[1];
|
|
123
|
+
}
|
|
124
|
+
const segments = filePart.replace(/^\/+/, "").split("/").filter(Boolean);
|
|
125
|
+
if (segments.length >= 2) {
|
|
126
|
+
return `${segments[0]}/${segments[1]}/`;
|
|
127
|
+
}
|
|
128
|
+
if (segments.length === 1) {
|
|
129
|
+
return segments[0];
|
|
130
|
+
}
|
|
131
|
+
return "other";
|
|
132
|
+
}
|
|
133
|
+
function buildTestTargetSummary(values) {
|
|
134
|
+
const uniqueValues = unique(values);
|
|
135
|
+
const counts = /* @__PURE__ */ new Map();
|
|
136
|
+
for (const value of uniqueValues) {
|
|
137
|
+
const prefix = extractFamilyPrefix(value);
|
|
138
|
+
counts.set(prefix, (counts.get(prefix) ?? 0) + 1);
|
|
139
|
+
}
|
|
140
|
+
const families = [...counts.entries()].map(([prefix, count]) => ({
|
|
141
|
+
prefix,
|
|
142
|
+
count
|
|
143
|
+
})).sort((left, right) => {
|
|
144
|
+
if (right.count !== left.count) {
|
|
145
|
+
return right.count - left.count;
|
|
146
|
+
}
|
|
147
|
+
return left.prefix.localeCompare(right.prefix);
|
|
148
|
+
}).slice(0, 5);
|
|
149
|
+
return {
|
|
150
|
+
count: uniqueValues.length,
|
|
151
|
+
families
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
function formatTargetSummary(summary) {
|
|
155
|
+
if (summary.count === 0) {
|
|
156
|
+
return "count=0";
|
|
157
|
+
}
|
|
158
|
+
const families = summary.families.length > 0 ? summary.families.map((family) => `${family.prefix}${family.count}`).join(", ") : "none";
|
|
159
|
+
return `count=${summary.count}; families=${families}`;
|
|
160
|
+
}
|
|
161
|
+
function joinFamilies(families) {
|
|
162
|
+
if (families.length === 0) {
|
|
163
|
+
return "";
|
|
164
|
+
}
|
|
165
|
+
if (families.length === 1) {
|
|
166
|
+
return families[0];
|
|
167
|
+
}
|
|
168
|
+
if (families.length === 2) {
|
|
169
|
+
return `${families[0]} and ${families[1]}`;
|
|
170
|
+
}
|
|
171
|
+
return `${families.slice(0, -1).join(", ")}, and ${families.at(-1)}`;
|
|
172
|
+
}
|
|
173
|
+
function describeTargetSummary(summary) {
|
|
174
|
+
if (summary.count === 0 || summary.families.length === 0) {
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
const families = summary.families.map((family) => `${family.prefix} (${family.count})`);
|
|
178
|
+
return `across ${joinFamilies(families)}`;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// src/core/testStatusDecision.ts
|
|
182
|
+
var TEST_STATUS_DIAGNOSE_JSON_CONTRACT = '{"status":"ok|insufficient","diagnosis_complete":boolean,"raw_needed":boolean,"additional_source_read_likely_low_value":boolean,"read_raw_only_if":string|null,"decision":"stop|zoom|read_source|read_raw","remaining_mode":"none|subset_rerun|full_rerun_diff","primary_suspect_kind":"test|app_code|config|environment|tooling|unknown","confidence_reason":string,"dominant_blocker_bucket_index":number|null,"provider_used":boolean,"provider_confidence":number|null,"provider_failed":boolean,"raw_slice_used":boolean,"raw_slice_strategy":"none|bucket_evidence|traceback_window|head_tail","resolved_summary":{"count":number,"families":[{"prefix":string,"count":number}]},"remaining_summary":{"count":number,"families":[{"prefix":string,"count":number}]},"remaining_subset_available":boolean,"main_buckets":[{"bucket_index":number,"label":string,"count":number,"root_cause":string,"suspect_kind":"test|app_code|config|environment|tooling|unknown","fix_hint":string,"evidence":string[],"bucket_confidence":number,"root_cause_confidence":number,"dominant":boolean,"secondary_visible_despite_blocker":boolean,"mini_diff":{"added_paths"?:number,"removed_models"?:number,"changed_task_mappings"?:number}|null}],"read_targets":[{"file":string,"line":number|null,"why":string,"bucket_index":number,"context_hint":{"start_line":number|null,"end_line":number|null,"search_hint":string|null}}],"next_best_action":{"code":"fix_dominant_blocker|read_source_for_bucket|read_raw_for_exact_traceback|insufficient_signal","bucket_index":number|null,"note":string},"resolved_tests"?:string[],"remaining_tests"?:string[]}';
|
|
65
183
|
var TEST_STATUS_PROVIDER_SUPPLEMENT_JSON_CONTRACT = '{"diagnosis_complete":boolean,"raw_needed":boolean,"additional_source_read_likely_low_value":boolean,"read_raw_only_if":string|null,"decision":"stop|zoom|read_source|read_raw","provider_confidence":number|null,"bucket_supplements":[{"label":string,"count":number,"root_cause":string,"anchor":{"file":string|null,"line":number|null,"search_hint":string|null},"fix_hint":string|null,"confidence":number}],"next_best_action":{"code":"fix_dominant_blocker|read_source_for_bucket|read_raw_for_exact_traceback|insufficient_signal","bucket_index":number|null,"note":string}}';
|
|
66
184
|
var nextBestActionSchema = z.object({
|
|
67
185
|
code: z.enum([
|
|
@@ -103,6 +221,7 @@ var testStatusDiagnoseContractSchema = z.object({
|
|
|
103
221
|
additional_source_read_likely_low_value: z.boolean(),
|
|
104
222
|
read_raw_only_if: z.string().nullable(),
|
|
105
223
|
decision: z.enum(["stop", "zoom", "read_source", "read_raw"]),
|
|
224
|
+
remaining_mode: z.enum(["none", "subset_rerun", "full_rerun_diff"]),
|
|
106
225
|
primary_suspect_kind: z.enum([
|
|
107
226
|
"test",
|
|
108
227
|
"app_code",
|
|
@@ -436,54 +555,127 @@ function extractReasonDetail(reason, prefix) {
|
|
|
436
555
|
function formatCount(count, singular, plural = `${singular}s`) {
|
|
437
556
|
return `${count} ${count === 1 ? singular : plural}`;
|
|
438
557
|
}
|
|
439
|
-
function
|
|
558
|
+
function unique2(values) {
|
|
440
559
|
return [...new Set(values)];
|
|
441
560
|
}
|
|
442
|
-
function
|
|
561
|
+
function normalizeTestId2(value) {
|
|
443
562
|
return value.replace(/\\/g, "/").trim();
|
|
444
563
|
}
|
|
445
|
-
function
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
if (testsMatch) {
|
|
449
|
-
return testsMatch[1];
|
|
564
|
+
function normalizePathCandidate(value) {
|
|
565
|
+
if (!value) {
|
|
566
|
+
return null;
|
|
450
567
|
}
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
568
|
+
let normalized = value.replace(/\\/g, "/").trim();
|
|
569
|
+
normalized = normalized.replace(/^[("'`<\[]+/, "").replace(/[>"'`\]),:;]+$/, "");
|
|
570
|
+
normalized = normalized.replace(/^<repo>\//, "").replace(/^\.\//, "");
|
|
571
|
+
if (normalized.includes("::")) {
|
|
572
|
+
normalized = normalized.split("::")[0]?.trim() ?? normalized;
|
|
454
573
|
}
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
574
|
+
if (normalized.startsWith("/") && !normalized.startsWith("/tmp/") && !normalized.startsWith("/var/tmp/")) {
|
|
575
|
+
return null;
|
|
576
|
+
}
|
|
577
|
+
if (/^\.github\/workflows\/.+\.(?:yml|yaml)$/i.test(normalized)) {
|
|
578
|
+
return normalized;
|
|
579
|
+
}
|
|
580
|
+
if (/^(?:src|test|tests)\/.+\.[A-Za-z0-9._-]+$/i.test(normalized)) {
|
|
581
|
+
return normalized;
|
|
582
|
+
}
|
|
583
|
+
if (/^(?:package\.json|pytest\.ini|pyproject\.toml|tox\.ini|(?:[A-Za-z0-9._/-]+\/)?conftest\.py)$/i.test(
|
|
584
|
+
normalized
|
|
585
|
+
)) {
|
|
586
|
+
return normalized;
|
|
587
|
+
}
|
|
588
|
+
if (/^(?:[A-Za-z0-9._/-]+\/)?(?:vitest|jest)\.config\.[A-Za-z0-9._-]+$/i.test(normalized)) {
|
|
589
|
+
return normalized;
|
|
590
|
+
}
|
|
591
|
+
if (/^(?:[A-Za-z0-9._/-]+\/)?tsconfig(?:\.[A-Za-z0-9_-]+)?\.json$/i.test(normalized)) {
|
|
592
|
+
return normalized;
|
|
593
|
+
}
|
|
594
|
+
if (/^[A-Za-z0-9._/-]*config[A-Za-z0-9._/-]*\.(?:json|yml|yaml)$/i.test(normalized)) {
|
|
595
|
+
return normalized;
|
|
458
596
|
}
|
|
459
|
-
return
|
|
597
|
+
return null;
|
|
460
598
|
}
|
|
461
|
-
function
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
const prefix = extractTestFamilyPrefix(value);
|
|
465
|
-
counts.set(prefix, (counts.get(prefix) ?? 0) + 1);
|
|
599
|
+
function addPathCandidatesFromText(target, text) {
|
|
600
|
+
if (!text) {
|
|
601
|
+
return;
|
|
466
602
|
}
|
|
467
|
-
const
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
603
|
+
const pattern = /(?:^|[\s("'`])((?:\.github\/workflows\/[A-Za-z0-9._/-]+\.(?:yml|yaml)|(?:src|test|tests)\/[A-Za-z0-9._/-]+\.[A-Za-z0-9._-]+|package\.json|pytest\.ini|pyproject\.toml|tox\.ini|(?:[A-Za-z0-9._/-]+\/)?conftest\.py|(?:[A-Za-z0-9._/-]+\/)?(?:vitest|jest)\.config\.[A-Za-z0-9._-]+|(?:[A-Za-z0-9._/-]+\/)?tsconfig(?:\.[A-Za-z0-9_-]+)?\.json|[A-Za-z0-9._/-]*config[A-Za-z0-9._/-]*\.(?:json|yml|yaml)))/g;
|
|
604
|
+
for (const match of text.matchAll(pattern)) {
|
|
605
|
+
const normalized = normalizePathCandidate(match[1] ?? null);
|
|
606
|
+
if (normalized) {
|
|
607
|
+
target.add(normalized);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
function extractBucketPathCandidates(args) {
|
|
612
|
+
const candidates = /* @__PURE__ */ new Set();
|
|
613
|
+
const push = (value) => {
|
|
614
|
+
const normalized = normalizePathCandidate(value);
|
|
615
|
+
if (normalized) {
|
|
616
|
+
candidates.add(normalized);
|
|
473
617
|
}
|
|
474
|
-
return left.prefix.localeCompare(right.prefix);
|
|
475
|
-
}).slice(0, 5);
|
|
476
|
-
return {
|
|
477
|
-
count: values.length,
|
|
478
|
-
families
|
|
479
618
|
};
|
|
619
|
+
push(args.readTarget?.file);
|
|
620
|
+
for (const item of args.bucket.representativeItems) {
|
|
621
|
+
push(item.file);
|
|
622
|
+
addPathCandidatesFromText(candidates, item.label);
|
|
623
|
+
addPathCandidatesFromText(candidates, item.reason);
|
|
624
|
+
}
|
|
625
|
+
addPathCandidatesFromText(candidates, args.bucket.reason);
|
|
626
|
+
addPathCandidatesFromText(candidates, args.bucket.headline);
|
|
627
|
+
for (const line of args.bucket.summaryLines) {
|
|
628
|
+
addPathCandidatesFromText(candidates, line);
|
|
629
|
+
}
|
|
630
|
+
return [...candidates];
|
|
480
631
|
}
|
|
481
|
-
function
|
|
482
|
-
|
|
483
|
-
|
|
632
|
+
function isConfigPathCandidate(path4) {
|
|
633
|
+
return /^\.github\/workflows\/.+\.(?:yml|yaml)$/i.test(path4) || /^(?:package\.json|pytest\.ini|pyproject\.toml|tox\.ini|(?:[A-Za-z0-9._/-]+\/)?conftest\.py)$/i.test(
|
|
634
|
+
path4
|
|
635
|
+
) || /^(?:[A-Za-z0-9._/-]+\/)?(?:vitest|jest)\.config\.[A-Za-z0-9._-]+$/i.test(path4) || /^(?:[A-Za-z0-9._/-]+\/)?tsconfig(?:\.[A-Za-z0-9_-]+)?\.json$/i.test(path4) || /^[A-Za-z0-9._/-]*config[A-Za-z0-9._/-]*\.(?:json|yml|yaml)$/i.test(path4);
|
|
636
|
+
}
|
|
637
|
+
function isAppPathCandidate(path4) {
|
|
638
|
+
return path4.startsWith("src/");
|
|
639
|
+
}
|
|
640
|
+
function isTestPathCandidate(path4) {
|
|
641
|
+
return path4.startsWith("test/") || path4.startsWith("tests/");
|
|
642
|
+
}
|
|
643
|
+
function looksLikeMatcherLiteralComparison(detail) {
|
|
644
|
+
return /\bexpected\b[\s\S]*\bto (?:be|contain)\b/i.test(detail);
|
|
645
|
+
}
|
|
646
|
+
function looksLikeGoldenLiteralDrift(detail) {
|
|
647
|
+
return /\\n/.test(detail) || /-\s+(?:Tests|Decision|Likely owner|Next|Stop signal)\b/.test(detail) || /\b(?:node-version|workflow_dispatch|run-name|matrix|registry-url)\b/i.test(detail);
|
|
648
|
+
}
|
|
649
|
+
function isGoldenOutputDriftBucket(bucket) {
|
|
650
|
+
if (bucket.type !== "assertion_failure") {
|
|
651
|
+
return false;
|
|
484
652
|
}
|
|
485
|
-
const
|
|
486
|
-
|
|
653
|
+
const detail = extractReasonDetail(bucket.reason, "assertion failed:") ?? bucket.reason;
|
|
654
|
+
if (!looksLikeMatcherLiteralComparison(detail)) {
|
|
655
|
+
return false;
|
|
656
|
+
}
|
|
657
|
+
if (bucket.reason.startsWith("snapshot mismatch:")) {
|
|
658
|
+
return false;
|
|
659
|
+
}
|
|
660
|
+
if (!looksLikeGoldenLiteralDrift(detail)) {
|
|
661
|
+
return false;
|
|
662
|
+
}
|
|
663
|
+
const candidates = extractBucketPathCandidates({
|
|
664
|
+
bucket
|
|
665
|
+
});
|
|
666
|
+
return candidates.some((candidate) => isConfigPathCandidate(candidate) || isTestPathCandidate(candidate));
|
|
667
|
+
}
|
|
668
|
+
function specializeBucket(bucket) {
|
|
669
|
+
if (!isGoldenOutputDriftBucket(bucket)) {
|
|
670
|
+
return bucket;
|
|
671
|
+
}
|
|
672
|
+
return {
|
|
673
|
+
...bucket,
|
|
674
|
+
type: "golden_output_drift",
|
|
675
|
+
reason: "golden output drift: expected literal or golden output no longer matches current output",
|
|
676
|
+
labelOverride: "golden output drift",
|
|
677
|
+
hint: "Update the expected literal or golden output if the new output is intentional; otherwise fix the generated output and rerun."
|
|
678
|
+
};
|
|
487
679
|
}
|
|
488
680
|
function classifyGenericBucketType(reason) {
|
|
489
681
|
const extended = findExtendedBucketSpec(reason);
|
|
@@ -508,6 +700,9 @@ function classifyGenericBucketType(reason) {
|
|
|
508
700
|
if (reason.startsWith("missing module:")) {
|
|
509
701
|
return "import_dependency_failure";
|
|
510
702
|
}
|
|
703
|
+
if (reason.startsWith("golden output drift:")) {
|
|
704
|
+
return "golden_output_drift";
|
|
705
|
+
}
|
|
511
706
|
if (reason.startsWith("assertion failed:")) {
|
|
512
707
|
return "assertion_failure";
|
|
513
708
|
}
|
|
@@ -660,7 +855,7 @@ function mergeBucketDetails(existing, incoming) {
|
|
|
660
855
|
count,
|
|
661
856
|
confidence: Math.max(existing.confidence, incoming.confidence),
|
|
662
857
|
representativeItems,
|
|
663
|
-
entities:
|
|
858
|
+
entities: unique2([...existing.entities, ...incoming.entities]),
|
|
664
859
|
hint: existing.hint ?? incoming.hint,
|
|
665
860
|
overflowCount: Math.max(
|
|
666
861
|
existing.overflowCount,
|
|
@@ -852,6 +1047,9 @@ function labelForBucket(bucket) {
|
|
|
852
1047
|
if (bucket.type === "import_dependency_failure") {
|
|
853
1048
|
return "import dependency failure";
|
|
854
1049
|
}
|
|
1050
|
+
if (bucket.type === "golden_output_drift") {
|
|
1051
|
+
return "golden output drift";
|
|
1052
|
+
}
|
|
855
1053
|
if (bucket.type === "assertion_failure") {
|
|
856
1054
|
return "assertion failure";
|
|
857
1055
|
}
|
|
@@ -886,6 +1084,9 @@ function rootCauseConfidenceFor(bucket) {
|
|
|
886
1084
|
if (bucket.type === "contract_snapshot_drift") {
|
|
887
1085
|
return bucket.entities.length > 0 ? 0.92 : 0.76;
|
|
888
1086
|
}
|
|
1087
|
+
if (bucket.type === "golden_output_drift") {
|
|
1088
|
+
return 0.78;
|
|
1089
|
+
}
|
|
889
1090
|
if (bucket.source === "provider") {
|
|
890
1091
|
return Math.max(0.6, Math.min(bucket.confidence, 0.82));
|
|
891
1092
|
}
|
|
@@ -960,6 +1161,9 @@ function buildReadTargetWhy(args) {
|
|
|
960
1161
|
if (args.bucket.type === "import_dependency_failure") {
|
|
961
1162
|
return "it is the first visible failing module in this missing dependency bucket";
|
|
962
1163
|
}
|
|
1164
|
+
if (args.bucket.type === "golden_output_drift") {
|
|
1165
|
+
return "it is the first visible golden or literal drift anchor for this bucket";
|
|
1166
|
+
}
|
|
963
1167
|
if (args.bucket.type === "assertion_failure") {
|
|
964
1168
|
return "it is the first visible failing test in this bucket";
|
|
965
1169
|
}
|
|
@@ -1037,6 +1241,9 @@ function buildReadTargetSearchHint(bucket, anchor) {
|
|
|
1037
1241
|
if (assertionText) {
|
|
1038
1242
|
return assertionText;
|
|
1039
1243
|
}
|
|
1244
|
+
if (bucket.type === "golden_output_drift") {
|
|
1245
|
+
return bucket.representativeItems.map((item) => item.reason.match(/^assertion failed:\s+(.+)$/)?.[1] ?? item.reason).find(Boolean) ?? anchor.label.split("::")[1]?.trim() ?? null;
|
|
1246
|
+
}
|
|
1040
1247
|
if (bucket.reason.startsWith("unknown ")) {
|
|
1041
1248
|
return anchor.reason;
|
|
1042
1249
|
}
|
|
@@ -1091,18 +1298,36 @@ function buildConcreteNextNote(args) {
|
|
|
1091
1298
|
}
|
|
1092
1299
|
const lead = primaryTarget.context_hint.start_line !== null && primaryTarget.context_hint.end_line !== null ? `Read ${primaryTarget.file} lines ${primaryTarget.context_hint.start_line}-${primaryTarget.context_hint.end_line} first; ${primaryTarget.why}.` : primaryTarget.context_hint.search_hint ? `Search for ${primaryTarget.context_hint.search_hint} in ${primaryTarget.file} first; ${primaryTarget.why}.` : `Read ${formatReadTargetLocation(primaryTarget)} first; ${primaryTarget.why}.`;
|
|
1093
1300
|
if (args.nextBestAction.code === "fix_dominant_blocker") {
|
|
1301
|
+
if (args.remainingMode === "subset_rerun") {
|
|
1302
|
+
return "Fix the remaining bucket first, then refresh the full-suite truth with sift rerun.";
|
|
1303
|
+
}
|
|
1304
|
+
if (args.remainingMode === "full_rerun_diff") {
|
|
1305
|
+
return "Fix the remaining bucket first. The cached full-suite baseline is still preserved; use sift rerun when you want to refresh it.";
|
|
1306
|
+
}
|
|
1094
1307
|
if (args.nextBestAction.bucket_index === 1 && args.hasSecondaryVisibleBucket) {
|
|
1095
1308
|
return "Fix bucket 1 first, then rerun the full suite at standard. Secondary buckets are already visible behind it.";
|
|
1096
1309
|
}
|
|
1097
1310
|
return `Fix bucket ${args.nextBestAction.bucket_index ?? 1} first, then rerun the full suite at standard.`;
|
|
1098
1311
|
}
|
|
1099
1312
|
if (args.nextBestAction.code === "read_source_for_bucket") {
|
|
1313
|
+
if (args.remainingMode === "subset_rerun") {
|
|
1314
|
+
return "Fix the remaining bucket first, then refresh the full-suite truth with sift rerun.";
|
|
1315
|
+
}
|
|
1316
|
+
if (args.remainingMode === "full_rerun_diff") {
|
|
1317
|
+
return "Fix the remaining bucket first. The cached full-suite baseline is still preserved; use sift rerun when you want to refresh it.";
|
|
1318
|
+
}
|
|
1100
1319
|
return lead;
|
|
1101
1320
|
}
|
|
1102
1321
|
if (args.nextBestAction.code === "insufficient_signal") {
|
|
1103
|
-
if (args.nextBestAction.note.startsWith("Provider follow-up
|
|
1322
|
+
if (args.nextBestAction.note.startsWith("Provider follow-up")) {
|
|
1104
1323
|
return args.nextBestAction.note;
|
|
1105
1324
|
}
|
|
1325
|
+
if (args.remainingMode === "subset_rerun") {
|
|
1326
|
+
return "Fix the remaining bucket first, then refresh the full-suite truth with sift rerun.";
|
|
1327
|
+
}
|
|
1328
|
+
if (args.remainingMode === "full_rerun_diff") {
|
|
1329
|
+
return "Fix the remaining bucket first. The cached full-suite baseline is still preserved; use sift rerun when you want to refresh it.";
|
|
1330
|
+
}
|
|
1106
1331
|
return `${lead} Then take one deeper sift pass before raw traceback.`;
|
|
1107
1332
|
}
|
|
1108
1333
|
return args.nextBestAction.note;
|
|
@@ -1111,13 +1336,13 @@ function extractMiniDiff(input, bucket) {
|
|
|
1111
1336
|
if (bucket.type !== "contract_snapshot_drift") {
|
|
1112
1337
|
return null;
|
|
1113
1338
|
}
|
|
1114
|
-
const addedPaths =
|
|
1339
|
+
const addedPaths = unique2(
|
|
1115
1340
|
[...input.matchAll(/[+-]\s+'(\/api\/[^']+)'/g)].map((match) => match[1])
|
|
1116
1341
|
).length;
|
|
1117
|
-
const removedModels =
|
|
1342
|
+
const removedModels = unique2(
|
|
1118
1343
|
[...input.matchAll(/[+-]\s+'([A-Za-z0-9._/-]+-[A-Za-z0-9._-]+)'/g)].map((match) => match[1])
|
|
1119
1344
|
).length;
|
|
1120
|
-
const changedTaskMappings =
|
|
1345
|
+
const changedTaskMappings = unique2(
|
|
1121
1346
|
[...input.matchAll(/[+-]\s+'([a-z]+(?:_[a-z0-9]+)+)'/g)].map((match) => match[1])
|
|
1122
1347
|
).length;
|
|
1123
1348
|
if (addedPaths === 0 && removedModels === 0 && changedTaskMappings === 0) {
|
|
@@ -1218,7 +1443,7 @@ function pickUnknownAnchor(args) {
|
|
|
1218
1443
|
}
|
|
1219
1444
|
const label = args.kind === "error" ? args.analysis.visibleErrorLabels[0] : args.analysis.visibleFailedLabels[0];
|
|
1220
1445
|
if (label) {
|
|
1221
|
-
const normalizedLabel =
|
|
1446
|
+
const normalizedLabel = normalizeTestId2(label);
|
|
1222
1447
|
const fileMatch = normalizedLabel.match(/^([A-Za-z0-9_./-]+\.[A-Za-z0-9]+)\b/);
|
|
1223
1448
|
const file = fileMatch?.[1] ?? normalizedLabel.split("::")[0] ?? null;
|
|
1224
1449
|
return {
|
|
@@ -1346,16 +1571,29 @@ function buildDecisionLine(contract) {
|
|
|
1346
1571
|
}
|
|
1347
1572
|
return "- Decision: raw only if exact traceback is required.";
|
|
1348
1573
|
}
|
|
1574
|
+
function buildRemainingPassLine(contract) {
|
|
1575
|
+
if (contract.remaining_mode === "subset_rerun") {
|
|
1576
|
+
return "- Remaining pass: showing only what is still failing from the cached baseline.";
|
|
1577
|
+
}
|
|
1578
|
+
if (contract.remaining_mode === "full_rerun_diff") {
|
|
1579
|
+
return "- Remaining pass: full rerun analyzed against the cached baseline because narrowed rerun is not available for this runner.";
|
|
1580
|
+
}
|
|
1581
|
+
return null;
|
|
1582
|
+
}
|
|
1349
1583
|
function buildComparisonLines(contract) {
|
|
1350
1584
|
const lines = [];
|
|
1585
|
+
const resolvedSummary = buildTestTargetSummary(contract.resolved_tests);
|
|
1586
|
+
const remainingSummary = buildTestTargetSummary(contract.remaining_tests);
|
|
1351
1587
|
if (contract.resolved_tests.length > 0) {
|
|
1588
|
+
const summaryText = describeTargetSummary(resolvedSummary);
|
|
1352
1589
|
lines.push(
|
|
1353
|
-
`- Resolved in this rerun: ${formatCount(contract.resolved_tests.length, "test")} dropped out of the failing set.`
|
|
1590
|
+
`- Resolved in this rerun: ${formatCount(contract.resolved_tests.length, "test")} dropped out of the failing set${summaryText ? ` ${summaryText}` : ""}.`
|
|
1354
1591
|
);
|
|
1355
1592
|
}
|
|
1356
|
-
if (contract.
|
|
1593
|
+
if (contract.remaining_tests.length > 0 && (contract.resolved_tests.length > 0 || contract.remaining_mode !== "none")) {
|
|
1594
|
+
const summaryText = describeTargetSummary(remainingSummary);
|
|
1357
1595
|
lines.push(
|
|
1358
|
-
`- Remaining failing targets: ${formatCount(contract.remaining_tests.length, "test/module", "tests/modules")}.`
|
|
1596
|
+
`- Remaining failing targets: ${formatCount(contract.remaining_tests.length, "test/module", "tests/modules")}${summaryText ? ` ${summaryText}` : ""}.`
|
|
1359
1597
|
);
|
|
1360
1598
|
}
|
|
1361
1599
|
return lines;
|
|
@@ -1427,6 +1665,13 @@ function resolveBucketFixHint(args) {
|
|
|
1427
1665
|
return "Inspect the first visible anchor for this bucket, apply the smallest fix that explains it, then rerun the full suite at standard.";
|
|
1428
1666
|
}
|
|
1429
1667
|
function deriveBucketSuspectKind(args) {
|
|
1668
|
+
const pathCandidates = extractBucketPathCandidates({
|
|
1669
|
+
bucket: args.bucket,
|
|
1670
|
+
readTarget: args.readTarget
|
|
1671
|
+
});
|
|
1672
|
+
const hasConfigCandidate = pathCandidates.some((candidate) => isConfigPathCandidate(candidate));
|
|
1673
|
+
const hasAppCandidate = pathCandidates.some((candidate) => isAppPathCandidate(candidate));
|
|
1674
|
+
const hasTestCandidate = pathCandidates.some((candidate) => isTestPathCandidate(candidate));
|
|
1430
1675
|
if (args.bucket.type === "shared_environment_blocker" || args.bucket.type === "fixture_guard_failure" || args.bucket.type === "permission_denied_failure" || args.bucket.type === "django_db_access_denied" || args.bucket.type === "network_failure" || args.bucket.type === "service_unavailable" || args.bucket.type === "db_connection_failure" || args.bucket.type === "auth_bypass_absent" || args.bucket.type === "fixture_teardown_failure") {
|
|
1431
1676
|
return "environment";
|
|
1432
1677
|
}
|
|
@@ -1436,6 +1681,18 @@ function deriveBucketSuspectKind(args) {
|
|
|
1436
1681
|
if (args.bucket.type === "contract_snapshot_drift" || args.bucket.type === "snapshot_mismatch" || args.bucket.type === "flaky_test_detected" || args.bucket.type === "xfail_strict_unexpected_pass") {
|
|
1437
1682
|
return "test";
|
|
1438
1683
|
}
|
|
1684
|
+
if (args.bucket.type === "golden_output_drift") {
|
|
1685
|
+
if (hasConfigCandidate) {
|
|
1686
|
+
return "config";
|
|
1687
|
+
}
|
|
1688
|
+
if (hasAppCandidate) {
|
|
1689
|
+
return "app_code";
|
|
1690
|
+
}
|
|
1691
|
+
if (hasTestCandidate) {
|
|
1692
|
+
return "test";
|
|
1693
|
+
}
|
|
1694
|
+
return "unknown";
|
|
1695
|
+
}
|
|
1439
1696
|
if (args.bucket.type === "xdist_worker_crash" || args.bucket.type === "timeout_failure" || args.bucket.type === "async_event_loop_failure" || args.bucket.type === "subprocess_crash_segfault" || args.bucket.type === "memory_error" || args.bucket.type === "resource_leak_warning" || args.bucket.type === "interrupted_run") {
|
|
1440
1697
|
return "tooling";
|
|
1441
1698
|
}
|
|
@@ -1443,11 +1700,13 @@ function deriveBucketSuspectKind(args) {
|
|
|
1443
1700
|
return "unknown";
|
|
1444
1701
|
}
|
|
1445
1702
|
if (args.bucket.type === "assertion_failure" || args.bucket.type === "runtime_failure" || args.bucket.type === "type_error_failure" || args.bucket.type === "serialization_encoding_failure") {
|
|
1446
|
-
|
|
1447
|
-
|
|
1703
|
+
if (hasConfigCandidate) {
|
|
1704
|
+
return "config";
|
|
1705
|
+
}
|
|
1706
|
+
if (hasAppCandidate) {
|
|
1448
1707
|
return "app_code";
|
|
1449
1708
|
}
|
|
1450
|
-
if (
|
|
1709
|
+
if (hasTestCandidate) {
|
|
1451
1710
|
return "test";
|
|
1452
1711
|
}
|
|
1453
1712
|
return "unknown";
|
|
@@ -1500,6 +1759,10 @@ function buildStandardBucketSupport(args) {
|
|
|
1500
1759
|
}
|
|
1501
1760
|
function renderStandard(args) {
|
|
1502
1761
|
const lines = [...buildOutcomeLines(args.analysis), ...buildComparisonLines(args.contract)];
|
|
1762
|
+
const remainingPassLine = buildRemainingPassLine(args.contract);
|
|
1763
|
+
if (remainingPassLine) {
|
|
1764
|
+
lines.push(remainingPassLine);
|
|
1765
|
+
}
|
|
1503
1766
|
if (args.contract.main_buckets.length > 0) {
|
|
1504
1767
|
for (const bucket of args.contract.main_buckets.slice(0, 3)) {
|
|
1505
1768
|
const rawBucket = args.buckets[bucket.bucket_index - 1];
|
|
@@ -1527,13 +1790,19 @@ function renderStandard(args) {
|
|
|
1527
1790
|
}
|
|
1528
1791
|
}
|
|
1529
1792
|
lines.push(buildDecisionLine(args.contract));
|
|
1530
|
-
|
|
1793
|
+
if (args.contract.main_buckets.length > 0 && args.contract.primary_suspect_kind !== "unknown") {
|
|
1794
|
+
lines.push(`- Likely owner: ${formatSuspectKindLabel(args.contract.primary_suspect_kind)}`);
|
|
1795
|
+
}
|
|
1531
1796
|
lines.push(`- Next: ${args.contract.next_best_action.note}`);
|
|
1532
1797
|
lines.push(buildStopSignal(args.contract));
|
|
1533
1798
|
return lines.join("\n");
|
|
1534
1799
|
}
|
|
1535
1800
|
function renderFocused(args) {
|
|
1536
1801
|
const lines = [...buildOutcomeLines(args.analysis), ...buildComparisonLines(args.contract)];
|
|
1802
|
+
const remainingPassLine = buildRemainingPassLine(args.contract);
|
|
1803
|
+
if (remainingPassLine) {
|
|
1804
|
+
lines.push(remainingPassLine);
|
|
1805
|
+
}
|
|
1537
1806
|
for (const bucket of args.contract.main_buckets) {
|
|
1538
1807
|
const rawBucket = args.buckets[bucket.bucket_index - 1];
|
|
1539
1808
|
lines.push(
|
|
@@ -1553,6 +1822,10 @@ function renderFocused(args) {
|
|
|
1553
1822
|
}
|
|
1554
1823
|
function renderVerbose(args) {
|
|
1555
1824
|
const lines = [...buildOutcomeLines(args.analysis), ...buildComparisonLines(args.contract)];
|
|
1825
|
+
const remainingPassLine = buildRemainingPassLine(args.contract);
|
|
1826
|
+
if (remainingPassLine) {
|
|
1827
|
+
lines.push(remainingPassLine);
|
|
1828
|
+
}
|
|
1556
1829
|
for (const bucket of args.contract.main_buckets) {
|
|
1557
1830
|
const rawBucket = args.buckets[bucket.bucket_index - 1];
|
|
1558
1831
|
lines.push(
|
|
@@ -1602,7 +1875,9 @@ function buildTestStatusDiagnoseContract(args) {
|
|
|
1602
1875
|
count: residuals.remainingFailed
|
|
1603
1876
|
})
|
|
1604
1877
|
].filter((bucket) => Boolean(bucket));
|
|
1605
|
-
const buckets = prioritizeBuckets(
|
|
1878
|
+
const buckets = prioritizeBuckets(
|
|
1879
|
+
[...combinedBuckets, ...unknownBuckets].map((bucket) => specializeBucket(bucket))
|
|
1880
|
+
).slice(0, 3);
|
|
1606
1881
|
const simpleCollectionFailure = args.analysis.collectionErrorCount !== void 0 && args.analysis.collectionItems.length === 0 && buckets.length === 0;
|
|
1607
1882
|
const dominantBucket = buckets.map((bucket, index) => ({
|
|
1608
1883
|
bucket,
|
|
@@ -1650,9 +1925,9 @@ function buildTestStatusDiagnoseContract(args) {
|
|
|
1650
1925
|
mini_diff: extractMiniDiff(args.input, bucket)
|
|
1651
1926
|
};
|
|
1652
1927
|
});
|
|
1653
|
-
const resolvedTests =
|
|
1654
|
-
const remainingTests =
|
|
1655
|
-
args.remainingTests ??
|
|
1928
|
+
const resolvedTests = unique2(args.resolvedTests ?? []);
|
|
1929
|
+
const remainingTests = unique2(
|
|
1930
|
+
args.remainingTests ?? unique2([...args.analysis.visibleErrorLabels, ...args.analysis.visibleFailedLabels])
|
|
1656
1931
|
);
|
|
1657
1932
|
const primarySuspectKind = derivePrimarySuspectKind({
|
|
1658
1933
|
mainBuckets,
|
|
@@ -1702,6 +1977,7 @@ function buildTestStatusDiagnoseContract(args) {
|
|
|
1702
1977
|
raw_needed: rawNeeded,
|
|
1703
1978
|
additional_source_read_likely_low_value: diagnosisComplete && !rawNeeded,
|
|
1704
1979
|
read_raw_only_if: rawNeeded ? "you still need exact traceback lines after focused or verbose detail" : null,
|
|
1980
|
+
remaining_mode: args.remainingMode ?? "none",
|
|
1705
1981
|
dominant_blocker_bucket_index: dominantBlockerBucketIndex,
|
|
1706
1982
|
primary_suspect_kind: primarySuspectKind,
|
|
1707
1983
|
confidence_reason: "Unknown or low-confidence buckets remain; one deeper sift pass is justified.",
|
|
@@ -1732,7 +2008,8 @@ function buildTestStatusDiagnoseContract(args) {
|
|
|
1732
2008
|
readTargets,
|
|
1733
2009
|
hasSecondaryVisibleBucket: mainBuckets.some(
|
|
1734
2010
|
(bucket) => bucket.secondary_visible_despite_blocker
|
|
1735
|
-
)
|
|
2011
|
+
),
|
|
2012
|
+
remainingMode: args.contractOverrides?.remaining_mode ?? baseContract.remaining_mode
|
|
1736
2013
|
})
|
|
1737
2014
|
}
|
|
1738
2015
|
};
|
|
@@ -1797,6 +2074,7 @@ function buildTestStatusAnalysisContext(args) {
|
|
|
1797
2074
|
`- diagnosis_complete=${args.contract.diagnosis_complete}`,
|
|
1798
2075
|
`- raw_needed=${args.contract.raw_needed}`,
|
|
1799
2076
|
`- decision=${args.contract.decision}`,
|
|
2077
|
+
`- remaining_mode=${args.contract.remaining_mode}`,
|
|
1800
2078
|
`- provider_used=${args.contract.provider_used}`,
|
|
1801
2079
|
`- provider_failed=${args.contract.provider_failed}`,
|
|
1802
2080
|
`- raw_slice_strategy=${args.contract.raw_slice_strategy}`,
|
|
@@ -2031,7 +2309,7 @@ function detectTestRunner(input) {
|
|
|
2031
2309
|
if (/^\s*Test Suites:\s+\d+\s+failed,\s+\d+\s+passed(?:,\s+\d+\s+total)?/m.test(input) || /^\s*Tests:\s+\d+\s+failed,\s+\d+\s+passed(?:,\s+\d+\s+total)?/m.test(input)) {
|
|
2032
2310
|
return "jest";
|
|
2033
2311
|
}
|
|
2034
|
-
if (/\bpytest\b/i.test(input) || /^\s*=+.*\b\d+\s+failed\b.*=+\s*$/m.test(input) || /\bcollected\s+\d+\s+items\b/i.test(input)) {
|
|
2312
|
+
if (/\bpytest\b/i.test(input) || /^\s*(?:FAILED|ERROR)\s+[A-Za-z0-9_./-]+::[^\n]+$/m.test(input) || /^\s*=+.*\b\d+\s+failed\b.*=+\s*$/m.test(input) || /\bcollected\s+\d+\s+items\b/i.test(input)) {
|
|
2035
2313
|
return "pytest";
|
|
2036
2314
|
}
|
|
2037
2315
|
return "unknown";
|
|
@@ -3236,6 +3514,9 @@ function classifyBucketTypeFromReason(reason) {
|
|
|
3236
3514
|
if (reason.startsWith("missing module:")) {
|
|
3237
3515
|
return "import_dependency_failure";
|
|
3238
3516
|
}
|
|
3517
|
+
if (reason.startsWith("golden output drift:")) {
|
|
3518
|
+
return "golden_output_drift";
|
|
3519
|
+
}
|
|
3239
3520
|
if (reason.startsWith("assertion failed:")) {
|
|
3240
3521
|
return "assertion_failure";
|
|
3241
3522
|
}
|
|
@@ -5069,7 +5350,7 @@ function prepareInput(raw, config) {
|
|
|
5069
5350
|
function escapeRegExp(value) {
|
|
5070
5351
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
5071
5352
|
}
|
|
5072
|
-
function
|
|
5353
|
+
function unique3(values) {
|
|
5073
5354
|
return [...new Set(values)];
|
|
5074
5355
|
}
|
|
5075
5356
|
var genericBucketSearchTerms = /* @__PURE__ */ new Set([
|
|
@@ -5170,7 +5451,7 @@ function extractBucketSearchTerms(args) {
|
|
|
5170
5451
|
...args.bucket.evidence,
|
|
5171
5452
|
...args.readTargets.filter((target) => target.bucket_index === args.bucket.bucket_index).flatMap((target) => [target.context_hint.search_hint ?? "", target.file])
|
|
5172
5453
|
];
|
|
5173
|
-
const prioritized =
|
|
5454
|
+
const prioritized = unique3(
|
|
5174
5455
|
sources.flatMap((value) => collectCandidateSearchTerms(value)).filter(isHighSignalSearchTerm)
|
|
5175
5456
|
).sort((left, right) => {
|
|
5176
5457
|
const delta = scoreSearchTerm(right) - scoreSearchTerm(left);
|
|
@@ -5182,7 +5463,7 @@ function extractBucketSearchTerms(args) {
|
|
|
5182
5463
|
if (prioritized.length > 0) {
|
|
5183
5464
|
return prioritized.slice(0, 6);
|
|
5184
5465
|
}
|
|
5185
|
-
const fallbackTerms =
|
|
5466
|
+
const fallbackTerms = unique3(
|
|
5186
5467
|
[...args.bucket.evidence, args.bucket.root_cause].flatMap((value) => value.split(/->|:/).map((part) => normalizeSearchTerm(part))).filter(isHighSignalSearchTerm)
|
|
5187
5468
|
);
|
|
5188
5469
|
return fallbackTerms.slice(0, 4);
|
|
@@ -5220,7 +5501,7 @@ function buildLineWindows(args) {
|
|
|
5220
5501
|
return [...selected].sort((left, right) => left - right).map((index) => args.lines[index]);
|
|
5221
5502
|
}
|
|
5222
5503
|
function buildPriorityLineGroup(args) {
|
|
5223
|
-
return
|
|
5504
|
+
return unique3([
|
|
5224
5505
|
...args.indexes.map((index) => args.lines[index]).filter(Boolean),
|
|
5225
5506
|
...buildLineWindows(args)
|
|
5226
5507
|
]);
|
|
@@ -5229,7 +5510,7 @@ function collapseSelectedLines(args) {
|
|
|
5229
5510
|
if (args.lines.length === 0) {
|
|
5230
5511
|
return args.fallback();
|
|
5231
5512
|
}
|
|
5232
|
-
const joined =
|
|
5513
|
+
const joined = unique3(args.lines).join("\n").trim();
|
|
5233
5514
|
if (joined.length === 0) {
|
|
5234
5515
|
return args.fallback();
|
|
5235
5516
|
}
|
|
@@ -5380,7 +5661,7 @@ function buildTestStatusRawSlice(args) {
|
|
|
5380
5661
|
const indexes = lines.map(
|
|
5381
5662
|
(line, index) => bucketTerms.some((term) => new RegExp(escapeRegExp(term), "i").test(line)) ? index : -1
|
|
5382
5663
|
).filter((index) => index >= 0);
|
|
5383
|
-
return
|
|
5664
|
+
return unique3([
|
|
5384
5665
|
...indexes.map((index) => lines[index]).filter(Boolean),
|
|
5385
5666
|
...buildPriorityLineGroup({
|
|
5386
5667
|
lines,
|
|
@@ -5423,7 +5704,7 @@ function buildTestStatusRawSlice(args) {
|
|
|
5423
5704
|
return [
|
|
5424
5705
|
buildPriorityLineGroup({
|
|
5425
5706
|
lines,
|
|
5426
|
-
indexes:
|
|
5707
|
+
indexes: unique3([...searchHintIndexes, ...fileIndexes]),
|
|
5427
5708
|
radius,
|
|
5428
5709
|
maxLines
|
|
5429
5710
|
})
|
|
@@ -5442,7 +5723,7 @@ function buildTestStatusRawSlice(args) {
|
|
|
5442
5723
|
const selected = collapseSelectedLineGroups({
|
|
5443
5724
|
groups: [
|
|
5444
5725
|
...targetGroups,
|
|
5445
|
-
|
|
5726
|
+
unique3([
|
|
5446
5727
|
...summaryIndexes.map((index) => lines[index]).filter(Boolean),
|
|
5447
5728
|
...buildLineWindows({
|
|
5448
5729
|
lines,
|
|
@@ -5640,6 +5921,34 @@ function hasRecognizableTestStatusSignal(input) {
|
|
|
5640
5921
|
const analysis = analyzeTestStatus(input);
|
|
5641
5922
|
return analysis.collectionErrorCount !== void 0 || analysis.noTestsCollected || analysis.interrupted || analysis.failed > 0 || analysis.errors > 0 || analysis.passed > 0 || analysis.inlineItems.length > 0 || analysis.buckets.length > 0;
|
|
5642
5923
|
}
|
|
5924
|
+
function shouldUseCompactTestStatusBypass(args) {
|
|
5925
|
+
if (args.request.policyName !== "test-status") {
|
|
5926
|
+
return false;
|
|
5927
|
+
}
|
|
5928
|
+
if (args.request.detail && args.request.detail !== "standard") {
|
|
5929
|
+
return false;
|
|
5930
|
+
}
|
|
5931
|
+
if (args.request.goal === "diagnose" && args.request.format === "json") {
|
|
5932
|
+
return false;
|
|
5933
|
+
}
|
|
5934
|
+
if (args.request.testStatusContext?.resolvedTests?.length || args.request.testStatusContext?.remainingTests?.length || args.request.testStatusContext?.remainingSubsetAvailable || args.request.testStatusContext?.remainingMode && args.request.testStatusContext.remainingMode !== "none") {
|
|
5935
|
+
return false;
|
|
5936
|
+
}
|
|
5937
|
+
return args.analysis.failed === 0 && args.analysis.errors === 0 && args.analysis.passed > 0 || args.analysis.collectionErrorCount !== void 0 && args.analysis.collectionItems.length === 0 && args.analysis.inlineItems.length === 0 && args.analysis.buckets.length === 0 || args.analysis.noTestsCollected || args.analysis.interrupted && args.analysis.failed === 0 && args.analysis.errors === 0;
|
|
5938
|
+
}
|
|
5939
|
+
function sanitizeProviderFailureReason(reason) {
|
|
5940
|
+
const normalized = reason.trim();
|
|
5941
|
+
const httpStatus = normalized.match(/\bHTTP\s+(\d{3})\b/i)?.[1];
|
|
5942
|
+
if (httpStatus) {
|
|
5943
|
+
return `provider follow-up unavailable (HTTP ${httpStatus})`;
|
|
5944
|
+
}
|
|
5945
|
+
if (/unterminated string|invalid json|unexpected token|json at position|schema|zod|parse/i.test(
|
|
5946
|
+
normalized
|
|
5947
|
+
)) {
|
|
5948
|
+
return "provider follow-up returned unusable structured output";
|
|
5949
|
+
}
|
|
5950
|
+
return "provider follow-up failed";
|
|
5951
|
+
}
|
|
5643
5952
|
function renderTestStatusDecisionOutput(args) {
|
|
5644
5953
|
if (args.request.goal === "diagnose" && args.request.format === "json") {
|
|
5645
5954
|
return JSON.stringify(
|
|
@@ -5661,6 +5970,7 @@ function renderTestStatusDecisionOutput(args) {
|
|
|
5661
5970
|
return args.decision.standardText;
|
|
5662
5971
|
}
|
|
5663
5972
|
function buildTestStatusProviderFailureDecision(args) {
|
|
5973
|
+
const sanitizedReason = sanitizeProviderFailureReason(args.reason);
|
|
5664
5974
|
const concreteReadTarget = args.baseDecision.contract.read_targets.find(
|
|
5665
5975
|
(target) => Boolean(target.file)
|
|
5666
5976
|
);
|
|
@@ -5673,6 +5983,7 @@ function buildTestStatusProviderFailureDecision(args) {
|
|
|
5673
5983
|
analysis: args.analysis,
|
|
5674
5984
|
resolvedTests: args.baseDecision.contract.resolved_tests,
|
|
5675
5985
|
remainingTests: args.baseDecision.contract.remaining_tests,
|
|
5986
|
+
remainingMode: args.request.testStatusContext?.remainingMode,
|
|
5676
5987
|
contractOverrides: {
|
|
5677
5988
|
...args.baseDecision.contract,
|
|
5678
5989
|
diagnosis_complete: false,
|
|
@@ -5688,7 +5999,9 @@ function buildTestStatusProviderFailureDecision(args) {
|
|
|
5688
5999
|
next_best_action: {
|
|
5689
6000
|
code: "read_source_for_bucket",
|
|
5690
6001
|
bucket_index: args.baseDecision.contract.dominant_blocker_bucket_index ?? concreteReadTarget.bucket_index,
|
|
5691
|
-
note:
|
|
6002
|
+
note: `${sanitizedReason[0].toUpperCase()}${sanitizedReason.slice(
|
|
6003
|
+
1
|
|
6004
|
+
)}. The heuristic anchor is concrete enough to inspect source for the current bucket before reading raw traceback.`
|
|
5692
6005
|
}
|
|
5693
6006
|
}
|
|
5694
6007
|
});
|
|
@@ -5699,6 +6012,7 @@ function buildTestStatusProviderFailureDecision(args) {
|
|
|
5699
6012
|
analysis: args.analysis,
|
|
5700
6013
|
resolvedTests: args.baseDecision.contract.resolved_tests,
|
|
5701
6014
|
remainingTests: args.baseDecision.contract.remaining_tests,
|
|
6015
|
+
remainingMode: args.request.testStatusContext?.remainingMode,
|
|
5702
6016
|
contractOverrides: {
|
|
5703
6017
|
...args.baseDecision.contract,
|
|
5704
6018
|
diagnosis_complete: false,
|
|
@@ -5714,7 +6028,11 @@ function buildTestStatusProviderFailureDecision(args) {
|
|
|
5714
6028
|
next_best_action: {
|
|
5715
6029
|
code: shouldZoomFirst ? "insufficient_signal" : "read_raw_for_exact_traceback",
|
|
5716
6030
|
bucket_index: args.baseDecision.contract.dominant_blocker_bucket_index ?? args.baseDecision.contract.main_buckets[0]?.bucket_index ?? null,
|
|
5717
|
-
note: shouldZoomFirst ?
|
|
6031
|
+
note: shouldZoomFirst ? `${sanitizedReason[0].toUpperCase()}${sanitizedReason.slice(
|
|
6032
|
+
1
|
|
6033
|
+
)}. Use one deeper sift pass on the same cached output before reading raw traceback lines.` : `${sanitizedReason[0].toUpperCase()}${sanitizedReason.slice(
|
|
6034
|
+
1
|
|
6035
|
+
)}. Read raw traceback only if exact stack lines are still needed.`
|
|
5718
6036
|
}
|
|
5719
6037
|
}
|
|
5720
6038
|
});
|
|
@@ -5735,23 +6053,28 @@ async function runSiftCore(request, recorder) {
|
|
|
5735
6053
|
const provider = createProvider(request.config);
|
|
5736
6054
|
const hasTestStatusSignal = request.policyName === "test-status" && hasRecognizableTestStatusSignal(heuristicInput);
|
|
5737
6055
|
const testStatusAnalysis = hasTestStatusSignal ? analyzeTestStatus(heuristicInput) : null;
|
|
5738
|
-
const
|
|
6056
|
+
const useCompactTestStatusOutput = hasTestStatusSignal && testStatusAnalysis ? shouldUseCompactTestStatusBypass({
|
|
6057
|
+
request,
|
|
6058
|
+
analysis: testStatusAnalysis
|
|
6059
|
+
}) : false;
|
|
6060
|
+
const testStatusDecision = hasTestStatusSignal && testStatusAnalysis && !useCompactTestStatusOutput ? buildTestStatusDiagnoseContract({
|
|
5739
6061
|
input: heuristicInput,
|
|
5740
6062
|
analysis: testStatusAnalysis,
|
|
5741
6063
|
resolvedTests: request.testStatusContext?.resolvedTests,
|
|
5742
|
-
remainingTests: request.testStatusContext?.remainingTests
|
|
6064
|
+
remainingTests: request.testStatusContext?.remainingTests,
|
|
6065
|
+
remainingMode: request.testStatusContext?.remainingMode
|
|
5743
6066
|
}) : null;
|
|
5744
6067
|
const testStatusHeuristicOutput = testStatusDecision ? renderTestStatusDecisionOutput({
|
|
5745
6068
|
request,
|
|
5746
6069
|
decision: testStatusDecision
|
|
5747
|
-
}) : null;
|
|
6070
|
+
}) : useCompactTestStatusOutput ? applyHeuristicPolicy("test-status", heuristicInput, "standard") : null;
|
|
5748
6071
|
if (request.config.runtime.verbose) {
|
|
5749
6072
|
process.stderr.write(
|
|
5750
6073
|
`${pc.dim("sift")} provider=${provider.name} model=${request.config.provider.model} base_url=${request.config.provider.baseUrl} input_chars=${prepared.meta.finalLength}
|
|
5751
6074
|
`
|
|
5752
6075
|
);
|
|
5753
6076
|
}
|
|
5754
|
-
const heuristicOutput = request.policyName === "test-status" ? testStatusDecision?.contract.diagnosis_complete ? testStatusHeuristicOutput : null : applyHeuristicPolicy(request.policyName, heuristicInput, request.detail);
|
|
6077
|
+
const heuristicOutput = request.policyName === "test-status" ? useCompactTestStatusOutput ? testStatusHeuristicOutput : testStatusDecision?.contract.diagnosis_complete ? testStatusHeuristicOutput : null : applyHeuristicPolicy(request.policyName, heuristicInput, request.detail);
|
|
5755
6078
|
if (heuristicOutput) {
|
|
5756
6079
|
if (request.config.runtime.verbose) {
|
|
5757
6080
|
process.stderr.write(`${pc.dim("sift")} heuristic=${request.policyName}
|
|
@@ -5875,6 +6198,7 @@ async function runSiftCore(request, recorder) {
|
|
|
5875
6198
|
analysis: testStatusAnalysis,
|
|
5876
6199
|
resolvedTests: request.testStatusContext?.resolvedTests,
|
|
5877
6200
|
remainingTests: request.testStatusContext?.remainingTests,
|
|
6201
|
+
remainingMode: request.testStatusContext?.remainingMode,
|
|
5878
6202
|
providerBucketSupplements: supplement.bucket_supplements,
|
|
5879
6203
|
contractOverrides: {
|
|
5880
6204
|
diagnosis_complete: supplement.diagnosis_complete,
|
|
@@ -6120,6 +6444,7 @@ var failureBucketTypeSchema = z2.enum([
|
|
|
6120
6444
|
"import_dependency_failure",
|
|
6121
6445
|
"collection_failure",
|
|
6122
6446
|
"assertion_failure",
|
|
6447
|
+
"golden_output_drift",
|
|
6123
6448
|
"runtime_failure",
|
|
6124
6449
|
"interrupted_run",
|
|
6125
6450
|
"no_tests_collected",
|
|
@@ -6160,7 +6485,19 @@ var cachedPytestStateSchema = z2.object({
|
|
|
6160
6485
|
failingNodeIds: z2.array(z2.string()),
|
|
6161
6486
|
remainingNodeIds: z2.array(z2.string()).optional()
|
|
6162
6487
|
}).optional();
|
|
6163
|
-
var
|
|
6488
|
+
var testRunnerSchema = z2.enum(["pytest", "vitest", "jest", "unknown"]);
|
|
6489
|
+
var cachedRunnerSubsetSchema = z2.object({
|
|
6490
|
+
available: z2.boolean(),
|
|
6491
|
+
strategy: z2.enum(["pytest-node-ids", "none"]),
|
|
6492
|
+
baseArgv: z2.array(z2.string()).min(1).optional()
|
|
6493
|
+
});
|
|
6494
|
+
var cachedRunnerStateSchema = z2.object({
|
|
6495
|
+
name: testRunnerSchema,
|
|
6496
|
+
failingTargets: z2.array(z2.string()),
|
|
6497
|
+
baselineCommand: cachedCommandSchema,
|
|
6498
|
+
subset: cachedRunnerSubsetSchema
|
|
6499
|
+
});
|
|
6500
|
+
var cachedRunV1Schema = z2.object({
|
|
6164
6501
|
version: z2.literal(1),
|
|
6165
6502
|
timestamp: z2.string(),
|
|
6166
6503
|
presetName: z2.literal("test-status"),
|
|
@@ -6178,6 +6515,25 @@ var cachedRunSchema = z2.object({
|
|
|
6178
6515
|
analysis: cachedAnalysisSchema,
|
|
6179
6516
|
pytest: cachedPytestStateSchema
|
|
6180
6517
|
});
|
|
6518
|
+
var cachedRunV2Schema = z2.object({
|
|
6519
|
+
version: z2.literal(2),
|
|
6520
|
+
timestamp: z2.string(),
|
|
6521
|
+
presetName: z2.literal("test-status"),
|
|
6522
|
+
cwd: z2.string(),
|
|
6523
|
+
commandKey: z2.string(),
|
|
6524
|
+
commandPreview: z2.string(),
|
|
6525
|
+
command: cachedCommandSchema,
|
|
6526
|
+
detail: detailSchema,
|
|
6527
|
+
exitCode: z2.number().int(),
|
|
6528
|
+
rawOutput: z2.string(),
|
|
6529
|
+
capture: z2.object({
|
|
6530
|
+
originalChars: countSchema,
|
|
6531
|
+
truncatedApplied: z2.boolean()
|
|
6532
|
+
}),
|
|
6533
|
+
analysis: cachedAnalysisSchema,
|
|
6534
|
+
runner: cachedRunnerStateSchema
|
|
6535
|
+
});
|
|
6536
|
+
var cachedRunSchema = z2.discriminatedUnion("version", [cachedRunV1Schema, cachedRunV2Schema]);
|
|
6181
6537
|
var MissingCachedTestStatusRunError = class extends Error {
|
|
6182
6538
|
constructor() {
|
|
6183
6539
|
super(
|
|
@@ -6226,6 +6582,37 @@ function isPytestExecutable(value) {
|
|
|
6226
6582
|
function isPythonExecutable(value) {
|
|
6227
6583
|
return basenameMatches(value, /^python(?:\d+(?:\.\d+)*)?(?:\.exe)?$/i);
|
|
6228
6584
|
}
|
|
6585
|
+
function detectRunnerFromCommand(command) {
|
|
6586
|
+
if (!command) {
|
|
6587
|
+
return "unknown";
|
|
6588
|
+
}
|
|
6589
|
+
if (command.mode === "argv") {
|
|
6590
|
+
const [first, second, third] = command.argv;
|
|
6591
|
+
if (first && isPytestExecutable(first)) {
|
|
6592
|
+
return "pytest";
|
|
6593
|
+
}
|
|
6594
|
+
if (first && isPythonExecutable(first) && second === "-m" && third === "pytest") {
|
|
6595
|
+
return "pytest";
|
|
6596
|
+
}
|
|
6597
|
+
if (first && basenameMatches(first, /^vitest(?:\.exe)?$/i)) {
|
|
6598
|
+
return "vitest";
|
|
6599
|
+
}
|
|
6600
|
+
if (first && basenameMatches(first, /^jest(?:\.exe)?$/i)) {
|
|
6601
|
+
return "jest";
|
|
6602
|
+
}
|
|
6603
|
+
return "unknown";
|
|
6604
|
+
}
|
|
6605
|
+
if (/\bpython(?:\d+(?:\.\d+)*)?\s+-m\s+pytest\b|\bpytest\b/i.test(command.shellCommand)) {
|
|
6606
|
+
return "pytest";
|
|
6607
|
+
}
|
|
6608
|
+
if (/\bvitest\b/i.test(command.shellCommand)) {
|
|
6609
|
+
return "vitest";
|
|
6610
|
+
}
|
|
6611
|
+
if (/\bjest\b/i.test(command.shellCommand)) {
|
|
6612
|
+
return "jest";
|
|
6613
|
+
}
|
|
6614
|
+
return "unknown";
|
|
6615
|
+
}
|
|
6229
6616
|
var shortPytestOptionsWithValue = /* @__PURE__ */ new Set([
|
|
6230
6617
|
"-c",
|
|
6231
6618
|
"-k",
|
|
@@ -6320,26 +6707,52 @@ function buildCachedCommand(args) {
|
|
|
6320
6707
|
}
|
|
6321
6708
|
return void 0;
|
|
6322
6709
|
}
|
|
6323
|
-
function
|
|
6710
|
+
function buildFailingTargets(analysis) {
|
|
6711
|
+
const runner = analysis.runner;
|
|
6324
6712
|
const values = [];
|
|
6325
6713
|
for (const value of [...analysis.visibleErrorLabels, ...analysis.visibleFailedLabels]) {
|
|
6326
|
-
|
|
6327
|
-
|
|
6714
|
+
const normalized = normalizeFailingTarget(value, runner);
|
|
6715
|
+
if (normalized.length > 0 && !values.includes(normalized)) {
|
|
6716
|
+
values.push(normalized);
|
|
6328
6717
|
}
|
|
6329
6718
|
}
|
|
6330
6719
|
return values;
|
|
6331
6720
|
}
|
|
6332
|
-
function
|
|
6721
|
+
function buildCachedRunnerState(args) {
|
|
6333
6722
|
const baseArgv = args.command?.mode === "argv" && isSubsetCapablePytestArgv(args.command.argv) ? [...args.command.argv] : void 0;
|
|
6723
|
+
const runnerName = args.analysis.runner !== "unknown" ? args.analysis.runner : detectRunnerFromCommand(args.command);
|
|
6334
6724
|
return {
|
|
6335
|
-
|
|
6336
|
-
|
|
6337
|
-
|
|
6338
|
-
|
|
6725
|
+
name: runnerName,
|
|
6726
|
+
failingTargets: buildFailingTargets(args.analysis),
|
|
6727
|
+
baselineCommand: args.command,
|
|
6728
|
+
subset: {
|
|
6729
|
+
available: runnerName === "pytest" && Boolean(baseArgv),
|
|
6730
|
+
strategy: runnerName === "pytest" && baseArgv ? "pytest-node-ids" : "none",
|
|
6731
|
+
...runnerName === "pytest" && baseArgv ? { baseArgv } : {}
|
|
6732
|
+
}
|
|
6339
6733
|
};
|
|
6340
6734
|
}
|
|
6735
|
+
function normalizeCwd(value) {
|
|
6736
|
+
return path2.resolve(value).replace(/\\/g, "/");
|
|
6737
|
+
}
|
|
6738
|
+
function buildTestStatusBaselineIdentity(args) {
|
|
6739
|
+
const cwd = normalizeCwd(args.cwd);
|
|
6740
|
+
const command = args.command ?? buildCachedCommand({
|
|
6741
|
+
shellCommand: args.shellCommand,
|
|
6742
|
+
command: args.shellCommand ? void 0 : args.commandPreview?.split(" ")
|
|
6743
|
+
});
|
|
6744
|
+
const mode = command?.mode ?? (args.shellCommand ? "shell" : "argv");
|
|
6745
|
+
const normalizedCommand = command?.mode === "argv" ? command.argv.join("") : command?.mode === "shell" ? command.shellCommand.trim().replace(/\s+/g, " ") : (args.commandPreview ?? "").trim().replace(/\s+/g, " ");
|
|
6746
|
+
return [cwd, args.runner, mode, normalizedCommand].join("");
|
|
6747
|
+
}
|
|
6341
6748
|
function buildTestStatusCommandKey(args) {
|
|
6342
|
-
return
|
|
6749
|
+
return buildTestStatusBaselineIdentity({
|
|
6750
|
+
cwd: args.cwd ?? process.cwd(),
|
|
6751
|
+
runner: args.runner ?? "unknown",
|
|
6752
|
+
command: args.command,
|
|
6753
|
+
commandPreview: args.commandPreview,
|
|
6754
|
+
shellCommand: args.shellCommand
|
|
6755
|
+
});
|
|
6343
6756
|
}
|
|
6344
6757
|
function snapshotTestStatusAnalysis(analysis) {
|
|
6345
6758
|
return {
|
|
@@ -6365,13 +6778,22 @@ function createCachedTestStatusRun(args) {
|
|
|
6365
6778
|
command: args.command,
|
|
6366
6779
|
shellCommand: args.shellCommand
|
|
6367
6780
|
});
|
|
6781
|
+
const runnerName = args.analysis.runner !== "unknown" ? args.analysis.runner : detectRunnerFromCommand(command);
|
|
6782
|
+
const commandPreview = args.commandPreview ?? args.shellCommand ?? (args.command ?? []).join(" ");
|
|
6783
|
+
const commandKey = args.commandKey ?? buildTestStatusBaselineIdentity({
|
|
6784
|
+
cwd: args.cwd,
|
|
6785
|
+
runner: runnerName,
|
|
6786
|
+
command,
|
|
6787
|
+
commandPreview,
|
|
6788
|
+
shellCommand: args.shellCommand
|
|
6789
|
+
});
|
|
6368
6790
|
return {
|
|
6369
|
-
version:
|
|
6791
|
+
version: 2,
|
|
6370
6792
|
timestamp: args.timestamp ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
6371
6793
|
presetName: "test-status",
|
|
6372
6794
|
cwd: args.cwd,
|
|
6373
|
-
commandKey
|
|
6374
|
-
commandPreview
|
|
6795
|
+
commandKey,
|
|
6796
|
+
commandPreview,
|
|
6375
6797
|
command,
|
|
6376
6798
|
detail: args.detail,
|
|
6377
6799
|
exitCode: args.exitCode,
|
|
@@ -6381,13 +6803,61 @@ function createCachedTestStatusRun(args) {
|
|
|
6381
6803
|
truncatedApplied: args.truncatedApplied
|
|
6382
6804
|
},
|
|
6383
6805
|
analysis: snapshotTestStatusAnalysis(args.analysis),
|
|
6384
|
-
|
|
6806
|
+
runner: buildCachedRunnerState({
|
|
6385
6807
|
command,
|
|
6386
|
-
analysis: args.analysis
|
|
6387
|
-
remainingNodeIds: args.remainingNodeIds
|
|
6808
|
+
analysis: args.analysis
|
|
6388
6809
|
})
|
|
6389
6810
|
};
|
|
6390
6811
|
}
|
|
6812
|
+
function migrateCachedTestStatusRun(state) {
|
|
6813
|
+
if (state.version === 2) {
|
|
6814
|
+
return state;
|
|
6815
|
+
}
|
|
6816
|
+
const runnerFromOutput = detectTestRunner(state.rawOutput);
|
|
6817
|
+
const runner = runnerFromOutput !== "unknown" ? runnerFromOutput : detectRunnerFromCommand(state.command);
|
|
6818
|
+
const storedCommand = state.command;
|
|
6819
|
+
const fallbackBaseArgv = !storedCommand && state.pytest?.baseArgv ? {
|
|
6820
|
+
mode: "argv",
|
|
6821
|
+
argv: [...state.pytest.baseArgv]
|
|
6822
|
+
} : void 0;
|
|
6823
|
+
const baselineCommand = storedCommand ?? fallbackBaseArgv;
|
|
6824
|
+
const commandPreview = state.commandPreview ?? (baselineCommand?.mode === "argv" ? baselineCommand.argv.join(" ") : baselineCommand?.mode === "shell" ? baselineCommand.shellCommand : "");
|
|
6825
|
+
const commandKey = buildTestStatusBaselineIdentity({
|
|
6826
|
+
cwd: state.cwd,
|
|
6827
|
+
runner,
|
|
6828
|
+
command: baselineCommand,
|
|
6829
|
+
commandPreview
|
|
6830
|
+
});
|
|
6831
|
+
return {
|
|
6832
|
+
version: 2,
|
|
6833
|
+
timestamp: state.timestamp,
|
|
6834
|
+
presetName: state.presetName,
|
|
6835
|
+
cwd: state.cwd,
|
|
6836
|
+
commandKey,
|
|
6837
|
+
commandPreview,
|
|
6838
|
+
command: state.command,
|
|
6839
|
+
detail: state.detail,
|
|
6840
|
+
exitCode: state.exitCode,
|
|
6841
|
+
rawOutput: state.rawOutput,
|
|
6842
|
+
capture: state.capture,
|
|
6843
|
+
analysis: state.analysis,
|
|
6844
|
+
runner: {
|
|
6845
|
+
name: runner,
|
|
6846
|
+
failingTargets: [...new Set((state.pytest?.failingNodeIds ?? []).map(
|
|
6847
|
+
(target) => normalizeFailingTarget(target, runner)
|
|
6848
|
+
))],
|
|
6849
|
+
baselineCommand,
|
|
6850
|
+
subset: {
|
|
6851
|
+
available: runner === "pytest" && Boolean(state.pytest?.baseArgv),
|
|
6852
|
+
strategy: runner === "pytest" && state.pytest?.baseArgv ? "pytest-node-ids" : "none",
|
|
6853
|
+
...runner === "pytest" && state.pytest?.baseArgv ? {
|
|
6854
|
+
baseArgv: [...state.pytest.baseArgv]
|
|
6855
|
+
} : {}
|
|
6856
|
+
}
|
|
6857
|
+
},
|
|
6858
|
+
...fallbackBaseArgv ? { runnerMigrationFallbackUsed: true } : {}
|
|
6859
|
+
};
|
|
6860
|
+
}
|
|
6391
6861
|
function readCachedTestStatusRun(statePath = getDefaultTestStatusStatePath()) {
|
|
6392
6862
|
let raw = "";
|
|
6393
6863
|
try {
|
|
@@ -6399,7 +6869,7 @@ function readCachedTestStatusRun(statePath = getDefaultTestStatusStatePath()) {
|
|
|
6399
6869
|
throw new InvalidCachedTestStatusRunError();
|
|
6400
6870
|
}
|
|
6401
6871
|
try {
|
|
6402
|
-
return cachedRunSchema.parse(JSON.parse(raw));
|
|
6872
|
+
return migrateCachedTestStatusRun(cachedRunSchema.parse(JSON.parse(raw)));
|
|
6403
6873
|
} catch {
|
|
6404
6874
|
throw new InvalidCachedTestStatusRunError();
|
|
6405
6875
|
}
|
|
@@ -6419,15 +6889,7 @@ function writeCachedTestStatusRun(state, statePath = getDefaultTestStatusStatePa
|
|
|
6419
6889
|
`, "utf8");
|
|
6420
6890
|
}
|
|
6421
6891
|
function buildTargetDelta(args) {
|
|
6422
|
-
if (args.previous.presetName !== "test-status" || args.current.presetName !== "test-status" || args.previous.cwd !== args.current.cwd || args.previous.commandKey !== args.current.commandKey) {
|
|
6423
|
-
return {
|
|
6424
|
-
comparable: false,
|
|
6425
|
-
resolved: [],
|
|
6426
|
-
remaining: [],
|
|
6427
|
-
introduced: []
|
|
6428
|
-
};
|
|
6429
|
-
}
|
|
6430
|
-
if (!args.previous.pytest || !args.current.pytest) {
|
|
6892
|
+
if (args.previous.presetName !== "test-status" || args.current.presetName !== "test-status" || args.previous.cwd !== args.current.cwd || args.previous.commandKey !== args.current.commandKey || args.previous.runner.name !== args.current.runner.name || args.previous.runner.name === "unknown") {
|
|
6431
6893
|
return {
|
|
6432
6894
|
comparable: false,
|
|
6433
6895
|
resolved: [],
|
|
@@ -6435,8 +6897,8 @@ function buildTargetDelta(args) {
|
|
|
6435
6897
|
introduced: []
|
|
6436
6898
|
};
|
|
6437
6899
|
}
|
|
6438
|
-
const previousTargets = args.previous.
|
|
6439
|
-
const currentTargets = args.current.
|
|
6900
|
+
const previousTargets = args.previous.runner.failingTargets;
|
|
6901
|
+
const currentTargets = args.current.runner.failingTargets;
|
|
6440
6902
|
const currentTargetSet = new Set(currentTargets);
|
|
6441
6903
|
const previousTargetSet = new Set(previousTargets);
|
|
6442
6904
|
return {
|
|
@@ -6449,6 +6911,9 @@ function buildTargetDelta(args) {
|
|
|
6449
6911
|
function diffTestStatusTargets(args) {
|
|
6450
6912
|
return buildTargetDelta(args);
|
|
6451
6913
|
}
|
|
6914
|
+
function isRemainingSubsetAvailable(state) {
|
|
6915
|
+
return state.runner.name === "pytest" && state.runner.subset.available;
|
|
6916
|
+
}
|
|
6452
6917
|
function diffTestStatusRuns(args) {
|
|
6453
6918
|
const targetDelta = buildTargetDelta(args);
|
|
6454
6919
|
const previousBuckets = new Map(
|
|
@@ -6458,21 +6923,45 @@ function diffTestStatusRuns(args) {
|
|
|
6458
6923
|
args.current.analysis.buckets.map((bucket) => [buildBucketSignature(bucket), bucket])
|
|
6459
6924
|
);
|
|
6460
6925
|
const lines = [];
|
|
6461
|
-
|
|
6462
|
-
|
|
6463
|
-
|
|
6464
|
-
|
|
6465
|
-
|
|
6466
|
-
|
|
6467
|
-
|
|
6468
|
-
|
|
6469
|
-
)
|
|
6470
|
-
|
|
6471
|
-
|
|
6926
|
+
const resolvedSummary = buildTestTargetSummary(targetDelta.resolved);
|
|
6927
|
+
const remainingSummary = buildTestTargetSummary(targetDelta.remaining);
|
|
6928
|
+
const introducedSummary = buildTestTargetSummary(targetDelta.introduced);
|
|
6929
|
+
const pushTargetLine = (args2) => {
|
|
6930
|
+
if (args2.summary.count === 0) {
|
|
6931
|
+
return;
|
|
6932
|
+
}
|
|
6933
|
+
const summaryText = describeTargetSummary(args2.summary);
|
|
6934
|
+
if (summaryText) {
|
|
6935
|
+
lines.push(
|
|
6936
|
+
`- ${args2.kind}: ${formatCount3(args2.summary.count, args2.countLabel, `${args2.countLabel}s`)} ${args2.verb} ${summaryText}.`
|
|
6937
|
+
);
|
|
6938
|
+
return;
|
|
6939
|
+
}
|
|
6472
6940
|
lines.push(
|
|
6473
|
-
`-
|
|
6941
|
+
`- ${args2.kind}: ${formatCount3(args2.summary.count, args2.countLabel, `${args2.countLabel}s`)} ${args2.verb}${appendPreview(args2.fallbackValues)}.`
|
|
6474
6942
|
);
|
|
6475
|
-
}
|
|
6943
|
+
};
|
|
6944
|
+
pushTargetLine({
|
|
6945
|
+
kind: "Resolved",
|
|
6946
|
+
summary: resolvedSummary,
|
|
6947
|
+
countLabel: "failing target",
|
|
6948
|
+
fallbackValues: targetDelta.resolved,
|
|
6949
|
+
verb: "no longer appear"
|
|
6950
|
+
});
|
|
6951
|
+
pushTargetLine({
|
|
6952
|
+
kind: "Remaining",
|
|
6953
|
+
summary: remainingSummary,
|
|
6954
|
+
countLabel: "failing target",
|
|
6955
|
+
fallbackValues: targetDelta.remaining,
|
|
6956
|
+
verb: "still appear"
|
|
6957
|
+
});
|
|
6958
|
+
pushTargetLine({
|
|
6959
|
+
kind: "New",
|
|
6960
|
+
summary: introducedSummary,
|
|
6961
|
+
countLabel: "failing target",
|
|
6962
|
+
fallbackValues: targetDelta.introduced,
|
|
6963
|
+
verb: "appeared"
|
|
6964
|
+
});
|
|
6476
6965
|
for (const bucket of args.current.analysis.buckets) {
|
|
6477
6966
|
const signature = buildBucketSignature(bucket);
|
|
6478
6967
|
const previous = previousBuckets.get(signature);
|
|
@@ -6500,8 +6989,7 @@ function diffTestStatusRuns(args) {
|
|
|
6500
6989
|
}
|
|
6501
6990
|
}
|
|
6502
6991
|
return {
|
|
6503
|
-
lines: lines.slice(0, 4)
|
|
6504
|
-
remainingNodeIds: targetDelta.comparable ? targetDelta.remaining : void 0
|
|
6992
|
+
lines: lines.slice(0, 4)
|
|
6505
6993
|
};
|
|
6506
6994
|
}
|
|
6507
6995
|
|
|
@@ -6634,8 +7122,9 @@ async function runTestStatusWatch(request, cycles) {
|
|
|
6634
7122
|
testStatusContext: {
|
|
6635
7123
|
...request.testStatusContext,
|
|
6636
7124
|
resolvedTests: targetDelta?.resolved ?? request.testStatusContext?.resolvedTests,
|
|
6637
|
-
remainingTests: targetDelta?.remaining ?? currentRun.
|
|
6638
|
-
remainingSubsetAvailable: request.testStatusContext?.remainingSubsetAvailable ?? (
|
|
7125
|
+
remainingTests: targetDelta?.remaining ?? currentRun.runner.failingTargets ?? request.testStatusContext?.remainingTests,
|
|
7126
|
+
remainingSubsetAvailable: request.testStatusContext?.remainingSubsetAvailable ?? (isRemainingSubsetAvailable(currentRun) && currentRun.runner.failingTargets.length > 0),
|
|
7127
|
+
remainingMode: request.testStatusContext?.remainingMode ?? "none"
|
|
6639
7128
|
}
|
|
6640
7129
|
});
|
|
6641
7130
|
if (request.goal === "diagnose" && request.format === "json") {
|
|
@@ -6782,8 +7271,10 @@ async function runExec(request) {
|
|
|
6782
7271
|
const shellPath = process.env.SHELL || "/bin/bash";
|
|
6783
7272
|
const commandPreview = buildCommandPreview(request);
|
|
6784
7273
|
const commandCwd = request.cwd ?? process.cwd();
|
|
6785
|
-
const
|
|
6786
|
-
const
|
|
7274
|
+
const isTestStatusPreset = request.presetName === "test-status";
|
|
7275
|
+
const readCachedBaseline = isTestStatusPreset && (request.readCachedBaseline ?? true);
|
|
7276
|
+
const writeCachedBaselineRequested = isTestStatusPreset && (request.writeCachedBaseline ?? (request.skipCacheWrite ? false : true));
|
|
7277
|
+
const previousCachedRun = readCachedBaseline ? tryReadCachedTestStatusRun() : null;
|
|
6787
7278
|
if (request.config.runtime.verbose) {
|
|
6788
7279
|
process.stderr.write(
|
|
6789
7280
|
`${pc3.dim("sift")} exec mode=${hasShellCommand ? "shell" : "argv"} command=${commandPreview}
|
|
@@ -6841,7 +7332,8 @@ async function runExec(request) {
|
|
|
6841
7332
|
const capturedOutput = capture.render();
|
|
6842
7333
|
const autoWatchDetected = !request.watch && looksLikeWatchStream(capturedOutput);
|
|
6843
7334
|
const useWatchFlow = Boolean(request.watch) || autoWatchDetected;
|
|
6844
|
-
const
|
|
7335
|
+
const shouldBuildTestStatusState = isTestStatusPreset && !useWatchFlow;
|
|
7336
|
+
const shouldWriteCachedBaseline = writeCachedBaselineRequested && !useWatchFlow;
|
|
6845
7337
|
if (request.config.runtime.verbose) {
|
|
6846
7338
|
process.stderr.write(
|
|
6847
7339
|
`${pc3.dim("sift")} child_exit=${exitCode} captured_chars=${capture.getTotalChars()} capture_truncated=${capture.wasTruncated()}
|
|
@@ -6904,10 +7396,19 @@ async function runExec(request) {
|
|
|
6904
7396
|
`);
|
|
6905
7397
|
return exitCode;
|
|
6906
7398
|
}
|
|
6907
|
-
const analysis =
|
|
6908
|
-
let currentCachedRun =
|
|
7399
|
+
const analysis = shouldBuildTestStatusState ? analyzeTestStatus(capturedOutput) : null;
|
|
7400
|
+
let currentCachedRun = shouldBuildTestStatusState && analysis ? createCachedTestStatusRun({
|
|
6909
7401
|
cwd: commandCwd,
|
|
6910
7402
|
commandKey: buildTestStatusCommandKey({
|
|
7403
|
+
cwd: commandCwd,
|
|
7404
|
+
runner: analysis.runner,
|
|
7405
|
+
command: Array.isArray(request.command) && request.command.length > 0 ? {
|
|
7406
|
+
mode: "argv",
|
|
7407
|
+
argv: [...request.command]
|
|
7408
|
+
} : request.shellCommand ? {
|
|
7409
|
+
mode: "shell",
|
|
7410
|
+
shellCommand: request.shellCommand
|
|
7411
|
+
} : void 0,
|
|
6911
7412
|
commandPreview,
|
|
6912
7413
|
shellCommand: request.shellCommand
|
|
6913
7414
|
}),
|
|
@@ -6921,31 +7422,32 @@ async function runExec(request) {
|
|
|
6921
7422
|
truncatedApplied: capture.wasTruncated(),
|
|
6922
7423
|
analysis
|
|
6923
7424
|
}) : null;
|
|
6924
|
-
const targetDelta = request.diff && !request.dryRun && previousCachedRun && currentCachedRun ? diffTestStatusTargets({
|
|
7425
|
+
const targetDelta = (request.diff || request.testStatusContext?.remainingMode === "subset_rerun" || request.testStatusContext?.remainingMode === "full_rerun_diff") && !request.dryRun && previousCachedRun && currentCachedRun ? diffTestStatusTargets({
|
|
6925
7426
|
previous: previousCachedRun,
|
|
6926
7427
|
current: currentCachedRun
|
|
6927
7428
|
}) : null;
|
|
6928
7429
|
const result = await runSiftWithStats({
|
|
6929
7430
|
...request,
|
|
6930
7431
|
stdin: capturedOutput,
|
|
6931
|
-
analysisContext: request.
|
|
7432
|
+
analysisContext: request.testStatusContext?.remainingMode && request.testStatusContext.remainingMode !== "none" && request.presetName === "test-status" ? [
|
|
6932
7433
|
request.analysisContext,
|
|
6933
7434
|
"Zoom context:",
|
|
6934
7435
|
"- This pass is remaining-only.",
|
|
6935
7436
|
"- The full-suite truth already exists from the cached full run.",
|
|
6936
7437
|
"- Do not reintroduce resolved tests into the diagnosis."
|
|
6937
7438
|
].filter((value) => Boolean(value)).join("\n") : request.analysisContext,
|
|
6938
|
-
testStatusContext:
|
|
7439
|
+
testStatusContext: shouldBuildTestStatusState && analysis ? {
|
|
6939
7440
|
...request.testStatusContext,
|
|
6940
7441
|
resolvedTests: targetDelta?.resolved ?? request.testStatusContext?.resolvedTests,
|
|
6941
|
-
remainingTests: targetDelta?.remaining ?? currentCachedRun?.
|
|
7442
|
+
remainingTests: targetDelta?.remaining ?? currentCachedRun?.runner.failingTargets ?? request.testStatusContext?.remainingTests,
|
|
6942
7443
|
remainingSubsetAvailable: request.testStatusContext?.remainingSubsetAvailable ?? Boolean(
|
|
6943
|
-
currentCachedRun
|
|
6944
|
-
)
|
|
7444
|
+
currentCachedRun && isRemainingSubsetAvailable(currentCachedRun) && (targetDelta?.remaining ?? currentCachedRun?.runner.failingTargets ?? []).length > 0
|
|
7445
|
+
),
|
|
7446
|
+
remainingMode: request.testStatusContext?.remainingMode ?? "none"
|
|
6945
7447
|
} : request.testStatusContext
|
|
6946
7448
|
});
|
|
6947
7449
|
let output = result.output;
|
|
6948
|
-
if (
|
|
7450
|
+
if (shouldBuildTestStatusState) {
|
|
6949
7451
|
if (isInsufficientSignalOutput(output)) {
|
|
6950
7452
|
output = buildInsufficientSignalOutput({
|
|
6951
7453
|
presetName: request.presetName,
|
|
@@ -6960,26 +7462,12 @@ async function runExec(request) {
|
|
|
6960
7462
|
previous: previousCachedRun,
|
|
6961
7463
|
current: currentCachedRun
|
|
6962
7464
|
});
|
|
6963
|
-
currentCachedRun = createCachedTestStatusRun({
|
|
6964
|
-
cwd: commandCwd,
|
|
6965
|
-
commandKey: currentCachedRun.commandKey,
|
|
6966
|
-
commandPreview,
|
|
6967
|
-
command: request.command,
|
|
6968
|
-
shellCommand: request.shellCommand,
|
|
6969
|
-
detail: request.detail ?? "standard",
|
|
6970
|
-
exitCode,
|
|
6971
|
-
rawOutput: capturedOutput,
|
|
6972
|
-
originalChars: capture.getTotalChars(),
|
|
6973
|
-
truncatedApplied: capture.wasTruncated(),
|
|
6974
|
-
analysis,
|
|
6975
|
-
remainingNodeIds: delta.remainingNodeIds
|
|
6976
|
-
});
|
|
6977
7465
|
if (delta.lines.length > 0) {
|
|
6978
7466
|
output = `${delta.lines.join("\n")}
|
|
6979
7467
|
${output}`;
|
|
6980
7468
|
}
|
|
6981
7469
|
}
|
|
6982
|
-
if (currentCachedRun) {
|
|
7470
|
+
if (currentCachedRun && shouldWriteCachedBaseline) {
|
|
6983
7471
|
try {
|
|
6984
7472
|
writeCachedTestStatusRun(currentCachedRun);
|
|
6985
7473
|
} catch (error) {
|