@bilalimamoglu/sift 0.3.2 → 0.3.3

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/cli.js CHANGED
@@ -1876,7 +1876,7 @@ function showPreset(config, name, includeInternal = false) {
1876
1876
  }
1877
1877
 
1878
1878
  // src/core/escalate.ts
1879
- import pc3 from "picocolors";
1879
+ import pc4 from "picocolors";
1880
1880
 
1881
1881
  // src/core/insufficient.ts
1882
1882
  function isInsufficientSignalOutput(output) {
@@ -1897,8 +1897,8 @@ function buildInsufficientSignalOutput(input) {
1897
1897
  } else {
1898
1898
  hint = "Hint: the captured output did not contain a clear answer for this preset.";
1899
1899
  }
1900
- return `${INSUFFICIENT_SIGNAL_TEXT}
1901
- ${hint}`;
1900
+ const presetSuggestion = input.recognizedRunner && input.recognizedRunner !== "unknown" && input.presetName !== "test-status" ? `Hint: captured output looks like ${input.recognizedRunner} test output; try --preset test-status.` : null;
1901
+ return [INSUFFICIENT_SIGNAL_TEXT, hint, presetSuggestion].filter((value) => Boolean(value)).join("\n");
1902
1902
  }
1903
1903
 
1904
1904
  // src/core/run.ts
@@ -2124,7 +2124,7 @@ function createProvider(config) {
2124
2124
 
2125
2125
  // src/core/testStatusDecision.ts
2126
2126
  import { z as z2 } from "zod";
2127
- 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","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,"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[]}';
2127
+ 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[]}';
2128
2128
  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}}';
2129
2129
  var nextBestActionSchema = z2.object({
2130
2130
  code: z2.enum([
@@ -2166,6 +2166,15 @@ var testStatusDiagnoseContractSchema = z2.object({
2166
2166
  additional_source_read_likely_low_value: z2.boolean(),
2167
2167
  read_raw_only_if: z2.string().nullable(),
2168
2168
  decision: z2.enum(["stop", "zoom", "read_source", "read_raw"]),
2169
+ primary_suspect_kind: z2.enum([
2170
+ "test",
2171
+ "app_code",
2172
+ "config",
2173
+ "environment",
2174
+ "tooling",
2175
+ "unknown"
2176
+ ]),
2177
+ confidence_reason: z2.string().min(1),
2169
2178
  dominant_blocker_bucket_index: z2.number().int().nullable(),
2170
2179
  provider_used: z2.boolean(),
2171
2180
  provider_confidence: z2.number().min(0).max(1).nullable(),
@@ -2180,6 +2189,15 @@ var testStatusDiagnoseContractSchema = z2.object({
2180
2189
  label: z2.string(),
2181
2190
  count: z2.number().int(),
2182
2191
  root_cause: z2.string(),
2192
+ suspect_kind: z2.enum([
2193
+ "test",
2194
+ "app_code",
2195
+ "config",
2196
+ "environment",
2197
+ "tooling",
2198
+ "unknown"
2199
+ ]),
2200
+ fix_hint: z2.string().min(1),
2183
2201
  evidence: z2.array(z2.string()).max(2),
2184
2202
  bucket_confidence: z2.number(),
2185
2203
  root_cause_confidence: z2.number(),
@@ -2230,6 +2248,42 @@ function parseTestStatusProviderSupplement(input) {
2230
2248
  return testStatusProviderSupplementSchema.parse(JSON.parse(input));
2231
2249
  }
2232
2250
  var extendedBucketSpecs = [
2251
+ {
2252
+ prefix: "service unavailable:",
2253
+ type: "service_unavailable",
2254
+ label: "service unavailable",
2255
+ genericTitle: "Service unavailable failures",
2256
+ defaultCoverage: "error",
2257
+ rootCauseConfidence: 0.9,
2258
+ dominantPriority: 2,
2259
+ dominantBlocker: true,
2260
+ why: "it contains the dependency service or API path that is unavailable in the test environment",
2261
+ fix: "Restore the dependency service or test double before rerunning the full suite."
2262
+ },
2263
+ {
2264
+ prefix: "db refused:",
2265
+ type: "db_connection_failure",
2266
+ label: "database connection",
2267
+ genericTitle: "Database connection failures",
2268
+ defaultCoverage: "error",
2269
+ rootCauseConfidence: 0.9,
2270
+ dominantPriority: 2,
2271
+ dominantBlocker: true,
2272
+ why: "it contains the database host, DSN, or startup path that is refusing connections",
2273
+ fix: "Restore the test database connectivity before rerunning the full suite."
2274
+ },
2275
+ {
2276
+ prefix: "auth bypass absent:",
2277
+ type: "auth_bypass_absent",
2278
+ label: "auth bypass missing",
2279
+ genericTitle: "Auth bypass setup failures",
2280
+ defaultCoverage: "error",
2281
+ rootCauseConfidence: 0.86,
2282
+ dominantPriority: 2,
2283
+ dominantBlocker: true,
2284
+ why: "it contains the auth bypass fixture or setup path that tests expected to be active",
2285
+ fix: "Restore the test auth bypass fixture or mock before rerunning the full suite."
2286
+ },
2233
2287
  {
2234
2288
  prefix: "snapshot mismatch:",
2235
2289
  type: "snapshot_mismatch",
@@ -2414,6 +2468,16 @@ var extendedBucketSpecs = [
2414
2468
  why: "it contains the deprecated API or warning filter that is failing the test run",
2415
2469
  fix: "Update the deprecated call site or relax the warning policy only if that is intentional."
2416
2470
  },
2471
+ {
2472
+ prefix: "assertion failed:",
2473
+ type: "assertion_failure",
2474
+ label: "assertion failure",
2475
+ genericTitle: "Assertion failures",
2476
+ defaultCoverage: "failed",
2477
+ rootCauseConfidence: 0.76,
2478
+ why: "it contains the expected-versus-actual assertion that failed inside the visible test",
2479
+ fix: "Read the assertion diff or expectation and fix the code or expected value before rerunning."
2480
+ },
2417
2481
  {
2418
2482
  prefix: "xfail strict:",
2419
2483
  type: "xfail_strict_unexpected_pass",
@@ -3203,7 +3267,7 @@ function buildProviderSupplementBuckets(args) {
3203
3267
  });
3204
3268
  }
3205
3269
  function pickUnknownAnchor(args) {
3206
- const fromStatusItems = args.kind === "error" ? args.analysis.visibleErrorItems[0] : null;
3270
+ const fromStatusItems = args.kind === "error" ? args.analysis.visibleErrorItems[0] : args.analysis.visibleFailedItems[0];
3207
3271
  if (fromStatusItems) {
3208
3272
  return {
3209
3273
  label: fromStatusItems.label,
@@ -3240,12 +3304,14 @@ function buildUnknownBucket(args) {
3240
3304
  const isError = args.kind === "error";
3241
3305
  const label = isError ? "unknown setup blocker" : "unknown failure family";
3242
3306
  const reason = isError ? "unknown setup blocker: setup failures share a repeated but unclassified pattern" : "unknown failure family: failing tests share a repeated but unclassified pattern";
3307
+ const firstConcreteSignal = anchor && anchor.reason !== reason && anchor.reason !== "setup failures share a repeated but unclassified pattern" && anchor.reason !== "failing tests share a repeated but unclassified pattern" ? `First concrete signal: ${anchor.reason}` : null;
3243
3308
  return {
3244
3309
  type: "unknown_failure",
3245
3310
  headline: `${label}: ${formatCount(args.count, "visible failure")} share a repeated but unclassified pattern.`,
3246
3311
  summaryLines: [
3247
- `${label}: ${formatCount(args.count, "visible failure")} share a repeated but unclassified pattern.`
3248
- ],
3312
+ `${label}: ${formatCount(args.count, "visible failure")} share a repeated but unclassified pattern.`,
3313
+ firstConcreteSignal
3314
+ ].filter((value) => Boolean(value)),
3249
3315
  reason,
3250
3316
  count: args.count,
3251
3317
  confidence: 0.45,
@@ -3372,7 +3438,7 @@ function buildStandardAnchorText(target) {
3372
3438
  }
3373
3439
  return formatReadTargetLocation(target);
3374
3440
  }
3375
- function buildStandardFixText(args) {
3441
+ function resolveBucketFixHint(args) {
3376
3442
  if (args.bucket.hint) {
3377
3443
  return args.bucket.hint;
3378
3444
  }
@@ -3421,13 +3487,75 @@ function buildStandardFixText(args) {
3421
3487
  if (args.bucket.type === "runtime_failure") {
3422
3488
  return `Fix the visible ${args.bucketLabel} and rerun the full suite at standard.`;
3423
3489
  }
3424
- return null;
3490
+ return "Inspect the first visible anchor for this bucket, apply the smallest fix that explains it, then rerun the full suite at standard.";
3491
+ }
3492
+ function deriveBucketSuspectKind(args) {
3493
+ 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") {
3494
+ return "environment";
3495
+ }
3496
+ if (args.bucket.type === "configuration_error" || args.bucket.type === "db_migration_failure" || args.bucket.type === "import_dependency_failure" || args.bucket.type === "collection_failure" || args.bucket.type === "no_tests_collected" || args.bucket.type === "deprecation_warning_as_error" || args.bucket.type === "file_not_found_failure") {
3497
+ return "config";
3498
+ }
3499
+ 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") {
3500
+ return "test";
3501
+ }
3502
+ 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") {
3503
+ return "tooling";
3504
+ }
3505
+ if (args.bucket.type === "unknown_failure") {
3506
+ return "unknown";
3507
+ }
3508
+ if (args.bucket.type === "assertion_failure" || args.bucket.type === "runtime_failure" || args.bucket.type === "type_error_failure" || args.bucket.type === "serialization_encoding_failure") {
3509
+ const file = args.readTarget?.file ?? "";
3510
+ if (file.startsWith("src/")) {
3511
+ return "app_code";
3512
+ }
3513
+ if (file.startsWith("test/") || file.startsWith("tests/")) {
3514
+ return "test";
3515
+ }
3516
+ return "unknown";
3517
+ }
3518
+ return "unknown";
3519
+ }
3520
+ function derivePrimarySuspectKind(args) {
3521
+ const primaryBucket = (args.dominantBlockerBucketIndex !== null ? args.mainBuckets.find((bucket) => bucket.bucket_index === args.dominantBlockerBucketIndex) : null) ?? args.mainBuckets[0];
3522
+ return primaryBucket?.suspect_kind ?? "unknown";
3523
+ }
3524
+ function buildConfidenceReason(args) {
3525
+ const primaryBucket = args.mainBuckets.find((bucket) => bucket.dominant) ?? args.mainBuckets[0];
3526
+ if (args.decision === "stop" && primaryBucket && args.primarySuspectKind !== "unknown") {
3527
+ return `Dominant blocker (${primaryBucket.label}) is anchored and actionable.`;
3528
+ }
3529
+ if (args.decision === "zoom") {
3530
+ return "Unknown or low-confidence buckets remain; one deeper sift pass is justified.";
3531
+ }
3532
+ if (args.decision === "read_source") {
3533
+ return "The bucket is identified, but source context is still needed to make the next fix clear.";
3534
+ }
3535
+ return "Heuristic signal is still insufficient; exact traceback lines are needed.";
3536
+ }
3537
+ function formatSuspectKindLabel(kind) {
3538
+ switch (kind) {
3539
+ case "test":
3540
+ return "test code";
3541
+ case "app_code":
3542
+ return "application code";
3543
+ case "config":
3544
+ return "test or project configuration";
3545
+ case "environment":
3546
+ return "environment setup";
3547
+ case "tooling":
3548
+ return "test runner or tooling";
3549
+ default:
3550
+ return "unknown";
3551
+ }
3425
3552
  }
3426
3553
  function buildStandardBucketSupport(args) {
3427
3554
  return {
3428
3555
  headline: args.bucket.summaryLines[0] ? `- ${args.bucket.summaryLines[0]}` : renderBucketHeadline(args.contractBucket),
3556
+ firstConcreteSignalText: args.bucket.source === "unknown" ? args.bucket.summaryLines[1] ?? null : null,
3429
3557
  anchorText: buildStandardAnchorText(args.readTarget),
3430
- fixText: buildStandardFixText({
3558
+ fixText: resolveBucketFixHint({
3431
3559
  bucket: args.bucket,
3432
3560
  bucketLabel: args.contractBucket.label
3433
3561
  })
@@ -3450,6 +3578,9 @@ function renderStandard(args) {
3450
3578
  )
3451
3579
  });
3452
3580
  lines.push(support.headline);
3581
+ if (support.firstConcreteSignalText) {
3582
+ lines.push(`- ${support.firstConcreteSignalText}`);
3583
+ }
3453
3584
  if (support.anchorText) {
3454
3585
  lines.push(`- Anchor: ${support.anchorText}`);
3455
3586
  }
@@ -3459,6 +3590,7 @@ function renderStandard(args) {
3459
3590
  }
3460
3591
  }
3461
3592
  lines.push(buildDecisionLine(args.contract));
3593
+ lines.push(`- Likely owner: ${formatSuspectKindLabel(args.contract.primary_suspect_kind)}`);
3462
3594
  lines.push(`- Next: ${args.contract.next_best_action.note}`);
3463
3595
  lines.push(buildStopSignal(args.contract));
3464
3596
  return lines.join("\n");
@@ -3546,29 +3678,49 @@ function buildTestStatusDiagnoseContract(args) {
3546
3678
  })[0] ?? null;
3547
3679
  const hasUnknownBucket = buckets.some((bucket) => isUnknownBucket(bucket));
3548
3680
  const hasConcreteCoverage = args.analysis.failed === 0 && args.analysis.errors === 0 ? true : residuals.remainingErrors === 0 && residuals.remainingFailed === 0;
3549
- const diagnosisComplete = args.analysis.failed === 0 && args.analysis.errors === 0 && args.analysis.passed > 0 || simpleCollectionFailure || buckets.length > 0 && hasConcreteCoverage && !hasUnknownBucket && (dominantBucket?.bucket.confidence ?? 0) >= 0.6;
3550
- const rawNeeded = buckets.length === 0 ? !(args.analysis.failed === 0 && args.analysis.errors === 0 && args.analysis.passed > 0 || simpleCollectionFailure) : !diagnosisComplete && !hasUnknownBucket && buckets.every((bucket) => bucket.confidence < 0.7);
3551
3681
  const dominantBlockerBucketIndex = dominantBucket && isDominantBlockerType(dominantBucket.bucket.type) ? dominantBucket.index + 1 : null;
3552
3682
  const readTargets = buildReadTargets({
3553
3683
  buckets,
3554
3684
  dominantBucketIndex: dominantBlockerBucketIndex
3555
3685
  });
3556
- const mainBuckets = buckets.map((bucket, index) => ({
3557
- bucket_index: index + 1,
3558
- label: labelForBucket(bucket),
3559
- count: bucket.count,
3560
- root_cause: bucket.reason,
3561
- evidence: buildBucketEvidence(bucket),
3562
- bucket_confidence: Number(bucket.confidence.toFixed(2)),
3563
- root_cause_confidence: Number(rootCauseConfidenceFor(bucket).toFixed(2)),
3564
- dominant: dominantBucket?.index === index,
3565
- secondary_visible_despite_blocker: dominantBlockerBucketIndex !== null && dominantBlockerBucketIndex !== index + 1,
3566
- mini_diff: extractMiniDiff(args.input, bucket)
3567
- }));
3686
+ const dominantBucketHasConcreteAnchor = dominantBucket !== null && (readTargets.some((target) => target.bucket_index === dominantBucket.index + 1 && target.file.length > 0) || dominantBucket.bucket.representativeItems.some((item) => item.anchor_kind !== "none"));
3687
+ const smallConcreteSuite = args.analysis.failed + args.analysis.errors <= 2 && residuals.remainingErrors === 0 && residuals.remainingFailed === 0 && buckets.length === 1 && !hasUnknownBucket && dominantBucket !== null && dominantBucketHasConcreteAnchor;
3688
+ const dominantConfidenceThreshold = smallConcreteSuite ? 0.55 : 0.6;
3689
+ const diagnosisComplete = args.analysis.failed === 0 && args.analysis.errors === 0 && args.analysis.passed > 0 || simpleCollectionFailure || buckets.length > 0 && hasConcreteCoverage && !hasUnknownBucket && (dominantBucket?.bucket.confidence ?? 0) >= dominantConfidenceThreshold;
3690
+ const rawNeeded = buckets.length === 0 ? !(args.analysis.failed === 0 && args.analysis.errors === 0 && args.analysis.passed > 0 || simpleCollectionFailure) : !diagnosisComplete && !hasUnknownBucket && buckets.every((bucket) => bucket.confidence < 0.7);
3691
+ const mainBuckets = buckets.map((bucket, index) => {
3692
+ const bucketIndex = index + 1;
3693
+ const label = labelForBucket(bucket);
3694
+ const readTarget = readTargets.find((target) => target.bucket_index === bucketIndex);
3695
+ return {
3696
+ bucket_index: bucketIndex,
3697
+ label,
3698
+ count: bucket.count,
3699
+ root_cause: bucket.reason,
3700
+ suspect_kind: deriveBucketSuspectKind({
3701
+ bucket,
3702
+ readTarget
3703
+ }),
3704
+ fix_hint: resolveBucketFixHint({
3705
+ bucket,
3706
+ bucketLabel: label
3707
+ }),
3708
+ evidence: buildBucketEvidence(bucket),
3709
+ bucket_confidence: Number(bucket.confidence.toFixed(2)),
3710
+ root_cause_confidence: Number(rootCauseConfidenceFor(bucket).toFixed(2)),
3711
+ dominant: dominantBucket?.index === index,
3712
+ secondary_visible_despite_blocker: dominantBlockerBucketIndex !== null && dominantBlockerBucketIndex !== bucketIndex,
3713
+ mini_diff: extractMiniDiff(args.input, bucket)
3714
+ };
3715
+ });
3568
3716
  const resolvedTests = unique(args.resolvedTests ?? []);
3569
3717
  const remainingTests = unique(
3570
3718
  args.remainingTests ?? unique([...args.analysis.visibleErrorLabels, ...args.analysis.visibleFailedLabels])
3571
3719
  );
3720
+ const primarySuspectKind = derivePrimarySuspectKind({
3721
+ mainBuckets,
3722
+ dominantBlockerBucketIndex
3723
+ });
3572
3724
  let nextBestAction;
3573
3725
  if (args.analysis.failed === 0 && args.analysis.errors === 0 && args.analysis.passed > 0) {
3574
3726
  nextBestAction = {
@@ -3614,6 +3766,8 @@ function buildTestStatusDiagnoseContract(args) {
3614
3766
  additional_source_read_likely_low_value: diagnosisComplete && !rawNeeded,
3615
3767
  read_raw_only_if: rawNeeded ? "you still need exact traceback lines after focused or verbose detail" : null,
3616
3768
  dominant_blocker_bucket_index: dominantBlockerBucketIndex,
3769
+ primary_suspect_kind: primarySuspectKind,
3770
+ confidence_reason: "Unknown or low-confidence buckets remain; one deeper sift pass is justified.",
3617
3771
  provider_used: false,
3618
3772
  provider_confidence: null,
3619
3773
  provider_failed: false,
@@ -3645,9 +3799,16 @@ function buildTestStatusDiagnoseContract(args) {
3645
3799
  })
3646
3800
  }
3647
3801
  };
3802
+ const resolvedDecision = effectiveDecision ?? deriveDecision(mergedContractWithoutDecision);
3803
+ const resolvedConfidenceReason = buildConfidenceReason({
3804
+ decision: resolvedDecision,
3805
+ mainBuckets,
3806
+ primarySuspectKind: mergedContractWithoutDecision.primary_suspect_kind
3807
+ });
3648
3808
  const contract = testStatusDiagnoseContractSchema.parse({
3649
3809
  ...mergedContractWithoutDecision,
3650
- decision: effectiveDecision ?? deriveDecision(mergedContractWithoutDecision)
3810
+ confidence_reason: resolvedConfidenceReason,
3811
+ decision: resolvedDecision
3651
3812
  });
3652
3813
  return {
3653
3814
  contract,
@@ -4052,6 +4213,27 @@ function buildFallbackOutput(args) {
4052
4213
  var RISK_LINE_PATTERN = /(destroy|delete|drop|recreate|replace|revoke|deny|downtime|data loss|iam|network exposure)/i;
4053
4214
  var ZERO_DESTRUCTIVE_SUMMARY_PATTERN = /\b0\s+to\s+(destroy|delete|drop|recreate|replace|revoke)\b/i;
4054
4215
  var SAFE_LINE_PATTERN = /(no changes|up-to-date|up to date|no risky changes|safe to apply)/i;
4216
+ var RESOURCE_DESTROY_HEADER_PATTERN = /^#\s+.+\bwill be (destroyed|deleted|replaced)\b/i;
4217
+ var DESTROY_ERROR_PATTERN = /(instance cannot be destroyed|prevent_destroy|downtime|data loss)/i;
4218
+ var ACTION_DESTROY_PATTERN = /^-\s+destroy$/i;
4219
+ var TSC_CODE_LABELS = {
4220
+ TS1002: "syntax error",
4221
+ TS1005: "syntax error",
4222
+ TS2304: "cannot find name",
4223
+ TS2307: "cannot find module",
4224
+ TS2322: "type mismatch",
4225
+ TS2339: "missing property on type",
4226
+ TS2345: "argument type mismatch",
4227
+ TS2554: "wrong argument count",
4228
+ TS2741: "missing required property",
4229
+ TS2769: "no matching overload",
4230
+ TS5083: "config file error",
4231
+ TS6133: "declared but unused",
4232
+ TS7006: "implicit any",
4233
+ TS18003: "no inputs were found",
4234
+ TS18046: "unknown type",
4235
+ TS18048: "possibly undefined"
4236
+ };
4055
4237
  function collectEvidence(input, matcher, limit = 3) {
4056
4238
  return input.split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && matcher.test(line)).slice(0, limit);
4057
4239
  }
@@ -4065,11 +4247,179 @@ function inferPackage(line) {
4065
4247
  function inferRemediation(pkg2) {
4066
4248
  return `Upgrade ${pkg2} to a patched version.`;
4067
4249
  }
4250
+ function parseCompactAuditVulnerability(line) {
4251
+ if (/^Severity:\s*/i.test(line)) {
4252
+ return null;
4253
+ }
4254
+ if (!/\b(critical|high)\b/i.test(line)) {
4255
+ return null;
4256
+ }
4257
+ const pkg2 = inferPackage(line);
4258
+ if (!pkg2) {
4259
+ return null;
4260
+ }
4261
+ return {
4262
+ package: pkg2,
4263
+ severity: inferSeverity(line),
4264
+ remediation: inferRemediation(pkg2)
4265
+ };
4266
+ }
4267
+ function inferAuditPackageHeader(line) {
4268
+ const trimmed = line.trim();
4269
+ if (trimmed.length === 0 || trimmed.startsWith("#") || trimmed.includes(":") || /^node_modules\//i.test(trimmed)) {
4270
+ return null;
4271
+ }
4272
+ const match = trimmed.match(/^([@a-z0-9._/-]+)(?:\s{2,}|\s+(?:[<>=~^*]|\d))/i);
4273
+ return match?.[1] ?? null;
4274
+ }
4275
+ function collectAuditCriticalVulnerabilities(input) {
4276
+ const lines = input.split("\n");
4277
+ const vulnerabilities = [];
4278
+ const seen = /* @__PURE__ */ new Set();
4279
+ const pushVulnerability = (pkg2, severity) => {
4280
+ const key = `${pkg2}:${severity}`;
4281
+ if (seen.has(key)) {
4282
+ return;
4283
+ }
4284
+ seen.add(key);
4285
+ vulnerabilities.push({
4286
+ package: pkg2,
4287
+ severity,
4288
+ remediation: inferRemediation(pkg2)
4289
+ });
4290
+ };
4291
+ for (let index = 0; index < lines.length; index += 1) {
4292
+ const line = lines[index].trim();
4293
+ if (!line) {
4294
+ continue;
4295
+ }
4296
+ const compact = parseCompactAuditVulnerability(line);
4297
+ if (compact) {
4298
+ pushVulnerability(compact.package, compact.severity);
4299
+ continue;
4300
+ }
4301
+ const pkg2 = inferAuditPackageHeader(line);
4302
+ if (!pkg2) {
4303
+ continue;
4304
+ }
4305
+ for (let cursor = index + 1; cursor < Math.min(lines.length, index + 5); cursor += 1) {
4306
+ const candidate = lines[cursor].trim();
4307
+ if (!candidate) {
4308
+ continue;
4309
+ }
4310
+ const severityMatch = candidate.match(/^Severity:\s*(critical|high)\b/i);
4311
+ if (severityMatch) {
4312
+ pushVulnerability(pkg2, severityMatch[1].toLowerCase());
4313
+ break;
4314
+ }
4315
+ if (inferAuditPackageHeader(candidate) || parseCompactAuditVulnerability(candidate)) {
4316
+ break;
4317
+ }
4318
+ }
4319
+ }
4320
+ return vulnerabilities;
4321
+ }
4068
4322
  function getCount(input, label) {
4069
4323
  const matches = [...input.matchAll(new RegExp(`(\\d+)\\s+${label}`, "gi"))];
4070
4324
  const lastMatch = matches.at(-1);
4071
4325
  return lastMatch ? Number(lastMatch[1]) : 0;
4072
4326
  }
4327
+ function collectInfraRiskEvidence(input) {
4328
+ const lines = input.split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
4329
+ const evidence = [];
4330
+ const seen = /* @__PURE__ */ new Set();
4331
+ const pushMatches = (matcher, options) => {
4332
+ let added = 0;
4333
+ for (const line of lines) {
4334
+ if (!matcher.test(line)) {
4335
+ continue;
4336
+ }
4337
+ if (options?.exclude?.test(line)) {
4338
+ continue;
4339
+ }
4340
+ if (seen.has(line)) {
4341
+ continue;
4342
+ }
4343
+ evidence.push(line);
4344
+ seen.add(line);
4345
+ added += 1;
4346
+ if (options?.limit && added >= options.limit) {
4347
+ return;
4348
+ }
4349
+ if (evidence.length >= (options?.maxEvidence ?? 4)) {
4350
+ return;
4351
+ }
4352
+ }
4353
+ };
4354
+ pushMatches(/Plan:/i, {
4355
+ exclude: ZERO_DESTRUCTIVE_SUMMARY_PATTERN,
4356
+ limit: 1
4357
+ });
4358
+ if (evidence.length < 4) {
4359
+ pushMatches(RESOURCE_DESTROY_HEADER_PATTERN, { limit: 2 });
4360
+ }
4361
+ if (evidence.length < 4) {
4362
+ pushMatches(DESTROY_ERROR_PATTERN, { limit: 1 });
4363
+ }
4364
+ if (evidence.length < 4) {
4365
+ pushMatches(ACTION_DESTROY_PATTERN, { limit: 1 });
4366
+ }
4367
+ if (evidence.length < 4) {
4368
+ pushMatches(RISK_LINE_PATTERN, {
4369
+ exclude: /->\s+null$|\b0\s+to\s+(destroy|delete|drop|recreate|replace|revoke)\b/i,
4370
+ maxEvidence: 4
4371
+ });
4372
+ }
4373
+ return evidence.slice(0, 4);
4374
+ }
4375
+ function collectInfraDestroyTargets(input) {
4376
+ const targets = [];
4377
+ const seen = /* @__PURE__ */ new Set();
4378
+ for (const line of input.split("\n").map((entry) => entry.trim())) {
4379
+ const match = line.match(/^#\s+(.+?)\s+will be (destroyed|deleted|replaced)\b/i);
4380
+ const target = match?.[1]?.trim();
4381
+ if (!target || seen.has(target)) {
4382
+ continue;
4383
+ }
4384
+ seen.add(target);
4385
+ targets.push(target);
4386
+ }
4387
+ return targets;
4388
+ }
4389
+ function inferInfraDestroyCount(input, destroyTargets) {
4390
+ const matches = [
4391
+ ...input.matchAll(/\b(\d+)\s+to\s+(destroy|delete|drop|recreate|replace|revoke)\b/gi)
4392
+ ];
4393
+ const lastMatch = matches.at(-1);
4394
+ return lastMatch ? Number(lastMatch[1]) : destroyTargets.length;
4395
+ }
4396
+ function collectInfraBlockers(input) {
4397
+ const lines = input.split("\n");
4398
+ const blockers = [];
4399
+ const seen = /* @__PURE__ */ new Set();
4400
+ for (let index = 0; index < lines.length; index += 1) {
4401
+ const trimmed = lines[index]?.trim();
4402
+ const errorMatch = trimmed?.match(/^(?:[│|]\s*)?Error:\s+(.+)$/);
4403
+ if (!errorMatch) {
4404
+ continue;
4405
+ }
4406
+ const message = errorMatch[1].trim();
4407
+ const nearby = lines.slice(index, index + 8).join("\n");
4408
+ const preventDestroyTarget = nearby.match(/Resource\s+([^\s]+)\s+has lifecycle\.prevent_destroy set/i)?.[1] ?? null;
4409
+ const type = preventDestroyTarget ? "prevent_destroy" : "destroy_blocked";
4410
+ const key = `${type}:${preventDestroyTarget ?? ""}:${message}`;
4411
+ if (seen.has(key)) {
4412
+ continue;
4413
+ }
4414
+ seen.add(key);
4415
+ blockers.push({
4416
+ type,
4417
+ target: preventDestroyTarget,
4418
+ message
4419
+ });
4420
+ }
4421
+ return blockers;
4422
+ }
4073
4423
  function detectTestRunner(input) {
4074
4424
  if (/^\s*Test Files?\s+(?:\d+\s+failed\s*\|\s*)?\d+\s+passed/m.test(input) || /^\s*Tests?\s+(?:\d+\s+failed\s*\|\s*)?\d+\s+passed/m.test(input) || /^\s*Snapshots?\s+(?:\d+\s+failed\s*\|\s*)?\d+\s+passed/m.test(input) || /⎯{2,}\s+Failed Tests?\s+\d+\s+⎯{2,}/.test(input)) {
4075
4425
  return "vitest";
@@ -4147,6 +4497,21 @@ function collectUniqueMatches(input, matcher, limit = 6) {
4147
4497
  }
4148
4498
  return values;
4149
4499
  }
4500
+ function compactDisplayFile(file) {
4501
+ const normalized = file.replace(/\\/g, "/").trim();
4502
+ if (!normalized) {
4503
+ return file;
4504
+ }
4505
+ const looksAbsolute = normalized.startsWith("/") || /^[A-Za-z]:\//.test(normalized);
4506
+ if (!looksAbsolute && normalized.length <= 60) {
4507
+ return normalized;
4508
+ }
4509
+ const basename = normalized.split("/").at(-1);
4510
+ return basename && basename.length > 0 ? basename : normalized;
4511
+ }
4512
+ function formatDisplayedFiles(files, limit = 3) {
4513
+ return [...new Set([...files].map((file) => file.trim()).filter(Boolean))].sort((left, right) => left.localeCompare(right)).slice(0, limit).map((file) => compactDisplayFile(file));
4514
+ }
4150
4515
  function emptyAnchor() {
4151
4516
  return {
4152
4517
  file: null,
@@ -4426,6 +4791,31 @@ function classifyFailureReason(line, options) {
4426
4791
  group: "permission or locked resource failures"
4427
4792
  };
4428
4793
  }
4794
+ const osDiskFullFailure = normalized.match(
4795
+ /(OSError:\s*\[Errno 28\][^$]*|No space left on device)/i
4796
+ );
4797
+ if (osDiskFullFailure) {
4798
+ return {
4799
+ reason: buildClassifiedReason(
4800
+ "configuration",
4801
+ `disk full (${buildExcerptDetail(
4802
+ osDiskFullFailure[1] ?? normalized,
4803
+ "No space left on device"
4804
+ )})`
4805
+ ),
4806
+ group: "test configuration failures"
4807
+ };
4808
+ }
4809
+ const osPermissionFailure = normalized.match(/OSError:\s*\[Errno 13\][^$]*/i);
4810
+ if (osPermissionFailure) {
4811
+ return {
4812
+ reason: buildClassifiedReason(
4813
+ "permission",
4814
+ buildExcerptDetail(osPermissionFailure[0] ?? normalized, "permission denied")
4815
+ ),
4816
+ group: "permission or locked resource failures"
4817
+ };
4818
+ }
4429
4819
  const xdistWorkerCrash = normalized.match(
4430
4820
  /(worker ['"][^'"]+['"] crashed|node down:\s*[^,;]+|WorkerLost[^,;]*|Worker exited unexpectedly[^,;]*|worker exited unexpectedly[^,;]*)/i
4431
4821
  );
@@ -4451,7 +4841,7 @@ function classifyFailureReason(line, options) {
4451
4841
  };
4452
4842
  }
4453
4843
  const networkFailure = normalized.match(
4454
- /(Max retries exceeded[^,;]*|gaierror[^,;]*|SSLCertVerificationError[^,;]*|Network is unreachable)/i
4844
+ /(Max retries exceeded[^,;]*|gaierror[^,;]*|SSLCertVerificationError[^,;]*|Network is unreachable|ConnectionResetError[^,;]*|BrokenPipeError[^,;]*|HTTPError:\s*[45]\d\d[^,;]*)/i
4455
4845
  );
4456
4846
  if (networkFailure) {
4457
4847
  return {
@@ -4462,6 +4852,15 @@ function classifyFailureReason(line, options) {
4462
4852
  group: "network dependency failures"
4463
4853
  };
4464
4854
  }
4855
+ const matcherAssertionFailure = normalized.match(
4856
+ /(expect\(received\)\.(?:toBe|toEqual|toStrictEqual|toMatchObject)\(expected\))/i
4857
+ );
4858
+ if (matcherAssertionFailure) {
4859
+ return {
4860
+ reason: `assertion failed: ${matcherAssertionFailure[1]}`.slice(0, 120),
4861
+ group: "assertion failures"
4862
+ };
4863
+ }
4465
4864
  const relationMigration = normalized.match(/relation ["'`]([^"'`]+)["'`] does not exist/i);
4466
4865
  if (relationMigration) {
4467
4866
  return {
@@ -4500,6 +4899,34 @@ function classifyFailureReason(line, options) {
4500
4899
  group: "memory exhaustion failures"
4501
4900
  };
4502
4901
  }
4902
+ const propertySetterOverrideFailure = normalized.match(
4903
+ /AttributeError:\s*(property ['"][^'"]+['"] of ['"][^'"]+['"] object has no setter|can't set attribute|readonly attribute|read-only attribute)/i
4904
+ );
4905
+ if (propertySetterOverrideFailure) {
4906
+ return {
4907
+ reason: buildClassifiedReason(
4908
+ "configuration",
4909
+ `invalid test setup override (${buildExcerptDetail(
4910
+ `AttributeError: ${propertySetterOverrideFailure[1] ?? normalized}`,
4911
+ "AttributeError: can't set attribute"
4912
+ )})`
4913
+ ),
4914
+ group: "test configuration failures"
4915
+ };
4916
+ }
4917
+ const setupOverrideFailure = normalized.match(/\b(AttributeError|TypeError):\s*(.+)$/i);
4918
+ if (setupOverrideFailure && /(monkeypatch|patch|fixture|settings|conftest)/i.test(normalized)) {
4919
+ return {
4920
+ reason: buildClassifiedReason(
4921
+ "configuration",
4922
+ `invalid test setup override (${buildExcerptDetail(
4923
+ `${setupOverrideFailure[1]}: ${setupOverrideFailure[2] ?? ""}`,
4924
+ `${setupOverrideFailure[1]}`
4925
+ )})`
4926
+ ),
4927
+ group: "test configuration failures"
4928
+ };
4929
+ }
4503
4930
  const typeErrorFailure = normalized.match(/TypeError:\s*(.+)$/i);
4504
4931
  if (typeErrorFailure) {
4505
4932
  return {
@@ -5581,13 +6008,17 @@ function analyzeTestStatus(input) {
5581
6008
  const interrupted = /\binterrupted\b/i.test(input) || /\bKeyboardInterrupt\b/i.test(input);
5582
6009
  const collectionItems = chooseStrongestFailureItems(collectCollectionFailureItems(input));
5583
6010
  const inlineItems = chooseStrongestFailureItems(collectInlineFailureItems(input));
6011
+ const statusItems = collectInlineFailureItemsWithStatus(input);
5584
6012
  const visibleErrorItems = chooseStrongestStatusFailureItems([
5585
6013
  ...collectionItems.map((item) => ({
5586
6014
  ...item,
5587
6015
  status: "error"
5588
6016
  })),
5589
- ...collectInlineFailureItemsWithStatus(input).filter((item) => item.status === "error")
6017
+ ...statusItems.filter((item) => item.status === "error")
5590
6018
  ]);
6019
+ const visibleFailedItems = chooseStrongestStatusFailureItems(
6020
+ statusItems.filter((item) => item.status === "failed")
6021
+ );
5591
6022
  const labels = collectFailureLabels(input);
5592
6023
  const visibleErrorLabels = labels.filter((item) => item.status === "error").map((item) => item.label);
5593
6024
  const visibleFailedLabels = labels.filter((item) => item.status === "failed").map((item) => item.label);
@@ -5646,6 +6077,7 @@ function analyzeTestStatus(input) {
5646
6077
  visibleErrorLabels,
5647
6078
  visibleFailedLabels,
5648
6079
  visibleErrorItems,
6080
+ visibleFailedItems,
5649
6081
  buckets
5650
6082
  };
5651
6083
  }
@@ -5706,20 +6138,18 @@ function testStatusHeuristic(input, detail = "standard") {
5706
6138
  return null;
5707
6139
  }
5708
6140
  function auditCriticalHeuristic(input) {
5709
- const vulnerabilities = input.split("\n").map((line) => line.trim()).filter(Boolean).map((line) => {
5710
- if (!/\b(critical|high)\b/i.test(line)) {
5711
- return null;
5712
- }
5713
- const pkg2 = inferPackage(line);
5714
- if (!pkg2) {
5715
- return null;
5716
- }
5717
- return {
5718
- package: pkg2,
5719
- severity: inferSeverity(line),
5720
- remediation: inferRemediation(pkg2)
5721
- };
5722
- }).filter((item) => item !== null);
6141
+ if (/\bfound\s+0\s+vulnerabilities\b/i.test(input) || /\b0\s+vulnerabilities\b/i.test(input)) {
6142
+ return JSON.stringify(
6143
+ {
6144
+ status: "ok",
6145
+ vulnerabilities: [],
6146
+ summary: "No high or critical vulnerabilities found in the provided input."
6147
+ },
6148
+ null,
6149
+ 2
6150
+ );
6151
+ }
6152
+ const vulnerabilities = collectAuditCriticalVulnerabilities(input);
5723
6153
  if (vulnerabilities.length === 0) {
5724
6154
  return null;
5725
6155
  }
@@ -5735,16 +6165,19 @@ function auditCriticalHeuristic(input) {
5735
6165
  );
5736
6166
  }
5737
6167
  function infraRiskHeuristic(input) {
6168
+ const destroyTargets = collectInfraDestroyTargets(input);
6169
+ const blockers = collectInfraBlockers(input);
5738
6170
  const zeroDestructiveEvidence = input.split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && ZERO_DESTRUCTIVE_SUMMARY_PATTERN.test(line)).slice(0, 3);
5739
- const riskEvidence = input.split("\n").map((line) => line.trim()).filter(
5740
- (line) => line.length > 0 && RISK_LINE_PATTERN.test(line) && !ZERO_DESTRUCTIVE_SUMMARY_PATTERN.test(line)
5741
- ).slice(0, 3);
6171
+ const riskEvidence = collectInfraRiskEvidence(input);
5742
6172
  if (riskEvidence.length > 0) {
5743
6173
  return JSON.stringify(
5744
6174
  {
5745
6175
  verdict: "fail",
5746
6176
  reason: "Destructive or clearly risky infrastructure change signals are present.",
5747
- evidence: riskEvidence
6177
+ evidence: riskEvidence,
6178
+ destroy_count: inferInfraDestroyCount(input, destroyTargets),
6179
+ destroy_targets: destroyTargets,
6180
+ blockers
5748
6181
  },
5749
6182
  null,
5750
6183
  2
@@ -5755,7 +6188,10 @@ function infraRiskHeuristic(input) {
5755
6188
  {
5756
6189
  verdict: "pass",
5757
6190
  reason: "The provided input explicitly indicates zero destructive changes.",
5758
- evidence: zeroDestructiveEvidence
6191
+ evidence: zeroDestructiveEvidence,
6192
+ destroy_count: 0,
6193
+ destroy_targets: [],
6194
+ blockers: []
5759
6195
  },
5760
6196
  null,
5761
6197
  2
@@ -5767,7 +6203,10 @@ function infraRiskHeuristic(input) {
5767
6203
  {
5768
6204
  verdict: "pass",
5769
6205
  reason: "The provided input explicitly indicates no risky infrastructure changes.",
5770
- evidence: safeEvidence
6206
+ evidence: safeEvidence,
6207
+ destroy_count: 0,
6208
+ destroy_targets: [],
6209
+ blockers: []
5771
6210
  },
5772
6211
  null,
5773
6212
  2
@@ -5775,6 +6214,551 @@ function infraRiskHeuristic(input) {
5775
6214
  }
5776
6215
  return null;
5777
6216
  }
6217
+ function parseTscErrors(input) {
6218
+ const diagnostics = [];
6219
+ for (const rawLine of input.split("\n")) {
6220
+ const line = rawLine.replace(/\u001b\[[0-9;]*m/g, "").trimEnd();
6221
+ if (!line.trim()) {
6222
+ continue;
6223
+ }
6224
+ let match = line.match(/^(.+)\((\d+),(\d+)\):\s+error\s+(TS\d+):\s+(.+)$/);
6225
+ if (match) {
6226
+ diagnostics.push({
6227
+ file: match[1].replace(/\\/g, "/").trim(),
6228
+ line: Number(match[2]),
6229
+ column: Number(match[3]),
6230
+ code: match[4],
6231
+ message: match[5].trim()
6232
+ });
6233
+ continue;
6234
+ }
6235
+ match = line.match(/^(.+):(\d+):(\d+)\s+-\s+error\s+(TS\d+):\s+(.+)$/);
6236
+ if (match) {
6237
+ diagnostics.push({
6238
+ file: match[1].replace(/\\/g, "/").trim(),
6239
+ line: Number(match[2]),
6240
+ column: Number(match[3]),
6241
+ code: match[4],
6242
+ message: match[5].trim()
6243
+ });
6244
+ continue;
6245
+ }
6246
+ match = line.match(/^\s*error\s+(TS\d+):\s+(.+)$/);
6247
+ if (match) {
6248
+ diagnostics.push({
6249
+ file: null,
6250
+ line: null,
6251
+ column: null,
6252
+ code: match[1],
6253
+ message: match[2].trim()
6254
+ });
6255
+ }
6256
+ }
6257
+ return diagnostics;
6258
+ }
6259
+ function extractTscSummary(input) {
6260
+ const matches = [
6261
+ ...input.matchAll(/\bFound\s+(\d+)\s+errors?\b(?:\s+in\s+(\d+)\s+files?)?\.?/gi)
6262
+ ];
6263
+ const summary = matches.at(-1);
6264
+ if (!summary) {
6265
+ return null;
6266
+ }
6267
+ return {
6268
+ errorCount: Number(summary[1]),
6269
+ fileCount: summary[2] ? Number(summary[2]) : null
6270
+ };
6271
+ }
6272
+ function formatTscGroup(args) {
6273
+ const label = TSC_CODE_LABELS[args.code];
6274
+ const displayFiles = formatDisplayedFiles(args.files);
6275
+ let line = `- ${args.code}`;
6276
+ if (label) {
6277
+ line += ` (${label})`;
6278
+ }
6279
+ line += `: ${formatCount2(args.count, "occurrence")}`;
6280
+ if (displayFiles.length > 0) {
6281
+ line += ` across ${displayFiles.join(", ")}`;
6282
+ }
6283
+ return `${line}.`;
6284
+ }
6285
+ function typecheckSummaryHeuristic(input) {
6286
+ if (input.trim().length === 0) {
6287
+ return null;
6288
+ }
6289
+ const diagnostics = parseTscErrors(input);
6290
+ const summary = extractTscSummary(input);
6291
+ const hasTscSignal = diagnostics.length > 0 || summary !== null || /\berror\s+TS\d+:/m.test(input);
6292
+ if (!hasTscSignal) {
6293
+ return null;
6294
+ }
6295
+ if (summary?.errorCount === 0) {
6296
+ return "No type errors.";
6297
+ }
6298
+ if (diagnostics.length === 0 && summary === null) {
6299
+ return null;
6300
+ }
6301
+ const errorCount = summary?.errorCount ?? diagnostics.length;
6302
+ const allFiles = new Set(
6303
+ diagnostics.map((diagnostic) => diagnostic.file).filter((file) => Boolean(file))
6304
+ );
6305
+ const fileCount = summary?.fileCount ?? (allFiles.size > 0 ? allFiles.size : null);
6306
+ const groups = /* @__PURE__ */ new Map();
6307
+ for (const diagnostic of diagnostics) {
6308
+ const group = groups.get(diagnostic.code) ?? {
6309
+ count: 0,
6310
+ files: /* @__PURE__ */ new Set()
6311
+ };
6312
+ group.count += 1;
6313
+ if (diagnostic.file) {
6314
+ group.files.add(diagnostic.file);
6315
+ }
6316
+ groups.set(diagnostic.code, group);
6317
+ }
6318
+ const bullets = [
6319
+ `- Typecheck failed: ${formatCount2(errorCount, "error")}${fileCount ? ` in ${formatCount2(fileCount, "file")}` : ""}.`
6320
+ ];
6321
+ const sortedGroups = [...groups.entries()].map(([code, group]) => ({
6322
+ code,
6323
+ count: group.count,
6324
+ files: group.files
6325
+ })).sort((left, right) => right.count - left.count || left.code.localeCompare(right.code));
6326
+ for (const group of sortedGroups.slice(0, 3)) {
6327
+ bullets.push(formatTscGroup(group));
6328
+ }
6329
+ if (sortedGroups.length > 3) {
6330
+ const overflowFiles = /* @__PURE__ */ new Set();
6331
+ for (const group of sortedGroups.slice(3)) {
6332
+ for (const file of group.files) {
6333
+ overflowFiles.add(file);
6334
+ }
6335
+ }
6336
+ let overflow = `- ${formatCount2(sortedGroups.length - 3, "more error code")}`;
6337
+ if (overflowFiles.size > 0) {
6338
+ overflow += ` across ${formatCount2(overflowFiles.size, "file")}`;
6339
+ }
6340
+ bullets.push(`${overflow}.`);
6341
+ }
6342
+ return bullets.join("\n");
6343
+ }
6344
+ function looksLikeEslintFileHeader(line) {
6345
+ if (line.trim().length === 0 || line.trim() !== line) {
6346
+ return false;
6347
+ }
6348
+ if (/^\s*[✖×x]\s+\d+\s+problems?\b/i.test(line) || /potentially\s+fixable/i.test(line) || /^\d+\s+problems?\b/i.test(line)) {
6349
+ return false;
6350
+ }
6351
+ const normalized = line.replace(/\\/g, "/");
6352
+ const pathLike = normalized.startsWith("/") || normalized.startsWith("./") || normalized.startsWith("../") || /^[A-Za-z]:\//.test(normalized) || /^[A-Za-z0-9_.-]+\//.test(normalized);
6353
+ return pathLike && /\.[A-Za-z0-9]+$/.test(normalized);
6354
+ }
6355
+ function normalizeEslintRule(rule, message) {
6356
+ if (rule && rule.trim().length > 0) {
6357
+ return rule.trim();
6358
+ }
6359
+ if (/parsing error/i.test(message)) {
6360
+ return "parsing error";
6361
+ }
6362
+ if (/fatal/i.test(message)) {
6363
+ return "fatal error";
6364
+ }
6365
+ return "unclassified lint error";
6366
+ }
6367
+ function parseEslintStylish(input) {
6368
+ const violations = [];
6369
+ let currentFile = null;
6370
+ for (const rawLine of input.split("\n")) {
6371
+ const line = rawLine.replace(/\u001b\[[0-9;]*m/g, "").replace(/\r$/, "");
6372
+ if (looksLikeEslintFileHeader(line.trim())) {
6373
+ currentFile = line.trim().replace(/\\/g, "/");
6374
+ continue;
6375
+ }
6376
+ let match = line.match(/^\s*(\d+):(\d+)\s+(error|warning)\s+(.+?)\s{2,}(\S+)\s*$/);
6377
+ if (match) {
6378
+ violations.push({
6379
+ file: currentFile ?? "(unknown file)",
6380
+ line: Number(match[1]),
6381
+ column: Number(match[2]),
6382
+ severity: match[3],
6383
+ message: match[4].trim(),
6384
+ rule: normalizeEslintRule(match[5], match[4])
6385
+ });
6386
+ continue;
6387
+ }
6388
+ match = line.match(/^\s*(\d+):(\d+)\s+(error|warning)\s+(.+?)\s*$/);
6389
+ if (match) {
6390
+ violations.push({
6391
+ file: currentFile ?? "(unknown file)",
6392
+ line: Number(match[1]),
6393
+ column: Number(match[2]),
6394
+ severity: match[3],
6395
+ message: match[4].trim(),
6396
+ rule: normalizeEslintRule(null, match[4])
6397
+ });
6398
+ }
6399
+ }
6400
+ return violations;
6401
+ }
6402
+ function extractEslintSummary(input) {
6403
+ const summaryMatches = [
6404
+ ...input.matchAll(
6405
+ /^\s*[✖×x]?\s*(\d+)\s+problems?\s+\((\d+)\s+errors?,\s+(\d+)\s+warnings?\)/gim
6406
+ )
6407
+ ];
6408
+ const summary = summaryMatches.at(-1);
6409
+ if (!summary) {
6410
+ return null;
6411
+ }
6412
+ const fixableMatch = input.match(
6413
+ /(\d+)\s+errors?\s+and\s+(\d+)\s+warnings?\s+(?:are|is)\s+potentially\s+fixable/i
6414
+ );
6415
+ return {
6416
+ problems: Number(summary[1]),
6417
+ errors: Number(summary[2]),
6418
+ warnings: Number(summary[3]),
6419
+ fixableProblems: fixableMatch ? Number(fixableMatch[1]) + Number(fixableMatch[2]) : null
6420
+ };
6421
+ }
6422
+ function formatLintGroup(args) {
6423
+ const totalErrors = args.errors;
6424
+ const totalWarnings = args.warnings;
6425
+ const displayFiles = formatDisplayedFiles(args.files);
6426
+ let detail = "";
6427
+ if (totalErrors > 0 && totalWarnings > 0) {
6428
+ detail = `${formatCount2(totalErrors, "error")}, ${formatCount2(totalWarnings, "warning")}`;
6429
+ } else if (totalErrors > 0) {
6430
+ detail = formatCount2(totalErrors, "error");
6431
+ } else {
6432
+ detail = formatCount2(totalWarnings, "warning");
6433
+ }
6434
+ let line = `- ${args.rule}: ${detail}`;
6435
+ if (displayFiles.length > 0) {
6436
+ line += ` across ${displayFiles.join(", ")}`;
6437
+ }
6438
+ return `${line}.`;
6439
+ }
6440
+ function lintFailuresHeuristic(input) {
6441
+ const trimmed = input.trim();
6442
+ if (trimmed.length === 0 || trimmed.startsWith("[") || trimmed.startsWith("{")) {
6443
+ return null;
6444
+ }
6445
+ const summary = extractEslintSummary(input);
6446
+ const violations = parseEslintStylish(input);
6447
+ if (summary === null && violations.length === 0) {
6448
+ return null;
6449
+ }
6450
+ if (summary?.problems === 0) {
6451
+ return "No lint failures.";
6452
+ }
6453
+ const problems = summary?.problems ?? violations.length;
6454
+ const errors = summary?.errors ?? countPattern(input, /^\s*\d+:\d+\s+error\b/gm);
6455
+ const warnings = summary?.warnings ?? countPattern(input, /^\s*\d+:\d+\s+warning\b/gm);
6456
+ const bullets = [];
6457
+ if (errors > 0) {
6458
+ let headline = `- Lint failed: ${formatCount2(problems, "problem")} (${formatCount2(errors, "error")}, ${formatCount2(warnings, "warning")}).`;
6459
+ if ((summary?.fixableProblems ?? 0) > 0) {
6460
+ headline += ` ${formatCount2(summary.fixableProblems, "problem")} potentially fixable with --fix.`;
6461
+ }
6462
+ bullets.push(headline);
6463
+ } else {
6464
+ bullets.push(`- No lint errors visible: ${formatCount2(warnings, "warning")}.`);
6465
+ }
6466
+ const groups = /* @__PURE__ */ new Map();
6467
+ for (const violation of violations) {
6468
+ const group = groups.get(violation.rule) ?? {
6469
+ errors: 0,
6470
+ warnings: 0,
6471
+ files: /* @__PURE__ */ new Set()
6472
+ };
6473
+ if (violation.severity === "error") {
6474
+ group.errors += 1;
6475
+ } else {
6476
+ group.warnings += 1;
6477
+ }
6478
+ group.files.add(violation.file);
6479
+ groups.set(violation.rule, group);
6480
+ }
6481
+ const sortedGroups = [...groups.entries()].map(([rule, group]) => ({
6482
+ rule,
6483
+ errors: group.errors,
6484
+ warnings: group.warnings,
6485
+ total: group.errors + group.warnings,
6486
+ files: group.files
6487
+ })).sort((left, right) => {
6488
+ const leftHasErrors = left.errors > 0 ? 1 : 0;
6489
+ const rightHasErrors = right.errors > 0 ? 1 : 0;
6490
+ return rightHasErrors - leftHasErrors || right.total - left.total || left.rule.localeCompare(right.rule);
6491
+ });
6492
+ for (const group of sortedGroups.slice(0, 3)) {
6493
+ bullets.push(formatLintGroup(group));
6494
+ }
6495
+ if (sortedGroups.length > 3) {
6496
+ const overflowFiles = /* @__PURE__ */ new Set();
6497
+ for (const group of sortedGroups.slice(3)) {
6498
+ for (const file of group.files) {
6499
+ overflowFiles.add(file);
6500
+ }
6501
+ }
6502
+ let overflow = `- ${formatCount2(sortedGroups.length - 3, "more rule")}`;
6503
+ if (overflowFiles.size > 0) {
6504
+ overflow += ` across ${formatCount2(overflowFiles.size, "file")}`;
6505
+ }
6506
+ bullets.push(`${overflow}.`);
6507
+ }
6508
+ return bullets.join("\n");
6509
+ }
6510
+ function stripAnsiText(input) {
6511
+ return input.replace(/\u001b\[[0-9;]*m/g, "");
6512
+ }
6513
+ function normalizeBuildPath(file) {
6514
+ return file.replace(/\\/g, "/").replace(/^\.\//, "").trim();
6515
+ }
6516
+ function trimTrailingSentencePunctuation(input) {
6517
+ return input.replace(/[.:]+$/, "").trim();
6518
+ }
6519
+ function containsKnownBuildFailureSignal(input) {
6520
+ return /^ERROR in /m.test(input) || /^(?:[✘✗]\s*)?\[ERROR\]\s+/m.test(input) || /^error(?:\[E\d+\])?:\s+/m.test(input) || /^.+?\.go:\d+:\d+:\s+\S+/m.test(input) || /^.+?\.(?:c|cc|cpp|cxx|h|hpp|m|mm):\d+:\d+:\s*error:\s+/m.test(input) || /\berror\s+TS\d+:/m.test(input) || /^\s*npm ERR!/m.test(input) || /\bERR_PNPM_/m.test(input) || /^\s*error Command failed/m.test(input);
6521
+ }
6522
+ function detectExplicitBuildSuccess(input) {
6523
+ if (containsKnownBuildFailureSignal(input)) {
6524
+ return false;
6525
+ }
6526
+ return /\bcompiled successfully\b/i.test(input) || /^\s*Build succeeded\.?\s*$/im.test(input) || /\bcompiled with 0 errors?\b/i.test(input);
6527
+ }
6528
+ function inferBuildFailureCategory(message) {
6529
+ if (/module not found|can't resolve|could not resolve|cannot find module|no required module provides package/i.test(
6530
+ message
6531
+ )) {
6532
+ return "module-resolution";
6533
+ }
6534
+ if (/no matching export|does not provide an export named|missing export/i.test(message)) {
6535
+ return "missing-export";
6536
+ }
6537
+ if (/cannot find name|cannot find value|not found in this scope|undefined:|undeclared identifier/i.test(
6538
+ message
6539
+ )) {
6540
+ return "undefined-identifier";
6541
+ }
6542
+ if (/syntax error|unexpected token|expected ['"`;)]|expected .* after expression/i.test(message)) {
6543
+ return "syntax";
6544
+ }
6545
+ if (/\bTS\d+\b/.test(message) || /type .* is not assignable|type error|no matching overload/i.test(message)) {
6546
+ return "type";
6547
+ }
6548
+ return "generic";
6549
+ }
6550
+ function buildFailureSuggestion(category) {
6551
+ switch (category) {
6552
+ case "module-resolution":
6553
+ return "Install the missing package or fix the import path.";
6554
+ case "missing-export":
6555
+ return "Check the export name in the source module.";
6556
+ case "undefined-identifier":
6557
+ return "Define or import the missing identifier.";
6558
+ case "syntax":
6559
+ return "Fix the syntax error at the indicated location.";
6560
+ case "type":
6561
+ return "Fix the type error at the indicated location.";
6562
+ case "wrapper":
6563
+ return "Check the underlying build tool output above.";
6564
+ default:
6565
+ return "Fix the first reported error and rebuild.";
6566
+ }
6567
+ }
6568
+ function formatBuildFailureOutput(match) {
6569
+ const message = trimTrailingSentencePunctuation(match.message);
6570
+ const suggestion = buildFailureSuggestion(match.category);
6571
+ const displayFile = match.file ? compactDisplayFile(match.file) : null;
6572
+ if (displayFile && match.line !== null) {
6573
+ return `Build failed: ${message} in ${displayFile}:${match.line}. Fix: ${suggestion}`;
6574
+ }
6575
+ if (displayFile) {
6576
+ return `Build failed: ${message} in ${displayFile}. Fix: ${suggestion}`;
6577
+ }
6578
+ return `Build failed: ${message}. Fix: ${suggestion}`;
6579
+ }
6580
+ function extractWebpackBuildFailure(input) {
6581
+ const lines = stripAnsiText(input).split("\n").map((line) => line.trimEnd());
6582
+ for (let index = 0; index < lines.length; index += 1) {
6583
+ const match = lines[index]?.match(/^ERROR in (.+?)(?:\s+(\d+):(\d+))?$/);
6584
+ if (!match) {
6585
+ continue;
6586
+ }
6587
+ const candidates = [];
6588
+ for (let cursor = index + 1; cursor < Math.min(lines.length, index + 6); cursor += 1) {
6589
+ const candidate = lines[cursor]?.trim();
6590
+ if (!candidate) {
6591
+ continue;
6592
+ }
6593
+ if (/^ERROR in /.test(candidate) || /compiled with \d+ errors?/i.test(candidate)) {
6594
+ break;
6595
+ }
6596
+ if (/^(?:>|\|)|^\d+\s+\|/.test(candidate)) {
6597
+ continue;
6598
+ }
6599
+ candidates.push(candidate);
6600
+ }
6601
+ let message = "Compilation error";
6602
+ if (candidates.length > 0) {
6603
+ const preferred = candidates.find(
6604
+ (candidate) => !/^Module build failed\b/i.test(candidate) && !/^Error:\s+TypeScript compilation failed\b/i.test(candidate) && inferBuildFailureCategory(candidate) !== "generic"
6605
+ ) ?? candidates.find(
6606
+ (candidate) => !/^Module build failed\b/i.test(candidate) && !/^Error:\s+TypeScript compilation failed\b/i.test(candidate)
6607
+ ) ?? candidates[0];
6608
+ message = preferred ?? message;
6609
+ }
6610
+ return {
6611
+ message,
6612
+ file: normalizeBuildPath(match[1]),
6613
+ line: match[2] ? Number(match[2]) : null,
6614
+ column: match[3] ? Number(match[3]) : null,
6615
+ category: inferBuildFailureCategory(message)
6616
+ };
6617
+ }
6618
+ return null;
6619
+ }
6620
+ function extractViteImportAnalysisBuildFailure(input) {
6621
+ const lines = stripAnsiText(input).split("\n").map((line) => line.trim());
6622
+ for (const line of lines) {
6623
+ const match = line.match(
6624
+ /^\[plugin:vite:import-analysis\]\s+Failed to resolve import\s+"([^"]+)"\s+from\s+"([^"]+)"/i
6625
+ );
6626
+ if (!match) {
6627
+ continue;
6628
+ }
6629
+ return {
6630
+ message: `Failed to resolve import "${match[1]}"`,
6631
+ file: normalizeBuildPath(match[2]),
6632
+ line: null,
6633
+ column: null,
6634
+ category: "module-resolution"
6635
+ };
6636
+ }
6637
+ return null;
6638
+ }
6639
+ function extractEsbuildBuildFailure(input) {
6640
+ const lines = stripAnsiText(input).split("\n").map((line) => line.trimEnd());
6641
+ for (let index = 0; index < lines.length; index += 1) {
6642
+ const match = lines[index]?.match(/^(?:[✘✗]\s*)?\[ERROR\]\s*(.+)$/);
6643
+ if (!match) {
6644
+ continue;
6645
+ }
6646
+ const message = match[1].replace(/^\[vite\]\s*/i, "").trim();
6647
+ let file = null;
6648
+ let line = null;
6649
+ let column = null;
6650
+ for (let cursor = index + 1; cursor < Math.min(lines.length, index + 6); cursor += 1) {
6651
+ const locationMatch = lines[cursor]?.trim().match(/^(.+?):(\d+):(\d+):$/);
6652
+ if (!locationMatch) {
6653
+ continue;
6654
+ }
6655
+ file = normalizeBuildPath(locationMatch[1]);
6656
+ line = Number(locationMatch[2]);
6657
+ column = Number(locationMatch[3]);
6658
+ break;
6659
+ }
6660
+ return {
6661
+ message,
6662
+ file,
6663
+ line,
6664
+ column,
6665
+ category: inferBuildFailureCategory(message)
6666
+ };
6667
+ }
6668
+ return null;
6669
+ }
6670
+ function extractCargoBuildFailure(input) {
6671
+ if (!/^error(?:\[E\d+\])?:\s+/m.test(input) || !(/^\s*-->\s+/m.test(input) || /could not compile/i.test(input))) {
6672
+ return null;
6673
+ }
6674
+ const lines = stripAnsiText(input).split("\n").map((line) => line.trimEnd());
6675
+ for (let index = 0; index < lines.length; index += 1) {
6676
+ const match = lines[index]?.match(/^error(?:\[(E\d+)\])?:\s+(.+)$/);
6677
+ if (!match) {
6678
+ continue;
6679
+ }
6680
+ const code = match[1];
6681
+ const locationMatch = lines.slice(index + 1, index + 7).join("\n").match(/^\s*-->\s+(.+?):(\d+):(\d+)/m);
6682
+ return {
6683
+ message: code ? `${code}: ${match[2].trim()}` : match[2].trim(),
6684
+ file: locationMatch ? normalizeBuildPath(locationMatch[1]) : null,
6685
+ line: locationMatch ? Number(locationMatch[2]) : null,
6686
+ column: locationMatch ? Number(locationMatch[3]) : null,
6687
+ category: inferBuildFailureCategory(match[2])
6688
+ };
6689
+ }
6690
+ return null;
6691
+ }
6692
+ function extractCompilerStyleBuildFailure(input) {
6693
+ const lines = stripAnsiText(input).split("\n").map((line) => line.trimEnd());
6694
+ for (const rawLine of lines) {
6695
+ let match = rawLine.match(
6696
+ /^(.+?\.(?:c|cc|cpp|cxx|h|hpp|m|mm)):([0-9]+):([0-9]+):\s*error:\s+(.+)$/
6697
+ );
6698
+ if (match) {
6699
+ return {
6700
+ message: match[4].trim(),
6701
+ file: normalizeBuildPath(match[1]),
6702
+ line: Number(match[2]),
6703
+ column: Number(match[3]),
6704
+ category: inferBuildFailureCategory(match[4])
6705
+ };
6706
+ }
6707
+ match = rawLine.match(/^(.+?\.go):([0-9]+):([0-9]+):\s+(.+)$/);
6708
+ if (match && !/^\s*warning:/i.test(match[4])) {
6709
+ return {
6710
+ message: match[4].trim(),
6711
+ file: normalizeBuildPath(match[1]),
6712
+ line: Number(match[2]),
6713
+ column: Number(match[3]),
6714
+ category: inferBuildFailureCategory(match[4])
6715
+ };
6716
+ }
6717
+ }
6718
+ return null;
6719
+ }
6720
+ function extractTscBuildFailure(input) {
6721
+ const diagnostics = parseTscErrors(input);
6722
+ const first = diagnostics[0];
6723
+ if (!first) {
6724
+ return null;
6725
+ }
6726
+ return {
6727
+ message: `${first.code}: ${first.message}`,
6728
+ file: first.file,
6729
+ line: first.line,
6730
+ column: first.column,
6731
+ category: inferBuildFailureCategory(`${first.code}: ${first.message}`)
6732
+ };
6733
+ }
6734
+ function extractWrapperBuildFailure(input) {
6735
+ if (!/^\s*npm ERR!|\bERR_PNPM_|^\s*error Command failed/m.test(input)) {
6736
+ return null;
6737
+ }
6738
+ const npmCommandMatch = input.match(/^\s*npm ERR!\s+.*?\bbuild:\s+`([^`]+)`/m);
6739
+ const genericCommandMatch = input.match(/^\s*.+?\s+build:\s+`([^`]+)`/m);
6740
+ const command = npmCommandMatch?.[1] ?? genericCommandMatch?.[1] ?? null;
6741
+ return {
6742
+ message: command ? `build script \`${command}\` failed` : "the build script failed",
6743
+ file: null,
6744
+ line: null,
6745
+ column: null,
6746
+ category: "wrapper"
6747
+ };
6748
+ }
6749
+ function buildFailureHeuristic(input) {
6750
+ if (input.trim().length === 0) {
6751
+ return null;
6752
+ }
6753
+ if (detectExplicitBuildSuccess(input)) {
6754
+ return "Build succeeded.";
6755
+ }
6756
+ const match = extractViteImportAnalysisBuildFailure(input) ?? extractWebpackBuildFailure(input) ?? extractEsbuildBuildFailure(input) ?? extractCargoBuildFailure(input) ?? extractCompilerStyleBuildFailure(input) ?? extractTscBuildFailure(input) ?? extractWrapperBuildFailure(input);
6757
+ if (!match) {
6758
+ return null;
6759
+ }
6760
+ return formatBuildFailureOutput(match);
6761
+ }
5778
6762
  function applyHeuristicPolicy(policyName, input, detail) {
5779
6763
  if (!policyName) {
5780
6764
  return null;
@@ -5788,6 +6772,15 @@ function applyHeuristicPolicy(policyName, input, detail) {
5788
6772
  if (policyName === "test-status") {
5789
6773
  return testStatusHeuristic(input, detail);
5790
6774
  }
6775
+ if (policyName === "typecheck-summary") {
6776
+ return typecheckSummaryHeuristic(input);
6777
+ }
6778
+ if (policyName === "lint-failures") {
6779
+ return lintFailuresHeuristic(input);
6780
+ }
6781
+ if (policyName === "build-failure") {
6782
+ return buildFailureHeuristic(input);
6783
+ }
5791
6784
  return null;
5792
6785
  }
5793
6786
 
@@ -5898,6 +6891,138 @@ function escapeRegExp2(value) {
5898
6891
  function unique2(values) {
5899
6892
  return [...new Set(values)];
5900
6893
  }
6894
+ var genericBucketSearchTerms = /* @__PURE__ */ new Set([
6895
+ "runtimeerror",
6896
+ "typeerror",
6897
+ "error",
6898
+ "exception",
6899
+ "failed",
6900
+ "failure",
6901
+ "visible failure",
6902
+ "failing tests",
6903
+ "setup failures",
6904
+ "runtime failure",
6905
+ "assertion failed",
6906
+ "network",
6907
+ "permission",
6908
+ "configuration"
6909
+ ]);
6910
+ function normalizeSearchTerm(value) {
6911
+ return value.replace(/^['"`]+|['"`]+$/g, "").trim();
6912
+ }
6913
+ function isHighSignalSearchTerm(term) {
6914
+ const normalized = normalizeSearchTerm(term);
6915
+ if (normalized.length < 4) {
6916
+ return false;
6917
+ }
6918
+ const lower = normalized.toLowerCase();
6919
+ if (genericBucketSearchTerms.has(lower)) {
6920
+ return false;
6921
+ }
6922
+ if (/^(runtime|type|assertion|network|permission|configuration)\b/i.test(normalized)) {
6923
+ return false;
6924
+ }
6925
+ return true;
6926
+ }
6927
+ function scoreSearchTerm(term) {
6928
+ const normalized = normalizeSearchTerm(term);
6929
+ let score = normalized.length;
6930
+ if (/^[A-Z][A-Z0-9_]{2,}$/.test(normalized)) {
6931
+ score += 80;
6932
+ }
6933
+ if (/^TS\d+$/.test(normalized)) {
6934
+ score += 70;
6935
+ }
6936
+ if (/^[45]\d\d\b/.test(normalized) || /\bHTTPError:\s*[45]\d\d\b/i.test(normalized)) {
6937
+ score += 60;
6938
+ }
6939
+ if (normalized.includes("/") || normalized.includes("\\")) {
6940
+ score += 50;
6941
+ }
6942
+ if (/\b[A-Za-z0-9_.-]+\.[A-Za-z0-9_.-]+\b/.test(normalized)) {
6943
+ score += 40;
6944
+ }
6945
+ if (/['"`]/.test(term)) {
6946
+ score += 30;
6947
+ }
6948
+ if (normalized.includes("::")) {
6949
+ score += 25;
6950
+ }
6951
+ return score;
6952
+ }
6953
+ function collectCandidateSearchTerms(value) {
6954
+ const candidates = [];
6955
+ const normalized = value.trim();
6956
+ if (!normalized) {
6957
+ return candidates;
6958
+ }
6959
+ for (const match of normalized.matchAll(/['"`]([^'"`]{4,})['"`]/g)) {
6960
+ candidates.push(match[1]);
6961
+ }
6962
+ for (const match of normalized.matchAll(/\b[A-Z][A-Z0-9_]{2,}\b/g)) {
6963
+ candidates.push(match[0]);
6964
+ }
6965
+ for (const match of normalized.matchAll(/\bTS\d+\b/g)) {
6966
+ candidates.push(match[0]);
6967
+ }
6968
+ for (const match of normalized.matchAll(/\bHTTPError:\s*[45]\d\d\b/gi)) {
6969
+ candidates.push(match[0]);
6970
+ }
6971
+ for (const match of normalized.matchAll(/\/[A-Za-z0-9_./:{}-]{4,}/g)) {
6972
+ candidates.push(match[0]);
6973
+ }
6974
+ for (const match of normalized.matchAll(/\b(?:[A-Za-z0-9_.-]+\/)+[A-Za-z0-9_.-]+\b/g)) {
6975
+ candidates.push(match[0]);
6976
+ }
6977
+ for (const match of normalized.matchAll(/\b[A-Za-z0-9_.-]+\.[A-Za-z0-9_.-]+\b/g)) {
6978
+ candidates.push(match[0]);
6979
+ }
6980
+ const detail = normalized.split(":").slice(1).join(":").trim();
6981
+ if (detail.length >= 8) {
6982
+ candidates.push(detail);
6983
+ }
6984
+ return candidates;
6985
+ }
6986
+ function extractBucketSearchTerms(args) {
6987
+ const sources = [
6988
+ args.bucket.root_cause,
6989
+ ...args.bucket.evidence,
6990
+ ...args.readTargets.filter((target) => target.bucket_index === args.bucket.bucket_index).flatMap((target) => [target.context_hint.search_hint ?? "", target.file])
6991
+ ];
6992
+ const prioritized = unique2(
6993
+ sources.flatMap((value) => collectCandidateSearchTerms(value)).filter(isHighSignalSearchTerm)
6994
+ ).sort((left, right) => {
6995
+ const delta = scoreSearchTerm(right) - scoreSearchTerm(left);
6996
+ if (delta !== 0) {
6997
+ return delta;
6998
+ }
6999
+ return left.localeCompare(right);
7000
+ });
7001
+ if (prioritized.length > 0) {
7002
+ return prioritized.slice(0, 6);
7003
+ }
7004
+ const fallbackTerms = unique2(
7005
+ [...args.bucket.evidence, args.bucket.root_cause].flatMap((value) => value.split(/->|:/).map((part) => normalizeSearchTerm(part))).filter(isHighSignalSearchTerm)
7006
+ );
7007
+ return fallbackTerms.slice(0, 4);
7008
+ }
7009
+ function clusterIndexes(indexes, maxGap = 12) {
7010
+ if (indexes.length === 0) {
7011
+ return [];
7012
+ }
7013
+ const clusters = [];
7014
+ let currentCluster = [indexes[0]];
7015
+ for (const index of indexes.slice(1)) {
7016
+ if (index - currentCluster[currentCluster.length - 1] <= maxGap) {
7017
+ currentCluster.push(index);
7018
+ continue;
7019
+ }
7020
+ clusters.push(currentCluster);
7021
+ currentCluster = [index];
7022
+ }
7023
+ clusters.push(currentCluster);
7024
+ return clusters;
7025
+ }
5901
7026
  function buildLineWindows(args) {
5902
7027
  const selected = /* @__PURE__ */ new Set();
5903
7028
  for (const index of args.indexes) {
@@ -5913,6 +7038,12 @@ function buildLineWindows(args) {
5913
7038
  }
5914
7039
  return [...selected].sort((left, right) => left - right).map((index) => args.lines[index]);
5915
7040
  }
7041
+ function buildPriorityLineGroup(args) {
7042
+ return unique2([
7043
+ ...args.indexes.map((index) => args.lines[index]).filter(Boolean),
7044
+ ...buildLineWindows(args)
7045
+ ]);
7046
+ }
5916
7047
  function collapseSelectedLines(args) {
5917
7048
  if (args.lines.length === 0) {
5918
7049
  return args.fallback();
@@ -6061,15 +7192,16 @@ function buildTestStatusRawSlice(args) {
6061
7192
  ) ? index : -1
6062
7193
  ).filter((index) => index >= 0);
6063
7194
  const bucketGroups = args.contract.main_buckets.map((bucket) => {
6064
- const bucketTerms = unique2(
6065
- [bucket.root_cause, ...bucket.evidence].map((value) => value.split(":").at(-1)?.trim() ?? value.trim()).filter((value) => value.length >= 4)
6066
- );
7195
+ const bucketTerms = extractBucketSearchTerms({
7196
+ bucket,
7197
+ readTargets: args.contract.read_targets
7198
+ });
6067
7199
  const indexes = lines.map(
6068
7200
  (line, index) => bucketTerms.some((term) => new RegExp(escapeRegExp2(term), "i").test(line)) ? index : -1
6069
7201
  ).filter((index) => index >= 0);
6070
7202
  return unique2([
6071
7203
  ...indexes.map((index) => lines[index]).filter(Boolean),
6072
- ...buildLineWindows({
7204
+ ...buildPriorityLineGroup({
6073
7205
  lines,
6074
7206
  indexes,
6075
7207
  radius: 2,
@@ -6077,26 +7209,55 @@ function buildTestStatusRawSlice(args) {
6077
7209
  })
6078
7210
  ]);
6079
7211
  });
6080
- const targetGroups = args.contract.read_targets.map(
6081
- (target) => buildLineWindows({
7212
+ const targetGroups = args.contract.read_targets.flatMap((target) => {
7213
+ const searchHintIndexes = findSearchHintIndexes({
6082
7214
  lines,
6083
- indexes: unique2([
6084
- ...findReadTargetIndexes({
6085
- lines,
6086
- file: target.file,
6087
- line: target.line,
6088
- contextHint: target.context_hint
6089
- }),
6090
- ...findSearchHintIndexes({
6091
- lines,
6092
- searchHint: target.context_hint.search_hint
6093
- })
6094
- ]),
6095
- radius: target.line === null ? 1 : 2,
6096
- maxLines: target.line === null ? 6 : 8
7215
+ searchHint: target.context_hint.search_hint
7216
+ });
7217
+ const fileIndexes = findReadTargetIndexes({
7218
+ lines,
7219
+ file: target.file,
7220
+ line: target.line,
7221
+ contextHint: target.context_hint
7222
+ });
7223
+ const radius = target.line === null ? 1 : 2;
7224
+ const maxLines = target.line === null ? 6 : 8;
7225
+ const groups = [
7226
+ searchHintIndexes.length > 0 ? buildPriorityLineGroup({
7227
+ lines,
7228
+ indexes: searchHintIndexes,
7229
+ radius,
7230
+ maxLines
7231
+ }) : null,
7232
+ fileIndexes.length > 0 ? buildPriorityLineGroup({
7233
+ lines,
7234
+ indexes: fileIndexes,
7235
+ radius,
7236
+ maxLines
7237
+ }) : null
7238
+ ].filter((group) => group !== null && group.length > 0);
7239
+ if (groups.length > 0) {
7240
+ return groups;
7241
+ }
7242
+ return [
7243
+ buildPriorityLineGroup({
7244
+ lines,
7245
+ indexes: unique2([...searchHintIndexes, ...fileIndexes]),
7246
+ radius,
7247
+ maxLines
7248
+ })
7249
+ ];
7250
+ });
7251
+ const failureHeaderIndexes = lines.map((line, index) => /\b(FAILED|ERROR)\b/.test(line) ? index : -1).filter((index) => index >= 0);
7252
+ const failureIndexes = (failureHeaderIndexes.length > 0 ? failureHeaderIndexes : lines.map((line, index) => /^E\s/.test(line) ? index : -1).filter((index) => index >= 0)).filter((index) => index >= 0);
7253
+ const failureHeaderGroups = clusterIndexes(failureIndexes).slice(0, 8).map(
7254
+ (cluster) => buildPriorityLineGroup({
7255
+ lines,
7256
+ indexes: cluster,
7257
+ radius: 1,
7258
+ maxLines: 8
6097
7259
  })
6098
- );
6099
- const failureIndexes = lines.map((line, index) => /\b(FAILED|ERROR)\b/.test(line) || /^E\s/.test(line) ? index : -1).filter((index) => index >= 0);
7260
+ ).filter((group) => group.length > 0);
6100
7261
  const selected = collapseSelectedLineGroups({
6101
7262
  groups: [
6102
7263
  ...targetGroups,
@@ -6110,12 +7271,14 @@ function buildTestStatusRawSlice(args) {
6110
7271
  })
6111
7272
  ]),
6112
7273
  ...bucketGroups,
6113
- buildLineWindows({
6114
- lines,
6115
- indexes: failureIndexes,
6116
- radius: 1,
6117
- maxLines: 24
6118
- })
7274
+ ...failureHeaderGroups.length > 0 ? failureHeaderGroups : [
7275
+ buildLineWindows({
7276
+ lines,
7277
+ indexes: failureIndexes,
7278
+ radius: 1,
7279
+ maxLines: 24
7280
+ })
7281
+ ]
6119
7282
  ],
6120
7283
  maxInputChars: args.config.maxInputChars,
6121
7284
  fallback: () => truncateInput(args.input, {
@@ -6256,7 +7419,8 @@ function withInsufficientHint(args) {
6256
7419
  return buildInsufficientSignalOutput({
6257
7420
  presetName: args.request.presetName,
6258
7421
  originalLength: args.prepared.meta.originalLength,
6259
- truncatedApplied: args.prepared.meta.truncatedApplied
7422
+ truncatedApplied: args.prepared.meta.truncatedApplied,
7423
+ recognizedRunner: detectTestRunner(args.prepared.redacted)
6260
7424
  });
6261
7425
  }
6262
7426
  async function generateWithRetry(args) {
@@ -6316,6 +7480,38 @@ function renderTestStatusDecisionOutput(args) {
6316
7480
  return args.decision.standardText;
6317
7481
  }
6318
7482
  function buildTestStatusProviderFailureDecision(args) {
7483
+ const concreteReadTarget = args.baseDecision.contract.read_targets.find(
7484
+ (target) => Boolean(target.file)
7485
+ );
7486
+ const hasUnknownBucket = args.baseDecision.contract.main_buckets.some(
7487
+ (bucket) => bucket.root_cause.startsWith("unknown ")
7488
+ );
7489
+ if (concreteReadTarget && !hasUnknownBucket) {
7490
+ return buildTestStatusDiagnoseContract({
7491
+ input: args.input,
7492
+ analysis: args.analysis,
7493
+ resolvedTests: args.baseDecision.contract.resolved_tests,
7494
+ remainingTests: args.baseDecision.contract.remaining_tests,
7495
+ contractOverrides: {
7496
+ ...args.baseDecision.contract,
7497
+ diagnosis_complete: false,
7498
+ raw_needed: false,
7499
+ additional_source_read_likely_low_value: false,
7500
+ read_raw_only_if: null,
7501
+ decision: "read_source",
7502
+ provider_used: true,
7503
+ provider_confidence: null,
7504
+ provider_failed: true,
7505
+ raw_slice_used: args.rawSliceUsed,
7506
+ raw_slice_strategy: args.rawSliceStrategy,
7507
+ next_best_action: {
7508
+ code: "read_source_for_bucket",
7509
+ bucket_index: args.baseDecision.contract.dominant_blocker_bucket_index ?? concreteReadTarget.bucket_index,
7510
+ note: `Provider follow-up failed (${args.reason}). The heuristic anchor is concrete enough to inspect source for the current bucket before reading raw traceback.`
7511
+ }
7512
+ }
7513
+ });
7514
+ }
6319
7515
  const shouldZoomFirst = args.request.detail !== "verbose";
6320
7516
  return buildTestStatusDiagnoseContract({
6321
7517
  input: args.input,
@@ -6342,7 +7538,7 @@ function buildTestStatusProviderFailureDecision(args) {
6342
7538
  }
6343
7539
  });
6344
7540
  }
6345
- async function runSift(request) {
7541
+ async function runSiftCore(request, recorder) {
6346
7542
  const prepared = prepareInput(request.stdin, request.config.input);
6347
7543
  const heuristicInput = prepared.redacted;
6348
7544
  const heuristicInputTruncated = false;
@@ -6428,6 +7624,7 @@ async function runSift(request) {
6428
7624
  finalOutput
6429
7625
  });
6430
7626
  }
7627
+ recorder?.heuristic();
6431
7628
  return finalOutput;
6432
7629
  }
6433
7630
  if (testStatusDecision && testStatusAnalysis) {
@@ -6527,6 +7724,7 @@ async function runSift(request) {
6527
7724
  providerInputChars: providerPrepared2.truncated.length,
6528
7725
  providerOutputChars: result.text.length
6529
7726
  });
7727
+ recorder?.provider(result.usage);
6530
7728
  return finalOutput;
6531
7729
  } catch (error) {
6532
7730
  const reason = error instanceof Error ? error.message : "unknown_error";
@@ -6561,6 +7759,7 @@ async function runSift(request) {
6561
7759
  rawSliceChars: rawSlice.text.length,
6562
7760
  providerInputChars: providerPrepared2.truncated.length
6563
7761
  });
7762
+ recorder?.fallback();
6564
7763
  return finalOutput;
6565
7764
  }
6566
7765
  }
@@ -6617,6 +7816,7 @@ async function runSift(request) {
6617
7816
  })) {
6618
7817
  throw new Error("Model output rejected by quality gate");
6619
7818
  }
7819
+ recorder?.provider(result.usage);
6620
7820
  return withInsufficientHint({
6621
7821
  output: normalizeOutput(result.text, providerPrompt.responseMode),
6622
7822
  request,
@@ -6624,6 +7824,7 @@ async function runSift(request) {
6624
7824
  });
6625
7825
  } catch (error) {
6626
7826
  const reason = error instanceof Error ? error.message : "unknown_error";
7827
+ recorder?.fallback();
6627
7828
  return withInsufficientHint({
6628
7829
  output: buildFallbackOutput({
6629
7830
  format: request.format,
@@ -6637,6 +7838,72 @@ async function runSift(request) {
6637
7838
  });
6638
7839
  }
6639
7840
  }
7841
+ async function runSift(request) {
7842
+ return runSiftCore(request);
7843
+ }
7844
+ async function runSiftWithStats(request) {
7845
+ if (request.dryRun) {
7846
+ return {
7847
+ output: await runSiftCore(request),
7848
+ stats: null
7849
+ };
7850
+ }
7851
+ const startedAt = Date.now();
7852
+ let layer = "fallback";
7853
+ let providerCalled = false;
7854
+ let totalTokens = null;
7855
+ const output = await runSiftCore(request, {
7856
+ heuristic() {
7857
+ layer = "heuristic";
7858
+ providerCalled = false;
7859
+ totalTokens = null;
7860
+ },
7861
+ provider(usage) {
7862
+ layer = "provider";
7863
+ providerCalled = true;
7864
+ totalTokens = usage?.totalTokens ?? null;
7865
+ },
7866
+ fallback() {
7867
+ layer = "fallback";
7868
+ providerCalled = true;
7869
+ totalTokens = null;
7870
+ }
7871
+ });
7872
+ return {
7873
+ output,
7874
+ stats: {
7875
+ layer,
7876
+ providerCalled,
7877
+ totalTokens,
7878
+ durationMs: Date.now() - startedAt,
7879
+ presetName: request.presetName
7880
+ }
7881
+ };
7882
+ }
7883
+
7884
+ // src/core/stats.ts
7885
+ import pc3 from "picocolors";
7886
+ function formatDuration(durationMs) {
7887
+ return durationMs >= 1e3 ? `${(durationMs / 1e3).toFixed(1)}s` : `${durationMs}ms`;
7888
+ }
7889
+ function formatStatsFooter(stats) {
7890
+ const duration = formatDuration(stats.durationMs);
7891
+ if (stats.layer === "heuristic") {
7892
+ return `[sift: heuristic \u2022 LLM skipped \u2022 summary ${duration}]`;
7893
+ }
7894
+ if (stats.layer === "provider") {
7895
+ const tokenSegment = stats.totalTokens !== null ? ` \u2022 ${stats.totalTokens} tokens` : "";
7896
+ return `[sift: provider \u2022 LLM used${tokenSegment} \u2022 summary ${duration}]`;
7897
+ }
7898
+ return `[sift: fallback \u2022 provider failed \u2022 summary ${duration}]`;
7899
+ }
7900
+ function emitStatsFooter(args) {
7901
+ if (args.quiet || !args.stats || !process.stderr.isTTY) {
7902
+ return;
7903
+ }
7904
+ process.stderr.write(`${pc3.dim(formatStatsFooter(args.stats))}
7905
+ `);
7906
+ }
6640
7907
 
6641
7908
  // src/core/testStatusState.ts
6642
7909
  import fs5 from "fs";
@@ -7114,7 +8381,7 @@ async function runEscalate(request) {
7114
8381
  const detail = resolveEscalationDetail(state, request.detail, request.showRaw);
7115
8382
  if (request.verbose) {
7116
8383
  process.stderr.write(
7117
- `${pc3.dim("sift")} escalate detail=${detail} cached_detail=${state.detail} command=${state.commandPreview}
8384
+ `${pc4.dim("sift")} escalate detail=${detail} cached_detail=${state.detail} command=${state.commandPreview}
7118
8385
  `
7119
8386
  );
7120
8387
  }
@@ -7124,7 +8391,7 @@ async function runEscalate(request) {
7124
8391
  process.stderr.write("\n");
7125
8392
  }
7126
8393
  }
7127
- let output = await runSift({
8394
+ const result = await runSiftWithStats({
7128
8395
  question: request.question,
7129
8396
  format: request.format,
7130
8397
  goal: request.goal,
@@ -7141,6 +8408,7 @@ async function runEscalate(request) {
7141
8408
  remainingSubsetAvailable: Boolean(state.pytest?.subsetCapable) && (state.pytest?.failingNodeIds.length ?? 0) > 0
7142
8409
  }
7143
8410
  });
8411
+ let output = result.output;
7144
8412
  if (isInsufficientSignalOutput(output)) {
7145
8413
  output = buildInsufficientSignalOutput({
7146
8414
  presetName: "test-status",
@@ -7151,6 +8419,10 @@ async function runEscalate(request) {
7151
8419
  }
7152
8420
  process.stdout.write(`${output}
7153
8421
  `);
8422
+ emitStatsFooter({
8423
+ stats: result.stats,
8424
+ quiet: Boolean(request.quiet)
8425
+ });
7154
8426
  try {
7155
8427
  writeCachedTestStatusRun({
7156
8428
  ...state,
@@ -7159,7 +8431,7 @@ async function runEscalate(request) {
7159
8431
  } catch (error) {
7160
8432
  if (request.verbose) {
7161
8433
  const reason = error instanceof Error ? error.message : "unknown_error";
7162
- process.stderr.write(`${pc3.dim("sift")} cache_write=failed reason=${reason}
8434
+ process.stderr.write(`${pc4.dim("sift")} cache_write=failed reason=${reason}
7163
8435
  `);
7164
8436
  }
7165
8437
  }
@@ -7169,7 +8441,7 @@ async function runEscalate(request) {
7169
8441
  // src/core/exec.ts
7170
8442
  import { spawn } from "child_process";
7171
8443
  import { constants as osConstants } from "os";
7172
- import pc4 from "picocolors";
8444
+ import pc5 from "picocolors";
7173
8445
 
7174
8446
  // src/core/gate.ts
7175
8447
  var FAIL_ON_SUPPORTED_PRESETS = /* @__PURE__ */ new Set(["infra-risk", "audit-critical"]);
@@ -7499,7 +8771,7 @@ async function runExec(request) {
7499
8771
  const previousCachedRun = shouldCacheTestStatusBase ? tryReadCachedTestStatusRun() : null;
7500
8772
  if (request.config.runtime.verbose) {
7501
8773
  process.stderr.write(
7502
- `${pc4.dim("sift")} exec mode=${hasShellCommand ? "shell" : "argv"} command=${commandPreview}
8774
+ `${pc5.dim("sift")} exec mode=${hasShellCommand ? "shell" : "argv"} command=${commandPreview}
7503
8775
  `
7504
8776
  );
7505
8777
  }
@@ -7528,7 +8800,7 @@ async function runExec(request) {
7528
8800
  }
7529
8801
  bypassed = true;
7530
8802
  if (request.config.runtime.verbose) {
7531
- process.stderr.write(`${pc4.dim("sift")} bypass=interactive-prompt
8803
+ process.stderr.write(`${pc5.dim("sift")} bypass=interactive-prompt
7532
8804
  `);
7533
8805
  }
7534
8806
  process.stderr.write(capture.render());
@@ -7557,15 +8829,16 @@ async function runExec(request) {
7557
8829
  const shouldCacheTestStatus = shouldCacheTestStatusBase && !useWatchFlow;
7558
8830
  if (request.config.runtime.verbose) {
7559
8831
  process.stderr.write(
7560
- `${pc4.dim("sift")} child_exit=${exitCode} captured_chars=${capture.getTotalChars()} capture_truncated=${capture.wasTruncated()}
8832
+ `${pc5.dim("sift")} child_exit=${exitCode} captured_chars=${capture.getTotalChars()} capture_truncated=${capture.wasTruncated()}
7561
8833
  `
7562
8834
  );
7563
8835
  }
7564
8836
  if (autoWatchDetected) {
7565
- process.stderr.write(`${pc4.dim("sift")} auto-watch=detected
8837
+ process.stderr.write(`${pc5.dim("sift")} auto-watch=detected
7566
8838
  `);
7567
8839
  }
7568
8840
  if (!bypassed) {
8841
+ const reductionStartedAt = Date.now();
7569
8842
  if (request.showRaw && capturedOutput.length > 0) {
7570
8843
  process.stderr.write(capturedOutput);
7571
8844
  if (!capturedOutput.endsWith("\n")) {
@@ -7580,12 +8853,22 @@ async function runExec(request) {
7580
8853
  if (execSuccessShortcut && !request.dryRun) {
7581
8854
  if (request.config.runtime.verbose) {
7582
8855
  process.stderr.write(
7583
- `${pc4.dim("sift")} exec_shortcut=${request.presetName}
8856
+ `${pc5.dim("sift")} exec_shortcut=${request.presetName}
7584
8857
  `
7585
8858
  );
7586
8859
  }
7587
8860
  process.stdout.write(`${execSuccessShortcut}
7588
8861
  `);
8862
+ emitStatsFooter({
8863
+ stats: {
8864
+ layer: "heuristic",
8865
+ providerCalled: false,
8866
+ totalTokens: null,
8867
+ durationMs: Date.now() - reductionStartedAt,
8868
+ presetName: request.presetName
8869
+ },
8870
+ quiet: Boolean(request.quiet)
8871
+ });
7589
8872
  return exitCode;
7590
8873
  }
7591
8874
  if (useWatchFlow) {
@@ -7598,7 +8881,8 @@ async function runExec(request) {
7598
8881
  presetName: request.presetName,
7599
8882
  originalLength: capture.getTotalChars(),
7600
8883
  truncatedApplied: capture.wasTruncated(),
7601
- exitCode
8884
+ exitCode,
8885
+ recognizedRunner: detectTestRunner(capturedOutput)
7602
8886
  });
7603
8887
  }
7604
8888
  process.stdout.write(`${output2}
@@ -7626,7 +8910,7 @@ async function runExec(request) {
7626
8910
  previous: previousCachedRun,
7627
8911
  current: currentCachedRun
7628
8912
  }) : null;
7629
- let output = await runSift({
8913
+ const result = await runSiftWithStats({
7630
8914
  ...request,
7631
8915
  stdin: capturedOutput,
7632
8916
  analysisContext: request.skipCacheWrite && request.presetName === "test-status" ? [
@@ -7645,13 +8929,15 @@ async function runExec(request) {
7645
8929
  )
7646
8930
  } : request.testStatusContext
7647
8931
  });
8932
+ let output = result.output;
7648
8933
  if (shouldCacheTestStatus) {
7649
8934
  if (isInsufficientSignalOutput(output)) {
7650
8935
  output = buildInsufficientSignalOutput({
7651
8936
  presetName: request.presetName,
7652
8937
  originalLength: capture.getTotalChars(),
7653
8938
  truncatedApplied: capture.wasTruncated(),
7654
- exitCode
8939
+ exitCode,
8940
+ recognizedRunner: detectTestRunner(capturedOutput)
7655
8941
  });
7656
8942
  }
7657
8943
  if (request.diff && !request.dryRun && previousCachedRun && currentCachedRun) {
@@ -7684,7 +8970,7 @@ ${output}`;
7684
8970
  } catch (error) {
7685
8971
  if (request.config.runtime.verbose) {
7686
8972
  const reason = error instanceof Error ? error.message : "unknown_error";
7687
- process.stderr.write(`${pc4.dim("sift")} cache_write=failed reason=${reason}
8973
+ process.stderr.write(`${pc5.dim("sift")} cache_write=failed reason=${reason}
7688
8974
  `);
7689
8975
  }
7690
8976
  }
@@ -7694,11 +8980,16 @@ ${output}`;
7694
8980
  presetName: request.presetName,
7695
8981
  originalLength: capture.getTotalChars(),
7696
8982
  truncatedApplied: capture.wasTruncated(),
7697
- exitCode
8983
+ exitCode,
8984
+ recognizedRunner: detectTestRunner(capturedOutput)
7698
8985
  });
7699
8986
  }
7700
8987
  process.stdout.write(`${output}
7701
8988
  `);
8989
+ emitStatsFooter({
8990
+ stats: result.stats,
8991
+ quiet: Boolean(request.quiet)
8992
+ });
7702
8993
  if (request.failOn && !request.dryRun && exitCode === 0 && supportsFailOnPreset(request.presetName) && evaluateGate({
7703
8994
  presetName: request.presetName,
7704
8995
  output
@@ -7788,6 +9079,7 @@ var defaultCliDeps = {
7788
9079
  evaluateGate,
7789
9080
  readStdin,
7790
9081
  runSift,
9082
+ runSiftWithStats,
7791
9083
  runWatch,
7792
9084
  looksLikeWatchStream,
7793
9085
  getPreset
@@ -7896,7 +9188,7 @@ function applySharedOptions(command) {
7896
9188
  ).option(
7897
9189
  "--fail-on",
7898
9190
  "Fail with exit code 1 when a supported built-in preset produces a blocking result"
7899
- ).option("--config <path>", "Path to config file").option("--verbose", "Enable verbose stderr logging");
9191
+ ).option("--config <path>", "Path to config file").option("--quiet", "Suppress the stats footer on stderr").option("--verbose", "Enable verbose stderr logging");
7900
9192
  }
7901
9193
  function normalizeDetail(value) {
7902
9194
  if (value === void 0 || value === null || value === "") {
@@ -7995,21 +9287,25 @@ function createCliApp(args = {}) {
7995
9287
  stderr.write("\n");
7996
9288
  }
7997
9289
  }
7998
- const output = deps.looksLikeWatchStream(stdin) ? await deps.runWatch({
7999
- question: input.question,
8000
- format: input.format,
8001
- goal: input.goal,
8002
- stdin,
8003
- config,
8004
- dryRun: Boolean(input.options.dryRun),
8005
- showRaw: Boolean(input.options.showRaw),
8006
- includeTestIds: Boolean(input.options.includeTestIds),
8007
- detail: input.detail,
8008
- presetName: input.presetName,
8009
- policyName: input.policyName,
8010
- outputContract: input.outputContract,
8011
- fallbackJson: input.fallbackJson
8012
- }) : await deps.runSift({
9290
+ const isWatchStream = deps.looksLikeWatchStream(stdin);
9291
+ const result = isWatchStream ? {
9292
+ output: await deps.runWatch({
9293
+ question: input.question,
9294
+ format: input.format,
9295
+ goal: input.goal,
9296
+ stdin,
9297
+ config,
9298
+ dryRun: Boolean(input.options.dryRun),
9299
+ showRaw: Boolean(input.options.showRaw),
9300
+ includeTestIds: Boolean(input.options.includeTestIds),
9301
+ detail: input.detail,
9302
+ presetName: input.presetName,
9303
+ policyName: input.policyName,
9304
+ outputContract: input.outputContract,
9305
+ fallbackJson: input.fallbackJson
9306
+ }),
9307
+ stats: null
9308
+ } : await deps.runSiftWithStats({
8013
9309
  question: input.question,
8014
9310
  format: input.format,
8015
9311
  goal: input.goal,
@@ -8024,8 +9320,13 @@ function createCliApp(args = {}) {
8024
9320
  outputContract: input.outputContract,
8025
9321
  fallbackJson: input.fallbackJson
8026
9322
  });
9323
+ const output = result.output;
8027
9324
  stdout.write(`${output}
8028
9325
  `);
9326
+ emitStatsFooter({
9327
+ stats: result.stats,
9328
+ quiet: Boolean(input.options.quiet)
9329
+ });
8029
9330
  if (Boolean(input.options.failOn) && !Boolean(input.options.dryRun) && input.presetName && deps.evaluateGate({
8030
9331
  presetName: input.presetName,
8031
9332
  output
@@ -8055,6 +9356,7 @@ function createCliApp(args = {}) {
8055
9356
  dryRun: Boolean(input.options.dryRun),
8056
9357
  diff: input.diff,
8057
9358
  failOn: Boolean(input.options.failOn),
9359
+ quiet: Boolean(input.options.quiet),
8058
9360
  showRaw: Boolean(input.options.showRaw),
8059
9361
  includeTestIds: Boolean(input.options.includeTestIds),
8060
9362
  watch: Boolean(input.options.watch),
@@ -8234,6 +9536,7 @@ function createCliApp(args = {}) {
8234
9536
  outputContract: preset.outputContract,
8235
9537
  fallbackJson: preset.fallbackJson,
8236
9538
  detail: normalizeEscalateDetail(options.detail),
9539
+ quiet: Boolean(options.quiet),
8237
9540
  showRaw: Boolean(options.showRaw),
8238
9541
  verbose: Boolean(options.verbose)
8239
9542
  });