@bilalimamoglu/sift 0.3.2 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +154 -185
- package/dist/cli.js +1410 -107
- package/dist/index.d.ts +15 -1
- package/dist/index.js +1375 -88
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/core/exec.ts
|
|
2
2
|
import { spawn } from "child_process";
|
|
3
3
|
import { constants as osConstants } from "os";
|
|
4
|
-
import
|
|
4
|
+
import pc3 from "picocolors";
|
|
5
5
|
|
|
6
6
|
// src/constants.ts
|
|
7
7
|
import os from "os";
|
|
@@ -61,7 +61,7 @@ function evaluateGate(args) {
|
|
|
61
61
|
|
|
62
62
|
// src/core/testStatusDecision.ts
|
|
63
63
|
import { z } from "zod";
|
|
64
|
-
var TEST_STATUS_DIAGNOSE_JSON_CONTRACT = '{"status":"ok|insufficient","diagnosis_complete":boolean,"raw_needed":boolean,"additional_source_read_likely_low_value":boolean,"read_raw_only_if":string|null,"decision":"stop|zoom|read_source|read_raw","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[]}';
|
|
64
|
+
var TEST_STATUS_DIAGNOSE_JSON_CONTRACT = '{"status":"ok|insufficient","diagnosis_complete":boolean,"raw_needed":boolean,"additional_source_read_likely_low_value":boolean,"read_raw_only_if":string|null,"decision":"stop|zoom|read_source|read_raw","primary_suspect_kind":"test|app_code|config|environment|tooling|unknown","confidence_reason":string,"dominant_blocker_bucket_index":number|null,"provider_used":boolean,"provider_confidence":number|null,"provider_failed":boolean,"raw_slice_used":boolean,"raw_slice_strategy":"none|bucket_evidence|traceback_window|head_tail","resolved_summary":{"count":number,"families":[{"prefix":string,"count":number}]},"remaining_summary":{"count":number,"families":[{"prefix":string,"count":number}]},"remaining_subset_available":boolean,"main_buckets":[{"bucket_index":number,"label":string,"count":number,"root_cause":string,"suspect_kind":"test|app_code|config|environment|tooling|unknown","fix_hint":string,"evidence":string[],"bucket_confidence":number,"root_cause_confidence":number,"dominant":boolean,"secondary_visible_despite_blocker":boolean,"mini_diff":{"added_paths"?:number,"removed_models"?:number,"changed_task_mappings"?:number}|null}],"read_targets":[{"file":string,"line":number|null,"why":string,"bucket_index":number,"context_hint":{"start_line":number|null,"end_line":number|null,"search_hint":string|null}}],"next_best_action":{"code":"fix_dominant_blocker|read_source_for_bucket|read_raw_for_exact_traceback|insufficient_signal","bucket_index":number|null,"note":string},"resolved_tests"?:string[],"remaining_tests"?:string[]}';
|
|
65
65
|
var TEST_STATUS_PROVIDER_SUPPLEMENT_JSON_CONTRACT = '{"diagnosis_complete":boolean,"raw_needed":boolean,"additional_source_read_likely_low_value":boolean,"read_raw_only_if":string|null,"decision":"stop|zoom|read_source|read_raw","provider_confidence":number|null,"bucket_supplements":[{"label":string,"count":number,"root_cause":string,"anchor":{"file":string|null,"line":number|null,"search_hint":string|null},"fix_hint":string|null,"confidence":number}],"next_best_action":{"code":"fix_dominant_blocker|read_source_for_bucket|read_raw_for_exact_traceback|insufficient_signal","bucket_index":number|null,"note":string}}';
|
|
66
66
|
var nextBestActionSchema = z.object({
|
|
67
67
|
code: z.enum([
|
|
@@ -103,6 +103,15 @@ var testStatusDiagnoseContractSchema = z.object({
|
|
|
103
103
|
additional_source_read_likely_low_value: z.boolean(),
|
|
104
104
|
read_raw_only_if: z.string().nullable(),
|
|
105
105
|
decision: z.enum(["stop", "zoom", "read_source", "read_raw"]),
|
|
106
|
+
primary_suspect_kind: z.enum([
|
|
107
|
+
"test",
|
|
108
|
+
"app_code",
|
|
109
|
+
"config",
|
|
110
|
+
"environment",
|
|
111
|
+
"tooling",
|
|
112
|
+
"unknown"
|
|
113
|
+
]),
|
|
114
|
+
confidence_reason: z.string().min(1),
|
|
106
115
|
dominant_blocker_bucket_index: z.number().int().nullable(),
|
|
107
116
|
provider_used: z.boolean(),
|
|
108
117
|
provider_confidence: z.number().min(0).max(1).nullable(),
|
|
@@ -117,6 +126,15 @@ var testStatusDiagnoseContractSchema = z.object({
|
|
|
117
126
|
label: z.string(),
|
|
118
127
|
count: z.number().int(),
|
|
119
128
|
root_cause: z.string(),
|
|
129
|
+
suspect_kind: z.enum([
|
|
130
|
+
"test",
|
|
131
|
+
"app_code",
|
|
132
|
+
"config",
|
|
133
|
+
"environment",
|
|
134
|
+
"tooling",
|
|
135
|
+
"unknown"
|
|
136
|
+
]),
|
|
137
|
+
fix_hint: z.string().min(1),
|
|
120
138
|
evidence: z.array(z.string()).max(2),
|
|
121
139
|
bucket_confidence: z.number(),
|
|
122
140
|
root_cause_confidence: z.number(),
|
|
@@ -167,6 +185,42 @@ function parseTestStatusProviderSupplement(input) {
|
|
|
167
185
|
return testStatusProviderSupplementSchema.parse(JSON.parse(input));
|
|
168
186
|
}
|
|
169
187
|
var extendedBucketSpecs = [
|
|
188
|
+
{
|
|
189
|
+
prefix: "service unavailable:",
|
|
190
|
+
type: "service_unavailable",
|
|
191
|
+
label: "service unavailable",
|
|
192
|
+
genericTitle: "Service unavailable failures",
|
|
193
|
+
defaultCoverage: "error",
|
|
194
|
+
rootCauseConfidence: 0.9,
|
|
195
|
+
dominantPriority: 2,
|
|
196
|
+
dominantBlocker: true,
|
|
197
|
+
why: "it contains the dependency service or API path that is unavailable in the test environment",
|
|
198
|
+
fix: "Restore the dependency service or test double before rerunning the full suite."
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
prefix: "db refused:",
|
|
202
|
+
type: "db_connection_failure",
|
|
203
|
+
label: "database connection",
|
|
204
|
+
genericTitle: "Database connection failures",
|
|
205
|
+
defaultCoverage: "error",
|
|
206
|
+
rootCauseConfidence: 0.9,
|
|
207
|
+
dominantPriority: 2,
|
|
208
|
+
dominantBlocker: true,
|
|
209
|
+
why: "it contains the database host, DSN, or startup path that is refusing connections",
|
|
210
|
+
fix: "Restore the test database connectivity before rerunning the full suite."
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
prefix: "auth bypass absent:",
|
|
214
|
+
type: "auth_bypass_absent",
|
|
215
|
+
label: "auth bypass missing",
|
|
216
|
+
genericTitle: "Auth bypass setup failures",
|
|
217
|
+
defaultCoverage: "error",
|
|
218
|
+
rootCauseConfidence: 0.86,
|
|
219
|
+
dominantPriority: 2,
|
|
220
|
+
dominantBlocker: true,
|
|
221
|
+
why: "it contains the auth bypass fixture or setup path that tests expected to be active",
|
|
222
|
+
fix: "Restore the test auth bypass fixture or mock before rerunning the full suite."
|
|
223
|
+
},
|
|
170
224
|
{
|
|
171
225
|
prefix: "snapshot mismatch:",
|
|
172
226
|
type: "snapshot_mismatch",
|
|
@@ -351,6 +405,16 @@ var extendedBucketSpecs = [
|
|
|
351
405
|
why: "it contains the deprecated API or warning filter that is failing the test run",
|
|
352
406
|
fix: "Update the deprecated call site or relax the warning policy only if that is intentional."
|
|
353
407
|
},
|
|
408
|
+
{
|
|
409
|
+
prefix: "assertion failed:",
|
|
410
|
+
type: "assertion_failure",
|
|
411
|
+
label: "assertion failure",
|
|
412
|
+
genericTitle: "Assertion failures",
|
|
413
|
+
defaultCoverage: "failed",
|
|
414
|
+
rootCauseConfidence: 0.76,
|
|
415
|
+
why: "it contains the expected-versus-actual assertion that failed inside the visible test",
|
|
416
|
+
fix: "Read the assertion diff or expectation and fix the code or expected value before rerunning."
|
|
417
|
+
},
|
|
354
418
|
{
|
|
355
419
|
prefix: "xfail strict:",
|
|
356
420
|
type: "xfail_strict_unexpected_pass",
|
|
@@ -1140,7 +1204,7 @@ function buildProviderSupplementBuckets(args) {
|
|
|
1140
1204
|
});
|
|
1141
1205
|
}
|
|
1142
1206
|
function pickUnknownAnchor(args) {
|
|
1143
|
-
const fromStatusItems = args.kind === "error" ? args.analysis.visibleErrorItems[0] :
|
|
1207
|
+
const fromStatusItems = args.kind === "error" ? args.analysis.visibleErrorItems[0] : args.analysis.visibleFailedItems[0];
|
|
1144
1208
|
if (fromStatusItems) {
|
|
1145
1209
|
return {
|
|
1146
1210
|
label: fromStatusItems.label,
|
|
@@ -1177,12 +1241,14 @@ function buildUnknownBucket(args) {
|
|
|
1177
1241
|
const isError = args.kind === "error";
|
|
1178
1242
|
const label = isError ? "unknown setup blocker" : "unknown failure family";
|
|
1179
1243
|
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";
|
|
1244
|
+
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;
|
|
1180
1245
|
return {
|
|
1181
1246
|
type: "unknown_failure",
|
|
1182
1247
|
headline: `${label}: ${formatCount(args.count, "visible failure")} share a repeated but unclassified pattern.`,
|
|
1183
1248
|
summaryLines: [
|
|
1184
|
-
`${label}: ${formatCount(args.count, "visible failure")} share a repeated but unclassified pattern
|
|
1185
|
-
|
|
1249
|
+
`${label}: ${formatCount(args.count, "visible failure")} share a repeated but unclassified pattern.`,
|
|
1250
|
+
firstConcreteSignal
|
|
1251
|
+
].filter((value) => Boolean(value)),
|
|
1186
1252
|
reason,
|
|
1187
1253
|
count: args.count,
|
|
1188
1254
|
confidence: 0.45,
|
|
@@ -1309,7 +1375,7 @@ function buildStandardAnchorText(target) {
|
|
|
1309
1375
|
}
|
|
1310
1376
|
return formatReadTargetLocation(target);
|
|
1311
1377
|
}
|
|
1312
|
-
function
|
|
1378
|
+
function resolveBucketFixHint(args) {
|
|
1313
1379
|
if (args.bucket.hint) {
|
|
1314
1380
|
return args.bucket.hint;
|
|
1315
1381
|
}
|
|
@@ -1358,13 +1424,75 @@ function buildStandardFixText(args) {
|
|
|
1358
1424
|
if (args.bucket.type === "runtime_failure") {
|
|
1359
1425
|
return `Fix the visible ${args.bucketLabel} and rerun the full suite at standard.`;
|
|
1360
1426
|
}
|
|
1361
|
-
return
|
|
1427
|
+
return "Inspect the first visible anchor for this bucket, apply the smallest fix that explains it, then rerun the full suite at standard.";
|
|
1428
|
+
}
|
|
1429
|
+
function deriveBucketSuspectKind(args) {
|
|
1430
|
+
if (args.bucket.type === "shared_environment_blocker" || args.bucket.type === "fixture_guard_failure" || args.bucket.type === "permission_denied_failure" || args.bucket.type === "django_db_access_denied" || args.bucket.type === "network_failure" || args.bucket.type === "service_unavailable" || args.bucket.type === "db_connection_failure" || args.bucket.type === "auth_bypass_absent" || args.bucket.type === "fixture_teardown_failure") {
|
|
1431
|
+
return "environment";
|
|
1432
|
+
}
|
|
1433
|
+
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") {
|
|
1434
|
+
return "config";
|
|
1435
|
+
}
|
|
1436
|
+
if (args.bucket.type === "contract_snapshot_drift" || args.bucket.type === "snapshot_mismatch" || args.bucket.type === "flaky_test_detected" || args.bucket.type === "xfail_strict_unexpected_pass") {
|
|
1437
|
+
return "test";
|
|
1438
|
+
}
|
|
1439
|
+
if (args.bucket.type === "xdist_worker_crash" || args.bucket.type === "timeout_failure" || args.bucket.type === "async_event_loop_failure" || args.bucket.type === "subprocess_crash_segfault" || args.bucket.type === "memory_error" || args.bucket.type === "resource_leak_warning" || args.bucket.type === "interrupted_run") {
|
|
1440
|
+
return "tooling";
|
|
1441
|
+
}
|
|
1442
|
+
if (args.bucket.type === "unknown_failure") {
|
|
1443
|
+
return "unknown";
|
|
1444
|
+
}
|
|
1445
|
+
if (args.bucket.type === "assertion_failure" || args.bucket.type === "runtime_failure" || args.bucket.type === "type_error_failure" || args.bucket.type === "serialization_encoding_failure") {
|
|
1446
|
+
const file = args.readTarget?.file ?? "";
|
|
1447
|
+
if (file.startsWith("src/")) {
|
|
1448
|
+
return "app_code";
|
|
1449
|
+
}
|
|
1450
|
+
if (file.startsWith("test/") || file.startsWith("tests/")) {
|
|
1451
|
+
return "test";
|
|
1452
|
+
}
|
|
1453
|
+
return "unknown";
|
|
1454
|
+
}
|
|
1455
|
+
return "unknown";
|
|
1456
|
+
}
|
|
1457
|
+
function derivePrimarySuspectKind(args) {
|
|
1458
|
+
const primaryBucket = (args.dominantBlockerBucketIndex !== null ? args.mainBuckets.find((bucket) => bucket.bucket_index === args.dominantBlockerBucketIndex) : null) ?? args.mainBuckets[0];
|
|
1459
|
+
return primaryBucket?.suspect_kind ?? "unknown";
|
|
1460
|
+
}
|
|
1461
|
+
function buildConfidenceReason(args) {
|
|
1462
|
+
const primaryBucket = args.mainBuckets.find((bucket) => bucket.dominant) ?? args.mainBuckets[0];
|
|
1463
|
+
if (args.decision === "stop" && primaryBucket && args.primarySuspectKind !== "unknown") {
|
|
1464
|
+
return `Dominant blocker (${primaryBucket.label}) is anchored and actionable.`;
|
|
1465
|
+
}
|
|
1466
|
+
if (args.decision === "zoom") {
|
|
1467
|
+
return "Unknown or low-confidence buckets remain; one deeper sift pass is justified.";
|
|
1468
|
+
}
|
|
1469
|
+
if (args.decision === "read_source") {
|
|
1470
|
+
return "The bucket is identified, but source context is still needed to make the next fix clear.";
|
|
1471
|
+
}
|
|
1472
|
+
return "Heuristic signal is still insufficient; exact traceback lines are needed.";
|
|
1473
|
+
}
|
|
1474
|
+
function formatSuspectKindLabel(kind) {
|
|
1475
|
+
switch (kind) {
|
|
1476
|
+
case "test":
|
|
1477
|
+
return "test code";
|
|
1478
|
+
case "app_code":
|
|
1479
|
+
return "application code";
|
|
1480
|
+
case "config":
|
|
1481
|
+
return "test or project configuration";
|
|
1482
|
+
case "environment":
|
|
1483
|
+
return "environment setup";
|
|
1484
|
+
case "tooling":
|
|
1485
|
+
return "test runner or tooling";
|
|
1486
|
+
default:
|
|
1487
|
+
return "unknown";
|
|
1488
|
+
}
|
|
1362
1489
|
}
|
|
1363
1490
|
function buildStandardBucketSupport(args) {
|
|
1364
1491
|
return {
|
|
1365
1492
|
headline: args.bucket.summaryLines[0] ? `- ${args.bucket.summaryLines[0]}` : renderBucketHeadline(args.contractBucket),
|
|
1493
|
+
firstConcreteSignalText: args.bucket.source === "unknown" ? args.bucket.summaryLines[1] ?? null : null,
|
|
1366
1494
|
anchorText: buildStandardAnchorText(args.readTarget),
|
|
1367
|
-
fixText:
|
|
1495
|
+
fixText: resolveBucketFixHint({
|
|
1368
1496
|
bucket: args.bucket,
|
|
1369
1497
|
bucketLabel: args.contractBucket.label
|
|
1370
1498
|
})
|
|
@@ -1387,6 +1515,9 @@ function renderStandard(args) {
|
|
|
1387
1515
|
)
|
|
1388
1516
|
});
|
|
1389
1517
|
lines.push(support.headline);
|
|
1518
|
+
if (support.firstConcreteSignalText) {
|
|
1519
|
+
lines.push(`- ${support.firstConcreteSignalText}`);
|
|
1520
|
+
}
|
|
1390
1521
|
if (support.anchorText) {
|
|
1391
1522
|
lines.push(`- Anchor: ${support.anchorText}`);
|
|
1392
1523
|
}
|
|
@@ -1396,6 +1527,7 @@ function renderStandard(args) {
|
|
|
1396
1527
|
}
|
|
1397
1528
|
}
|
|
1398
1529
|
lines.push(buildDecisionLine(args.contract));
|
|
1530
|
+
lines.push(`- Likely owner: ${formatSuspectKindLabel(args.contract.primary_suspect_kind)}`);
|
|
1399
1531
|
lines.push(`- Next: ${args.contract.next_best_action.note}`);
|
|
1400
1532
|
lines.push(buildStopSignal(args.contract));
|
|
1401
1533
|
return lines.join("\n");
|
|
@@ -1483,29 +1615,49 @@ function buildTestStatusDiagnoseContract(args) {
|
|
|
1483
1615
|
})[0] ?? null;
|
|
1484
1616
|
const hasUnknownBucket = buckets.some((bucket) => isUnknownBucket(bucket));
|
|
1485
1617
|
const hasConcreteCoverage = args.analysis.failed === 0 && args.analysis.errors === 0 ? true : residuals.remainingErrors === 0 && residuals.remainingFailed === 0;
|
|
1486
|
-
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;
|
|
1487
|
-
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);
|
|
1488
1618
|
const dominantBlockerBucketIndex = dominantBucket && isDominantBlockerType(dominantBucket.bucket.type) ? dominantBucket.index + 1 : null;
|
|
1489
1619
|
const readTargets = buildReadTargets({
|
|
1490
1620
|
buckets,
|
|
1491
1621
|
dominantBucketIndex: dominantBlockerBucketIndex
|
|
1492
1622
|
});
|
|
1493
|
-
const
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1623
|
+
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"));
|
|
1624
|
+
const smallConcreteSuite = args.analysis.failed + args.analysis.errors <= 2 && residuals.remainingErrors === 0 && residuals.remainingFailed === 0 && buckets.length === 1 && !hasUnknownBucket && dominantBucket !== null && dominantBucketHasConcreteAnchor;
|
|
1625
|
+
const dominantConfidenceThreshold = smallConcreteSuite ? 0.55 : 0.6;
|
|
1626
|
+
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;
|
|
1627
|
+
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);
|
|
1628
|
+
const mainBuckets = buckets.map((bucket, index) => {
|
|
1629
|
+
const bucketIndex = index + 1;
|
|
1630
|
+
const label = labelForBucket(bucket);
|
|
1631
|
+
const readTarget = readTargets.find((target) => target.bucket_index === bucketIndex);
|
|
1632
|
+
return {
|
|
1633
|
+
bucket_index: bucketIndex,
|
|
1634
|
+
label,
|
|
1635
|
+
count: bucket.count,
|
|
1636
|
+
root_cause: bucket.reason,
|
|
1637
|
+
suspect_kind: deriveBucketSuspectKind({
|
|
1638
|
+
bucket,
|
|
1639
|
+
readTarget
|
|
1640
|
+
}),
|
|
1641
|
+
fix_hint: resolveBucketFixHint({
|
|
1642
|
+
bucket,
|
|
1643
|
+
bucketLabel: label
|
|
1644
|
+
}),
|
|
1645
|
+
evidence: buildBucketEvidence(bucket),
|
|
1646
|
+
bucket_confidence: Number(bucket.confidence.toFixed(2)),
|
|
1647
|
+
root_cause_confidence: Number(rootCauseConfidenceFor(bucket).toFixed(2)),
|
|
1648
|
+
dominant: dominantBucket?.index === index,
|
|
1649
|
+
secondary_visible_despite_blocker: dominantBlockerBucketIndex !== null && dominantBlockerBucketIndex !== bucketIndex,
|
|
1650
|
+
mini_diff: extractMiniDiff(args.input, bucket)
|
|
1651
|
+
};
|
|
1652
|
+
});
|
|
1505
1653
|
const resolvedTests = unique(args.resolvedTests ?? []);
|
|
1506
1654
|
const remainingTests = unique(
|
|
1507
1655
|
args.remainingTests ?? unique([...args.analysis.visibleErrorLabels, ...args.analysis.visibleFailedLabels])
|
|
1508
1656
|
);
|
|
1657
|
+
const primarySuspectKind = derivePrimarySuspectKind({
|
|
1658
|
+
mainBuckets,
|
|
1659
|
+
dominantBlockerBucketIndex
|
|
1660
|
+
});
|
|
1509
1661
|
let nextBestAction;
|
|
1510
1662
|
if (args.analysis.failed === 0 && args.analysis.errors === 0 && args.analysis.passed > 0) {
|
|
1511
1663
|
nextBestAction = {
|
|
@@ -1551,6 +1703,8 @@ function buildTestStatusDiagnoseContract(args) {
|
|
|
1551
1703
|
additional_source_read_likely_low_value: diagnosisComplete && !rawNeeded,
|
|
1552
1704
|
read_raw_only_if: rawNeeded ? "you still need exact traceback lines after focused or verbose detail" : null,
|
|
1553
1705
|
dominant_blocker_bucket_index: dominantBlockerBucketIndex,
|
|
1706
|
+
primary_suspect_kind: primarySuspectKind,
|
|
1707
|
+
confidence_reason: "Unknown or low-confidence buckets remain; one deeper sift pass is justified.",
|
|
1554
1708
|
provider_used: false,
|
|
1555
1709
|
provider_confidence: null,
|
|
1556
1710
|
provider_failed: false,
|
|
@@ -1582,9 +1736,16 @@ function buildTestStatusDiagnoseContract(args) {
|
|
|
1582
1736
|
})
|
|
1583
1737
|
}
|
|
1584
1738
|
};
|
|
1739
|
+
const resolvedDecision = effectiveDecision ?? deriveDecision(mergedContractWithoutDecision);
|
|
1740
|
+
const resolvedConfidenceReason = buildConfidenceReason({
|
|
1741
|
+
decision: resolvedDecision,
|
|
1742
|
+
mainBuckets,
|
|
1743
|
+
primarySuspectKind: mergedContractWithoutDecision.primary_suspect_kind
|
|
1744
|
+
});
|
|
1585
1745
|
const contract = testStatusDiagnoseContractSchema.parse({
|
|
1586
1746
|
...mergedContractWithoutDecision,
|
|
1587
|
-
|
|
1747
|
+
confidence_reason: resolvedConfidenceReason,
|
|
1748
|
+
decision: resolvedDecision
|
|
1588
1749
|
});
|
|
1589
1750
|
return {
|
|
1590
1751
|
contract,
|
|
@@ -1656,6 +1817,27 @@ function buildTestStatusAnalysisContext(args) {
|
|
|
1656
1817
|
var RISK_LINE_PATTERN = /(destroy|delete|drop|recreate|replace|revoke|deny|downtime|data loss|iam|network exposure)/i;
|
|
1657
1818
|
var ZERO_DESTRUCTIVE_SUMMARY_PATTERN = /\b0\s+to\s+(destroy|delete|drop|recreate|replace|revoke)\b/i;
|
|
1658
1819
|
var SAFE_LINE_PATTERN = /(no changes|up-to-date|up to date|no risky changes|safe to apply)/i;
|
|
1820
|
+
var RESOURCE_DESTROY_HEADER_PATTERN = /^#\s+.+\bwill be (destroyed|deleted|replaced)\b/i;
|
|
1821
|
+
var DESTROY_ERROR_PATTERN = /(instance cannot be destroyed|prevent_destroy|downtime|data loss)/i;
|
|
1822
|
+
var ACTION_DESTROY_PATTERN = /^-\s+destroy$/i;
|
|
1823
|
+
var TSC_CODE_LABELS = {
|
|
1824
|
+
TS1002: "syntax error",
|
|
1825
|
+
TS1005: "syntax error",
|
|
1826
|
+
TS2304: "cannot find name",
|
|
1827
|
+
TS2307: "cannot find module",
|
|
1828
|
+
TS2322: "type mismatch",
|
|
1829
|
+
TS2339: "missing property on type",
|
|
1830
|
+
TS2345: "argument type mismatch",
|
|
1831
|
+
TS2554: "wrong argument count",
|
|
1832
|
+
TS2741: "missing required property",
|
|
1833
|
+
TS2769: "no matching overload",
|
|
1834
|
+
TS5083: "config file error",
|
|
1835
|
+
TS6133: "declared but unused",
|
|
1836
|
+
TS7006: "implicit any",
|
|
1837
|
+
TS18003: "no inputs were found",
|
|
1838
|
+
TS18046: "unknown type",
|
|
1839
|
+
TS18048: "possibly undefined"
|
|
1840
|
+
};
|
|
1659
1841
|
function collectEvidence(input, matcher, limit = 3) {
|
|
1660
1842
|
return input.split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && matcher.test(line)).slice(0, limit);
|
|
1661
1843
|
}
|
|
@@ -1669,11 +1851,179 @@ function inferPackage(line) {
|
|
|
1669
1851
|
function inferRemediation(pkg) {
|
|
1670
1852
|
return `Upgrade ${pkg} to a patched version.`;
|
|
1671
1853
|
}
|
|
1854
|
+
function parseCompactAuditVulnerability(line) {
|
|
1855
|
+
if (/^Severity:\s*/i.test(line)) {
|
|
1856
|
+
return null;
|
|
1857
|
+
}
|
|
1858
|
+
if (!/\b(critical|high)\b/i.test(line)) {
|
|
1859
|
+
return null;
|
|
1860
|
+
}
|
|
1861
|
+
const pkg = inferPackage(line);
|
|
1862
|
+
if (!pkg) {
|
|
1863
|
+
return null;
|
|
1864
|
+
}
|
|
1865
|
+
return {
|
|
1866
|
+
package: pkg,
|
|
1867
|
+
severity: inferSeverity(line),
|
|
1868
|
+
remediation: inferRemediation(pkg)
|
|
1869
|
+
};
|
|
1870
|
+
}
|
|
1871
|
+
function inferAuditPackageHeader(line) {
|
|
1872
|
+
const trimmed = line.trim();
|
|
1873
|
+
if (trimmed.length === 0 || trimmed.startsWith("#") || trimmed.includes(":") || /^node_modules\//i.test(trimmed)) {
|
|
1874
|
+
return null;
|
|
1875
|
+
}
|
|
1876
|
+
const match = trimmed.match(/^([@a-z0-9._/-]+)(?:\s{2,}|\s+(?:[<>=~^*]|\d))/i);
|
|
1877
|
+
return match?.[1] ?? null;
|
|
1878
|
+
}
|
|
1879
|
+
function collectAuditCriticalVulnerabilities(input) {
|
|
1880
|
+
const lines = input.split("\n");
|
|
1881
|
+
const vulnerabilities = [];
|
|
1882
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1883
|
+
const pushVulnerability = (pkg, severity) => {
|
|
1884
|
+
const key = `${pkg}:${severity}`;
|
|
1885
|
+
if (seen.has(key)) {
|
|
1886
|
+
return;
|
|
1887
|
+
}
|
|
1888
|
+
seen.add(key);
|
|
1889
|
+
vulnerabilities.push({
|
|
1890
|
+
package: pkg,
|
|
1891
|
+
severity,
|
|
1892
|
+
remediation: inferRemediation(pkg)
|
|
1893
|
+
});
|
|
1894
|
+
};
|
|
1895
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
1896
|
+
const line = lines[index].trim();
|
|
1897
|
+
if (!line) {
|
|
1898
|
+
continue;
|
|
1899
|
+
}
|
|
1900
|
+
const compact = parseCompactAuditVulnerability(line);
|
|
1901
|
+
if (compact) {
|
|
1902
|
+
pushVulnerability(compact.package, compact.severity);
|
|
1903
|
+
continue;
|
|
1904
|
+
}
|
|
1905
|
+
const pkg = inferAuditPackageHeader(line);
|
|
1906
|
+
if (!pkg) {
|
|
1907
|
+
continue;
|
|
1908
|
+
}
|
|
1909
|
+
for (let cursor = index + 1; cursor < Math.min(lines.length, index + 5); cursor += 1) {
|
|
1910
|
+
const candidate = lines[cursor].trim();
|
|
1911
|
+
if (!candidate) {
|
|
1912
|
+
continue;
|
|
1913
|
+
}
|
|
1914
|
+
const severityMatch = candidate.match(/^Severity:\s*(critical|high)\b/i);
|
|
1915
|
+
if (severityMatch) {
|
|
1916
|
+
pushVulnerability(pkg, severityMatch[1].toLowerCase());
|
|
1917
|
+
break;
|
|
1918
|
+
}
|
|
1919
|
+
if (inferAuditPackageHeader(candidate) || parseCompactAuditVulnerability(candidate)) {
|
|
1920
|
+
break;
|
|
1921
|
+
}
|
|
1922
|
+
}
|
|
1923
|
+
}
|
|
1924
|
+
return vulnerabilities;
|
|
1925
|
+
}
|
|
1672
1926
|
function getCount(input, label) {
|
|
1673
1927
|
const matches = [...input.matchAll(new RegExp(`(\\d+)\\s+${label}`, "gi"))];
|
|
1674
1928
|
const lastMatch = matches.at(-1);
|
|
1675
1929
|
return lastMatch ? Number(lastMatch[1]) : 0;
|
|
1676
1930
|
}
|
|
1931
|
+
function collectInfraRiskEvidence(input) {
|
|
1932
|
+
const lines = input.split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
|
|
1933
|
+
const evidence = [];
|
|
1934
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1935
|
+
const pushMatches = (matcher, options) => {
|
|
1936
|
+
let added = 0;
|
|
1937
|
+
for (const line of lines) {
|
|
1938
|
+
if (!matcher.test(line)) {
|
|
1939
|
+
continue;
|
|
1940
|
+
}
|
|
1941
|
+
if (options?.exclude?.test(line)) {
|
|
1942
|
+
continue;
|
|
1943
|
+
}
|
|
1944
|
+
if (seen.has(line)) {
|
|
1945
|
+
continue;
|
|
1946
|
+
}
|
|
1947
|
+
evidence.push(line);
|
|
1948
|
+
seen.add(line);
|
|
1949
|
+
added += 1;
|
|
1950
|
+
if (options?.limit && added >= options.limit) {
|
|
1951
|
+
return;
|
|
1952
|
+
}
|
|
1953
|
+
if (evidence.length >= (options?.maxEvidence ?? 4)) {
|
|
1954
|
+
return;
|
|
1955
|
+
}
|
|
1956
|
+
}
|
|
1957
|
+
};
|
|
1958
|
+
pushMatches(/Plan:/i, {
|
|
1959
|
+
exclude: ZERO_DESTRUCTIVE_SUMMARY_PATTERN,
|
|
1960
|
+
limit: 1
|
|
1961
|
+
});
|
|
1962
|
+
if (evidence.length < 4) {
|
|
1963
|
+
pushMatches(RESOURCE_DESTROY_HEADER_PATTERN, { limit: 2 });
|
|
1964
|
+
}
|
|
1965
|
+
if (evidence.length < 4) {
|
|
1966
|
+
pushMatches(DESTROY_ERROR_PATTERN, { limit: 1 });
|
|
1967
|
+
}
|
|
1968
|
+
if (evidence.length < 4) {
|
|
1969
|
+
pushMatches(ACTION_DESTROY_PATTERN, { limit: 1 });
|
|
1970
|
+
}
|
|
1971
|
+
if (evidence.length < 4) {
|
|
1972
|
+
pushMatches(RISK_LINE_PATTERN, {
|
|
1973
|
+
exclude: /->\s+null$|\b0\s+to\s+(destroy|delete|drop|recreate|replace|revoke)\b/i,
|
|
1974
|
+
maxEvidence: 4
|
|
1975
|
+
});
|
|
1976
|
+
}
|
|
1977
|
+
return evidence.slice(0, 4);
|
|
1978
|
+
}
|
|
1979
|
+
function collectInfraDestroyTargets(input) {
|
|
1980
|
+
const targets = [];
|
|
1981
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1982
|
+
for (const line of input.split("\n").map((entry) => entry.trim())) {
|
|
1983
|
+
const match = line.match(/^#\s+(.+?)\s+will be (destroyed|deleted|replaced)\b/i);
|
|
1984
|
+
const target = match?.[1]?.trim();
|
|
1985
|
+
if (!target || seen.has(target)) {
|
|
1986
|
+
continue;
|
|
1987
|
+
}
|
|
1988
|
+
seen.add(target);
|
|
1989
|
+
targets.push(target);
|
|
1990
|
+
}
|
|
1991
|
+
return targets;
|
|
1992
|
+
}
|
|
1993
|
+
function inferInfraDestroyCount(input, destroyTargets) {
|
|
1994
|
+
const matches = [
|
|
1995
|
+
...input.matchAll(/\b(\d+)\s+to\s+(destroy|delete|drop|recreate|replace|revoke)\b/gi)
|
|
1996
|
+
];
|
|
1997
|
+
const lastMatch = matches.at(-1);
|
|
1998
|
+
return lastMatch ? Number(lastMatch[1]) : destroyTargets.length;
|
|
1999
|
+
}
|
|
2000
|
+
function collectInfraBlockers(input) {
|
|
2001
|
+
const lines = input.split("\n");
|
|
2002
|
+
const blockers = [];
|
|
2003
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2004
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
2005
|
+
const trimmed = lines[index]?.trim();
|
|
2006
|
+
const errorMatch = trimmed?.match(/^(?:[│|]\s*)?Error:\s+(.+)$/);
|
|
2007
|
+
if (!errorMatch) {
|
|
2008
|
+
continue;
|
|
2009
|
+
}
|
|
2010
|
+
const message = errorMatch[1].trim();
|
|
2011
|
+
const nearby = lines.slice(index, index + 8).join("\n");
|
|
2012
|
+
const preventDestroyTarget = nearby.match(/Resource\s+([^\s]+)\s+has lifecycle\.prevent_destroy set/i)?.[1] ?? null;
|
|
2013
|
+
const type = preventDestroyTarget ? "prevent_destroy" : "destroy_blocked";
|
|
2014
|
+
const key = `${type}:${preventDestroyTarget ?? ""}:${message}`;
|
|
2015
|
+
if (seen.has(key)) {
|
|
2016
|
+
continue;
|
|
2017
|
+
}
|
|
2018
|
+
seen.add(key);
|
|
2019
|
+
blockers.push({
|
|
2020
|
+
type,
|
|
2021
|
+
target: preventDestroyTarget,
|
|
2022
|
+
message
|
|
2023
|
+
});
|
|
2024
|
+
}
|
|
2025
|
+
return blockers;
|
|
2026
|
+
}
|
|
1677
2027
|
function detectTestRunner(input) {
|
|
1678
2028
|
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)) {
|
|
1679
2029
|
return "vitest";
|
|
@@ -1751,6 +2101,21 @@ function collectUniqueMatches(input, matcher, limit = 6) {
|
|
|
1751
2101
|
}
|
|
1752
2102
|
return values;
|
|
1753
2103
|
}
|
|
2104
|
+
function compactDisplayFile(file) {
|
|
2105
|
+
const normalized = file.replace(/\\/g, "/").trim();
|
|
2106
|
+
if (!normalized) {
|
|
2107
|
+
return file;
|
|
2108
|
+
}
|
|
2109
|
+
const looksAbsolute = normalized.startsWith("/") || /^[A-Za-z]:\//.test(normalized);
|
|
2110
|
+
if (!looksAbsolute && normalized.length <= 60) {
|
|
2111
|
+
return normalized;
|
|
2112
|
+
}
|
|
2113
|
+
const basename = normalized.split("/").at(-1);
|
|
2114
|
+
return basename && basename.length > 0 ? basename : normalized;
|
|
2115
|
+
}
|
|
2116
|
+
function formatDisplayedFiles(files, limit = 3) {
|
|
2117
|
+
return [...new Set([...files].map((file) => file.trim()).filter(Boolean))].sort((left, right) => left.localeCompare(right)).slice(0, limit).map((file) => compactDisplayFile(file));
|
|
2118
|
+
}
|
|
1754
2119
|
function emptyAnchor() {
|
|
1755
2120
|
return {
|
|
1756
2121
|
file: null,
|
|
@@ -2030,6 +2395,31 @@ function classifyFailureReason(line, options) {
|
|
|
2030
2395
|
group: "permission or locked resource failures"
|
|
2031
2396
|
};
|
|
2032
2397
|
}
|
|
2398
|
+
const osDiskFullFailure = normalized.match(
|
|
2399
|
+
/(OSError:\s*\[Errno 28\][^$]*|No space left on device)/i
|
|
2400
|
+
);
|
|
2401
|
+
if (osDiskFullFailure) {
|
|
2402
|
+
return {
|
|
2403
|
+
reason: buildClassifiedReason(
|
|
2404
|
+
"configuration",
|
|
2405
|
+
`disk full (${buildExcerptDetail(
|
|
2406
|
+
osDiskFullFailure[1] ?? normalized,
|
|
2407
|
+
"No space left on device"
|
|
2408
|
+
)})`
|
|
2409
|
+
),
|
|
2410
|
+
group: "test configuration failures"
|
|
2411
|
+
};
|
|
2412
|
+
}
|
|
2413
|
+
const osPermissionFailure = normalized.match(/OSError:\s*\[Errno 13\][^$]*/i);
|
|
2414
|
+
if (osPermissionFailure) {
|
|
2415
|
+
return {
|
|
2416
|
+
reason: buildClassifiedReason(
|
|
2417
|
+
"permission",
|
|
2418
|
+
buildExcerptDetail(osPermissionFailure[0] ?? normalized, "permission denied")
|
|
2419
|
+
),
|
|
2420
|
+
group: "permission or locked resource failures"
|
|
2421
|
+
};
|
|
2422
|
+
}
|
|
2033
2423
|
const xdistWorkerCrash = normalized.match(
|
|
2034
2424
|
/(worker ['"][^'"]+['"] crashed|node down:\s*[^,;]+|WorkerLost[^,;]*|Worker exited unexpectedly[^,;]*|worker exited unexpectedly[^,;]*)/i
|
|
2035
2425
|
);
|
|
@@ -2055,7 +2445,7 @@ function classifyFailureReason(line, options) {
|
|
|
2055
2445
|
};
|
|
2056
2446
|
}
|
|
2057
2447
|
const networkFailure = normalized.match(
|
|
2058
|
-
/(Max retries exceeded[^,;]*|gaierror[^,;]*|SSLCertVerificationError[^,;]*|Network is unreachable)/i
|
|
2448
|
+
/(Max retries exceeded[^,;]*|gaierror[^,;]*|SSLCertVerificationError[^,;]*|Network is unreachable|ConnectionResetError[^,;]*|BrokenPipeError[^,;]*|HTTPError:\s*[45]\d\d[^,;]*)/i
|
|
2059
2449
|
);
|
|
2060
2450
|
if (networkFailure) {
|
|
2061
2451
|
return {
|
|
@@ -2066,6 +2456,15 @@ function classifyFailureReason(line, options) {
|
|
|
2066
2456
|
group: "network dependency failures"
|
|
2067
2457
|
};
|
|
2068
2458
|
}
|
|
2459
|
+
const matcherAssertionFailure = normalized.match(
|
|
2460
|
+
/(expect\(received\)\.(?:toBe|toEqual|toStrictEqual|toMatchObject)\(expected\))/i
|
|
2461
|
+
);
|
|
2462
|
+
if (matcherAssertionFailure) {
|
|
2463
|
+
return {
|
|
2464
|
+
reason: `assertion failed: ${matcherAssertionFailure[1]}`.slice(0, 120),
|
|
2465
|
+
group: "assertion failures"
|
|
2466
|
+
};
|
|
2467
|
+
}
|
|
2069
2468
|
const relationMigration = normalized.match(/relation ["'`]([^"'`]+)["'`] does not exist/i);
|
|
2070
2469
|
if (relationMigration) {
|
|
2071
2470
|
return {
|
|
@@ -2104,6 +2503,34 @@ function classifyFailureReason(line, options) {
|
|
|
2104
2503
|
group: "memory exhaustion failures"
|
|
2105
2504
|
};
|
|
2106
2505
|
}
|
|
2506
|
+
const propertySetterOverrideFailure = normalized.match(
|
|
2507
|
+
/AttributeError:\s*(property ['"][^'"]+['"] of ['"][^'"]+['"] object has no setter|can't set attribute|readonly attribute|read-only attribute)/i
|
|
2508
|
+
);
|
|
2509
|
+
if (propertySetterOverrideFailure) {
|
|
2510
|
+
return {
|
|
2511
|
+
reason: buildClassifiedReason(
|
|
2512
|
+
"configuration",
|
|
2513
|
+
`invalid test setup override (${buildExcerptDetail(
|
|
2514
|
+
`AttributeError: ${propertySetterOverrideFailure[1] ?? normalized}`,
|
|
2515
|
+
"AttributeError: can't set attribute"
|
|
2516
|
+
)})`
|
|
2517
|
+
),
|
|
2518
|
+
group: "test configuration failures"
|
|
2519
|
+
};
|
|
2520
|
+
}
|
|
2521
|
+
const setupOverrideFailure = normalized.match(/\b(AttributeError|TypeError):\s*(.+)$/i);
|
|
2522
|
+
if (setupOverrideFailure && /(monkeypatch|patch|fixture|settings|conftest)/i.test(normalized)) {
|
|
2523
|
+
return {
|
|
2524
|
+
reason: buildClassifiedReason(
|
|
2525
|
+
"configuration",
|
|
2526
|
+
`invalid test setup override (${buildExcerptDetail(
|
|
2527
|
+
`${setupOverrideFailure[1]}: ${setupOverrideFailure[2] ?? ""}`,
|
|
2528
|
+
`${setupOverrideFailure[1]}`
|
|
2529
|
+
)})`
|
|
2530
|
+
),
|
|
2531
|
+
group: "test configuration failures"
|
|
2532
|
+
};
|
|
2533
|
+
}
|
|
2107
2534
|
const typeErrorFailure = normalized.match(/TypeError:\s*(.+)$/i);
|
|
2108
2535
|
if (typeErrorFailure) {
|
|
2109
2536
|
return {
|
|
@@ -3185,13 +3612,17 @@ function analyzeTestStatus(input) {
|
|
|
3185
3612
|
const interrupted = /\binterrupted\b/i.test(input) || /\bKeyboardInterrupt\b/i.test(input);
|
|
3186
3613
|
const collectionItems = chooseStrongestFailureItems(collectCollectionFailureItems(input));
|
|
3187
3614
|
const inlineItems = chooseStrongestFailureItems(collectInlineFailureItems(input));
|
|
3615
|
+
const statusItems = collectInlineFailureItemsWithStatus(input);
|
|
3188
3616
|
const visibleErrorItems = chooseStrongestStatusFailureItems([
|
|
3189
3617
|
...collectionItems.map((item) => ({
|
|
3190
3618
|
...item,
|
|
3191
3619
|
status: "error"
|
|
3192
3620
|
})),
|
|
3193
|
-
...
|
|
3621
|
+
...statusItems.filter((item) => item.status === "error")
|
|
3194
3622
|
]);
|
|
3623
|
+
const visibleFailedItems = chooseStrongestStatusFailureItems(
|
|
3624
|
+
statusItems.filter((item) => item.status === "failed")
|
|
3625
|
+
);
|
|
3195
3626
|
const labels = collectFailureLabels(input);
|
|
3196
3627
|
const visibleErrorLabels = labels.filter((item) => item.status === "error").map((item) => item.label);
|
|
3197
3628
|
const visibleFailedLabels = labels.filter((item) => item.status === "failed").map((item) => item.label);
|
|
@@ -3250,6 +3681,7 @@ function analyzeTestStatus(input) {
|
|
|
3250
3681
|
visibleErrorLabels,
|
|
3251
3682
|
visibleFailedLabels,
|
|
3252
3683
|
visibleErrorItems,
|
|
3684
|
+
visibleFailedItems,
|
|
3253
3685
|
buckets
|
|
3254
3686
|
};
|
|
3255
3687
|
}
|
|
@@ -3310,20 +3742,18 @@ function testStatusHeuristic(input, detail = "standard") {
|
|
|
3310
3742
|
return null;
|
|
3311
3743
|
}
|
|
3312
3744
|
function auditCriticalHeuristic(input) {
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
};
|
|
3326
|
-
}).filter((item) => item !== null);
|
|
3745
|
+
if (/\bfound\s+0\s+vulnerabilities\b/i.test(input) || /\b0\s+vulnerabilities\b/i.test(input)) {
|
|
3746
|
+
return JSON.stringify(
|
|
3747
|
+
{
|
|
3748
|
+
status: "ok",
|
|
3749
|
+
vulnerabilities: [],
|
|
3750
|
+
summary: "No high or critical vulnerabilities found in the provided input."
|
|
3751
|
+
},
|
|
3752
|
+
null,
|
|
3753
|
+
2
|
|
3754
|
+
);
|
|
3755
|
+
}
|
|
3756
|
+
const vulnerabilities = collectAuditCriticalVulnerabilities(input);
|
|
3327
3757
|
if (vulnerabilities.length === 0) {
|
|
3328
3758
|
return null;
|
|
3329
3759
|
}
|
|
@@ -3339,16 +3769,19 @@ function auditCriticalHeuristic(input) {
|
|
|
3339
3769
|
);
|
|
3340
3770
|
}
|
|
3341
3771
|
function infraRiskHeuristic(input) {
|
|
3772
|
+
const destroyTargets = collectInfraDestroyTargets(input);
|
|
3773
|
+
const blockers = collectInfraBlockers(input);
|
|
3342
3774
|
const zeroDestructiveEvidence = input.split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && ZERO_DESTRUCTIVE_SUMMARY_PATTERN.test(line)).slice(0, 3);
|
|
3343
|
-
const riskEvidence = input
|
|
3344
|
-
(line) => line.length > 0 && RISK_LINE_PATTERN.test(line) && !ZERO_DESTRUCTIVE_SUMMARY_PATTERN.test(line)
|
|
3345
|
-
).slice(0, 3);
|
|
3775
|
+
const riskEvidence = collectInfraRiskEvidence(input);
|
|
3346
3776
|
if (riskEvidence.length > 0) {
|
|
3347
3777
|
return JSON.stringify(
|
|
3348
3778
|
{
|
|
3349
3779
|
verdict: "fail",
|
|
3350
3780
|
reason: "Destructive or clearly risky infrastructure change signals are present.",
|
|
3351
|
-
evidence: riskEvidence
|
|
3781
|
+
evidence: riskEvidence,
|
|
3782
|
+
destroy_count: inferInfraDestroyCount(input, destroyTargets),
|
|
3783
|
+
destroy_targets: destroyTargets,
|
|
3784
|
+
blockers
|
|
3352
3785
|
},
|
|
3353
3786
|
null,
|
|
3354
3787
|
2
|
|
@@ -3359,7 +3792,10 @@ function infraRiskHeuristic(input) {
|
|
|
3359
3792
|
{
|
|
3360
3793
|
verdict: "pass",
|
|
3361
3794
|
reason: "The provided input explicitly indicates zero destructive changes.",
|
|
3362
|
-
evidence: zeroDestructiveEvidence
|
|
3795
|
+
evidence: zeroDestructiveEvidence,
|
|
3796
|
+
destroy_count: 0,
|
|
3797
|
+
destroy_targets: [],
|
|
3798
|
+
blockers: []
|
|
3363
3799
|
},
|
|
3364
3800
|
null,
|
|
3365
3801
|
2
|
|
@@ -3371,7 +3807,10 @@ function infraRiskHeuristic(input) {
|
|
|
3371
3807
|
{
|
|
3372
3808
|
verdict: "pass",
|
|
3373
3809
|
reason: "The provided input explicitly indicates no risky infrastructure changes.",
|
|
3374
|
-
evidence: safeEvidence
|
|
3810
|
+
evidence: safeEvidence,
|
|
3811
|
+
destroy_count: 0,
|
|
3812
|
+
destroy_targets: [],
|
|
3813
|
+
blockers: []
|
|
3375
3814
|
},
|
|
3376
3815
|
null,
|
|
3377
3816
|
2
|
|
@@ -3379,6 +3818,551 @@ function infraRiskHeuristic(input) {
|
|
|
3379
3818
|
}
|
|
3380
3819
|
return null;
|
|
3381
3820
|
}
|
|
3821
|
+
function parseTscErrors(input) {
|
|
3822
|
+
const diagnostics = [];
|
|
3823
|
+
for (const rawLine of input.split("\n")) {
|
|
3824
|
+
const line = rawLine.replace(/\u001b\[[0-9;]*m/g, "").trimEnd();
|
|
3825
|
+
if (!line.trim()) {
|
|
3826
|
+
continue;
|
|
3827
|
+
}
|
|
3828
|
+
let match = line.match(/^(.+)\((\d+),(\d+)\):\s+error\s+(TS\d+):\s+(.+)$/);
|
|
3829
|
+
if (match) {
|
|
3830
|
+
diagnostics.push({
|
|
3831
|
+
file: match[1].replace(/\\/g, "/").trim(),
|
|
3832
|
+
line: Number(match[2]),
|
|
3833
|
+
column: Number(match[3]),
|
|
3834
|
+
code: match[4],
|
|
3835
|
+
message: match[5].trim()
|
|
3836
|
+
});
|
|
3837
|
+
continue;
|
|
3838
|
+
}
|
|
3839
|
+
match = line.match(/^(.+):(\d+):(\d+)\s+-\s+error\s+(TS\d+):\s+(.+)$/);
|
|
3840
|
+
if (match) {
|
|
3841
|
+
diagnostics.push({
|
|
3842
|
+
file: match[1].replace(/\\/g, "/").trim(),
|
|
3843
|
+
line: Number(match[2]),
|
|
3844
|
+
column: Number(match[3]),
|
|
3845
|
+
code: match[4],
|
|
3846
|
+
message: match[5].trim()
|
|
3847
|
+
});
|
|
3848
|
+
continue;
|
|
3849
|
+
}
|
|
3850
|
+
match = line.match(/^\s*error\s+(TS\d+):\s+(.+)$/);
|
|
3851
|
+
if (match) {
|
|
3852
|
+
diagnostics.push({
|
|
3853
|
+
file: null,
|
|
3854
|
+
line: null,
|
|
3855
|
+
column: null,
|
|
3856
|
+
code: match[1],
|
|
3857
|
+
message: match[2].trim()
|
|
3858
|
+
});
|
|
3859
|
+
}
|
|
3860
|
+
}
|
|
3861
|
+
return diagnostics;
|
|
3862
|
+
}
|
|
3863
|
+
function extractTscSummary(input) {
|
|
3864
|
+
const matches = [
|
|
3865
|
+
...input.matchAll(/\bFound\s+(\d+)\s+errors?\b(?:\s+in\s+(\d+)\s+files?)?\.?/gi)
|
|
3866
|
+
];
|
|
3867
|
+
const summary = matches.at(-1);
|
|
3868
|
+
if (!summary) {
|
|
3869
|
+
return null;
|
|
3870
|
+
}
|
|
3871
|
+
return {
|
|
3872
|
+
errorCount: Number(summary[1]),
|
|
3873
|
+
fileCount: summary[2] ? Number(summary[2]) : null
|
|
3874
|
+
};
|
|
3875
|
+
}
|
|
3876
|
+
function formatTscGroup(args) {
|
|
3877
|
+
const label = TSC_CODE_LABELS[args.code];
|
|
3878
|
+
const displayFiles = formatDisplayedFiles(args.files);
|
|
3879
|
+
let line = `- ${args.code}`;
|
|
3880
|
+
if (label) {
|
|
3881
|
+
line += ` (${label})`;
|
|
3882
|
+
}
|
|
3883
|
+
line += `: ${formatCount2(args.count, "occurrence")}`;
|
|
3884
|
+
if (displayFiles.length > 0) {
|
|
3885
|
+
line += ` across ${displayFiles.join(", ")}`;
|
|
3886
|
+
}
|
|
3887
|
+
return `${line}.`;
|
|
3888
|
+
}
|
|
3889
|
+
function typecheckSummaryHeuristic(input) {
|
|
3890
|
+
if (input.trim().length === 0) {
|
|
3891
|
+
return null;
|
|
3892
|
+
}
|
|
3893
|
+
const diagnostics = parseTscErrors(input);
|
|
3894
|
+
const summary = extractTscSummary(input);
|
|
3895
|
+
const hasTscSignal = diagnostics.length > 0 || summary !== null || /\berror\s+TS\d+:/m.test(input);
|
|
3896
|
+
if (!hasTscSignal) {
|
|
3897
|
+
return null;
|
|
3898
|
+
}
|
|
3899
|
+
if (summary?.errorCount === 0) {
|
|
3900
|
+
return "No type errors.";
|
|
3901
|
+
}
|
|
3902
|
+
if (diagnostics.length === 0 && summary === null) {
|
|
3903
|
+
return null;
|
|
3904
|
+
}
|
|
3905
|
+
const errorCount = summary?.errorCount ?? diagnostics.length;
|
|
3906
|
+
const allFiles = new Set(
|
|
3907
|
+
diagnostics.map((diagnostic) => diagnostic.file).filter((file) => Boolean(file))
|
|
3908
|
+
);
|
|
3909
|
+
const fileCount = summary?.fileCount ?? (allFiles.size > 0 ? allFiles.size : null);
|
|
3910
|
+
const groups = /* @__PURE__ */ new Map();
|
|
3911
|
+
for (const diagnostic of diagnostics) {
|
|
3912
|
+
const group = groups.get(diagnostic.code) ?? {
|
|
3913
|
+
count: 0,
|
|
3914
|
+
files: /* @__PURE__ */ new Set()
|
|
3915
|
+
};
|
|
3916
|
+
group.count += 1;
|
|
3917
|
+
if (diagnostic.file) {
|
|
3918
|
+
group.files.add(diagnostic.file);
|
|
3919
|
+
}
|
|
3920
|
+
groups.set(diagnostic.code, group);
|
|
3921
|
+
}
|
|
3922
|
+
const bullets = [
|
|
3923
|
+
`- Typecheck failed: ${formatCount2(errorCount, "error")}${fileCount ? ` in ${formatCount2(fileCount, "file")}` : ""}.`
|
|
3924
|
+
];
|
|
3925
|
+
const sortedGroups = [...groups.entries()].map(([code, group]) => ({
|
|
3926
|
+
code,
|
|
3927
|
+
count: group.count,
|
|
3928
|
+
files: group.files
|
|
3929
|
+
})).sort((left, right) => right.count - left.count || left.code.localeCompare(right.code));
|
|
3930
|
+
for (const group of sortedGroups.slice(0, 3)) {
|
|
3931
|
+
bullets.push(formatTscGroup(group));
|
|
3932
|
+
}
|
|
3933
|
+
if (sortedGroups.length > 3) {
|
|
3934
|
+
const overflowFiles = /* @__PURE__ */ new Set();
|
|
3935
|
+
for (const group of sortedGroups.slice(3)) {
|
|
3936
|
+
for (const file of group.files) {
|
|
3937
|
+
overflowFiles.add(file);
|
|
3938
|
+
}
|
|
3939
|
+
}
|
|
3940
|
+
let overflow = `- ${formatCount2(sortedGroups.length - 3, "more error code")}`;
|
|
3941
|
+
if (overflowFiles.size > 0) {
|
|
3942
|
+
overflow += ` across ${formatCount2(overflowFiles.size, "file")}`;
|
|
3943
|
+
}
|
|
3944
|
+
bullets.push(`${overflow}.`);
|
|
3945
|
+
}
|
|
3946
|
+
return bullets.join("\n");
|
|
3947
|
+
}
|
|
3948
|
+
function looksLikeEslintFileHeader(line) {
|
|
3949
|
+
if (line.trim().length === 0 || line.trim() !== line) {
|
|
3950
|
+
return false;
|
|
3951
|
+
}
|
|
3952
|
+
if (/^\s*[✖×x]\s+\d+\s+problems?\b/i.test(line) || /potentially\s+fixable/i.test(line) || /^\d+\s+problems?\b/i.test(line)) {
|
|
3953
|
+
return false;
|
|
3954
|
+
}
|
|
3955
|
+
const normalized = line.replace(/\\/g, "/");
|
|
3956
|
+
const pathLike = normalized.startsWith("/") || normalized.startsWith("./") || normalized.startsWith("../") || /^[A-Za-z]:\//.test(normalized) || /^[A-Za-z0-9_.-]+\//.test(normalized);
|
|
3957
|
+
return pathLike && /\.[A-Za-z0-9]+$/.test(normalized);
|
|
3958
|
+
}
|
|
3959
|
+
function normalizeEslintRule(rule, message) {
|
|
3960
|
+
if (rule && rule.trim().length > 0) {
|
|
3961
|
+
return rule.trim();
|
|
3962
|
+
}
|
|
3963
|
+
if (/parsing error/i.test(message)) {
|
|
3964
|
+
return "parsing error";
|
|
3965
|
+
}
|
|
3966
|
+
if (/fatal/i.test(message)) {
|
|
3967
|
+
return "fatal error";
|
|
3968
|
+
}
|
|
3969
|
+
return "unclassified lint error";
|
|
3970
|
+
}
|
|
3971
|
+
function parseEslintStylish(input) {
|
|
3972
|
+
const violations = [];
|
|
3973
|
+
let currentFile = null;
|
|
3974
|
+
for (const rawLine of input.split("\n")) {
|
|
3975
|
+
const line = rawLine.replace(/\u001b\[[0-9;]*m/g, "").replace(/\r$/, "");
|
|
3976
|
+
if (looksLikeEslintFileHeader(line.trim())) {
|
|
3977
|
+
currentFile = line.trim().replace(/\\/g, "/");
|
|
3978
|
+
continue;
|
|
3979
|
+
}
|
|
3980
|
+
let match = line.match(/^\s*(\d+):(\d+)\s+(error|warning)\s+(.+?)\s{2,}(\S+)\s*$/);
|
|
3981
|
+
if (match) {
|
|
3982
|
+
violations.push({
|
|
3983
|
+
file: currentFile ?? "(unknown file)",
|
|
3984
|
+
line: Number(match[1]),
|
|
3985
|
+
column: Number(match[2]),
|
|
3986
|
+
severity: match[3],
|
|
3987
|
+
message: match[4].trim(),
|
|
3988
|
+
rule: normalizeEslintRule(match[5], match[4])
|
|
3989
|
+
});
|
|
3990
|
+
continue;
|
|
3991
|
+
}
|
|
3992
|
+
match = line.match(/^\s*(\d+):(\d+)\s+(error|warning)\s+(.+?)\s*$/);
|
|
3993
|
+
if (match) {
|
|
3994
|
+
violations.push({
|
|
3995
|
+
file: currentFile ?? "(unknown file)",
|
|
3996
|
+
line: Number(match[1]),
|
|
3997
|
+
column: Number(match[2]),
|
|
3998
|
+
severity: match[3],
|
|
3999
|
+
message: match[4].trim(),
|
|
4000
|
+
rule: normalizeEslintRule(null, match[4])
|
|
4001
|
+
});
|
|
4002
|
+
}
|
|
4003
|
+
}
|
|
4004
|
+
return violations;
|
|
4005
|
+
}
|
|
4006
|
+
function extractEslintSummary(input) {
|
|
4007
|
+
const summaryMatches = [
|
|
4008
|
+
...input.matchAll(
|
|
4009
|
+
/^\s*[✖×x]?\s*(\d+)\s+problems?\s+\((\d+)\s+errors?,\s+(\d+)\s+warnings?\)/gim
|
|
4010
|
+
)
|
|
4011
|
+
];
|
|
4012
|
+
const summary = summaryMatches.at(-1);
|
|
4013
|
+
if (!summary) {
|
|
4014
|
+
return null;
|
|
4015
|
+
}
|
|
4016
|
+
const fixableMatch = input.match(
|
|
4017
|
+
/(\d+)\s+errors?\s+and\s+(\d+)\s+warnings?\s+(?:are|is)\s+potentially\s+fixable/i
|
|
4018
|
+
);
|
|
4019
|
+
return {
|
|
4020
|
+
problems: Number(summary[1]),
|
|
4021
|
+
errors: Number(summary[2]),
|
|
4022
|
+
warnings: Number(summary[3]),
|
|
4023
|
+
fixableProblems: fixableMatch ? Number(fixableMatch[1]) + Number(fixableMatch[2]) : null
|
|
4024
|
+
};
|
|
4025
|
+
}
|
|
4026
|
+
function formatLintGroup(args) {
|
|
4027
|
+
const totalErrors = args.errors;
|
|
4028
|
+
const totalWarnings = args.warnings;
|
|
4029
|
+
const displayFiles = formatDisplayedFiles(args.files);
|
|
4030
|
+
let detail = "";
|
|
4031
|
+
if (totalErrors > 0 && totalWarnings > 0) {
|
|
4032
|
+
detail = `${formatCount2(totalErrors, "error")}, ${formatCount2(totalWarnings, "warning")}`;
|
|
4033
|
+
} else if (totalErrors > 0) {
|
|
4034
|
+
detail = formatCount2(totalErrors, "error");
|
|
4035
|
+
} else {
|
|
4036
|
+
detail = formatCount2(totalWarnings, "warning");
|
|
4037
|
+
}
|
|
4038
|
+
let line = `- ${args.rule}: ${detail}`;
|
|
4039
|
+
if (displayFiles.length > 0) {
|
|
4040
|
+
line += ` across ${displayFiles.join(", ")}`;
|
|
4041
|
+
}
|
|
4042
|
+
return `${line}.`;
|
|
4043
|
+
}
|
|
4044
|
+
function lintFailuresHeuristic(input) {
|
|
4045
|
+
const trimmed = input.trim();
|
|
4046
|
+
if (trimmed.length === 0 || trimmed.startsWith("[") || trimmed.startsWith("{")) {
|
|
4047
|
+
return null;
|
|
4048
|
+
}
|
|
4049
|
+
const summary = extractEslintSummary(input);
|
|
4050
|
+
const violations = parseEslintStylish(input);
|
|
4051
|
+
if (summary === null && violations.length === 0) {
|
|
4052
|
+
return null;
|
|
4053
|
+
}
|
|
4054
|
+
if (summary?.problems === 0) {
|
|
4055
|
+
return "No lint failures.";
|
|
4056
|
+
}
|
|
4057
|
+
const problems = summary?.problems ?? violations.length;
|
|
4058
|
+
const errors = summary?.errors ?? countPattern(input, /^\s*\d+:\d+\s+error\b/gm);
|
|
4059
|
+
const warnings = summary?.warnings ?? countPattern(input, /^\s*\d+:\d+\s+warning\b/gm);
|
|
4060
|
+
const bullets = [];
|
|
4061
|
+
if (errors > 0) {
|
|
4062
|
+
let headline = `- Lint failed: ${formatCount2(problems, "problem")} (${formatCount2(errors, "error")}, ${formatCount2(warnings, "warning")}).`;
|
|
4063
|
+
if ((summary?.fixableProblems ?? 0) > 0) {
|
|
4064
|
+
headline += ` ${formatCount2(summary.fixableProblems, "problem")} potentially fixable with --fix.`;
|
|
4065
|
+
}
|
|
4066
|
+
bullets.push(headline);
|
|
4067
|
+
} else {
|
|
4068
|
+
bullets.push(`- No lint errors visible: ${formatCount2(warnings, "warning")}.`);
|
|
4069
|
+
}
|
|
4070
|
+
const groups = /* @__PURE__ */ new Map();
|
|
4071
|
+
for (const violation of violations) {
|
|
4072
|
+
const group = groups.get(violation.rule) ?? {
|
|
4073
|
+
errors: 0,
|
|
4074
|
+
warnings: 0,
|
|
4075
|
+
files: /* @__PURE__ */ new Set()
|
|
4076
|
+
};
|
|
4077
|
+
if (violation.severity === "error") {
|
|
4078
|
+
group.errors += 1;
|
|
4079
|
+
} else {
|
|
4080
|
+
group.warnings += 1;
|
|
4081
|
+
}
|
|
4082
|
+
group.files.add(violation.file);
|
|
4083
|
+
groups.set(violation.rule, group);
|
|
4084
|
+
}
|
|
4085
|
+
const sortedGroups = [...groups.entries()].map(([rule, group]) => ({
|
|
4086
|
+
rule,
|
|
4087
|
+
errors: group.errors,
|
|
4088
|
+
warnings: group.warnings,
|
|
4089
|
+
total: group.errors + group.warnings,
|
|
4090
|
+
files: group.files
|
|
4091
|
+
})).sort((left, right) => {
|
|
4092
|
+
const leftHasErrors = left.errors > 0 ? 1 : 0;
|
|
4093
|
+
const rightHasErrors = right.errors > 0 ? 1 : 0;
|
|
4094
|
+
return rightHasErrors - leftHasErrors || right.total - left.total || left.rule.localeCompare(right.rule);
|
|
4095
|
+
});
|
|
4096
|
+
for (const group of sortedGroups.slice(0, 3)) {
|
|
4097
|
+
bullets.push(formatLintGroup(group));
|
|
4098
|
+
}
|
|
4099
|
+
if (sortedGroups.length > 3) {
|
|
4100
|
+
const overflowFiles = /* @__PURE__ */ new Set();
|
|
4101
|
+
for (const group of sortedGroups.slice(3)) {
|
|
4102
|
+
for (const file of group.files) {
|
|
4103
|
+
overflowFiles.add(file);
|
|
4104
|
+
}
|
|
4105
|
+
}
|
|
4106
|
+
let overflow = `- ${formatCount2(sortedGroups.length - 3, "more rule")}`;
|
|
4107
|
+
if (overflowFiles.size > 0) {
|
|
4108
|
+
overflow += ` across ${formatCount2(overflowFiles.size, "file")}`;
|
|
4109
|
+
}
|
|
4110
|
+
bullets.push(`${overflow}.`);
|
|
4111
|
+
}
|
|
4112
|
+
return bullets.join("\n");
|
|
4113
|
+
}
|
|
4114
|
+
function stripAnsiText(input) {
|
|
4115
|
+
return input.replace(/\u001b\[[0-9;]*m/g, "");
|
|
4116
|
+
}
|
|
4117
|
+
function normalizeBuildPath(file) {
|
|
4118
|
+
return file.replace(/\\/g, "/").replace(/^\.\//, "").trim();
|
|
4119
|
+
}
|
|
4120
|
+
function trimTrailingSentencePunctuation(input) {
|
|
4121
|
+
return input.replace(/[.:]+$/, "").trim();
|
|
4122
|
+
}
|
|
4123
|
+
function containsKnownBuildFailureSignal(input) {
|
|
4124
|
+
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);
|
|
4125
|
+
}
|
|
4126
|
+
function detectExplicitBuildSuccess(input) {
|
|
4127
|
+
if (containsKnownBuildFailureSignal(input)) {
|
|
4128
|
+
return false;
|
|
4129
|
+
}
|
|
4130
|
+
return /\bcompiled successfully\b/i.test(input) || /^\s*Build succeeded\.?\s*$/im.test(input) || /\bcompiled with 0 errors?\b/i.test(input);
|
|
4131
|
+
}
|
|
4132
|
+
function inferBuildFailureCategory(message) {
|
|
4133
|
+
if (/module not found|can't resolve|could not resolve|cannot find module|no required module provides package/i.test(
|
|
4134
|
+
message
|
|
4135
|
+
)) {
|
|
4136
|
+
return "module-resolution";
|
|
4137
|
+
}
|
|
4138
|
+
if (/no matching export|does not provide an export named|missing export/i.test(message)) {
|
|
4139
|
+
return "missing-export";
|
|
4140
|
+
}
|
|
4141
|
+
if (/cannot find name|cannot find value|not found in this scope|undefined:|undeclared identifier/i.test(
|
|
4142
|
+
message
|
|
4143
|
+
)) {
|
|
4144
|
+
return "undefined-identifier";
|
|
4145
|
+
}
|
|
4146
|
+
if (/syntax error|unexpected token|expected ['"`;)]|expected .* after expression/i.test(message)) {
|
|
4147
|
+
return "syntax";
|
|
4148
|
+
}
|
|
4149
|
+
if (/\bTS\d+\b/.test(message) || /type .* is not assignable|type error|no matching overload/i.test(message)) {
|
|
4150
|
+
return "type";
|
|
4151
|
+
}
|
|
4152
|
+
return "generic";
|
|
4153
|
+
}
|
|
4154
|
+
function buildFailureSuggestion(category) {
|
|
4155
|
+
switch (category) {
|
|
4156
|
+
case "module-resolution":
|
|
4157
|
+
return "Install the missing package or fix the import path.";
|
|
4158
|
+
case "missing-export":
|
|
4159
|
+
return "Check the export name in the source module.";
|
|
4160
|
+
case "undefined-identifier":
|
|
4161
|
+
return "Define or import the missing identifier.";
|
|
4162
|
+
case "syntax":
|
|
4163
|
+
return "Fix the syntax error at the indicated location.";
|
|
4164
|
+
case "type":
|
|
4165
|
+
return "Fix the type error at the indicated location.";
|
|
4166
|
+
case "wrapper":
|
|
4167
|
+
return "Check the underlying build tool output above.";
|
|
4168
|
+
default:
|
|
4169
|
+
return "Fix the first reported error and rebuild.";
|
|
4170
|
+
}
|
|
4171
|
+
}
|
|
4172
|
+
function formatBuildFailureOutput(match) {
|
|
4173
|
+
const message = trimTrailingSentencePunctuation(match.message);
|
|
4174
|
+
const suggestion = buildFailureSuggestion(match.category);
|
|
4175
|
+
const displayFile = match.file ? compactDisplayFile(match.file) : null;
|
|
4176
|
+
if (displayFile && match.line !== null) {
|
|
4177
|
+
return `Build failed: ${message} in ${displayFile}:${match.line}. Fix: ${suggestion}`;
|
|
4178
|
+
}
|
|
4179
|
+
if (displayFile) {
|
|
4180
|
+
return `Build failed: ${message} in ${displayFile}. Fix: ${suggestion}`;
|
|
4181
|
+
}
|
|
4182
|
+
return `Build failed: ${message}. Fix: ${suggestion}`;
|
|
4183
|
+
}
|
|
4184
|
+
function extractWebpackBuildFailure(input) {
|
|
4185
|
+
const lines = stripAnsiText(input).split("\n").map((line) => line.trimEnd());
|
|
4186
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
4187
|
+
const match = lines[index]?.match(/^ERROR in (.+?)(?:\s+(\d+):(\d+))?$/);
|
|
4188
|
+
if (!match) {
|
|
4189
|
+
continue;
|
|
4190
|
+
}
|
|
4191
|
+
const candidates = [];
|
|
4192
|
+
for (let cursor = index + 1; cursor < Math.min(lines.length, index + 6); cursor += 1) {
|
|
4193
|
+
const candidate = lines[cursor]?.trim();
|
|
4194
|
+
if (!candidate) {
|
|
4195
|
+
continue;
|
|
4196
|
+
}
|
|
4197
|
+
if (/^ERROR in /.test(candidate) || /compiled with \d+ errors?/i.test(candidate)) {
|
|
4198
|
+
break;
|
|
4199
|
+
}
|
|
4200
|
+
if (/^(?:>|\|)|^\d+\s+\|/.test(candidate)) {
|
|
4201
|
+
continue;
|
|
4202
|
+
}
|
|
4203
|
+
candidates.push(candidate);
|
|
4204
|
+
}
|
|
4205
|
+
let message = "Compilation error";
|
|
4206
|
+
if (candidates.length > 0) {
|
|
4207
|
+
const preferred = candidates.find(
|
|
4208
|
+
(candidate) => !/^Module build failed\b/i.test(candidate) && !/^Error:\s+TypeScript compilation failed\b/i.test(candidate) && inferBuildFailureCategory(candidate) !== "generic"
|
|
4209
|
+
) ?? candidates.find(
|
|
4210
|
+
(candidate) => !/^Module build failed\b/i.test(candidate) && !/^Error:\s+TypeScript compilation failed\b/i.test(candidate)
|
|
4211
|
+
) ?? candidates[0];
|
|
4212
|
+
message = preferred ?? message;
|
|
4213
|
+
}
|
|
4214
|
+
return {
|
|
4215
|
+
message,
|
|
4216
|
+
file: normalizeBuildPath(match[1]),
|
|
4217
|
+
line: match[2] ? Number(match[2]) : null,
|
|
4218
|
+
column: match[3] ? Number(match[3]) : null,
|
|
4219
|
+
category: inferBuildFailureCategory(message)
|
|
4220
|
+
};
|
|
4221
|
+
}
|
|
4222
|
+
return null;
|
|
4223
|
+
}
|
|
4224
|
+
function extractViteImportAnalysisBuildFailure(input) {
|
|
4225
|
+
const lines = stripAnsiText(input).split("\n").map((line) => line.trim());
|
|
4226
|
+
for (const line of lines) {
|
|
4227
|
+
const match = line.match(
|
|
4228
|
+
/^\[plugin:vite:import-analysis\]\s+Failed to resolve import\s+"([^"]+)"\s+from\s+"([^"]+)"/i
|
|
4229
|
+
);
|
|
4230
|
+
if (!match) {
|
|
4231
|
+
continue;
|
|
4232
|
+
}
|
|
4233
|
+
return {
|
|
4234
|
+
message: `Failed to resolve import "${match[1]}"`,
|
|
4235
|
+
file: normalizeBuildPath(match[2]),
|
|
4236
|
+
line: null,
|
|
4237
|
+
column: null,
|
|
4238
|
+
category: "module-resolution"
|
|
4239
|
+
};
|
|
4240
|
+
}
|
|
4241
|
+
return null;
|
|
4242
|
+
}
|
|
4243
|
+
function extractEsbuildBuildFailure(input) {
|
|
4244
|
+
const lines = stripAnsiText(input).split("\n").map((line) => line.trimEnd());
|
|
4245
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
4246
|
+
const match = lines[index]?.match(/^(?:[✘✗]\s*)?\[ERROR\]\s*(.+)$/);
|
|
4247
|
+
if (!match) {
|
|
4248
|
+
continue;
|
|
4249
|
+
}
|
|
4250
|
+
const message = match[1].replace(/^\[vite\]\s*/i, "").trim();
|
|
4251
|
+
let file = null;
|
|
4252
|
+
let line = null;
|
|
4253
|
+
let column = null;
|
|
4254
|
+
for (let cursor = index + 1; cursor < Math.min(lines.length, index + 6); cursor += 1) {
|
|
4255
|
+
const locationMatch = lines[cursor]?.trim().match(/^(.+?):(\d+):(\d+):$/);
|
|
4256
|
+
if (!locationMatch) {
|
|
4257
|
+
continue;
|
|
4258
|
+
}
|
|
4259
|
+
file = normalizeBuildPath(locationMatch[1]);
|
|
4260
|
+
line = Number(locationMatch[2]);
|
|
4261
|
+
column = Number(locationMatch[3]);
|
|
4262
|
+
break;
|
|
4263
|
+
}
|
|
4264
|
+
return {
|
|
4265
|
+
message,
|
|
4266
|
+
file,
|
|
4267
|
+
line,
|
|
4268
|
+
column,
|
|
4269
|
+
category: inferBuildFailureCategory(message)
|
|
4270
|
+
};
|
|
4271
|
+
}
|
|
4272
|
+
return null;
|
|
4273
|
+
}
|
|
4274
|
+
function extractCargoBuildFailure(input) {
|
|
4275
|
+
if (!/^error(?:\[E\d+\])?:\s+/m.test(input) || !(/^\s*-->\s+/m.test(input) || /could not compile/i.test(input))) {
|
|
4276
|
+
return null;
|
|
4277
|
+
}
|
|
4278
|
+
const lines = stripAnsiText(input).split("\n").map((line) => line.trimEnd());
|
|
4279
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
4280
|
+
const match = lines[index]?.match(/^error(?:\[(E\d+)\])?:\s+(.+)$/);
|
|
4281
|
+
if (!match) {
|
|
4282
|
+
continue;
|
|
4283
|
+
}
|
|
4284
|
+
const code = match[1];
|
|
4285
|
+
const locationMatch = lines.slice(index + 1, index + 7).join("\n").match(/^\s*-->\s+(.+?):(\d+):(\d+)/m);
|
|
4286
|
+
return {
|
|
4287
|
+
message: code ? `${code}: ${match[2].trim()}` : match[2].trim(),
|
|
4288
|
+
file: locationMatch ? normalizeBuildPath(locationMatch[1]) : null,
|
|
4289
|
+
line: locationMatch ? Number(locationMatch[2]) : null,
|
|
4290
|
+
column: locationMatch ? Number(locationMatch[3]) : null,
|
|
4291
|
+
category: inferBuildFailureCategory(match[2])
|
|
4292
|
+
};
|
|
4293
|
+
}
|
|
4294
|
+
return null;
|
|
4295
|
+
}
|
|
4296
|
+
function extractCompilerStyleBuildFailure(input) {
|
|
4297
|
+
const lines = stripAnsiText(input).split("\n").map((line) => line.trimEnd());
|
|
4298
|
+
for (const rawLine of lines) {
|
|
4299
|
+
let match = rawLine.match(
|
|
4300
|
+
/^(.+?\.(?:c|cc|cpp|cxx|h|hpp|m|mm)):([0-9]+):([0-9]+):\s*error:\s+(.+)$/
|
|
4301
|
+
);
|
|
4302
|
+
if (match) {
|
|
4303
|
+
return {
|
|
4304
|
+
message: match[4].trim(),
|
|
4305
|
+
file: normalizeBuildPath(match[1]),
|
|
4306
|
+
line: Number(match[2]),
|
|
4307
|
+
column: Number(match[3]),
|
|
4308
|
+
category: inferBuildFailureCategory(match[4])
|
|
4309
|
+
};
|
|
4310
|
+
}
|
|
4311
|
+
match = rawLine.match(/^(.+?\.go):([0-9]+):([0-9]+):\s+(.+)$/);
|
|
4312
|
+
if (match && !/^\s*warning:/i.test(match[4])) {
|
|
4313
|
+
return {
|
|
4314
|
+
message: match[4].trim(),
|
|
4315
|
+
file: normalizeBuildPath(match[1]),
|
|
4316
|
+
line: Number(match[2]),
|
|
4317
|
+
column: Number(match[3]),
|
|
4318
|
+
category: inferBuildFailureCategory(match[4])
|
|
4319
|
+
};
|
|
4320
|
+
}
|
|
4321
|
+
}
|
|
4322
|
+
return null;
|
|
4323
|
+
}
|
|
4324
|
+
function extractTscBuildFailure(input) {
|
|
4325
|
+
const diagnostics = parseTscErrors(input);
|
|
4326
|
+
const first = diagnostics[0];
|
|
4327
|
+
if (!first) {
|
|
4328
|
+
return null;
|
|
4329
|
+
}
|
|
4330
|
+
return {
|
|
4331
|
+
message: `${first.code}: ${first.message}`,
|
|
4332
|
+
file: first.file,
|
|
4333
|
+
line: first.line,
|
|
4334
|
+
column: first.column,
|
|
4335
|
+
category: inferBuildFailureCategory(`${first.code}: ${first.message}`)
|
|
4336
|
+
};
|
|
4337
|
+
}
|
|
4338
|
+
function extractWrapperBuildFailure(input) {
|
|
4339
|
+
if (!/^\s*npm ERR!|\bERR_PNPM_|^\s*error Command failed/m.test(input)) {
|
|
4340
|
+
return null;
|
|
4341
|
+
}
|
|
4342
|
+
const npmCommandMatch = input.match(/^\s*npm ERR!\s+.*?\bbuild:\s+`([^`]+)`/m);
|
|
4343
|
+
const genericCommandMatch = input.match(/^\s*.+?\s+build:\s+`([^`]+)`/m);
|
|
4344
|
+
const command = npmCommandMatch?.[1] ?? genericCommandMatch?.[1] ?? null;
|
|
4345
|
+
return {
|
|
4346
|
+
message: command ? `build script \`${command}\` failed` : "the build script failed",
|
|
4347
|
+
file: null,
|
|
4348
|
+
line: null,
|
|
4349
|
+
column: null,
|
|
4350
|
+
category: "wrapper"
|
|
4351
|
+
};
|
|
4352
|
+
}
|
|
4353
|
+
function buildFailureHeuristic(input) {
|
|
4354
|
+
if (input.trim().length === 0) {
|
|
4355
|
+
return null;
|
|
4356
|
+
}
|
|
4357
|
+
if (detectExplicitBuildSuccess(input)) {
|
|
4358
|
+
return "Build succeeded.";
|
|
4359
|
+
}
|
|
4360
|
+
const match = extractViteImportAnalysisBuildFailure(input) ?? extractWebpackBuildFailure(input) ?? extractEsbuildBuildFailure(input) ?? extractCargoBuildFailure(input) ?? extractCompilerStyleBuildFailure(input) ?? extractTscBuildFailure(input) ?? extractWrapperBuildFailure(input);
|
|
4361
|
+
if (!match) {
|
|
4362
|
+
return null;
|
|
4363
|
+
}
|
|
4364
|
+
return formatBuildFailureOutput(match);
|
|
4365
|
+
}
|
|
3382
4366
|
function applyHeuristicPolicy(policyName, input, detail) {
|
|
3383
4367
|
if (!policyName) {
|
|
3384
4368
|
return null;
|
|
@@ -3392,6 +4376,15 @@ function applyHeuristicPolicy(policyName, input, detail) {
|
|
|
3392
4376
|
if (policyName === "test-status") {
|
|
3393
4377
|
return testStatusHeuristic(input, detail);
|
|
3394
4378
|
}
|
|
4379
|
+
if (policyName === "typecheck-summary") {
|
|
4380
|
+
return typecheckSummaryHeuristic(input);
|
|
4381
|
+
}
|
|
4382
|
+
if (policyName === "lint-failures") {
|
|
4383
|
+
return lintFailuresHeuristic(input);
|
|
4384
|
+
}
|
|
4385
|
+
if (policyName === "build-failure") {
|
|
4386
|
+
return buildFailureHeuristic(input);
|
|
4387
|
+
}
|
|
3395
4388
|
return null;
|
|
3396
4389
|
}
|
|
3397
4390
|
|
|
@@ -3414,8 +4407,8 @@ function buildInsufficientSignalOutput(input) {
|
|
|
3414
4407
|
} else {
|
|
3415
4408
|
hint = "Hint: the captured output did not contain a clear answer for this preset.";
|
|
3416
4409
|
}
|
|
3417
|
-
|
|
3418
|
-
|
|
4410
|
+
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;
|
|
4411
|
+
return [INSUFFICIENT_SIGNAL_TEXT, hint, presetSuggestion].filter((value) => Boolean(value)).join("\n");
|
|
3419
4412
|
}
|
|
3420
4413
|
|
|
3421
4414
|
// src/core/run.ts
|
|
@@ -4079,6 +5072,138 @@ function escapeRegExp(value) {
|
|
|
4079
5072
|
function unique2(values) {
|
|
4080
5073
|
return [...new Set(values)];
|
|
4081
5074
|
}
|
|
5075
|
+
var genericBucketSearchTerms = /* @__PURE__ */ new Set([
|
|
5076
|
+
"runtimeerror",
|
|
5077
|
+
"typeerror",
|
|
5078
|
+
"error",
|
|
5079
|
+
"exception",
|
|
5080
|
+
"failed",
|
|
5081
|
+
"failure",
|
|
5082
|
+
"visible failure",
|
|
5083
|
+
"failing tests",
|
|
5084
|
+
"setup failures",
|
|
5085
|
+
"runtime failure",
|
|
5086
|
+
"assertion failed",
|
|
5087
|
+
"network",
|
|
5088
|
+
"permission",
|
|
5089
|
+
"configuration"
|
|
5090
|
+
]);
|
|
5091
|
+
function normalizeSearchTerm(value) {
|
|
5092
|
+
return value.replace(/^['"`]+|['"`]+$/g, "").trim();
|
|
5093
|
+
}
|
|
5094
|
+
function isHighSignalSearchTerm(term) {
|
|
5095
|
+
const normalized = normalizeSearchTerm(term);
|
|
5096
|
+
if (normalized.length < 4) {
|
|
5097
|
+
return false;
|
|
5098
|
+
}
|
|
5099
|
+
const lower = normalized.toLowerCase();
|
|
5100
|
+
if (genericBucketSearchTerms.has(lower)) {
|
|
5101
|
+
return false;
|
|
5102
|
+
}
|
|
5103
|
+
if (/^(runtime|type|assertion|network|permission|configuration)\b/i.test(normalized)) {
|
|
5104
|
+
return false;
|
|
5105
|
+
}
|
|
5106
|
+
return true;
|
|
5107
|
+
}
|
|
5108
|
+
function scoreSearchTerm(term) {
|
|
5109
|
+
const normalized = normalizeSearchTerm(term);
|
|
5110
|
+
let score = normalized.length;
|
|
5111
|
+
if (/^[A-Z][A-Z0-9_]{2,}$/.test(normalized)) {
|
|
5112
|
+
score += 80;
|
|
5113
|
+
}
|
|
5114
|
+
if (/^TS\d+$/.test(normalized)) {
|
|
5115
|
+
score += 70;
|
|
5116
|
+
}
|
|
5117
|
+
if (/^[45]\d\d\b/.test(normalized) || /\bHTTPError:\s*[45]\d\d\b/i.test(normalized)) {
|
|
5118
|
+
score += 60;
|
|
5119
|
+
}
|
|
5120
|
+
if (normalized.includes("/") || normalized.includes("\\")) {
|
|
5121
|
+
score += 50;
|
|
5122
|
+
}
|
|
5123
|
+
if (/\b[A-Za-z0-9_.-]+\.[A-Za-z0-9_.-]+\b/.test(normalized)) {
|
|
5124
|
+
score += 40;
|
|
5125
|
+
}
|
|
5126
|
+
if (/['"`]/.test(term)) {
|
|
5127
|
+
score += 30;
|
|
5128
|
+
}
|
|
5129
|
+
if (normalized.includes("::")) {
|
|
5130
|
+
score += 25;
|
|
5131
|
+
}
|
|
5132
|
+
return score;
|
|
5133
|
+
}
|
|
5134
|
+
function collectCandidateSearchTerms(value) {
|
|
5135
|
+
const candidates = [];
|
|
5136
|
+
const normalized = value.trim();
|
|
5137
|
+
if (!normalized) {
|
|
5138
|
+
return candidates;
|
|
5139
|
+
}
|
|
5140
|
+
for (const match of normalized.matchAll(/['"`]([^'"`]{4,})['"`]/g)) {
|
|
5141
|
+
candidates.push(match[1]);
|
|
5142
|
+
}
|
|
5143
|
+
for (const match of normalized.matchAll(/\b[A-Z][A-Z0-9_]{2,}\b/g)) {
|
|
5144
|
+
candidates.push(match[0]);
|
|
5145
|
+
}
|
|
5146
|
+
for (const match of normalized.matchAll(/\bTS\d+\b/g)) {
|
|
5147
|
+
candidates.push(match[0]);
|
|
5148
|
+
}
|
|
5149
|
+
for (const match of normalized.matchAll(/\bHTTPError:\s*[45]\d\d\b/gi)) {
|
|
5150
|
+
candidates.push(match[0]);
|
|
5151
|
+
}
|
|
5152
|
+
for (const match of normalized.matchAll(/\/[A-Za-z0-9_./:{}-]{4,}/g)) {
|
|
5153
|
+
candidates.push(match[0]);
|
|
5154
|
+
}
|
|
5155
|
+
for (const match of normalized.matchAll(/\b(?:[A-Za-z0-9_.-]+\/)+[A-Za-z0-9_.-]+\b/g)) {
|
|
5156
|
+
candidates.push(match[0]);
|
|
5157
|
+
}
|
|
5158
|
+
for (const match of normalized.matchAll(/\b[A-Za-z0-9_.-]+\.[A-Za-z0-9_.-]+\b/g)) {
|
|
5159
|
+
candidates.push(match[0]);
|
|
5160
|
+
}
|
|
5161
|
+
const detail = normalized.split(":").slice(1).join(":").trim();
|
|
5162
|
+
if (detail.length >= 8) {
|
|
5163
|
+
candidates.push(detail);
|
|
5164
|
+
}
|
|
5165
|
+
return candidates;
|
|
5166
|
+
}
|
|
5167
|
+
function extractBucketSearchTerms(args) {
|
|
5168
|
+
const sources = [
|
|
5169
|
+
args.bucket.root_cause,
|
|
5170
|
+
...args.bucket.evidence,
|
|
5171
|
+
...args.readTargets.filter((target) => target.bucket_index === args.bucket.bucket_index).flatMap((target) => [target.context_hint.search_hint ?? "", target.file])
|
|
5172
|
+
];
|
|
5173
|
+
const prioritized = unique2(
|
|
5174
|
+
sources.flatMap((value) => collectCandidateSearchTerms(value)).filter(isHighSignalSearchTerm)
|
|
5175
|
+
).sort((left, right) => {
|
|
5176
|
+
const delta = scoreSearchTerm(right) - scoreSearchTerm(left);
|
|
5177
|
+
if (delta !== 0) {
|
|
5178
|
+
return delta;
|
|
5179
|
+
}
|
|
5180
|
+
return left.localeCompare(right);
|
|
5181
|
+
});
|
|
5182
|
+
if (prioritized.length > 0) {
|
|
5183
|
+
return prioritized.slice(0, 6);
|
|
5184
|
+
}
|
|
5185
|
+
const fallbackTerms = unique2(
|
|
5186
|
+
[...args.bucket.evidence, args.bucket.root_cause].flatMap((value) => value.split(/->|:/).map((part) => normalizeSearchTerm(part))).filter(isHighSignalSearchTerm)
|
|
5187
|
+
);
|
|
5188
|
+
return fallbackTerms.slice(0, 4);
|
|
5189
|
+
}
|
|
5190
|
+
function clusterIndexes(indexes, maxGap = 12) {
|
|
5191
|
+
if (indexes.length === 0) {
|
|
5192
|
+
return [];
|
|
5193
|
+
}
|
|
5194
|
+
const clusters = [];
|
|
5195
|
+
let currentCluster = [indexes[0]];
|
|
5196
|
+
for (const index of indexes.slice(1)) {
|
|
5197
|
+
if (index - currentCluster[currentCluster.length - 1] <= maxGap) {
|
|
5198
|
+
currentCluster.push(index);
|
|
5199
|
+
continue;
|
|
5200
|
+
}
|
|
5201
|
+
clusters.push(currentCluster);
|
|
5202
|
+
currentCluster = [index];
|
|
5203
|
+
}
|
|
5204
|
+
clusters.push(currentCluster);
|
|
5205
|
+
return clusters;
|
|
5206
|
+
}
|
|
4082
5207
|
function buildLineWindows(args) {
|
|
4083
5208
|
const selected = /* @__PURE__ */ new Set();
|
|
4084
5209
|
for (const index of args.indexes) {
|
|
@@ -4094,6 +5219,12 @@ function buildLineWindows(args) {
|
|
|
4094
5219
|
}
|
|
4095
5220
|
return [...selected].sort((left, right) => left - right).map((index) => args.lines[index]);
|
|
4096
5221
|
}
|
|
5222
|
+
function buildPriorityLineGroup(args) {
|
|
5223
|
+
return unique2([
|
|
5224
|
+
...args.indexes.map((index) => args.lines[index]).filter(Boolean),
|
|
5225
|
+
...buildLineWindows(args)
|
|
5226
|
+
]);
|
|
5227
|
+
}
|
|
4097
5228
|
function collapseSelectedLines(args) {
|
|
4098
5229
|
if (args.lines.length === 0) {
|
|
4099
5230
|
return args.fallback();
|
|
@@ -4242,15 +5373,16 @@ function buildTestStatusRawSlice(args) {
|
|
|
4242
5373
|
) ? index : -1
|
|
4243
5374
|
).filter((index) => index >= 0);
|
|
4244
5375
|
const bucketGroups = args.contract.main_buckets.map((bucket) => {
|
|
4245
|
-
const bucketTerms =
|
|
4246
|
-
|
|
4247
|
-
|
|
5376
|
+
const bucketTerms = extractBucketSearchTerms({
|
|
5377
|
+
bucket,
|
|
5378
|
+
readTargets: args.contract.read_targets
|
|
5379
|
+
});
|
|
4248
5380
|
const indexes = lines.map(
|
|
4249
5381
|
(line, index) => bucketTerms.some((term) => new RegExp(escapeRegExp(term), "i").test(line)) ? index : -1
|
|
4250
5382
|
).filter((index) => index >= 0);
|
|
4251
5383
|
return unique2([
|
|
4252
5384
|
...indexes.map((index) => lines[index]).filter(Boolean),
|
|
4253
|
-
...
|
|
5385
|
+
...buildPriorityLineGroup({
|
|
4254
5386
|
lines,
|
|
4255
5387
|
indexes,
|
|
4256
5388
|
radius: 2,
|
|
@@ -4258,26 +5390,55 @@ function buildTestStatusRawSlice(args) {
|
|
|
4258
5390
|
})
|
|
4259
5391
|
]);
|
|
4260
5392
|
});
|
|
4261
|
-
const targetGroups = args.contract.read_targets.
|
|
4262
|
-
|
|
5393
|
+
const targetGroups = args.contract.read_targets.flatMap((target) => {
|
|
5394
|
+
const searchHintIndexes = findSearchHintIndexes({
|
|
4263
5395
|
lines,
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
|
|
4277
|
-
|
|
5396
|
+
searchHint: target.context_hint.search_hint
|
|
5397
|
+
});
|
|
5398
|
+
const fileIndexes = findReadTargetIndexes({
|
|
5399
|
+
lines,
|
|
5400
|
+
file: target.file,
|
|
5401
|
+
line: target.line,
|
|
5402
|
+
contextHint: target.context_hint
|
|
5403
|
+
});
|
|
5404
|
+
const radius = target.line === null ? 1 : 2;
|
|
5405
|
+
const maxLines = target.line === null ? 6 : 8;
|
|
5406
|
+
const groups = [
|
|
5407
|
+
searchHintIndexes.length > 0 ? buildPriorityLineGroup({
|
|
5408
|
+
lines,
|
|
5409
|
+
indexes: searchHintIndexes,
|
|
5410
|
+
radius,
|
|
5411
|
+
maxLines
|
|
5412
|
+
}) : null,
|
|
5413
|
+
fileIndexes.length > 0 ? buildPriorityLineGroup({
|
|
5414
|
+
lines,
|
|
5415
|
+
indexes: fileIndexes,
|
|
5416
|
+
radius,
|
|
5417
|
+
maxLines
|
|
5418
|
+
}) : null
|
|
5419
|
+
].filter((group) => group !== null && group.length > 0);
|
|
5420
|
+
if (groups.length > 0) {
|
|
5421
|
+
return groups;
|
|
5422
|
+
}
|
|
5423
|
+
return [
|
|
5424
|
+
buildPriorityLineGroup({
|
|
5425
|
+
lines,
|
|
5426
|
+
indexes: unique2([...searchHintIndexes, ...fileIndexes]),
|
|
5427
|
+
radius,
|
|
5428
|
+
maxLines
|
|
5429
|
+
})
|
|
5430
|
+
];
|
|
5431
|
+
});
|
|
5432
|
+
const failureHeaderIndexes = lines.map((line, index) => /\b(FAILED|ERROR)\b/.test(line) ? index : -1).filter((index) => index >= 0);
|
|
5433
|
+
const failureIndexes = (failureHeaderIndexes.length > 0 ? failureHeaderIndexes : lines.map((line, index) => /^E\s/.test(line) ? index : -1).filter((index) => index >= 0)).filter((index) => index >= 0);
|
|
5434
|
+
const failureHeaderGroups = clusterIndexes(failureIndexes).slice(0, 8).map(
|
|
5435
|
+
(cluster) => buildPriorityLineGroup({
|
|
5436
|
+
lines,
|
|
5437
|
+
indexes: cluster,
|
|
5438
|
+
radius: 1,
|
|
5439
|
+
maxLines: 8
|
|
4278
5440
|
})
|
|
4279
|
-
);
|
|
4280
|
-
const failureIndexes = lines.map((line, index) => /\b(FAILED|ERROR)\b/.test(line) || /^E\s/.test(line) ? index : -1).filter((index) => index >= 0);
|
|
5441
|
+
).filter((group) => group.length > 0);
|
|
4281
5442
|
const selected = collapseSelectedLineGroups({
|
|
4282
5443
|
groups: [
|
|
4283
5444
|
...targetGroups,
|
|
@@ -4291,12 +5452,14 @@ function buildTestStatusRawSlice(args) {
|
|
|
4291
5452
|
})
|
|
4292
5453
|
]),
|
|
4293
5454
|
...bucketGroups,
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
|
|
4297
|
-
|
|
4298
|
-
|
|
4299
|
-
|
|
5455
|
+
...failureHeaderGroups.length > 0 ? failureHeaderGroups : [
|
|
5456
|
+
buildLineWindows({
|
|
5457
|
+
lines,
|
|
5458
|
+
indexes: failureIndexes,
|
|
5459
|
+
radius: 1,
|
|
5460
|
+
maxLines: 24
|
|
5461
|
+
})
|
|
5462
|
+
]
|
|
4300
5463
|
],
|
|
4301
5464
|
maxInputChars: args.config.maxInputChars,
|
|
4302
5465
|
fallback: () => truncateInput(args.input, {
|
|
@@ -4437,7 +5600,8 @@ function withInsufficientHint(args) {
|
|
|
4437
5600
|
return buildInsufficientSignalOutput({
|
|
4438
5601
|
presetName: args.request.presetName,
|
|
4439
5602
|
originalLength: args.prepared.meta.originalLength,
|
|
4440
|
-
truncatedApplied: args.prepared.meta.truncatedApplied
|
|
5603
|
+
truncatedApplied: args.prepared.meta.truncatedApplied,
|
|
5604
|
+
recognizedRunner: detectTestRunner(args.prepared.redacted)
|
|
4441
5605
|
});
|
|
4442
5606
|
}
|
|
4443
5607
|
async function generateWithRetry(args) {
|
|
@@ -4497,6 +5661,38 @@ function renderTestStatusDecisionOutput(args) {
|
|
|
4497
5661
|
return args.decision.standardText;
|
|
4498
5662
|
}
|
|
4499
5663
|
function buildTestStatusProviderFailureDecision(args) {
|
|
5664
|
+
const concreteReadTarget = args.baseDecision.contract.read_targets.find(
|
|
5665
|
+
(target) => Boolean(target.file)
|
|
5666
|
+
);
|
|
5667
|
+
const hasUnknownBucket = args.baseDecision.contract.main_buckets.some(
|
|
5668
|
+
(bucket) => bucket.root_cause.startsWith("unknown ")
|
|
5669
|
+
);
|
|
5670
|
+
if (concreteReadTarget && !hasUnknownBucket) {
|
|
5671
|
+
return buildTestStatusDiagnoseContract({
|
|
5672
|
+
input: args.input,
|
|
5673
|
+
analysis: args.analysis,
|
|
5674
|
+
resolvedTests: args.baseDecision.contract.resolved_tests,
|
|
5675
|
+
remainingTests: args.baseDecision.contract.remaining_tests,
|
|
5676
|
+
contractOverrides: {
|
|
5677
|
+
...args.baseDecision.contract,
|
|
5678
|
+
diagnosis_complete: false,
|
|
5679
|
+
raw_needed: false,
|
|
5680
|
+
additional_source_read_likely_low_value: false,
|
|
5681
|
+
read_raw_only_if: null,
|
|
5682
|
+
decision: "read_source",
|
|
5683
|
+
provider_used: true,
|
|
5684
|
+
provider_confidence: null,
|
|
5685
|
+
provider_failed: true,
|
|
5686
|
+
raw_slice_used: args.rawSliceUsed,
|
|
5687
|
+
raw_slice_strategy: args.rawSliceStrategy,
|
|
5688
|
+
next_best_action: {
|
|
5689
|
+
code: "read_source_for_bucket",
|
|
5690
|
+
bucket_index: args.baseDecision.contract.dominant_blocker_bucket_index ?? concreteReadTarget.bucket_index,
|
|
5691
|
+
note: `Provider follow-up failed (${args.reason}). The heuristic anchor is concrete enough to inspect source for the current bucket before reading raw traceback.`
|
|
5692
|
+
}
|
|
5693
|
+
}
|
|
5694
|
+
});
|
|
5695
|
+
}
|
|
4500
5696
|
const shouldZoomFirst = args.request.detail !== "verbose";
|
|
4501
5697
|
return buildTestStatusDiagnoseContract({
|
|
4502
5698
|
input: args.input,
|
|
@@ -4523,7 +5719,7 @@ function buildTestStatusProviderFailureDecision(args) {
|
|
|
4523
5719
|
}
|
|
4524
5720
|
});
|
|
4525
5721
|
}
|
|
4526
|
-
async function
|
|
5722
|
+
async function runSiftCore(request, recorder) {
|
|
4527
5723
|
const prepared = prepareInput(request.stdin, request.config.input);
|
|
4528
5724
|
const heuristicInput = prepared.redacted;
|
|
4529
5725
|
const heuristicInputTruncated = false;
|
|
@@ -4609,6 +5805,7 @@ async function runSift(request) {
|
|
|
4609
5805
|
finalOutput
|
|
4610
5806
|
});
|
|
4611
5807
|
}
|
|
5808
|
+
recorder?.heuristic();
|
|
4612
5809
|
return finalOutput;
|
|
4613
5810
|
}
|
|
4614
5811
|
if (testStatusDecision && testStatusAnalysis) {
|
|
@@ -4708,6 +5905,7 @@ async function runSift(request) {
|
|
|
4708
5905
|
providerInputChars: providerPrepared2.truncated.length,
|
|
4709
5906
|
providerOutputChars: result.text.length
|
|
4710
5907
|
});
|
|
5908
|
+
recorder?.provider(result.usage);
|
|
4711
5909
|
return finalOutput;
|
|
4712
5910
|
} catch (error) {
|
|
4713
5911
|
const reason = error instanceof Error ? error.message : "unknown_error";
|
|
@@ -4742,6 +5940,7 @@ async function runSift(request) {
|
|
|
4742
5940
|
rawSliceChars: rawSlice.text.length,
|
|
4743
5941
|
providerInputChars: providerPrepared2.truncated.length
|
|
4744
5942
|
});
|
|
5943
|
+
recorder?.fallback();
|
|
4745
5944
|
return finalOutput;
|
|
4746
5945
|
}
|
|
4747
5946
|
}
|
|
@@ -4798,6 +5997,7 @@ async function runSift(request) {
|
|
|
4798
5997
|
})) {
|
|
4799
5998
|
throw new Error("Model output rejected by quality gate");
|
|
4800
5999
|
}
|
|
6000
|
+
recorder?.provider(result.usage);
|
|
4801
6001
|
return withInsufficientHint({
|
|
4802
6002
|
output: normalizeOutput(result.text, providerPrompt.responseMode),
|
|
4803
6003
|
request,
|
|
@@ -4805,6 +6005,7 @@ async function runSift(request) {
|
|
|
4805
6005
|
});
|
|
4806
6006
|
} catch (error) {
|
|
4807
6007
|
const reason = error instanceof Error ? error.message : "unknown_error";
|
|
6008
|
+
recorder?.fallback();
|
|
4808
6009
|
return withInsufficientHint({
|
|
4809
6010
|
output: buildFallbackOutput({
|
|
4810
6011
|
format: request.format,
|
|
@@ -4818,6 +6019,72 @@ async function runSift(request) {
|
|
|
4818
6019
|
});
|
|
4819
6020
|
}
|
|
4820
6021
|
}
|
|
6022
|
+
async function runSift(request) {
|
|
6023
|
+
return runSiftCore(request);
|
|
6024
|
+
}
|
|
6025
|
+
async function runSiftWithStats(request) {
|
|
6026
|
+
if (request.dryRun) {
|
|
6027
|
+
return {
|
|
6028
|
+
output: await runSiftCore(request),
|
|
6029
|
+
stats: null
|
|
6030
|
+
};
|
|
6031
|
+
}
|
|
6032
|
+
const startedAt = Date.now();
|
|
6033
|
+
let layer = "fallback";
|
|
6034
|
+
let providerCalled = false;
|
|
6035
|
+
let totalTokens = null;
|
|
6036
|
+
const output = await runSiftCore(request, {
|
|
6037
|
+
heuristic() {
|
|
6038
|
+
layer = "heuristic";
|
|
6039
|
+
providerCalled = false;
|
|
6040
|
+
totalTokens = null;
|
|
6041
|
+
},
|
|
6042
|
+
provider(usage) {
|
|
6043
|
+
layer = "provider";
|
|
6044
|
+
providerCalled = true;
|
|
6045
|
+
totalTokens = usage?.totalTokens ?? null;
|
|
6046
|
+
},
|
|
6047
|
+
fallback() {
|
|
6048
|
+
layer = "fallback";
|
|
6049
|
+
providerCalled = true;
|
|
6050
|
+
totalTokens = null;
|
|
6051
|
+
}
|
|
6052
|
+
});
|
|
6053
|
+
return {
|
|
6054
|
+
output,
|
|
6055
|
+
stats: {
|
|
6056
|
+
layer,
|
|
6057
|
+
providerCalled,
|
|
6058
|
+
totalTokens,
|
|
6059
|
+
durationMs: Date.now() - startedAt,
|
|
6060
|
+
presetName: request.presetName
|
|
6061
|
+
}
|
|
6062
|
+
};
|
|
6063
|
+
}
|
|
6064
|
+
|
|
6065
|
+
// src/core/stats.ts
|
|
6066
|
+
import pc2 from "picocolors";
|
|
6067
|
+
function formatDuration(durationMs) {
|
|
6068
|
+
return durationMs >= 1e3 ? `${(durationMs / 1e3).toFixed(1)}s` : `${durationMs}ms`;
|
|
6069
|
+
}
|
|
6070
|
+
function formatStatsFooter(stats) {
|
|
6071
|
+
const duration = formatDuration(stats.durationMs);
|
|
6072
|
+
if (stats.layer === "heuristic") {
|
|
6073
|
+
return `[sift: heuristic \u2022 LLM skipped \u2022 summary ${duration}]`;
|
|
6074
|
+
}
|
|
6075
|
+
if (stats.layer === "provider") {
|
|
6076
|
+
const tokenSegment = stats.totalTokens !== null ? ` \u2022 ${stats.totalTokens} tokens` : "";
|
|
6077
|
+
return `[sift: provider \u2022 LLM used${tokenSegment} \u2022 summary ${duration}]`;
|
|
6078
|
+
}
|
|
6079
|
+
return `[sift: fallback \u2022 provider failed \u2022 summary ${duration}]`;
|
|
6080
|
+
}
|
|
6081
|
+
function emitStatsFooter(args) {
|
|
6082
|
+
if (args.quiet || !args.stats || !process.stderr.isTTY) {
|
|
6083
|
+
return;
|
|
6084
|
+
}
|
|
6085
|
+
process.stderr.write(`${pc2.dim(formatStatsFooter(args.stats))}
|
|
6086
|
+
`);
|
|
6087
|
+
}
|
|
4821
6088
|
|
|
4822
6089
|
// src/core/testStatusState.ts
|
|
4823
6090
|
import fs from "fs";
|
|
@@ -5519,7 +6786,7 @@ async function runExec(request) {
|
|
|
5519
6786
|
const previousCachedRun = shouldCacheTestStatusBase ? tryReadCachedTestStatusRun() : null;
|
|
5520
6787
|
if (request.config.runtime.verbose) {
|
|
5521
6788
|
process.stderr.write(
|
|
5522
|
-
`${
|
|
6789
|
+
`${pc3.dim("sift")} exec mode=${hasShellCommand ? "shell" : "argv"} command=${commandPreview}
|
|
5523
6790
|
`
|
|
5524
6791
|
);
|
|
5525
6792
|
}
|
|
@@ -5548,7 +6815,7 @@ async function runExec(request) {
|
|
|
5548
6815
|
}
|
|
5549
6816
|
bypassed = true;
|
|
5550
6817
|
if (request.config.runtime.verbose) {
|
|
5551
|
-
process.stderr.write(`${
|
|
6818
|
+
process.stderr.write(`${pc3.dim("sift")} bypass=interactive-prompt
|
|
5552
6819
|
`);
|
|
5553
6820
|
}
|
|
5554
6821
|
process.stderr.write(capture.render());
|
|
@@ -5577,15 +6844,16 @@ async function runExec(request) {
|
|
|
5577
6844
|
const shouldCacheTestStatus = shouldCacheTestStatusBase && !useWatchFlow;
|
|
5578
6845
|
if (request.config.runtime.verbose) {
|
|
5579
6846
|
process.stderr.write(
|
|
5580
|
-
`${
|
|
6847
|
+
`${pc3.dim("sift")} child_exit=${exitCode} captured_chars=${capture.getTotalChars()} capture_truncated=${capture.wasTruncated()}
|
|
5581
6848
|
`
|
|
5582
6849
|
);
|
|
5583
6850
|
}
|
|
5584
6851
|
if (autoWatchDetected) {
|
|
5585
|
-
process.stderr.write(`${
|
|
6852
|
+
process.stderr.write(`${pc3.dim("sift")} auto-watch=detected
|
|
5586
6853
|
`);
|
|
5587
6854
|
}
|
|
5588
6855
|
if (!bypassed) {
|
|
6856
|
+
const reductionStartedAt = Date.now();
|
|
5589
6857
|
if (request.showRaw && capturedOutput.length > 0) {
|
|
5590
6858
|
process.stderr.write(capturedOutput);
|
|
5591
6859
|
if (!capturedOutput.endsWith("\n")) {
|
|
@@ -5600,12 +6868,22 @@ async function runExec(request) {
|
|
|
5600
6868
|
if (execSuccessShortcut && !request.dryRun) {
|
|
5601
6869
|
if (request.config.runtime.verbose) {
|
|
5602
6870
|
process.stderr.write(
|
|
5603
|
-
`${
|
|
6871
|
+
`${pc3.dim("sift")} exec_shortcut=${request.presetName}
|
|
5604
6872
|
`
|
|
5605
6873
|
);
|
|
5606
6874
|
}
|
|
5607
6875
|
process.stdout.write(`${execSuccessShortcut}
|
|
5608
6876
|
`);
|
|
6877
|
+
emitStatsFooter({
|
|
6878
|
+
stats: {
|
|
6879
|
+
layer: "heuristic",
|
|
6880
|
+
providerCalled: false,
|
|
6881
|
+
totalTokens: null,
|
|
6882
|
+
durationMs: Date.now() - reductionStartedAt,
|
|
6883
|
+
presetName: request.presetName
|
|
6884
|
+
},
|
|
6885
|
+
quiet: Boolean(request.quiet)
|
|
6886
|
+
});
|
|
5609
6887
|
return exitCode;
|
|
5610
6888
|
}
|
|
5611
6889
|
if (useWatchFlow) {
|
|
@@ -5618,7 +6896,8 @@ async function runExec(request) {
|
|
|
5618
6896
|
presetName: request.presetName,
|
|
5619
6897
|
originalLength: capture.getTotalChars(),
|
|
5620
6898
|
truncatedApplied: capture.wasTruncated(),
|
|
5621
|
-
exitCode
|
|
6899
|
+
exitCode,
|
|
6900
|
+
recognizedRunner: detectTestRunner(capturedOutput)
|
|
5622
6901
|
});
|
|
5623
6902
|
}
|
|
5624
6903
|
process.stdout.write(`${output2}
|
|
@@ -5646,7 +6925,7 @@ async function runExec(request) {
|
|
|
5646
6925
|
previous: previousCachedRun,
|
|
5647
6926
|
current: currentCachedRun
|
|
5648
6927
|
}) : null;
|
|
5649
|
-
|
|
6928
|
+
const result = await runSiftWithStats({
|
|
5650
6929
|
...request,
|
|
5651
6930
|
stdin: capturedOutput,
|
|
5652
6931
|
analysisContext: request.skipCacheWrite && request.presetName === "test-status" ? [
|
|
@@ -5665,13 +6944,15 @@ async function runExec(request) {
|
|
|
5665
6944
|
)
|
|
5666
6945
|
} : request.testStatusContext
|
|
5667
6946
|
});
|
|
6947
|
+
let output = result.output;
|
|
5668
6948
|
if (shouldCacheTestStatus) {
|
|
5669
6949
|
if (isInsufficientSignalOutput(output)) {
|
|
5670
6950
|
output = buildInsufficientSignalOutput({
|
|
5671
6951
|
presetName: request.presetName,
|
|
5672
6952
|
originalLength: capture.getTotalChars(),
|
|
5673
6953
|
truncatedApplied: capture.wasTruncated(),
|
|
5674
|
-
exitCode
|
|
6954
|
+
exitCode,
|
|
6955
|
+
recognizedRunner: detectTestRunner(capturedOutput)
|
|
5675
6956
|
});
|
|
5676
6957
|
}
|
|
5677
6958
|
if (request.diff && !request.dryRun && previousCachedRun && currentCachedRun) {
|
|
@@ -5704,7 +6985,7 @@ ${output}`;
|
|
|
5704
6985
|
} catch (error) {
|
|
5705
6986
|
if (request.config.runtime.verbose) {
|
|
5706
6987
|
const reason = error instanceof Error ? error.message : "unknown_error";
|
|
5707
|
-
process.stderr.write(`${
|
|
6988
|
+
process.stderr.write(`${pc3.dim("sift")} cache_write=failed reason=${reason}
|
|
5708
6989
|
`);
|
|
5709
6990
|
}
|
|
5710
6991
|
}
|
|
@@ -5714,11 +6995,16 @@ ${output}`;
|
|
|
5714
6995
|
presetName: request.presetName,
|
|
5715
6996
|
originalLength: capture.getTotalChars(),
|
|
5716
6997
|
truncatedApplied: capture.wasTruncated(),
|
|
5717
|
-
exitCode
|
|
6998
|
+
exitCode,
|
|
6999
|
+
recognizedRunner: detectTestRunner(capturedOutput)
|
|
5718
7000
|
});
|
|
5719
7001
|
}
|
|
5720
7002
|
process.stdout.write(`${output}
|
|
5721
7003
|
`);
|
|
7004
|
+
emitStatsFooter({
|
|
7005
|
+
stats: result.stats,
|
|
7006
|
+
quiet: Boolean(request.quiet)
|
|
7007
|
+
});
|
|
5722
7008
|
if (request.failOn && !request.dryRun && exitCode === 0 && supportsFailOnPreset(request.presetName) && evaluateGate({
|
|
5723
7009
|
presetName: request.presetName,
|
|
5724
7010
|
output
|
|
@@ -6078,5 +7364,6 @@ export {
|
|
|
6078
7364
|
normalizeChildExitCode,
|
|
6079
7365
|
resolveConfig,
|
|
6080
7366
|
runExec,
|
|
6081
|
-
runSift
|
|
7367
|
+
runSift,
|
|
7368
|
+
runSiftWithStats
|
|
6082
7369
|
};
|