@bilalimamoglu/sift 0.3.1 → 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/README.md +142 -386
- package/dist/cli.js +2500 -226
- package/dist/index.d.ts +15 -1
- package/dist/index.js +2474 -216
- package/package.json +1 -1
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
|
|
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
|
-
|
|
1901
|
-
|
|
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(),
|
|
@@ -2229,6 +2247,255 @@ var testStatusPublicDiagnoseContractSchema = testStatusDiagnoseContractSchema.om
|
|
|
2229
2247
|
function parseTestStatusProviderSupplement(input) {
|
|
2230
2248
|
return testStatusProviderSupplementSchema.parse(JSON.parse(input));
|
|
2231
2249
|
}
|
|
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
|
+
},
|
|
2287
|
+
{
|
|
2288
|
+
prefix: "snapshot mismatch:",
|
|
2289
|
+
type: "snapshot_mismatch",
|
|
2290
|
+
label: "snapshot mismatch",
|
|
2291
|
+
genericTitle: "Snapshot mismatches",
|
|
2292
|
+
defaultCoverage: "failed",
|
|
2293
|
+
rootCauseConfidence: 0.84,
|
|
2294
|
+
why: "it contains the failing snapshot expectation behind this bucket",
|
|
2295
|
+
fix: "Update the snapshots if these output changes are intentional, then rerun the suite."
|
|
2296
|
+
},
|
|
2297
|
+
{
|
|
2298
|
+
prefix: "timeout:",
|
|
2299
|
+
type: "timeout_failure",
|
|
2300
|
+
label: "timeout",
|
|
2301
|
+
genericTitle: "Timeout failures",
|
|
2302
|
+
defaultCoverage: "mixed",
|
|
2303
|
+
rootCauseConfidence: 0.9,
|
|
2304
|
+
why: "it contains the test or fixture that exceeded the timeout threshold",
|
|
2305
|
+
fix: "Check for deadlocks, slow setup, or increase the timeout threshold before rerunning."
|
|
2306
|
+
},
|
|
2307
|
+
{
|
|
2308
|
+
prefix: "permission:",
|
|
2309
|
+
type: "permission_denied_failure",
|
|
2310
|
+
label: "permission denied",
|
|
2311
|
+
genericTitle: "Permission failures",
|
|
2312
|
+
defaultCoverage: "error",
|
|
2313
|
+
rootCauseConfidence: 0.85,
|
|
2314
|
+
why: "it contains the file, socket, or port access that was denied",
|
|
2315
|
+
fix: "Check file or port permissions in the CI environment before rerunning."
|
|
2316
|
+
},
|
|
2317
|
+
{
|
|
2318
|
+
prefix: "async loop:",
|
|
2319
|
+
type: "async_event_loop_failure",
|
|
2320
|
+
label: "async event loop",
|
|
2321
|
+
genericTitle: "Async event loop failures",
|
|
2322
|
+
defaultCoverage: "mixed",
|
|
2323
|
+
rootCauseConfidence: 0.88,
|
|
2324
|
+
why: "it contains the async setup or coroutine that caused the event loop error",
|
|
2325
|
+
fix: "Check event loop scope and pytest-asyncio configuration before rerunning."
|
|
2326
|
+
},
|
|
2327
|
+
{
|
|
2328
|
+
prefix: "fixture teardown:",
|
|
2329
|
+
type: "fixture_teardown_failure",
|
|
2330
|
+
label: "fixture teardown",
|
|
2331
|
+
genericTitle: "Fixture teardown failures",
|
|
2332
|
+
defaultCoverage: "error",
|
|
2333
|
+
rootCauseConfidence: 0.85,
|
|
2334
|
+
why: "it contains the fixture teardown path that failed after the test body completed",
|
|
2335
|
+
fix: "Inspect the teardown cleanup path and restore idempotent fixture cleanup before rerunning."
|
|
2336
|
+
},
|
|
2337
|
+
{
|
|
2338
|
+
prefix: "db migration:",
|
|
2339
|
+
type: "db_migration_failure",
|
|
2340
|
+
label: "db migration",
|
|
2341
|
+
genericTitle: "DB migration failures",
|
|
2342
|
+
defaultCoverage: "error",
|
|
2343
|
+
rootCauseConfidence: 0.9,
|
|
2344
|
+
why: "it contains the migration or model definition behind the missing table or relation",
|
|
2345
|
+
fix: "Run pending migrations or fix the expected model schema before rerunning."
|
|
2346
|
+
},
|
|
2347
|
+
{
|
|
2348
|
+
prefix: "configuration:",
|
|
2349
|
+
type: "configuration_error",
|
|
2350
|
+
label: "configuration error",
|
|
2351
|
+
genericTitle: "Configuration errors",
|
|
2352
|
+
defaultCoverage: "error",
|
|
2353
|
+
rootCauseConfidence: 0.95,
|
|
2354
|
+
dominantPriority: 4,
|
|
2355
|
+
dominantBlocker: true,
|
|
2356
|
+
why: "it contains the pytest configuration or conftest setup error that blocks the run",
|
|
2357
|
+
fix: "Fix the pytest configuration, CLI usage, or conftest import error before rerunning."
|
|
2358
|
+
},
|
|
2359
|
+
{
|
|
2360
|
+
prefix: "xdist worker crash:",
|
|
2361
|
+
type: "xdist_worker_crash",
|
|
2362
|
+
label: "xdist worker crash",
|
|
2363
|
+
genericTitle: "xdist worker crashes",
|
|
2364
|
+
defaultCoverage: "error",
|
|
2365
|
+
rootCauseConfidence: 0.92,
|
|
2366
|
+
dominantPriority: 3,
|
|
2367
|
+
why: "it contains the worker startup or shared-state path that crashed an xdist worker",
|
|
2368
|
+
fix: "Check shared state, worker startup hooks, or resource contention between workers before rerunning."
|
|
2369
|
+
},
|
|
2370
|
+
{
|
|
2371
|
+
prefix: "type error:",
|
|
2372
|
+
type: "type_error_failure",
|
|
2373
|
+
label: "type error",
|
|
2374
|
+
genericTitle: "Type errors",
|
|
2375
|
+
defaultCoverage: "mixed",
|
|
2376
|
+
rootCauseConfidence: 0.8,
|
|
2377
|
+
why: "it contains the call site or fixture value that triggered the type error",
|
|
2378
|
+
fix: "Inspect the mismatched argument or object shape and rerun the full suite at standard."
|
|
2379
|
+
},
|
|
2380
|
+
{
|
|
2381
|
+
prefix: "resource leak:",
|
|
2382
|
+
type: "resource_leak_warning",
|
|
2383
|
+
label: "resource leak",
|
|
2384
|
+
genericTitle: "Resource leak warnings",
|
|
2385
|
+
defaultCoverage: "mixed",
|
|
2386
|
+
rootCauseConfidence: 0.74,
|
|
2387
|
+
why: "it contains the warning source behind the leaked file, socket, or coroutine",
|
|
2388
|
+
fix: "Close the leaked resource or suppress the warning only if the cleanup is intentional."
|
|
2389
|
+
},
|
|
2390
|
+
{
|
|
2391
|
+
prefix: "django db access:",
|
|
2392
|
+
type: "django_db_access_denied",
|
|
2393
|
+
label: "django db access",
|
|
2394
|
+
genericTitle: "Django DB access failures",
|
|
2395
|
+
defaultCoverage: "error",
|
|
2396
|
+
rootCauseConfidence: 0.95,
|
|
2397
|
+
why: "it needs the @pytest.mark.django_db decorator or fixture permission to access the database",
|
|
2398
|
+
fix: "Add @pytest.mark.django_db to the test or class before rerunning."
|
|
2399
|
+
},
|
|
2400
|
+
{
|
|
2401
|
+
prefix: "network:",
|
|
2402
|
+
type: "network_failure",
|
|
2403
|
+
label: "network failure",
|
|
2404
|
+
genericTitle: "Network failures",
|
|
2405
|
+
defaultCoverage: "error",
|
|
2406
|
+
rootCauseConfidence: 0.88,
|
|
2407
|
+
dominantPriority: 2,
|
|
2408
|
+
why: "it contains the host, URL, or TLS path behind the network failure",
|
|
2409
|
+
fix: "Check DNS, outbound network access, retries, or TLS trust before rerunning."
|
|
2410
|
+
},
|
|
2411
|
+
{
|
|
2412
|
+
prefix: "segfault:",
|
|
2413
|
+
type: "subprocess_crash_segfault",
|
|
2414
|
+
label: "segfault",
|
|
2415
|
+
genericTitle: "Segfault crashes",
|
|
2416
|
+
defaultCoverage: "mixed",
|
|
2417
|
+
rootCauseConfidence: 0.8,
|
|
2418
|
+
why: "it contains the subprocess or native extension path that crashed with SIGSEGV",
|
|
2419
|
+
fix: "Inspect the native extension, subprocess boundary, or incompatible binary before rerunning."
|
|
2420
|
+
},
|
|
2421
|
+
{
|
|
2422
|
+
prefix: "flaky:",
|
|
2423
|
+
type: "flaky_test_detected",
|
|
2424
|
+
label: "flaky test",
|
|
2425
|
+
genericTitle: "Flaky test detections",
|
|
2426
|
+
defaultCoverage: "mixed",
|
|
2427
|
+
rootCauseConfidence: 0.72,
|
|
2428
|
+
why: "it contains the rerun-prone test that behaved inconsistently across attempts",
|
|
2429
|
+
fix: "Stabilize the nondeterministic test or fixture before relying on reruns."
|
|
2430
|
+
},
|
|
2431
|
+
{
|
|
2432
|
+
prefix: "serialization:",
|
|
2433
|
+
type: "serialization_encoding_failure",
|
|
2434
|
+
label: "serialization or encoding",
|
|
2435
|
+
genericTitle: "Serialization or encoding failures",
|
|
2436
|
+
defaultCoverage: "mixed",
|
|
2437
|
+
rootCauseConfidence: 0.78,
|
|
2438
|
+
why: "it contains the serialization or decoding path behind the malformed payload",
|
|
2439
|
+
fix: "Inspect the encoded payload, serializer, or fixture data before rerunning."
|
|
2440
|
+
},
|
|
2441
|
+
{
|
|
2442
|
+
prefix: "file not found:",
|
|
2443
|
+
type: "file_not_found_failure",
|
|
2444
|
+
label: "file not found",
|
|
2445
|
+
genericTitle: "Missing file failures",
|
|
2446
|
+
defaultCoverage: "mixed",
|
|
2447
|
+
rootCauseConfidence: 0.82,
|
|
2448
|
+
why: "it contains the missing file path or fixture artifact required by the test",
|
|
2449
|
+
fix: "Restore the missing file, fixture artifact, or working-directory assumption before rerunning."
|
|
2450
|
+
},
|
|
2451
|
+
{
|
|
2452
|
+
prefix: "memory:",
|
|
2453
|
+
type: "memory_error",
|
|
2454
|
+
label: "memory error",
|
|
2455
|
+
genericTitle: "Memory failures",
|
|
2456
|
+
defaultCoverage: "mixed",
|
|
2457
|
+
rootCauseConfidence: 0.78,
|
|
2458
|
+
why: "it contains the allocation path that exhausted available memory",
|
|
2459
|
+
fix: "Reduce memory pressure or investigate the large allocation before rerunning."
|
|
2460
|
+
},
|
|
2461
|
+
{
|
|
2462
|
+
prefix: "deprecation as error:",
|
|
2463
|
+
type: "deprecation_warning_as_error",
|
|
2464
|
+
label: "deprecation as error",
|
|
2465
|
+
genericTitle: "Deprecation warnings as errors",
|
|
2466
|
+
defaultCoverage: "mixed",
|
|
2467
|
+
rootCauseConfidence: 0.74,
|
|
2468
|
+
why: "it contains the deprecated API or warning filter that is failing the test run",
|
|
2469
|
+
fix: "Update the deprecated call site or relax the warning policy only if that is intentional."
|
|
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
|
+
},
|
|
2481
|
+
{
|
|
2482
|
+
prefix: "xfail strict:",
|
|
2483
|
+
type: "xfail_strict_unexpected_pass",
|
|
2484
|
+
label: "strict xfail unexpected pass",
|
|
2485
|
+
genericTitle: "Strict xfail unexpected passes",
|
|
2486
|
+
defaultCoverage: "failed",
|
|
2487
|
+
rootCauseConfidence: 0.78,
|
|
2488
|
+
why: "it contains the strict xfail case that unexpectedly passed",
|
|
2489
|
+
fix: "Remove or update the strict xfail expectation if the test is now passing intentionally."
|
|
2490
|
+
}
|
|
2491
|
+
];
|
|
2492
|
+
function findExtendedBucketSpec(reason) {
|
|
2493
|
+
return extendedBucketSpecs.find((spec) => reason.startsWith(spec.prefix)) ?? null;
|
|
2494
|
+
}
|
|
2495
|
+
function extractReasonDetail(reason, prefix) {
|
|
2496
|
+
const detail = reason.slice(prefix.length).trim();
|
|
2497
|
+
return detail.length > 0 ? detail : null;
|
|
2498
|
+
}
|
|
2232
2499
|
function formatCount(count, singular, plural = `${singular}s`) {
|
|
2233
2500
|
return `${count} ${count === 1 ? singular : plural}`;
|
|
2234
2501
|
}
|
|
@@ -2282,6 +2549,10 @@ function formatTargetSummary(summary) {
|
|
|
2282
2549
|
return `count=${summary.count}; families=${families}`;
|
|
2283
2550
|
}
|
|
2284
2551
|
function classifyGenericBucketType(reason) {
|
|
2552
|
+
const extended = findExtendedBucketSpec(reason);
|
|
2553
|
+
if (extended) {
|
|
2554
|
+
return extended.type;
|
|
2555
|
+
}
|
|
2285
2556
|
if (reason.startsWith("missing test env:")) {
|
|
2286
2557
|
return "shared_environment_blocker";
|
|
2287
2558
|
}
|
|
@@ -2326,6 +2597,10 @@ function classifyVisibleStatusForLabel(args) {
|
|
|
2326
2597
|
return "unknown";
|
|
2327
2598
|
}
|
|
2328
2599
|
function inferCoverageFromReason(reason) {
|
|
2600
|
+
const extended = findExtendedBucketSpec(reason);
|
|
2601
|
+
if (extended) {
|
|
2602
|
+
return extended.defaultCoverage;
|
|
2603
|
+
}
|
|
2329
2604
|
if (reason.startsWith("missing test env:") || reason.startsWith("fixture guard:") || reason.startsWith("service unavailable:") || reason.startsWith("db refused:") || reason.startsWith("auth bypass absent:") || reason.startsWith("missing module:")) {
|
|
2330
2605
|
return "error";
|
|
2331
2606
|
}
|
|
@@ -2386,7 +2661,13 @@ function buildGenericBuckets(analysis) {
|
|
|
2386
2661
|
summaryLines: [],
|
|
2387
2662
|
reason,
|
|
2388
2663
|
count: 1,
|
|
2389
|
-
confidence:
|
|
2664
|
+
confidence: (() => {
|
|
2665
|
+
const extended = findExtendedBucketSpec(reason);
|
|
2666
|
+
if (extended) {
|
|
2667
|
+
return Math.max(0.72, Math.min(extended.rootCauseConfidence, 0.82));
|
|
2668
|
+
}
|
|
2669
|
+
return reason.startsWith("assertion failed:") || /^[A-Z][A-Za-z]+(?:Error|Exception):/.test(reason) ? 0.74 : 0.62;
|
|
2670
|
+
})(),
|
|
2390
2671
|
representativeItems: [item],
|
|
2391
2672
|
entities: [],
|
|
2392
2673
|
hint: void 0,
|
|
@@ -2403,7 +2684,7 @@ function buildGenericBuckets(analysis) {
|
|
|
2403
2684
|
push(item.reason, item);
|
|
2404
2685
|
}
|
|
2405
2686
|
for (const bucket of grouped.values()) {
|
|
2406
|
-
const title = bucket.type === "assertion_failure" ? "Assertion failures" : bucket.type === "import_dependency_failure" ? "Import/dependency failures" : bucket.type === "collection_failure" ? "Collection or fixture failures" : "Runtime failures";
|
|
2687
|
+
const title = findExtendedBucketSpec(bucket.reason)?.genericTitle ?? (bucket.type === "assertion_failure" || bucket.type === "snapshot_mismatch" ? "Assertion failures" : bucket.type === "import_dependency_failure" ? "Import/dependency failures" : bucket.type === "collection_failure" ? "Collection or fixture failures" : "Runtime failures");
|
|
2407
2688
|
bucket.headline = `${title}: ${formatCount(bucket.count, "visible failure")} share ${bucket.reason}.`;
|
|
2408
2689
|
bucket.summaryLines = [bucket.headline];
|
|
2409
2690
|
bucket.overflowCount = Math.max(bucket.count - bucket.representativeItems.length, 0);
|
|
@@ -2476,13 +2757,13 @@ function inferFailureBucketCoverage(bucket, analysis) {
|
|
|
2476
2757
|
}
|
|
2477
2758
|
}
|
|
2478
2759
|
const claimed = bucket.countClaimed ?? bucket.countVisible;
|
|
2479
|
-
if (bucket.type === "contract_snapshot_drift" || bucket.type === "assertion_failure") {
|
|
2760
|
+
if (bucket.type === "contract_snapshot_drift" || bucket.type === "assertion_failure" || bucket.type === "snapshot_mismatch") {
|
|
2480
2761
|
return {
|
|
2481
2762
|
error,
|
|
2482
2763
|
failed: Math.max(failed, claimed)
|
|
2483
2764
|
};
|
|
2484
2765
|
}
|
|
2485
|
-
if (bucket.type === "shared_environment_blocker" || bucket.type === "import_dependency_failure" || bucket.type === "collection_failure" || bucket.type === "fixture_guard_failure" || bucket.type === "service_unavailable" || bucket.type === "db_connection_failure" || bucket.type === "auth_bypass_absent") {
|
|
2766
|
+
if (bucket.type === "shared_environment_blocker" || bucket.type === "import_dependency_failure" || bucket.type === "collection_failure" || bucket.type === "fixture_guard_failure" || bucket.type === "permission_denied_failure" || bucket.type === "fixture_teardown_failure" || bucket.type === "db_migration_failure" || bucket.type === "configuration_error" || bucket.type === "xdist_worker_crash" || bucket.type === "django_db_access_denied" || bucket.type === "network_failure" || bucket.type === "service_unavailable" || bucket.type === "db_connection_failure" || bucket.type === "auth_bypass_absent") {
|
|
2486
2767
|
return {
|
|
2487
2768
|
error: Math.max(error, claimed),
|
|
2488
2769
|
failed
|
|
@@ -2557,6 +2838,10 @@ function dominantBucketPriority(bucket) {
|
|
|
2557
2838
|
if (bucket.reason.startsWith("missing test env:")) {
|
|
2558
2839
|
return 5;
|
|
2559
2840
|
}
|
|
2841
|
+
const extended = findExtendedBucketSpec(bucket.reason);
|
|
2842
|
+
if (extended?.dominantPriority !== void 0) {
|
|
2843
|
+
return extended.dominantPriority;
|
|
2844
|
+
}
|
|
2560
2845
|
if (bucket.type === "shared_environment_blocker") {
|
|
2561
2846
|
return 4;
|
|
2562
2847
|
}
|
|
@@ -2590,12 +2875,16 @@ function prioritizeBuckets(buckets) {
|
|
|
2590
2875
|
});
|
|
2591
2876
|
}
|
|
2592
2877
|
function isDominantBlockerType(type) {
|
|
2593
|
-
return type === "shared_environment_blocker" || type === "import_dependency_failure" || type === "collection_failure";
|
|
2878
|
+
return type === "shared_environment_blocker" || type === "configuration_error" || type === "import_dependency_failure" || type === "collection_failure";
|
|
2594
2879
|
}
|
|
2595
2880
|
function labelForBucket(bucket) {
|
|
2596
2881
|
if (bucket.labelOverride) {
|
|
2597
2882
|
return bucket.labelOverride;
|
|
2598
2883
|
}
|
|
2884
|
+
const extended = findExtendedBucketSpec(bucket.reason);
|
|
2885
|
+
if (extended) {
|
|
2886
|
+
return extended.label;
|
|
2887
|
+
}
|
|
2599
2888
|
if (bucket.reason.startsWith("missing test env:")) {
|
|
2600
2889
|
return "missing test env";
|
|
2601
2890
|
}
|
|
@@ -2629,6 +2918,9 @@ function labelForBucket(bucket) {
|
|
|
2629
2918
|
if (bucket.type === "assertion_failure") {
|
|
2630
2919
|
return "assertion failure";
|
|
2631
2920
|
}
|
|
2921
|
+
if (bucket.type === "snapshot_mismatch") {
|
|
2922
|
+
return "snapshot mismatch";
|
|
2923
|
+
}
|
|
2632
2924
|
if (bucket.type === "collection_failure") {
|
|
2633
2925
|
return "collection failure";
|
|
2634
2926
|
}
|
|
@@ -2647,6 +2939,10 @@ function rootCauseConfidenceFor(bucket) {
|
|
|
2647
2939
|
if (isUnknownBucket(bucket)) {
|
|
2648
2940
|
return 0.52;
|
|
2649
2941
|
}
|
|
2942
|
+
const extended = findExtendedBucketSpec(bucket.reason);
|
|
2943
|
+
if (extended) {
|
|
2944
|
+
return extended.rootCauseConfidence;
|
|
2945
|
+
}
|
|
2650
2946
|
if (bucket.reason.startsWith("missing test env:") || bucket.reason.startsWith("missing module:") || bucket.reason.startsWith("db refused:") || bucket.reason.startsWith("service unavailable:") || bucket.reason.startsWith("auth bypass absent:")) {
|
|
2651
2947
|
return 0.95;
|
|
2652
2948
|
}
|
|
@@ -2687,6 +2983,10 @@ function buildReadTargetWhy(args) {
|
|
|
2687
2983
|
if (envVar) {
|
|
2688
2984
|
return `it contains the ${envVar} setup guard`;
|
|
2689
2985
|
}
|
|
2986
|
+
const extended = findExtendedBucketSpec(args.bucket.reason);
|
|
2987
|
+
if (extended) {
|
|
2988
|
+
return extended.why;
|
|
2989
|
+
}
|
|
2690
2990
|
if (args.bucket.reason.startsWith("fixture guard:")) {
|
|
2691
2991
|
return "it contains the fixture/setup guard behind this bucket";
|
|
2692
2992
|
}
|
|
@@ -2717,6 +3017,9 @@ function buildReadTargetWhy(args) {
|
|
|
2717
3017
|
}
|
|
2718
3018
|
return "it maps to the visible stale snapshot expectation";
|
|
2719
3019
|
}
|
|
3020
|
+
if (args.bucket.type === "snapshot_mismatch") {
|
|
3021
|
+
return "it maps to the visible snapshot mismatch bucket";
|
|
3022
|
+
}
|
|
2720
3023
|
if (args.bucket.type === "import_dependency_failure") {
|
|
2721
3024
|
return "it is the first visible failing module in this missing dependency bucket";
|
|
2722
3025
|
}
|
|
@@ -2728,11 +3031,54 @@ function buildReadTargetWhy(args) {
|
|
|
2728
3031
|
}
|
|
2729
3032
|
return `it maps to the visible ${args.bucketLabel} bucket`;
|
|
2730
3033
|
}
|
|
3034
|
+
function buildExtendedBucketSearchHint(bucket, anchor) {
|
|
3035
|
+
const extended = findExtendedBucketSpec(bucket.reason);
|
|
3036
|
+
if (!extended) {
|
|
3037
|
+
return null;
|
|
3038
|
+
}
|
|
3039
|
+
const detail = extractReasonDetail(bucket.reason, extended.prefix);
|
|
3040
|
+
if (!detail) {
|
|
3041
|
+
return anchor.label.split("::")[1]?.trim() ?? anchor.label ?? null;
|
|
3042
|
+
}
|
|
3043
|
+
if (extended.type === "timeout_failure") {
|
|
3044
|
+
const duration = detail.match(/>\s*([0-9]+(?:\.[0-9]+)?s?)/i)?.[1];
|
|
3045
|
+
return duration ?? anchor.label.split("::")[1]?.trim() ?? detail;
|
|
3046
|
+
}
|
|
3047
|
+
if (extended.type === "db_migration_failure") {
|
|
3048
|
+
const relation = detail.match(/\b(?:relation|table)\s+([A-Za-z0-9_.-]+)/i)?.[1];
|
|
3049
|
+
return relation ?? detail;
|
|
3050
|
+
}
|
|
3051
|
+
if (extended.type === "network_failure") {
|
|
3052
|
+
const url = detail.match(/\bhttps?:\/\/[^\s)'"`]+/i)?.[0];
|
|
3053
|
+
const host = detail.match(/\b(?:[A-Za-z0-9-]+\.)+[A-Za-z]{2,}\b/)?.[0];
|
|
3054
|
+
return url ?? host ?? detail;
|
|
3055
|
+
}
|
|
3056
|
+
if (extended.type === "xdist_worker_crash") {
|
|
3057
|
+
return detail.match(/\bgw\d+\b/)?.[0] ?? detail;
|
|
3058
|
+
}
|
|
3059
|
+
if (extended.type === "fixture_teardown_failure") {
|
|
3060
|
+
return detail.replace(/^of\s+/i, "") || anchor.label;
|
|
3061
|
+
}
|
|
3062
|
+
if (extended.type === "file_not_found_failure") {
|
|
3063
|
+
const path8 = detail.match(/['"]([^'"]+)['"]/)?.[1];
|
|
3064
|
+
return path8 ?? detail;
|
|
3065
|
+
}
|
|
3066
|
+
if (extended.type === "permission_denied_failure") {
|
|
3067
|
+
const path8 = detail.match(/['"]([^'"]+)['"]/)?.[1];
|
|
3068
|
+
const port = detail.match(/\bport\s+(\d+)\b/i)?.[1];
|
|
3069
|
+
return path8 ?? (port ? `port ${port}` : detail);
|
|
3070
|
+
}
|
|
3071
|
+
return detail;
|
|
3072
|
+
}
|
|
2731
3073
|
function buildReadTargetSearchHint(bucket, anchor) {
|
|
2732
3074
|
const envVar = bucket.reason.match(/^missing test env:\s+([A-Z][A-Z0-9_]{2,})$/)?.[1];
|
|
2733
3075
|
if (envVar) {
|
|
2734
3076
|
return envVar;
|
|
2735
3077
|
}
|
|
3078
|
+
const extendedHint = buildExtendedBucketSearchHint(bucket, anchor);
|
|
3079
|
+
if (extendedHint) {
|
|
3080
|
+
return extendedHint;
|
|
3081
|
+
}
|
|
2736
3082
|
if (bucket.type === "contract_snapshot_drift") {
|
|
2737
3083
|
return bucket.entities.find((value) => value.startsWith("/api/")) ?? bucket.entities[0] ?? null;
|
|
2738
3084
|
}
|
|
@@ -2847,6 +3193,10 @@ function extractMiniDiff(input, bucket) {
|
|
|
2847
3193
|
};
|
|
2848
3194
|
}
|
|
2849
3195
|
function inferSupplementCoverageKind(args) {
|
|
3196
|
+
const extended = findExtendedBucketSpec(args.rootCause);
|
|
3197
|
+
if (extended?.defaultCoverage === "error" || extended?.defaultCoverage === "failed") {
|
|
3198
|
+
return extended.defaultCoverage;
|
|
3199
|
+
}
|
|
2850
3200
|
const normalized = `${args.label} ${args.rootCause}`.toLowerCase();
|
|
2851
3201
|
if (/env|setup|fixture|import|dependency|service|db|database|auth bypass|collection|connection refused/.test(
|
|
2852
3202
|
normalized
|
|
@@ -2917,7 +3267,7 @@ function buildProviderSupplementBuckets(args) {
|
|
|
2917
3267
|
});
|
|
2918
3268
|
}
|
|
2919
3269
|
function pickUnknownAnchor(args) {
|
|
2920
|
-
const fromStatusItems = args.kind === "error" ? args.analysis.visibleErrorItems[0] :
|
|
3270
|
+
const fromStatusItems = args.kind === "error" ? args.analysis.visibleErrorItems[0] : args.analysis.visibleFailedItems[0];
|
|
2921
3271
|
if (fromStatusItems) {
|
|
2922
3272
|
return {
|
|
2923
3273
|
label: fromStatusItems.label,
|
|
@@ -2954,12 +3304,14 @@ function buildUnknownBucket(args) {
|
|
|
2954
3304
|
const isError = args.kind === "error";
|
|
2955
3305
|
const label = isError ? "unknown setup blocker" : "unknown failure family";
|
|
2956
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;
|
|
2957
3308
|
return {
|
|
2958
3309
|
type: "unknown_failure",
|
|
2959
3310
|
headline: `${label}: ${formatCount(args.count, "visible failure")} share a repeated but unclassified pattern.`,
|
|
2960
3311
|
summaryLines: [
|
|
2961
|
-
`${label}: ${formatCount(args.count, "visible failure")} share a repeated but unclassified pattern
|
|
2962
|
-
|
|
3312
|
+
`${label}: ${formatCount(args.count, "visible failure")} share a repeated but unclassified pattern.`,
|
|
3313
|
+
firstConcreteSignal
|
|
3314
|
+
].filter((value) => Boolean(value)),
|
|
2963
3315
|
reason,
|
|
2964
3316
|
count: args.count,
|
|
2965
3317
|
confidence: 0.45,
|
|
@@ -3086,10 +3438,14 @@ function buildStandardAnchorText(target) {
|
|
|
3086
3438
|
}
|
|
3087
3439
|
return formatReadTargetLocation(target);
|
|
3088
3440
|
}
|
|
3089
|
-
function
|
|
3441
|
+
function resolveBucketFixHint(args) {
|
|
3090
3442
|
if (args.bucket.hint) {
|
|
3091
3443
|
return args.bucket.hint;
|
|
3092
3444
|
}
|
|
3445
|
+
const extended = findExtendedBucketSpec(args.bucket.reason);
|
|
3446
|
+
if (extended) {
|
|
3447
|
+
return extended.fix;
|
|
3448
|
+
}
|
|
3093
3449
|
const envVar = args.bucket.reason.match(/^missing test env:\s+([A-Z][A-Z0-9_]{2,})$/)?.[1];
|
|
3094
3450
|
if (envVar) {
|
|
3095
3451
|
return `Set ${envVar} before rerunning the affected tests.`;
|
|
@@ -3119,6 +3475,9 @@ function buildStandardFixText(args) {
|
|
|
3119
3475
|
if (args.bucket.type === "contract_snapshot_drift") {
|
|
3120
3476
|
return "Review the visible drift and regenerate the contract snapshots if the changes are intentional.";
|
|
3121
3477
|
}
|
|
3478
|
+
if (args.bucket.type === "snapshot_mismatch") {
|
|
3479
|
+
return "Update the snapshots if these output changes are intentional, then rerun the full suite at standard.";
|
|
3480
|
+
}
|
|
3122
3481
|
if (args.bucket.type === "assertion_failure") {
|
|
3123
3482
|
return "Inspect the failing assertion and rerun the full suite at standard.";
|
|
3124
3483
|
}
|
|
@@ -3128,13 +3487,75 @@ function buildStandardFixText(args) {
|
|
|
3128
3487
|
if (args.bucket.type === "runtime_failure") {
|
|
3129
3488
|
return `Fix the visible ${args.bucketLabel} and rerun the full suite at standard.`;
|
|
3130
3489
|
}
|
|
3131
|
-
return
|
|
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
|
+
}
|
|
3132
3552
|
}
|
|
3133
3553
|
function buildStandardBucketSupport(args) {
|
|
3134
3554
|
return {
|
|
3135
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,
|
|
3136
3557
|
anchorText: buildStandardAnchorText(args.readTarget),
|
|
3137
|
-
fixText:
|
|
3558
|
+
fixText: resolveBucketFixHint({
|
|
3138
3559
|
bucket: args.bucket,
|
|
3139
3560
|
bucketLabel: args.contractBucket.label
|
|
3140
3561
|
})
|
|
@@ -3157,6 +3578,9 @@ function renderStandard(args) {
|
|
|
3157
3578
|
)
|
|
3158
3579
|
});
|
|
3159
3580
|
lines.push(support.headline);
|
|
3581
|
+
if (support.firstConcreteSignalText) {
|
|
3582
|
+
lines.push(`- ${support.firstConcreteSignalText}`);
|
|
3583
|
+
}
|
|
3160
3584
|
if (support.anchorText) {
|
|
3161
3585
|
lines.push(`- Anchor: ${support.anchorText}`);
|
|
3162
3586
|
}
|
|
@@ -3166,6 +3590,7 @@ function renderStandard(args) {
|
|
|
3166
3590
|
}
|
|
3167
3591
|
}
|
|
3168
3592
|
lines.push(buildDecisionLine(args.contract));
|
|
3593
|
+
lines.push(`- Likely owner: ${formatSuspectKindLabel(args.contract.primary_suspect_kind)}`);
|
|
3169
3594
|
lines.push(`- Next: ${args.contract.next_best_action.note}`);
|
|
3170
3595
|
lines.push(buildStopSignal(args.contract));
|
|
3171
3596
|
return lines.join("\n");
|
|
@@ -3253,29 +3678,49 @@ function buildTestStatusDiagnoseContract(args) {
|
|
|
3253
3678
|
})[0] ?? null;
|
|
3254
3679
|
const hasUnknownBucket = buckets.some((bucket) => isUnknownBucket(bucket));
|
|
3255
3680
|
const hasConcreteCoverage = args.analysis.failed === 0 && args.analysis.errors === 0 ? true : residuals.remainingErrors === 0 && residuals.remainingFailed === 0;
|
|
3256
|
-
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;
|
|
3257
|
-
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);
|
|
3258
3681
|
const dominantBlockerBucketIndex = dominantBucket && isDominantBlockerType(dominantBucket.bucket.type) ? dominantBucket.index + 1 : null;
|
|
3259
3682
|
const readTargets = buildReadTargets({
|
|
3260
3683
|
buckets,
|
|
3261
3684
|
dominantBucketIndex: dominantBlockerBucketIndex
|
|
3262
3685
|
});
|
|
3263
|
-
const
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
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
|
+
});
|
|
3275
3716
|
const resolvedTests = unique(args.resolvedTests ?? []);
|
|
3276
3717
|
const remainingTests = unique(
|
|
3277
3718
|
args.remainingTests ?? unique([...args.analysis.visibleErrorLabels, ...args.analysis.visibleFailedLabels])
|
|
3278
3719
|
);
|
|
3720
|
+
const primarySuspectKind = derivePrimarySuspectKind({
|
|
3721
|
+
mainBuckets,
|
|
3722
|
+
dominantBlockerBucketIndex
|
|
3723
|
+
});
|
|
3279
3724
|
let nextBestAction;
|
|
3280
3725
|
if (args.analysis.failed === 0 && args.analysis.errors === 0 && args.analysis.passed > 0) {
|
|
3281
3726
|
nextBestAction = {
|
|
@@ -3321,6 +3766,8 @@ function buildTestStatusDiagnoseContract(args) {
|
|
|
3321
3766
|
additional_source_read_likely_low_value: diagnosisComplete && !rawNeeded,
|
|
3322
3767
|
read_raw_only_if: rawNeeded ? "you still need exact traceback lines after focused or verbose detail" : null,
|
|
3323
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.",
|
|
3324
3771
|
provider_used: false,
|
|
3325
3772
|
provider_confidence: null,
|
|
3326
3773
|
provider_failed: false,
|
|
@@ -3352,9 +3799,16 @@ function buildTestStatusDiagnoseContract(args) {
|
|
|
3352
3799
|
})
|
|
3353
3800
|
}
|
|
3354
3801
|
};
|
|
3802
|
+
const resolvedDecision = effectiveDecision ?? deriveDecision(mergedContractWithoutDecision);
|
|
3803
|
+
const resolvedConfidenceReason = buildConfidenceReason({
|
|
3804
|
+
decision: resolvedDecision,
|
|
3805
|
+
mainBuckets,
|
|
3806
|
+
primarySuspectKind: mergedContractWithoutDecision.primary_suspect_kind
|
|
3807
|
+
});
|
|
3355
3808
|
const contract = testStatusDiagnoseContractSchema.parse({
|
|
3356
3809
|
...mergedContractWithoutDecision,
|
|
3357
|
-
|
|
3810
|
+
confidence_reason: resolvedConfidenceReason,
|
|
3811
|
+
decision: resolvedDecision
|
|
3358
3812
|
});
|
|
3359
3813
|
return {
|
|
3360
3814
|
contract,
|
|
@@ -3759,6 +4213,27 @@ function buildFallbackOutput(args) {
|
|
|
3759
4213
|
var RISK_LINE_PATTERN = /(destroy|delete|drop|recreate|replace|revoke|deny|downtime|data loss|iam|network exposure)/i;
|
|
3760
4214
|
var ZERO_DESTRUCTIVE_SUMMARY_PATTERN = /\b0\s+to\s+(destroy|delete|drop|recreate|replace|revoke)\b/i;
|
|
3761
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
|
+
};
|
|
3762
4237
|
function collectEvidence(input, matcher, limit = 3) {
|
|
3763
4238
|
return input.split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && matcher.test(line)).slice(0, limit);
|
|
3764
4239
|
}
|
|
@@ -3772,11 +4247,236 @@ function inferPackage(line) {
|
|
|
3772
4247
|
function inferRemediation(pkg2) {
|
|
3773
4248
|
return `Upgrade ${pkg2} to a patched version.`;
|
|
3774
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
|
+
}
|
|
3775
4322
|
function getCount(input, label) {
|
|
3776
4323
|
const matches = [...input.matchAll(new RegExp(`(\\d+)\\s+${label}`, "gi"))];
|
|
3777
4324
|
const lastMatch = matches.at(-1);
|
|
3778
4325
|
return lastMatch ? Number(lastMatch[1]) : 0;
|
|
3779
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
|
+
}
|
|
4423
|
+
function detectTestRunner(input) {
|
|
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)) {
|
|
4425
|
+
return "vitest";
|
|
4426
|
+
}
|
|
4427
|
+
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)) {
|
|
4428
|
+
return "jest";
|
|
4429
|
+
}
|
|
4430
|
+
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)) {
|
|
4431
|
+
return "pytest";
|
|
4432
|
+
}
|
|
4433
|
+
return "unknown";
|
|
4434
|
+
}
|
|
4435
|
+
function extractVitestLineCount(input, label, metric) {
|
|
4436
|
+
const matcher = new RegExp(`^\\s*${label}\\s+(.+)$`, "gmi");
|
|
4437
|
+
const lines = [...input.matchAll(matcher)];
|
|
4438
|
+
const line = lines.at(-1)?.[1];
|
|
4439
|
+
if (!line) {
|
|
4440
|
+
return null;
|
|
4441
|
+
}
|
|
4442
|
+
const metricMatch = line.match(new RegExp(`(\\d+)\\s+${metric}`, "i"));
|
|
4443
|
+
return metricMatch ? Number(metricMatch[1]) : null;
|
|
4444
|
+
}
|
|
4445
|
+
function extractJestLineCount(input, label, metric) {
|
|
4446
|
+
const matcher = new RegExp(`^\\s*${label}:\\s+(.+)$`, "gmi");
|
|
4447
|
+
const lines = [...input.matchAll(matcher)];
|
|
4448
|
+
const line = lines.at(-1)?.[1];
|
|
4449
|
+
if (!line) {
|
|
4450
|
+
return null;
|
|
4451
|
+
}
|
|
4452
|
+
const metricMatch = line.match(new RegExp(`(\\d+)\\s+${metric}`, "i"));
|
|
4453
|
+
return metricMatch ? Number(metricMatch[1]) : null;
|
|
4454
|
+
}
|
|
4455
|
+
function extractTestStatusCounts(input, runner) {
|
|
4456
|
+
if (runner === "vitest") {
|
|
4457
|
+
return {
|
|
4458
|
+
passed: extractVitestLineCount(input, "Tests?", "passed") ?? getCount(input, "passed"),
|
|
4459
|
+
failed: extractVitestLineCount(input, "Tests?", "failed") ?? getCount(input, "failed"),
|
|
4460
|
+
errors: extractVitestLineCount(input, "Errors?", "error") ?? extractVitestLineCount(input, "Errors?", "errors") ?? Math.max(getCount(input, "errors"), getCount(input, "error")),
|
|
4461
|
+
skipped: extractVitestLineCount(input, "Tests?", "skipped") ?? getCount(input, "skipped"),
|
|
4462
|
+
snapshotFailures: extractVitestLineCount(input, "Snapshots?", "failed") ?? void 0
|
|
4463
|
+
};
|
|
4464
|
+
}
|
|
4465
|
+
if (runner === "jest") {
|
|
4466
|
+
return {
|
|
4467
|
+
passed: extractJestLineCount(input, "Tests", "passed") ?? getCount(input, "passed"),
|
|
4468
|
+
failed: extractJestLineCount(input, "Tests", "failed") ?? getCount(input, "failed"),
|
|
4469
|
+
errors: Math.max(getCount(input, "errors"), getCount(input, "error")),
|
|
4470
|
+
skipped: extractJestLineCount(input, "Tests", "skipped") ?? getCount(input, "skipped")
|
|
4471
|
+
};
|
|
4472
|
+
}
|
|
4473
|
+
return {
|
|
4474
|
+
passed: getCount(input, "passed"),
|
|
4475
|
+
failed: getCount(input, "failed"),
|
|
4476
|
+
errors: Math.max(getCount(input, "errors"), getCount(input, "error")),
|
|
4477
|
+
skipped: getCount(input, "skipped")
|
|
4478
|
+
};
|
|
4479
|
+
}
|
|
3780
4480
|
function formatCount2(count, singular, plural = `${singular}s`) {
|
|
3781
4481
|
return `${count} ${count === 1 ? singular : plural}`;
|
|
3782
4482
|
}
|
|
@@ -3797,6 +4497,21 @@ function collectUniqueMatches(input, matcher, limit = 6) {
|
|
|
3797
4497
|
}
|
|
3798
4498
|
return values;
|
|
3799
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
|
+
}
|
|
3800
4515
|
function emptyAnchor() {
|
|
3801
4516
|
return {
|
|
3802
4517
|
file: null,
|
|
@@ -3809,7 +4524,8 @@ function normalizeAnchorFile(value) {
|
|
|
3809
4524
|
return value.replace(/\\/g, "/").trim();
|
|
3810
4525
|
}
|
|
3811
4526
|
function inferFileFromLabel(label) {
|
|
3812
|
-
const
|
|
4527
|
+
const cleaned = cleanFailureLabel(label);
|
|
4528
|
+
const candidate = cleaned.split("::")[0]?.split(" > ")[0]?.trim();
|
|
3813
4529
|
if (!candidate) {
|
|
3814
4530
|
return null;
|
|
3815
4531
|
}
|
|
@@ -3864,6 +4580,15 @@ function parseObservedAnchor(line) {
|
|
|
3864
4580
|
anchor_confidence: 0.92
|
|
3865
4581
|
};
|
|
3866
4582
|
}
|
|
4583
|
+
const vitestTraceback = normalized.match(/^\s*❯\s+([^:\s][^:]*\.[A-Za-z0-9]+):(\d+)(?::\d+)?/);
|
|
4584
|
+
if (vitestTraceback) {
|
|
4585
|
+
return {
|
|
4586
|
+
file: normalizeAnchorFile(vitestTraceback[1]),
|
|
4587
|
+
line: Number(vitestTraceback[2]),
|
|
4588
|
+
anchor_kind: "traceback",
|
|
4589
|
+
anchor_confidence: 1
|
|
4590
|
+
};
|
|
4591
|
+
}
|
|
3867
4592
|
return null;
|
|
3868
4593
|
}
|
|
3869
4594
|
function resolveAnchorForLabel(args) {
|
|
@@ -3880,15 +4605,27 @@ function isLowValueInternalReason(normalized) {
|
|
|
3880
4605
|
) || /\bpython\.py:\d+:\s+in\s+importtestmodule\b/i.test(normalized) || /\bpython\.py:\d+:\s+in\s+import_path\b/i.test(normalized);
|
|
3881
4606
|
}
|
|
3882
4607
|
function scoreFailureReason(reason) {
|
|
4608
|
+
if (reason.startsWith("configuration:")) {
|
|
4609
|
+
return 6;
|
|
4610
|
+
}
|
|
3883
4611
|
if (reason.startsWith("missing test env:")) {
|
|
3884
4612
|
return 6;
|
|
3885
4613
|
}
|
|
3886
4614
|
if (reason.startsWith("missing module:")) {
|
|
3887
4615
|
return 5;
|
|
3888
4616
|
}
|
|
4617
|
+
if (reason.startsWith("snapshot mismatch:")) {
|
|
4618
|
+
return 4;
|
|
4619
|
+
}
|
|
3889
4620
|
if (reason.startsWith("assertion failed:")) {
|
|
3890
4621
|
return 4;
|
|
3891
4622
|
}
|
|
4623
|
+
if (reason.startsWith("timeout:") || reason.startsWith("async loop:") || reason.startsWith("django db access:") || reason.startsWith("db migration:")) {
|
|
4624
|
+
return 3;
|
|
4625
|
+
}
|
|
4626
|
+
if (reason.startsWith("permission:") || reason.startsWith("xdist worker crash:") || reason.startsWith("network:") || reason.startsWith("segfault:") || reason.startsWith("memory:") || reason.startsWith("type error:") || reason.startsWith("serialization:") || reason.startsWith("file not found:") || reason.startsWith("deprecation as error:") || reason.startsWith("xfail strict:") || reason.startsWith("resource leak:") || reason.startsWith("flaky:") || reason.startsWith("fixture teardown:")) {
|
|
4627
|
+
return 2;
|
|
4628
|
+
}
|
|
3892
4629
|
if (/^[A-Z][A-Za-z]+(?:Error|Exception):/.test(reason)) {
|
|
3893
4630
|
return 3;
|
|
3894
4631
|
}
|
|
@@ -3897,6 +4634,16 @@ function scoreFailureReason(reason) {
|
|
|
3897
4634
|
}
|
|
3898
4635
|
return 1;
|
|
3899
4636
|
}
|
|
4637
|
+
function buildClassifiedReason(prefix, detail) {
|
|
4638
|
+
return `${prefix}: ${detail}`.slice(0, 120);
|
|
4639
|
+
}
|
|
4640
|
+
function buildExcerptDetail(value, fallback) {
|
|
4641
|
+
const trimmed = value.trim().replace(/\s+/g, " ");
|
|
4642
|
+
return trimmed.length > 0 ? trimmed : fallback;
|
|
4643
|
+
}
|
|
4644
|
+
function sharedBlockerThreshold(reason) {
|
|
4645
|
+
return reason.startsWith("configuration:") ? 1 : 3;
|
|
4646
|
+
}
|
|
3900
4647
|
function extractEnvBlockerName(normalized) {
|
|
3901
4648
|
const directMatch = normalized.match(
|
|
3902
4649
|
/\bDB-isolated tests require\s+([A-Z][A-Z0-9_]{2,})\b/
|
|
@@ -3996,59 +4743,365 @@ function classifyFailureReason(line, options) {
|
|
|
3996
4743
|
group: "authentication test setup failures"
|
|
3997
4744
|
};
|
|
3998
4745
|
}
|
|
3999
|
-
const
|
|
4000
|
-
/
|
|
4746
|
+
const snapshotMismatch = normalized.match(
|
|
4747
|
+
/((?:Error:\s*)?Snapshot\b.+\bmismatched\b[^$]*|Snapshot comparison failed[^$]*)/i
|
|
4001
4748
|
);
|
|
4002
|
-
if (
|
|
4749
|
+
if (snapshotMismatch) {
|
|
4003
4750
|
return {
|
|
4004
|
-
reason:
|
|
4005
|
-
|
|
4751
|
+
reason: buildClassifiedReason(
|
|
4752
|
+
"snapshot mismatch",
|
|
4753
|
+
buildExcerptDetail(snapshotMismatch[1] ?? normalized, "snapshot output changed")
|
|
4754
|
+
),
|
|
4755
|
+
group: "snapshot mismatches"
|
|
4006
4756
|
};
|
|
4007
4757
|
}
|
|
4008
|
-
const
|
|
4009
|
-
|
|
4758
|
+
const timeoutFailure = normalized.match(
|
|
4759
|
+
/(Failed:\s*Timeout\s*>[^,;]+|asyncio\.exceptions\.TimeoutError:\s*.+|TimeoutError:\s*.+|(?:Test|Hook)\s+timed out in\s+\d+(?:\.\d+)?m?s[^$]*|(?:\[vitest-(?:worker|pool)\]:\s*)?Timeout[^$]*)$/i
|
|
4760
|
+
);
|
|
4761
|
+
if (timeoutFailure) {
|
|
4010
4762
|
return {
|
|
4011
|
-
reason:
|
|
4012
|
-
|
|
4763
|
+
reason: buildClassifiedReason(
|
|
4764
|
+
"timeout",
|
|
4765
|
+
buildExcerptDetail(timeoutFailure[1] ?? normalized, "test exceeded timeout threshold")
|
|
4766
|
+
),
|
|
4767
|
+
group: "timeout failures"
|
|
4013
4768
|
};
|
|
4014
4769
|
}
|
|
4015
|
-
const
|
|
4016
|
-
|
|
4770
|
+
const asyncLoopFailure = normalized.match(
|
|
4771
|
+
/(Event loop is closed|no current event loop|coroutine .* was never awaited|coroutine was never awaited)/i
|
|
4772
|
+
);
|
|
4773
|
+
if (asyncLoopFailure) {
|
|
4017
4774
|
return {
|
|
4018
|
-
reason:
|
|
4019
|
-
|
|
4775
|
+
reason: buildClassifiedReason(
|
|
4776
|
+
"async loop",
|
|
4777
|
+
buildExcerptDetail(asyncLoopFailure[1] ?? normalized, "async event loop failure")
|
|
4778
|
+
),
|
|
4779
|
+
group: "async event loop failures"
|
|
4020
4780
|
};
|
|
4021
4781
|
}
|
|
4022
|
-
const
|
|
4023
|
-
|
|
4024
|
-
|
|
4782
|
+
const permissionFailure = normalized.match(
|
|
4783
|
+
/(PermissionError:\s*\[Errno 13\][^$]*|Address already in use)/i
|
|
4784
|
+
);
|
|
4785
|
+
if (permissionFailure) {
|
|
4025
4786
|
return {
|
|
4026
|
-
reason:
|
|
4027
|
-
|
|
4787
|
+
reason: buildClassifiedReason(
|
|
4788
|
+
"permission",
|
|
4789
|
+
buildExcerptDetail(permissionFailure[1] ?? normalized, "permission denied or resource locked")
|
|
4790
|
+
),
|
|
4791
|
+
group: "permission or locked resource failures"
|
|
4028
4792
|
};
|
|
4029
4793
|
}
|
|
4030
|
-
|
|
4794
|
+
const osDiskFullFailure = normalized.match(
|
|
4795
|
+
/(OSError:\s*\[Errno 28\][^$]*|No space left on device)/i
|
|
4796
|
+
);
|
|
4797
|
+
if (osDiskFullFailure) {
|
|
4031
4798
|
return {
|
|
4032
|
-
reason:
|
|
4033
|
-
|
|
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"
|
|
4034
4807
|
};
|
|
4035
4808
|
}
|
|
4036
|
-
|
|
4037
|
-
|
|
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
|
+
};
|
|
4038
4818
|
}
|
|
4039
|
-
|
|
4040
|
-
|
|
4041
|
-
|
|
4042
|
-
|
|
4043
|
-
|
|
4044
|
-
|
|
4045
|
-
|
|
4046
|
-
|
|
4819
|
+
const xdistWorkerCrash = normalized.match(
|
|
4820
|
+
/(worker ['"][^'"]+['"] crashed|node down:\s*[^,;]+|WorkerLost[^,;]*|Worker exited unexpectedly[^,;]*|worker exited unexpectedly[^,;]*)/i
|
|
4821
|
+
);
|
|
4822
|
+
if (xdistWorkerCrash) {
|
|
4823
|
+
return {
|
|
4824
|
+
reason: buildClassifiedReason(
|
|
4825
|
+
"xdist worker crash",
|
|
4826
|
+
buildExcerptDetail(xdistWorkerCrash[1] ?? normalized, "pytest-xdist worker crashed")
|
|
4827
|
+
),
|
|
4828
|
+
group: "xdist worker crashes"
|
|
4829
|
+
};
|
|
4047
4830
|
}
|
|
4048
|
-
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
|
|
4831
|
+
if (/Worker terminated due to reaching memory limit/i.test(normalized)) {
|
|
4832
|
+
return {
|
|
4833
|
+
reason: "memory: Worker terminated due to reaching memory limit",
|
|
4834
|
+
group: "memory exhaustion failures"
|
|
4835
|
+
};
|
|
4836
|
+
}
|
|
4837
|
+
if (/Database access not allowed, use the ["']django_db["'] mark/i.test(normalized)) {
|
|
4838
|
+
return {
|
|
4839
|
+
reason: 'django db access: Database access not allowed, use the "django_db" mark',
|
|
4840
|
+
group: "django database marker failures"
|
|
4841
|
+
};
|
|
4842
|
+
}
|
|
4843
|
+
const networkFailure = normalized.match(
|
|
4844
|
+
/(Max retries exceeded[^,;]*|gaierror[^,;]*|SSLCertVerificationError[^,;]*|Network is unreachable|ConnectionResetError[^,;]*|BrokenPipeError[^,;]*|HTTPError:\s*[45]\d\d[^,;]*)/i
|
|
4845
|
+
);
|
|
4846
|
+
if (networkFailure) {
|
|
4847
|
+
return {
|
|
4848
|
+
reason: buildClassifiedReason(
|
|
4849
|
+
"network",
|
|
4850
|
+
buildExcerptDetail(networkFailure[1] ?? normalized, "network dependency failure")
|
|
4851
|
+
),
|
|
4852
|
+
group: "network dependency failures"
|
|
4853
|
+
};
|
|
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
|
+
}
|
|
4864
|
+
const relationMigration = normalized.match(/relation ["'`]([^"'`]+)["'`] does not exist/i);
|
|
4865
|
+
if (relationMigration) {
|
|
4866
|
+
return {
|
|
4867
|
+
reason: buildClassifiedReason("db migration", `relation ${relationMigration[1]} does not exist`),
|
|
4868
|
+
group: "database migration or schema failures"
|
|
4869
|
+
};
|
|
4870
|
+
}
|
|
4871
|
+
const noSuchTable = normalized.match(/no such table(?::)?\s*([A-Za-z0-9_.-]+)/i);
|
|
4872
|
+
if (noSuchTable) {
|
|
4873
|
+
return {
|
|
4874
|
+
reason: buildClassifiedReason("db migration", `no such table ${noSuchTable[1]}`),
|
|
4875
|
+
group: "database migration or schema failures"
|
|
4876
|
+
};
|
|
4877
|
+
}
|
|
4878
|
+
if (/InconsistentMigrationHistory/i.test(normalized)) {
|
|
4879
|
+
return {
|
|
4880
|
+
reason: "db migration: InconsistentMigrationHistory",
|
|
4881
|
+
group: "database migration or schema failures"
|
|
4882
|
+
};
|
|
4883
|
+
}
|
|
4884
|
+
if (/(Segmentation fault|SIGSEGV|\bexit 139\b)/i.test(normalized)) {
|
|
4885
|
+
return {
|
|
4886
|
+
reason: buildClassifiedReason(
|
|
4887
|
+
"segfault",
|
|
4888
|
+
buildExcerptDetail(normalized, "subprocess crashed with SIGSEGV")
|
|
4889
|
+
),
|
|
4890
|
+
group: "subprocess crash failures"
|
|
4891
|
+
};
|
|
4892
|
+
}
|
|
4893
|
+
if (/(MemoryError\b|\bexit 137\b|OOMKilled|OutOfMemory)/i.test(normalized)) {
|
|
4894
|
+
return {
|
|
4895
|
+
reason: buildClassifiedReason(
|
|
4896
|
+
"memory",
|
|
4897
|
+
buildExcerptDetail(normalized, "process exhausted available memory")
|
|
4898
|
+
),
|
|
4899
|
+
group: "memory exhaustion failures"
|
|
4900
|
+
};
|
|
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
|
+
}
|
|
4930
|
+
const typeErrorFailure = normalized.match(/TypeError:\s*(.+)$/i);
|
|
4931
|
+
if (typeErrorFailure) {
|
|
4932
|
+
return {
|
|
4933
|
+
reason: buildClassifiedReason(
|
|
4934
|
+
"type error",
|
|
4935
|
+
buildExcerptDetail(typeErrorFailure[1] ?? normalized, "TypeError")
|
|
4936
|
+
),
|
|
4937
|
+
group: "type errors"
|
|
4938
|
+
};
|
|
4939
|
+
}
|
|
4940
|
+
const serializationFailure = normalized.match(
|
|
4941
|
+
/\b(UnicodeDecodeError|JSONDecodeError|PicklingError):\s*(.+)$/i
|
|
4942
|
+
);
|
|
4943
|
+
if (serializationFailure) {
|
|
4944
|
+
return {
|
|
4945
|
+
reason: buildClassifiedReason(
|
|
4946
|
+
"serialization",
|
|
4947
|
+
`${serializationFailure[1]}: ${buildExcerptDetail(serializationFailure[2] ?? "", serializationFailure[1] ?? "serialization failure")}`
|
|
4948
|
+
),
|
|
4949
|
+
group: "serialization and encoding failures"
|
|
4950
|
+
};
|
|
4951
|
+
}
|
|
4952
|
+
const fileNotFoundFailure = normalized.match(/FileNotFoundError:\s*(.+)$/i);
|
|
4953
|
+
if (fileNotFoundFailure) {
|
|
4954
|
+
return {
|
|
4955
|
+
reason: buildClassifiedReason(
|
|
4956
|
+
"file not found",
|
|
4957
|
+
buildExcerptDetail(fileNotFoundFailure[1] ?? normalized, "missing file during test execution")
|
|
4958
|
+
),
|
|
4959
|
+
group: "missing file failures"
|
|
4960
|
+
};
|
|
4961
|
+
}
|
|
4962
|
+
const deprecationFailure = normalized.match(
|
|
4963
|
+
/\b(DeprecationWarning|FutureWarning|PytestRemovedIn9Warning):\s*(.+)$/i
|
|
4964
|
+
);
|
|
4965
|
+
if (deprecationFailure) {
|
|
4966
|
+
return {
|
|
4967
|
+
reason: buildClassifiedReason(
|
|
4968
|
+
"deprecation as error",
|
|
4969
|
+
`${deprecationFailure[1]}: ${buildExcerptDetail(deprecationFailure[2] ?? "", deprecationFailure[1] ?? "warning treated as error")}`
|
|
4970
|
+
),
|
|
4971
|
+
group: "warnings treated as errors"
|
|
4972
|
+
};
|
|
4973
|
+
}
|
|
4974
|
+
const strictXfail = normalized.match(/XPASS\(strict\)\s*:?\s*(.+)?$/i);
|
|
4975
|
+
if (strictXfail) {
|
|
4976
|
+
return {
|
|
4977
|
+
reason: buildClassifiedReason(
|
|
4978
|
+
"xfail strict",
|
|
4979
|
+
buildExcerptDetail(strictXfail[1] ?? normalized, "strict xfail unexpectedly passed")
|
|
4980
|
+
),
|
|
4981
|
+
group: "strict xfail expectation failures"
|
|
4982
|
+
};
|
|
4983
|
+
}
|
|
4984
|
+
const resourceLeak = normalized.match(
|
|
4985
|
+
/(PytestUnraisableExceptionWarning[^,;]*|ResourceWarning:\s*unclosed[^,;]*)/i
|
|
4986
|
+
);
|
|
4987
|
+
if (resourceLeak) {
|
|
4988
|
+
return {
|
|
4989
|
+
reason: buildClassifiedReason(
|
|
4990
|
+
"resource leak",
|
|
4991
|
+
buildExcerptDetail(resourceLeak[1] ?? normalized, "resource leak warning promoted to failure")
|
|
4992
|
+
),
|
|
4993
|
+
group: "resource leak warnings"
|
|
4994
|
+
};
|
|
4995
|
+
}
|
|
4996
|
+
const flakyFailure = normalized.match(/\b(RERUN|[0-9]+\s+rerun|Flaky test passed)\b[^$]*/i);
|
|
4997
|
+
if (flakyFailure) {
|
|
4998
|
+
return {
|
|
4999
|
+
reason: buildClassifiedReason(
|
|
5000
|
+
"flaky",
|
|
5001
|
+
buildExcerptDetail(flakyFailure[0] ?? normalized, "test required reruns before passing")
|
|
5002
|
+
),
|
|
5003
|
+
group: "flaky test detections"
|
|
5004
|
+
};
|
|
5005
|
+
}
|
|
5006
|
+
const teardownFailure = normalized.match(/ERROR at teardown of\s+(.+)$/i);
|
|
5007
|
+
if (teardownFailure) {
|
|
5008
|
+
return {
|
|
5009
|
+
reason: buildClassifiedReason(
|
|
5010
|
+
"fixture teardown",
|
|
5011
|
+
buildExcerptDetail(teardownFailure[1] ?? normalized, "fixture teardown failed")
|
|
5012
|
+
),
|
|
5013
|
+
group: "fixture teardown failures"
|
|
5014
|
+
};
|
|
5015
|
+
}
|
|
5016
|
+
const configurationFailure = normalized.match(
|
|
5017
|
+
/(INTERNALERROR>.+|ConftestImportFailure[^,;]*|UsageError:\s*.+|ERROR:\s*usage:\s*.+|pytest:\s*error:\s*.+|Cannot use import statement outside a module[^$]*|Named export.+not found.+CommonJS[^$]*|failed to load config from.+|localStorage is not available[^$]*|No test suite found in file.+|No test found in suite.+)$/i
|
|
5018
|
+
);
|
|
5019
|
+
if (configurationFailure) {
|
|
5020
|
+
return {
|
|
5021
|
+
reason: buildClassifiedReason(
|
|
5022
|
+
"configuration",
|
|
5023
|
+
buildExcerptDetail(configurationFailure[1] ?? normalized, "test configuration error")
|
|
5024
|
+
),
|
|
5025
|
+
group: "test configuration failures"
|
|
5026
|
+
};
|
|
5027
|
+
}
|
|
5028
|
+
const pythonMissingModule = normalized.match(
|
|
5029
|
+
/ModuleNotFoundError:\s+No module named ['"]([^'"]+)['"]/i
|
|
5030
|
+
);
|
|
5031
|
+
if (pythonMissingModule) {
|
|
5032
|
+
return {
|
|
5033
|
+
reason: `missing module: ${pythonMissingModule[1]}`,
|
|
5034
|
+
group: options.duringCollection ? "import/dependency errors during collection" : "missing dependency/module errors"
|
|
5035
|
+
};
|
|
5036
|
+
}
|
|
5037
|
+
const nodeMissingModule = normalized.match(/Cannot find module ['"]([^'"]+)['"]/i);
|
|
5038
|
+
if (nodeMissingModule) {
|
|
5039
|
+
return {
|
|
5040
|
+
reason: `missing module: ${nodeMissingModule[1]}`,
|
|
5041
|
+
group: options.duringCollection ? "import/dependency errors during collection" : "missing dependency/module errors"
|
|
5042
|
+
};
|
|
5043
|
+
}
|
|
5044
|
+
const importResolutionFailure = normalized.match(/Failed to resolve import ['"]([^'"]+)['"]/i);
|
|
5045
|
+
if (importResolutionFailure) {
|
|
5046
|
+
return {
|
|
5047
|
+
reason: `missing module: ${importResolutionFailure[1]}`,
|
|
5048
|
+
group: options.duringCollection ? "import/dependency errors during collection" : "missing dependency/module errors"
|
|
5049
|
+
};
|
|
5050
|
+
}
|
|
5051
|
+
const esmModuleFailure = normalized.match(/ERR_MODULE_NOT_FOUND[^'"`]*['"`]([^'"`]+)['"`]/i) ?? normalized.match(/Cannot find package ['"`]([^'"`]+)['"`]/i);
|
|
5052
|
+
if (esmModuleFailure) {
|
|
5053
|
+
return {
|
|
5054
|
+
reason: `missing module: ${esmModuleFailure[1]}`,
|
|
5055
|
+
group: options.duringCollection ? "import/dependency errors during collection" : "missing dependency/module errors"
|
|
5056
|
+
};
|
|
5057
|
+
}
|
|
5058
|
+
const assertionFailure = normalized.match(/AssertionError:\s*(.+)$/i);
|
|
5059
|
+
if (assertionFailure) {
|
|
5060
|
+
return {
|
|
5061
|
+
reason: `assertion failed: ${assertionFailure[1]}`.slice(0, 120),
|
|
5062
|
+
group: "assertion failures"
|
|
5063
|
+
};
|
|
5064
|
+
}
|
|
5065
|
+
const vitestUnhandled = normalized.match(/Vitest caught\s+\d+\s+unhandled errors?/i);
|
|
5066
|
+
if (vitestUnhandled) {
|
|
5067
|
+
return {
|
|
5068
|
+
reason: `RuntimeError: ${buildExcerptDetail(vitestUnhandled[0] ?? normalized, "Vitest caught unhandled errors")}`.slice(
|
|
5069
|
+
0,
|
|
5070
|
+
120
|
|
5071
|
+
),
|
|
5072
|
+
group: "runtime failures"
|
|
5073
|
+
};
|
|
5074
|
+
}
|
|
5075
|
+
const genericError = normalized.match(/\b([A-Z][A-Za-z]+(?:Error|Exception)):\s*(.+)$/);
|
|
5076
|
+
if (genericError) {
|
|
5077
|
+
const errorType = genericError[1];
|
|
5078
|
+
return {
|
|
5079
|
+
reason: `${errorType}: ${genericError[2]}`.slice(0, 120),
|
|
5080
|
+
group: options.duringCollection && errorType === "ImportError" ? "import/dependency errors during collection" : `${errorType} failures`
|
|
5081
|
+
};
|
|
5082
|
+
}
|
|
5083
|
+
if (/ImportError while importing test module/i.test(normalized)) {
|
|
5084
|
+
return {
|
|
5085
|
+
reason: "import error during collection",
|
|
5086
|
+
group: "import/dependency errors during collection"
|
|
5087
|
+
};
|
|
5088
|
+
}
|
|
5089
|
+
if (!/[A-Za-z]/.test(normalized)) {
|
|
5090
|
+
return null;
|
|
5091
|
+
}
|
|
5092
|
+
return {
|
|
5093
|
+
reason: normalized.slice(0, 120),
|
|
5094
|
+
group: options.duringCollection ? "collection/import errors" : "other failures"
|
|
5095
|
+
};
|
|
5096
|
+
}
|
|
5097
|
+
function pushFocusedFailureItem(items, candidate) {
|
|
5098
|
+
if (items.some((item) => item.label === candidate.label && item.reason === candidate.reason)) {
|
|
5099
|
+
return;
|
|
5100
|
+
}
|
|
5101
|
+
items.push(candidate);
|
|
5102
|
+
}
|
|
5103
|
+
function chooseStrongestFailureItems(items) {
|
|
5104
|
+
const strongest = /* @__PURE__ */ new Map();
|
|
4052
5105
|
const order = [];
|
|
4053
5106
|
for (const item of items) {
|
|
4054
5107
|
const existing = strongest.get(item.label);
|
|
@@ -4063,6 +5116,125 @@ function chooseStrongestFailureItems(items) {
|
|
|
4063
5116
|
}
|
|
4064
5117
|
return order.map((label) => strongest.get(label));
|
|
4065
5118
|
}
|
|
5119
|
+
function extractJsTestFile(value) {
|
|
5120
|
+
const match = value.match(/([A-Za-z0-9_./-]+\.(?:test|spec)\.[cm]?[jt]sx?)/i);
|
|
5121
|
+
return match ? normalizeAnchorFile(match[1]) : null;
|
|
5122
|
+
}
|
|
5123
|
+
function normalizeJsFailureLabel(label) {
|
|
5124
|
+
return cleanFailureLabel(label).replace(/^[❯×]\s*/, "").replace(/\s+\[[^\]]+\]\s*$/, "").replace(/\s+/g, " ").trim();
|
|
5125
|
+
}
|
|
5126
|
+
function classifyFailureLines(args) {
|
|
5127
|
+
let observedAnchor = null;
|
|
5128
|
+
let strongest = null;
|
|
5129
|
+
for (const line of args.lines) {
|
|
5130
|
+
observedAnchor = parseObservedAnchor(line) ?? observedAnchor;
|
|
5131
|
+
const classification = classifyFailureReason(line, {
|
|
5132
|
+
duringCollection: args.duringCollection
|
|
5133
|
+
});
|
|
5134
|
+
if (!classification) {
|
|
5135
|
+
continue;
|
|
5136
|
+
}
|
|
5137
|
+
const score = scoreFailureReason(classification.reason);
|
|
5138
|
+
if (!strongest || score > strongest.score) {
|
|
5139
|
+
strongest = {
|
|
5140
|
+
classification,
|
|
5141
|
+
score,
|
|
5142
|
+
observedAnchor: parseObservedAnchor(line) ?? observedAnchor
|
|
5143
|
+
};
|
|
5144
|
+
}
|
|
5145
|
+
}
|
|
5146
|
+
if (!strongest) {
|
|
5147
|
+
return null;
|
|
5148
|
+
}
|
|
5149
|
+
return {
|
|
5150
|
+
classification: strongest.classification,
|
|
5151
|
+
observedAnchor: strongest.observedAnchor ?? observedAnchor
|
|
5152
|
+
};
|
|
5153
|
+
}
|
|
5154
|
+
function collectJsFailureBlocks(input) {
|
|
5155
|
+
const blocks = [];
|
|
5156
|
+
let current = null;
|
|
5157
|
+
let section = null;
|
|
5158
|
+
let currentFile = null;
|
|
5159
|
+
const flushCurrent = () => {
|
|
5160
|
+
if (!current) {
|
|
5161
|
+
return;
|
|
5162
|
+
}
|
|
5163
|
+
blocks.push(current);
|
|
5164
|
+
current = null;
|
|
5165
|
+
};
|
|
5166
|
+
for (const rawLine of input.split("\n")) {
|
|
5167
|
+
const line = rawLine.trimEnd();
|
|
5168
|
+
const trimmed = line.trim();
|
|
5169
|
+
if (/⎯{2,}\s+Failed Tests?\s+\d+\s+⎯{2,}/.test(line)) {
|
|
5170
|
+
flushCurrent();
|
|
5171
|
+
section = "failed_tests";
|
|
5172
|
+
continue;
|
|
5173
|
+
}
|
|
5174
|
+
if (/⎯{2,}\s+Failed Suites?\s+\d+\s+⎯{2,}/.test(line)) {
|
|
5175
|
+
flushCurrent();
|
|
5176
|
+
section = "failed_suites";
|
|
5177
|
+
continue;
|
|
5178
|
+
}
|
|
5179
|
+
if (section && /^⎯{2,}.+⎯{2,}\s*$/.test(line)) {
|
|
5180
|
+
flushCurrent();
|
|
5181
|
+
section = null;
|
|
5182
|
+
continue;
|
|
5183
|
+
}
|
|
5184
|
+
const progress = line.match(
|
|
5185
|
+
/^(.+?\.(?:test|spec)\.[cm]?[jt]sx?(?:\s+>.+?)?)\s+(FAILED|ERROR)\s+\[[^\]]+\]\s*$/
|
|
5186
|
+
);
|
|
5187
|
+
if (progress) {
|
|
5188
|
+
flushCurrent();
|
|
5189
|
+
const label = normalizeJsFailureLabel(progress[1]);
|
|
5190
|
+
current = {
|
|
5191
|
+
label,
|
|
5192
|
+
status: progress[2] === "ERROR" ? "error" : "failed",
|
|
5193
|
+
detailLines: []
|
|
5194
|
+
};
|
|
5195
|
+
currentFile = extractJsTestFile(label);
|
|
5196
|
+
continue;
|
|
5197
|
+
}
|
|
5198
|
+
const failHeader = line.match(/^\s*FAIL\s+(.+)$/);
|
|
5199
|
+
if (failHeader) {
|
|
5200
|
+
const label = normalizeJsFailureLabel(failHeader[1]);
|
|
5201
|
+
if (extractJsTestFile(label)) {
|
|
5202
|
+
flushCurrent();
|
|
5203
|
+
current = {
|
|
5204
|
+
label,
|
|
5205
|
+
status: section === "failed_suites" || !label.includes(" > ") ? "error" : "failed",
|
|
5206
|
+
detailLines: []
|
|
5207
|
+
};
|
|
5208
|
+
currentFile = extractJsTestFile(label);
|
|
5209
|
+
continue;
|
|
5210
|
+
}
|
|
5211
|
+
}
|
|
5212
|
+
const failedTest = line.match(/^\s*×\s+(.+)$/);
|
|
5213
|
+
if (failedTest && (section === "failed_tests" || extractJsTestFile(failedTest[1]))) {
|
|
5214
|
+
flushCurrent();
|
|
5215
|
+
const candidate = normalizeJsFailureLabel(failedTest[1]);
|
|
5216
|
+
const file = extractJsTestFile(candidate) ?? currentFile;
|
|
5217
|
+
const label = file && !extractJsTestFile(candidate) ? `${file} > ${candidate}` : candidate;
|
|
5218
|
+
current = {
|
|
5219
|
+
label,
|
|
5220
|
+
status: "failed",
|
|
5221
|
+
detailLines: []
|
|
5222
|
+
};
|
|
5223
|
+
currentFile = extractJsTestFile(label) ?? currentFile;
|
|
5224
|
+
continue;
|
|
5225
|
+
}
|
|
5226
|
+
if (/^\s*(?:Tests?|Snapshots?|Test Files?|Test Suites?)\b/.test(line)) {
|
|
5227
|
+
flushCurrent();
|
|
5228
|
+
section = null;
|
|
5229
|
+
continue;
|
|
5230
|
+
}
|
|
5231
|
+
if (current && trimmed.length > 0) {
|
|
5232
|
+
current.detailLines.push(line);
|
|
5233
|
+
}
|
|
5234
|
+
}
|
|
5235
|
+
flushCurrent();
|
|
5236
|
+
return blocks;
|
|
5237
|
+
}
|
|
4066
5238
|
function collectCollectionFailureItems(input) {
|
|
4067
5239
|
const items = [];
|
|
4068
5240
|
const lines = input.split("\n");
|
|
@@ -4070,6 +5242,24 @@ function collectCollectionFailureItems(input) {
|
|
|
4070
5242
|
let pendingGenericReason = null;
|
|
4071
5243
|
let currentAnchor = null;
|
|
4072
5244
|
for (const line of lines) {
|
|
5245
|
+
const standaloneCollectionLabel = line.match(/No test suite found in file\s+(.+)$/i)?.[1] ?? line.match(/No test found in suite\s+(.+)$/i)?.[1] ?? line.match(/failed to load config from\s+(.+)$/i)?.[1];
|
|
5246
|
+
if (standaloneCollectionLabel) {
|
|
5247
|
+
const classification2 = classifyFailureReason(line, {
|
|
5248
|
+
duringCollection: true
|
|
5249
|
+
});
|
|
5250
|
+
if (classification2) {
|
|
5251
|
+
pushFocusedFailureItem(items, {
|
|
5252
|
+
label: cleanFailureLabel(standaloneCollectionLabel),
|
|
5253
|
+
reason: classification2.reason,
|
|
5254
|
+
group: classification2.group,
|
|
5255
|
+
...resolveAnchorForLabel({
|
|
5256
|
+
label: cleanFailureLabel(standaloneCollectionLabel),
|
|
5257
|
+
observedAnchor: parseObservedAnchor(line)
|
|
5258
|
+
})
|
|
5259
|
+
});
|
|
5260
|
+
}
|
|
5261
|
+
continue;
|
|
5262
|
+
}
|
|
4073
5263
|
const collecting = line.match(/^_+\s+ERROR collecting\s+(.+?)\s+_+\s*$/);
|
|
4074
5264
|
if (collecting) {
|
|
4075
5265
|
if (currentLabel && pendingGenericReason) {
|
|
@@ -4158,6 +5348,24 @@ function collectInlineFailureItems(input) {
|
|
|
4158
5348
|
})
|
|
4159
5349
|
});
|
|
4160
5350
|
}
|
|
5351
|
+
for (const block of collectJsFailureBlocks(input)) {
|
|
5352
|
+
const resolved = classifyFailureLines({
|
|
5353
|
+
lines: block.detailLines,
|
|
5354
|
+
duringCollection: block.status === "error"
|
|
5355
|
+
});
|
|
5356
|
+
if (!resolved) {
|
|
5357
|
+
continue;
|
|
5358
|
+
}
|
|
5359
|
+
pushFocusedFailureItem(items, {
|
|
5360
|
+
label: block.label,
|
|
5361
|
+
reason: resolved.classification.reason,
|
|
5362
|
+
group: resolved.classification.group,
|
|
5363
|
+
...resolveAnchorForLabel({
|
|
5364
|
+
label: block.label,
|
|
5365
|
+
observedAnchor: resolved.observedAnchor
|
|
5366
|
+
})
|
|
5367
|
+
});
|
|
5368
|
+
}
|
|
4161
5369
|
return items;
|
|
4162
5370
|
}
|
|
4163
5371
|
function collectInlineFailureItemsWithStatus(input) {
|
|
@@ -4192,16 +5400,42 @@ function collectInlineFailureItemsWithStatus(input) {
|
|
|
4192
5400
|
})
|
|
4193
5401
|
});
|
|
4194
5402
|
}
|
|
5403
|
+
for (const block of collectJsFailureBlocks(input)) {
|
|
5404
|
+
const resolved = classifyFailureLines({
|
|
5405
|
+
lines: block.detailLines,
|
|
5406
|
+
duringCollection: block.status === "error"
|
|
5407
|
+
});
|
|
5408
|
+
if (!resolved) {
|
|
5409
|
+
continue;
|
|
5410
|
+
}
|
|
5411
|
+
items.push({
|
|
5412
|
+
label: block.label,
|
|
5413
|
+
reason: resolved.classification.reason,
|
|
5414
|
+
group: resolved.classification.group,
|
|
5415
|
+
status: block.status,
|
|
5416
|
+
...resolveAnchorForLabel({
|
|
5417
|
+
label: block.label,
|
|
5418
|
+
observedAnchor: resolved.observedAnchor
|
|
5419
|
+
})
|
|
5420
|
+
});
|
|
5421
|
+
}
|
|
4195
5422
|
return items;
|
|
4196
5423
|
}
|
|
4197
5424
|
function collectStandaloneErrorClassifications(input) {
|
|
4198
5425
|
const classifications = [];
|
|
4199
5426
|
for (const line of input.split("\n")) {
|
|
5427
|
+
const trimmed = line.trim();
|
|
5428
|
+
if (!trimmed) {
|
|
5429
|
+
continue;
|
|
5430
|
+
}
|
|
4200
5431
|
const standalone = line.match(/^\s*E\s+(.+)$/);
|
|
4201
|
-
|
|
5432
|
+
const candidate = standalone?.[1] ?? (/^(INTERNALERROR>|ConftestImportFailure\b|UsageError:|ERROR:\s*usage:|pytest:\s*error:)/i.test(
|
|
5433
|
+
trimmed
|
|
5434
|
+
) ? trimmed : null);
|
|
5435
|
+
if (!candidate) {
|
|
4202
5436
|
continue;
|
|
4203
5437
|
}
|
|
4204
|
-
const classification = classifyFailureReason(
|
|
5438
|
+
const classification = classifyFailureReason(candidate, {
|
|
4205
5439
|
duringCollection: false
|
|
4206
5440
|
});
|
|
4207
5441
|
if (!classification || classification.reason === "import error during collection") {
|
|
@@ -4317,6 +5551,9 @@ function collectFailureLabels(input) {
|
|
|
4317
5551
|
pushLabel(summary[2], summary[1] === "FAILED" ? "failed" : "error");
|
|
4318
5552
|
}
|
|
4319
5553
|
}
|
|
5554
|
+
for (const block of collectJsFailureBlocks(input)) {
|
|
5555
|
+
pushLabel(block.label, block.status);
|
|
5556
|
+
}
|
|
4320
5557
|
return labels;
|
|
4321
5558
|
}
|
|
4322
5559
|
function classifyBucketTypeFromReason(reason) {
|
|
@@ -4326,6 +5563,60 @@ function classifyBucketTypeFromReason(reason) {
|
|
|
4326
5563
|
if (reason.startsWith("fixture guard:")) {
|
|
4327
5564
|
return "fixture_guard_failure";
|
|
4328
5565
|
}
|
|
5566
|
+
if (reason.startsWith("timeout:")) {
|
|
5567
|
+
return "timeout_failure";
|
|
5568
|
+
}
|
|
5569
|
+
if (reason.startsWith("permission:")) {
|
|
5570
|
+
return "permission_denied_failure";
|
|
5571
|
+
}
|
|
5572
|
+
if (reason.startsWith("async loop:")) {
|
|
5573
|
+
return "async_event_loop_failure";
|
|
5574
|
+
}
|
|
5575
|
+
if (reason.startsWith("fixture teardown:")) {
|
|
5576
|
+
return "fixture_teardown_failure";
|
|
5577
|
+
}
|
|
5578
|
+
if (reason.startsWith("db migration:")) {
|
|
5579
|
+
return "db_migration_failure";
|
|
5580
|
+
}
|
|
5581
|
+
if (reason.startsWith("configuration:")) {
|
|
5582
|
+
return "configuration_error";
|
|
5583
|
+
}
|
|
5584
|
+
if (reason.startsWith("xdist worker crash:")) {
|
|
5585
|
+
return "xdist_worker_crash";
|
|
5586
|
+
}
|
|
5587
|
+
if (reason.startsWith("type error:")) {
|
|
5588
|
+
return "type_error_failure";
|
|
5589
|
+
}
|
|
5590
|
+
if (reason.startsWith("resource leak:")) {
|
|
5591
|
+
return "resource_leak_warning";
|
|
5592
|
+
}
|
|
5593
|
+
if (reason.startsWith("django db access:")) {
|
|
5594
|
+
return "django_db_access_denied";
|
|
5595
|
+
}
|
|
5596
|
+
if (reason.startsWith("network:")) {
|
|
5597
|
+
return "network_failure";
|
|
5598
|
+
}
|
|
5599
|
+
if (reason.startsWith("segfault:")) {
|
|
5600
|
+
return "subprocess_crash_segfault";
|
|
5601
|
+
}
|
|
5602
|
+
if (reason.startsWith("flaky:")) {
|
|
5603
|
+
return "flaky_test_detected";
|
|
5604
|
+
}
|
|
5605
|
+
if (reason.startsWith("serialization:")) {
|
|
5606
|
+
return "serialization_encoding_failure";
|
|
5607
|
+
}
|
|
5608
|
+
if (reason.startsWith("file not found:")) {
|
|
5609
|
+
return "file_not_found_failure";
|
|
5610
|
+
}
|
|
5611
|
+
if (reason.startsWith("memory:")) {
|
|
5612
|
+
return "memory_error";
|
|
5613
|
+
}
|
|
5614
|
+
if (reason.startsWith("deprecation as error:")) {
|
|
5615
|
+
return "deprecation_warning_as_error";
|
|
5616
|
+
}
|
|
5617
|
+
if (reason.startsWith("xfail strict:")) {
|
|
5618
|
+
return "xfail_strict_unexpected_pass";
|
|
5619
|
+
}
|
|
4329
5620
|
if (reason.startsWith("service unavailable:")) {
|
|
4330
5621
|
return "service_unavailable";
|
|
4331
5622
|
}
|
|
@@ -4335,6 +5626,9 @@ function classifyBucketTypeFromReason(reason) {
|
|
|
4335
5626
|
if (reason.startsWith("auth bypass absent:")) {
|
|
4336
5627
|
return "auth_bypass_absent";
|
|
4337
5628
|
}
|
|
5629
|
+
if (reason.startsWith("snapshot mismatch:")) {
|
|
5630
|
+
return "snapshot_mismatch";
|
|
5631
|
+
}
|
|
4338
5632
|
if (reason.startsWith("missing module:")) {
|
|
4339
5633
|
return "import_dependency_failure";
|
|
4340
5634
|
}
|
|
@@ -4347,9 +5641,6 @@ function classifyBucketTypeFromReason(reason) {
|
|
|
4347
5641
|
return "unknown_failure";
|
|
4348
5642
|
}
|
|
4349
5643
|
function synthesizeSharedBlockerBucket(args) {
|
|
4350
|
-
if (args.errors === 0) {
|
|
4351
|
-
return null;
|
|
4352
|
-
}
|
|
4353
5644
|
const visibleReasonGroups = /* @__PURE__ */ new Map();
|
|
4354
5645
|
for (const item of args.visibleErrorItems) {
|
|
4355
5646
|
const entry = visibleReasonGroups.get(item.reason);
|
|
@@ -4364,7 +5655,7 @@ function synthesizeSharedBlockerBucket(args) {
|
|
|
4364
5655
|
items: [item]
|
|
4365
5656
|
});
|
|
4366
5657
|
}
|
|
4367
|
-
const top = [...visibleReasonGroups.entries()].filter(([, entry]) => entry.count >=
|
|
5658
|
+
const top = [...visibleReasonGroups.entries()].filter(([reason, entry]) => entry.count >= sharedBlockerThreshold(reason)).sort((left, right) => right[1].count - left[1].count)[0];
|
|
4368
5659
|
const standaloneReasonGroups = /* @__PURE__ */ new Map();
|
|
4369
5660
|
for (const classification of collectStandaloneErrorClassifications(args.input)) {
|
|
4370
5661
|
const entry = standaloneReasonGroups.get(classification.reason);
|
|
@@ -4377,7 +5668,7 @@ function synthesizeSharedBlockerBucket(args) {
|
|
|
4377
5668
|
group: classification.group
|
|
4378
5669
|
});
|
|
4379
5670
|
}
|
|
4380
|
-
const standaloneTop = [...standaloneReasonGroups.entries()].filter(([, entry]) => entry.count >=
|
|
5671
|
+
const standaloneTop = [...standaloneReasonGroups.entries()].filter(([reason, entry]) => entry.count >= sharedBlockerThreshold(reason)).sort((left, right) => right[1].count - left[1].count)[0];
|
|
4381
5672
|
const visibleTopReason = top?.[0];
|
|
4382
5673
|
const visibleTopStats = top?.[1];
|
|
4383
5674
|
const standaloneTopReason = standaloneTop?.[0];
|
|
@@ -4416,6 +5707,12 @@ function synthesizeSharedBlockerBucket(args) {
|
|
|
4416
5707
|
let hint;
|
|
4417
5708
|
if (envVar) {
|
|
4418
5709
|
hint = `Set ${envVar} (or pass --pgtest-dsn) before rerunning DB-isolated tests.`;
|
|
5710
|
+
} else if (effectiveReason.startsWith("configuration:")) {
|
|
5711
|
+
hint = "Fix the pytest configuration or conftest import error before rerunning the suite.";
|
|
5712
|
+
} else if (effectiveReason.startsWith("xdist worker crash:")) {
|
|
5713
|
+
hint = "Check shared state, worker startup, or resource contention between xdist workers before rerunning.";
|
|
5714
|
+
} else if (effectiveReason.startsWith("network:")) {
|
|
5715
|
+
hint = "Restore DNS, TLS, or outbound network access for the affected dependency before rerunning.";
|
|
4419
5716
|
} else if (effectiveReason.startsWith("fixture guard:")) {
|
|
4420
5717
|
hint = "Unblock the required fixture or setup guard before rerunning the affected tests.";
|
|
4421
5718
|
} else if (effectiveReason.startsWith("db refused:")) {
|
|
@@ -4430,6 +5727,12 @@ function synthesizeSharedBlockerBucket(args) {
|
|
|
4430
5727
|
let headline;
|
|
4431
5728
|
if (envVar) {
|
|
4432
5729
|
headline = `Shared blocker: ${atLeastPrefix}${countText} errors require ${envVar} for DB-isolated tests.`;
|
|
5730
|
+
} else if (effectiveReason.startsWith("configuration:")) {
|
|
5731
|
+
headline = `Shared blocker: ${atLeastPrefix}${countText} visible failure${countText === 1 ? "" : "s"} are caused by a pytest configuration error.`;
|
|
5732
|
+
} else if (effectiveReason.startsWith("xdist worker crash:")) {
|
|
5733
|
+
headline = `Shared blocker: ${atLeastPrefix}${countText} errors are caused by xdist worker crashes.`;
|
|
5734
|
+
} else if (effectiveReason.startsWith("network:")) {
|
|
5735
|
+
headline = `Shared blocker: ${atLeastPrefix}${countText} errors are caused by a network dependency failure.`;
|
|
4433
5736
|
} else if (effectiveReason.startsWith("fixture guard:")) {
|
|
4434
5737
|
headline = `Shared blocker: ${atLeastPrefix}${countText} errors are gated by the same fixture/setup guard.`;
|
|
4435
5738
|
} else if (effectiveReason.startsWith("db refused:")) {
|
|
@@ -4460,11 +5763,17 @@ function synthesizeSharedBlockerBucket(args) {
|
|
|
4460
5763
|
};
|
|
4461
5764
|
}
|
|
4462
5765
|
function synthesizeImportDependencyBucket(args) {
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
const
|
|
4467
|
-
|
|
5766
|
+
const visibleImportItems = args.visibleErrorItems.filter(
|
|
5767
|
+
(item) => item.reason.startsWith("missing module:")
|
|
5768
|
+
);
|
|
5769
|
+
const inlineImportItems = chooseStrongestFailureItems(
|
|
5770
|
+
args.inlineItems.filter((item) => item.reason.startsWith("missing module:"))
|
|
5771
|
+
);
|
|
5772
|
+
const importItems = visibleImportItems.length > 0 ? visibleImportItems : inlineImportItems.map((item) => ({
|
|
5773
|
+
...item,
|
|
5774
|
+
status: "failed"
|
|
5775
|
+
}));
|
|
5776
|
+
if (importItems.length === 0) {
|
|
4468
5777
|
return null;
|
|
4469
5778
|
}
|
|
4470
5779
|
const allVisibleErrorsAreImportRelated = args.visibleErrorItems.length > 0 && args.visibleErrorItems.every((item) => item.reason.startsWith("missing module:"));
|
|
@@ -4475,7 +5784,7 @@ function synthesizeImportDependencyBucket(args) {
|
|
|
4475
5784
|
)
|
|
4476
5785
|
).slice(0, 6);
|
|
4477
5786
|
const headlineCount = countClaimed ?? importItems.length;
|
|
4478
|
-
const headline = countClaimed ? `Import/dependency blocker: ${headlineCount} errors are caused by missing dependencies during test collection.` : `Import/dependency blocker: at least ${headlineCount} visible
|
|
5787
|
+
const headline = countClaimed ? `Import/dependency blocker: ${headlineCount} errors are caused by missing dependencies during test collection.` : `Import/dependency blocker: at least ${headlineCount} visible failure${headlineCount === 1 ? "" : "s"} are caused by missing dependencies during test collection.`;
|
|
4479
5788
|
const summaryLines = [headline];
|
|
4480
5789
|
if (modules.length > 0) {
|
|
4481
5790
|
summaryLines.push(`Missing modules include ${modules.join(", ")}.`);
|
|
@@ -4485,7 +5794,7 @@ function synthesizeImportDependencyBucket(args) {
|
|
|
4485
5794
|
headline,
|
|
4486
5795
|
countVisible: importItems.length,
|
|
4487
5796
|
countClaimed,
|
|
4488
|
-
reason: "missing dependencies during test collection",
|
|
5797
|
+
reason: modules.length === 1 ? `missing module: ${modules[0]}` : "missing dependencies during test collection",
|
|
4489
5798
|
representativeItems: importItems.slice(0, 4).map((item) => ({
|
|
4490
5799
|
label: item.label,
|
|
4491
5800
|
reason: item.reason,
|
|
@@ -4504,7 +5813,7 @@ function synthesizeImportDependencyBucket(args) {
|
|
|
4504
5813
|
};
|
|
4505
5814
|
}
|
|
4506
5815
|
function isContractDriftLabel(label) {
|
|
4507
|
-
return /(freeze|
|
|
5816
|
+
return /(freeze|contract|manifest|openapi|golden)/i.test(label);
|
|
4508
5817
|
}
|
|
4509
5818
|
function looksLikeTaskKey(value) {
|
|
4510
5819
|
return /^[a-z]+(?:_[a-z0-9]+)+$/i.test(value) && !value.startsWith("/api/");
|
|
@@ -4635,23 +5944,81 @@ function synthesizeContractDriftBucket(args) {
|
|
|
4635
5944
|
overflowLabel: "changed entities"
|
|
4636
5945
|
};
|
|
4637
5946
|
}
|
|
5947
|
+
function synthesizeSnapshotMismatchBucket(args) {
|
|
5948
|
+
const snapshotItems = chooseStrongestFailureItems(
|
|
5949
|
+
args.inlineItems.filter((item) => item.reason.startsWith("snapshot mismatch:"))
|
|
5950
|
+
);
|
|
5951
|
+
if (snapshotItems.length === 0) {
|
|
5952
|
+
return null;
|
|
5953
|
+
}
|
|
5954
|
+
const countClaimed = args.snapshotFailures && args.snapshotFailures >= snapshotItems.length ? args.snapshotFailures : void 0;
|
|
5955
|
+
const countText = countClaimed ?? snapshotItems.length;
|
|
5956
|
+
const summaryLines = [
|
|
5957
|
+
`Snapshot mismatches: ${formatCount2(countText, "snapshot expectation")} ${countText === 1 ? "is" : "are"} out of date with current output.`
|
|
5958
|
+
];
|
|
5959
|
+
return {
|
|
5960
|
+
type: "snapshot_mismatch",
|
|
5961
|
+
headline: summaryLines[0],
|
|
5962
|
+
countVisible: snapshotItems.length,
|
|
5963
|
+
countClaimed,
|
|
5964
|
+
reason: "snapshot mismatch: snapshot expectations differ from current output",
|
|
5965
|
+
representativeItems: snapshotItems.slice(0, 4),
|
|
5966
|
+
entities: snapshotItems.map((item) => item.label.split(" > ").slice(1).join(" > ").trim() || item.label).slice(0, 6),
|
|
5967
|
+
hint: "Update the snapshots if these output changes are intentional.",
|
|
5968
|
+
confidence: countClaimed ? 0.92 : 0.8,
|
|
5969
|
+
summaryLines,
|
|
5970
|
+
overflowCount: Math.max((countClaimed ?? snapshotItems.length) - Math.min(snapshotItems.length, 4), 0),
|
|
5971
|
+
overflowLabel: "snapshot failures"
|
|
5972
|
+
};
|
|
5973
|
+
}
|
|
5974
|
+
function synthesizeTimeoutBucket(args) {
|
|
5975
|
+
const timeoutItems = chooseStrongestFailureItems(
|
|
5976
|
+
args.inlineItems.filter((item) => item.reason.startsWith("timeout:"))
|
|
5977
|
+
);
|
|
5978
|
+
if (timeoutItems.length === 0) {
|
|
5979
|
+
return null;
|
|
5980
|
+
}
|
|
5981
|
+
const summaryLines = [
|
|
5982
|
+
`Timeout failures: ${formatCount2(timeoutItems.length, "test")} exceeded the configured timeout threshold.`
|
|
5983
|
+
];
|
|
5984
|
+
return {
|
|
5985
|
+
type: "timeout_failure",
|
|
5986
|
+
headline: summaryLines[0],
|
|
5987
|
+
countVisible: timeoutItems.length,
|
|
5988
|
+
countClaimed: timeoutItems.length,
|
|
5989
|
+
reason: timeoutItems.length === 1 ? timeoutItems[0].reason : "timeout: tests exceeded the configured timeout threshold",
|
|
5990
|
+
representativeItems: timeoutItems.slice(0, 4),
|
|
5991
|
+
entities: timeoutItems.map((item) => item.label.split(" > ").slice(1).join(" > ").trim() || item.label).slice(0, 6),
|
|
5992
|
+
hint: "Check for deadlocks, slow setup, or increase the timeout threshold before rerunning.",
|
|
5993
|
+
confidence: 0.84,
|
|
5994
|
+
summaryLines,
|
|
5995
|
+
overflowCount: Math.max(timeoutItems.length - Math.min(timeoutItems.length, 4), 0),
|
|
5996
|
+
overflowLabel: "timeout failures"
|
|
5997
|
+
};
|
|
5998
|
+
}
|
|
4638
5999
|
function analyzeTestStatus(input) {
|
|
4639
|
-
const
|
|
4640
|
-
const
|
|
4641
|
-
const
|
|
4642
|
-
const
|
|
6000
|
+
const runner = detectTestRunner(input);
|
|
6001
|
+
const counts = extractTestStatusCounts(input, runner);
|
|
6002
|
+
const passed = counts.passed;
|
|
6003
|
+
const failed = counts.failed;
|
|
6004
|
+
const errors = counts.errors;
|
|
6005
|
+
const skipped = counts.skipped;
|
|
4643
6006
|
const collectionErrors = input.match(/(\d+)\s+errors?\s+during collection/i);
|
|
4644
|
-
const noTestsCollected = /\bcollected\s+0\s+items\b/i.test(input) || /\bno tests ran\b/i.test(input);
|
|
6007
|
+
const noTestsCollected = /\bcollected\s+0\s+items\b/i.test(input) || /\bno tests ran\b/i.test(input) || /No test suite found in file/i.test(input) || /No test found in suite/i.test(input);
|
|
4645
6008
|
const interrupted = /\binterrupted\b/i.test(input) || /\bKeyboardInterrupt\b/i.test(input);
|
|
4646
6009
|
const collectionItems = chooseStrongestFailureItems(collectCollectionFailureItems(input));
|
|
4647
6010
|
const inlineItems = chooseStrongestFailureItems(collectInlineFailureItems(input));
|
|
6011
|
+
const statusItems = collectInlineFailureItemsWithStatus(input);
|
|
4648
6012
|
const visibleErrorItems = chooseStrongestStatusFailureItems([
|
|
4649
6013
|
...collectionItems.map((item) => ({
|
|
4650
6014
|
...item,
|
|
4651
6015
|
status: "error"
|
|
4652
6016
|
})),
|
|
4653
|
-
...
|
|
6017
|
+
...statusItems.filter((item) => item.status === "error")
|
|
4654
6018
|
]);
|
|
6019
|
+
const visibleFailedItems = chooseStrongestStatusFailureItems(
|
|
6020
|
+
statusItems.filter((item) => item.status === "failed")
|
|
6021
|
+
);
|
|
4655
6022
|
const labels = collectFailureLabels(input);
|
|
4656
6023
|
const visibleErrorLabels = labels.filter((item) => item.status === "error").map((item) => item.label);
|
|
4657
6024
|
const visibleFailedLabels = labels.filter((item) => item.status === "failed").map((item) => item.label);
|
|
@@ -4668,7 +6035,8 @@ function analyzeTestStatus(input) {
|
|
|
4668
6035
|
if (!sharedBlocker) {
|
|
4669
6036
|
const importDependencyBucket = synthesizeImportDependencyBucket({
|
|
4670
6037
|
errors,
|
|
4671
|
-
visibleErrorItems
|
|
6038
|
+
visibleErrorItems,
|
|
6039
|
+
inlineItems
|
|
4672
6040
|
});
|
|
4673
6041
|
if (importDependencyBucket) {
|
|
4674
6042
|
buckets.push(importDependencyBucket);
|
|
@@ -4681,11 +6049,26 @@ function analyzeTestStatus(input) {
|
|
|
4681
6049
|
if (contractDrift) {
|
|
4682
6050
|
buckets.push(contractDrift);
|
|
4683
6051
|
}
|
|
6052
|
+
const snapshotMismatch = synthesizeSnapshotMismatchBucket({
|
|
6053
|
+
inlineItems,
|
|
6054
|
+
snapshotFailures: counts.snapshotFailures
|
|
6055
|
+
});
|
|
6056
|
+
if (snapshotMismatch) {
|
|
6057
|
+
buckets.push(snapshotMismatch);
|
|
6058
|
+
}
|
|
6059
|
+
const timeoutBucket = synthesizeTimeoutBucket({
|
|
6060
|
+
inlineItems
|
|
6061
|
+
});
|
|
6062
|
+
if (timeoutBucket) {
|
|
6063
|
+
buckets.push(timeoutBucket);
|
|
6064
|
+
}
|
|
4684
6065
|
return {
|
|
6066
|
+
runner,
|
|
4685
6067
|
passed,
|
|
4686
6068
|
failed,
|
|
4687
6069
|
errors,
|
|
4688
6070
|
skipped,
|
|
6071
|
+
snapshotFailures: counts.snapshotFailures,
|
|
4689
6072
|
noTestsCollected,
|
|
4690
6073
|
interrupted,
|
|
4691
6074
|
collectionErrorCount: collectionErrors ? Number(collectionErrors[1]) : void 0,
|
|
@@ -4694,6 +6077,7 @@ function analyzeTestStatus(input) {
|
|
|
4694
6077
|
visibleErrorLabels,
|
|
4695
6078
|
visibleFailedLabels,
|
|
4696
6079
|
visibleErrorItems,
|
|
6080
|
+
visibleFailedItems,
|
|
4697
6081
|
buckets
|
|
4698
6082
|
};
|
|
4699
6083
|
}
|
|
@@ -4725,104 +6109,656 @@ function testStatusHeuristic(input, detail = "standard") {
|
|
|
4725
6109
|
})
|
|
4726
6110
|
].join("\n");
|
|
4727
6111
|
}
|
|
4728
|
-
if (analysis.noTestsCollected) {
|
|
4729
|
-
return ["- Tests did not run.", "- Collected 0 items."].join("\n");
|
|
6112
|
+
if (analysis.noTestsCollected) {
|
|
6113
|
+
return ["- Tests did not run.", "- Collected 0 items."].join("\n");
|
|
6114
|
+
}
|
|
6115
|
+
if (analysis.interrupted && analysis.failed === 0 && analysis.errors === 0) {
|
|
6116
|
+
return "- Test run was interrupted.";
|
|
6117
|
+
}
|
|
6118
|
+
if (analysis.failed === 0 && analysis.errors === 0 && analysis.passed > 0) {
|
|
6119
|
+
const details = [formatCount2(analysis.passed, "test")];
|
|
6120
|
+
if (analysis.skipped > 0) {
|
|
6121
|
+
details.push(formatCount2(analysis.skipped, "skip"));
|
|
6122
|
+
}
|
|
6123
|
+
return ["- Tests passed.", `- ${details.join(", ")}.`].join("\n");
|
|
6124
|
+
}
|
|
6125
|
+
if (analysis.failed > 0 || analysis.errors > 0 || analysis.inlineItems.length > 0 || analysis.buckets.length > 0) {
|
|
6126
|
+
const decision = buildTestStatusDiagnoseContract({
|
|
6127
|
+
input,
|
|
6128
|
+
analysis
|
|
6129
|
+
});
|
|
6130
|
+
if (detail === "verbose") {
|
|
6131
|
+
return decision.verboseText;
|
|
6132
|
+
}
|
|
6133
|
+
if (detail === "focused") {
|
|
6134
|
+
return decision.focusedText;
|
|
6135
|
+
}
|
|
6136
|
+
return decision.standardText;
|
|
6137
|
+
}
|
|
6138
|
+
return null;
|
|
6139
|
+
}
|
|
6140
|
+
function auditCriticalHeuristic(input) {
|
|
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);
|
|
6153
|
+
if (vulnerabilities.length === 0) {
|
|
6154
|
+
return null;
|
|
6155
|
+
}
|
|
6156
|
+
const firstVulnerability = vulnerabilities[0];
|
|
6157
|
+
return JSON.stringify(
|
|
6158
|
+
{
|
|
6159
|
+
status: "ok",
|
|
6160
|
+
vulnerabilities,
|
|
6161
|
+
summary: vulnerabilities.length === 1 ? `One ${firstVulnerability.severity} vulnerability found in ${firstVulnerability.package}.` : `${vulnerabilities.length} high or critical vulnerabilities found in the provided input.`
|
|
6162
|
+
},
|
|
6163
|
+
null,
|
|
6164
|
+
2
|
|
6165
|
+
);
|
|
6166
|
+
}
|
|
6167
|
+
function infraRiskHeuristic(input) {
|
|
6168
|
+
const destroyTargets = collectInfraDestroyTargets(input);
|
|
6169
|
+
const blockers = collectInfraBlockers(input);
|
|
6170
|
+
const zeroDestructiveEvidence = input.split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && ZERO_DESTRUCTIVE_SUMMARY_PATTERN.test(line)).slice(0, 3);
|
|
6171
|
+
const riskEvidence = collectInfraRiskEvidence(input);
|
|
6172
|
+
if (riskEvidence.length > 0) {
|
|
6173
|
+
return JSON.stringify(
|
|
6174
|
+
{
|
|
6175
|
+
verdict: "fail",
|
|
6176
|
+
reason: "Destructive or clearly risky infrastructure change signals are present.",
|
|
6177
|
+
evidence: riskEvidence,
|
|
6178
|
+
destroy_count: inferInfraDestroyCount(input, destroyTargets),
|
|
6179
|
+
destroy_targets: destroyTargets,
|
|
6180
|
+
blockers
|
|
6181
|
+
},
|
|
6182
|
+
null,
|
|
6183
|
+
2
|
|
6184
|
+
);
|
|
6185
|
+
}
|
|
6186
|
+
if (zeroDestructiveEvidence.length > 0) {
|
|
6187
|
+
return JSON.stringify(
|
|
6188
|
+
{
|
|
6189
|
+
verdict: "pass",
|
|
6190
|
+
reason: "The provided input explicitly indicates zero destructive changes.",
|
|
6191
|
+
evidence: zeroDestructiveEvidence,
|
|
6192
|
+
destroy_count: 0,
|
|
6193
|
+
destroy_targets: [],
|
|
6194
|
+
blockers: []
|
|
6195
|
+
},
|
|
6196
|
+
null,
|
|
6197
|
+
2
|
|
6198
|
+
);
|
|
6199
|
+
}
|
|
6200
|
+
const safeEvidence = collectEvidence(input, SAFE_LINE_PATTERN);
|
|
6201
|
+
if (safeEvidence.length > 0) {
|
|
6202
|
+
return JSON.stringify(
|
|
6203
|
+
{
|
|
6204
|
+
verdict: "pass",
|
|
6205
|
+
reason: "The provided input explicitly indicates no risky infrastructure changes.",
|
|
6206
|
+
evidence: safeEvidence,
|
|
6207
|
+
destroy_count: 0,
|
|
6208
|
+
destroy_targets: [],
|
|
6209
|
+
blockers: []
|
|
6210
|
+
},
|
|
6211
|
+
null,
|
|
6212
|
+
2
|
|
6213
|
+
);
|
|
6214
|
+
}
|
|
6215
|
+
return null;
|
|
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";
|
|
4730
6533
|
}
|
|
4731
|
-
if (
|
|
4732
|
-
return "-
|
|
6534
|
+
if (/no matching export|does not provide an export named|missing export/i.test(message)) {
|
|
6535
|
+
return "missing-export";
|
|
4733
6536
|
}
|
|
4734
|
-
if (
|
|
4735
|
-
|
|
4736
|
-
|
|
4737
|
-
|
|
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;
|
|
4738
6586
|
}
|
|
4739
|
-
|
|
4740
|
-
|
|
4741
|
-
|
|
4742
|
-
|
|
4743
|
-
|
|
4744
|
-
|
|
4745
|
-
|
|
4746
|
-
|
|
4747
|
-
|
|
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);
|
|
4748
6600
|
}
|
|
4749
|
-
|
|
4750
|
-
|
|
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;
|
|
4751
6609
|
}
|
|
4752
|
-
return
|
|
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
|
+
};
|
|
4753
6617
|
}
|
|
4754
6618
|
return null;
|
|
4755
6619
|
}
|
|
4756
|
-
function
|
|
4757
|
-
const
|
|
4758
|
-
|
|
4759
|
-
|
|
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;
|
|
4760
6628
|
}
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
|
|
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;
|
|
4764
6659
|
}
|
|
4765
6660
|
return {
|
|
4766
|
-
|
|
4767
|
-
|
|
4768
|
-
|
|
6661
|
+
message,
|
|
6662
|
+
file,
|
|
6663
|
+
line,
|
|
6664
|
+
column,
|
|
6665
|
+
category: inferBuildFailureCategory(message)
|
|
4769
6666
|
};
|
|
4770
|
-
}).filter((item) => item !== null);
|
|
4771
|
-
if (vulnerabilities.length === 0) {
|
|
4772
|
-
return null;
|
|
4773
6667
|
}
|
|
4774
|
-
|
|
4775
|
-
return JSON.stringify(
|
|
4776
|
-
{
|
|
4777
|
-
status: "ok",
|
|
4778
|
-
vulnerabilities,
|
|
4779
|
-
summary: vulnerabilities.length === 1 ? `One ${firstVulnerability.severity} vulnerability found in ${firstVulnerability.package}.` : `${vulnerabilities.length} high or critical vulnerabilities found in the provided input.`
|
|
4780
|
-
},
|
|
4781
|
-
null,
|
|
4782
|
-
2
|
|
4783
|
-
);
|
|
6668
|
+
return null;
|
|
4784
6669
|
}
|
|
4785
|
-
function
|
|
4786
|
-
|
|
4787
|
-
|
|
4788
|
-
(line) => line.length > 0 && RISK_LINE_PATTERN.test(line) && !ZERO_DESTRUCTIVE_SUMMARY_PATTERN.test(line)
|
|
4789
|
-
).slice(0, 3);
|
|
4790
|
-
if (riskEvidence.length > 0) {
|
|
4791
|
-
return JSON.stringify(
|
|
4792
|
-
{
|
|
4793
|
-
verdict: "fail",
|
|
4794
|
-
reason: "Destructive or clearly risky infrastructure change signals are present.",
|
|
4795
|
-
evidence: riskEvidence
|
|
4796
|
-
},
|
|
4797
|
-
null,
|
|
4798
|
-
2
|
|
4799
|
-
);
|
|
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;
|
|
4800
6673
|
}
|
|
4801
|
-
|
|
4802
|
-
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
|
|
4807
|
-
|
|
4808
|
-
|
|
4809
|
-
|
|
4810
|
-
|
|
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
|
+
};
|
|
4811
6689
|
}
|
|
4812
|
-
|
|
4813
|
-
|
|
4814
|
-
|
|
4815
|
-
|
|
4816
|
-
|
|
4817
|
-
|
|
4818
|
-
|
|
4819
|
-
},
|
|
4820
|
-
null,
|
|
4821
|
-
2
|
|
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+(.+)$/
|
|
4822
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
|
+
}
|
|
4823
6717
|
}
|
|
4824
6718
|
return null;
|
|
4825
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
|
+
}
|
|
4826
6762
|
function applyHeuristicPolicy(policyName, input, detail) {
|
|
4827
6763
|
if (!policyName) {
|
|
4828
6764
|
return null;
|
|
@@ -4836,6 +6772,15 @@ function applyHeuristicPolicy(policyName, input, detail) {
|
|
|
4836
6772
|
if (policyName === "test-status") {
|
|
4837
6773
|
return testStatusHeuristic(input, detail);
|
|
4838
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
|
+
}
|
|
4839
6784
|
return null;
|
|
4840
6785
|
}
|
|
4841
6786
|
|
|
@@ -4946,6 +6891,138 @@ function escapeRegExp2(value) {
|
|
|
4946
6891
|
function unique2(values) {
|
|
4947
6892
|
return [...new Set(values)];
|
|
4948
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
|
+
}
|
|
4949
7026
|
function buildLineWindows(args) {
|
|
4950
7027
|
const selected = /* @__PURE__ */ new Set();
|
|
4951
7028
|
for (const index of args.indexes) {
|
|
@@ -4961,6 +7038,12 @@ function buildLineWindows(args) {
|
|
|
4961
7038
|
}
|
|
4962
7039
|
return [...selected].sort((left, right) => left - right).map((index) => args.lines[index]);
|
|
4963
7040
|
}
|
|
7041
|
+
function buildPriorityLineGroup(args) {
|
|
7042
|
+
return unique2([
|
|
7043
|
+
...args.indexes.map((index) => args.lines[index]).filter(Boolean),
|
|
7044
|
+
...buildLineWindows(args)
|
|
7045
|
+
]);
|
|
7046
|
+
}
|
|
4964
7047
|
function collapseSelectedLines(args) {
|
|
4965
7048
|
if (args.lines.length === 0) {
|
|
4966
7049
|
return args.fallback();
|
|
@@ -5109,15 +7192,16 @@ function buildTestStatusRawSlice(args) {
|
|
|
5109
7192
|
) ? index : -1
|
|
5110
7193
|
).filter((index) => index >= 0);
|
|
5111
7194
|
const bucketGroups = args.contract.main_buckets.map((bucket) => {
|
|
5112
|
-
const bucketTerms =
|
|
5113
|
-
|
|
5114
|
-
|
|
7195
|
+
const bucketTerms = extractBucketSearchTerms({
|
|
7196
|
+
bucket,
|
|
7197
|
+
readTargets: args.contract.read_targets
|
|
7198
|
+
});
|
|
5115
7199
|
const indexes = lines.map(
|
|
5116
7200
|
(line, index) => bucketTerms.some((term) => new RegExp(escapeRegExp2(term), "i").test(line)) ? index : -1
|
|
5117
7201
|
).filter((index) => index >= 0);
|
|
5118
7202
|
return unique2([
|
|
5119
7203
|
...indexes.map((index) => lines[index]).filter(Boolean),
|
|
5120
|
-
...
|
|
7204
|
+
...buildPriorityLineGroup({
|
|
5121
7205
|
lines,
|
|
5122
7206
|
indexes,
|
|
5123
7207
|
radius: 2,
|
|
@@ -5125,26 +7209,55 @@ function buildTestStatusRawSlice(args) {
|
|
|
5125
7209
|
})
|
|
5126
7210
|
]);
|
|
5127
7211
|
});
|
|
5128
|
-
const targetGroups = args.contract.read_targets.
|
|
5129
|
-
|
|
7212
|
+
const targetGroups = args.contract.read_targets.flatMap((target) => {
|
|
7213
|
+
const searchHintIndexes = findSearchHintIndexes({
|
|
5130
7214
|
lines,
|
|
5131
|
-
|
|
5132
|
-
|
|
5133
|
-
|
|
5134
|
-
|
|
5135
|
-
|
|
5136
|
-
|
|
5137
|
-
|
|
5138
|
-
|
|
5139
|
-
|
|
5140
|
-
|
|
5141
|
-
|
|
5142
|
-
|
|
5143
|
-
|
|
5144
|
-
|
|
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
|
|
5145
7259
|
})
|
|
5146
|
-
);
|
|
5147
|
-
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);
|
|
5148
7261
|
const selected = collapseSelectedLineGroups({
|
|
5149
7262
|
groups: [
|
|
5150
7263
|
...targetGroups,
|
|
@@ -5158,12 +7271,14 @@ function buildTestStatusRawSlice(args) {
|
|
|
5158
7271
|
})
|
|
5159
7272
|
]),
|
|
5160
7273
|
...bucketGroups,
|
|
5161
|
-
|
|
5162
|
-
|
|
5163
|
-
|
|
5164
|
-
|
|
5165
|
-
|
|
5166
|
-
|
|
7274
|
+
...failureHeaderGroups.length > 0 ? failureHeaderGroups : [
|
|
7275
|
+
buildLineWindows({
|
|
7276
|
+
lines,
|
|
7277
|
+
indexes: failureIndexes,
|
|
7278
|
+
radius: 1,
|
|
7279
|
+
maxLines: 24
|
|
7280
|
+
})
|
|
7281
|
+
]
|
|
5167
7282
|
],
|
|
5168
7283
|
maxInputChars: args.config.maxInputChars,
|
|
5169
7284
|
fallback: () => truncateInput(args.input, {
|
|
@@ -5304,7 +7419,8 @@ function withInsufficientHint(args) {
|
|
|
5304
7419
|
return buildInsufficientSignalOutput({
|
|
5305
7420
|
presetName: args.request.presetName,
|
|
5306
7421
|
originalLength: args.prepared.meta.originalLength,
|
|
5307
|
-
truncatedApplied: args.prepared.meta.truncatedApplied
|
|
7422
|
+
truncatedApplied: args.prepared.meta.truncatedApplied,
|
|
7423
|
+
recognizedRunner: detectTestRunner(args.prepared.redacted)
|
|
5308
7424
|
});
|
|
5309
7425
|
}
|
|
5310
7426
|
async function generateWithRetry(args) {
|
|
@@ -5364,6 +7480,38 @@ function renderTestStatusDecisionOutput(args) {
|
|
|
5364
7480
|
return args.decision.standardText;
|
|
5365
7481
|
}
|
|
5366
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
|
+
}
|
|
5367
7515
|
const shouldZoomFirst = args.request.detail !== "verbose";
|
|
5368
7516
|
return buildTestStatusDiagnoseContract({
|
|
5369
7517
|
input: args.input,
|
|
@@ -5390,7 +7538,7 @@ function buildTestStatusProviderFailureDecision(args) {
|
|
|
5390
7538
|
}
|
|
5391
7539
|
});
|
|
5392
7540
|
}
|
|
5393
|
-
async function
|
|
7541
|
+
async function runSiftCore(request, recorder) {
|
|
5394
7542
|
const prepared = prepareInput(request.stdin, request.config.input);
|
|
5395
7543
|
const heuristicInput = prepared.redacted;
|
|
5396
7544
|
const heuristicInputTruncated = false;
|
|
@@ -5476,6 +7624,7 @@ async function runSift(request) {
|
|
|
5476
7624
|
finalOutput
|
|
5477
7625
|
});
|
|
5478
7626
|
}
|
|
7627
|
+
recorder?.heuristic();
|
|
5479
7628
|
return finalOutput;
|
|
5480
7629
|
}
|
|
5481
7630
|
if (testStatusDecision && testStatusAnalysis) {
|
|
@@ -5575,6 +7724,7 @@ async function runSift(request) {
|
|
|
5575
7724
|
providerInputChars: providerPrepared2.truncated.length,
|
|
5576
7725
|
providerOutputChars: result.text.length
|
|
5577
7726
|
});
|
|
7727
|
+
recorder?.provider(result.usage);
|
|
5578
7728
|
return finalOutput;
|
|
5579
7729
|
} catch (error) {
|
|
5580
7730
|
const reason = error instanceof Error ? error.message : "unknown_error";
|
|
@@ -5609,6 +7759,7 @@ async function runSift(request) {
|
|
|
5609
7759
|
rawSliceChars: rawSlice.text.length,
|
|
5610
7760
|
providerInputChars: providerPrepared2.truncated.length
|
|
5611
7761
|
});
|
|
7762
|
+
recorder?.fallback();
|
|
5612
7763
|
return finalOutput;
|
|
5613
7764
|
}
|
|
5614
7765
|
}
|
|
@@ -5665,6 +7816,7 @@ async function runSift(request) {
|
|
|
5665
7816
|
})) {
|
|
5666
7817
|
throw new Error("Model output rejected by quality gate");
|
|
5667
7818
|
}
|
|
7819
|
+
recorder?.provider(result.usage);
|
|
5668
7820
|
return withInsufficientHint({
|
|
5669
7821
|
output: normalizeOutput(result.text, providerPrompt.responseMode),
|
|
5670
7822
|
request,
|
|
@@ -5672,6 +7824,7 @@ async function runSift(request) {
|
|
|
5672
7824
|
});
|
|
5673
7825
|
} catch (error) {
|
|
5674
7826
|
const reason = error instanceof Error ? error.message : "unknown_error";
|
|
7827
|
+
recorder?.fallback();
|
|
5675
7828
|
return withInsufficientHint({
|
|
5676
7829
|
output: buildFallbackOutput({
|
|
5677
7830
|
format: request.format,
|
|
@@ -5685,6 +7838,72 @@ async function runSift(request) {
|
|
|
5685
7838
|
});
|
|
5686
7839
|
}
|
|
5687
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
|
+
}
|
|
5688
7907
|
|
|
5689
7908
|
// src/core/testStatusState.ts
|
|
5690
7909
|
import fs5 from "fs";
|
|
@@ -5694,10 +7913,29 @@ var detailSchema = z3.enum(["standard", "focused", "verbose"]);
|
|
|
5694
7913
|
var failureBucketTypeSchema = z3.enum([
|
|
5695
7914
|
"shared_environment_blocker",
|
|
5696
7915
|
"fixture_guard_failure",
|
|
7916
|
+
"timeout_failure",
|
|
7917
|
+
"permission_denied_failure",
|
|
7918
|
+
"async_event_loop_failure",
|
|
7919
|
+
"fixture_teardown_failure",
|
|
7920
|
+
"db_migration_failure",
|
|
7921
|
+
"configuration_error",
|
|
7922
|
+
"xdist_worker_crash",
|
|
7923
|
+
"type_error_failure",
|
|
7924
|
+
"resource_leak_warning",
|
|
7925
|
+
"django_db_access_denied",
|
|
7926
|
+
"network_failure",
|
|
7927
|
+
"subprocess_crash_segfault",
|
|
7928
|
+
"flaky_test_detected",
|
|
7929
|
+
"serialization_encoding_failure",
|
|
7930
|
+
"file_not_found_failure",
|
|
7931
|
+
"memory_error",
|
|
7932
|
+
"deprecation_warning_as_error",
|
|
7933
|
+
"xfail_strict_unexpected_pass",
|
|
5697
7934
|
"service_unavailable",
|
|
5698
7935
|
"db_connection_failure",
|
|
5699
7936
|
"auth_bypass_absent",
|
|
5700
7937
|
"contract_snapshot_drift",
|
|
7938
|
+
"snapshot_mismatch",
|
|
5701
7939
|
"import_dependency_failure",
|
|
5702
7940
|
"collection_failure",
|
|
5703
7941
|
"assertion_failure",
|
|
@@ -6143,7 +8381,7 @@ async function runEscalate(request) {
|
|
|
6143
8381
|
const detail = resolveEscalationDetail(state, request.detail, request.showRaw);
|
|
6144
8382
|
if (request.verbose) {
|
|
6145
8383
|
process.stderr.write(
|
|
6146
|
-
`${
|
|
8384
|
+
`${pc4.dim("sift")} escalate detail=${detail} cached_detail=${state.detail} command=${state.commandPreview}
|
|
6147
8385
|
`
|
|
6148
8386
|
);
|
|
6149
8387
|
}
|
|
@@ -6153,7 +8391,7 @@ async function runEscalate(request) {
|
|
|
6153
8391
|
process.stderr.write("\n");
|
|
6154
8392
|
}
|
|
6155
8393
|
}
|
|
6156
|
-
|
|
8394
|
+
const result = await runSiftWithStats({
|
|
6157
8395
|
question: request.question,
|
|
6158
8396
|
format: request.format,
|
|
6159
8397
|
goal: request.goal,
|
|
@@ -6170,6 +8408,7 @@ async function runEscalate(request) {
|
|
|
6170
8408
|
remainingSubsetAvailable: Boolean(state.pytest?.subsetCapable) && (state.pytest?.failingNodeIds.length ?? 0) > 0
|
|
6171
8409
|
}
|
|
6172
8410
|
});
|
|
8411
|
+
let output = result.output;
|
|
6173
8412
|
if (isInsufficientSignalOutput(output)) {
|
|
6174
8413
|
output = buildInsufficientSignalOutput({
|
|
6175
8414
|
presetName: "test-status",
|
|
@@ -6180,6 +8419,10 @@ async function runEscalate(request) {
|
|
|
6180
8419
|
}
|
|
6181
8420
|
process.stdout.write(`${output}
|
|
6182
8421
|
`);
|
|
8422
|
+
emitStatsFooter({
|
|
8423
|
+
stats: result.stats,
|
|
8424
|
+
quiet: Boolean(request.quiet)
|
|
8425
|
+
});
|
|
6183
8426
|
try {
|
|
6184
8427
|
writeCachedTestStatusRun({
|
|
6185
8428
|
...state,
|
|
@@ -6188,7 +8431,7 @@ async function runEscalate(request) {
|
|
|
6188
8431
|
} catch (error) {
|
|
6189
8432
|
if (request.verbose) {
|
|
6190
8433
|
const reason = error instanceof Error ? error.message : "unknown_error";
|
|
6191
|
-
process.stderr.write(`${
|
|
8434
|
+
process.stderr.write(`${pc4.dim("sift")} cache_write=failed reason=${reason}
|
|
6192
8435
|
`);
|
|
6193
8436
|
}
|
|
6194
8437
|
}
|
|
@@ -6198,7 +8441,7 @@ async function runEscalate(request) {
|
|
|
6198
8441
|
// src/core/exec.ts
|
|
6199
8442
|
import { spawn } from "child_process";
|
|
6200
8443
|
import { constants as osConstants } from "os";
|
|
6201
|
-
import
|
|
8444
|
+
import pc5 from "picocolors";
|
|
6202
8445
|
|
|
6203
8446
|
// src/core/gate.ts
|
|
6204
8447
|
var FAIL_ON_SUPPORTED_PRESETS = /* @__PURE__ */ new Set(["infra-risk", "audit-critical"]);
|
|
@@ -6528,7 +8771,7 @@ async function runExec(request) {
|
|
|
6528
8771
|
const previousCachedRun = shouldCacheTestStatusBase ? tryReadCachedTestStatusRun() : null;
|
|
6529
8772
|
if (request.config.runtime.verbose) {
|
|
6530
8773
|
process.stderr.write(
|
|
6531
|
-
`${
|
|
8774
|
+
`${pc5.dim("sift")} exec mode=${hasShellCommand ? "shell" : "argv"} command=${commandPreview}
|
|
6532
8775
|
`
|
|
6533
8776
|
);
|
|
6534
8777
|
}
|
|
@@ -6557,7 +8800,7 @@ async function runExec(request) {
|
|
|
6557
8800
|
}
|
|
6558
8801
|
bypassed = true;
|
|
6559
8802
|
if (request.config.runtime.verbose) {
|
|
6560
|
-
process.stderr.write(`${
|
|
8803
|
+
process.stderr.write(`${pc5.dim("sift")} bypass=interactive-prompt
|
|
6561
8804
|
`);
|
|
6562
8805
|
}
|
|
6563
8806
|
process.stderr.write(capture.render());
|
|
@@ -6586,15 +8829,16 @@ async function runExec(request) {
|
|
|
6586
8829
|
const shouldCacheTestStatus = shouldCacheTestStatusBase && !useWatchFlow;
|
|
6587
8830
|
if (request.config.runtime.verbose) {
|
|
6588
8831
|
process.stderr.write(
|
|
6589
|
-
`${
|
|
8832
|
+
`${pc5.dim("sift")} child_exit=${exitCode} captured_chars=${capture.getTotalChars()} capture_truncated=${capture.wasTruncated()}
|
|
6590
8833
|
`
|
|
6591
8834
|
);
|
|
6592
8835
|
}
|
|
6593
8836
|
if (autoWatchDetected) {
|
|
6594
|
-
process.stderr.write(`${
|
|
8837
|
+
process.stderr.write(`${pc5.dim("sift")} auto-watch=detected
|
|
6595
8838
|
`);
|
|
6596
8839
|
}
|
|
6597
8840
|
if (!bypassed) {
|
|
8841
|
+
const reductionStartedAt = Date.now();
|
|
6598
8842
|
if (request.showRaw && capturedOutput.length > 0) {
|
|
6599
8843
|
process.stderr.write(capturedOutput);
|
|
6600
8844
|
if (!capturedOutput.endsWith("\n")) {
|
|
@@ -6609,12 +8853,22 @@ async function runExec(request) {
|
|
|
6609
8853
|
if (execSuccessShortcut && !request.dryRun) {
|
|
6610
8854
|
if (request.config.runtime.verbose) {
|
|
6611
8855
|
process.stderr.write(
|
|
6612
|
-
`${
|
|
8856
|
+
`${pc5.dim("sift")} exec_shortcut=${request.presetName}
|
|
6613
8857
|
`
|
|
6614
8858
|
);
|
|
6615
8859
|
}
|
|
6616
8860
|
process.stdout.write(`${execSuccessShortcut}
|
|
6617
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
|
+
});
|
|
6618
8872
|
return exitCode;
|
|
6619
8873
|
}
|
|
6620
8874
|
if (useWatchFlow) {
|
|
@@ -6627,7 +8881,8 @@ async function runExec(request) {
|
|
|
6627
8881
|
presetName: request.presetName,
|
|
6628
8882
|
originalLength: capture.getTotalChars(),
|
|
6629
8883
|
truncatedApplied: capture.wasTruncated(),
|
|
6630
|
-
exitCode
|
|
8884
|
+
exitCode,
|
|
8885
|
+
recognizedRunner: detectTestRunner(capturedOutput)
|
|
6631
8886
|
});
|
|
6632
8887
|
}
|
|
6633
8888
|
process.stdout.write(`${output2}
|
|
@@ -6655,7 +8910,7 @@ async function runExec(request) {
|
|
|
6655
8910
|
previous: previousCachedRun,
|
|
6656
8911
|
current: currentCachedRun
|
|
6657
8912
|
}) : null;
|
|
6658
|
-
|
|
8913
|
+
const result = await runSiftWithStats({
|
|
6659
8914
|
...request,
|
|
6660
8915
|
stdin: capturedOutput,
|
|
6661
8916
|
analysisContext: request.skipCacheWrite && request.presetName === "test-status" ? [
|
|
@@ -6674,13 +8929,15 @@ async function runExec(request) {
|
|
|
6674
8929
|
)
|
|
6675
8930
|
} : request.testStatusContext
|
|
6676
8931
|
});
|
|
8932
|
+
let output = result.output;
|
|
6677
8933
|
if (shouldCacheTestStatus) {
|
|
6678
8934
|
if (isInsufficientSignalOutput(output)) {
|
|
6679
8935
|
output = buildInsufficientSignalOutput({
|
|
6680
8936
|
presetName: request.presetName,
|
|
6681
8937
|
originalLength: capture.getTotalChars(),
|
|
6682
8938
|
truncatedApplied: capture.wasTruncated(),
|
|
6683
|
-
exitCode
|
|
8939
|
+
exitCode,
|
|
8940
|
+
recognizedRunner: detectTestRunner(capturedOutput)
|
|
6684
8941
|
});
|
|
6685
8942
|
}
|
|
6686
8943
|
if (request.diff && !request.dryRun && previousCachedRun && currentCachedRun) {
|
|
@@ -6713,7 +8970,7 @@ ${output}`;
|
|
|
6713
8970
|
} catch (error) {
|
|
6714
8971
|
if (request.config.runtime.verbose) {
|
|
6715
8972
|
const reason = error instanceof Error ? error.message : "unknown_error";
|
|
6716
|
-
process.stderr.write(`${
|
|
8973
|
+
process.stderr.write(`${pc5.dim("sift")} cache_write=failed reason=${reason}
|
|
6717
8974
|
`);
|
|
6718
8975
|
}
|
|
6719
8976
|
}
|
|
@@ -6723,11 +8980,16 @@ ${output}`;
|
|
|
6723
8980
|
presetName: request.presetName,
|
|
6724
8981
|
originalLength: capture.getTotalChars(),
|
|
6725
8982
|
truncatedApplied: capture.wasTruncated(),
|
|
6726
|
-
exitCode
|
|
8983
|
+
exitCode,
|
|
8984
|
+
recognizedRunner: detectTestRunner(capturedOutput)
|
|
6727
8985
|
});
|
|
6728
8986
|
}
|
|
6729
8987
|
process.stdout.write(`${output}
|
|
6730
8988
|
`);
|
|
8989
|
+
emitStatsFooter({
|
|
8990
|
+
stats: result.stats,
|
|
8991
|
+
quiet: Boolean(request.quiet)
|
|
8992
|
+
});
|
|
6731
8993
|
if (request.failOn && !request.dryRun && exitCode === 0 && supportsFailOnPreset(request.presetName) && evaluateGate({
|
|
6732
8994
|
presetName: request.presetName,
|
|
6733
8995
|
output
|
|
@@ -6817,6 +9079,7 @@ var defaultCliDeps = {
|
|
|
6817
9079
|
evaluateGate,
|
|
6818
9080
|
readStdin,
|
|
6819
9081
|
runSift,
|
|
9082
|
+
runSiftWithStats,
|
|
6820
9083
|
runWatch,
|
|
6821
9084
|
looksLikeWatchStream,
|
|
6822
9085
|
getPreset
|
|
@@ -6925,7 +9188,7 @@ function applySharedOptions(command) {
|
|
|
6925
9188
|
).option(
|
|
6926
9189
|
"--fail-on",
|
|
6927
9190
|
"Fail with exit code 1 when a supported built-in preset produces a blocking result"
|
|
6928
|
-
).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");
|
|
6929
9192
|
}
|
|
6930
9193
|
function normalizeDetail(value) {
|
|
6931
9194
|
if (value === void 0 || value === null || value === "") {
|
|
@@ -7024,21 +9287,25 @@ function createCliApp(args = {}) {
|
|
|
7024
9287
|
stderr.write("\n");
|
|
7025
9288
|
}
|
|
7026
9289
|
}
|
|
7027
|
-
const
|
|
7028
|
-
|
|
7029
|
-
|
|
7030
|
-
|
|
7031
|
-
|
|
7032
|
-
|
|
7033
|
-
|
|
7034
|
-
|
|
7035
|
-
|
|
7036
|
-
|
|
7037
|
-
|
|
7038
|
-
|
|
7039
|
-
|
|
7040
|
-
|
|
7041
|
-
|
|
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({
|
|
7042
9309
|
question: input.question,
|
|
7043
9310
|
format: input.format,
|
|
7044
9311
|
goal: input.goal,
|
|
@@ -7053,8 +9320,13 @@ function createCliApp(args = {}) {
|
|
|
7053
9320
|
outputContract: input.outputContract,
|
|
7054
9321
|
fallbackJson: input.fallbackJson
|
|
7055
9322
|
});
|
|
9323
|
+
const output = result.output;
|
|
7056
9324
|
stdout.write(`${output}
|
|
7057
9325
|
`);
|
|
9326
|
+
emitStatsFooter({
|
|
9327
|
+
stats: result.stats,
|
|
9328
|
+
quiet: Boolean(input.options.quiet)
|
|
9329
|
+
});
|
|
7058
9330
|
if (Boolean(input.options.failOn) && !Boolean(input.options.dryRun) && input.presetName && deps.evaluateGate({
|
|
7059
9331
|
presetName: input.presetName,
|
|
7060
9332
|
output
|
|
@@ -7084,6 +9356,7 @@ function createCliApp(args = {}) {
|
|
|
7084
9356
|
dryRun: Boolean(input.options.dryRun),
|
|
7085
9357
|
diff: input.diff,
|
|
7086
9358
|
failOn: Boolean(input.options.failOn),
|
|
9359
|
+
quiet: Boolean(input.options.quiet),
|
|
7087
9360
|
showRaw: Boolean(input.options.showRaw),
|
|
7088
9361
|
includeTestIds: Boolean(input.options.includeTestIds),
|
|
7089
9362
|
watch: Boolean(input.options.watch),
|
|
@@ -7263,6 +9536,7 @@ function createCliApp(args = {}) {
|
|
|
7263
9536
|
outputContract: preset.outputContract,
|
|
7264
9537
|
fallbackJson: preset.fallbackJson,
|
|
7265
9538
|
detail: normalizeEscalateDetail(options.detail),
|
|
9539
|
+
quiet: Boolean(options.quiet),
|
|
7266
9540
|
showRaw: Boolean(options.showRaw),
|
|
7267
9541
|
verbose: Boolean(options.verbose)
|
|
7268
9542
|
});
|