@bilalimamoglu/sift 0.3.3 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +84 -39
- package/dist/cli.js +725 -183
- package/dist/index.d.ts +6 -1
- package/dist/index.js +657 -158
- 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;
|
|
458
593
|
}
|
|
459
|
-
|
|
594
|
+
if (/^[A-Za-z0-9._/-]*config[A-Za-z0-9._/-]*\.(?:json|yml|yaml)$/i.test(normalized)) {
|
|
595
|
+
return normalized;
|
|
596
|
+
}
|
|
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,
|
|
@@ -5493,7 +5774,7 @@ function buildGenericRawSlice(args) {
|
|
|
5493
5774
|
|
|
5494
5775
|
// src/core/run.ts
|
|
5495
5776
|
var RETRY_DELAY_MS = 300;
|
|
5496
|
-
var
|
|
5777
|
+
var PENDING_NOTICE_DELAY_MS = 150;
|
|
5497
5778
|
function estimateTokenCount(text) {
|
|
5498
5779
|
return Math.max(1, Math.ceil(text.length / 4));
|
|
5499
5780
|
}
|
|
@@ -5574,17 +5855,16 @@ function buildDryRunOutput(args) {
|
|
|
5574
5855
|
async function delay(ms) {
|
|
5575
5856
|
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
5576
5857
|
}
|
|
5577
|
-
function
|
|
5578
|
-
if (!
|
|
5858
|
+
function startPendingNotice(message, enabled) {
|
|
5859
|
+
if (!enabled) {
|
|
5579
5860
|
return () => {
|
|
5580
5861
|
};
|
|
5581
5862
|
}
|
|
5582
|
-
const message = "sift waiting for provider...";
|
|
5583
5863
|
let shown = false;
|
|
5584
5864
|
const timer = setTimeout(() => {
|
|
5585
5865
|
shown = true;
|
|
5586
5866
|
process.stderr.write(`${message}\r`);
|
|
5587
|
-
},
|
|
5867
|
+
}, PENDING_NOTICE_DELAY_MS);
|
|
5588
5868
|
return () => {
|
|
5589
5869
|
clearTimeout(timer);
|
|
5590
5870
|
if (!shown) {
|
|
@@ -5614,7 +5894,10 @@ async function generateWithRetry(args) {
|
|
|
5614
5894
|
responseMode: args.responseMode,
|
|
5615
5895
|
jsonResponseFormat: args.request.config.provider.jsonResponseFormat
|
|
5616
5896
|
});
|
|
5617
|
-
const stopPendingNotice =
|
|
5897
|
+
const stopPendingNotice = startPendingNotice(
|
|
5898
|
+
"sift waiting for provider...",
|
|
5899
|
+
Boolean(process.stderr.isTTY)
|
|
5900
|
+
);
|
|
5618
5901
|
try {
|
|
5619
5902
|
try {
|
|
5620
5903
|
return await generate();
|
|
@@ -5640,6 +5923,34 @@ function hasRecognizableTestStatusSignal(input) {
|
|
|
5640
5923
|
const analysis = analyzeTestStatus(input);
|
|
5641
5924
|
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
5925
|
}
|
|
5926
|
+
function shouldUseCompactTestStatusBypass(args) {
|
|
5927
|
+
if (args.request.policyName !== "test-status") {
|
|
5928
|
+
return false;
|
|
5929
|
+
}
|
|
5930
|
+
if (args.request.detail && args.request.detail !== "standard") {
|
|
5931
|
+
return false;
|
|
5932
|
+
}
|
|
5933
|
+
if (args.request.goal === "diagnose" && args.request.format === "json") {
|
|
5934
|
+
return false;
|
|
5935
|
+
}
|
|
5936
|
+
if (args.request.testStatusContext?.resolvedTests?.length || args.request.testStatusContext?.remainingTests?.length || args.request.testStatusContext?.remainingSubsetAvailable || args.request.testStatusContext?.remainingMode && args.request.testStatusContext.remainingMode !== "none") {
|
|
5937
|
+
return false;
|
|
5938
|
+
}
|
|
5939
|
+
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;
|
|
5940
|
+
}
|
|
5941
|
+
function sanitizeProviderFailureReason(reason) {
|
|
5942
|
+
const normalized = reason.trim();
|
|
5943
|
+
const httpStatus = normalized.match(/\bHTTP\s+(\d{3})\b/i)?.[1];
|
|
5944
|
+
if (httpStatus) {
|
|
5945
|
+
return `provider follow-up unavailable (HTTP ${httpStatus})`;
|
|
5946
|
+
}
|
|
5947
|
+
if (/unterminated string|invalid json|unexpected token|json at position|schema|zod|parse/i.test(
|
|
5948
|
+
normalized
|
|
5949
|
+
)) {
|
|
5950
|
+
return "provider follow-up returned unusable structured output";
|
|
5951
|
+
}
|
|
5952
|
+
return "provider follow-up failed";
|
|
5953
|
+
}
|
|
5643
5954
|
function renderTestStatusDecisionOutput(args) {
|
|
5644
5955
|
if (args.request.goal === "diagnose" && args.request.format === "json") {
|
|
5645
5956
|
return JSON.stringify(
|
|
@@ -5661,6 +5972,7 @@ function renderTestStatusDecisionOutput(args) {
|
|
|
5661
5972
|
return args.decision.standardText;
|
|
5662
5973
|
}
|
|
5663
5974
|
function buildTestStatusProviderFailureDecision(args) {
|
|
5975
|
+
const sanitizedReason = sanitizeProviderFailureReason(args.reason);
|
|
5664
5976
|
const concreteReadTarget = args.baseDecision.contract.read_targets.find(
|
|
5665
5977
|
(target) => Boolean(target.file)
|
|
5666
5978
|
);
|
|
@@ -5673,6 +5985,7 @@ function buildTestStatusProviderFailureDecision(args) {
|
|
|
5673
5985
|
analysis: args.analysis,
|
|
5674
5986
|
resolvedTests: args.baseDecision.contract.resolved_tests,
|
|
5675
5987
|
remainingTests: args.baseDecision.contract.remaining_tests,
|
|
5988
|
+
remainingMode: args.request.testStatusContext?.remainingMode,
|
|
5676
5989
|
contractOverrides: {
|
|
5677
5990
|
...args.baseDecision.contract,
|
|
5678
5991
|
diagnosis_complete: false,
|
|
@@ -5688,7 +6001,9 @@ function buildTestStatusProviderFailureDecision(args) {
|
|
|
5688
6001
|
next_best_action: {
|
|
5689
6002
|
code: "read_source_for_bucket",
|
|
5690
6003
|
bucket_index: args.baseDecision.contract.dominant_blocker_bucket_index ?? concreteReadTarget.bucket_index,
|
|
5691
|
-
note:
|
|
6004
|
+
note: `${sanitizedReason[0].toUpperCase()}${sanitizedReason.slice(
|
|
6005
|
+
1
|
|
6006
|
+
)}. The heuristic anchor is concrete enough to inspect source for the current bucket before reading raw traceback.`
|
|
5692
6007
|
}
|
|
5693
6008
|
}
|
|
5694
6009
|
});
|
|
@@ -5699,6 +6014,7 @@ function buildTestStatusProviderFailureDecision(args) {
|
|
|
5699
6014
|
analysis: args.analysis,
|
|
5700
6015
|
resolvedTests: args.baseDecision.contract.resolved_tests,
|
|
5701
6016
|
remainingTests: args.baseDecision.contract.remaining_tests,
|
|
6017
|
+
remainingMode: args.request.testStatusContext?.remainingMode,
|
|
5702
6018
|
contractOverrides: {
|
|
5703
6019
|
...args.baseDecision.contract,
|
|
5704
6020
|
diagnosis_complete: false,
|
|
@@ -5714,7 +6030,11 @@ function buildTestStatusProviderFailureDecision(args) {
|
|
|
5714
6030
|
next_best_action: {
|
|
5715
6031
|
code: shouldZoomFirst ? "insufficient_signal" : "read_raw_for_exact_traceback",
|
|
5716
6032
|
bucket_index: args.baseDecision.contract.dominant_blocker_bucket_index ?? args.baseDecision.contract.main_buckets[0]?.bucket_index ?? null,
|
|
5717
|
-
note: shouldZoomFirst ?
|
|
6033
|
+
note: shouldZoomFirst ? `${sanitizedReason[0].toUpperCase()}${sanitizedReason.slice(
|
|
6034
|
+
1
|
|
6035
|
+
)}. Use one deeper sift pass on the same cached output before reading raw traceback lines.` : `${sanitizedReason[0].toUpperCase()}${sanitizedReason.slice(
|
|
6036
|
+
1
|
|
6037
|
+
)}. Read raw traceback only if exact stack lines are still needed.`
|
|
5718
6038
|
}
|
|
5719
6039
|
}
|
|
5720
6040
|
});
|
|
@@ -5735,23 +6055,28 @@ async function runSiftCore(request, recorder) {
|
|
|
5735
6055
|
const provider = createProvider(request.config);
|
|
5736
6056
|
const hasTestStatusSignal = request.policyName === "test-status" && hasRecognizableTestStatusSignal(heuristicInput);
|
|
5737
6057
|
const testStatusAnalysis = hasTestStatusSignal ? analyzeTestStatus(heuristicInput) : null;
|
|
5738
|
-
const
|
|
6058
|
+
const useCompactTestStatusOutput = hasTestStatusSignal && testStatusAnalysis ? shouldUseCompactTestStatusBypass({
|
|
6059
|
+
request,
|
|
6060
|
+
analysis: testStatusAnalysis
|
|
6061
|
+
}) : false;
|
|
6062
|
+
const testStatusDecision = hasTestStatusSignal && testStatusAnalysis && !useCompactTestStatusOutput ? buildTestStatusDiagnoseContract({
|
|
5739
6063
|
input: heuristicInput,
|
|
5740
6064
|
analysis: testStatusAnalysis,
|
|
5741
6065
|
resolvedTests: request.testStatusContext?.resolvedTests,
|
|
5742
|
-
remainingTests: request.testStatusContext?.remainingTests
|
|
6066
|
+
remainingTests: request.testStatusContext?.remainingTests,
|
|
6067
|
+
remainingMode: request.testStatusContext?.remainingMode
|
|
5743
6068
|
}) : null;
|
|
5744
6069
|
const testStatusHeuristicOutput = testStatusDecision ? renderTestStatusDecisionOutput({
|
|
5745
6070
|
request,
|
|
5746
6071
|
decision: testStatusDecision
|
|
5747
|
-
}) : null;
|
|
6072
|
+
}) : useCompactTestStatusOutput ? applyHeuristicPolicy("test-status", heuristicInput, "standard") : null;
|
|
5748
6073
|
if (request.config.runtime.verbose) {
|
|
5749
6074
|
process.stderr.write(
|
|
5750
6075
|
`${pc.dim("sift")} provider=${provider.name} model=${request.config.provider.model} base_url=${request.config.provider.baseUrl} input_chars=${prepared.meta.finalLength}
|
|
5751
6076
|
`
|
|
5752
6077
|
);
|
|
5753
6078
|
}
|
|
5754
|
-
const heuristicOutput = request.policyName === "test-status" ? testStatusDecision?.contract.diagnosis_complete ? testStatusHeuristicOutput : null : applyHeuristicPolicy(request.policyName, heuristicInput, request.detail);
|
|
6079
|
+
const heuristicOutput = request.policyName === "test-status" ? useCompactTestStatusOutput ? testStatusHeuristicOutput : testStatusDecision?.contract.diagnosis_complete ? testStatusHeuristicOutput : null : applyHeuristicPolicy(request.policyName, heuristicInput, request.detail);
|
|
5755
6080
|
if (heuristicOutput) {
|
|
5756
6081
|
if (request.config.runtime.verbose) {
|
|
5757
6082
|
process.stderr.write(`${pc.dim("sift")} heuristic=${request.policyName}
|
|
@@ -5875,6 +6200,7 @@ async function runSiftCore(request, recorder) {
|
|
|
5875
6200
|
analysis: testStatusAnalysis,
|
|
5876
6201
|
resolvedTests: request.testStatusContext?.resolvedTests,
|
|
5877
6202
|
remainingTests: request.testStatusContext?.remainingTests,
|
|
6203
|
+
remainingMode: request.testStatusContext?.remainingMode,
|
|
5878
6204
|
providerBucketSupplements: supplement.bucket_supplements,
|
|
5879
6205
|
contractOverrides: {
|
|
5880
6206
|
diagnosis_complete: supplement.diagnosis_complete,
|
|
@@ -6120,6 +6446,7 @@ var failureBucketTypeSchema = z2.enum([
|
|
|
6120
6446
|
"import_dependency_failure",
|
|
6121
6447
|
"collection_failure",
|
|
6122
6448
|
"assertion_failure",
|
|
6449
|
+
"golden_output_drift",
|
|
6123
6450
|
"runtime_failure",
|
|
6124
6451
|
"interrupted_run",
|
|
6125
6452
|
"no_tests_collected",
|
|
@@ -6160,7 +6487,19 @@ var cachedPytestStateSchema = z2.object({
|
|
|
6160
6487
|
failingNodeIds: z2.array(z2.string()),
|
|
6161
6488
|
remainingNodeIds: z2.array(z2.string()).optional()
|
|
6162
6489
|
}).optional();
|
|
6163
|
-
var
|
|
6490
|
+
var testRunnerSchema = z2.enum(["pytest", "vitest", "jest", "unknown"]);
|
|
6491
|
+
var cachedRunnerSubsetSchema = z2.object({
|
|
6492
|
+
available: z2.boolean(),
|
|
6493
|
+
strategy: z2.enum(["pytest-node-ids", "none"]),
|
|
6494
|
+
baseArgv: z2.array(z2.string()).min(1).optional()
|
|
6495
|
+
});
|
|
6496
|
+
var cachedRunnerStateSchema = z2.object({
|
|
6497
|
+
name: testRunnerSchema,
|
|
6498
|
+
failingTargets: z2.array(z2.string()),
|
|
6499
|
+
baselineCommand: cachedCommandSchema,
|
|
6500
|
+
subset: cachedRunnerSubsetSchema
|
|
6501
|
+
});
|
|
6502
|
+
var cachedRunV1Schema = z2.object({
|
|
6164
6503
|
version: z2.literal(1),
|
|
6165
6504
|
timestamp: z2.string(),
|
|
6166
6505
|
presetName: z2.literal("test-status"),
|
|
@@ -6178,6 +6517,25 @@ var cachedRunSchema = z2.object({
|
|
|
6178
6517
|
analysis: cachedAnalysisSchema,
|
|
6179
6518
|
pytest: cachedPytestStateSchema
|
|
6180
6519
|
});
|
|
6520
|
+
var cachedRunV2Schema = z2.object({
|
|
6521
|
+
version: z2.literal(2),
|
|
6522
|
+
timestamp: z2.string(),
|
|
6523
|
+
presetName: z2.literal("test-status"),
|
|
6524
|
+
cwd: z2.string(),
|
|
6525
|
+
commandKey: z2.string(),
|
|
6526
|
+
commandPreview: z2.string(),
|
|
6527
|
+
command: cachedCommandSchema,
|
|
6528
|
+
detail: detailSchema,
|
|
6529
|
+
exitCode: z2.number().int(),
|
|
6530
|
+
rawOutput: z2.string(),
|
|
6531
|
+
capture: z2.object({
|
|
6532
|
+
originalChars: countSchema,
|
|
6533
|
+
truncatedApplied: z2.boolean()
|
|
6534
|
+
}),
|
|
6535
|
+
analysis: cachedAnalysisSchema,
|
|
6536
|
+
runner: cachedRunnerStateSchema
|
|
6537
|
+
});
|
|
6538
|
+
var cachedRunSchema = z2.discriminatedUnion("version", [cachedRunV1Schema, cachedRunV2Schema]);
|
|
6181
6539
|
var MissingCachedTestStatusRunError = class extends Error {
|
|
6182
6540
|
constructor() {
|
|
6183
6541
|
super(
|
|
@@ -6226,6 +6584,37 @@ function isPytestExecutable(value) {
|
|
|
6226
6584
|
function isPythonExecutable(value) {
|
|
6227
6585
|
return basenameMatches(value, /^python(?:\d+(?:\.\d+)*)?(?:\.exe)?$/i);
|
|
6228
6586
|
}
|
|
6587
|
+
function detectRunnerFromCommand(command) {
|
|
6588
|
+
if (!command) {
|
|
6589
|
+
return "unknown";
|
|
6590
|
+
}
|
|
6591
|
+
if (command.mode === "argv") {
|
|
6592
|
+
const [first, second, third] = command.argv;
|
|
6593
|
+
if (first && isPytestExecutable(first)) {
|
|
6594
|
+
return "pytest";
|
|
6595
|
+
}
|
|
6596
|
+
if (first && isPythonExecutable(first) && second === "-m" && third === "pytest") {
|
|
6597
|
+
return "pytest";
|
|
6598
|
+
}
|
|
6599
|
+
if (first && basenameMatches(first, /^vitest(?:\.exe)?$/i)) {
|
|
6600
|
+
return "vitest";
|
|
6601
|
+
}
|
|
6602
|
+
if (first && basenameMatches(first, /^jest(?:\.exe)?$/i)) {
|
|
6603
|
+
return "jest";
|
|
6604
|
+
}
|
|
6605
|
+
return "unknown";
|
|
6606
|
+
}
|
|
6607
|
+
if (/\bpython(?:\d+(?:\.\d+)*)?\s+-m\s+pytest\b|\bpytest\b/i.test(command.shellCommand)) {
|
|
6608
|
+
return "pytest";
|
|
6609
|
+
}
|
|
6610
|
+
if (/\bvitest\b/i.test(command.shellCommand)) {
|
|
6611
|
+
return "vitest";
|
|
6612
|
+
}
|
|
6613
|
+
if (/\bjest\b/i.test(command.shellCommand)) {
|
|
6614
|
+
return "jest";
|
|
6615
|
+
}
|
|
6616
|
+
return "unknown";
|
|
6617
|
+
}
|
|
6229
6618
|
var shortPytestOptionsWithValue = /* @__PURE__ */ new Set([
|
|
6230
6619
|
"-c",
|
|
6231
6620
|
"-k",
|
|
@@ -6320,26 +6709,52 @@ function buildCachedCommand(args) {
|
|
|
6320
6709
|
}
|
|
6321
6710
|
return void 0;
|
|
6322
6711
|
}
|
|
6323
|
-
function
|
|
6712
|
+
function buildFailingTargets(analysis) {
|
|
6713
|
+
const runner = analysis.runner;
|
|
6324
6714
|
const values = [];
|
|
6325
6715
|
for (const value of [...analysis.visibleErrorLabels, ...analysis.visibleFailedLabels]) {
|
|
6326
|
-
|
|
6327
|
-
|
|
6716
|
+
const normalized = normalizeFailingTarget(value, runner);
|
|
6717
|
+
if (normalized.length > 0 && !values.includes(normalized)) {
|
|
6718
|
+
values.push(normalized);
|
|
6328
6719
|
}
|
|
6329
6720
|
}
|
|
6330
6721
|
return values;
|
|
6331
6722
|
}
|
|
6332
|
-
function
|
|
6723
|
+
function buildCachedRunnerState(args) {
|
|
6333
6724
|
const baseArgv = args.command?.mode === "argv" && isSubsetCapablePytestArgv(args.command.argv) ? [...args.command.argv] : void 0;
|
|
6725
|
+
const runnerName = args.analysis.runner !== "unknown" ? args.analysis.runner : detectRunnerFromCommand(args.command);
|
|
6334
6726
|
return {
|
|
6335
|
-
|
|
6336
|
-
|
|
6337
|
-
|
|
6338
|
-
|
|
6727
|
+
name: runnerName,
|
|
6728
|
+
failingTargets: buildFailingTargets(args.analysis),
|
|
6729
|
+
baselineCommand: args.command,
|
|
6730
|
+
subset: {
|
|
6731
|
+
available: runnerName === "pytest" && Boolean(baseArgv),
|
|
6732
|
+
strategy: runnerName === "pytest" && baseArgv ? "pytest-node-ids" : "none",
|
|
6733
|
+
...runnerName === "pytest" && baseArgv ? { baseArgv } : {}
|
|
6734
|
+
}
|
|
6339
6735
|
};
|
|
6340
6736
|
}
|
|
6737
|
+
function normalizeCwd(value) {
|
|
6738
|
+
return path2.resolve(value).replace(/\\/g, "/");
|
|
6739
|
+
}
|
|
6740
|
+
function buildTestStatusBaselineIdentity(args) {
|
|
6741
|
+
const cwd = normalizeCwd(args.cwd);
|
|
6742
|
+
const command = args.command ?? buildCachedCommand({
|
|
6743
|
+
shellCommand: args.shellCommand,
|
|
6744
|
+
command: args.shellCommand ? void 0 : args.commandPreview?.split(" ")
|
|
6745
|
+
});
|
|
6746
|
+
const mode = command?.mode ?? (args.shellCommand ? "shell" : "argv");
|
|
6747
|
+
const normalizedCommand = command?.mode === "argv" ? command.argv.join("") : command?.mode === "shell" ? command.shellCommand.trim().replace(/\s+/g, " ") : (args.commandPreview ?? "").trim().replace(/\s+/g, " ");
|
|
6748
|
+
return [cwd, args.runner, mode, normalizedCommand].join("");
|
|
6749
|
+
}
|
|
6341
6750
|
function buildTestStatusCommandKey(args) {
|
|
6342
|
-
return
|
|
6751
|
+
return buildTestStatusBaselineIdentity({
|
|
6752
|
+
cwd: args.cwd ?? process.cwd(),
|
|
6753
|
+
runner: args.runner ?? "unknown",
|
|
6754
|
+
command: args.command,
|
|
6755
|
+
commandPreview: args.commandPreview,
|
|
6756
|
+
shellCommand: args.shellCommand
|
|
6757
|
+
});
|
|
6343
6758
|
}
|
|
6344
6759
|
function snapshotTestStatusAnalysis(analysis) {
|
|
6345
6760
|
return {
|
|
@@ -6365,13 +6780,22 @@ function createCachedTestStatusRun(args) {
|
|
|
6365
6780
|
command: args.command,
|
|
6366
6781
|
shellCommand: args.shellCommand
|
|
6367
6782
|
});
|
|
6783
|
+
const runnerName = args.analysis.runner !== "unknown" ? args.analysis.runner : detectRunnerFromCommand(command);
|
|
6784
|
+
const commandPreview = args.commandPreview ?? args.shellCommand ?? (args.command ?? []).join(" ");
|
|
6785
|
+
const commandKey = args.commandKey ?? buildTestStatusBaselineIdentity({
|
|
6786
|
+
cwd: args.cwd,
|
|
6787
|
+
runner: runnerName,
|
|
6788
|
+
command,
|
|
6789
|
+
commandPreview,
|
|
6790
|
+
shellCommand: args.shellCommand
|
|
6791
|
+
});
|
|
6368
6792
|
return {
|
|
6369
|
-
version:
|
|
6793
|
+
version: 2,
|
|
6370
6794
|
timestamp: args.timestamp ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
6371
6795
|
presetName: "test-status",
|
|
6372
6796
|
cwd: args.cwd,
|
|
6373
|
-
commandKey
|
|
6374
|
-
commandPreview
|
|
6797
|
+
commandKey,
|
|
6798
|
+
commandPreview,
|
|
6375
6799
|
command,
|
|
6376
6800
|
detail: args.detail,
|
|
6377
6801
|
exitCode: args.exitCode,
|
|
@@ -6381,13 +6805,61 @@ function createCachedTestStatusRun(args) {
|
|
|
6381
6805
|
truncatedApplied: args.truncatedApplied
|
|
6382
6806
|
},
|
|
6383
6807
|
analysis: snapshotTestStatusAnalysis(args.analysis),
|
|
6384
|
-
|
|
6808
|
+
runner: buildCachedRunnerState({
|
|
6385
6809
|
command,
|
|
6386
|
-
analysis: args.analysis
|
|
6387
|
-
remainingNodeIds: args.remainingNodeIds
|
|
6810
|
+
analysis: args.analysis
|
|
6388
6811
|
})
|
|
6389
6812
|
};
|
|
6390
6813
|
}
|
|
6814
|
+
function migrateCachedTestStatusRun(state) {
|
|
6815
|
+
if (state.version === 2) {
|
|
6816
|
+
return state;
|
|
6817
|
+
}
|
|
6818
|
+
const runnerFromOutput = detectTestRunner(state.rawOutput);
|
|
6819
|
+
const runner = runnerFromOutput !== "unknown" ? runnerFromOutput : detectRunnerFromCommand(state.command);
|
|
6820
|
+
const storedCommand = state.command;
|
|
6821
|
+
const fallbackBaseArgv = !storedCommand && state.pytest?.baseArgv ? {
|
|
6822
|
+
mode: "argv",
|
|
6823
|
+
argv: [...state.pytest.baseArgv]
|
|
6824
|
+
} : void 0;
|
|
6825
|
+
const baselineCommand = storedCommand ?? fallbackBaseArgv;
|
|
6826
|
+
const commandPreview = state.commandPreview ?? (baselineCommand?.mode === "argv" ? baselineCommand.argv.join(" ") : baselineCommand?.mode === "shell" ? baselineCommand.shellCommand : "");
|
|
6827
|
+
const commandKey = buildTestStatusBaselineIdentity({
|
|
6828
|
+
cwd: state.cwd,
|
|
6829
|
+
runner,
|
|
6830
|
+
command: baselineCommand,
|
|
6831
|
+
commandPreview
|
|
6832
|
+
});
|
|
6833
|
+
return {
|
|
6834
|
+
version: 2,
|
|
6835
|
+
timestamp: state.timestamp,
|
|
6836
|
+
presetName: state.presetName,
|
|
6837
|
+
cwd: state.cwd,
|
|
6838
|
+
commandKey,
|
|
6839
|
+
commandPreview,
|
|
6840
|
+
command: state.command,
|
|
6841
|
+
detail: state.detail,
|
|
6842
|
+
exitCode: state.exitCode,
|
|
6843
|
+
rawOutput: state.rawOutput,
|
|
6844
|
+
capture: state.capture,
|
|
6845
|
+
analysis: state.analysis,
|
|
6846
|
+
runner: {
|
|
6847
|
+
name: runner,
|
|
6848
|
+
failingTargets: [...new Set((state.pytest?.failingNodeIds ?? []).map(
|
|
6849
|
+
(target) => normalizeFailingTarget(target, runner)
|
|
6850
|
+
))],
|
|
6851
|
+
baselineCommand,
|
|
6852
|
+
subset: {
|
|
6853
|
+
available: runner === "pytest" && Boolean(state.pytest?.baseArgv),
|
|
6854
|
+
strategy: runner === "pytest" && state.pytest?.baseArgv ? "pytest-node-ids" : "none",
|
|
6855
|
+
...runner === "pytest" && state.pytest?.baseArgv ? {
|
|
6856
|
+
baseArgv: [...state.pytest.baseArgv]
|
|
6857
|
+
} : {}
|
|
6858
|
+
}
|
|
6859
|
+
},
|
|
6860
|
+
...fallbackBaseArgv ? { runnerMigrationFallbackUsed: true } : {}
|
|
6861
|
+
};
|
|
6862
|
+
}
|
|
6391
6863
|
function readCachedTestStatusRun(statePath = getDefaultTestStatusStatePath()) {
|
|
6392
6864
|
let raw = "";
|
|
6393
6865
|
try {
|
|
@@ -6399,7 +6871,7 @@ function readCachedTestStatusRun(statePath = getDefaultTestStatusStatePath()) {
|
|
|
6399
6871
|
throw new InvalidCachedTestStatusRunError();
|
|
6400
6872
|
}
|
|
6401
6873
|
try {
|
|
6402
|
-
return cachedRunSchema.parse(JSON.parse(raw));
|
|
6874
|
+
return migrateCachedTestStatusRun(cachedRunSchema.parse(JSON.parse(raw)));
|
|
6403
6875
|
} catch {
|
|
6404
6876
|
throw new InvalidCachedTestStatusRunError();
|
|
6405
6877
|
}
|
|
@@ -6419,7 +6891,7 @@ function writeCachedTestStatusRun(state, statePath = getDefaultTestStatusStatePa
|
|
|
6419
6891
|
`, "utf8");
|
|
6420
6892
|
}
|
|
6421
6893
|
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) {
|
|
6894
|
+
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") {
|
|
6423
6895
|
return {
|
|
6424
6896
|
comparable: false,
|
|
6425
6897
|
resolved: [],
|
|
@@ -6427,16 +6899,8 @@ function buildTargetDelta(args) {
|
|
|
6427
6899
|
introduced: []
|
|
6428
6900
|
};
|
|
6429
6901
|
}
|
|
6430
|
-
|
|
6431
|
-
|
|
6432
|
-
comparable: false,
|
|
6433
|
-
resolved: [],
|
|
6434
|
-
remaining: [],
|
|
6435
|
-
introduced: []
|
|
6436
|
-
};
|
|
6437
|
-
}
|
|
6438
|
-
const previousTargets = args.previous.pytest.failingNodeIds;
|
|
6439
|
-
const currentTargets = args.current.pytest.failingNodeIds;
|
|
6902
|
+
const previousTargets = args.previous.runner.failingTargets;
|
|
6903
|
+
const currentTargets = args.current.runner.failingTargets;
|
|
6440
6904
|
const currentTargetSet = new Set(currentTargets);
|
|
6441
6905
|
const previousTargetSet = new Set(previousTargets);
|
|
6442
6906
|
return {
|
|
@@ -6449,6 +6913,9 @@ function buildTargetDelta(args) {
|
|
|
6449
6913
|
function diffTestStatusTargets(args) {
|
|
6450
6914
|
return buildTargetDelta(args);
|
|
6451
6915
|
}
|
|
6916
|
+
function isRemainingSubsetAvailable(state) {
|
|
6917
|
+
return state.runner.name === "pytest" && state.runner.subset.available;
|
|
6918
|
+
}
|
|
6452
6919
|
function diffTestStatusRuns(args) {
|
|
6453
6920
|
const targetDelta = buildTargetDelta(args);
|
|
6454
6921
|
const previousBuckets = new Map(
|
|
@@ -6458,21 +6925,45 @@ function diffTestStatusRuns(args) {
|
|
|
6458
6925
|
args.current.analysis.buckets.map((bucket) => [buildBucketSignature(bucket), bucket])
|
|
6459
6926
|
);
|
|
6460
6927
|
const lines = [];
|
|
6461
|
-
|
|
6462
|
-
|
|
6463
|
-
|
|
6464
|
-
|
|
6465
|
-
|
|
6466
|
-
|
|
6467
|
-
|
|
6468
|
-
|
|
6469
|
-
)
|
|
6470
|
-
|
|
6471
|
-
|
|
6928
|
+
const resolvedSummary = buildTestTargetSummary(targetDelta.resolved);
|
|
6929
|
+
const remainingSummary = buildTestTargetSummary(targetDelta.remaining);
|
|
6930
|
+
const introducedSummary = buildTestTargetSummary(targetDelta.introduced);
|
|
6931
|
+
const pushTargetLine = (args2) => {
|
|
6932
|
+
if (args2.summary.count === 0) {
|
|
6933
|
+
return;
|
|
6934
|
+
}
|
|
6935
|
+
const summaryText = describeTargetSummary(args2.summary);
|
|
6936
|
+
if (summaryText) {
|
|
6937
|
+
lines.push(
|
|
6938
|
+
`- ${args2.kind}: ${formatCount3(args2.summary.count, args2.countLabel, `${args2.countLabel}s`)} ${args2.verb} ${summaryText}.`
|
|
6939
|
+
);
|
|
6940
|
+
return;
|
|
6941
|
+
}
|
|
6472
6942
|
lines.push(
|
|
6473
|
-
`-
|
|
6943
|
+
`- ${args2.kind}: ${formatCount3(args2.summary.count, args2.countLabel, `${args2.countLabel}s`)} ${args2.verb}${appendPreview(args2.fallbackValues)}.`
|
|
6474
6944
|
);
|
|
6475
|
-
}
|
|
6945
|
+
};
|
|
6946
|
+
pushTargetLine({
|
|
6947
|
+
kind: "Resolved",
|
|
6948
|
+
summary: resolvedSummary,
|
|
6949
|
+
countLabel: "failing target",
|
|
6950
|
+
fallbackValues: targetDelta.resolved,
|
|
6951
|
+
verb: "no longer appear"
|
|
6952
|
+
});
|
|
6953
|
+
pushTargetLine({
|
|
6954
|
+
kind: "Remaining",
|
|
6955
|
+
summary: remainingSummary,
|
|
6956
|
+
countLabel: "failing target",
|
|
6957
|
+
fallbackValues: targetDelta.remaining,
|
|
6958
|
+
verb: "still appear"
|
|
6959
|
+
});
|
|
6960
|
+
pushTargetLine({
|
|
6961
|
+
kind: "New",
|
|
6962
|
+
summary: introducedSummary,
|
|
6963
|
+
countLabel: "failing target",
|
|
6964
|
+
fallbackValues: targetDelta.introduced,
|
|
6965
|
+
verb: "appeared"
|
|
6966
|
+
});
|
|
6476
6967
|
for (const bucket of args.current.analysis.buckets) {
|
|
6477
6968
|
const signature = buildBucketSignature(bucket);
|
|
6478
6969
|
const previous = previousBuckets.get(signature);
|
|
@@ -6500,8 +6991,7 @@ function diffTestStatusRuns(args) {
|
|
|
6500
6991
|
}
|
|
6501
6992
|
}
|
|
6502
6993
|
return {
|
|
6503
|
-
lines: lines.slice(0, 4)
|
|
6504
|
-
remainingNodeIds: targetDelta.comparable ? targetDelta.remaining : void 0
|
|
6994
|
+
lines: lines.slice(0, 4)
|
|
6505
6995
|
};
|
|
6506
6996
|
}
|
|
6507
6997
|
|
|
@@ -6634,8 +7124,9 @@ async function runTestStatusWatch(request, cycles) {
|
|
|
6634
7124
|
testStatusContext: {
|
|
6635
7125
|
...request.testStatusContext,
|
|
6636
7126
|
resolvedTests: targetDelta?.resolved ?? request.testStatusContext?.resolvedTests,
|
|
6637
|
-
remainingTests: targetDelta?.remaining ?? currentRun.
|
|
6638
|
-
remainingSubsetAvailable: request.testStatusContext?.remainingSubsetAvailable ?? (
|
|
7127
|
+
remainingTests: targetDelta?.remaining ?? currentRun.runner.failingTargets ?? request.testStatusContext?.remainingTests,
|
|
7128
|
+
remainingSubsetAvailable: request.testStatusContext?.remainingSubsetAvailable ?? (isRemainingSubsetAvailable(currentRun) && currentRun.runner.failingTargets.length > 0),
|
|
7129
|
+
remainingMode: request.testStatusContext?.remainingMode ?? "none"
|
|
6639
7130
|
}
|
|
6640
7131
|
});
|
|
6641
7132
|
if (request.goal === "diagnose" && request.format === "json") {
|
|
@@ -6782,8 +7273,10 @@ async function runExec(request) {
|
|
|
6782
7273
|
const shellPath = process.env.SHELL || "/bin/bash";
|
|
6783
7274
|
const commandPreview = buildCommandPreview(request);
|
|
6784
7275
|
const commandCwd = request.cwd ?? process.cwd();
|
|
6785
|
-
const
|
|
6786
|
-
const
|
|
7276
|
+
const isTestStatusPreset = request.presetName === "test-status";
|
|
7277
|
+
const readCachedBaseline = isTestStatusPreset && (request.readCachedBaseline ?? true);
|
|
7278
|
+
const writeCachedBaselineRequested = isTestStatusPreset && (request.writeCachedBaseline ?? (request.skipCacheWrite ? false : true));
|
|
7279
|
+
const previousCachedRun = readCachedBaseline ? tryReadCachedTestStatusRun() : null;
|
|
6787
7280
|
if (request.config.runtime.verbose) {
|
|
6788
7281
|
process.stderr.write(
|
|
6789
7282
|
`${pc3.dim("sift")} exec mode=${hasShellCommand ? "shell" : "argv"} command=${commandPreview}
|
|
@@ -6802,6 +7295,10 @@ async function runExec(request) {
|
|
|
6802
7295
|
cwd: commandCwd,
|
|
6803
7296
|
stdio: ["inherit", "pipe", "pipe"]
|
|
6804
7297
|
});
|
|
7298
|
+
const stopChildPendingNotice = startPendingNotice(
|
|
7299
|
+
"sift waiting for child command...",
|
|
7300
|
+
Boolean(process.stderr.isTTY) && !request.quiet
|
|
7301
|
+
);
|
|
6805
7302
|
const handleChunk = (chunk) => {
|
|
6806
7303
|
const text = Buffer.isBuffer(chunk) ? chunk.toString("utf8") : String(chunk);
|
|
6807
7304
|
if (bypassed) {
|
|
@@ -6822,26 +7319,31 @@ async function runExec(request) {
|
|
|
6822
7319
|
};
|
|
6823
7320
|
child.stdout.on("data", handleChunk);
|
|
6824
7321
|
child.stderr.on("data", handleChunk);
|
|
6825
|
-
|
|
6826
|
-
|
|
6827
|
-
|
|
6828
|
-
|
|
6829
|
-
|
|
6830
|
-
|
|
6831
|
-
|
|
6832
|
-
|
|
7322
|
+
try {
|
|
7323
|
+
await new Promise((resolve, reject) => {
|
|
7324
|
+
child.on("error", (error) => {
|
|
7325
|
+
reject(error);
|
|
7326
|
+
});
|
|
7327
|
+
child.on("close", (status, signal) => {
|
|
7328
|
+
childStatus = status;
|
|
7329
|
+
childSignal = signal;
|
|
7330
|
+
resolve();
|
|
7331
|
+
});
|
|
6833
7332
|
});
|
|
6834
|
-
}
|
|
7333
|
+
} catch (error) {
|
|
6835
7334
|
if (error instanceof Error) {
|
|
6836
7335
|
throw error;
|
|
6837
7336
|
}
|
|
6838
7337
|
throw new Error("Failed to start child process.");
|
|
6839
|
-
}
|
|
7338
|
+
} finally {
|
|
7339
|
+
stopChildPendingNotice();
|
|
7340
|
+
}
|
|
6840
7341
|
const exitCode = normalizeChildExitCode(childStatus, childSignal);
|
|
6841
7342
|
const capturedOutput = capture.render();
|
|
6842
7343
|
const autoWatchDetected = !request.watch && looksLikeWatchStream(capturedOutput);
|
|
6843
7344
|
const useWatchFlow = Boolean(request.watch) || autoWatchDetected;
|
|
6844
|
-
const
|
|
7345
|
+
const shouldBuildTestStatusState = isTestStatusPreset && !useWatchFlow;
|
|
7346
|
+
const shouldWriteCachedBaseline = writeCachedBaselineRequested && !useWatchFlow;
|
|
6845
7347
|
if (request.config.runtime.verbose) {
|
|
6846
7348
|
process.stderr.write(
|
|
6847
7349
|
`${pc3.dim("sift")} child_exit=${exitCode} captured_chars=${capture.getTotalChars()} capture_truncated=${capture.wasTruncated()}
|
|
@@ -6904,10 +7406,19 @@ async function runExec(request) {
|
|
|
6904
7406
|
`);
|
|
6905
7407
|
return exitCode;
|
|
6906
7408
|
}
|
|
6907
|
-
const analysis =
|
|
6908
|
-
let currentCachedRun =
|
|
7409
|
+
const analysis = shouldBuildTestStatusState ? analyzeTestStatus(capturedOutput) : null;
|
|
7410
|
+
let currentCachedRun = shouldBuildTestStatusState && analysis ? createCachedTestStatusRun({
|
|
6909
7411
|
cwd: commandCwd,
|
|
6910
7412
|
commandKey: buildTestStatusCommandKey({
|
|
7413
|
+
cwd: commandCwd,
|
|
7414
|
+
runner: analysis.runner,
|
|
7415
|
+
command: Array.isArray(request.command) && request.command.length > 0 ? {
|
|
7416
|
+
mode: "argv",
|
|
7417
|
+
argv: [...request.command]
|
|
7418
|
+
} : request.shellCommand ? {
|
|
7419
|
+
mode: "shell",
|
|
7420
|
+
shellCommand: request.shellCommand
|
|
7421
|
+
} : void 0,
|
|
6911
7422
|
commandPreview,
|
|
6912
7423
|
shellCommand: request.shellCommand
|
|
6913
7424
|
}),
|
|
@@ -6921,31 +7432,32 @@ async function runExec(request) {
|
|
|
6921
7432
|
truncatedApplied: capture.wasTruncated(),
|
|
6922
7433
|
analysis
|
|
6923
7434
|
}) : null;
|
|
6924
|
-
const targetDelta = request.diff && !request.dryRun && previousCachedRun && currentCachedRun ? diffTestStatusTargets({
|
|
7435
|
+
const targetDelta = (request.diff || request.testStatusContext?.remainingMode === "subset_rerun" || request.testStatusContext?.remainingMode === "full_rerun_diff") && !request.dryRun && previousCachedRun && currentCachedRun ? diffTestStatusTargets({
|
|
6925
7436
|
previous: previousCachedRun,
|
|
6926
7437
|
current: currentCachedRun
|
|
6927
7438
|
}) : null;
|
|
6928
7439
|
const result = await runSiftWithStats({
|
|
6929
7440
|
...request,
|
|
6930
7441
|
stdin: capturedOutput,
|
|
6931
|
-
analysisContext: request.
|
|
7442
|
+
analysisContext: request.testStatusContext?.remainingMode && request.testStatusContext.remainingMode !== "none" && request.presetName === "test-status" ? [
|
|
6932
7443
|
request.analysisContext,
|
|
6933
7444
|
"Zoom context:",
|
|
6934
7445
|
"- This pass is remaining-only.",
|
|
6935
7446
|
"- The full-suite truth already exists from the cached full run.",
|
|
6936
7447
|
"- Do not reintroduce resolved tests into the diagnosis."
|
|
6937
7448
|
].filter((value) => Boolean(value)).join("\n") : request.analysisContext,
|
|
6938
|
-
testStatusContext:
|
|
7449
|
+
testStatusContext: shouldBuildTestStatusState && analysis ? {
|
|
6939
7450
|
...request.testStatusContext,
|
|
6940
7451
|
resolvedTests: targetDelta?.resolved ?? request.testStatusContext?.resolvedTests,
|
|
6941
|
-
remainingTests: targetDelta?.remaining ?? currentCachedRun?.
|
|
7452
|
+
remainingTests: targetDelta?.remaining ?? currentCachedRun?.runner.failingTargets ?? request.testStatusContext?.remainingTests,
|
|
6942
7453
|
remainingSubsetAvailable: request.testStatusContext?.remainingSubsetAvailable ?? Boolean(
|
|
6943
|
-
currentCachedRun
|
|
6944
|
-
)
|
|
7454
|
+
currentCachedRun && isRemainingSubsetAvailable(currentCachedRun) && (targetDelta?.remaining ?? currentCachedRun?.runner.failingTargets ?? []).length > 0
|
|
7455
|
+
),
|
|
7456
|
+
remainingMode: request.testStatusContext?.remainingMode ?? "none"
|
|
6945
7457
|
} : request.testStatusContext
|
|
6946
7458
|
});
|
|
6947
7459
|
let output = result.output;
|
|
6948
|
-
if (
|
|
7460
|
+
if (shouldBuildTestStatusState) {
|
|
6949
7461
|
if (isInsufficientSignalOutput(output)) {
|
|
6950
7462
|
output = buildInsufficientSignalOutput({
|
|
6951
7463
|
presetName: request.presetName,
|
|
@@ -6960,26 +7472,12 @@ async function runExec(request) {
|
|
|
6960
7472
|
previous: previousCachedRun,
|
|
6961
7473
|
current: currentCachedRun
|
|
6962
7474
|
});
|
|
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
7475
|
if (delta.lines.length > 0) {
|
|
6978
7476
|
output = `${delta.lines.join("\n")}
|
|
6979
7477
|
${output}`;
|
|
6980
7478
|
}
|
|
6981
7479
|
}
|
|
6982
|
-
if (currentCachedRun) {
|
|
7480
|
+
if (currentCachedRun && shouldWriteCachedBaseline) {
|
|
6983
7481
|
try {
|
|
6984
7482
|
writeCachedTestStatusRun(currentCachedRun);
|
|
6985
7483
|
} catch (error) {
|
|
@@ -7365,5 +7863,6 @@ export {
|
|
|
7365
7863
|
resolveConfig,
|
|
7366
7864
|
runExec,
|
|
7367
7865
|
runSift,
|
|
7368
|
-
runSiftWithStats
|
|
7866
|
+
runSiftWithStats,
|
|
7867
|
+
startPendingNotice
|
|
7369
7868
|
};
|