@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/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
- 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","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[]}';
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 unique(values) {
558
+ function unique2(values) {
440
559
  return [...new Set(values)];
441
560
  }
442
- function normalizeTestId(value) {
561
+ function normalizeTestId2(value) {
443
562
  return value.replace(/\\/g, "/").trim();
444
563
  }
445
- function extractTestFamilyPrefix(value) {
446
- const normalized = normalizeTestId(value);
447
- const testsMatch = normalized.match(/^(tests\/[^/]+\/)/);
448
- if (testsMatch) {
449
- return testsMatch[1];
564
+ function normalizePathCandidate(value) {
565
+ if (!value) {
566
+ return null;
450
567
  }
451
- const filePart = normalized.split("::")[0]?.trim() ?? "";
452
- if (!filePart.includes("/")) {
453
- return "other";
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
- const segments = filePart.replace(/^\/+/, "").split("/").filter(Boolean);
456
- if (segments.length === 0) {
457
- return "other";
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
- return `${segments[0]}/`;
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 buildTestTargetSummary(values) {
462
- const counts = /* @__PURE__ */ new Map();
463
- for (const value of values) {
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 families = [...counts.entries()].map(([prefix, count]) => ({
468
- prefix,
469
- count
470
- })).sort((left, right) => {
471
- if (right.count !== left.count) {
472
- return right.count - left.count;
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 formatTargetSummary(summary) {
482
- if (summary.count === 0) {
483
- return "count=0";
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 families = summary.families.length > 0 ? summary.families.map((family) => `${family.prefix}${family.count}`).join(", ") : "none";
486
- return `count=${summary.count}; families=${families}`;
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: unique([...existing.entities, ...incoming.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 failed")) {
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 = unique(
1339
+ const addedPaths = unique2(
1115
1340
  [...input.matchAll(/[+-]\s+'(\/api\/[^']+)'/g)].map((match) => match[1])
1116
1341
  ).length;
1117
- const removedModels = unique(
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 = unique(
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 = normalizeTestId(label);
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.resolved_tests.length > 0 && contract.remaining_tests.length > 0) {
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
- const file = args.readTarget?.file ?? "";
1447
- if (file.startsWith("src/")) {
1703
+ if (hasConfigCandidate) {
1704
+ return "config";
1705
+ }
1706
+ if (hasAppCandidate) {
1448
1707
  return "app_code";
1449
1708
  }
1450
- if (file.startsWith("test/") || file.startsWith("tests/")) {
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
- lines.push(`- Likely owner: ${formatSuspectKindLabel(args.contract.primary_suspect_kind)}`);
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([...combinedBuckets, ...unknownBuckets]).slice(0, 3);
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 = unique(args.resolvedTests ?? []);
1654
- const remainingTests = unique(
1655
- args.remainingTests ?? unique([...args.analysis.visibleErrorLabels, ...args.analysis.visibleFailedLabels])
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 unique2(values) {
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 = unique2(
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 = unique2(
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 unique2([
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 = unique2(args.lines).join("\n").trim();
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 unique2([
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: unique2([...searchHintIndexes, ...fileIndexes]),
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
- unique2([
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 PROVIDER_PENDING_NOTICE_DELAY_MS = 150;
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 startProviderPendingNotice() {
5578
- if (!process.stderr.isTTY) {
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
- }, PROVIDER_PENDING_NOTICE_DELAY_MS);
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 = startProviderPendingNotice();
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: `Provider follow-up failed (${args.reason}). The heuristic anchor is concrete enough to inspect source for the current bucket before reading raw traceback.`
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 ? `Provider follow-up failed (${args.reason}). Use one deeper sift pass on the same cached output before reading raw traceback lines.` : `Provider follow-up failed (${args.reason}). Read raw traceback only if exact stack lines are still needed.`
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 testStatusDecision = hasTestStatusSignal && testStatusAnalysis ? buildTestStatusDiagnoseContract({
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 cachedRunSchema = z2.object({
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 buildFailingNodeIds(analysis) {
6712
+ function buildFailingTargets(analysis) {
6713
+ const runner = analysis.runner;
6324
6714
  const values = [];
6325
6715
  for (const value of [...analysis.visibleErrorLabels, ...analysis.visibleFailedLabels]) {
6326
- if (value.length > 0 && !values.includes(value)) {
6327
- values.push(value);
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 buildCachedPytestState(args) {
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
- subsetCapable: Boolean(baseArgv),
6336
- baseArgv,
6337
- failingNodeIds: buildFailingNodeIds(args.analysis),
6338
- remainingNodeIds: args.remainingNodeIds
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 `${args.shellCommand ? "shell" : "argv"}:${args.commandPreview}`;
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: 1,
6793
+ version: 2,
6370
6794
  timestamp: args.timestamp ?? (/* @__PURE__ */ new Date()).toISOString(),
6371
6795
  presetName: "test-status",
6372
6796
  cwd: args.cwd,
6373
- commandKey: args.commandKey,
6374
- commandPreview: args.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
- pytest: buildCachedPytestState({
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
- if (!args.previous.pytest || !args.current.pytest) {
6431
- return {
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
- if (targetDelta.resolved.length > 0) {
6462
- lines.push(
6463
- `- Resolved: ${formatCount3(targetDelta.resolved.length, "failing test/module", "failing tests/modules")} no longer appear${appendPreview(targetDelta.resolved)}.`
6464
- );
6465
- }
6466
- if (targetDelta.remaining.length > 0) {
6467
- lines.push(
6468
- `- Remaining: ${formatCount3(targetDelta.remaining.length, "failing test/module", "failing tests/modules")} still appear${appendPreview(targetDelta.remaining)}.`
6469
- );
6470
- }
6471
- if (targetDelta.introduced.length > 0) {
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
- `- New: ${formatCount3(targetDelta.introduced.length, "failing test/module", "failing tests/modules")} appeared${appendPreview(targetDelta.introduced)}.`
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.pytest?.failingNodeIds ?? request.testStatusContext?.remainingTests,
6638
- remainingSubsetAvailable: request.testStatusContext?.remainingSubsetAvailable ?? (Boolean(currentRun.pytest?.subsetCapable) && (currentRun.pytest?.failingNodeIds.length ?? 0) > 0)
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 shouldCacheTestStatusBase = request.presetName === "test-status" && !request.skipCacheWrite;
6786
- const previousCachedRun = shouldCacheTestStatusBase ? tryReadCachedTestStatusRun() : null;
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
- await new Promise((resolve, reject) => {
6826
- child.on("error", (error) => {
6827
- reject(error);
6828
- });
6829
- child.on("close", (status, signal) => {
6830
- childStatus = status;
6831
- childSignal = signal;
6832
- resolve();
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
- }).catch((error) => {
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 shouldCacheTestStatus = shouldCacheTestStatusBase && !useWatchFlow;
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 = shouldCacheTestStatus ? analyzeTestStatus(capturedOutput) : null;
6908
- let currentCachedRun = shouldCacheTestStatus && analysis ? createCachedTestStatusRun({
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.skipCacheWrite && request.presetName === "test-status" ? [
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: shouldCacheTestStatus && analysis ? {
7449
+ testStatusContext: shouldBuildTestStatusState && analysis ? {
6939
7450
  ...request.testStatusContext,
6940
7451
  resolvedTests: targetDelta?.resolved ?? request.testStatusContext?.resolvedTests,
6941
- remainingTests: targetDelta?.remaining ?? currentCachedRun?.pytest?.failingNodeIds ?? request.testStatusContext?.remainingTests,
7452
+ remainingTests: targetDelta?.remaining ?? currentCachedRun?.runner.failingTargets ?? request.testStatusContext?.remainingTests,
6942
7453
  remainingSubsetAvailable: request.testStatusContext?.remainingSubsetAvailable ?? Boolean(
6943
- currentCachedRun?.pytest?.subsetCapable && (targetDelta?.remaining ?? currentCachedRun?.pytest?.failingNodeIds ?? []).length > 0
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 (shouldCacheTestStatus) {
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
  };